/** * System One contradiction check for ADR decision writes (T12493). * * `validateDecisionConflicts` used to find contradictions by asking a * generative model for free-form "insights", then scraping decision ids out of * the prose with `/\bD\d{3,}\b/` and keeping any insight whose key mentioned * "contradict". That call had no timeout, so it could hold a decision write * open indefinitely. * * This module replaces it with ONE `decide()` request: * * - Candidates are the top {@link MAX_CONTRADICTION_CANDIDATES} prior * decisions by the validator's own word-Jaccard score (score > 0). * - Each candidate gets one `choice` question with the options in * {@link CONTRADICTION_RELATIONS}. A contradiction is a typed answer naming a * known candidate id — never an id found in prose. `supersedes` about any * decision other than a validly declared `supersedes` target also counts * (undeclared supersession); see {@link countsAsContradiction}. * - The whole step, module load included, is bounded by * {@link DECISION_CONTRADICTION_BUDGET_MS}. * - Every text field is redacted, then clipped, before it leaves the machine. * - A `choice` outside the offered options is rejected here (the client only * checks the answer type), and the heuristic acts. * * Mode comes from `decide.sites.decisionContradiction` (`off | shadow | on`): * `shadow` (the default once a provider is configured) audits the decision * next to the heuristic and acts on the heuristic; `on` acts on the decision * when every answer has confidence ≥ {@link DECISION_CONTRADICTION_MIN_CONFIDENCE}. * No provider configured → `off`, no network call. * * @task T12493 * @epic T12486 */ import type { DecisionRequest } from '@cleocode/contracts'; import type { DecideOptions } from '../decide/client.js'; import { type DecisionSiteMode } from '../decide/site.js'; /** * End-to-end budget for the contradiction decision, in milliseconds: module * load, redaction, the shared request budget and the HTTP round trip. */ export declare const DECISION_CONTRADICTION_BUDGET_MS = 300; /** Minimum confidence EVERY answer needs before `on` mode acts on the decision. */ export declare const DECISION_CONTRADICTION_MIN_CONFIDENCE = 0.6; /** Maximum prior decisions offered to the decision. */ export declare const MAX_CONTRADICTION_CANDIDATES = 3; /** Call-site id for the contradiction decision; keys the audit line. */ export declare const DECISION_CONTRADICTION_SITE: "memory.decision-contradiction"; /** Config key selecting the System One mode for the contradiction check. */ export declare const DECISION_CONTRADICTION_MODE_KEY: "decide.sites.decisionContradiction"; /** * Config key for the older generative-LLM contradiction check (T1828). * Absent → on in every mode except `on` (unconfigured, `off` and `shadow` * keep today's behaviour); only `on` replaces it. */ export declare const DECISION_CONTRADICTION_LLM_KEY: "decide.generativeFallback.decisionContradiction"; /** The relations a candidate can have to the new decision; the `choice` options. */ export declare const CONTRADICTION_RELATIONS: readonly ["contradicts", "supersedes", "refines", "unrelated"]; /** One of {@link CONTRADICTION_RELATIONS}. */ export type ContradictionRelation = (typeof CONTRADICTION_RELATIONS)[number]; /** The candidate decision being written. */ export interface ContradictionSubject { /** Decision type (e.g. `architecture`). */ readonly type: string; /** Decision text. */ readonly decision: string; /** Rationale text. */ readonly rationale: string; /** Id of the decision this one declares it supersedes, if any. Sent in `state.new`. */ readonly supersedes?: string; /** * Whether the declared supersession passed the validator's graph check * (target exists and is not already superseded). Only a valid declaration * exempts its target from being counted. */ readonly supersessionValid?: boolean; } /** A prior decision offered to the decision, with its lexical score. */ export interface ContradictionCandidate { /** Decision id (e.g. `D042`). */ readonly id: string; /** Decision text. */ readonly decision: string; /** Rationale text. */ readonly rationale: string; /** Word-Jaccard score against the new decision (the heuristic's signal). */ readonly score: number; } /** Options for {@link askContradictionDecision}. */ export interface ContradictionDecisionOptions { /** Effective mode (`off` never reaches this function). */ readonly mode: Exclude; /** The heuristic's overall verdict, in the site's vocabulary (`clear` or `collision`). */ readonly heuristicVerdict: string; /** Collision threshold the heuristic uses per candidate. */ readonly collisionThreshold: number; /** Provider, connection, budget, cache and audit wiring forwarded to `decide()`. */ readonly decide?: DecideOptions; /** Project root for the default audit sink. */ readonly projectRoot?: string; } /** The decision's verdict, when a provider (or its cache) answered with valid options. */ export interface ContradictionVerdict { /** Candidate id → decided relation. */ readonly relations: Readonly>; /** Candidate ids counted as contradicted; see {@link countsAsContradiction}. */ readonly contradictions: readonly string[]; /** Candidate id → probability of the counted relation, for every counted contradiction. */ readonly contradictionProbabilities: Readonly>; /** Whether every answer met {@link DECISION_CONTRADICTION_MIN_CONFIDENCE}; only then may `on` act. */ readonly confident: boolean; } /** * Whether a decided relation counts as a contradiction of candidate `id`. * * - `contradicts` counts, and `supersedes` counts too: replacing a decision * without declaring it is an undeclared supersession, and letting it through * would store two live decisions that cannot both hold. * - Neither counts for the candidate the new decision validly declares it * supersedes (declared, and the graph check found no violation). * - `refines` and `unrelated` never count. * * @param subject - The decision being written. * @param id - Candidate decision id. * @param relation - The decided relation. * @returns Whether the relation is a counted contradiction. */ export declare function countsAsContradiction(subject: ContradictionSubject, id: string, relation: ContradictionRelation): boolean; /** * Build ONE decision request: the new decision plus up to * {@link MAX_CONTRADICTION_CANDIDATES} prior decisions as structured state, * and one `choice` question per prior decision. * * @param subject - The decision being written. * @param candidates - Prior decisions, highest score first. * @param redact - Redaction applied to every text field BEFORE clipping. * @returns The request. */ export declare function buildContradictionRequest(subject: ContradictionSubject, candidates: readonly ContradictionCandidate[], redact: (s: string) => string): DecisionRequest; /** * Ask System One how each candidate relates to the new decision. * * Never throws and never waits longer than {@link DECISION_CONTRADICTION_BUDGET_MS} * from its own start. The audit line carries the heuristic answers next to the * decision answers. * * @param subject - The decision being written. * @param candidates - Prior decisions, highest score first (at most {@link MAX_CONTRADICTION_CANDIDATES}). * @param opts - Mode, heuristic verdict and `decide()` wiring. * @returns The verdict, or `null` when the heuristic answered (fallback or an out-of-range choice). */ export declare function askContradictionDecision(subject: ContradictionSubject, candidates: readonly ContradictionCandidate[], opts: ContradictionDecisionOptions): Promise; //# sourceMappingURL=decision-contradiction.d.ts.map