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<string, {
        TP: number;
        FP: number;
    }>;
    /** Breakdown of why firings went unlabeled. */
    unlabeledByReason: Record<UnlabeledReason, number>;
}
/**
 * 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<string, GroundTruthLabel>;
    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