fix: close the six defects an independent review found in the trial fixes

Independent read-only review of c56f852..2d29966 (2026-10-08), each defect
reproduced by the reviewer; each new test fails on that code.

- test-lock green ran ANY allowlisted command: `green --cmd "git status"`
  marked a red step green and the stop guard let it stop. The lock now
  stores the step's test command (`lock --cmd`); `green` runs only that
  one (a different --cmd, or a lock without one, exits 2). trekexecute D1
  calls `green` with no command, which also removes the quoting hazard.
- plan-gate --accept-open worked at round 1 and passed the gate; it now
  refuses while rounds are left.
- findings-schema --repo-root swallowed a following flag, or took a root
  that does not exist, and silently skipped the line check: exit 2.
- brief-review-stamp hashed only the body, so a changed `framing` or
  `research_topics` stayed "current"; it now covers the reviewed
  frontmatter too (only the post-review stamps are left out).
- red-proof red_kind read an assertion message naming SyntaxError as a
  load error; the signatures are now specific.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-08 07:17:45 +02:00
commit 5f9faeed4a
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
12 changed files with 192 additions and 35 deletions

View file

@ -10,10 +10,10 @@
// test-lock.json), so it can never be committed, and each worktree has its own.
//
// Usage:
// node lib/util/test-lock.mjs lock --files <a,b> --step <N> [--cwd <dir>]
// node lib/util/test-lock.mjs lock --files <a,b> --step <N> [--cmd "<test command>"] [--cwd <dir>]
// node lib/util/test-lock.mjs unlock [--cwd <dir>]
// node lib/util/test-lock.mjs status [--cwd <dir>] (JSON on stdout)
// node lib/util/test-lock.mjs green --cmd "<test command>" [--cwd <dir>]
// node lib/util/test-lock.mjs green [--cmd "<test command>"] [--cwd <dir>]
// node lib/util/test-lock.mjs pending [--cwd <dir>]
// Exit: 0 = done, 1 = refused (another step holds the lock; for green: no
// lock, or the test command did not exit 0), 2 = could not run (not a
@ -24,7 +24,10 @@
// three times while the executor waited for its controller, with the step's
// test already green (voyage-lekerepo trial, 2026-10-06). `green` RUNS the
// step's test command (the same allowlist and denylist as done-line.mjs) and
// records `green_at` only on exit 0; the stop guard then lets the session
// records `green_at` only on exit 0. The command is the one stored at `lock
// --cmd` time, never one chosen at mark time: an independent review showed
// `green --cmd "git status"` marking a red step green. A lock without a
// stored command is never marked (exit 2); a --cmd that differs is refused; the stop guard then lets the session
// stop, while the write guard keeps refusing the locked files. `pending`
// clears the mark when a fix round starts; a new `lock` starts without one.
// Paths in --files are relative to the --cwd (default: the current directory).
@ -157,7 +160,7 @@ function main(argv) {
const l = cur.lock || {};
process.stdout.write(JSON.stringify({
locked: cur.state === 'locked', step: l.step ?? null, files: l.files ?? [], session: l.session ?? null,
locked_at: l.locked_at ?? null, green_at: l.green_at ?? null, lock_path: cur.path,
locked_at: l.locked_at ?? null, cmd: l.cmd ?? null, green_at: l.green_at ?? null, lock_path: cur.path,
}, null, 2) + '\n');
process.exit(0);
}
@ -169,12 +172,14 @@ function main(argv) {
if (cmd === 'pending') {
delete lock.green_at;
} else {
if (!args.cmd) fail(2, 'green needs --cmd "<the step\'s test command>"');
const allowed = allowedCommand(args.cmd, { cwd: cur.repo.root });
const testCmd = typeof lock.cmd === 'string' && lock.cmd.trim() !== '' ? lock.cmd : null;
if (!testCmd) fail(2, 'the lock names no test command (lock with --cmd "<Verify>"); never marked green');
if (args.cmd !== undefined && args.cmd !== testCmd) fail(2, `--cmd is not the step's test command stored in the lock (${testCmd})`);
const allowed = allowedCommand(testCmd, { cwd: cur.repo.root });
if (!allowed.allowed) fail(2, `command not run: ${allowed.reason}`);
const screen = screenCommand(args.cmd);
const screen = screenCommand(testCmd);
if (!screen.allowed) fail(2, `command blocked: ${screen.rule}`);
const r = spawnSync('/bin/sh', ['-c', args.cmd], {
const r = spawnSync('/bin/sh', ['-c', testCmd], {
cwd: cur.repo.root, env: commandEnv(), encoding: 'utf8', timeout: DEFAULT_TIMEOUT_MS, maxBuffer: 64 * 1024 * 1024,
});
if (r.error || r.status === null || r.status === 126 || r.status === 127) {
@ -211,6 +216,10 @@ function main(argv) {
session: process.env.CLAUDE_CODE_SESSION_ID || null,
locked_at: new Date().toISOString(),
};
if (args.cmd !== undefined) {
if (String(args.cmd).trim() === '') fail(2, '--cmd is empty');
lock.cmd = String(args.cmd);
}
mkdirSync(dirname(cur.path), { recursive: true });
writeFileSync(cur.path, JSON.stringify(lock, null, 2) + '\n');
writeSessionPointer(lock.session, cur.path);