@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.
- package/HARNESSES.md +8 -4
- package/dist/ce-review-validator.d.ts +43 -2
- package/dist/cli.js +10 -16
- package/dist/lib/review-artifact-schema.d.ts +18 -1
- package/dist/lib/review-pipeline-contract.d.ts +1446 -0
- package/dist/lib/review-pipeline.d.ts +812 -0
- package/dist/lib/review-return-validator.d.ts +19 -0
- package/package.json +1 -1
- package/skills/ce-review/SKILL.md +54 -83
- package/skills/ce-review/references/pipeline-invocation.md +263 -0
- package/skills/ce-review/references/review-output-template.md +2 -1
- package/skills/ce-review/references/review-pipeline-schema.json +391 -0
- package/skills/ce-review/references/subagent-template.md +1 -1
- package/skills/ce-review/references/synthesis-artifact-contract.md +57 -68
- package/skills/ce-review/scripts/validate-review.mjs +3277 -58
|
@@ -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 {};
|