@fro.bot/systematic 3.12.3 → 3.13.0

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.
@@ -19,11 +19,13 @@ reason. An unfinished `in_progress` artifact is evidence of an abnormal run,
19
19
  not evidence of a clean run. Never infer a clean run from an absent artifact.
20
20
 
21
21
  The artifact is parent-owned. Per-agent full-detail JSON files are written
22
- only after the persona return passes full-schema and environment-value
23
- validation. Rejected or never-returned personas do not produce per-agent
24
- files. If a later confidence or validation stage changes an input disposition,
25
- the parent updates the record and synthesis ledger before finalizing the
26
- artifact.
22
+ only for findings admitted after the parent completes schema and
23
+ environment-value validation. A finding rejected by environment-value
24
+ detection is not persisted; other findings from the same return may proceed.
25
+ A payload rejected at top level, or a rejected or never-returned persona,
26
+ does not produce a per-agent file. If a later confidence or validation stage
27
+ changes an input disposition, the parent updates the record and synthesis
28
+ ledger before finalizing the artifact.
27
29
 
28
30
  ## Required distinctions and reconciliation
29
31
 
@@ -32,6 +34,7 @@ The artifact must preserve these distinctions:
32
34
  ```json
33
35
  {
34
36
  "run_id": "<run-id>",
37
+ "schema_version": 1,
35
38
  "mode": "<interactive | autofix | headless>",
36
39
  "harness": "<opencode | pi | claude-code>",
37
40
  "run_status": "<in_progress | completed | degraded | abnormal>",
@@ -41,10 +44,15 @@ The artifact must preserve these distinctions:
41
44
  "dispatch_outcome": "findings",
42
45
  "input_finding_count": 2
43
46
  },
47
+ {
48
+ "persona": "testing",
49
+ "dispatch_outcome": "findings",
50
+ "input_finding_count": 1
51
+ },
44
52
  {
45
53
  "persona": "kieran-typescript",
46
54
  "dispatch_outcome": "malformed",
47
- "input_finding_count": 1,
55
+ "input_finding_count": 2,
48
56
  "rejection_reason": "Rejected persona kieran-typescript return: field findings[0].evidence failed schema validation."
49
57
  }
50
58
  ],
@@ -55,6 +63,14 @@ The artifact must preserve these distinctions:
55
63
  "confidence": 0.55,
56
64
  "disposition": "suppressed",
57
65
  "reason": "confidence 0.55 is below the 0.60 gate"
66
+ },
67
+ {
68
+ "reviewer": "kieran-typescript",
69
+ "dispatch_outcome": "malformed",
70
+ "rejected_finding_count": 2,
71
+ "rejected_severities": ["P2", "P3"],
72
+ "disposition": "rejected",
73
+ "reason": "Rejected persona kieran-typescript return: field findings[0].evidence failed schema validation."
58
74
  }
59
75
  ],
60
76
  "findings": [
@@ -73,7 +89,7 @@ The artifact must preserve these distinctions:
73
89
  "merged": 2,
74
90
  "suppressed": 1,
75
91
  "filtered": 0,
76
- "rejected": 0
92
+ "rejected": 2
77
93
  }
78
94
  }
79
95
  ```
@@ -84,16 +100,24 @@ The artifact must preserve these distinctions:
84
100
  naming persona and field without echoing the offending value. Dispatch
85
101
  outcome is separate from finding disposition.
86
102
  - `input_findings` is the authoritative parent-owned ledger. Before the
87
- confidence gate, every safely enumerable finding receives an `input_id` of
88
- `<reviewer>#<1-based finding index>`. Every enumerated input has exactly one
89
- final `disposition`: `surviving`, `merged`, `suppressed`, `filtered`, or
90
- `rejected`, plus a reason. Disposition counts equal the input-finding count.
91
- If a rejected return has a safely enumerable `findings` array, assign IDs and
92
- record each enumerated input as `rejected`.
93
- A malformed JSON return with no safely enumerable finding has zero ledger
94
- entries, not a fabricated finding. A rejected payload's reason is the exact
95
- safe rejection message, not a bucket such as `invalid`; never include the
96
- offending value.
103
+ confidence gate, every admitted finding receives an `input_id` of
104
+ `<reviewer>#<1-based finding index>`. Every admitted input has exactly one
105
+ final `disposition`: `surviving`, `merged`, `suppressed`, or `filtered`, plus
106
+ a reason. A rejected payload is represented by one summary ledger entry,
107
+ carrying the persona name, its `dispatch_outcome`, the
108
+ `rejected_finding_count` of findings not admitted, `disposition: "rejected"`,
109
+ `rejected_severities`, a list of the severities of the findings not
110
+ admitted as parsed from the payload, and the exact safe rejection message.
111
+ When a rejected finding's severity is absent, malformed, or not a valid
112
+ severity value, record it as `unknown`. Severity is metadata; recording it
113
+ never includes the offending value. Do not enumerate rejected findings or
114
+ assign them input IDs. A finding-level environment rejection uses the same
115
+ summary entry while admitted findings from that return continue normally.
116
+ Disposition counts are weighted by `rejected_finding_count` for that summary
117
+ entry, so their sum equals the total number of findings observed, not the
118
+ number of ledger rows. A malformed JSON return with no safely enumerable
119
+ finding has zero ledger entries, not a fabricated finding. Never include the
120
+ offending value in a rejection reason.
97
121
  - Synthesized and filtered findings retain their original fields plus
98
122
  `input_finding_ids` and provenance. Provenance contains the exact dedup
99
123
  fingerprint `normalize(file) + line_bucket(line, +/-3) + normalize(title)`,
@@ -120,8 +144,105 @@ the verdict is finalized. Existing artifacts without this additive metadata
120
144
  remain valid, with downstream consumers falling back to file mtime.
121
145
 
122
146
  Validation and persistence remain parent-side: no per-agent record or finding
123
- is written or merged until the complete persona payload passes schema and
124
- environment-value validation. Rejected or malformed persona returns do not
125
- fail the whole review; the review degrades while conforming returns continue
126
- through synthesis. Only an orchestration or storage failure that prevents the
127
- parent from producing the required run artifact is run-fatal.
147
+ is written or merged until that finding passes schema and environment-value
148
+ validation. Rejected findings are recorded through the single rejected-payload
149
+ ledger summary; admitted findings from the same return remain eligible for
150
+ synthesis. Rejected or malformed persona returns do not fail the whole review;
151
+ the review degrades while conforming returns continue through synthesis. Only
152
+ an orchestration or storage failure that prevents the parent from producing the
153
+ required run artifact is run-fatal.
154
+
155
+ ## Environment-value validation
156
+
157
+ The parent recursively inspects every string leaf without logging the raw
158
+ return or any matched value. Structural environment detectors remain
159
+ unbounded and unchanged: `$NAME`, `${NAME}`, `process.env.NAME`,
160
+ `os.environ[...]`, and `NAME=value` assignments using a known environment
161
+ variable name are shape-based checks.
162
+
163
+ Value-based matching uses only non-empty runtime environment values that are
164
+ at least 16 characters long and are not composed solely of digits, dots,
165
+ dashes, or path-separator characters (forward slash or backslash). A
166
+ value is also eligible regardless of length when
167
+ its variable name contains one of `TOKEN`, `SECRET`, `KEY`, `PASSWORD`,
168
+ `PASSWD`, `CREDENTIAL`, `AUTH`, `SESSION`, `COOKIE`, `PRIVATE`, `_PASS`,
169
+ `_PWD`, `PASSPHRASE`, or `_SALT`, matched as a case-insensitive substring.
170
+ Entries containing an underscore are matched against the variable name as
171
+ written; the underscore is deliberate and prevents matching benign names that
172
+ merely contain the bare word. Values that satisfy neither condition are not
173
+ matched. A match is an exact or embedded match.
174
+
175
+ If the offending string is inside one finding, drop that finding and record it
176
+ through the rejected-payload summary entry; the remaining findings continue
177
+ through validation and synthesis. If the offending string is outside any
178
+ finding, reject the whole payload. Every rejection uses only the persona name,
179
+ JSON path, and a fixed reason (`schema validation`, `environment-value
180
+ detection`, or `malformed JSON`):
181
+ `Rejected persona <name> return: field <JSON path> failed <reason>.` Never
182
+ echo the matched value.
183
+
184
+ ## Artifact validation
185
+
186
+ The parent writes `schema_version: 1` into `review-summary.json` before
187
+ validating it. This ordering makes the artifact validatable at all: without
188
+ `schema_version`, the validator reports the legacy status (exit 3) rather than
189
+ a real validation result.
190
+
191
+ After writing `review-summary.json`, the parent runs
192
+ `systematic validate-review-artifact <path>` against it. A nonzero exit means
193
+ the run is not complete. The [executable schema](./review-summary-schema.json)
194
+ is generated from a Zod source and is the machine-checkable form of the shape
195
+ described here.
196
+
197
+ On validation failure, the parent repairs the artifact and re-runs the
198
+ validator. It does not report a verdict over an artifact that failed
199
+ validation, and it does not delete the artifact to escape the check. A failing
200
+ artifact is evidence and stays on disk; an absent artifact is never evidence
201
+ of a clean run.
202
+
203
+ This is enforcement by visible failure, not by containment. An agent that
204
+ never runs the command can still finalize an artifact, but produces no evidence
205
+ in either direction. That is why the command exists as an independently
206
+ runnable check rather than as a self-validation instruction, and why its result
207
+ belongs in the run record.
208
+
209
+ `mode:report-only` writes no artifact and therefore performs no validation.
210
+
211
+ ## Historical corpus exclusion
212
+
213
+ Artifacts without `schema_version` predate this contract, are excluded from
214
+ quantitative analysis, and no legacy reader will be written. The validator
215
+ backs this exclusion with its exit 3 legacy status rather than leaving it as
216
+ a prose-only decision. Across 26 run directories, 7 synthesis artifacts were
217
+ written under two different filenames (`review-summary.json` and
218
+ `summary.json`); no two shared a shape. A parser would need seven special
219
+ cases to recover data that still could not answer cross-reviewer agreement
220
+ questions, because 19 of the 26 runs had no synthesis artifact to reconcile
221
+ against at all.
222
+
223
+ ## Risk-aware degraded verdict
224
+
225
+ The risk-critical surfaces are `security`, `data-migrations`, `api-contract`,
226
+ `reliability`, and `performance`. They are the conditional personas selected
227
+ specifically for the matching diff shape in Stage 3. If one of those selected
228
+ personas has `dispatch_outcome: "malformed"` or
229
+ `dispatch_outcome: "never_returned"`, the review verdict must not be clean:
230
+ it is blocking unless another persona covered the same surface and returned
231
+ validated evidence for it. For this rule, validated evidence means at least
232
+ one finding from that other persona's return passed complete schema and
233
+ environment-value validation and is relevant to the same surface. A coverage
234
+ note alone cannot satisfy this rule; the verdict must reflect the missing
235
+ risk-critical evidence.
236
+
237
+ Finding-level rejection is keyed by the severities in
238
+ `rejected_severities`. A selected risk-critical persona whose rejected
239
+ findings include any `P0`, `P1`, or `unknown` severity is treated exactly as a
240
+ rejected persona for this verdict rule: blocking unless another persona
241
+ covered the same surface with validated evidence. A selected risk-critical
242
+ persona whose rejected findings are only `P2` or `P3` does not block on that
243
+ basis alone; record it in the Coverage section instead. Unknown severity is
244
+ treated as blocking as deliberate fail-closed behavior because the parent
245
+ could not determine what was lost. Admitted findings and verdict blocking
246
+ are independent: surviving findings from the same return continue through
247
+ synthesis normally. Partial return is not partial coverage when the lost
248
+ part was critical.