12 KiB
Row 239 (#1745) build notes (append only)
2026-10-22 b1 (Honker)
Design d1 5da5f28f6 was approved by Duckworth with C1 and C2 and no blockers (duckworth-0165, note
agents/duckworth/work/row239/REVIEW-design-d1.md). The code is on honker/row239 from aca16404c. No test
has run yet: every run waits for Sage's CLEAR.
Duckworth's conditions and answers
- C1 (option a):
outcome-unknown-revokedis in the Console'sOPENlist (webui bus.mjs), sosendOutcomeanswers it asoutcome-unknownwithsent: 'unknown'and never "Nothing changed". T6 covers it and M3f is its mutant. - C2:
write()setsrevoked = trueafterbeforeWrite()returns, and a catch around the wholewithLock(...)call marks the error. So a throw from the lock's release in its finally is marked as well. This is structural and has no test, as Duckworth said. - Q1:
beforeWriteruns after both validations. Q2: two codes. Q3: M2 removes the:188check (now organization.mjs:195).
Notes N1-N5
- N1:
beforeWritemust not write the same business, because it would find its own lock and refuseorganization-busy. This is said in the comments onwrite()andcommit(). Today's revokes (the bus's credential revoke) write no business file. - N2: the lock is held across the revoke. A fire or a delete now keeps other writers of that business
out for as long as the revoke takes. They refuse
organization-busy, as they do for any held lock. - N3: a revoke that fails partway (some tokens revoked, then
credential-revoke-failed) behaves as today. The error carries norevokedmark, becausebeforeWritedidn't return, and nothing is written. This row doesn't change that. - N4: a delete closed with
outcome-unknown-revokedexits 3 in the CLI, as every closed refusal does. Exit 1 (transport EXIT) applies only to a direct verb answer. - N5: I grepped for "before the write" and similar comments. The ones left (lifecycle-revoke.test:2, instance-settings:56, cli instances.mjs:53, serve.mjs:365) are still true and are unchanged. T21 in bus lifecycle-revoke.test:173 is unchanged.
Differences from the design
- The business tests are in lifecycle-settings.test.mjs, not instance-settings.test.mjs. The fire tests
and their fixture already live there. The fixture now returns
dirandrolesDirtoo. - T3 covers T3b (the delete) in the same test, looping over fire and delete. It also checks that a
revoke throwing
credential-revoke-failedcarries no mark and writes nothing. - T4b is the plain-code check. The design's "T4b, outcome-unknown-revoked for an fs-style error" is in T4, which checks EIO with and without the mark for both a fire and a delete.
- The bus test's fake org gets
deleteError, thrown from itsinstances.setbranch, so T4 can drive a delete. - transport.test gets one assertion,
busExit("outcome-unknown-revoked") === 1, for M3g. - A latent Console text: a direct (not closed-request)
revision-conflict-revokedanswer in the Console would still get " Nothing changed." appended, because onlyOPENcodes skip it. That sentence is true there: the file is unchanged. No direct human path gives this code today, since a delete reaches the Console as a closed request's refusal. - The Console's STATUS entry for
outcome-unknown-revoked(503) can't be reached while the code is inOPEN. It stays for symmetry with the CLI and is harmless.
What changed
- packages/business/src/organization.mjs:
write()takesbeforeWrite, runs it under the lock after the revision check and both validations, and marks any later errorrevoked. The body is reindented inside the try. - packages/business/src/instance-settings.mjs:
instanceSettingstakes an optionalwriter.commit()passesbeforeWritetowriter.writeinstead of calling it. The early check stays, and its comment is rewritten. - packages/bus/src/broker.mjs:
#organizationFailmaps a marked error torevision-conflict-revokedoroutcome-unknown-revokedbefore the existing mappings.#fireJob's notice adds "Its credential tokens may already be revoked." for a-revokedcode. - packages/bus/src/runtime.mjs: comment only.
- packages/cli/src/instances.mjs: two REASONS entries. packages/cli/src/transport.mjs: EXIT
outcome-unknown-revoked1. - packages/webui/src/serve.mjs: two AGENT_REASONS entries. packages/webui/src/bus.mjs:
OPENentry and two STATUS entries. - Tests:
- business lifecycle-settings.test: "row 239 T1", "row 239 T2", "row 239 T3".
- bus fire-revokes-grants.test: "row 239 T4", "row 239 T4b".
- cli instances.test: "row 239 T5 CLI". cli transport.test: one assertion in "busExit and refuseInsideAgent".
- webui agents.test: "row 239 T6".
- bus channel-proof.test re-pins packages/cli/src/transport.mjs (662642a8...) and packages/webui/src/bus.mjs (1eba9774...), with a comment on each. Neither changes a proof or the transport. The design's file list missed this. The pin grep found it before the first run.
Mutants for the gate (exact-once replacements)
- M1 (instance-settings.mjs, beforeWrite before the lock): replace
const receipt = writer.write(business, next, { revision, actor, context, beforeWrite });withif (beforeWrite) beforeWrite();\n const receipt = writer.write(business, next, { revision, actor, context });. The kill is lifecycle-settings "row 239 T1" (the revoke spy has a call). - M2 (organization.mjs, the fire refusal dropped): delete the line
if (revision !== before) fail("revision-conflict", "organization revision changed; read again");. The kill is "row 239 T1" (the write lands over the hand edit). - M3a (organization.mjs, the mark dropped):
if (revoked && e instanceof Error) e.revoked = true;becomesvoid revoked;. The kills are "row 239 T2" and "row 239 T3" (business). T4 drives the mark through a fake org, so it doesn't see M3a. - M3b (broker.mjs): delete the line
if (e.revoked) fail(e.code === "revision-conflict" ? "revision-conflict-revoked" : "outcome-unknown-revoked");. The kill is fire-revokes-grants "row 239 T4". - M3c (cli instances.mjs): delete the five lines from
"revision-conflict-revoked": "the business filethrough"business file again with \mosaic business instances` before trying again",`. The kill is instances.test "row 239 T5 CLI". - M3d (webui serve.mjs): delete the two lines starting
'revision-conflict-revoked': 'the business fileand'outcome-unknown-revoked': 'the agent\'s tokens. The kill is agents.test "row 239 T6". - M3e (broker.mjs):
const tokens = code.endsWith('-revoked') ? ' Its credential tokens may already be revoked.' : '';becomesconst tokens = '';. The kill is "row 239 T4". - M3f (webui bus.mjs, C1): the
OPENline loses, 'outcome-unknown-revoked'. The kill is "row 239 T6" (the launch POST answers 503 outcome-unknown, sent unknown). - M3g (cli transport.mjs): delete the line
"outcome-unknown-revoked": 1,. The kill is transport.test "busExit and refuseInsideAgent".
A kill counts only as a failed assertion in the named test, not a load error or a different test.
Gate plan (mos-1275 timeouts)
- My row 256 gate (2026-10-22 10:34Z, same machine) took 590s for the cli package, 57s for webui and 38s for test-foundation, the longest shell script.
- Packages:
timeout -k 10 1800per package (3x the cli run). - Each scripts/test-*.sh:
timeout -k 10 300(more than 3x the 38s longest). - Single files and mutants:
timeout -k 10 600per file run (cli instances.test whole is the longest single file). - Order on Sage's CLEAR: the touched test files whole (business lifecycle-settings, bus fire-revokes-grants and channel-proof, cli instances and transport, webui agents), then M1-M3g, then the full gate.
- Before the in-review move: grep packages/*/tests for the changed comment and error text and for 64-hex pins on the changed files, and drift-check BASE..refactor against them.
2026-10-22 r2 rebase (Honker)
- Duckworth approved b1
b0c28366fstatically, with no blockers (duckworth-0168). On N1 (endsWith('-revoked')would also matchaccess-revoked, which can't reach a fire job today), the check stays as it is, so the approved candidate doesn't change before the gate (honker-0163). - Rows 253 (
48e450ce8) and 275 landed. b1 is rebased onto refactor94c33a43cas honker/row239-r2. The push branch honker/row239 isn't rewritten. - Drift between
aca16404cand94c33a43con the 15 paths: only packages/bus/src/broker.mjs, where row 275 changes the viewer verbs. Neither of row 239's two hunks (#fireJoband#organizationFail) moves in meaning. The rebase applied with no conflict. The two pinned files (cli transport.mjs, webui bus.mjs) and channel-proof.test have no upstream change, so the b1 pins stand. - Each M1-M3g replacement string still matches exactly once.
- The touched test files run whole again on the new base, in the slot (the rebase rule).
2026-10-23 r3 rebase (Honker, sage-3730)
- r2 (
70c6e3772) is rebased onto refactor93694c518as honker/row239-r3. honker/row239-r2 isn't rewritten. - Between
94c33a43cand93694c518, five of the 16 paths changed upstream: bus broker.mjs and runtime.mjs, channel-proof.test, and webui bus.mjs and serve.mjs. Every file merged on its own except channel-proof.test, where row 301 (59f0e44f4, mos-1449) re-pinned webui bus.mjs for the user transport fold. The merged pin keeps row 301's comment and row 239's, and takes sha256 e3f52172…1c2a89 of the merged bus.mjs. The transport.mjs pin (662642a8…6e55b) still matches the file. - Row 239's hunks in broker.mjs (
#fireJob,#organizationFail), runtime.mjs (the revoke comment), bus.mjs (OPEN, STATUS) and serve.mjs are unchanged apart from their position. The range-diff shows only the channel-proof pin and the hunk context moved. - Each M1-M3g replacement string still matches exactly once. M3f's target is the whole
OPENline, since, 'outcome-unknown-revoked'also appears in the STATUS line. - No 64-hex pin in packages/*/tests or scripts names a changed file except channel-proof.test.
- On the CLEAR (sage-3730): the touched files whole, then M1-M3g, then the counts here. Sage runs the full gate on the replay at landing.
2026-10-23 r4 replay and the slot run (Honker, sage-3773 CLEAR)
-
On the CLEAR, refactor was
546366f4a(row 320 landed at094124b4e). None of the 16 manifest paths changed between93694c518and546366f4a. Upstream did change library src/index.mjs, which bus runtime.mjs imports, and business src/index.mjs, which the cli imports, so the run was on a replay onto546366f4arather than on r3's base. That replay is honker/row239-r40cae2dad5. The range-diff of r3 against r4 is four=lines with equal patch-ids, so duckworth-0292's read of r3 applies to r4. r3 isn't rewritten. -
The run was on
0cae2dad5with a clean tree, TMPDIR under /mnt/storage/scratch/tmp, 22:01:27Z-22:01:52Z. -
The touched files, run whole: business lifecycle-settings 12/12, bus fire-revokes-grants 24/24, bus channel-proof 2/2, cli instances 15/15, cli transport 3/3, webui agents 9/9. That's 65/65 with 0 skipped, and no first-run red.
-
Mutants: 9 of 9 killed. Each was applied exactly once, and each made its named test fail as an assertion:
- M1 lifecycle: 3 fail (T1, T2, T3).
- M2: T1.
- M3a: T2 and T3.
- M3b fire-revokes-grants: T4.
- M3c instances: T5 CLI.
- M3d agents: T6.
- M3e: T4.
- M3f agents: T6.
- M3g transport: "busExit and refuseInsideAgent".
T4b passed under M3b and M3e. Every restore matched the HEAD blob by sha256, and the tree was clean after the run.
-
No full gate (sage-3730): Sage runs it on the replay at landing.