/**
 * `runProfileMatrix` — the missing keystone between `runAgentMatrix` and the
 * backend-integrity guard.
 *
 * The gap it closes: `runAgentMatrix` is a topology-opaque scheduler whose
 * cells return a bare `{ output, verdict, costUsd }` — no `tokenUsage`, not a
 * `RunRecord`. `assertRealBackend` / `summarizeBackendIntegrity` key on
 * `RunRecord.tokenUsage`, so they cannot run on a raw matrix result. Every
 * consumer therefore hand-writes the same bridge: fan a profile × scenario
 * cartesian, call dispatch, fabricate a `RunRecord` with token usage, thread it
 * back, run the integrity guard. That hand-rolled bridge is exactly the pile of
 * bespoke `eval:*` scripts the adoption skills keep trying (and failing) to
 * forbid.
 *
 * `runProfileMatrix` IS that bridge, once:
 *
 *   - axis 3 (PROFILE) = `profiles: AgentProfile[]`
 *   - axis 1 (PERSONA/SCENARIO) = `scenarios: Scenario[]` (each scenario carries
 *     its persona; `personaOf` groups them for the `byPersona` pivot)
 *   - the scoring axis = `judges`
 *
 * It runs `runCampaign` once per profile (reusing its seeds, reps, bootstrap
 * CIs, resumability, and the `LabeledScenarioStore` capture flywheel), maps
 * every cell to a validated `RunRecord` carrying the real `tokenUsage` the
 * dispatch reported via `ctx.cost.observeTokens`, and runs `assertRealBackend`
 * BY CONSTRUCTION before returning — so a stub-backend run fails loudly instead
 * of reporting a clean 0/N leaderboard.
 *
 * Dispatch contract: a dispatch that calls an LLM MUST report usage via
 * `ctx.cost.observeTokens({ input, output })` (and cost via `ctx.cost.observe`).
 * A dispatch that reports zero tokens is indistinguishable from a stub and the
 * integrity guard treats it as one.
 */
import { type AgentProfile } from '../../agent-profile';
import { AgentEvalError } from '../../errors';
import { type BackendIntegrityReport } from '../../integrity/backend-integrity';
import { type RunRecord, type RunSplitTag } from '../../run-record';
import type { CampaignStorage } from '../storage';
import type { CampaignResult, DispatchContext, JudgeConfig, LabeledScenarioSource, LabeledScenarioStore, Scenario } from '../types';
/** Thrown when the matrix is misconfigured (no profiles, a profile whose model
 *  lacks a snapshot version, etc.). Distinct from `BackendIntegrityError`,
 *  which signals a stub backend at run time. */
export declare class ProfileMatrixError extends AgentEvalError {
    constructor(message: string);
}
/** Dispatch for one cell: render `profile` against `scenario`, returning the
 *  artifact the judges score. Report LLM usage via `ctx.cost.observeTokens`
 *  and `ctx.cost.observe` — the integrity guard depends on it. */
export type ProfileDispatchFn<TScenario extends Scenario, TArtifact> = (profile: AgentProfile, scenario: TScenario, ctx: DispatchContext) => Promise<TArtifact>;
export interface RunProfileMatrixOptions<TScenario extends Scenario, TArtifact> {
    /** Axis 3 — the agent-under-test configurations. Each is one column. */
    profiles: AgentProfile[];
    /** Axis 1 — the persona/scenario corpus, run against every profile. */
    scenarios: TScenario[];
    /** Renders one (profile, scenario) cell. */
    dispatch: ProfileDispatchFn<TScenario, TArtifact>;
    /** The scoring axis. */
    judges?: JudgeConfig<TArtifact, TScenario>[];
    /** Where each profile's campaign writes artifacts/traces. One subdir per
     *  profile. */
    runDir: string;
    /** Git SHA the harness ran from — stamped onto every RunRecord (mandatory
     *  for paper-grade records). */
    commitSha: string;
    /** Logical experiment id shared across the whole matrix so the promotion
     *  gate can pair profiles on matched scenarios. Default: a hash of the
     *  profile + scenario ids. */
    experimentId?: string;
    /** Which split these runs belong to. Default `'search'`. */
    splitTag?: RunSplitTag;
    /** Replicates per (profile, scenario) cell for CI bands. Default 1. */
    reps?: number;
    /** Campaign seed (per profile). Default 42. */
    seed?: number;
    /**
     * Backend-integrity posture, enforced AFTER the matrix completes:
     *   - `'assert'` (default) — throw `BackendIntegrityError` if the run was a
     *     stub (and, with `allowMixed:false`, if it was mixed).
     *   - `'warn'` — log the verdict but never throw.
     *   - `'off'` — skip the guard entirely (only for offline/replay analysis).
     */
    integrity?: 'assert' | 'warn' | 'off';
    /** Forwarded to `assertRealBackend`. Default true (tolerate partial 429
     *  cascades); set false for strict CI gates. */
    allowMixed?: boolean;
    /** Max concurrent cells WITHIN each profile's campaign. Default 2.
     *  Profiles run sequentially so the cost ceiling is honored deterministically. */
    maxConcurrency?: number;
    /** Cumulative USD cap per profile campaign. */
    costCeiling?: number;
    /** Capture flywheel — forwarded to each campaign. */
    labeledStore?: LabeledScenarioStore | 'off';
    captureSource?: LabeledScenarioSource;
    /** Storage backend. Default `fsCampaignStorage`. Pass
     *  `inMemoryCampaignStorage()` for edge/CF-Worker/test runs. */
    storage?: CampaignStorage;
    /** Test seam — override the wall clock. */
    now?: () => Date;
    /** Optional persona key per scenario — drives the `byPersona` pivot. When
     *  unset, `byPersona` is omitted. */
    personaOf?: (scenario: TScenario) => string;
    /** Validate every produced RunRecord with `validateRunRecord` (fail-loud).
     *  Default true — catches bad model snapshots and non-finite judge dims at
     *  the boundary instead of letting them poison downstream analysis. */
    validate?: boolean;
    /** Corpus-by-default: derive the trajectory text (`prompt` + `completion`)
     *  for each cell from its artifact + scenario. When set, every produced
     *  record carries `prompt`/`completion` (a `CorpusRecord`) so the run's
     *  graded trajectories can be appended to the durable RL corpus with no
     *  side-channel — `appendToCorpus(result.records, path)`. Fail-soft: a
     *  throwing or undefined-returning extractor just omits the text. */
    corpusText?: (artifact: TArtifact, scenario: TScenario) => {
        prompt: string;
        completion: string;
    } | undefined;
}
export interface ProfileSummary {
    profileId: string;
    profileHash: string;
    model: string;
    /** RunRecords produced for this profile (= scenarios × reps). */
    records: number;
    /** Mean composite across this profile's records. */
    meanComposite: number;
    totalCostUsd: number;
    /** Per-profile integrity verdict — surfaces a single profile that ran stub
     *  even when the matrix as a whole looks real. */
    integrity: BackendIntegrityReport;
}
export interface ScenarioRollup {
    meanComposite: number;
    n: number;
}
export interface RunProfileMatrixResult<TArtifact, TScenario extends Scenario> {
    matrixId: string;
    experimentId: string;
    /** One RunRecord per (profile, scenario, rep) cell — the integrity-checked,
     *  paper-grade output. Feed straight into `analyzeRuns`, `HeldOutGate`,
     *  scorecards, the hosted wire format. */
    records: RunRecord[];
    byProfile: Record<string, ProfileSummary>;
    byScenario: Record<string, ScenarioRollup>;
    /** Present only when `personaOf` was supplied. */
    byPersona?: Record<string, ScenarioRollup>;
    /** Whole-matrix integrity report (the one `integrity:'assert'` enforces). */
    integrity: BackendIntegrityReport;
    /** The raw per-profile campaign results, keyed by profile id. */
    campaigns: Record<string, CampaignResult<TArtifact, TScenario>>;
}
export declare function runProfileMatrix<TScenario extends Scenario, TArtifact>(opts: RunProfileMatrixOptions<TScenario, TArtifact>): Promise<RunProfileMatrixResult<TArtifact, TScenario>>;
//# sourceMappingURL=run-profile-matrix.d.ts.map