fix(guards): a step whose test is seen green may stop while it waits for its controller

voyage-lekerepo trial (2026-10-06) point 7, operator decision 2026-10-08
(«grønt merke på låsen»): the stop guard blocked Stop three times while the
executor waited for the controller, with the step's test already green.

- test-lock.mjs green --cmd "<Verify>" runs the step's test command (same
  allowlist + denylist as done-line) and records green_at only on exit 0;
  pending clears it; a new lock starts without it.
- stop-test-lock lets a green-marked lock stop; the write guard still
  refuses the locked test files.
- trekexecute: D1 marks green after a PASS; each fix round marks pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-08 06:59:14 +02:00
commit 393b8e5a0a
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
5 changed files with 163 additions and 10 deletions

View file

@ -1486,9 +1486,20 @@ status = `blocked` and apply `On failure: escalate`. Skip D2 manifest
verification (nothing to verify for a read-only sandbox test). Commit none —
jump straight to Phase 7 with a structured "sandbox-blocked" reason.
If **PASS**: proceed to Sub-step D2 (manifest verification). If **FAIL**: the
failure output is a finding for Sub-step F; still run D2 and D3, so the
controller and the next fix round see every failure at once.
If **PASS**: mark the step's test lock green, then proceed to Sub-step D2
(manifest verification). The mark is what lets the session wait for its
controller (Sub-step E) without the stop guard refusing every turn — the
test files stay locked against edits (operator decision 2026-10-08):
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs green --cmd "{Verify command}"
```
It runs the test command itself and marks only on exit 0; exit 1 (the test
did not pass this time) is a finding for Sub-step F like a FAIL, exit 2 (could
not run) is never green. A step that is `"not-locked"` skips it.
If **FAIL**: the failure output is a finding for Sub-step F; still run D2 and
D3, so the controller and the next fix round see every failure at once.
#### Sub-step D2 — Manifest verification
@ -1629,7 +1640,7 @@ session's effort, Sub-step E).
| `fix_rounds` | Goes to | What happens |
|---|---|---|
| 1, 2, 3 | a fresh helper | Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
| 1, 2, 3 | a fresh helper | First `node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs pending` (the step is no longer seen green; D1 marks it again). Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
| 4 | the plan | **After three failed fix rounds the question is the architecture, not a fourth fix.** The step goes back to planning (below). Stop class 4 |
| 5 | the operator | A step already sent to the plan (`fix_rounds` 4) that fails its control again after re-planning goes to the operator. The executor never builds this step again: on a later `--resume` it only runs the checks (B, D1, D2, D3, E) on whatever the operator changed. If they fail again the step stays at 5 — there is no sixth round, `fix_rounds` never exceeds 5 — and the run stops again. Stop class 4 |