@fro.bot/systematic 3.18.2 → 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,263 @@
1
+ # Pipeline Phase Invocation
2
+
3
+ This is the canonical invocation detail for the four pure stdin phases of the
4
+ packaged validator shim that `ce:review`'s Stage 4-6 and post-review handoff
5
+ call at their decision boundaries: `screen`, `prepare`, `merge`, and
6
+ `finalize`. `SKILL.md` keeps
7
+ terse call sites; this document carries the envelope shapes and the full
8
+ invocation blocks. `return` and `artifact` are unchanged and documented at
9
+ their existing call sites (Stage 4 raw-return history and the
10
+ [synthesis artifact contract](./synthesis-artifact-contract.md)).
11
+
12
+ Every block below reassigns `SKILL_DIR` in the same fenced block, terminated
13
+ with `;`, and invokes `node "$SKILL_DIR/scripts/validate-review.mjs" <phase>`
14
+ -- never a bare relative path, and never a Claude-only path substitution
15
+ (off-Claude harnesses would silently expand it to nothing).
16
+
17
+ ## Never-bypass
18
+
19
+ A helper failure (exit 1 or exit 2) is never permission to hand-synthesize
20
+ the phase's output. On exit 2 (usage error, TTY input, or a stdin read
21
+ failure), retry the launch exactly once with byte-identical input; a second
22
+ exit 2 is a launch failure, not a payload problem. On exit 1 (the input
23
+ was structurally rejected), the parent may correct its own JSON envelope
24
+ exactly once -- fixing a genuine encoding mistake, never reshaping the
25
+ envelope to force acceptance -- and retry; a second exit 1 stops the run
26
+ visibly with degraded or abnormal status. Never fall back to writing a
27
+ finding, a merged finding, a queue, a disposition count, or a report section
28
+ by hand because a phase call failed.
29
+
30
+ ## screen
31
+
32
+ Structurally admits one persona's raw return and binds it to the dispatched
33
+ persona, replacing the former separate raw-return-admission and
34
+ dispatch-identity-binding steps with one call. Feed the persona's raw JSON
35
+ return on stdin through a fresh single-quoted heredoc delimiter -- never
36
+ argv, command substitution, or a temp file -- exactly as the packaged
37
+ validator's `return` subcommand was invoked before this phase replaced it.
38
+
39
+ **Output** (`exit 0`): `{ dispatch_outcome, admitted_findings[{input_id, ...finding}], rejected_summary?{dispatch_outcome, rejected_finding_count, rejected_severities, reason}, residual_risks[], testing_gaps[], harness }`.
40
+ `admitted_findings` carries each finding with a stable `<reviewer>#<index>`
41
+ `input_id` and `disposition: "surviving"` pre-assigned. A whole-payload
42
+ rejection (malformed JSON, schema violation, or an identity mismatch between
43
+ the return's `reviewer` field and the dispatched persona) never admits any
44
+ finding from that return.
45
+
46
+ Before each invocation, choose a fresh delimiter for that exact raw payload
47
+ over a safe token alphabet (`A-Z`, `0-9`, `_`), for example a random hex
48
+ token, and verify the delimiter is absent as a complete line in that exact
49
+ raw payload before running. Never reuse a fixed delimiter across payloads.
50
+ Open the heredoc with a single-quoted heredoc opener (`<<'DELIM'`) so the
51
+ payload is never interpolated, and close it with a line containing exactly
52
+ that delimiter.
53
+
54
+ ```bash
55
+ # Resolve the validator relative to the skill's own directory.
56
+ SKILL_DIR="<skill directory stated when this skill loads>";
57
+ node "$SKILL_DIR/scripts/validate-review.mjs" screen --reviewer <persona> --harness <opencode|pi|claude-code> <<'SCREEN_INPUT_A1B2C3D4'
58
+ <the persona's returned JSON payload, copied verbatim>
59
+ SCREEN_INPUT_A1B2C3D4
60
+ ```
61
+
62
+ Read the exit status:
63
+
64
+ - **exit 0** -- structurally admitted. Parse the already structurally
65
+ validated JSON without logging the raw text; the parent may then attach
66
+ both `harness` and `dispatch_outcome` from this result to the persisted
67
+ per-agent dispatch record. `dispatch_outcome: "empty"` means zero
68
+ findings; `"findings"` means one or more admitted findings.
69
+ - **exit 1** -- the whole return is `dispatch_outcome: "malformed"`, covering
70
+ malformed JSON, a schema violation, and a `reviewer`-field identity
71
+ mismatch alike. Retain only the bounded validator diagnostic on stderr in
72
+ Coverage; never parse or persist payload fields or values.
73
+ - **exit 2**, a missing or unreadable helper, or a command launch failure --
74
+ validation unavailable. Withhold the return and report the exact
75
+ unavailability and what was withheld. Update that selected persona's
76
+ preinitialized dispatch entry from `never_returned` to `dispatch_outcome:
77
+ "validation_unavailable"` with `input_finding_count: 0`, and set
78
+ `run_status` to `degraded`. A run containing `validation_unavailable`
79
+ evidence can never finalize as `completed`, and that persona must not have
80
+ an admitted input finding. `validation_unavailable` is not `malformed` and
81
+ is not `never_returned`; they are distinct coverage states. `never_returned`
82
+ is a task-lifecycle fact for a task that did not return at all, recorded
83
+ without invoking the validator.
84
+
85
+ Structural validity never implies evidence validity. A return that passes
86
+ `screen` is admitted structurally only; its claims still require evidence
87
+ assessment during adjudication.
88
+
89
+ ## prepare
90
+
91
+ Applies the confidence gate, forms dedup candidate groups, and unions
92
+ selection-surface coverage across every screened return -- deterministic
93
+ parent-owned bookkeeping that no longer needs model recomputation.
94
+
95
+ **Input:** `{ screen_results[{reviewer, result:<screen output>}], selected_dispatches[{persona, dispatch_outcome, selection_surface?}] }`.
96
+ Assemble `screen_results` from every `screen` call made in this run, and
97
+ `selected_dispatches` from the Stage 3 selection record (including any
98
+ persona whose dispatch never produced a screen result, so `never_returned`
99
+ and `validation_unavailable` personas are represented too).
100
+
101
+ **Output:** `{ confidence_dispositions, coverage_union, singletons, candidate_groups[{file, members[{input_id,line}]}], surviving_findings }`.
102
+ `candidate_groups` are the file-grouped, line-sorted sets of two or more
103
+ admitted findings from different personas that the model must adjudicate in
104
+ `merge`. `singletons` are admitted findings that passed the confidence gate
105
+ but did not land in any candidate group -- they need no adjudication and flow
106
+ straight through. `confidence_dispositions` and `coverage_union` are the
107
+ suppressed-finding ledger and the unioned selection-surface coverage list
108
+ respectively; both feed later phases and Coverage reporting without further
109
+ recomputation.
110
+
111
+ ```bash
112
+ # Resolve the validator relative to the skill's own directory.
113
+ SKILL_DIR="<skill directory stated when this skill loads>";
114
+ node "$SKILL_DIR/scripts/validate-review.mjs" prepare <<'PREPARE_INPUT_A1B2C3D4'
115
+ { "screen_results": [...], "selected_dispatches": [...] }
116
+ PREPARE_INPUT_A1B2C3D4
117
+ ```
118
+
119
+ Choose a fresh delimiter the same way as `screen`'s. Read the exit status:
120
+
121
+ - **exit 0** -- the aggregate envelope validated; parse the JSON result and
122
+ carry it into adjudication.
123
+ - **exit 1** -- the aggregate envelope was rejected (malformed JSON or a
124
+ structural violation against the screen results and selected dispatches
125
+ supplied). Never hand-assemble a substitute `prepare` output.
126
+ - **exit 2**, a missing helper, or a launch failure -- unavailable; retry
127
+ once with identical bytes per the never-bypass rule above, then stop
128
+ visibly.
129
+
130
+ ## merge
131
+
132
+ Applies the model's adjudication decisions to `prepare`'s candidate groups,
133
+ deriving each merged finding's severity, confidence (including the
134
+ cross-reviewer agreement boost), fingerprint, submitters, and conservatively
135
+ narrowed route -- the model supplies judgment per candidate group; the helper
136
+ supplies the arithmetic and the narrowing rule.
137
+
138
+ **Input:** `{ prepared:<prepare output>, adjudication:<model envelope with decisions[]> }`.
139
+ Build one `decisions[]` entry for every candidate-group member: a `merged`
140
+ decision citing 2+ input IDs from the same group with the merged finding's
141
+ `title`, `why_it_matters`, `evidence`, `line`, `proposed_route` (when the
142
+ merge's route should narrow), `route_narrowing_reason` (required whenever
143
+ `proposed_route` is present), and optional `disagreement_facts` and
144
+ `eligible_agreement_credit`; or a `declined` decision citing exactly one
145
+ input ID with a `declined_reason` explaining why it stays a separate defect.
146
+ Every candidate-group member must be cited by exactly one decision -- no
147
+ omissions, no double-citations. `prepare`'s true singletons need no decision
148
+ at all.
149
+
150
+ **Output:** `{ merged_findings, validator_requests[{finding_id,file,line}], disagreement_facts }`.
151
+ `validator_requests` names the merged findings that fall inside the Stage 5b
152
+ gating band (P0/P1 severity, or `requires_verification: true`) -- dispatch
153
+ exactly one validator subagent per entry, looking up that finding's full
154
+ fields from `merged_findings` by `finding_id`.
155
+
156
+ ```bash
157
+ # Resolve the validator relative to the skill's own directory.
158
+ SKILL_DIR="<skill directory stated when this skill loads>";
159
+ node "$SKILL_DIR/scripts/validate-review.mjs" merge <<'MERGE_INPUT_A1B2C3D4'
160
+ { "prepared": { ... }, "adjudication": { "decisions": [...] } }
161
+ MERGE_INPUT_A1B2C3D4
162
+ ```
163
+
164
+ Choose a fresh delimiter the same way as `screen`'s. Read the exit status:
165
+
166
+ - **exit 0** -- the adjudication envelope validated against the prepared
167
+ candidate set; parse the JSON result and carry `merged_findings` and
168
+ `validator_requests` forward.
169
+ - **exit 1** -- the envelope was rejected: malformed JSON, a schema
170
+ violation, an omitted or double-cited candidate member, or a merged
171
+ decision inconsistent with its cited members. Correct the envelope once
172
+ per the never-bypass rule, then stop visibly if it is rejected again.
173
+ Never hand-assemble a substitute merged-finding set.
174
+ - **exit 2**, a missing helper, or a launch failure -- unavailable; retry
175
+ once with identical bytes, then stop visibly.
176
+
177
+ ## finalize
178
+
179
+ Synthesizes the run's final report and, in writing modes, the persistable
180
+ artifact: reconciles validator lifecycle results against `merge`'s
181
+ `validator_requests`, routes the model's plan-assessment results into
182
+ residual actionable work and advisory output, derives the risk-aware
183
+ verdict, and computes every queue, disposition count, and coverage entry.
184
+ Called once with `applied_fixes: []` to obtain the queues that drive fix
185
+ dispatch; fix-applying modes call it again with the exact applied-fix
186
+ outcomes and the same validator results, and only that second call is
187
+ persisted. Report-only calls it once and writes nothing.
188
+
189
+ **Input:** `{ merge:<merge output>, prepared:<prepare output>, screen_results:<same array given to prepare>, dispatch_records:<same selected_dispatches>, validator_lifecycle_results[{finding_id, result:{outcome:'true'|'false'|'failed'|'unavailable', reason?}}], plan_assessment:{ verdict, results[{kind:'explicit_unmet_requirement'|'inferred_gap', description}] }, parent_run_metadata:{ run_id, mode:'interactive'|'autofix'|'headless'|'report-only', harness, branch, head_sha, selected_dispatches, timestamps:{started_at, completed_at}, validation:{status, reason?}, applied_fixes[] } }`.
190
+ Reuse `prepared`, `screen_results`, and `dispatch_records` wholesale from the
191
+ earlier phases rather than restating them; `finalize` re-derives the ledger,
192
+ rejected-payload weights, coverage notes, and reviewer ownership from this
193
+ carried state. `validator_lifecycle_results` carries one entry per
194
+ `validator_requests` finding ID -- `outcome: "true"` needs no reason;
195
+ `"false"`, `"failed"`, and `"unavailable"` each require one. `plan_assessment`
196
+ is the model's Stage 2b requirements check: each result's `kind` routes it --
197
+ `explicit_unmet_requirement` becomes residual actionable work and blocks a
198
+ clean verdict; `inferred_gap` becomes advisory-only output and never blocks
199
+ the verdict by itself. Neither kind becomes a finding. `parent_run_metadata`
200
+ carries the run's own identity and mode; `validation` is the artifact
201
+ self-validation envelope, but finalize is called before that
202
+ [artifact validation](./synthesis-artifact-contract.md#artifact-validation)
203
+ step can run, so the only truthful value at that point is
204
+ `{status: "not_attempted", reason}` -- finalize rejects any other value.
205
+ The post-write validation status is reported by the parent in the rendered
206
+ Coverage section, never rewritten into the persisted artifact.
207
+
208
+ **Output:** `{ kind:'writing', artifact, report }` or `{ kind:'report_only', ...report }`.
209
+ `report` carries `verdict`, `findings`, `applied_fixes`,
210
+ `residual_actionable_work`, `advisory_outputs`, `coverage`,
211
+ `input_dispositions`, `disposition_counts`, `queues{fixer,residual,report_only}`,
212
+ `pre_existing_findings`, and `risk_coverage` -- render Stage 6 directly from
213
+ this projection rather than recomputing any of it. In writing modes,
214
+ `artifact` is the exact `review-summary.json` payload to persist.
215
+
216
+ ```bash
217
+ # Resolve the validator relative to the skill's own directory.
218
+ SKILL_DIR="<skill directory stated when this skill loads>";
219
+ node "$SKILL_DIR/scripts/validate-review.mjs" finalize <<'FINALIZE_INPUT_A1B2C3D4'
220
+ { "merge": { ... }, "prepared": { ... }, "screen_results": [...], "dispatch_records": [...], "validator_lifecycle_results": [...], "plan_assessment": { ... }, "parent_run_metadata": { ... } }
221
+ FINALIZE_INPUT_A1B2C3D4
222
+ ```
223
+
224
+ Choose a fresh delimiter the same way as `screen`'s. Read the exit status:
225
+
226
+ - **exit 0** -- parse the JSON result. In writing modes, capture the exact
227
+ stdout bytes to a same-directory temp file (see
228
+ [Persisting the artifact](#persisting-the-artifact) below) before rendering
229
+ the report; report-only renders `report` directly and writes nothing.
230
+ - **exit 1** -- the aggregate envelope was rejected: a shape mismatch against
231
+ `merge`/`prepared`, a missing or extra validator-lifecycle result, an
232
+ invalid plan-assessment envelope, or an artifact that failed its own
233
+ internal schema check. Correct the envelope once per the never-bypass
234
+ rule, then stop visibly if it is rejected again. Never hand-assemble a
235
+ substitute report or artifact.
236
+ - **exit 2**, a missing helper, or a launch failure -- unavailable; retry
237
+ once with identical bytes, then stop visibly.
238
+
239
+ ### Persisting the artifact
240
+
241
+ Writing-mode `finalize` stdout is the wrapper
242
+ `{ kind: 'writing', artifact, report }`, not the artifact by itself. In
243
+ interactive, autofix, and headless modes, after the persisted `finalize` call
244
+ succeeds, extract only the captured stdout's `artifact` member -- never the
245
+ whole wrapper -- and write that JSON to a temp file created exclusively with
246
+ owner-only permissions in the same `.context/systematic/ce-review/<run-id>`
247
+ directory as the final artifact, then atomically rename it over
248
+ `review-summary.json`. Render the report from the same captured stdout's
249
+ `report` member. No `jq` dependency is assumed; Node performs the extraction:
250
+
251
+ ```bash
252
+ node -e 'const r=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write(JSON.stringify(r.artifact))' \
253
+ < "$FINALIZE_STDOUT" > "$TEMP_FILE"
254
+ ```
255
+
256
+ Remove the temp file on any non-success (a rejected `finalize` call, a write
257
+ failure, or an interrupted run) instead of leaving a partial file behind.
258
+ Only after the rename succeeds does the parent run the existing `artifact`
259
+ subcommand (see the
260
+ [synthesis artifact contract](./synthesis-artifact-contract.md#artifact-validation))
261
+ against the persisted path. Report-only never creates the run directory,
262
+ never writes a temp file, and never runs `artifact` validation -- it has no
263
+ artifact to validate.
@@ -130,6 +130,7 @@ This fails because: no pipe-delimited tables, no severity-grouped `###` headers,
130
130
  - **Route column** shows the synthesized handling decision as ``<autofix_class> -> <owner>``.
131
131
  - **Header includes** scope, intent, and reviewer team with per-conditional justifications
132
132
  - **Mode line** -- include `interactive`, `autofix`, `report-only`, or `headless`
133
+ - **Requirements Completeness section** -- include only when a plan was found in Stage 2b. Render the met/not-addressed/partially-addressed checklist, then list the plan-assessment routing output as its own bullet lists: `explicit_unmet_requirement` results appear as `report.residual_actionable_work` bullets (they gate the verdict); `inferred_gap` results appear as `report.advisory_outputs` bullets (they never gate the verdict alone). Neither kind is a finding -- they never gain a file, line, reviewer, confidence, or route, and never appear in a severity table. Omit the section entirely when no plan was found.
133
134
  - **Applied Fixes section** -- include only when a fix phase ran in this review invocation
134
135
  - **Residual Actionable Work section** -- include only when unresolved actionable findings were handed off for later work
135
136
  - **Pre-existing section** -- separate table, no confidence column (these are informational)
@@ -137,7 +138,7 @@ This fails because: no pipe-delimited tables, no severity-grouped `###` headers,
137
138
  - **Learnings & Past Solutions section** -- render only when CE `learnings-researcher` was selected and returned relevant output; results with links to docs/solutions/ files. Omit otherwise.
138
139
  - **Agent-Native Gaps section** -- render only when CE `agent-native-reviewer` was selected and returned relevant output. Omit otherwise.
139
140
  - **Deployment Notes section** -- key checklist items from deployment-verification-agent. Omit if the agent did not run.
140
- - **Coverage section** -- suppressed count with original confidences, residual risks, testing gaps, failed reviewers, disposition reconciliation, and risk-coverage entries with their citing input finding IDs and blocked-entry exit conditions. For raw returns, distinguish `findings`, `empty`, `malformed`, `never_returned`, `validation_unavailable` (the persisted raw dispatch outcome, distinct from the artifact-level `validation.status: "unavailable"`), and `environment-screen` rejection, and state what was admitted or withheld
141
+ - **Coverage section** -- suppressed count with original confidences, residual risks, testing gaps, failed reviewers, disposition reconciliation, and risk-coverage entries with their citing input finding IDs and blocked-entry exit conditions. For raw returns, distinguish `findings`, `empty`, `malformed`, `never_returned`, and `validation_unavailable` (the persisted raw dispatch outcome, distinct from the artifact-level `validation.status: "unavailable"`), and state what was admitted or withheld
141
142
  - **Summary uses blockquotes** for verdict, reasoning, and fix order
142
143
  - **Horizontal rule** (`---`) separates findings from verdict
143
144
  - **`###` headers** for each section -- never plain text headers
@@ -0,0 +1,391 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Code Review Pipeline Envelopes",
4
+ "description": "Structured output schemas for the model-authored envelopes of the ce:review synthesis pipeline (adjudication decisions, validator lifecycle results, and plan assessment). Helper-produced internal phase state (screen output, prepared state, merge output, finalize output) is intentionally excluded: it is TypeScript/Zod-only and never authored by a model.",
5
+ "definitions": {
6
+ "adjudicationEnvelope": {
7
+ "type": "object",
8
+ "properties": {
9
+ "decisions": {
10
+ "maxItems": 32,
11
+ "type": "array",
12
+ "items": {
13
+ "oneOf": [
14
+ {
15
+ "type": "object",
16
+ "properties": {
17
+ "decision_id": {
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "maxLength": 128,
21
+ "pattern": "\\S"
22
+ },
23
+ "disposition": {
24
+ "type": "string",
25
+ "const": "merged"
26
+ },
27
+ "input_finding_ids": {
28
+ "minItems": 2,
29
+ "maxItems": 32,
30
+ "type": "array",
31
+ "items": {
32
+ "type": "string",
33
+ "minLength": 1,
34
+ "maxLength": 128,
35
+ "pattern": "\\S"
36
+ }
37
+ },
38
+ "title": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "maxLength": 256,
42
+ "pattern": "\\S",
43
+ "description": "Short, specific issue title. 10 words or fewer."
44
+ },
45
+ "why_it_matters": {
46
+ "type": "string",
47
+ "minLength": 1,
48
+ "maxLength": 2048,
49
+ "pattern": "\\S",
50
+ "description": "Non-empty impact and failure mode -- not 'what is wrong' but 'what breaks'"
51
+ },
52
+ "evidence": {
53
+ "minItems": 1,
54
+ "maxItems": 5,
55
+ "type": "array",
56
+ "items": {
57
+ "anyOf": [
58
+ {
59
+ "type": "string",
60
+ "minLength": 1,
61
+ "maxLength": 500,
62
+ "allOf": [
63
+ {
64
+ "type": "string",
65
+ "pattern": "\\S"
66
+ },
67
+ {
68
+ "type": "string",
69
+ "pattern": "^(?!\\/)(?![A-Za-z]:[\\\\/])(?!\\\\).+"
70
+ }
71
+ ],
72
+ "description": "Bounded code-grounded evidence; absolute POSIX, drive-letter, and UNC paths are rejected"
73
+ },
74
+ {
75
+ "type": "object",
76
+ "properties": {
77
+ "overflow": {
78
+ "type": "boolean",
79
+ "const": true,
80
+ "description": "Explicit marker that the complete evidence did not fit in one bounded entry"
81
+ },
82
+ "excerpt": {
83
+ "type": "string",
84
+ "minLength": 1,
85
+ "maxLength": 500,
86
+ "allOf": [
87
+ {
88
+ "type": "string",
89
+ "pattern": "\\S"
90
+ },
91
+ {
92
+ "type": "string",
93
+ "pattern": "^(?!\\/)(?![A-Za-z]:[\\\\/])(?!\\\\).+"
94
+ }
95
+ ],
96
+ "description": "Bounded excerpt retained when evidence must be shortened"
97
+ }
98
+ },
99
+ "required": ["overflow", "excerpt"],
100
+ "additionalProperties": false
101
+ }
102
+ ]
103
+ },
104
+ "description": "Code-grounded evidence. At least 1 and at most 5 bounded entries; split evidence across entries or use an explicit overflow marker rather than silently truncating it."
105
+ },
106
+ "suggested_fix": {
107
+ "description": "Concrete minimal fix. Omit or null if no good fix is obvious -- a bad suggestion is worse than none.",
108
+ "anyOf": [
109
+ {
110
+ "type": "string",
111
+ "maxLength": 2048
112
+ },
113
+ {
114
+ "type": "null"
115
+ }
116
+ ]
117
+ },
118
+ "line": {
119
+ "type": "integer",
120
+ "exclusiveMinimum": 0,
121
+ "maximum": 9007199254740991,
122
+ "description": "Primary line number of the issue"
123
+ },
124
+ "disagreement_facts": {
125
+ "maxItems": 32,
126
+ "type": "array",
127
+ "items": {
128
+ "type": "string",
129
+ "minLength": 1,
130
+ "maxLength": 2048,
131
+ "pattern": "\\S"
132
+ }
133
+ },
134
+ "eligible_agreement_credit": {
135
+ "maxItems": 64,
136
+ "type": "array",
137
+ "items": {
138
+ "type": "string",
139
+ "minLength": 1,
140
+ "maxLength": 64,
141
+ "pattern": "\\S",
142
+ "description": "Persona name that produced this output (e.g., 'correctness', 'security')"
143
+ }
144
+ },
145
+ "proposed_route": {
146
+ "type": "object",
147
+ "properties": {
148
+ "autofix_class": {
149
+ "type": "string",
150
+ "enum": [
151
+ "safe_auto",
152
+ "gated_auto",
153
+ "manual",
154
+ "advisory"
155
+ ],
156
+ "description": "Reviewer's conservative recommendation for how this issue should be handled after synthesis"
157
+ },
158
+ "owner": {
159
+ "type": "string",
160
+ "enum": [
161
+ "review-fixer",
162
+ "downstream-resolver",
163
+ "human",
164
+ "release"
165
+ ],
166
+ "description": "Who should own the next action for this finding after synthesis"
167
+ },
168
+ "requires_verification": {
169
+ "type": "boolean",
170
+ "description": "Whether any fix for this finding must be re-verified with targeted tests or a follow-up review pass"
171
+ }
172
+ },
173
+ "required": [
174
+ "autofix_class",
175
+ "owner",
176
+ "requires_verification"
177
+ ],
178
+ "additionalProperties": false
179
+ },
180
+ "route_narrowing_reason": {
181
+ "type": "string",
182
+ "minLength": 1,
183
+ "maxLength": 2048,
184
+ "pattern": "\\S"
185
+ }
186
+ },
187
+ "required": [
188
+ "decision_id",
189
+ "disposition",
190
+ "input_finding_ids",
191
+ "title",
192
+ "why_it_matters",
193
+ "evidence",
194
+ "line"
195
+ ],
196
+ "additionalProperties": false
197
+ },
198
+ {
199
+ "type": "object",
200
+ "properties": {
201
+ "decision_id": {
202
+ "type": "string",
203
+ "minLength": 1,
204
+ "maxLength": 128,
205
+ "pattern": "\\S"
206
+ },
207
+ "disposition": {
208
+ "type": "string",
209
+ "const": "declined"
210
+ },
211
+ "input_finding_id": {
212
+ "type": "string",
213
+ "minLength": 1,
214
+ "maxLength": 128,
215
+ "pattern": "\\S"
216
+ },
217
+ "declined_reason": {
218
+ "type": "string",
219
+ "minLength": 1,
220
+ "maxLength": 2048,
221
+ "pattern": "\\S"
222
+ },
223
+ "disagreement_facts": {
224
+ "maxItems": 32,
225
+ "type": "array",
226
+ "items": {
227
+ "type": "string",
228
+ "minLength": 1,
229
+ "maxLength": 2048,
230
+ "pattern": "\\S"
231
+ }
232
+ },
233
+ "proposed_route": {
234
+ "type": "object",
235
+ "properties": {
236
+ "autofix_class": {
237
+ "type": "string",
238
+ "enum": [
239
+ "safe_auto",
240
+ "gated_auto",
241
+ "manual",
242
+ "advisory"
243
+ ],
244
+ "description": "Reviewer's conservative recommendation for how this issue should be handled after synthesis"
245
+ },
246
+ "owner": {
247
+ "type": "string",
248
+ "enum": [
249
+ "review-fixer",
250
+ "downstream-resolver",
251
+ "human",
252
+ "release"
253
+ ],
254
+ "description": "Who should own the next action for this finding after synthesis"
255
+ },
256
+ "requires_verification": {
257
+ "type": "boolean",
258
+ "description": "Whether any fix for this finding must be re-verified with targeted tests or a follow-up review pass"
259
+ }
260
+ },
261
+ "required": [
262
+ "autofix_class",
263
+ "owner",
264
+ "requires_verification"
265
+ ],
266
+ "additionalProperties": false
267
+ },
268
+ "route_narrowing_reason": {
269
+ "type": "string",
270
+ "minLength": 1,
271
+ "maxLength": 2048,
272
+ "pattern": "\\S"
273
+ }
274
+ },
275
+ "required": [
276
+ "decision_id",
277
+ "disposition",
278
+ "input_finding_id",
279
+ "declined_reason"
280
+ ],
281
+ "additionalProperties": false
282
+ }
283
+ ]
284
+ }
285
+ }
286
+ },
287
+ "required": ["decisions"],
288
+ "additionalProperties": false
289
+ },
290
+ "validatorLifecycleResult": {
291
+ "oneOf": [
292
+ {
293
+ "type": "object",
294
+ "properties": {
295
+ "outcome": {
296
+ "type": "string",
297
+ "const": "true"
298
+ }
299
+ },
300
+ "required": ["outcome"],
301
+ "additionalProperties": false
302
+ },
303
+ {
304
+ "type": "object",
305
+ "properties": {
306
+ "outcome": {
307
+ "type": "string",
308
+ "const": "false"
309
+ },
310
+ "reason": {
311
+ "type": "string",
312
+ "minLength": 1,
313
+ "maxLength": 2048,
314
+ "pattern": "\\S"
315
+ }
316
+ },
317
+ "required": ["outcome", "reason"],
318
+ "additionalProperties": false
319
+ },
320
+ {
321
+ "type": "object",
322
+ "properties": {
323
+ "outcome": {
324
+ "type": "string",
325
+ "const": "failed"
326
+ },
327
+ "reason": {
328
+ "type": "string",
329
+ "minLength": 1,
330
+ "maxLength": 2048,
331
+ "pattern": "\\S"
332
+ }
333
+ },
334
+ "required": ["outcome", "reason"],
335
+ "additionalProperties": false
336
+ },
337
+ {
338
+ "type": "object",
339
+ "properties": {
340
+ "outcome": {
341
+ "type": "string",
342
+ "const": "unavailable"
343
+ },
344
+ "reason": {
345
+ "type": "string",
346
+ "minLength": 1,
347
+ "maxLength": 2048,
348
+ "pattern": "\\S"
349
+ }
350
+ },
351
+ "required": ["outcome", "reason"],
352
+ "additionalProperties": false
353
+ }
354
+ ]
355
+ },
356
+ "planAssessmentEnvelope": {
357
+ "type": "object",
358
+ "properties": {
359
+ "verdict": {
360
+ "type": "string",
361
+ "minLength": 1,
362
+ "maxLength": 256,
363
+ "pattern": "\\S"
364
+ },
365
+ "results": {
366
+ "maxItems": 32,
367
+ "type": "array",
368
+ "items": {
369
+ "type": "object",
370
+ "properties": {
371
+ "kind": {
372
+ "type": "string",
373
+ "enum": ["explicit_unmet_requirement", "inferred_gap"]
374
+ },
375
+ "description": {
376
+ "type": "string",
377
+ "minLength": 1,
378
+ "maxLength": 2048,
379
+ "pattern": "\\S"
380
+ }
381
+ },
382
+ "required": ["kind", "description"],
383
+ "additionalProperties": false
384
+ }
385
+ }
386
+ },
387
+ "required": ["verdict", "results"],
388
+ "additionalProperties": false
389
+ }
390
+ }
391
+ }