import type { CompiledRule } from '../compiler-schema.js';
import type { RuleEngineContext } from '../rule-engine.js';
import type { RuleFiring } from './windtunnel-scorer.js';
/**
 * One resolved PR's diff plus its role in the wind-tunnel. The certifying run
 * (5c-ii) builds these from each resolved-PR diff + the controls; 5c-i is the
 * deterministic engine that turns them into `RuleFiring[]`.
 *
 * `controlKind` is the role of the PR/corpus item being scanned, NOT a property
 * of any individual rule — every rule that fires on a `negative` item is culled
 * (fold-H: neg-control firings pass through as `controlKind:'negative'`, never
 * dropped pre-score). `targetRuleId` is the rule a `positive` control MUST fire
 * to prove non-vacuousness (the positive-control contract the scorer checks).
 */
export interface ResolvedPrDiff {
    pr: number;
    /** Unified diff text for the PR (post-image additions are extracted from it). */
    diff: string;
    controlKind: 'corpus' | 'positive' | 'negative';
    /** For positive controls: the rule expected to fire (proves non-vacuousness). */
    targetRuleId?: string;
}
/**
 * C1 (fold-B's data prerequisite) — a PER-RULE control result over the
 * surviving (active, non-culled) rules. `positiveControl`/`negativeControl` are
 * derived from THIS rule's actual firings, NEVER from the run-level
 * `nonVacuity` (which is a global AND across all positive-control targets and
 * would over-stamp a rule that never exercised a control). 5c-ii consumes this
 * to stamp legitimacy per-rule, survivor-only.
 */
export interface PerRuleControlResult {
    /** True iff this rule's positive control is proven (per-rule, not global): a target firing (mined) or a §4 differential held at emission (authored). */
    positiveControl: boolean;
    /** True iff this rule did NOT fire on any negative control (clean = passed). */
    negativeControl: boolean;
    /**
     * Evidence for the positive result: MINED — the establishing firingLabelIds;
     * AUTHORED — `§6-emission:`-prefixed locus refs (option (i), #2291; see
     * `computeAuthoredPerRuleControlResults`). Never both shapes in one map.
     */
    evidenceRefs: string[];
}
/**
 * A1 (fold-D) collision detail — surfaced when two firings normalize to the
 * same `labelId` (the `labelId→evidenceRef`/ground-truth join would silently
 * overwrite). Emitted into the thrown error so the cert-run report can name the
 * colliding labelIds + their evidence refs.
 */
export interface FiringLabelCollision {
    labelId: string;
    /** The colliding firings' evidence (ruleId/pr/filePath/matchedLine). */
    evidenceRefs: Array<{
        ruleId: string;
        pr: number;
        filePath: string;
        matchedLine: string;
    }>;
}
/** Thrown by `assertUniqueFiringLabels` (A1 hard-gate floor, Tenet 4). */
export declare class FiringLabelCollisionError extends Error {
    readonly collisions: FiringLabelCollision[];
    constructor(collisions: FiringLabelCollision[]);
}
/** Thrown when an archived rule reaches the scored set (fold-F, Tenet 4). */
export declare class ArchivedRuleInScopeError extends Error {
    readonly archivedRuleIds: string[];
    constructor(archivedRuleIds: string[]);
}
/**
 * Normalize a matched line for the content-based labelId (A2/A3). Trailing
 * whitespace is dropped so cosmetic EOL drift between the post-image and the
 * diff does not split a firing into a new labelId; interior content is
 * preserved (the label must still distinguish genuinely different lines).
 *
 * Exported so the 5d-iii label-deriver's span-join normalizes a disposition's
 * added hunk rows with the EXACT same rule the firing's `matchedLine` uses —
 * the content bind keys on the same bytes the labelId keys on, so the two can
 * never silently drift apart (codex hard fold + the panel anti-drift mandate).
 */
export declare function normalizeMatchedLine(line: string): string;
/**
 * fold-F — assert no `status:'archived'` rule is in the scored set (Tenet 4).
 * Called at the scorer input boundary BEFORE the engine runs, so
 * `applyAstRulesToAdditions` is never invoked on an archived rule and zero
 * archived refs can reach `RuleFiring[]`. Archived ≠ wind-tunnel FP: an
 * archived rule that "fires" is not evidence of imprecision, it is a corpus
 * contamination that must fail loud.
 */
export declare function assertNoArchivedRules(rules: CompiledRule[]): void;
/**
 * A1 (fold-D) — assert `firings.length === unique(labelIds).size` BEFORE
 * `scoreWindtunnel` (Tenet 4). Originally specced as a hard-gate FLOOR that
 * threw on any collision; the strategy ruling (2026-06-20) DEMOTED it to a
 * post-dedup invariant once `buildFirings` learned to collapse same-labelId
 * matches (`dedupeFirings`). It still THROWS on a collision — but a collision
 * here now means a dedup BUG (or a caller that bypassed `buildFirings`), not an
 * honest multi-match line, so failing loud is correct. We deliberately do NOT
 * add an occurrence discriminator / ordinal (it would regress `firingLabelId`'s
 * line-drift resistance); a diff-hunk-span discriminator stays reserved. The
 * invariant guards the `labelId→evidenceRef` contract the ground-truth join
 * depends on: a 1:1 labelId→firing map.
 */
export declare function assertUniqueFiringLabels(firings: RuleFiring[]): void;
export interface BuildFiringsInput {
    /** Active compiled rules (already loaded; archived must be pre-filtered). */
    rules: CompiledRule[];
    /** Resolved-PR diffs + controls (corpus / positive / negative). */
    prDiffs: ResolvedPrDiff[];
    /** Repo root for AST file resolution (NOT process.cwd() — #1304). */
    cwd: string;
    /** Post-image content seam (S1/C1 — same content for regex astContext + AST). */
    readStrategy: (file: string) => Promise<string | null>;
    /** Per-invocation rule-engine context (logger + per-ctx state). */
    ruleEngineCtx: RuleEngineContext;
    onWarn?: (msg: string) => void;
}
export interface BuildFiringsResult {
    firings: RuleFiring[];
    /** Distinct files touched across all PR diffs (C2 — real exposure). */
    filesTouchedInWindow: number;
    /** Positive-control targets, derived from the prDiffs (for the scorer). */
    positiveControlTargets: Array<{
        pr: number;
        targetRuleId: string;
    }>;
}
/**
 * The real-engine firing path (replaces `runMockEngine` for the certifying
 * phase). For each resolved-PR diff: extract additions → `enrichWithAstContext`
 * (regex astContext via the shared post-image readStrategy) → regex engine +
 * `applyAstRulesToAdditions` (AST/ast-grep, same readStrategy) → map every
 * `Violation` to a `RuleFiring` with `labelId = firingLabelId(...)`.
 *
 * Invariants honored here:
 *  - **fold-F**: throws if any archived rule is present (assertNoArchivedRules),
 *    so the engine is never invoked on an archived rule.
 *  - **fold-H**: a firing on a `negative` PR is emitted as `controlKind:'negative'`
 *    (the scorer culls + ledgers it — never dropped pre-score). Unlabeled firings
 *    stay in `firings` (no ground-truth) so the scorer routes them to
 *    needsAdjudication.
 *  - **C2**: `filesTouchedInWindow` is the count of distinct post-image files
 *    across all diffs — the real third exposure leg.
 *
 * fold-D: before returning, same-`labelId` matches are collapsed to ONE logical
 * firing (`dedupeFirings`), retaining every raw match as `evidence`. The caller's
 * `assertUniqueFiringLabels` is therefore a post-dedup INVARIANT (it can no
 * longer fire on an honest multi-match line), not a pre-score floor.
 */
export declare function buildFirings(input: BuildFiringsInput): Promise<BuildFiringsResult>;
/**
 * C1 (fold-B's data prerequisite) — build a `Map<ruleId, PerRuleControlResult>`
 * over the SURVIVING rules (active rules that were NOT culled by a negative
 * control). `positiveControl` is true ONLY when THIS rule fired its declared
 * positive-control target — derived from the rule's own firings, never from the
 * global `nonVacuity`. `negativeControl` is true when the rule fired on NO
 * negative control. 5c-ii reads this to stamp legitimacy survivor-only.
 *
 * Survivors = mintedRuleIds minus rules that fired on any negative control
 * (those are culled; the scorer records them in the cullLedger and we do not
 * stamp them).
 */
export declare function computePerRuleControlResults(input: {
    firings: RuleFiring[];
    mintedRuleIds: string[];
    positiveControlTargets: Array<{
        pr: number;
        targetRuleId: string;
    }>;
}): Map<string, PerRuleControlResult>;
//# sourceMappingURL=windtunnel-firing.d.ts.map