docs(changelog): the narrow remediation of the A/B review, decisions 48 and 50, with door lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8ea98747d
commit
184fbe4633
4 changed files with 119 additions and 4 deletions
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -17,7 +17,7 @@ measured by `npm test` on the third remediation's Step 2 commit
|
|||
|
||||
The converging critic loop came after that measurement (decisions 43 and
|
||||
44). From round 2 the plan gate counts a critic round by rule A, and both
|
||||
reviewers state rule B. Suite 2592 (2590 pass, 0 fail, 2 skipped), measured
|
||||
reviewers state rule B. Suite 2602 (2600 pass, 0 fail, 2 skipped), measured
|
||||
by `npm test` on this branch.
|
||||
|
||||
Nothing here asks anything of users. Older plans warn, they do not fail: a plan
|
||||
|
|
@ -45,6 +45,9 @@ warning per step whose `Files:` names a path that is not backticked.
|
|||
- two-way: rule B and the security key in the reviewers (decision 43) — prose only; a revert restores the old rating.
|
||||
- two-way: plan dedup passes the reviewers' marks and rule-B fields through, and its two fixes — a revert drops the fields and brings back the line-0 collapse and the crash.
|
||||
- two-way: the post-compact hook test's timeout (decision 44) — test-side only.
|
||||
- two-way: the critic-loop matcher needs overlapping texts, and a new finding never takes an old id (operator decision 48) — a revert matches by id or line alone again.
|
||||
- two-way: plan dedup keeps a lowered_from that is not a severity (operator decision 48) — a revert counts it as no lowering again.
|
||||
- two-way: plan dedup ranks a merged pair by what rule B counts (operator decision 48) — a revert brings back the order-dependent merge.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -251,6 +254,22 @@ warning per step whose `Files:` names a path that is not backticked.
|
|||
or with no line no longer collapse into one. An empty `file` or a `line`
|
||||
that is not a whole number no longer crashes the finding id; the line
|
||||
becomes null and the finding stands.
|
||||
- **The critic loop no longer folds a new finding into an old one by its id
|
||||
or line alone** (reviews `4ab24b07`, `d03835c4`; operator decision 48,
|
||||
16.10).
|
||||
- What happened: an equal id or the old finding's line was enough. The new
|
||||
text was lost, and a new file-level major could count as an old rule-B
|
||||
minor.
|
||||
- Now the texts must overlap, a finding with a plan line is never matched
|
||||
by id, and a new finding never takes an old finding's id.
|
||||
- **A `lowered_from` that is not a severity now counts as blocker** (reviews
|
||||
`8782602b`, `ebe164e2`; operator decision 48, 16.10). It was read as no
|
||||
lowering. A blank one is still no lowering.
|
||||
- **Plan dedup no longer lets reviewer order lower a blocker** (review
|
||||
`69c9cebd`; operator decision 48, 16.10).
|
||||
- A merge keeps the copy rule B counts highest, on the merged keys.
|
||||
- This also covers a blocker claim merged into a plain major, found during
|
||||
this round.
|
||||
|
||||
### Known issues
|
||||
|
||||
|
|
@ -286,6 +305,23 @@ warning per step whose `Files:` names a path that is not backticked.
|
|||
- Accepted deviation: the 1.9 `File:` selector repeats the list-marker set of
|
||||
`LIST_ITEM` (review `4dce7c74`; operator decision 38, 14.10, decision 27
|
||||
stands).
|
||||
- The line diff's LCS table has no size bound. A rewritten gap of tens of
|
||||
thousands of lines takes seconds to minutes and gigabytes, and can end
|
||||
outside exit 0/1/2. Real plans measure about 1,200 lines (review
|
||||
`8a9b5a17`; deferred by operator decision 48, 16.10).
|
||||
- A previous finding re-reported on its own edited line is counted twice.
|
||||
Such a re-report of a lowered finding counts at its full severity (review
|
||||
`8004ea85`; deferred by operator decision 48, 16.10).
|
||||
- A `line` sent as a numeric string (`"42"`) is read as no line and counted
|
||||
at full severity. The agent templates give `line` as a number (review
|
||||
`c257625e`; deferred by operator decision 48, 16.10).
|
||||
- `npm test` does not run `tests/e2e-critic-diff-mode.sh`. It runs under
|
||||
`## Verification` and in `/trekreview`'s success-criteria check (review
|
||||
`818b1346`; deferred by operator decision 48, 16.10).
|
||||
- The matcher's residual (review `4ab24b07`; operator decision 50, 16.10): a
|
||||
previous finding marked resolved by every raiser and re-reported in other
|
||||
words (content overlap below 0.7) on an unchanged line is a new finding,
|
||||
which rule A can lower to minor.
|
||||
|
||||
## v6.3.1 — 2026-10-11 — Fixes from the release review of 6.3.0, and two verdict-gate holes closed
|
||||
|
||||
|
|
|
|||
|
|
@ -1023,8 +1023,8 @@ defect, where the read-only reviewers never wrote the files and the dedup ran on
|
|||
nothing). The merged array attributes each finding to `[plan-critic, scope-guardian]`
|
||||
when both reviewers raised the same issue (exact match on `file:line:rule_key`,
|
||||
Jaccard ≥ 0.7 on text tokens, or — same file and same line — most of the
|
||||
shorter text's content words in the longer one), and keeps the more severe of the two
|
||||
severities. Revise the plan once for the merged set, not twice for the
|
||||
shorter text's content words in the longer one), and keeps the copy rule B counts more severe
|
||||
(a refused lowering included). Revise the plan once for the merged set, not twice for the
|
||||
duplicates. Source: research/05 R1 + R2.
|
||||
|
||||
**After each round**, by the recorded verdict:
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ The strongest validation in the entire pipeline. Phase 5.5 (planning-orchestrato
|
|||
|
||||
Rule A: from round 2 on, a new major finding that cites a plan line outside the diff since the last reviewed text counts as minor; a blocker, a security finding and a finding with no plan line count at full severity, and a previous finding stays counted until it is marked resolved.
|
||||
|
||||
Rule B is stated by the reviewers and recorded by the gate from round 1 (`lowered_by`, `caught_by`); it never lowers a blocker or a security finding. `--diff <plan.md>` prints what the next round reviews: `diff-base: <sha256> (round N)` and each hunk in the plan's full-file line numbers, or `diff-base: none — <reason>; review the whole plan` (exit 0; 2 = unreadable plan or bad arguments). `--record` keeps each recorded round's operative text as a snapshot, `<git dir>/voyage/plan-snapshots/<sha256>.md`, named by its own sha256. git never tracks its own directory, so a snapshot is never committed, whatever the repo's `.gitignore` or `artifact_store` says. A plan in no git repository (plan mode) keeps none (`snapshot: "not kept: no git repository"`), and its next round counts fully. A missing, oversized, misnamed or tampered snapshot, or a plan unchanged since the base, also means full counting, said in one `diff_note` line. `--record`'s JSON line adds `diff_base`, `diff_note`, `lowered` (a number) and `snapshot`. Trust limit: anything that can write the plan can also rewrite the previous verdict's `plan_sha256` and plant a matching snapshot. That was already the limit of the verdict block; rule A raises what such a forgery buys, and an unchanged base lowers nothing.
|
||||
Rule B is stated by the reviewers and recorded by the gate from round 1 (`lowered_by`, `caught_by`); it never lowers a blocker or a security finding. `--diff <plan.md>` prints what the next round reviews: `diff-base: <sha256> (round N)` and each hunk in the plan's full-file line numbers, or `diff-base: none — <reason>; review the whole plan` (exit 0; 2 = unreadable plan or bad arguments). `--record` keeps each recorded round's operative text as a snapshot, `<git dir>/voyage/plan-snapshots/<sha256>.md`, named by its own sha256. git never tracks its own directory, so a snapshot is never committed, whatever the repo's `.gitignore` or `artifact_store` says. A plan in no git repository (plan mode) keeps none (`snapshot: "not kept: no git repository"`), and its next round counts fully. A missing, oversized, misnamed or tampered snapshot, or a plan unchanged since the base, also means full counting, said in one `diff_note` line. `--record`'s JSON line adds `diff_base`, `diff_note`, `lowered` (a number) and `snapshot`. Trust limit: anything that can write the plan can also rewrite the previous verdict's `plan_sha256` and plant a matching snapshot. That was already the limit of the verdict block; rule A raises what such a forgery buys, and an unchanged base lowers nothing. The gate matches a previous finding only on overlapping text, and a new finding never takes a previous finding's id: a repeated id gets `<rule_key>#n`.
|
||||
|
||||
**Verification CLIs that read a plan** (0/1/2, JSON on stdout, 2 is never green): `lib/verification/arch-rules.mjs --repo <dir>` runs the target repo's own dependency-cruiser or import-linter (installs nothing; `--prove --rule <name> --plant <file>=<dest>` shows pass → fail on that rule → pass, refusing a destination that exists or resolves outside the repo); `lib/verification/plan-scope-sweep.mjs --plan <plan.md> --base <ref>` lists changed files no step names (`Files:`, `Test first:` File, manifest `expected_paths`) and no process path covers.
|
||||
|
||||
|
|
|
|||
|
|
@ -66,3 +66,82 @@ test('Phase 9 names plan-gate --diff and says round 1 is unchanged', () => {
|
|||
test('Phase 9 no longer says the loop ends on a round with no BLOCKER', () => {
|
||||
assert.ok(!P9.includes('ends on a round with no BLOCKER'), 'the stale "ends on a round with no BLOCKER" is gone');
|
||||
});
|
||||
|
||||
// The narrow round of the A/B review (operator decisions 48 and 50): the
|
||||
// CHANGELOG's open Unreleased section records it, Phase 9 says what a merge
|
||||
// keeps, and Handover 4 states the matcher's overlap rule and the distinct id.
|
||||
// The release check is imported as a module namespace, so a missing parseDoors
|
||||
// export fails an assertion, not the load.
|
||||
const releaseCheck = await import('../../scripts/release-check.mjs');
|
||||
|
||||
// The body of the open `## Unreleased` section, up to the next `## ` heading.
|
||||
function unreleasedBody() {
|
||||
const lines = read('CHANGELOG.md').split('\n');
|
||||
const start = lines.findIndex(l => /^## Unreleased\b/.test(l));
|
||||
assert.ok(start >= 0, 'CHANGELOG.md has a "## Unreleased" section');
|
||||
let end = lines.length;
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
if (/^## /.test(lines[i])) { end = i; break; }
|
||||
}
|
||||
return lines.slice(start + 1, end).join('\n');
|
||||
}
|
||||
|
||||
// A `### <name>` body of the Unreleased section, up to the next heading.
|
||||
function subsection(body, name) {
|
||||
const lines = body.split('\n');
|
||||
const start = lines.findIndex(l => l.replace(/\s+$/, '') === `### ${name}`);
|
||||
assert.ok(start >= 0, `the Unreleased section has a "### ${name}" heading`);
|
||||
let end = lines.length;
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
if (/^###? /.test(lines[i])) { end = i; break; }
|
||||
}
|
||||
return lines.slice(start + 1, end).join('\n');
|
||||
}
|
||||
|
||||
// The top-level bullets of a slice: each `- ` line at column 0 with its
|
||||
// wrapped and nested lines, whitespace read as one space.
|
||||
function topBullets(slice) {
|
||||
const out = [];
|
||||
for (const line of slice.split('\n')) {
|
||||
if (/^- /.test(line)) out.push(line);
|
||||
else if (out.length && line.trim() !== '') out[out.length - 1] += `\n${line}`;
|
||||
}
|
||||
return out.map(flat);
|
||||
}
|
||||
|
||||
test('CHANGELOG: the narrow round of operator decisions 48 and 50', () => {
|
||||
assert.equal(typeof releaseCheck.parseDoors, 'function', 'scripts/release-check.mjs exports parseDoors');
|
||||
const body = unreleasedBody();
|
||||
const d = releaseCheck.parseDoors(body);
|
||||
assert.equal(d.found, true, 'the Unreleased section has a "### Doors" heading');
|
||||
assert.deepEqual(d.bad, [], 'every top-level door line reads "- one-way: ..." or "- two-way: ..."');
|
||||
const doors48 = d.entries.filter(e => /operator\s+decision\s+48\b/.test(flat(e.text)));
|
||||
assert.ok(doors48.length >= 3,
|
||||
`at least three door lines name operator decision 48: ${JSON.stringify(d.entries.map(e => e.text))}`);
|
||||
|
||||
const fixed = flat(subsection(body, 'Fixed'));
|
||||
for (const id of ['4ab24b07', 'd03835c4', '69c9cebd', '8782602b', 'ebe164e2']) {
|
||||
assert.ok(fixed.includes(id), `the Fixed slice names review ${id}`);
|
||||
}
|
||||
|
||||
const known = topBullets(subsection(body, 'Known issues'));
|
||||
for (const id of ['8a9b5a17', '8004ea85', 'c257625e', '818b1346']) {
|
||||
const hits = known.filter(b => b.includes(id));
|
||||
assert.equal(hits.length, 1, `exactly one Known-issues bullet names ${id}: ${JSON.stringify(hits)}`);
|
||||
assert.match(hits[0], /operator\s+decision\s+48\b/, `the ${id} Known-issues bullet names operator decision 48: ${JSON.stringify(hits[0])}`);
|
||||
}
|
||||
const residual = known.filter(b => b.includes('4ab24b07'));
|
||||
assert.equal(residual.length, 1, `exactly one Known-issues bullet names 4ab24b07: ${JSON.stringify(residual)}`);
|
||||
assert.match(residual[0], /operator\s+decision\s+50\b/, `the 4ab24b07 Known-issues bullet names operator decision 50: ${JSON.stringify(residual[0])}`);
|
||||
});
|
||||
|
||||
test('Phase 9 says the merge keeps the copy rule B counts more severe', () => {
|
||||
assert.ok(P9.includes('keeps the copy rule B counts more severe'), 'Phase 9 says "keeps the copy rule B counts more severe"');
|
||||
assert.ok(!P9.includes('keeps the more severe of the two severities'), 'the stale "keeps the more severe of the two severities" is gone');
|
||||
});
|
||||
|
||||
test('Handover 4 states the overlap match and the distinct id', () => {
|
||||
const h4 = flat(handover4());
|
||||
assert.ok(h4.includes('only on overlapping text'), 'Handover 4 says the gate matches "only on overlapping text"');
|
||||
assert.ok(h4.includes('`<rule_key>#n`'), 'Handover 4 names `<rule_key>#n`');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue