/** * Pre-registered signals and the persistent regression suite. * * TWO DEFECTS IN THE SCOREBOARD THAT THIS FILE EXISTS TO FIX. * * 1. The scoreboard could only report NOVELTY, never REGRESSION. Every row was * a point measurement of a different item, and nothing was ever re-measured. * If item 1 caused item 4's win to evaporate, or an unguarded ingest path * quietly refilled the store, no row in an append-only table would ever show * it. Here, every row re-runs EVERY prior item's signal, so a win that decays * is visible on the next row rather than never. * * 2. Claims could be fitted after the fact. An item that shipped against * Recall@5 could be defended with "but MRR improved" once Recall@5 came in * flat. A signal must be declared BEFORE the run, with its direction and * magnitude, and `declaredAt` is what makes that checkable. * * The sealed dev/test split stops us fitting the DATA. This stops us fitting * the CLAIM. They are different failure modes and need different machinery. * * @module v1/cli/knowledge/eval/signals */ import type { StoreProfile } from './harness.js'; export interface PreRegisteredSignal { /** Stable id, referenced by the scoreboard row that shipped the item. */ id: string; /** Backlog item this signal belongs to. */ item: string; /** ISO date the prediction was recorded. MUST predate the run that tests it — * a magnitude written down after seeing the number is not a prediction. */ declaredAt: string; /** One sentence, in words, of what is claimed. */ claim: string; /** Dotted path into the eval report, e.g. * 'results.dense-only (gte-modernbert-base).scoreboard.recallAt5'. */ metric: string; direction: 'increase' | 'decrease' | 'no-worse-than'; /** Declared before measuring. Compared against the CI half-width at scoring * time: a prediction smaller than the noise floor is not testable. */ expectedMagnitude: number; /** * Store profiles in which this signal is VISIBLE AT ALL. Item 4's superseded * rows do not exist in a 'fresh' corpus, so scoring it there yields a flat * number that means "cannot see", not "no effect". Getting this wrong is how * a working item gets dropped. */ visibleIn: StoreProfile[]; /** What would make this win decay. This is what populates the regression * suite — declared up front, not reconstructed later from a plan. */ decayCondition: string; /** Value when the item shipped. Written ONCE, never edited. */ shipValue?: number; /** Value before the item shipped. Written ONCE, never edited. */ baselineValue?: number; /** * The regime the reference value was measured under: corpus hash, golden-set * version and split scheme. If the current run does not match, the reference * describes a world that no longer exists and comparing against it is * meaningless — the signal reports `stale-baseline`, NOT `DECAYED`. * * This exists because it fired on its first run: the item-1 gate's 0.500 * baseline was measured on the rank-based split, and the stability fix * changed DEV membership. The suite dutifully reported DECAYED for a number * that had not regressed at all — it had been re-measured over a different * set. A false alarm is not harmless: it teaches people to ignore the alarm. */ measuredUnder?: { corpusHash?: string; goldenSetVersion?: string; splitScheme?: string; }; } /** * Why a delta came out flat. Three different situations produce an identical * null row and only one of them justifies dropping an item, so a null must say * which it is. `cannot-see-mechanism` is the DEFAULT: claiming "no effect" * requires first showing the harness could have seen an effect. */ export type NullVerdict = 'no-effect' | 'cannot-see-mechanism' | 'redundant' | 'undetermined'; export interface SignalResult { id: string; item: string; claim: string; metric: string; declaredAt: string; currentValue: number | null; shipValue: number | null; baselineValue: number | null; /** Movement since ship time. Negative for a signal declared 'increase' is decay. */ deltaSinceShip: number | null; verdict: 'holding' | 'DECAYED' | 'cannot-see' | 'not-yet-shipped' | 'below-noise-floor' | 'stale-baseline'; nullVerdict?: NullVerdict; note: string; } /** Read a dotted path, tolerating keys that themselves contain dots. */ export declare function readMetric(report: unknown, dotted: string): number | null; /** * THE PERSISTENT REGRESSION SUITE. * * Append one entry per shipped item, with `shipValue` and `baselineValue` * frozen at ship time. Never edit an existing entry: an edited prediction is * not a prediction. Every future scoreboard row re-scores all of them. */ export declare const SIGNAL_REGISTRY: PreRegisteredSignal[]; export interface Regime { corpusHash: string; goldenSetVersion: string; splitScheme: string; } export declare function scoreSignals(report: unknown, storeProfile: StoreProfile, noiseFloor: number, regime?: Regime): SignalResult[]; //# sourceMappingURL=signals.d.ts.map