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.
206 lines
12 KiB
Markdown
206 lines
12 KiB
Markdown
# 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`.
|