@fro.bot/systematic 3.13.4 → 3.13.6
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/dist/cli.js +51 -3
- package/dist/lib/review-artifact-schema.d.ts +19 -1
- package/package.json +1 -1
- package/skills/ce-review/SKILL.md +11 -5
- package/skills/ce-review/references/review-output-template.md +1 -1
- package/skills/ce-review/references/review-summary-schema.json +98 -0
- package/skills/ce-review/references/synthesis-artifact-contract.md +47 -14
package/dist/cli.js
CHANGED
|
@@ -1988,7 +1988,10 @@ var MAX_FINDINGS = 32;
|
|
|
1988
1988
|
var MAX_PERSONAS = 64;
|
|
1989
1989
|
var REVIEW_ARTIFACT_CUSTOM_MESSAGES = [
|
|
1990
1990
|
"severity count must match rejected finding count",
|
|
1991
|
-
"filtered findings require a validation reason"
|
|
1991
|
+
"filtered findings require a validation reason",
|
|
1992
|
+
"risk-critical dispatches require a non-empty selection surface",
|
|
1993
|
+
"satisfied risk coverage requires a citing input finding ID",
|
|
1994
|
+
"unsatisfied risk coverage must not cite an input finding ID"
|
|
1992
1995
|
];
|
|
1993
1996
|
var boundedText = (maxLength) => exports_external.string().min(1).max(maxLength).regex(/\S/);
|
|
1994
1997
|
var DispatchOutcomeSchema = exports_external.enum([
|
|
@@ -2012,6 +2015,14 @@ var BranchSchema = exports_external.string().max(MAX_BRANCH_LENGTH);
|
|
|
2012
2015
|
var HeadShaSchema = exports_external.string().regex(/^[0-9a-f]{40}$/);
|
|
2013
2016
|
var CompletedAtSchema = exports_external.iso.datetime({ offset: false });
|
|
2014
2017
|
var ReasonSchema = boundedText(MAX_REASON_LENGTH);
|
|
2018
|
+
var RISK_CRITICAL_PERSONAS = [
|
|
2019
|
+
"security",
|
|
2020
|
+
"data-migrations",
|
|
2021
|
+
"api-contract",
|
|
2022
|
+
"reliability",
|
|
2023
|
+
"performance"
|
|
2024
|
+
];
|
|
2025
|
+
var RiskCriticalPersonaSchema = exports_external.enum(RISK_CRITICAL_PERSONAS);
|
|
2015
2026
|
var FindingTitleSchema = boundedText(256);
|
|
2016
2027
|
var SeveritySchema = exports_external.enum(["P0", "P1", "P2", "P3", "unknown"]);
|
|
2017
2028
|
var FindingSeveritySchema = SeveritySchema.exclude(["unknown"]);
|
|
@@ -2098,8 +2109,18 @@ var DispatchSchema = exports_external.object({
|
|
|
2098
2109
|
persona: ReviewerSchema,
|
|
2099
2110
|
dispatch_outcome: DispatchOutcomeSchema,
|
|
2100
2111
|
input_finding_count: exports_external.number().int().nonnegative().max(MAX_FINDINGS),
|
|
2101
|
-
rejection_reason: ReasonSchema.optional()
|
|
2102
|
-
|
|
2112
|
+
rejection_reason: ReasonSchema.optional(),
|
|
2113
|
+
selection_surface: exports_external.array(RepoRelativePathSchema).max(MAX_FINDINGS).optional(),
|
|
2114
|
+
selection_reason: ReasonSchema.optional()
|
|
2115
|
+
}).strict().superRefine((dispatch, ctx) => {
|
|
2116
|
+
if (RISK_CRITICAL_PERSONAS.includes(dispatch.persona) && (!dispatch.selection_surface || dispatch.selection_surface.length === 0)) {
|
|
2117
|
+
ctx.addIssue({
|
|
2118
|
+
code: "custom",
|
|
2119
|
+
path: ["selection_surface"],
|
|
2120
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[2]
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2103
2124
|
var DispositionCountsSchema = exports_external.object({
|
|
2104
2125
|
surviving: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
|
|
2105
2126
|
merged: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
|
|
@@ -2116,6 +2137,31 @@ var CoverageSchema = exports_external.object({
|
|
|
2116
2137
|
validator_failures: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
|
|
2117
2138
|
intent_uncertainty: exports_external.array(ReasonSchema).max(MAX_PERSONAS)
|
|
2118
2139
|
}).strict();
|
|
2140
|
+
var DeclinedMergeSchema = exports_external.object({
|
|
2141
|
+
file: RepoRelativePathSchema,
|
|
2142
|
+
input_finding_ids: exports_external.array(boundedText(MAX_INPUT_ID_LENGTH)).min(2).max(MAX_FINDINGS),
|
|
2143
|
+
reason: ReasonSchema
|
|
2144
|
+
}).strict();
|
|
2145
|
+
var RiskCoverageSchema = exports_external.object({
|
|
2146
|
+
persona: RiskCriticalPersonaSchema,
|
|
2147
|
+
satisfied: exports_external.boolean(),
|
|
2148
|
+
input_finding_id: boundedText(MAX_INPUT_ID_LENGTH).optional()
|
|
2149
|
+
}).strict().superRefine((coverage, ctx) => {
|
|
2150
|
+
if (coverage.satisfied && coverage.input_finding_id === undefined) {
|
|
2151
|
+
ctx.addIssue({
|
|
2152
|
+
code: "custom",
|
|
2153
|
+
path: ["input_finding_id"],
|
|
2154
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[3]
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
if (!coverage.satisfied && coverage.input_finding_id !== undefined) {
|
|
2158
|
+
ctx.addIssue({
|
|
2159
|
+
code: "custom",
|
|
2160
|
+
path: ["input_finding_id"],
|
|
2161
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[4]
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2119
2165
|
var ReviewArtifactSchema = exports_external.object({
|
|
2120
2166
|
schema_version: exports_external.literal(1),
|
|
2121
2167
|
run_id: boundedText(MAX_RUN_ID_LENGTH),
|
|
@@ -2134,6 +2180,8 @@ var ReviewArtifactSchema = exports_external.object({
|
|
|
2134
2180
|
dispatches: exports_external.array(DispatchSchema).max(MAX_PERSONAS),
|
|
2135
2181
|
input_findings: exports_external.array(InputFindingSchema).max(MAX_FINDINGS * MAX_PERSONAS),
|
|
2136
2182
|
findings: exports_external.array(SynthesizedFindingSchema).max(MAX_FINDINGS),
|
|
2183
|
+
declined_merges: exports_external.array(DeclinedMergeSchema).max(MAX_FINDINGS).optional(),
|
|
2184
|
+
risk_coverage: exports_external.array(RiskCoverageSchema).max(MAX_PERSONAS).optional(),
|
|
2137
2185
|
disposition_counts: DispositionCountsSchema,
|
|
2138
2186
|
applied_fixes: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
|
|
2139
2187
|
residual_actionable_work: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const REVIEW_ARTIFACT_CUSTOM_MESSAGES: readonly ['severity count must match rejected finding count', 'filtered findings require a validation reason'];
|
|
2
|
+
export declare const REVIEW_ARTIFACT_CUSTOM_MESSAGES: readonly ['severity count must match rejected finding count', 'filtered findings require a validation reason', 'risk-critical dispatches require a non-empty selection surface', 'satisfied risk coverage requires a citing input finding ID', 'unsatisfied risk coverage must not cite an input finding ID'];
|
|
3
3
|
export declare const DispatchOutcomeSchema: z.ZodEnum<{
|
|
4
4
|
empty: "empty";
|
|
5
5
|
findings: "findings";
|
|
@@ -134,6 +134,8 @@ export declare const ReviewArtifactSchema: z.ZodObject<{
|
|
|
134
134
|
}>;
|
|
135
135
|
input_finding_count: z.ZodNumber;
|
|
136
136
|
rejection_reason: z.ZodOptional<z.ZodString>;
|
|
137
|
+
selection_surface: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
138
|
+
selection_reason: z.ZodOptional<z.ZodString>;
|
|
137
139
|
}, z.core.$strict>>;
|
|
138
140
|
input_findings: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
139
141
|
record_type: z.ZodLiteral<"admitted">;
|
|
@@ -218,6 +220,22 @@ export declare const ReviewArtifactSchema: z.ZodObject<{
|
|
|
218
220
|
agreement_credit: string[];
|
|
219
221
|
};
|
|
220
222
|
}>>>;
|
|
223
|
+
declined_merges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
224
|
+
file: z.ZodString;
|
|
225
|
+
input_finding_ids: z.ZodArray<z.ZodString>;
|
|
226
|
+
reason: z.ZodString;
|
|
227
|
+
}, z.core.$strict>>>;
|
|
228
|
+
risk_coverage: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
229
|
+
persona: z.ZodEnum<{
|
|
230
|
+
"api-contract": "api-contract";
|
|
231
|
+
"data-migrations": "data-migrations";
|
|
232
|
+
performance: "performance";
|
|
233
|
+
reliability: "reliability";
|
|
234
|
+
security: "security";
|
|
235
|
+
}>;
|
|
236
|
+
satisfied: z.ZodBoolean;
|
|
237
|
+
input_finding_id: z.ZodOptional<z.ZodString>;
|
|
238
|
+
}, z.core.$strict>>>;
|
|
221
239
|
disposition_counts: z.ZodObject<{
|
|
222
240
|
surviving: z.ZodNumber;
|
|
223
241
|
merged: z.ZodNumber;
|
package/package.json
CHANGED
|
@@ -360,6 +360,10 @@ Review team:
|
|
|
360
360
|
|
|
361
361
|
This is progress reporting, not a blocking confirmation.
|
|
362
362
|
|
|
363
|
+
Record each conditional persona's selection reason and triggering file paths
|
|
364
|
+
on its dispatch record; see the [synthesis artifact contract](./references/synthesis-artifact-contract.md)
|
|
365
|
+
for the field semantics.
|
|
366
|
+
|
|
363
367
|
### Stage 3b: Discover project standards paths
|
|
364
368
|
|
|
365
369
|
Before spawning sub-agents, find the file paths (not contents) of all relevant standards files for the `project-standards` persona. Use the native file-search/glob tool to locate:
|
|
@@ -468,8 +472,10 @@ Before applying the confidence gate, keep the parent-owned ledger through every
|
|
|
468
472
|
- **Dispatch outcome:** Record the parent-owned dispatch outcomes and ledger dispositions according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md); keep dispatch outcomes separate from finding dispositions.
|
|
469
473
|
- **Rejection policy: degrade, do not fail the whole review.** Continue merging conforming returns when a persona or finding is rejected; record the rejection and apply the risk-aware verdict according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md). If every persona fails or times out, use the existing degraded-review behavior.
|
|
470
474
|
2. **Confidence gate.** Suppress findings below 0.60 confidence. Exception: P0 findings at 0.50+ confidence survive the gate -- critical-but-uncertain issues must not be silently dropped. Record the suppressed finding's original confidence and an explicit reason in the input ledger. A retained P0 at 0.50+ is recorded as `surviving` unless it later participates in a deduplication merge. This matches the persona instructions and the schema's confidence thresholds.
|
|
471
|
-
3. **Deduplicate.**
|
|
472
|
-
|
|
475
|
+
3. **Deduplicate.** Group all gated findings by `normalize(file)`. A file with two or more findings from different personas forms a candidate group. Do not use line number to form groups. Sort findings within each group by line. Adjudicate each candidate group: merge findings judged to describe the same underlying defect; keep genuinely different defects separate. Adjacency creates a candidate, not a conclusion -- findings on the same line that describe different defects must not merge. For each resulting finding, derive the fingerprint from its file and line as `normalize(file) + "|" + line`. Keep highest severity, keep highest confidence, and retain the input IDs that produced the merged entry. Record each declined merge in the artifact's optional `declined_merges` field with the normalized file, input finding IDs considered but not merged, and a brief reason. A singleton that passes the gate is `surviving`; each input in a multi-input merge is provisionally `merged`.
|
|
476
|
+
|
|
477
|
+
Worked example: at `src/lib/model-availability.ts:139`, reliability's `Config hook awaits providers API without a timeout` and adversarial's `Config startup can hang forever behind a stalled /config` describe the same underlying defect in different words, so they merge.
|
|
478
|
+
4. **Cross-reviewer agreement.** When an adjudicated merge contains findings from 2+ independent reviewers, boost the merged confidence by 0.10 (capped at 1.0). Cross-reviewer agreement is strong signal -- independent reviewers converging on the same issue is more reliable than any single reviewer's confidence. Preserve the distinction in the merged finding's artifact provenance according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md).
|
|
473
479
|
5. **Separate pre-existing.** Pull out findings with `pre_existing: true` into a separate list.
|
|
474
480
|
6. **Resolve disagreements.** When reviewers flag the same code region but disagree on severity, autofix_class, or owner, annotate the Reviewer column with the disagreement (e.g., "security (P0), correctness (P1) -- kept P0"). This transparency helps the user understand why a finding was routed the way it was.
|
|
475
481
|
7. **Normalize routing.** For each merged finding, set the final `autofix_class`, `owner`, and `requires_verification`. If reviewers disagree, keep the most conservative route. Synthesis may narrow a finding from `safe_auto` to `gated_auto` or `manual`, but must not widen it without new evidence.
|
|
@@ -518,8 +524,8 @@ Assemble the final report using **pipe-delimited markdown tables for findings**
|
|
|
518
524
|
8. **Learnings & Past Solutions.** Surface learnings-researcher results: if past solutions are relevant, flag them as "Known Pattern" with links to docs/solutions/ files.
|
|
519
525
|
9. **Agent-Native Gaps.** Surface agent-native-reviewer results. Omit section if no gaps found.
|
|
520
526
|
10. **Deployment Notes.** If deployment-verification-agent ran, surface the key Go/No-Go items: blocking pre-deploy checks, the most important verification queries, rollback caveats, and monitoring focus areas. Keep the checklist actionable rather than dropping it into Coverage.
|
|
521
|
-
11. **Coverage.** Suppressed count, residual risks, testing gaps, failed/timed-out reviewers, validator failures, and any intent uncertainty carried by non-interactive modes.
|
|
522
|
-
12. **Verdict.** Ready to merge / Ready with fixes / Not ready. Fix order if applicable. When an `explicit` plan has unaddressed requirements, the verdict must reflect it — a PR that's code-clean but missing planned requirements is "Not ready" unless the omission is intentional. When an `inferred` plan has unaddressed requirements, note it in the verdict reasoning but do not block on it alone. Apply the risk-aware degraded verdict rule from the [synthesis artifact contract](./references/synthesis-artifact-contract.md).
|
|
527
|
+
11. **Coverage.** Suppressed count, residual risks, testing gaps, failed/timed-out reviewers, validator failures, risk-coverage entries with citing input finding IDs and exit conditions for blocked entries, and any intent uncertainty carried by non-interactive modes.
|
|
528
|
+
12. **Verdict.** Ready to merge / Ready with fixes / Not ready. Fix order if applicable. When an `explicit` plan has unaddressed requirements, the verdict must reflect it — a PR that's code-clean but missing planned requirements is "Not ready" unless the omission is intentional. When an `inferred` plan has unaddressed requirements, note it in the verdict reasoning but do not block on it alone. Apply the risk-aware degraded verdict rule from the [synthesis artifact contract](./references/synthesis-artifact-contract.md), including the recorded exit condition for a blocked risk-critical verdict.
|
|
523
529
|
|
|
524
530
|
Do not include time estimates.
|
|
525
531
|
|
|
@@ -596,7 +602,7 @@ Review complete
|
|
|
596
602
|
|
|
597
603
|
**Detail enrichment (headless only):** The headless envelope includes `Why:`, `Evidence:`, and `Suggested fix:` lines. After merge (Stage 5), use the validated full persona returns retained in parent memory for only the findings that survived dedup and confidence gating.
|
|
598
604
|
- **Field tiers:** `Why:` and `Evidence:` are detail-tier and are already present in the validated inline return. `Suggested fix:` is also available directly from that return and survives merge as optional fix context.
|
|
599
|
-
- **In-memory matching:** For each surviving finding, look up its detail-tier fields in the validated returns of the contributing reviewers.
|
|
605
|
+
- **In-memory matching:** For each surviving finding, look up its detail-tier fields in the validated returns of the contributing reviewers. Use the merged finding's `input_finding_ids` (`<reviewer>#<1-based finding index>`) to identify the contributing return and source finding. When an input ID cannot be resolved or multiple candidates remain, match on normalized `file`, then use line and `normalize(title)` only as tie-breakers.
|
|
600
606
|
- **Reviewer order:** Try contributing reviewers in the order they appear in the merged finding's reviewer list; use the first validated match.
|
|
601
607
|
- **No-match fallback:** If no validated in-memory return contains a match, omit the `Why:` and `Evidence:` lines for that finding and note the gap in Coverage. This should indicate a synthesis/matching gap, not a failed artifact-file write. Never re-read per-agent files to recover detail.
|
|
602
608
|
|
|
@@ -137,7 +137,7 @@ This fails because: no pipe-delimited tables, no severity-grouped `###` headers,
|
|
|
137
137
|
- **Learnings & Past Solutions section** -- results from learnings-researcher, with links to docs/solutions/ files
|
|
138
138
|
- **Agent-Native Gaps section** -- results from agent-native-reviewer. Omit if no gaps found.
|
|
139
139
|
- **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, and
|
|
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
|
|
141
141
|
- **Summary uses blockquotes** for verdict, reasoning, and fix order
|
|
142
142
|
- **Horizontal rule** (`---`) separates findings from verdict
|
|
143
143
|
- **`###` headers** for each section -- never plain text headers
|
|
@@ -69,6 +69,31 @@
|
|
|
69
69
|
"minLength": 1,
|
|
70
70
|
"maxLength": 2048,
|
|
71
71
|
"pattern": "\\S"
|
|
72
|
+
},
|
|
73
|
+
"selection_surface": {
|
|
74
|
+
"maxItems": 32,
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"minLength": 1,
|
|
79
|
+
"maxLength": 256,
|
|
80
|
+
"allOf": [
|
|
81
|
+
{
|
|
82
|
+
"type": "string",
|
|
83
|
+
"pattern": "\\S"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"type": "string",
|
|
87
|
+
"pattern": "^(?!\\/)(?![A-Za-z]:[\\\\/])(?!\\\\).+"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"selection_reason": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"minLength": 1,
|
|
95
|
+
"maxLength": 2048,
|
|
96
|
+
"pattern": "\\S"
|
|
72
97
|
}
|
|
73
98
|
},
|
|
74
99
|
"required": ["persona", "dispatch_outcome", "input_finding_count"],
|
|
@@ -374,6 +399,79 @@
|
|
|
374
399
|
"additionalProperties": false
|
|
375
400
|
}
|
|
376
401
|
},
|
|
402
|
+
"declined_merges": {
|
|
403
|
+
"maxItems": 32,
|
|
404
|
+
"type": "array",
|
|
405
|
+
"items": {
|
|
406
|
+
"type": "object",
|
|
407
|
+
"properties": {
|
|
408
|
+
"file": {
|
|
409
|
+
"type": "string",
|
|
410
|
+
"minLength": 1,
|
|
411
|
+
"maxLength": 256,
|
|
412
|
+
"allOf": [
|
|
413
|
+
{
|
|
414
|
+
"type": "string",
|
|
415
|
+
"pattern": "\\S"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"type": "string",
|
|
419
|
+
"pattern": "^(?!\\/)(?![A-Za-z]:[\\\\/])(?!\\\\).+"
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
},
|
|
423
|
+
"input_finding_ids": {
|
|
424
|
+
"minItems": 2,
|
|
425
|
+
"maxItems": 32,
|
|
426
|
+
"type": "array",
|
|
427
|
+
"items": {
|
|
428
|
+
"type": "string",
|
|
429
|
+
"minLength": 1,
|
|
430
|
+
"maxLength": 128,
|
|
431
|
+
"pattern": "\\S"
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
"reason": {
|
|
435
|
+
"type": "string",
|
|
436
|
+
"minLength": 1,
|
|
437
|
+
"maxLength": 2048,
|
|
438
|
+
"pattern": "\\S"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
"required": ["file", "input_finding_ids", "reason"],
|
|
442
|
+
"additionalProperties": false
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
"risk_coverage": {
|
|
446
|
+
"maxItems": 64,
|
|
447
|
+
"type": "array",
|
|
448
|
+
"items": {
|
|
449
|
+
"type": "object",
|
|
450
|
+
"properties": {
|
|
451
|
+
"persona": {
|
|
452
|
+
"type": "string",
|
|
453
|
+
"enum": [
|
|
454
|
+
"security",
|
|
455
|
+
"data-migrations",
|
|
456
|
+
"api-contract",
|
|
457
|
+
"reliability",
|
|
458
|
+
"performance"
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
"satisfied": {
|
|
462
|
+
"type": "boolean"
|
|
463
|
+
},
|
|
464
|
+
"input_finding_id": {
|
|
465
|
+
"type": "string",
|
|
466
|
+
"minLength": 1,
|
|
467
|
+
"maxLength": 128,
|
|
468
|
+
"pattern": "\\S"
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
"required": ["persona", "satisfied"],
|
|
472
|
+
"additionalProperties": false
|
|
473
|
+
}
|
|
474
|
+
},
|
|
377
475
|
"disposition_counts": {
|
|
378
476
|
"type": "object",
|
|
379
477
|
"properties": {
|
|
@@ -78,12 +78,19 @@ The artifact must preserve these distinctions:
|
|
|
78
78
|
"title": "<merged finding>",
|
|
79
79
|
"input_finding_ids": ["correctness#2", "testing#1"],
|
|
80
80
|
"provenance": {
|
|
81
|
-
"fingerprint": "<normalize(file) +
|
|
81
|
+
"fingerprint": "<normalize(file) + \"|\" + line>",
|
|
82
82
|
"submitters": ["correctness", "testing"],
|
|
83
83
|
"agreement_credit": []
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
],
|
|
87
|
+
"declined_merges": [
|
|
88
|
+
{
|
|
89
|
+
"file": "src/example.ts",
|
|
90
|
+
"input_finding_ids": ["correctness#3", "testing#2"],
|
|
91
|
+
"reason": "The findings concern separate validation paths."
|
|
92
|
+
}
|
|
93
|
+
],
|
|
87
94
|
"disposition_counts": {
|
|
88
95
|
"surviving": 0,
|
|
89
96
|
"merged": 2,
|
|
@@ -98,7 +105,10 @@ The artifact must preserve these distinctions:
|
|
|
98
105
|
records what a persona returned: `findings`, `empty`, `malformed`, or
|
|
99
106
|
`never_returned`. A rejection reason is the exact safe validation reason,
|
|
100
107
|
naming persona and field without echoing the offending value. Dispatch
|
|
101
|
-
outcome is separate from finding disposition.
|
|
108
|
+
outcome is separate from finding disposition. Conditional selections record
|
|
109
|
+
their triggering changed-file paths in `selection_surface` and the announced
|
|
110
|
+
selection explanation in `selection_reason`; always-on personas may omit
|
|
111
|
+
both fields.
|
|
102
112
|
- `input_findings` is the authoritative parent-owned ledger. Before the
|
|
103
113
|
confidence gate, every admitted finding receives an `input_id` of
|
|
104
114
|
`<reviewer>#<1-based finding index>`. Every admitted input has exactly one
|
|
@@ -119,12 +129,24 @@ The artifact must preserve these distinctions:
|
|
|
119
129
|
finding has zero ledger entries, not a fabricated finding. Never include the
|
|
120
130
|
offending value in a rejection reason.
|
|
121
131
|
- Synthesized and filtered findings retain their original fields plus
|
|
122
|
-
`input_finding_ids` and provenance. Provenance contains the
|
|
123
|
-
|
|
124
|
-
`submitters
|
|
125
|
-
- `
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
`input_finding_ids` and provenance. Provenance contains the fingerprint
|
|
133
|
+
`normalize(file) + "|" + line`, derived from the merged finding's file and
|
|
134
|
+
line, plus `submitters` and `agreement_credit` arrays.
|
|
135
|
+
- `declined_merges` is optional. For each candidate group that is not merged,
|
|
136
|
+
record the normalized file, the input finding IDs considered and not merged,
|
|
137
|
+
and a brief reason. Candidate groups contain two or more findings from
|
|
138
|
+
different personas on the same normalized file; sort their findings by line.
|
|
139
|
+
Adjacency creates a candidate, not a conclusion: genuinely different
|
|
140
|
+
defects on the same line remain separate.
|
|
141
|
+
- A candidate group may resolve partially. When some findings in a group merge
|
|
142
|
+
and others stay separate, record one entry per declined separation, listing
|
|
143
|
+
the input finding IDs on both sides of it. A group of three where two merge
|
|
144
|
+
and one stays separate records a single entry naming all three IDs, with a
|
|
145
|
+
reason describing why the third is a different defect. Record the separation,
|
|
146
|
+
not the merge; a fully merged group produces no entry.
|
|
147
|
+
- `submitters` contains only personas with an input finding in the adjudicated
|
|
148
|
+
merge. `agreement_credit` contains only personas credited by the
|
|
149
|
+
cross-reviewer agreement boost without an input finding in that merge. A
|
|
128
150
|
persona returning zero findings never appears in `submitters`; do not infer
|
|
129
151
|
submission from the report's Reviewer column.
|
|
130
152
|
- A `filtered` finding remains available for human review with the validator's
|
|
@@ -227,18 +249,20 @@ The risk-critical surfaces are `security`, `data-migrations`, `api-contract`,
|
|
|
227
249
|
specifically for the matching diff shape in Stage 3. If one of those selected
|
|
228
250
|
personas has `dispatch_outcome: "malformed"` or
|
|
229
251
|
`dispatch_outcome: "never_returned"`, the review verdict must not be clean:
|
|
230
|
-
it is blocking unless another persona covered the
|
|
231
|
-
|
|
252
|
+
it is blocking unless another persona covered the lost surface with validated
|
|
253
|
+
evidence. For this rule, a validated finding from another persona covers a
|
|
254
|
+
lost risk-critical surface if and only if the finding's `file` appears in the
|
|
255
|
+
lost persona's recorded `selection_surface`; validated evidence means at least
|
|
232
256
|
one finding from that other persona's return passed complete schema and
|
|
233
|
-
environment-value validation
|
|
234
|
-
|
|
235
|
-
risk-critical evidence.
|
|
257
|
+
environment-value validation. A coverage note alone cannot satisfy this rule;
|
|
258
|
+
the verdict must reflect the missing risk-critical evidence.
|
|
236
259
|
|
|
237
260
|
Finding-level rejection is keyed by the severities in
|
|
238
261
|
`rejected_severities`. A selected risk-critical persona whose rejected
|
|
239
262
|
findings include any `P0`, `P1`, or `unknown` severity is treated exactly as a
|
|
240
263
|
rejected persona for this verdict rule: blocking unless another persona
|
|
241
|
-
covered the
|
|
264
|
+
covered the lost surface with a validated finding whose `file` appears in the
|
|
265
|
+
lost persona's recorded `selection_surface`. A selected risk-critical
|
|
242
266
|
persona whose rejected findings are only `P2` or `P3` does not block on that
|
|
243
267
|
basis alone; record it in the Coverage section instead. Unknown severity is
|
|
244
268
|
treated as blocking as deliberate fail-closed behavior because the parent
|
|
@@ -246,3 +270,12 @@ could not determine what was lost. Admitted findings and verdict blocking
|
|
|
246
270
|
are independent: surviving findings from the same return continue through
|
|
247
271
|
synthesis normally. Partial return is not partial coverage when the lost
|
|
248
272
|
part was critical.
|
|
273
|
+
|
|
274
|
+
Record each lost risk-critical persona in the optional `risk_coverage` array
|
|
275
|
+
with `satisfied: true` and `input_finding_id` set to the citing input finding
|
|
276
|
+
ID when coverage is satisfied, or `satisfied: false` without an
|
|
277
|
+
`input_finding_id` otherwise. A blocked verdict must name its exit condition
|
|
278
|
+
in the report: re-run the lost persona, or supply a validated finding from
|
|
279
|
+
another persona whose `file` appears in the lost persona's recorded
|
|
280
|
+
`selection_surface`. Derive that condition from the dispatch and risk-coverage
|
|
281
|
+
records, not from a coverage note.
|