import type { Finding } from "../types/finding.js"; /** * Merge two grounding verdicts by precedence: grounded > refuted > ungrounded > * absent (S7). Grounded-wins (a verified span/anchor on ANY pass upgrades the * survivor; an ungrounded/absent verdict never downgrades it). A refutation * outranks ungrounded/absent, so a finding refuted on any pass is quarantined * UNLESS another pass grounded it. */ export declare function mergeGrounding(existing: Finding["grounding"], incoming: Finding["grounding"]): Finding["grounding"]; /** * Union `absorbed`'s affected_files into `survivor` (dedup by * path:line_start:line_end:symbol), optionally sorting by path then line. Shared by * the absorb mechanics AND audit's identity-key exact merge (`upsertFinding`). */ export declare function mergeAffectedFiles(survivor: Finding, absorbed: Finding, sort: boolean): void; export interface AbsorbOptions { /** Merge grounding verdicts by precedence (audit evidence integrity). */ mergeGrounding: boolean; /** Sort the survivor's affected_files after the union (audit). */ sortAffectedFiles: boolean; } /** * Merge `absorbed` INTO `survivor` in place: union affected_files + evidence, * systemic OR, longest summary; optionally grounding-precedence + sort. The caller * decides whether `survivor` is an original (mutate) or a clone (never-mutate). * Shared by the cross-lens core AND audit's same-lens pass. */ export declare function absorbFinding(survivor: Finding, absorbed: Finding, opts: AbsorbOptions): void; export interface CrossLensDedupePolicy { /** * Category handling: `soft` still merges two findings of different categories but * at a higher title-similarity threshold (audit review — a human reads the * report); `hard` NEVER merges across categories — a different category is a * structurally different fix, unsafe to auto-collapse (remediate, OBL-C003-DEDUP). */ categoryGate: "soft" | "hard"; /** * When true, two findings sharing a DISCRIMINATING shared-identity signature * collapse even below the title-Jaccard floor (remediate drift-plan R2). */ exactIdentityShortCircuit: boolean; /** * `mutate` the survivor original in place (audit report); `clone` it first so the * caller's Finding objects are never mutated (remediate block state machine, * INV-remediate-state-05). */ survivorMutation: "mutate" | "clone"; /** Merge grounding verdicts by precedence (audit evidence integrity). */ mergeGrounding: boolean; /** Sort a survivor's affected_files after each absorb (audit). */ sortAffectedFiles: boolean; /** * Stop the inner scan immediately when the i-slot finding is itself absorbed * (remediate). This is an OPTIMIZATION knob only: correctness never depends on * it — an absorbed finding is unconditionally excluded from every subsequent * pairwise comparison in both loops (conservation: once removed, a finding can * neither absorb nor be re-emitted). */ breakOnAbsorbedSurvivor: boolean; /** * What the caller's finding ids MEAN, which decides whether id-keyed provenance * is well-defined: * * - `global` (remediate): ids are globally unique (they come from * `audit-findings.json`, re-keyed at the synthesis boundary). Duplicate or * empty input ids are REFUSED, a merge-chain cycle is an internal invariant * failure, and the result carries `dispositionById` plus the terminal * evidence-conservation check. * - `local` (audit report merge): ids are packet-scoped (`MNT-001` collides * across units by construction) and only become unique AFTER this pass, at * `assignStableFindingIds`. Id-keyed provenance is meaningless here, so no * refusal, no `dispositionById`, and a chain cycle from a reused id breaks * tolerantly instead of throwing (`mergeMap` is unused by this caller). */ idDiscipline: "global" | "local"; /** Called for each merge (remediate emits a structured audit log). */ onMerge?: (info: { absorbed: Finding; survivor: Finding; }) => void; } export interface CrossLensDedupeResult { findings: Finding[]; /** * `absorbed.id → survivor.id` for every merge (empty when nothing merged). A * clone-mode caller uses it to rewrite downstream references (remediation blocks); * mutate-mode callers can ignore it. */ mergeMap: Map; /** * Membership-closed disposition for every unique input id. Merge paths retain * the direct provenance chain while `terminalFindingId` always names an * emitted finding. Present ONLY under `idDiscipline: "global"` — with * packet-local ids the map would be mis-keyed, and an empty map here must * never read as "nothing merged" ([[success-shaped-empty-needs-affirmation]]). */ dispositionById: Map | null; } export interface FindingDedupeDisposition { status: "retained" | "merged"; terminalFindingId: string; mergePath: string[]; } /** * Collapse cross-lens duplicate findings within each primary-path group. Only pairs * of DIFFERENT lenses are considered (same-lens dedup is a separate pass); the * winner by severity-then-confidence absorbs the loser's files/evidence. All policy * divergence between the two orchestrators is expressed through `policy`. */ export declare function crossLensDedupe(findings: Finding[], policy: CrossLensDedupePolicy): CrossLensDedupeResult; /** * File-independent finding identity for exact re-emission collapse: the same logical * finding (normalized lens + category + title) re-emitted across files / units / * passes shares one key. Distinct from `findingIdentityKey` (the 3-tier structural- * anchor ladder) — this is the coarse lens|category|title exact key the identity * upsert collapses on. Cross-file merging happens ONLY on this exact equality; the * fuzzy same/cross-lens passes stay grouped by primary path so distinct problems in * different units never collapse on mere similarity. */ export declare function findingReEmissionKey(finding: Finding): string; /** * Same-lens dedup: within each (lens, primary-path) group, collapse fuzzily-similar * findings (title Jaccard with a category-lowered threshold, plus line-range OR file * overlap), the higher sev/conf survivor absorbing the loser in place with grounding- * precedence + sorted files. A core capability only audit currently draws (remediate * consumes findings the auditor already collapsed), single-sourced here so the whole * finding-dedup family lives in one place. */ export declare function sameLensDedupe(findings: Finding[]): Finding[]; /** * Insert a finding into an identity-keyed map, or absorb it into the existing finding * of the same re-emission identity (`findingReEmissionKey`): affected_files + evidence * union, severity / confidence ESCALATE to the maximum rank seen, `systemic` ORs, * impact / likelihood backfill, longest summary wins. (Audit's exact re-emission * collapse — remediate has no identity-key merge today.) */ export declare function upsertFindingByIdentity(merged: Map, finding: Finding): void; //# sourceMappingURL=dedupe.d.ts.map