fix(queue): genesis owner-not-reviewer check, assign wording, queue-commit HEAD-moved message, calendar dates (row 33, #1508)
Built by filbert, approved by darkwing in round 1 (#1508 comment 26651). Manifest fe7da3ef, 10 paths. Suites green on an index export. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# Queue row 33 build notes (#1508)
|
||||
|
||||
Filbert, 2026-10-04. Brief: `docs/plans/2026-10-04_queue-follow-ups.md`
|
||||
(blob 1dc08bf0…). Reviewer: Darkwing. Base: HEAD 3e39a26a, queue rev 45.
|
||||
Candidate: `candidate-manifest.sha256` in this directory, uncommitted.
|
||||
Sage commits it after approval.
|
||||
|
||||
## What changed
|
||||
|
||||
1. **Genesis and the owner-not-reviewer rule.** `parseMigrationMap` refuses
|
||||
a map row whose owner is among its reviewers: "migration map row N owner
|
||||
SEAT can't be a listed reviewer", exit 2. Genesis parses the map before
|
||||
it writes anything, so no file, witness or view changes.
|
||||
2. **Assign wording.** "can't assign row N to SEAT: SEAT is one of its
|
||||
reviewers". The two tests that matched the old text now match this one.
|
||||
3. **HEAD moved.** `queue-commit.sh` gains `head_moved`, called in
|
||||
`guard_check` before the canary and again when the clean run fails. A
|
||||
moved HEAD exits 2 with "refused (STEP): HEAD moved since H was
|
||||
recorded (another commit landed); run queue-commit.sh again". A real
|
||||
guard failure, with HEAD unmoved, keeps the old message.
|
||||
4. **Calendar dates.** `checkTime` refuses any ISO time or date that
|
||||
doesn't format back to itself through `Date.parse` and `toISOString`:
|
||||
"WHAT is not a calendar time|date: VALUE". A shape error keeps its old
|
||||
message. Every time the validator reads goes through `checkTime`: row
|
||||
times, review times and log entry times.
|
||||
5. **Cold start.** See "Cold runs" below.
|
||||
|
||||
README: the commit steps, the owner rule and the time format. DEFERRED:
|
||||
four items move to Done, and the cold-start item too, if no failure shows
|
||||
up.
|
||||
|
||||
## Choices
|
||||
|
||||
- **C1. The genesis check sits in the map parser, not in replay.** The
|
||||
review-record refusal is kept as defense in depth for a log written
|
||||
before the rule (`review.test.mjs`). A check in `checkGenesis` would
|
||||
make such a log unreadable. A new data test pins this: the parser
|
||||
refuses the map, and a genesis document built past the parser still
|
||||
loads. Mutant M10 (the rule added to replay) is killed by it.
|
||||
- **C2. "another commit landed", not "another queue commit landed".** The
|
||||
brief quotes the second wording. The check before the canary fires for
|
||||
any commit, including one that touches no queue file, so "queue" would
|
||||
be wrong there. The rest of the message is the brief's.
|
||||
- **C3. HEAD is checked twice.** The brief asks for a check before the
|
||||
canary. A commit can still land between that check and the hook run, and
|
||||
then the old misdiagnosis comes back. A second check, only when the
|
||||
clean run fails, closes that gap. It adds one `rev-parse` on a failing
|
||||
path only.
|
||||
- **C4. Two existing F1 tests changed.**
|
||||
- "HEAD moving after commit-tree and before update-ref" moved its
|
||||
trigger to `before update-ref`. A move after commit-tree is now caught
|
||||
at the step-7 check (exit 2). The test still proves update-ref's
|
||||
expected-old-value check, now at the only point where it's the last
|
||||
line.
|
||||
- "H is recorded before the canary…": a commit outside the queue files
|
||||
during the step-1 canary used to reach update-ref (exit 1). It's now
|
||||
refused at the step-7 check (exit 2), before that check's canary, and
|
||||
update-ref never runs. The test asserts that.
|
||||
- **C5. 24:00 is refused.** V8 parses `T24:00:00.000Z` as the next day's
|
||||
midnight, so it doesn't round-trip. Minute 60 and second 60 already
|
||||
parse as NaN.
|
||||
|
||||
## Tests
|
||||
|
||||
- `data.test.mjs`:
|
||||
- a new genesis test (map refusal; replay tolerance);
|
||||
- a new calendar test: month 13, month 0, day 32, 2026-02-30,
|
||||
2027-02-29 and 2026-04-31 as dates in `requiredSince` and `createdAt`;
|
||||
the same days plus 24:00, minute 60 and second 60 as ISO times in
|
||||
`updatedAt` and `requiredSince`; 2028-02-29 valid in both forms; the
|
||||
shape message kept; a log entry `at` refused on replay;
|
||||
- the assign wording.
|
||||
- `store.test.mjs`: genesis from a map with row 9's owner among its
|
||||
reviewers exits 2. For that repository and the two refusals before it,
|
||||
there's no queue.json, no witness and an unchanged QUEUE.md. Also the
|
||||
assign wording.
|
||||
- `commit.test.mjs`, two new tests, both with a nested
|
||||
`queue-commit.sh` run landing a real queue commit:
|
||||
- after `symbolic-ref` (H recorded, before step 1's check): refused at
|
||||
step 1 with the HEAD-moved message, no canary run, and a rerun commits
|
||||
the next revision;
|
||||
- before the first `git hook run`: the clean run fails on the moved
|
||||
HEAD, and the recheck reports HEAD moved, not the guard.
|
||||
The existing "the canary refuses a hook that git would not run" test
|
||||
still covers the guard message.
|
||||
|
||||
## Mutants
|
||||
|
||||
Eleven, run on an export of the candidate. Ten are killed:
|
||||
|
||||
| | Mutant | Killed by |
|
||||
|---|---|---|
|
||||
| M1 | no HEAD check before the canary | step-1 and step-7 HEAD tests |
|
||||
| M2 | no recheck after a failed clean run | the between-check-and-canary test |
|
||||
| M3 | `head_moved` exits 1 | all three HEAD tests |
|
||||
| M4 | no round trip | calendar test |
|
||||
| M5 | NaN check only | calendar test |
|
||||
| M6 | round trip on ISO times only | calendar test |
|
||||
| M7 | round trip on dates only | calendar test |
|
||||
| M9 | genesis rule removed | data and store genesis tests |
|
||||
| M10 | genesis rule also in replay | data genesis test |
|
||||
| M11 | old assign wording | data and store assign tests |
|
||||
|
||||
One survives:
|
||||
|
||||
- **M8** compares only the date part of an ISO time. It's equivalent
|
||||
under V8. The only out-of-range time V8 parses is 24:00, and that moves
|
||||
the date, so the date part catches it.
|
||||
|
||||
## Suites
|
||||
|
||||
On the final candidate bytes, HEAD 3e39a26a:
|
||||
- canonical checkout: `node --test packages/queue/tests/` passes 148/148
|
||||
and `bash scripts/test-queue.sh` 29/0;
|
||||
- an export of HEAD with the candidate files: `bash scripts/test-queue.sh`
|
||||
27/0 (the live checks skip, as designed);
|
||||
- `queue verify --current`: ok at rev 45. The live log replays under the
|
||||
calendar check.
|
||||
|
||||
## Cold runs
|
||||
|
||||
Not reproduced in 20 cold runs. `cold.sh` (here) built each tree fresh
|
||||
from HEAD 3e39a26a plus the candidate files: odd runs as a
|
||||
`git clone --shared` with push set to DISABLED, even runs as a
|
||||
`git archive` export. It ran `node --test packages/queue/tests/` once in
|
||||
each, one run at a time with nothing else running, then deleted the tree.
|
||||
Every run passed 148/148, 2960 of 2960 in total, taking 39 to 61 s each.
|
||||
The counts are in `cold-runs.txt`. The 300 ms deadline test is unchanged.
|
||||
|
||||
Run 1 started with a `queue-commit.sh` that differed from the candidate in
|
||||
one comment line, the step-1 comment, which I reworded during run 1. Runs
|
||||
2 to 20 used the candidate bytes.
|
||||
@@ -0,0 +1,21 @@
|
||||
run 1 clone exit=0 pass=148 fail=0 secs=41
|
||||
run 2 export exit=0 pass=148 fail=0 secs=39
|
||||
run 3 clone exit=0 pass=148 fail=0 secs=39
|
||||
run 4 export exit=0 pass=148 fail=0 secs=48
|
||||
run 5 clone exit=0 pass=148 fail=0 secs=59
|
||||
run 6 export exit=0 pass=148 fail=0 secs=47
|
||||
run 7 clone exit=0 pass=148 fail=0 secs=42
|
||||
run 8 export exit=0 pass=148 fail=0 secs=46
|
||||
run 9 clone exit=0 pass=148 fail=0 secs=47
|
||||
run 10 export exit=0 pass=148 fail=0 secs=42
|
||||
run 11 clone exit=0 pass=148 fail=0 secs=45
|
||||
run 12 export exit=0 pass=148 fail=0 secs=47
|
||||
run 13 clone exit=0 pass=148 fail=0 secs=43
|
||||
run 14 export exit=0 pass=148 fail=0 secs=40
|
||||
run 15 clone exit=0 pass=148 fail=0 secs=40
|
||||
run 16 export exit=0 pass=148 fail=0 secs=42
|
||||
run 17 clone exit=0 pass=148 fail=0 secs=39
|
||||
run 18 export exit=0 pass=148 fail=0 secs=61
|
||||
run 19 clone exit=0 pass=148 fail=0 secs=57
|
||||
run 20 export exit=0 pass=148 fail=0 secs=52
|
||||
done
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# 20 cold runs of node --test packages/queue/tests/ for row 33 item 5.
|
||||
set -u
|
||||
SRC=/mnt/storage/src/mosaic-stack
|
||||
W=/tmp/fq33
|
||||
HEADC=$(cat $W/base.head)
|
||||
FILES="packages/queue/README.md packages/queue/src/queue.mjs packages/queue/tests/commit.test.mjs packages/queue/tests/data.test.mjs packages/queue/tests/store.test.mjs scripts/queue-commit.sh"
|
||||
: > $W/cold-results.txt
|
||||
for i in $(seq 1 20); do
|
||||
d=$W/cold-$i
|
||||
rm -rf $d
|
||||
if [ $((i % 2)) = 1 ]; then
|
||||
kind=clone
|
||||
git clone -q --shared --no-checkout $SRC $d && git -C $d remote set-url --push origin DISABLED && git -C $d checkout -q --detach $HEADC
|
||||
else
|
||||
kind=export
|
||||
mkdir -p $d && git -C $SRC archive $HEADC | tar -x -C $d
|
||||
fi
|
||||
(cd $W/base && tar -c $FILES) | tar -x -C $d
|
||||
start=$(date +%s)
|
||||
(cd $d && env -u NODE_TEST_CONTEXT node --test --test-reporter=tap packages/queue/tests/ > $W/cold-$i.tap 2>&1)
|
||||
rc=$?
|
||||
end=$(date +%s)
|
||||
pass=$(grep -E '^# pass' $W/cold-$i.tap | awk '{print $3}')
|
||||
fail=$(grep -E '^# fail' $W/cold-$i.tap | awk '{print $3}')
|
||||
failed=$(grep -E '^not ok' $W/cold-$i.tap | tr '\n' ';')
|
||||
echo "run $i $kind exit=$rc pass=$pass fail=$fail secs=$((end-start)) $failed" >> $W/cold-results.txt
|
||||
[ "$fail" = 0 ] && rm -f $W/cold-$i.tap
|
||||
rm -rf $d
|
||||
done
|
||||
echo done >> $W/cold-results.txt
|
||||
Reference in New Issue
Block a user