/**
 * Simulator fidelity — score a user SIMULATOR's realism against real-user
 * trace distributions.
 *
 * Synthetic-persona evals (`PersonaConfig`-driven canonical evals, fuzz
 * user-simulator objectives) stand in for real users in most of the numbers
 * we publish. The standing threat is the Sim2Real gap: a simulator that is
 * distributionally unlike production creates "easy mode" and silently
 * inflates every score built on it. This module measures that gap from the
 * SAME artifact both sides already produce — `RunRecord`s — so no new
 * capture pipeline is needed:
 *
 *   - `simFidelityReport` — per-feature Jensen-Shannon divergence between
 *     simulated and production record distributions, collapsed into a
 *     fidelity coefficient in [0,1].
 *   - `easyModeCheck` — the headline academic failure mode (sim inflates
 *     pass-rate over production) as its own named artifact.
 *
 * Every synthetic-persona eval result should publish its fidelity
 * coefficient alongside the score — a number from an unrepresentative
 * simulator is an unlabeled estimate. Wire-in points:
 *
 *   - canonical persona evals: pass the campaign's `RunRecord`s as
 *     `simulated` and intake-adapter output (`contract/intake`: OTel spans,
 *     feedback tables, coding-agent sessions) as `production`
 *   - the fuzz user-sim objective: use `1 - report.fidelity` as a realism
 *     penalty when searching over generated personas
 *   - the durable corpus (`./corpus`): both sides read straight from
 *     `readCorpus` — tag sim vs production by `experimentId`
 */
import type { RunRecord } from '../run-record';
/** Extracts a flat behavioral feature map from one record. `string` values
 *  are categorical, `number` values are quantile-bucketed over the union of
 *  both sides, `null` means the feature is absent on this record and is
 *  counted explicitly as its own category (never silently dropped). */
export type BehaviorFeatures = (record: RunRecord) => Record<string, string | number | null>;
/** Reserved histogram category for `null` feature values. A capture-rate
 *  difference (one side instruments a signal, the other does not) registers
 *  as divergence by design: a simulator that produces no tool traces is not
 *  representative of production that does. */
export declare const ABSENT_CATEGORY = "(absent)";
/** Minimum non-null observations PER SIDE for a feature to enter the
 *  fidelity mean. Below this the JSD estimate is sampling noise. */
export declare const DEFAULT_MIN_N_PER_FEATURE = 20;
/** Quantile buckets used to discretize numeric features. Quartiles balance
 *  resolution against per-bucket sample size at the default minN. */
export declare const DEFAULT_QUANTILE_BUCKETS = 4;
/** Fidelity at or above this → 'representative'; below → 'skewed'.
 *  1 − 0.8 = mean JSD 0.2 ≈ distributions that mostly overlap with one
 *  clearly shifted mode — the point where per-feature shifts start changing
 *  which failure classes an eval can even observe. */
export declare const REPRESENTATIVE_MIN_FIDELITY = 0.8;
/**
 * Default feature set — ONLY fields verified present on both simulated and
 * production records:
 *
 *   - `score`, `wall_ms`, `output_tokens` — mandatory per the `RunRecord`
 *     validator (non-finite values read as absent rather than poisoning a
 *     bucket).
 *   - `failure_class` — optional taxonomy field; absent counted explicitly.
 *   - `turn_count`, `tool_errors`, `tool_error_recovery` — derived from the
 *     `outcome.raw` counters the intake adapters and eval harnesses write
 *     (`turns_completed`, `assistant_messages`, `tool_errors`,
 *     `turns_aborted`); absent on records whose producer did not capture
 *     them, counted explicitly.
 *   - `completion_length` — from the optional `CorpusRecord` trajectory
 *     text; the message-length proxy when records come from the corpus.
 *
 * `RunRecord` carries event COUNTS, not event ordering, so
 * `tool_error_recovery` is a counts-only derivation: errors occurred and the
 * run still completed cleanly ('recovered') vs aborted or classified as a
 * failure ('unrecovered') — not a literal error→retry sequence check.
 */
export declare const defaultBehaviorFeatures: BehaviorFeatures;
/**
 * Jensen-Shannon divergence between two categorical histograms (raw counts;
 * normalized internally). Log base 2 → bounded [0,1]: 0 = identical
 * distributions, 1 = disjoint support. Symmetric, defined even where the
 * supports differ — exactly the regime sim-vs-production comparison lives in.
 * Throws on zero-mass or negative/non-finite counts: an empty histogram has
 * no distribution and a silent 0 would read as "perfectly representative".
 */
export declare function jsDivergence(p: Record<string, number>, q: Record<string, number>): number;
/**
 * Deterministic quantile edges over a value set (the UNION of both sides, so
 * sim and production land in the same buckets). Linear interpolation between
 * order statistics; duplicate edges from heavy ties collapse into fewer,
 * wider buckets. Returns `bucketCount - 1` edges before deduplication.
 */
export declare function quantileEdges(values: number[], bucketCount?: number): number[];
/** Stable half-open bucket label for a value against quantile edges:
 *  `[-inf,e0)`, `[e0,e1)`, …, `[eLast,+inf)`. */
export declare function bucketLabel(value: number, edges: number[]): string;
export interface FeatureShift {
    /** Category label (a string value, a numeric bucket, or `ABSENT_CATEGORY`). */
    value: string;
    /** Probability of this category among ALL simulated records (nulls included
     *  via `ABSENT_CATEGORY`, so each side's shifts sum to 1). */
    pSim: number;
    /** Probability among ALL production records. */
    pProd: number;
}
export interface FeatureDivergence {
    feature: string;
    /** Jensen-Shannon divergence in [0,1] for this feature. */
    divergence: number;
    /** Largest |pSim − pProd| categories, descending — where the sim deviates. */
    topShifts: FeatureShift[];
    /** Non-null observations on the simulated side. */
    nSim: number;
    /** Non-null observations on the production side. */
    nProd: number;
}
export type FidelityVerdict = 'representative' | 'skewed' | 'insufficient-data';
export interface FidelityReport {
    perDimension: FeatureDivergence[];
    /** 1 − mean divergence over features with sufficient data. NaN when the
     *  verdict is 'insufficient-data' — a 0 would read as "maximally skewed"
     *  and silently poison downstream aggregation; check `verdict` first. */
    fidelity: number;
    /** Features excluded because either side had fewer than `minNPerFeature`
     *  non-null observations. Named, never silently dropped. */
    insufficientData: string[];
    /** 'representative' when fidelity >= REPRESENTATIVE_MIN_FIDELITY (0.8),
     *  'skewed' below, 'insufficient-data' when no feature met minN. */
    verdict: FidelityVerdict;
}
export interface SimFidelityOptions {
    /** Feature extractor. Defaults to `defaultBehaviorFeatures`. */
    features?: BehaviorFeatures;
    /** Minimum non-null observations per side per feature. Default 20. */
    minNPerFeature?: number;
}
/**
 * Compare a simulator's RunRecords against production RunRecords, feature by
 * feature. Numeric features are bucketed by deterministic quantiles of the
 * union; nulls count as an explicit `ABSENT_CATEGORY`. Throws on empty
 * inputs — "no records" is a wiring error, not a distribution.
 */
export declare function simFidelityReport(simulated: RunRecord[], production: RunRecord[], opts?: SimFidelityOptions): FidelityReport;
export interface EasyModeOptions {
    /** A run passes when its score (holdout, else search) >= this. Default 0.5
     *  — matches the pass-threshold convention across the rl/ primitives. */
    passThreshold?: number;
    /** Pass-rate gap above which the sim is flagged inflated. Default 0.1 —
     *  a 10-point inflation is enough to flip most promotion gates. */
    inflationTolerance?: number;
}
export interface EasyModeReport {
    simPassRate: number;
    prodPassRate: number;
    /** simPassRate − prodPassRate. Positive = the simulator is easier than reality. */
    gap: number;
    /** True when gap > inflationTolerance: numbers measured against this
     *  simulator overstate production performance. */
    inflated: boolean;
}
/**
 * The headline simulator failure mode as its own named artifact: a simulator
 * that creates "easy mode" inflates pass-rate relative to production, and
 * every score measured against it overstates reality. Throws on empty inputs
 * and on records carrying neither score — a silently-skipped record would
 * bias the very rate this check exists to keep honest.
 */
export declare function easyModeCheck(simulated: RunRecord[], production: RunRecord[], opts?: EasyModeOptions): EasyModeReport;
//# sourceMappingURL=sim-fidelity.d.ts.map