@fro.bot/systematic 3.18.3 → 3.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,812 @@
1
+ import type { z } from 'zod';
2
+ import type { HarnessSchema } from './review-artifact-schema.js';
3
+ import { type AdjudicationEnvelopeSchema, type FinalizeInputSchema, FinalizeOutputSchema, MergeOutputSchema, type PipelineRoute, type PlanAssessmentEnvelopeSchema, PrepareOutputSchema, ScreenOutputSchema, type ValidatorLifecycleResultsSchema } from './review-pipeline-contract.js';
4
+ /**
5
+ * Normalizes a repo-relative path for grouping, sorting, and any later
6
+ * surface comparison. Collapses `\`-style separators to `/`, then applies
7
+ * POSIX lexical normalization (redundant slashes, `.` segments, and a
8
+ * leading `./`). Never touches the filesystem or the process environment --
9
+ * this is a pure string transform.
10
+ */
11
+ export declare function normalizeRepoRelativePath(filePath: string): string;
12
+ /**
13
+ * Pure, side-effect-free admission of one reviewer's raw `ce:review` return.
14
+ *
15
+ * Binds the parsed return to the parent-expected persona and validates its
16
+ * structure. Never reads `process.env`, the filesystem, or the clock --
17
+ * admission depends only on the payload itself.
18
+ *
19
+ * Reuses `validateReviewReturnValue` from `review-return-validator.ts` for
20
+ * the underlying `SubAgentReturnSchema` structural check rather than
21
+ * re-validating shape by hand.
22
+ */
23
+ export type ScreenOutput = ReturnType<typeof ScreenOutputSchema.parse>;
24
+ /** Raw screen input. `raw_return` is intentionally `unknown`: a reviewer's
25
+ * return may arrive as an unparsed JSON string (a raw subprocess payload) or
26
+ * as an already-decoded value; this is the boundary that admits it. */
27
+ export interface ScreenReviewReturnInput {
28
+ readonly raw_return: unknown;
29
+ readonly expected_reviewer: string;
30
+ readonly invoking_harness: z.infer<typeof HarnessSchema>;
31
+ }
32
+ /**
33
+ * Admit one reviewer's raw return and bind it to the parent-expected
34
+ * persona.
35
+ *
36
+ * Rejection is whole-payload only: a schema-invalid return, a reviewer
37
+ * identity mismatch, or malformed JSON rejects everything. Findings that
38
+ * pass structural validation are admitted with a stable
39
+ * `<reviewer>#<original-index>` input ID derived from their position in the
40
+ * original payload.
41
+ */
42
+ export declare function screenReviewReturn(input: ScreenReviewReturnInput): ScreenOutput;
43
+ export type PrepareOutput = ReturnType<typeof PrepareOutputSchema.parse>;
44
+ /** Raw prepare input. `raw_input` is intentionally `unknown`: the aggregate
45
+ * payload may arrive as an unparsed JSON string (a raw stdin payload) or as
46
+ * an already-decoded value; this is the boundary that admits it. */
47
+ export interface PrepareReviewCandidatesInput {
48
+ readonly raw_input: unknown;
49
+ }
50
+ type PrepareRejectReason = 'aggregate payload exceeds byte cap' | 'malformed JSON' | 'schema validation' | 'duplicate persona outcome' | 'duplicate input id' | 'unselected persona screen result' | 'missing screen result for selected dispatch';
51
+ /** One bounded, payload-safe rejection diagnostic: a fixed reason code and a
52
+ * safe JSON path only. Never payload content, never exception text. */
53
+ export interface PrepareRejection {
54
+ readonly path: string;
55
+ readonly reason: PrepareRejectReason;
56
+ }
57
+ export type PrepareReviewCandidatesResult = {
58
+ readonly ok: true;
59
+ readonly value: PrepareOutput;
60
+ } | {
61
+ readonly ok: false;
62
+ readonly rejection: PrepareRejection;
63
+ };
64
+ /**
65
+ * Consumes every selected persona's screen result plus the dispatch metadata
66
+ * the parent selected them under, applies the confidence gate, and forms
67
+ * candidate groups for the model to adjudicate.
68
+ *
69
+ * Rejection is whole-payload only, with a fixed reason code and a safe JSON
70
+ * path -- never payload content or exception text. Every count in the
71
+ * output is recomputed from the admitted arrays themselves; no count
72
+ * supplied by the input is trusted.
73
+ */
74
+ export declare function prepareReviewCandidates(input: PrepareReviewCandidatesInput): PrepareReviewCandidatesResult;
75
+ type AdjudicationDecisions = z.infer<typeof AdjudicationEnvelopeSchema>['decisions'];
76
+ type MergeDecision = AdjudicationDecisions[number];
77
+ type MergedMergeDecision = Extract<MergeDecision, {
78
+ disposition: 'merged';
79
+ }>;
80
+ type DeclinedMergeDecision = Extract<MergeDecision, {
81
+ disposition: 'declined';
82
+ }>;
83
+ /** One validated merge group: the model's merge decision plus the file its
84
+ * members were grouped under, so a later derivation step does not need to
85
+ * re-scan the candidate groups to recover it. */
86
+ export interface ValidatedMergedGroup {
87
+ readonly decision: MergedMergeDecision;
88
+ readonly file: string;
89
+ }
90
+ /** One validated declined singleton: the model's decline decision plus the
91
+ * file its input finding was grouped under. */
92
+ export interface ValidatedDeclinedSingleton {
93
+ readonly decision: DeclinedMergeDecision;
94
+ readonly file: string;
95
+ }
96
+ /** The validated partition: every eligible candidate accounted for, plus the
97
+ * true singletons passed through untouched. Sorted by `decision_id` /
98
+ * input ID so the result is byte-identical regardless of input decision
99
+ * order. */
100
+ export interface ValidatedAdjudication {
101
+ readonly merged: readonly ValidatedMergedGroup[];
102
+ readonly declined: readonly ValidatedDeclinedSingleton[];
103
+ readonly singletons: readonly string[];
104
+ }
105
+ type AdjudicationRejectReason = 'unknown input id' | 'suppressed input id' | 'duplicate input id citation' | 'omitted eligible input id' | 'cross-group input id citation' | 'representative line mismatch' | 'unexpected decisions for empty candidate set' | 'duplicate merged group decision id' | 'duplicate declined decision id' | 'duplicate passthrough singleton input id' | 'declined decision id collides with merged group decision id' | 'declined decision id collides with passthrough singleton input id' | 'passthrough singleton input id collides with merged group decision id';
106
+ /** One bounded, payload-safe rejection diagnostic: a fixed reason code and a
107
+ * safe JSON path only. Never payload content, never a finding title, never
108
+ * exception text. */
109
+ export interface AdjudicationRejection {
110
+ readonly path: string;
111
+ readonly reason: AdjudicationRejectReason;
112
+ }
113
+ export type ValidateAdjudicationResult = {
114
+ readonly ok: true;
115
+ readonly value: ValidatedAdjudication;
116
+ } | {
117
+ readonly ok: false;
118
+ readonly rejection: AdjudicationRejection;
119
+ };
120
+ /**
121
+ * Validates that the model's adjudication decisions form a valid partition
122
+ * over `prepared`'s eligible candidate set: every eligible input ID (one
123
+ * that appears in a candidate group) is cited by exactly one decision --
124
+ * either inside a merge group or as a declined singleton. Never repairs a
125
+ * malformed partition; any violation rejects the whole envelope with no
126
+ * partial output, since a repaired partition would silently invent a merge
127
+ * decision the model never made.
128
+ *
129
+ * A decision set is required to be empty when there are no candidate groups
130
+ * (a legitimate, non-error state); a non-empty decision set against zero
131
+ * candidates is rejected. This function never computes merged-finding
132
+ * fields (severity, confidence, provenance, routing) -- only structural
133
+ * partition validity.
134
+ */
135
+ export declare function validateAdjudication(prepared: PrepareOutput, decisions: AdjudicationDecisions): ValidateAdjudicationResult;
136
+ type SurvivingFinding = PrepareOutput['surviving_findings'][number];
137
+ /** A candidate group always has at least two members
138
+ * (`CandidateGroupSchema.members` is `.min(2)`), so the contributing set for
139
+ * one merge decision is a non-empty tuple by construction rather than a
140
+ * plain array that would need a defensive empty check on every derivation
141
+ * below. */
142
+ export type MergeContributingFindings = readonly [
143
+ SurvivingFinding,
144
+ SurvivingFinding,
145
+ ...SurvivingFinding[]
146
+ ];
147
+ /** The model-owned fields for one merge decision that this derivation
148
+ * consumes: the representative line it picked (already checked by
149
+ * `validateAdjudication` against the group's real member lines), any
150
+ * additional reviewers it claims agreed, and an optional narrower route with
151
+ * the reason narrowing requires. */
152
+ export interface MergedFindingModelDecision {
153
+ readonly line: number;
154
+ readonly eligible_agreement_credit?: readonly string[];
155
+ readonly proposed_route?: PipelineRoute;
156
+ readonly route_narrowing_reason?: string;
157
+ }
158
+ export interface DeriveMergedFindingInput {
159
+ readonly contributing: MergeContributingFindings;
160
+ readonly decision: MergedFindingModelDecision;
161
+ /** Every reviewer whose `SubAgentReturn` was actually admitted for this
162
+ * run -- the eligibility set agreement credit is checked against. */
163
+ readonly returned_reviewers: readonly string[];
164
+ }
165
+ export interface DerivedMergedFindingFields {
166
+ readonly severity: SurvivingFinding['severity'];
167
+ readonly submitters: readonly string[];
168
+ readonly confidence: number;
169
+ readonly agreement_credit: readonly string[];
170
+ readonly pre_existing: boolean;
171
+ readonly fingerprint: string;
172
+ readonly route: PipelineRoute;
173
+ }
174
+ type MergedFindingRejectReason = 'route widening' | 'route narrowing missing reason' | 'duplicate agreement credit reviewer' | 'agreement credit reviewer already a submitter' | 'agreement credit reviewer did not return';
175
+ /** One bounded, payload-safe rejection diagnostic: a fixed reason code and a
176
+ * safe JSON path only. Never payload content, never a finding title, never
177
+ * exception text. */
178
+ export interface MergedFindingRejection {
179
+ readonly path: string;
180
+ readonly reason: MergedFindingRejectReason;
181
+ }
182
+ export type DeriveMergedFindingResult = {
183
+ readonly ok: true;
184
+ readonly value: DerivedMergedFindingFields;
185
+ } | {
186
+ readonly ok: false;
187
+ readonly rejection: MergedFindingRejection;
188
+ };
189
+ /**
190
+ * Derives one merged finding's mechanical fields -- severity, submitters,
191
+ * confidence, agreement credit, `pre_existing`, fingerprint, and route --
192
+ * from its contributing surviving findings plus the model's decision fields
193
+ * for that group. Assumes `validateAdjudication` already confirmed the
194
+ * partition; never re-validates group membership. Rejection is
195
+ * whole-decision only: an invalid agreement-credit claim or a route
196
+ * widening attempt rejects the whole derivation with a fixed reason code
197
+ * and a safe JSON path, never payload content.
198
+ */
199
+ export declare function deriveMergedFindingFields(input: DeriveMergedFindingInput): DeriveMergedFindingResult;
200
+ export type MergeOutput = ReturnType<typeof MergeOutputSchema.parse>;
201
+ /** Raw application input: the prepared candidate state plus the model's
202
+ * adjudication decisions, matching `MergeInputSchema`'s two fields exactly. */
203
+ export interface ApplyReviewAdjudicationInput {
204
+ readonly prepared: PrepareOutput;
205
+ readonly decisions: AdjudicationDecisions;
206
+ }
207
+ export type ApplyReviewAdjudicationResult = {
208
+ readonly ok: true;
209
+ readonly value: MergeOutput;
210
+ } | {
211
+ readonly ok: false;
212
+ readonly rejection: AdjudicationRejection | MergedFindingRejection;
213
+ };
214
+ /**
215
+ * The top-level merge phase: validates the adjudication partition, derives
216
+ * every merged finding (one per merge group, one per declined singleton, one
217
+ * per passthrough singleton) via `deriveMergedFindingFields`, emits the
218
+ * mechanical validator request set, sorts everything stably, and parses the
219
+ * assembled result through `MergeOutputSchema` before returning success.
220
+ *
221
+ * Rejection is whole-phase only: a partition violation from
222
+ * `validateAdjudication` or a field-derivation violation from
223
+ * `deriveMergedFindingFields` (for any single group or singleton) aborts
224
+ * immediately with no partial output -- this function never accumulates
225
+ * merged findings past the first rejection, and never calls
226
+ * `MergeOutputSchema.parse` until every finding derived successfully. A
227
+ * `finding_id` collision across the three assembly sources -- a merge
228
+ * group's or declined singleton's model-chosen `decision_id`, or a
229
+ * passthrough singleton's carried-through `input_id` -- also rejects before
230
+ * parsing (`checkNoDuplicateAssembledFindingIds`), never silently
231
+ * namespaced or rewritten.
232
+ *
233
+ * The top-level `disagreement_facts` collects every decision's own
234
+ * `disagreement_facts`, plus every declined decision's `declined_reason` --
235
+ * the "why these were not merged" narrative that has no dedicated field on
236
+ * `MergedFindingSchema` itself.
237
+ */
238
+ export declare function applyReviewAdjudication(input: ApplyReviewAdjudicationInput): ApplyReviewAdjudicationResult;
239
+ type ValidatorLifecycleResults = z.infer<typeof ValidatorLifecycleResultsSchema>;
240
+ /** One merged finding carrying its reconciled validation state. `validated`
241
+ * is `true` when a validator confirmed the finding, `false` when a
242
+ * validator disproved it, and *absent* -- never coerced to either boolean
243
+ * -- both when the finding was never requested for validation and when its
244
+ * validator run failed or was unavailable. A consumer distinguishes those
245
+ * two absent cases by cross-referencing `lifecycle_failures`: a finding_id
246
+ * present there was requested but left uncertain by a `failed` or
247
+ * `unavailable` outcome; a finding_id absent from both `lifecycle_failures`
248
+ * and carrying no `validated` field was never requested at all. */
249
+ export type ReconciledFinding = MergeOutput['merged_findings'][number] & {
250
+ readonly validated?: boolean;
251
+ /** The disproving validator's own reason, carried onto the finding only
252
+ * when `validated` is `false` -- satisfies the artifact's
253
+ * `validation_reason` requirement at source instead of losing the reason
254
+ * on the way from the lifecycle result to the reconciled finding. Absent
255
+ * whenever `validated` is not `false`. */
256
+ readonly validation_reason?: string;
257
+ };
258
+ /** One recorded validator lifecycle failure: a requested finding whose
259
+ * validator run ended in uncertainty (`failed` or `unavailable`) rather
260
+ * than a definite answer. The `outcome` discriminant distinguishes a
261
+ * timeout/error from a validator that was never reachable at all. */
262
+ export interface ValidatorLifecycleFailure {
263
+ readonly finding_id: string;
264
+ readonly outcome: 'failed' | 'unavailable';
265
+ readonly reason: string;
266
+ }
267
+ export interface ReconcileValidatorResultsOutput {
268
+ readonly findings: readonly ReconciledFinding[];
269
+ readonly filtered_finding_ids: readonly string[];
270
+ readonly filtered_input_ids: readonly string[];
271
+ readonly lifecycle_failures: readonly ValidatorLifecycleFailure[];
272
+ readonly degraded: boolean;
273
+ }
274
+ type ReconcileValidatorResultsRejectReason = 'missing validator result' | 'duplicate validator result' | 'unrequested validator result';
275
+ /** One bounded, payload-safe rejection diagnostic: a fixed reason code and a
276
+ * safe JSON path only. Never payload content, never a finding title. */
277
+ export interface ReconcileValidatorResultsRejection {
278
+ readonly path: string;
279
+ readonly reason: ReconcileValidatorResultsRejectReason;
280
+ }
281
+ export interface ReconcileValidatorResultsInput {
282
+ readonly merge: MergeOutput;
283
+ readonly validator_lifecycle_results: ValidatorLifecycleResults;
284
+ }
285
+ export type ReconcileValidatorResultsResult = {
286
+ readonly ok: true;
287
+ readonly value: ReconcileValidatorResultsOutput;
288
+ } | {
289
+ readonly ok: false;
290
+ readonly rejection: ReconcileValidatorResultsRejection;
291
+ };
292
+ /**
293
+ * Reconciles the finding-validator lifecycle results against the merge
294
+ * output's validator request set, classifying every merged finding as
295
+ * validated, filtered, or left uncertain (see `ReconciledFinding` for the
296
+ * full state table). Rejection is whole-payload only: a missing result, a
297
+ * duplicate result, or a result for a finding that was never requested
298
+ * rejects everything, with a fixed reason code and a safe JSON path -- never
299
+ * payload content or a finding title.
300
+ *
301
+ * This reconciles validator results only -- it never builds action queues,
302
+ * disposition counts, risk coverage, plan-assessment routing, or the final
303
+ * artifact; those are separate slices. Never reads `process.env`, the
304
+ * filesystem, or the clock.
305
+ */
306
+ export declare function reconcileValidatorResults(input: ReconcileValidatorResultsInput): ReconcileValidatorResultsResult;
307
+ export type FinalizeInputValue = ReturnType<typeof FinalizeInputSchema.parse>;
308
+ type FinalizeScreenResults = FinalizeInputValue['screen_results'];
309
+ type FinalizeDispatchRecords = FinalizeInputValue['dispatch_records'];
310
+ type FinalizeDispatchRecord = FinalizeDispatchRecords[number];
311
+ type FinalizeParentRunMetadata = FinalizeInputValue['parent_run_metadata'];
312
+ export interface DeriveFinalizeContextInput {
313
+ readonly merge: MergeOutput;
314
+ readonly prepared: PrepareOutput;
315
+ readonly screen_results: FinalizeScreenResults;
316
+ readonly dispatch_records: FinalizeDispatchRecords;
317
+ readonly parent_run_metadata: Pick<FinalizeParentRunMetadata, 'selected_dispatches' | 'validation'>;
318
+ }
319
+ export interface FinalizeContext {
320
+ readonly rejected_payloads: readonly RejectedPayloadWeight[];
321
+ readonly lost_risk_critical_personas: readonly LostRiskCriticalPersona[];
322
+ }
323
+ type FinalizeContextRejectReason = 'duplicate merged finding ID' | 'merged finding references unknown survivor' | 'survivor missing from merge inputs' | 'survivor claimed by multiple merged findings' | 'validator request references unknown merged finding' | 'dispatch record mismatch' | 'duplicate selection surface entry' | 'screen result missing for selected persona' | 'unexpected screen result for persona' | 'screen result outcome does not match dispatch record' | 'merged finding fields diverge from derivation' | 'confidence disposition references unscreened finding' | 'merged finding cites input from unavailable reviewer' | 'merged finding submitter not a cited reviewer' | 'merged finding agreement credit overlaps submitters' | 'surviving finding diverges from its screened finding' | 'surviving finding references unscreened input' | 'duplicate confidence disposition' | 'validation must be not_attempted at finalize';
324
+ export interface FinalizeContextRejection {
325
+ readonly path: string;
326
+ readonly reason: FinalizeContextRejectReason;
327
+ }
328
+ export type DeriveFinalizeContextResult = {
329
+ readonly ok: true;
330
+ readonly value: FinalizeContext;
331
+ } | {
332
+ readonly ok: false;
333
+ readonly rejection: FinalizeContextRejection;
334
+ };
335
+ /**
336
+ * Validates the finalize envelope's cross-phase joins and derives the two
337
+ * inputs only finalize can compute: rejected-payload weights (from screen
338
+ * summaries) and lost risk-critical personas (from the loss rules in
339
+ * `synthesis-artifact-contract.md`). Rejection is whole-envelope only, with
340
+ * a fixed reason and a safe JSON path, never payload content -- and always
341
+ * happens before any derivation runs.
342
+ */
343
+ export declare function deriveFinalizeContext(input: DeriveFinalizeContextInput): DeriveFinalizeContextResult;
344
+ /** The final disposition of one admitted raw input: suppressed by the
345
+ * confidence gate, filtered by a disproven validation, folded into a
346
+ * synthesized (multi-input) finding, or surviving as its own finding.
347
+ * Mutually exclusive and exhaustive over every entry in
348
+ * `PrepareOutput['confidence_dispositions']` -- a rejected input never
349
+ * reaches this vocabulary at all, since whole-payload rejection happens
350
+ * before the `prepare` phase and is counted separately (see
351
+ * `FinalDispositionCounts.rejected`). */
352
+ export type FinalInputDisposition = 'suppressed' | 'filtered' | 'merged' | 'surviving';
353
+ /** One admitted raw input's final disposition. `reason` carries the
354
+ * confidence-gate suppression reason when present; every other disposition
355
+ * has no reason of its own to report here. */
356
+ export interface FinalizedInputDisposition {
357
+ readonly input_id: string;
358
+ readonly disposition: FinalInputDisposition;
359
+ readonly reason?: string;
360
+ }
361
+ /** One rejected reviewer payload's weight for the disposition-count total:
362
+ * the number of findings a whole-payload rejection discarded, exactly as
363
+ * `screenReviewReturn` recorded it in `ScreenRejectedSummarySchema`. A
364
+ * rejected payload contributes one weighted entry here, never one row per
365
+ * discarded finding -- so the disposition-count total stays anchored to
366
+ * findings actually observed rather than to ledger row count. */
367
+ export interface RejectedPayloadWeight {
368
+ readonly rejected_finding_count: number;
369
+ }
370
+ /** Every admitted-plus-rejected disposition count, weighted by findings
371
+ * observed rather than by ledger rows. `rejected` sums every
372
+ * `RejectedPayloadWeight.rejected_finding_count`; the other four fields each
373
+ * count one `FinalizedInputDisposition` entry, excluding any entry owned by
374
+ * a `validation_unavailable` persona -- the same exclusion
375
+ * `buildAdmittedLedgerRows` applies to the ledger, so the four admitted-
376
+ * weight fields always sum to the admitted row count in the ledger
377
+ * `finalizeReview` builds alongside it. That invariant is asserted by
378
+ * `checkDispositionCountsReconcileLedger` in `finalizeReview`, not left
379
+ * aspirational. */
380
+ export interface FinalDispositionCounts {
381
+ readonly surviving: number;
382
+ readonly merged: number;
383
+ readonly suppressed: number;
384
+ readonly filtered: number;
385
+ readonly rejected: number;
386
+ }
387
+ /** Where one actionable, surviving finding routes to: a fixer can take it
388
+ * directly (`owner: 'review-fixer'`), it needs a human or a downstream
389
+ * resolver (`owner: 'downstream-resolver' | 'human'`), or it is terminal and
390
+ * report-only (`owner: 'release'`). Derived solely from the finding's own
391
+ * mechanically-computed `owner` field -- never from anything a model
392
+ * proposed beyond the narrowing `deriveMergedFindingFields` already
393
+ * validated. */
394
+ export type FindingActionRoute = 'fixer' | 'residual' | 'report_only';
395
+ /** One finding placed in an action queue, or reported via `new_findings` /
396
+ * `pre_existing_findings` outside any queue. A finding whose validator run
397
+ * failed or was unavailable (present in `reconciled.lifecycle_failures`) is
398
+ * excluded from every action queue entirely -- nobody disproved it, but
399
+ * nobody confirmed it either, so `partitionFindings` will not auto-action
400
+ * it. It still surfaces through `new_findings` (or `pre_existing_findings`)
401
+ * with `unconfirmed: true`, and `reconcileValidatorResults`'s degraded
402
+ * validator lifecycle keeps the run from reaching a clean verdict
403
+ * (`deriveVerdict`'s `validator lifecycle degraded` blocking reason). As a
404
+ * consequence, `unconfirmed` is always `false` on every entry actually
405
+ * placed in `queues.fixer` / `queues.residual` / `queues.report_only` --
406
+ * see `partitionFindings`'s `an unconfirmed finding never enters an action
407
+ * queue` test. */
408
+ export interface QueuedFinding {
409
+ readonly finding_id: string;
410
+ readonly unconfirmed: boolean;
411
+ }
412
+ /** One reported finding that is not in any action queue because it predates
413
+ * the current change: every one of its contributing raw inputs was already
414
+ * `pre_existing`. Still reported, but not the same actionable class as a
415
+ * newly introduced finding. */
416
+ export interface PreExistingFinding {
417
+ readonly finding_id: string;
418
+ readonly unconfirmed: boolean;
419
+ }
420
+ export interface FinalizeReviewDispositionsInput {
421
+ readonly prepared: PrepareOutput;
422
+ readonly reconciled: ReconcileValidatorResultsOutput;
423
+ readonly rejected_payloads: readonly RejectedPayloadWeight[];
424
+ readonly screen_results: FinalizeScreenResults;
425
+ readonly dispatch_records: FinalizeDispatchRecords;
426
+ }
427
+ export interface FinalizeReviewDispositionsOutput {
428
+ readonly input_dispositions: readonly FinalizedInputDisposition[];
429
+ readonly disposition_counts: FinalDispositionCounts;
430
+ readonly pre_existing_findings: readonly PreExistingFinding[];
431
+ readonly new_findings: readonly QueuedFinding[];
432
+ readonly queues: {
433
+ readonly fixer: readonly QueuedFinding[];
434
+ readonly residual: readonly QueuedFinding[];
435
+ readonly report_only: readonly QueuedFinding[];
436
+ };
437
+ }
438
+ type FinalizeReviewDispositionsRejectReason = 'survivor missing from merged findings';
439
+ export interface FinalizeReviewDispositionsRejection {
440
+ readonly path: string;
441
+ readonly reason: FinalizeReviewDispositionsRejectReason;
442
+ }
443
+ export type FinalizeReviewDispositionsResult = {
444
+ readonly ok: true;
445
+ readonly value: FinalizeReviewDispositionsOutput;
446
+ } | {
447
+ readonly ok: false;
448
+ readonly rejection: FinalizeReviewDispositionsRejection;
449
+ };
450
+ /**
451
+ * The finalize-phase dispositions, counts, and action-queue step: derives
452
+ * every admitted input's final disposition, the weighted disposition
453
+ * counts (including the rejected-payload weight), the pre-existing/new
454
+ * finding split, and the three mutually exclusive, collectively exhaustive
455
+ * action queues (`fixer`, `residual`, `report_only`) that partition every
456
+ * surviving actionable finding by its mechanically-derived `owner`.
457
+ *
458
+ * Every list is sorted by a stable key (`input_id` or `finding_id`), so
459
+ * identical input in a different order always produces byte-identical
460
+ * output. This step never builds risk coverage, plan-assessment routing, or
461
+ * the final artifact -- those are separate slices. Never reads
462
+ * `process.env`, the filesystem, or the clock.
463
+ */
464
+ export declare function finalizeReviewDispositions(input: FinalizeReviewDispositionsInput): FinalizeReviewDispositionsResult;
465
+ /** One risk-critical persona whose dispatch was lost, paired with the
466
+ * selection surface it was recorded as covering. */
467
+ export interface LostRiskCriticalPersona {
468
+ readonly persona: string;
469
+ readonly selection_surface: readonly string[];
470
+ }
471
+ export interface DeriveRiskCoverageInput {
472
+ readonly lost_risk_critical_personas: readonly LostRiskCriticalPersona[];
473
+ readonly prepared: PrepareOutput;
474
+ readonly reconciled: ReconcileValidatorResultsOutput;
475
+ }
476
+ /** One lost persona's coverage verdict. `finding_id` and `input_finding_id`
477
+ * are present only when `satisfied` is `true`: `finding_id` names the
478
+ * reconciled finding whose evidence covers the lost surface, and
479
+ * `input_finding_id` names the specific admitted input row -- owned by a
480
+ * different persona than the lost one -- that finding cites as its cross-
481
+ * persona evidence. */
482
+ export interface RiskCoverageDerivation {
483
+ readonly persona: string;
484
+ readonly satisfied: boolean;
485
+ readonly finding_id?: string;
486
+ readonly input_finding_id?: string;
487
+ }
488
+ /**
489
+ * Derives risk-critical replacement coverage for every lost risk-critical
490
+ * persona: whether a *different* persona's validated, on-surface evidence
491
+ * independently covers the surface the lost persona was selected for. This
492
+ * step never identifies which personas are risk-critical or lost, never
493
+ * builds plan-assessment routing, and never assembles the final artifact --
494
+ * those are separate slices. Never reads `process.env`, the filesystem, or
495
+ * the clock.
496
+ */
497
+ export declare function deriveRiskCoverage(input: DeriveRiskCoverageInput): readonly RiskCoverageDerivation[];
498
+ /** One plan-assessment result the model produced while checking the plan's
499
+ * stated requirements against the work. `explicit_unmet_requirement` is a
500
+ * requirement the plan stated outright and the work demonstrably did not
501
+ * meet -- residual actionable work that gates the verdict.
502
+ * `inferred_gap` is something the model suspects is missing but the plan
503
+ * never stated outright -- advisory output only, and never gates the
504
+ * verdict by itself. Deliberately carries no persona, no evidence, and no
505
+ * input ID: unlike a reviewer's finding, nobody reviewed a line of code to
506
+ * produce it, so it must never be mistaken for one. */
507
+ export type PlanAssessmentResult = z.infer<typeof PlanAssessmentEnvelopeSchema>['results'][number];
508
+ export interface RoutePlanAssessmentInput {
509
+ /** Every plan-assessment result the model returned. Empty when the run
510
+ * had no plan to assess against -- never fabricated, and never a reason
511
+ * to silently relax the verdict gate. */
512
+ readonly results: readonly PlanAssessmentResult[];
513
+ }
514
+ export interface RoutedPlanAssessment {
515
+ readonly residual_actionable_work: readonly string[];
516
+ readonly advisory_outputs: readonly string[];
517
+ readonly gated_by_explicit_unmet_requirement: boolean;
518
+ }
519
+ /**
520
+ * Routes plan-assessment results into their two output channels: explicit
521
+ * unmet requirements become residual actionable work and gate the verdict;
522
+ * inferred gaps become advisory-only output and never gate the verdict on
523
+ * their own. A run with no plan assessment (`results` empty) produces empty
524
+ * output on both channels and an ungated verdict -- never fabricated
525
+ * entries, never a silently relaxed gate. `PlanAssessmentResult` has no
526
+ * persona or input-ID field to carry, so a plan-assessment result can never
527
+ * be attributed to a reviewer or cross-referenced against the input ledger
528
+ * by construction, and this function never touches a findings collection.
529
+ * Output is sorted for determinism, so identical input in a different order
530
+ * always produces byte-identical output.
531
+ */
532
+ export declare function routePlanAssessment(input: RoutePlanAssessmentInput): RoutedPlanAssessment;
533
+ export interface RunReviewPipelineInput {
534
+ readonly merge: MergeOutput;
535
+ readonly validator_lifecycle_results: ValidatorLifecycleResults;
536
+ readonly prepared: PrepareOutput;
537
+ readonly rejected_payloads: readonly RejectedPayloadWeight[];
538
+ readonly lost_risk_critical_personas: readonly LostRiskCriticalPersona[];
539
+ readonly plan_assessment: RoutePlanAssessmentInput;
540
+ readonly screen_results: FinalizeScreenResults;
541
+ readonly dispatch_records: FinalizeDispatchRecords;
542
+ }
543
+ /** One fact that withheld a clean verdict. Each `kind` is a distinct,
544
+ * independently-triggered block -- never collapsed into a shared shape --
545
+ * so a reader can always tell which of the three gating conditions fired
546
+ * and, within `explicit_unmet_plan_requirement` and
547
+ * `unsatisfied_risk_coverage`, which specific requirement or persona is
548
+ * responsible. */
549
+ export type VerdictBlockingReason = {
550
+ readonly kind: 'explicit_unmet_plan_requirement';
551
+ readonly description: string;
552
+ } | {
553
+ readonly kind: 'unsatisfied_risk_coverage';
554
+ readonly persona: string;
555
+ } | {
556
+ readonly kind: 'degraded_validator_lifecycle';
557
+ readonly finding_id: string;
558
+ readonly outcome: 'failed' | 'unavailable';
559
+ };
560
+ export interface ReviewRunVerdict {
561
+ readonly clean: boolean;
562
+ readonly blocking_reasons: readonly VerdictBlockingReason[];
563
+ }
564
+ export interface RunReviewPipelineOutput {
565
+ readonly reconciled: ReconcileValidatorResultsOutput;
566
+ readonly finalized: FinalizeReviewDispositionsOutput;
567
+ readonly risk_coverage: readonly RiskCoverageDerivation[];
568
+ readonly plan_assessment: RoutedPlanAssessment;
569
+ readonly verdict: ReviewRunVerdict;
570
+ }
571
+ export type RunReviewPipelineResult = {
572
+ readonly ok: true;
573
+ readonly value: RunReviewPipelineOutput;
574
+ } | {
575
+ readonly ok: false;
576
+ readonly rejection: ReconcileValidatorResultsRejection | FinalizeReviewDispositionsRejection;
577
+ };
578
+ /**
579
+ * Runs the full synthesis pipeline's finalize-and-verdict slice: calls
580
+ * `reconcileValidatorResults`, `finalizeReviewDispositions`,
581
+ * `deriveRiskCoverage`, and `routePlanAssessment` in order, then derives the
582
+ * run's verdict from their already-computed output. Never re-derives
583
+ * anything those four steps compute -- this is composition, not a fifth
584
+ * derivation. A rejection from `reconcileValidatorResults` aborts the whole
585
+ * run and is returned unchanged, with no partial output from the later
586
+ * steps. This step never builds the final artifact or the
587
+ * writing/report-only discriminated output -- that is a separate slice.
588
+ * Never reads `process.env`, the filesystem, or the clock.
589
+ */
590
+ export declare function runReviewPipeline(input: RunReviewPipelineInput): RunReviewPipelineResult;
591
+ type BuildInputLedgerScreenResult = FinalizeScreenResults[number];
592
+ type BuildInputLedgerRejectedSummary = NonNullable<BuildInputLedgerScreenResult['result']['rejected_summary']>;
593
+ /** One admitted raw input's ledger row: its owning reviewer, the confidence
594
+ * the reviewer reported, its final disposition, and a required reason --
595
+ * the confidence-gate reason for `suppressed`, the disproving validator's
596
+ * reason for `filtered`, and a fixed phrase for `surviving`/`merged`. */
597
+ export interface AdmittedInputLedgerRow {
598
+ readonly record_type: 'admitted';
599
+ readonly input_id: string;
600
+ readonly reviewer: string;
601
+ readonly confidence: number;
602
+ readonly disposition: FinalInputDisposition;
603
+ readonly reason: string;
604
+ }
605
+ /** One whole-payload rejection's ledger row. Only emitted when the screen
606
+ * result actually carried a `rejected_summary` -- an unknowable rejected
607
+ * count (KTD21) produces no row at all. */
608
+ export interface RejectedInputLedgerRow {
609
+ readonly record_type: 'rejected_summary';
610
+ readonly reviewer: string;
611
+ readonly dispatch_outcome: BuildInputLedgerRejectedSummary['dispatch_outcome'];
612
+ readonly rejected_finding_count: number;
613
+ readonly rejected_severities: BuildInputLedgerRejectedSummary['rejected_severities'];
614
+ readonly disposition: 'rejected';
615
+ readonly reason: string;
616
+ }
617
+ export type InputLedgerRow = AdmittedInputLedgerRow | RejectedInputLedgerRow;
618
+ export interface BuildInputLedgerInput {
619
+ readonly prepared: PrepareOutput;
620
+ readonly screen_results: FinalizeScreenResults;
621
+ readonly dispatch_records: FinalizeDispatchRecords;
622
+ readonly finalized: FinalizeReviewDispositionsOutput;
623
+ readonly reconciled: ReconcileValidatorResultsOutput;
624
+ }
625
+ /**
626
+ * Builds the artifact's input-finding ledger: one admitted row per
627
+ * `prepared.confidence_dispositions` entry, sorted by input ID, followed by
628
+ * one rejected-summary row per screen result that carried a
629
+ * `rejected_summary`, sorted by reviewer. A `validation_unavailable`
630
+ * persona contributes no row of either kind, and a rejection with no
631
+ * summary (KTD21) contributes no row at all -- never a fabricated count.
632
+ * Never reads `process.env`, the filesystem, or the clock.
633
+ */
634
+ export declare function buildInputLedger(input: BuildInputLedgerInput): readonly InputLedgerRow[];
635
+ export interface BuildReviewCoverageInput {
636
+ readonly dispatch_records: FinalizeDispatchRecords;
637
+ readonly validator_lifecycle_results: ValidatorLifecycleResults;
638
+ readonly screen_results: FinalizeScreenResults;
639
+ readonly reconciled: ReconcileValidatorResultsOutput;
640
+ readonly merge: MergeOutput;
641
+ }
642
+ export interface ReviewCoverageSummary {
643
+ readonly reviewers: number;
644
+ readonly validators: number;
645
+ readonly residual_risks: readonly string[];
646
+ readonly testing_gaps: readonly string[];
647
+ readonly failed_reviewers: readonly string[];
648
+ readonly validator_failures: readonly string[];
649
+ readonly intent_uncertainty: readonly string[];
650
+ }
651
+ type BuildReviewCoverageRejectReason = 'coverage array exceeds bound';
652
+ export interface BuildReviewCoverageRejection {
653
+ readonly path: string;
654
+ readonly reason: BuildReviewCoverageRejectReason;
655
+ }
656
+ export type BuildReviewCoverageResult = {
657
+ readonly ok: true;
658
+ readonly value: ReviewCoverageSummary;
659
+ } | {
660
+ readonly ok: false;
661
+ readonly rejection: BuildReviewCoverageRejection;
662
+ };
663
+ /**
664
+ * Aggregates screen-phase and validator-phase evidence into the artifact's
665
+ * `coverage` shape: deduped, sorted unions of residual risks and testing
666
+ * gaps; the personas whose dispatch outcome was `malformed`,
667
+ * `never_returned`, or `validation_unavailable`; the disproving reasons for
668
+ * every requested validator that never answered; and the merge phase's
669
+ * disagreement facts, carried through unchanged. Every array is checked
670
+ * against `MAX_PERSONAS` before assembly -- an overflow rejects rather than
671
+ * silently truncating. Never reads `process.env`, the filesystem, or the
672
+ * clock.
673
+ */
674
+ export declare function buildReviewCoverage(input: BuildReviewCoverageInput): BuildReviewCoverageResult;
675
+ export interface SynthesizedFindingProvenanceProjection {
676
+ readonly fingerprint: string;
677
+ readonly submitters: readonly string[];
678
+ readonly agreement_credit: readonly string[];
679
+ }
680
+ export interface SynthesizedFindingProjection {
681
+ readonly title: string;
682
+ readonly severity: ReconciledFinding['severity'];
683
+ readonly file: string;
684
+ readonly line: number;
685
+ readonly why_it_matters: string;
686
+ readonly autofix_class: ReconciledFinding['autofix_class'];
687
+ readonly owner: ReconciledFinding['owner'];
688
+ readonly requires_verification: boolean;
689
+ readonly confidence: number;
690
+ readonly evidence: ReconciledFinding['evidence'];
691
+ readonly pre_existing: boolean;
692
+ readonly suggested_fix?: ReconciledFinding['suggested_fix'];
693
+ readonly validated?: boolean;
694
+ readonly validation_reason?: string;
695
+ readonly input_finding_ids: readonly string[];
696
+ readonly provenance: SynthesizedFindingProvenanceProjection;
697
+ }
698
+ export interface ProjectSynthesizedFindingsInput {
699
+ readonly findings: readonly ReconciledFinding[];
700
+ }
701
+ /**
702
+ * Projects every reconciled finding into the artifact's synthesized-finding
703
+ * shape, in the reconciled order. Nests `fingerprint`, `submitters`, and
704
+ * `agreement_credit` under `provenance` (absent `agreement_credit` projects
705
+ * to an empty list, never omitted), carries `validated`/`validation_reason`
706
+ * through unchanged, and never emits the flat, helper-only `finding_id`.
707
+ * This is projection only -- it never re-validates referential integrity
708
+ * against the input ledger; `ReviewArtifactSchema.parse` is the actual
709
+ * enforcement point for that. Never reads `process.env`, the filesystem, or
710
+ * the clock.
711
+ */
712
+ export declare function projectSynthesizedFindings(input: ProjectSynthesizedFindingsInput): readonly SynthesizedFindingProjection[];
713
+ export type FinalizeReviewInput = FinalizeInputValue;
714
+ type FinalizeOutputValue = ReturnType<typeof FinalizeOutputSchema.parse>;
715
+ type FinalizeReviewRejection = FinalizeContextRejection | ReconcileValidatorResultsRejection | FinalizeReviewDispositionsRejection | BuildReviewCoverageRejection | RiskCoverageSemanticsRejection | DispositionLedgerReconciliationRejection | {
716
+ readonly path: string;
717
+ readonly reason: 'artifact failed schema validation';
718
+ } | {
719
+ readonly path: string;
720
+ readonly reason: 'finalize output failed schema validation';
721
+ };
722
+ export type FinalizeReviewResult = {
723
+ readonly ok: true;
724
+ readonly value: FinalizeOutputValue;
725
+ } | {
726
+ readonly ok: false;
727
+ readonly rejection: FinalizeReviewRejection;
728
+ };
729
+ interface ArtifactDispatchEntry {
730
+ readonly persona: string;
731
+ readonly dispatch_outcome: FinalizeDispatchRecord['dispatch_outcome'];
732
+ readonly input_finding_count: number;
733
+ readonly rejection_reason?: string;
734
+ readonly selection_surface?: readonly string[];
735
+ readonly selection_reason?: string;
736
+ }
737
+ interface ArtifactRiskCoverageEntry {
738
+ readonly persona: string;
739
+ readonly satisfied: boolean;
740
+ readonly input_finding_id?: string;
741
+ }
742
+ interface RiskCoverageSemanticsInput {
743
+ readonly dispatches: readonly ArtifactDispatchEntry[];
744
+ readonly findings: readonly SynthesizedFindingProjection[];
745
+ readonly risk_coverage?: readonly ArtifactRiskCoverageEntry[];
746
+ }
747
+ interface RiskCoverageSemanticsRejection {
748
+ readonly path: string;
749
+ readonly reason: 'satisfied risk coverage must cite a validated finding on the lost persona selection surface';
750
+ }
751
+ type RiskCoverageSemanticsResult = {
752
+ readonly ok: true;
753
+ } | {
754
+ readonly ok: false;
755
+ readonly rejection: RiskCoverageSemanticsRejection;
756
+ };
757
+ /**
758
+ * Defensive pipeline-side assertion that every satisfied risk-coverage entry
759
+ * cites a validated finding on the lost persona's recorded selection
760
+ * surface. `deriveRiskCoverage` already guarantees this at derivation time
761
+ * -- `isEligibleRiskCoverageCandidate` enforces the on-surface rule and
762
+ * `isValidationBandEligible` enforces the validation-band rule before a
763
+ * candidate can ever be cited -- so this check restates that guarantee at
764
+ * the pipeline boundary rather than deriving it independently, protecting
765
+ * against a future regression in artifact assembly. Surfaces compare
766
+ * through `normalizeRepoRelativePath` so alternate spellings (mixed
767
+ * separators, dot segments) of the same surface entry still match. This is
768
+ * the pipeline-owned counterpart to `ReviewArtifactSchema`'s structural
769
+ * `risk_coverage` refinement, which only checks referential integrity.
770
+ * Never reads `process.env`, the filesystem, or the clock.
771
+ */
772
+ export declare function checkRiskCoverageSemantics(artifactCandidate: RiskCoverageSemanticsInput): RiskCoverageSemanticsResult;
773
+ interface DispositionLedgerReconciliationRejection {
774
+ readonly path: string;
775
+ readonly reason: 'disposition counts do not reconcile with the admitted input ledger';
776
+ }
777
+ type DispositionLedgerReconciliationResult = {
778
+ readonly ok: true;
779
+ } | {
780
+ readonly ok: false;
781
+ readonly rejection: DispositionLedgerReconciliationRejection;
782
+ };
783
+ /**
784
+ * Defensive pipeline-side assertion that `disposition_counts`' four
785
+ * admitted-weight fields (`surviving` + `merged` + `suppressed` +
786
+ * `filtered`) sum to the number of `record_type: 'admitted'` rows in the
787
+ * independently-derived input ledger -- the documented invariant on
788
+ * `FinalDispositionCounts` ("the five fields always sum to the total
789
+ * findings observed"), restated here as the actual verifier instead of left
790
+ * aspirational. `disposition_counts` (via `deriveInputDispositions`) and
791
+ * the ledger (via `buildAdmittedLedgerRows`) both exclude
792
+ * `validation_unavailable` personas by construction, so this should never
793
+ * trip in a correctly wired pipeline; it exists to catch a future
794
+ * regression that lets the two derivations drift apart, the same role
795
+ * `checkRiskCoverageSemantics` plays for risk coverage. Never reads
796
+ * `process.env`, the filesystem, or the clock.
797
+ */
798
+ export declare function checkDispositionCountsReconcileLedger(dispositionCounts: FinalDispositionCounts, ledger: readonly InputLedgerRow[]): DispositionLedgerReconciliationResult;
799
+ /**
800
+ * Runs the full finalize envelope: `deriveFinalizeContext`, then
801
+ * `runReviewPipeline`, then the input ledger, review coverage, and
802
+ * synthesized-finding projections, then assembles the report projection
803
+ * both output kinds share. A `report-only` run returns that projection
804
+ * directly; every other mode also builds and parses the full
805
+ * `ReviewArtifactSchema` artifact, returning a rejection with the failing
806
+ * Zod path (never the message or input) and no partial output if it fails
807
+ * to parse. The whole result is parsed through `FinalizeOutputSchema`
808
+ * before returning. Any rejection from any step aborts the run with no
809
+ * partial output. Never reads `process.env`, the filesystem, or the clock.
810
+ */
811
+ export declare function finalizeReview(input: FinalizeReviewInput): FinalizeReviewResult;
812
+ export {};