fix(review): an engine finding raised again outside findings stays counted (decision 222)

A Claude Security finding counted at confidence high in round N that Claude
Security raises again at a lower confidence in round N+1 landed in that
payload's below_threshold, never in findings. Finalize passed only findings
to carry, the engine was in ran.engines, so the round-N finding was marked
resolved and the verdict fell from BLOCK to WARN (part 3 verifier, F1).

carry takes a new option, raised: the engine entries a round raised outside
findings. A previous engine finding that no current finding matches stays
carried, never resolved, when an entry of its own engine matches it (the
same file, then the same rule_key or content overlap); each entry holds at
most one previous finding. Without the option carry behaves as before.
Finalize's carry pass builds raised from every engine payload's
below_threshold and out_of_scope, the engine taken from the payload's
reviewer, never from the entry.

The carry rule is restated in the review-rounds and review-finalize headers,
/trekreview's round prose and Handover 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-08 18:22:49 +01:00
commit 3be2bd3386
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
5 changed files with 179 additions and 14 deletions

View file

@ -462,7 +462,7 @@ Since 1.1 (ordre 5):
- `## Remediation Steps` — required at `review_version` ≥ 1.1 when there is a BLOCKER: one `### Step R<n>` per BLOCKER carrying `Source finding: <id>` (strict error `REVIEW_MISSING_REMEDIATION_STEP`, soft warning). Steps are appended and numbered, never reworded across iterations (Spec Kit `/converge`).
- `## Declined to Judge` — required at `review_version` ≥ 1.1 (strict error, soft warning; 1.0 reviews exempt): every finding set aside without a ruling, with its reason (including engine findings outside the review scope, `engine:out-of-scope`, and Claude Security findings below confidence `high`, `engine:below-threshold`), plus `Trimmed: N`. **No finding is struck for length**: only MINOR/SUGGESTION text is trimmed, and counted (`trimmed` in the JSON block); a long BLOCKER/MAJOR keeps every character.
- Findings from the engines — Claude Code's own `/code-review` and one security engine, chosen by `lib/review/security-engine.mjs choose` (Claude Security where it is installed and the range has committed changes, else `/security-review`) — enter under the catalogue keys `CODE_REVIEW_DEFECT` (MAJOR), `CODE_REVIEW_IMPROVEMENT` (MINOR: a `/code-review` finding whose category is simplification, efficiency, reuse, style or readability) and `SECURITY_REVIEW_VULNERABILITY` (BLOCKER), or a narrower key, with `reviewer` `claude-code-review` / `claude-security` (engine `claude-security`) / `claude-security-review`. A Claude Security finding counts only at confidence `high` (decision 219); the rest stand under `## Declined to Judge` as `engine:below-threshold`, an unverified reason: not counted, it forbids `ALLOW`. With no security engine (`choose` gives `no-engine`, `no-origin` or `empty-diff`, or the chosen engine did not run) the expected reviewer `security-engine` never reports: `missing-reviewer:security-engine`, verdict gate exit 2 — security not measured, never 0.
- Working files of one review run, in the project folder (not part of `review.md`): the engine transcriptions `.review-engine-{code-review|security-review}.json` (`/trekreview` Phase 4.7) and, beside them, the converted payloads `.review-engine-payload-{engine}.json` (the `engine-findings.mjs` output, `{reviewer, findings, out_of_scope}`). Claude Security's is never transcribed: `.review-engine-payload-claude-security.json` is the `security-engine.mjs convert` output from its own report, `{reviewer, findings, out_of_scope, below_threshold, report}`; `below_threshold` is never compared or put back. `review-finalize.mjs` reads the payloads to record which engines ran and to put back, at its own severity, an engine finding at MAJOR or BLOCKER that no finding of `review.md` carries (the standard-path comparison); the verdict gate reads the payloads too, to compare them with `review.md`: an engine finding at MAJOR or BLOCKER missing from it is exit 2 (`review-not-measured: engine finding not in review`). `review-rounds.mjs prepare` deletes the payloads at the start of each round.
- Working files of one review run, in the project folder (not part of `review.md`): the engine transcriptions `.review-engine-{code-review|security-review}.json` (`/trekreview` Phase 4.7) and, beside them, the converted payloads `.review-engine-payload-{engine}.json` (the `engine-findings.mjs` output, `{reviewer, findings, out_of_scope}`). Claude Security's is never transcribed: `.review-engine-payload-claude-security.json` is the `security-engine.mjs convert` output from its own report, `{reviewer, findings, out_of_scope, below_threshold, report}`; `below_threshold` is never put back and never compared with `review.md`; from round 2, an entry of it (or of any engine payload's `out_of_scope`) that matches a previous finding of the same engine keeps that finding counted: carried, never resolved (decision 222). `review-finalize.mjs` reads the payloads to record which engines ran and to put back, at its own severity, an engine finding at MAJOR or BLOCKER that no finding of `review.md` carries (the standard-path comparison); the verdict gate reads the payloads too, to compare them with `review.md`: an engine finding at MAJOR or BLOCKER missing from it is exit 2 (`review-not-measured: engine finding not in review`). `review-rounds.mjs prepare` deletes the payloads at the start of each round.
Since 1.2 (every field and section optional; a malformed one present is a strict error, a soft warning):
- Trailing JSON block, additive: per finding `pre_existing` (boolean: the range did not add or change the cited line and `scope_sha_start` is bound to the fork point from main (the fork point itself, or a later commit reached through process files only); otherwise it is never true), `carried_from` (the previous round's finding id) and `instruction_ref` (the repo path of the instruction file a CLAUDE.md-key finding cites); top level `pre_existing_counts` (`{SEVERITY: n}`), `resolved` (the previous round's finding ids no longer raised) and `ran` (`{engines: [code-review|security-review|claude-security], reviewers: [string]}`, written with `finalized: true`). Codes `REVIEW_PRE_EXISTING_MALFORMED`, `REVIEW_ROUND_MALFORMED`, `REVIEW_POLICY_MALFORMED`.