Files
jason.woltje 158205c5cc lead: commit darkwing's untracked evidence (348 files: queue-137 queue-141 queue-150 queue-162 queue-165 queue-166 queue-167 queue-169 queue-179 queue-180 queue-181 queue-182 queue-185 queue-186 queue-187 queue-188 queue-190 queue-93)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:14 -06:00

12 KiB
Raw Permalink Blame History

Row 180: activation ids and plan-mode refusals

Brief: "# Brief: activation ids and plan-mode refusals (row A)" in docs/plans/2026-10-20_stack-release-after-window.md (blob aafa471ae, c7f3beb17). Gitea #1678, Vikunja P19 391, plus N1 and N2 from Dewey's row 176 review. Owner Darkwing, reviewer Dewey, gate owner Sage.

Files

File What
build.patch the change, git diff 7f519242e over the five paths below
candidate-manifest.sha256 one line per changed path
  • Base: 7f519242e for round 2 (sage-1171). Round 1 was built on c7f3beb17. No commit in c7f3beb17..7f519242e touches the five paths, and the patch is byte for byte the same on either base. At 03:29Z, refactor was c7421cabd, and the two commits after 7f519242e touch only plans and SESSIONS.
  • Changed: scripts/stack-release.sh, scripts/webui-service.sh, packages/cli/tests/stack-release.test.mjs, packages/webui/tests/service.test.mjs, docs/TOOLS.md. These are the brief's touched files, with nothing else.
  • Digests and patch-id: see "Candidate" at the end.

Item 1: activation ids within one second (P19 391)

next_activation() takes the UTC second once and tries the base id <second>-<TO:0:12>, then -1 through -9. A name is free when none of these exist:

  • $STATE/stack-rollback/<id>
  • $DATA/backups/stack/<id>
  • $DATA/bus.post-attempt-<second><suffix>

It sets ACT and POST from the same suffix. With all ten taken it refuses capture-failed ("every activation id for X is taken; nothing was stopped") and clears ACT and POST first, so the refused log line has activation null. new_activation() calls it and sets RS, and do_adopt calls it in place of its own date line. All three callers run after lock(): activate and rollback reach it only past the --plan exit, and adopt and pm-on lock first.

Why a suffix and not milliseconds. The brief allowed either. With milliseconds, the brief's pinned-clock test can't produce two runs in one stamp, and the shape of every id would change. With the suffix, the base form is still a whole id, and lexical order is still time order: X < X-1 < ... < X-9 < the next second. Sage approved it (sage-1155).

A side effect: the base code took POST from a second date call, which could land in the next second from ACT. POST now uses ACT's second.

--restore (:78) now takes ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[1-9])?$.

Check (b): everything that builds or parses an activation id (sage-1155)

git grep over the tree outside v1/, agents/ and docs/plans for stack-active, stack-activation-log, stack-rollback, backups/stack and post-attempt finds three files: scripts/stack-release.sh, its test and docs/TOOLS.md. Line numbers are at the candidate.

Hit What it does with the id Result
stack-release.sh:50 log_line writes ACT into each log line fine, writes only
:100-110 read_pointer checks typeof p.activation==="string", sets P_ACT fine; P_ACT is read nowhere else
:116 write_pointer writes ACT fine
:128 interrupted() keys open runs on the exact activation string fine; a suffixed id is a distinct key
:331 backup_store, :355 restore_store take the backup dir path, never parse it fine
:78 --restore regex parsed the old form only changed, takes both
:827, :883 do_rollback --restore path builds $DATA/backups/stack/$RESTORE fine, a name only
test :378 T1 ^\d{8}T\d{6}Z-<B12>$ on an adopt id fine, one run, no collision
test :558 T13 post-attempt name \d{8}T\d{6}Z fine, one run
test :678 T24 capture-failed message with the base id fine, the base form is still first
test :274 posts() startsWith('bus.post-attempt-') fine
test :950 T34 notEqual between two runs' ids now always true, which is what the test wanted
TOOLS.md :382-433 the pointer, rollback sets, backups, post-attempt and --restore updated

Unrelated hits I checked: packages/cli/tests/notifier.test.mjs:255, a torn-files regex. scripts/release.sh and packages/runs write their own activation-log.jsonl for the other release system.

Item 2: plan-mode refusals in console_for_target (N1)

  • parse_opts no longer checks the tester port inline. After the loop it checks the port unless PLAN=1. An empty --tester-port "" is still usage, through a new [ -n "$2" ].
  • In console_for_target, an invalid port calls usage outside --plan, as before. Under --plan it goes through check_refuse tester-port-invalid. tester-port-target goes through check_refuse in both modes. Outside --plan, check_refuse is refuse, so the output is unchanged.

Deviation from the brief (sage-1155)

The brief says the bad port "gives the same reason text usage gives today". Usage has no reason line. It prints the usage block and exits 4. Sage ruled that this was a brief error and that I should use a reason line:

  • main Console port 7332: --tester-port 80 is not a whole port from 1024 to 65535 other than 7332
  • otherwise: ... other than 7332 and the Console's --port N

Without --plan the output is still the usage block and exit 4, byte for byte. Sage won't re-pin the brief for this.

Item 3: webui-service tester port length (N2)

tester_port_ok checks ${#TESTER_PORT} -le 5 on the value as written, before it strips zeros. That's the count serve uses (webui cli.mjs:30, /^\d{1,5}$/) and the one valid_tester_port in stack-release.sh uses. 007333 now refuses with exit 4, and 07333 still renders.

Tests

  • T43 (stack-release.test.mjs), item 1. A date stand-in on PATH pins every call to one second, except calls with %s (the gate's start marks), which go to /usr/bin/date. Pinning those too would make two gates' journal windows overlap.
    • Activate C1 with --pm-after-harness, then pm-on on the same copy. The ids are id and id-1, with matching log lines, two rollback sets and a SHA256SUMS in each.
    • Two migrate-and-restore rounds. Round 1 restores an old-form id and round 2 a suffixed one. Each prints its own post-attempt path. At the end there are two posts, two backups and four rollback sets.
    • Ten names taken: eight rollback sets, one backup and one post-attempt store. The run refuses capture-failed with no start or stop and an unchanged fingerprint. Its log line has activation null.
    • --restore with -0, -10, -a, - or a 13th hex character appended is usage (exit 4). The base form, -1 and -9 are not.
  • T44, item 2.
    • --plan --tester-port P with --pm-after-harness on a business file that sets harness. P is 80 and the Console's port, against a pointer with --port, then 80 and 7332 against one without. Each case lists exactly the tester-port-invalid line and the pm-after-harness-set line, exits 3 and leaves stderr empty. The same flags without --plan give exit 4, empty stdout and stderr equal to --help's. Nothing starts or stops, and the log doesn't grow.
    • Activate A, whose Console lacks --tester-port, with --plan. It lists tester-port-target and unknown-kind-for-target and exits 3. Without --plan it refuses tester-port-target with today's message and one log line.
  • Round 2 added two subtests for Dewey's round 1 (#1678 comment 29352).
    • C1, in T43: "adopt takes the next free id too". The clock is pinned, there's no pointer, and stack-rollback/<id> already exists for the copy adopt will take. Adopt exits 0, and both the pointer's activation and the one log line are <id>-1.
    • C2, in T44: "without --plan a bad port is usage before any pointer read". With no pointer, activate C1 acme --tester-port 80 exits 4, with empty stdout, stderr equal to --help's, no log line and no start or stop. Without the post-loop check (:85), pointer_for_business refuses no-pointer first, with exit 3 and a log line.
  • service.test.mjs, item 3: "tester service counts the digits as written before it strips leading zeros".

Runs

All runs used the worktree ~/darkwing-scratch/r180/wt at base c7f3beb17, with node_modules linked. Totals come from the ℹ summary lines.

Run 1, slot 02:40:51.625Z to 02:44:44.402Z (sage-1155):

Run Result End
stack-release.test.mjs whole 106 pass, 0 fail 02:44:22.823Z
service.test.mjs whole 28 pass, 0 fail 02:44:25.286Z
m1 no suffix (for s in "") killed, T43: second run refuses capture-failed 02:44:31.894Z
m1b old --restore regex killed, T43: suffixed --restore is usage 02:44:39.467Z
m2 invalid port ends --plan (usage) killed, T44: exit 4, expected 3 02:44:41.429Z
m2b refuse tester-port-target killed, T44: stderr not empty under --plan 02:44:44.247Z
m3 length line removed in webui-service.sh killed: 007333 exit 0, expected 4 02:44:44.402Z

Defect found during the build

After run 1, while writing this packet, I found that the ten-names refusal wrote the -9 id, which belongs to another run, into its refused line. lock() refuses interrupted-run before this point (:562), so the line couldn't close an open run, but a refusal record named a run that wasn't its own. The fix clears ACT and POST before that refusal. The ten-names subtest in T43 tests it, and Sage agreed with the fix (sage-1159). Run 2 covers the fix and adds two mutants, on the backups/stack check and the post-attempt check. Those prove the brief's "chosen while holding the lock, against both stack-rollback/ and backups/stack/", which run 1 didn't cover.

Run 2, slot 02:47:46.563Z to 02:51:59.884Z (sage-1159):

Run Result End
stack-release.test.mjs whole 107 pass, 0 fail (run 1 plus the ten-names subtest) 02:51:22.962Z
m1 no suffix killed, T43: second run refuses capture-failed 02:51:29.999Z
m1c ACT="" POST="" removed killed: activation ...-9, expected null 02:51:39.603Z
m1d no backups/stack check killed: the run took the -8 name and captured 02:51:49.775Z
m1e no post-attempt check killed: the run took the -9 name and captured 02:51:59.883Z

service.test.mjs and scripts/webui-service.sh didn't change after run 1. Each mutant edited the worktree file after a backup checked by sha256, and the script put the file back and checked the sha again.

No other test file runs the changed code. packages/webui/tests/pinned-copy.test.mjs and packages/cli/tests/stack-copy.test.mjs run webui-service.sh copy, which doesn't reach tester_port_ok. No test pins a sha256 of either script. Sage's gate runs the suites.

Round 2, slot 03:24:32.105Z to 03:28:20.029Z (sage-1171), worktree on 7f519242e:

Run Result End
stack-release.test.mjs whole 109 pass, 0 fail (run 2 plus C1 and C2) 03:28:05.528Z
X2 adopt's own date line back in place of next_activation killed, C1: activation <id>, expected <id>-1 03:28:16.516Z
X1 post-loop port check (:85) removed killed, C2: exit 3 no-pointer, expected 4 03:28:20.029Z

Dewey said nothing else needed re-running. service.test.mjs and both scripts are unchanged since run 2.

Dewey's N1 is a note only and isn't changed in this round. Under --plan, a hard refusal before console_for_target, such as no-pointer, ends the run before the port is checked, so tester-port-invalid isn't on that plan.

Candidate

Round 2:

  • candidate-manifest.sha256 digest c743480eb1e92ee1e1c4365d715543466f58c712a73c8cc39d6147600c1b2c06.
  • build.patch sha256 109b774d43573cabb8044dac4907bdc8fb19295cedd2f7f46f8a9980154f3c45, git patch-id --stable 31b1d502444eb4b6589662e6b917600a40030b2f. 161 insertions, 10 deletions, five files. It applies to c7421cabd (git apply --check).
  • Only the test file changed from round 1. Round 1 was manifest digest 26ea8e88d23a and patch-id 30a272d49b79.
Path sha256
docs/TOOLS.md 3468a2d2eeeb0175dc37499e89e609144ff3a07bb275fea64b82bbdbd64e4de5
packages/cli/tests/stack-release.test.mjs 09c6cdd7568cd6769d9a39c7f5655df1850b1de7e01712fac0bad84ff2b692d5
packages/webui/tests/service.test.mjs 1fca5facd9be5014803cef88a4f79d51c7b67920bdf001a1909daa2a1339fa03
scripts/stack-release.sh f225cfdf5d2ba3f828b45c8af3c0da7f7f9d8722daa6890c374e4253f3ee37a7
scripts/webui-service.sh 0636852303b73aa9ad4761679bb7c7bda4eca3e4ff56d5f67d2cb1d1adc75b80

The patch adds no em dash. BUILD.md and the TOOLS.md lines pass tools/unslop-hook/unslop-check.js.