fix(gate): part 2 verifier findings — non-object findings, carried backed copy, rule_keys, marks, approval prose, brief_path, engine put-back once

1. plan-review-dedup skips only a null or undefined finding; any other
   entry that is not an object (a string, a number, an array) goes on as
   before E, a finding with no valid severity that the plan gate counts as
   a blocker (it was dropped, so a string blocker recorded PASS).
2. plan-gate --record: a previous finding that rule A carried as counted and
   whose id is backed this round leaves the counted set, the counts, the
   verdict and the stop rule's summary (it was counted and listed in
   known_outside at once: BLOCK, not-fewer, and --accept-open refused).
3. A known_outside entry keeps rule_keys; extractVerdict accepts it (a list
   of strings), and the bare gate re-checks with the same keys the recorder
   used, so a merged finding whose security key is only in rule_keys is a
   WARN, not a STOP.
4. plan-critic, scope-guardian and /trekplan Phase 9's round-2 sentences say
   the Critic Verdict also lists known_outside and each is marked by id in
   previous. Code already accepts such a mark (dedup passes it; countRound
   ignores ids it does not carry; --record removes the entry on true);
   pinned in diff mode and full count.
5. The approval prose names ## Threat Model where it named only ## Intent +
   ## Goal: skills/annotating/SKILL.md (description, step 4, guard list),
   docs/command-modes.md, Handover 1's intent_approved_hash row, README
   (approval, Approve row, intent approval gate) and CLAUDE.md's /trekbrief
   row.
6. threat-model.mjs gains usableInRepo (a brief path relative to the repo
   top level) and repoRelativeBrief; --record stores brief_path relative to
   the top level, never absolute, and both gates read it from there, so the
   gate run from a subdirectory reads the same brief. reviewThreatModel
   uses usableInRepo. Handover 4 and the gate header say so.
7. review-finalize's outside pass puts an entry back only when no finding
   carries its row yet (engine-compare rowOf): an engine finding listed in
   known_outside while its payload is saved went back twice.
8. A test runs scripts/release-check.mjs on a review whose known_outside
   entry the brief does not back: exit 2 (control: backed passes the review
   check).

Existing tests changed: plan-gate-threat-model's entry-keys assertion now
expects rule_keys (item 3), and its planted non-security entry drops the
security key from rule_keys too, since the gate reads rule_keys (item 3).

Part 2 fix round of the E plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-08 10:50:20 +01:00
commit 1be961aab1
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
17 changed files with 298 additions and 52 deletions

View file

@ -309,7 +309,9 @@ up to 3 times on successive revisions of the same plan. From round 2 the
prompt gives you the diff since the last reviewed text, and the plan's
`## Critic Verdict` lists the previous findings. List every previous finding
by id in `previous`, with `resolved: true` only when the plan now fixes it.
Review the changed text. Cite the exact line, with `line_end` for a range.
The `## Critic Verdict` also lists `known_outside`, the security findings the
brief's threat model puts outside. Mark each of them by id in `previous` the
same way. Review the changed text. Cite the exact line, with `line_end` for a range.
Dimensions 11 and 12 are scored inside the existing weights: 11 under Step
quality, 12 under Coverage completeness and Specification quality.
@ -408,7 +410,8 @@ instance hides a class that keeps coming back.
`severity` is exactly one of `blocker`, `major`, `minor`. Emit
`"findings": []` when the plan is clean.
`previous` (from round 2; omit it in round 1) holds one `{id, resolved}` per
finding the current `## Critic Verdict` lists; `resolved` is `true` or
finding the current `## Critic Verdict` lists, its `known_outside` entries
included; `resolved` is `true` or
`false`. `line_end` (optional) is the last line of a cited range, an integer
≥ `line`. `lowered_from` and `caught_by` go only on a finding lowered by rule
B: `"lowered_from": "major"`, and `caught_by` names the test, `Regression:`

View file

@ -127,7 +127,9 @@ You may be run up to 3 times on successive revisions of the same plan. From
round 2 the prompt gives you the diff since the last reviewed text, and the
plan's `## Critic Verdict` lists the previous findings. List every previous
finding by id in `previous`, with `resolved: true` only when the plan now
fixes it. Review the changed text. Cite the exact line, with `line_end` for a
fixes it. The `## Critic Verdict` also lists `known_outside`, the security
findings the brief's threat model puts outside. Mark each of them by id in
`previous` the same way. Review the changed text. Cite the exact line, with `line_end` for a
range.
## Output format
@ -197,7 +199,8 @@ by class (the catalogue key, else the part before the first `/`), so a key
made for one instance hides a class that keeps coming back.
Emit `"findings": []` when the plan is fully aligned.
`previous` (from round 2; omit it in round 1) holds one `{id, resolved}` per
finding the current `## Critic Verdict` lists; `resolved` is `true` or
finding the current `## Critic Verdict` lists, its `known_outside` entries
included; `resolved` is `true` or
`false`. `line_end` (optional) is the last line of a cited range, an integer
≥ `line`. `lowered_from` and `caught_by` go only on a finding lowered by rule
B: `"lowered_from": "major"`, and `caught_by` names the test, `Regression:`