import type { CorpusDisposition } from './corpus-dispositions.js'; import type { GroundTruthLabel, RuleFiring } from './windtunnel-scorer.js'; /** * strategy#709 5d-iii — the ground-truth label deriver (pure core). * * Produces the cert-run answer key (`firingLabelId → TP|FP`) by joining the * enumerated `RuleFiring`s against the frozen held-out `CorpusDisposition`s, then * classifying each bound thread through the closed 5d-i taxonomy. The CLI * `derive-labels` command supplies firings enumerated byte-identically to the * certifying run (shared firing-setup) and the integrity-gated dispositions; * this function is the deterministic, zero-LLM transform between them. * * Span-join invariant (codex hard fold): a corpus firing binds to a disposition * thread on the SAME pr ONLY when (a) the thread's path matches the firing's * file and (b) the firing's normalized `matchedLine` equals an ADDED (`+`) * post-image row of the thread's `diffHunk`. Context rows, removed rows, hunk * headers, and file headers are INELIGIBLE — a disposition labels a firing only * by content the PR actually added, never by a line it merely sits near. 0 or * >1 bound threads ⟹ omit (the scorer routes the un-keyed firing to * `needsAdjudication`). */ /** Why a non-negative firing received no label (diagnostic only; never in the answer key). */ export type UnlabeledReason = /** corpus firing: no disposition thread bound by path + added-line content. */ 'no-matching-disposition' /** corpus firing: >1 disposition thread bound — ambiguous, never labels. */ | 'ambiguous-multiple-dispositions' /** corpus firing: a thread bound, but its taxonomy class is non-label-bearing (UNLABELED). */ | 'unlabeled-class' /** positive-control firing that is NOT the declared (pr, targetRuleId) target. */ | 'incidental-positive'; /** * Deriver-side data-quality diagnostics (gemini: deriver reports DATA QUALITY, * the scorer reports MODEL PERFORMANCE). Deterministic + zero-LLM. Surfaced so a * sparse first verdict reads as "here's the coverage + why", not a silent fail. */ export interface DeriveLabelDiagnostics { /** Total firings enumerated (all control kinds). */ totalFirings: number; /** Negative-control firings (no label — the scorer culls the rule). */ negativeFirings: number; /** Corpus firings (the real precision surface). */ corpusFirings: number; /** Positive-control firings. */ positiveFirings: number; /** Corpus firings that received a TP/FP label. */ boundCorpusFirings: number; /** boundCorpusFirings / corpusFirings — 0 when there are no corpus firings. */ dispositionDensity: number; /** Non-negative firings with no label (the scorer's future `needsAdjudication` set). */ unlabeledFirings: number; /** unlabeledFirings / (corpusFirings + positiveFirings) — 0 when that denominator is 0. */ unlabeledRate: number; /** Label counts in the emitted answer key. */ labelCounts: { TP: number; FP: number; }; /** Per-rule labeled-firing counts (ruleId → {TP, FP}); only rules that labeled appear. */ perRuleLabeled: Record; /** Breakdown of why firings went unlabeled. */ unlabeledByReason: Record; } /** * Provenance for one emitted label — links the answer-key entry back to its * disposition source for audit. NOT part of the hashed answer key (whose values * stay a bare `TP|FP`); surfaced in the deriver's report only. */ export interface LabelEvidence { labelId: string; label: GroundTruthLabel; pr: number; ruleId: string; filePath: string; /** Source disposition thread id (corpus labels only; positive-target labels omit it). */ threadId?: string; /** Root review-comment databaseId of the bound thread (corpus labels only). */ commentId?: number; source: 'corpus-disposition' | 'positive-control-target'; } export interface DeriveLabelsResult { /** * The answer key: `firingLabelId → TP|FP`. The ONLY thing written to * `ground-truth-labels.json` (and the bytes `groundTruthSha` covers). */ labels: Record; diagnostics: DeriveLabelDiagnostics; /** Per-label provenance (audit; surfaced in the report, never in the hashed key). */ evidence: LabelEvidence[]; } /** * Derive the cert-run ground-truth answer key from enumerated firings + frozen * held-out dispositions. Pure + deterministic — no I/O, no clock, no LLM. */ export declare function deriveLabelsFromDispositions(firings: readonly RuleFiring[], dispositions: readonly CorpusDisposition[]): DeriveLabelsResult; //# sourceMappingURL=derive-labels.d.ts.map