sessions and session_defaults are new and append-only. thread_claims, message_threads and
thread_opens are rebuilt in place with a session column, and thread_claims gains a generation
counted from claim rows. Each existing thread gets a default session with the thread's id; new
threads get a random one. Holder, per-run, release and delivery triggers key on the session,
and two id triggers keep session and thread ids apart (Duckworth checks 1-7, duckworth-0106).
The broker binds a thread run to a session (the default when the record names none), and
thread.open, thread.send and thread.messages take an optional session. New verbs session.list
and session.create (idempotent on op). Tests: sessions.test.mjs for T1 bus half, T2 and T7 with
a pinned 3k fixture; count, version and verb pins; hand-made test threads write the legacy
default session.
Built on row 236 (8f51900b5), which takes 3j-3k, so this step is 3k-3l by landing order
(row264 Q2, sage-2412). The op values are legacy-3l and migration-3l. gitea-migration.test reads
its store after both steps. Not run yet: every run waits for Sage's CLEAR.
B1: row 200's rollback to a Console without --sso kept the pointer's --send,
which F39 b forbids once --send can sit beside --sso. send_for_target drops it
whenever the run drops the pointer's --sso, and the plan and steps say so.
N1: an ok line with no activation refuses sso-off-flags instead of being
skipped.
activate --send turns send on only when the run's Console flags hold
--sso, given or carried (F39 b), and puts it after --sso and before the
tester flags. activate --no-sso turns --sso off with the Console flags
from the set of the activation that turned it on, drops --send (F39 a)
and keeps the pointer's tester. Both are usage errors under --units
external. T58 covers the brief's T1-T4; the external test covers T5.
Design and the set-walk choice: agents/darkwing/work/queue-273/BUILD.md.
Lands Launchpad's round 3 candidate, manifest 1bf831f3 (4c90099ea on launchpad/row238-r4, base
c515273db), approved by Webby (29956, rev 2717). Three paths: scripts/stack-release.sh, packages/cli/tests/
stack-release.test.mjs and docs/TOOLS.md. It is row 200's follow-up N1. Round 3 only merges docs/TOOLS.md
onto the row 240 landing: the landed blob plus the one hunk at :599. The 3-path patch-id, b9d0bb0ee3c7,
is the same in all three rounds, and stack-release.sh stays 100755.
Evidence: Launchpad's slot run on round 2, 2afbfbb01: stack-release.test.mjs 196/0 (sage-1794). Sage's
combined gate with row 228 on c515273db plus both rows' paths (07:11:30Z to 07:23:34Z): packages/seat 165 pass, 0 fail; packages/cli 640 pass, 0 fail; all ten scripts/test-*.sh 585 pass, 0 fail (out/, run.log). Row 238's mutant, the sso-config first step dropping --instance NAME, was killed (2 fails), survived: none, and stack-release.sh was restored to its blob.
`queue review verify-commit 238` matches the manifest on all 3 paths. Round 1's N1 stays on the #1744
carry list.
Lands Darkwing's round 1 candidate, manifest 464ce9eb177a (1a1dc80ca on darkwing's branch), approved by
Filbert (29922, rev 2659). Seven paths: scripts/stack-upgrade.sh (new), scripts/stack-release.sh,
docs/guides/host-run-sheet.md (new), docs/TOOLS.md, packages/cli/tests/stack-upgrade.test.mjs (new),
packages/cli/tests/stack-release.test.mjs and agents/darkwing/work/queue-233/BUILD.md.
Evidence: Darkwing's gate on d99166ade (base e65a3ab4d) 1201/0, mutants m1-m8 8 of 8 killed. The N1 fix
08db6ac3f (flock sleep leak in stack-upgrade.test.mjs) reran c0 20/0 with m1 killed (sage-1708); BUILD.md
doesn't record that rerun. Since e65a3ab4d no commit touches the seven paths. Rows 241 and 242 changed bus
and cli files. Only cli tests read the seven paths, so Sage ran the cli suite on 5465fd52d plus the seven
paths: 617 pass, 0 fail (out233/pkg-cli.log, 05:26:33Z to 05:35:00Z). A combined gate with row 234 failed bus schema.test.mjs:522 on 234's new tables, and
Gizmoduck found a second 234 failure (stack-release.test.mjs T46), so Sage stopped it. Row 234 round 3 fixes
both and rebuilds on this landing, since its T46 lines go in a file this commit changes.
Filbert's N1, N4 and N5 go to row 240 (brief b00d7d94d). N2 and N3 are in DEFERRED.md.
Blobs of Darkwing's round 2 da2da2f6c on origin/darkwing/row232-r2 (base
8fede5fc6), the 9 manifest paths, manifest 7af3c943. Round 2 is a
rebase-only round over row 230 (c8bc5cf4c): packages/cli/README.md is a
clean 3-way merge with 232's hunk unchanged, and the other 8 paths keep the
round 1 hashes (manifest 7a3b4ea1). Filbert approve round 1 (comment 29829)
and round 2 (comment 29865).
Under --units user every command line and every byte of output is the
base's. Under external the script reads root's system units, starts and
stops none, stages a copy behind AWAITING-UNITS, and confirm gates root's
start on the bus unit's ActiveEnterTimestamp and launches list.
Mutants 17 of 17 killed (BUILD.md). Darkwing's landing gate on da2da2f6c,
03:07:15Z to 03:17:48Z: packages/cli 593 pass 0 fail, 10 scripts/test-*.sh
suites 429 pass 0 fail (plus 156 node tests in the queue suite). Refactor
moved to 0444acd08 since, three commits touching no manifest path. No live
change: the stack runs from copy 4429a104f, and --units user stays the
default.
Squash of Launchpad's 30e4bb1a3..9df40878f (round 3 on row 190's landing
00d4cc1f6), the 3 manifest paths: scripts/stack-release.sh,
packages/cli/tests/stack-release.test.mjs, docs/TOOLS.md. Manifest
b3ead55c8b48 (Webby approve round 3, rev 2539, comment 29772).
Patch b2a8f5de18b7. Gate on 9df40878f: 15 package suites 2675 pass
0 fail, 10 scripts/test-*.sh suites 429 pass 0 fail (plus 156 node tests
in the queue suite). Mutants 13 of 14 killed, M12 the ruled equivalent
(sage-1446). Refactor moved to 722136772 since the base, touching none of
the 3 paths. No live change: the stack runs from copy 4429a104f until a
re-pin.
Squash of Darkwing's 176a6bf4b..df65a3d4c on origin/darkwing/row190-r3,
the 25 manifest paths, build-r3.patch efe898c2, manifest cc0ea9e8
(Filbert approve round 3, comment 29713). Design revision 3 in
agents/darkwing/work/queue-190/DESIGN.md. Gate on base d0bbc5c1c with
the patch: 15 package suites 2664 pass 0 fail, 10 scripts/test-*.sh
suites 429 pass 0 fail (plus 156 node tests in the queue suite).
Refactor moved to 99a406ed3 since, a window record touching no manifest
path. Mutants 20 of 20 killed (BUILD.md, round 3). No live change: the
stack runs from copy 4429a104f until a re-pin.
Row 178 gave the Console --proof-header NAME --proof-file PATH, but on the pinned stack only
activate --tester-port could write a new Console flag into the pointer. A hand install of the pair
would make every later activate, pm-on and rollback refuse unit-flags-drift. activate now takes
the pair, which replaces any pair in the pointer, and --no-proof, which drops it. The pair goes
after the preview flags and before --send and --tester-port, the order webui-service.sh render
writes and flags_drift compares. With neither flag, a target Console that takes --proof-header
carries the pair; a rollback to one that doesn't drops it and says so, and an activate to one
refuses proof-target unless --no-proof is given.
An activate may name the pointer's copy only when it sets a pair or drops one with --no-proof, so
Part B's B3 and its corrected rollback line B-R1 (activate <live copy> --pm --no-proof) can run;
every other activate of the live copy still refuses already-active. Before anything stops, the run
refuses proof-needs-preview, proof-header-invalid (proofOptions' own rules) and proof-file-invalid:
not absolute, a character the unit can't carry, a symlink, missing, not a regular file, inside the
repository or the copies, mode other than 600 or 400, or another owner. The file gets lstat, stat
and realpath only; the script never opens it and no line it writes holds the value. --plan prints
one proof: line. docs/TOOLS.md "Pinned stack update" lists the flags and describes the pair.
stack-release.test.mjs adds T53, eleven subtests. Each ends with a scan of every written file and
output for the proof file's sentinel and an inotify watch that counts no open or read of it. The
five brief mutants and Darkwing's two are killed. Filbert's M8 (a carried pair skips the checks)
and M9 (the repository case dropped) survive T53; both are follow-ups on P19, with a stale comment
in proof_for_target and the stale row 178 paragraph in docs/TOOLS.md.
This is step A0 of mos-0928 Part B and builds the tool only; nothing turns the proof on for the
live Console. Filbert approved round 1 on #1685.
activate --plan printed the backup line whenever the store file existed. But run_steps rolls back
with store-newer or migration-needs-flag before backup_store, so in those two cases the plan said
both that the run rolls back and that it backs up the store. The plan now prints
"backup: none; the run rolls back before the backup" there. The no-store line comes first, since
with no store file the run skips the whole store block. When S_CHANGED is set the plan keeps the
backup line: a wrong backup line costs nothing, a wrong none line hides the directory a --restore
needs. run_steps, backup_store, refusals, exit codes and flags are unchanged; the diff to
stack-release.sh is inside plan only. docs/TOOLS.md "Pinned stack update" step 3 names the
backup line. These are Filbert's N1 and N2 on row 185 (P19 404).
stack-release.test.mjs updates T26's not-given case and adds T52, six activate cases that plan,
check the one backup line and that nothing was written, then run and check the backup exists
exactly when the plan said so. The three brief mutants are killed, and so is Filbert's M7 (an
S_CHANGED read counted as early, by T29). Filbert's M6 (the early test ahead of the no-store test)
survives, since no test plans a missing store with an out-of-chain pointer schema; that is a
bounded follow-up with the store-unclean reading, on P19 404.
Filbert approved round 1 in #1684 comment 29419, with two non-blocking notes.
stack-release.sh activate now backs up the stopped store under its activation id on every run
with a store file, before any migration. Before this, backup_store ran only when the schemas
differed, so a same-schema activate took no backup and rollback --restore had nothing to name.
store-newer and migration-needs-flag still roll back before the backup, since those runs never
touch the store, and a missing store takes no backup.
BACKUP and TAKEN now mean two things. BACKUP is set by every backup, so the log line names it and
from_log backup finds it. TAKEN is set only right before a migration, so a gate failure after a
same-schema activate puts back the units and the wrapper and leaves the store as the failed run
left it. activate --plan prints a backup line, or "backup: none" with no store file. Rollback and
pm-on are unchanged; a same-schema rollback --restore already works through the row 181 guard.
docs/TOOLS.md "Pinned stack update" steps 5-7 say so.
stack-release.test.mjs updates T5, T11, T26 and T29 and adds T47-T51, with one fixture fault
(start-writes-row-<copy>). The three brief mutants are killed, and Filbert's two more (the backup
ahead of the schema checks, the plan line on a rollback) are killed by T6, T9 and T50.
This is the precondition for mos-0928 Part A, the re-pin from 89af38a83.
Filbert approved round 1 in #1683 comment 29404, with two non-blocking notes.
webui serve takes --proof-header NAME --proof-file PATH, only with the --public-host preview flags.
Before listen it reads the file once through O_NOFOLLOW and fstat: a regular file owned by the
Console uid, mode 0400 or 0600, at least 32 base64url bytes with one optional final LF. Any file
problem gives one fixed refusal that names neither the path nor the contents. The header name can't
collide with Host, Origin, auth or tester names.
On a public Host the guard runs before any auth header is read. It needs exactly one proof header
and compares equal-length buffers with timingSafeEqual. Missing, wrong and repeated proofs get the
same fixed 401. A proof header on another Host or on the tester listener gets 403. The existing
auth, Origin, Fetch Metadata and human write checks are unchanged.
webui-service.sh renders both flags and checks the file's metadata and canonical location without
opening it, and the send-status regex accepts the proof pair. stack-release.sh only gains a comment:
it already carries Console flags through re-pin and rollback, and new tests pin that.
This is a partial fix. Same-uid seats can still read the proof file. Activation (middleware, unit
flags, restart, smoke) is a separate Mos-checked window and isn't part of this commit.
Dewey approved round 2 in #1676 comment 29386, candidate 9300a670. Round 1 changes were in 29374.
COUNTED, next to STORE_JS, lists the 11 tables a bus writer fills without an event in the same
transaction (role_claims through usage_records, in schema order). STORE_JS count and heldcount
return events after STAMP plus each listed table after STAMP from the same open as before. A
listed table the store lacks counts 0, and one that exists but can't be read refuses
restore-mismatch.
rollback --restore refuses post-backup-events unless the total is 0 or --events equals it, and
the message names each table with rows. The in-run recount compares the same total. An
events-only store prints the old text. T46 reads every table in schema.sql and fails on a table
in no list or in two.
Filbert approved round 1 in #1679 comment 29372. Follow-ups F1 (task_comment_cursors) and F2
(the refusal text at stack-release.sh:909) are recorded in agents/darkwing/work/queue-181/BUILD.md.
next_activation() takes the UTC second once and tries the base id, then -1 through -9, against
stack-rollback/, backups/stack/ and the post-attempt store, all under the lock. ACT and POST share
the suffix, and with all ten taken it refuses capture-failed with nothing stopped. Adopt uses the
same helper. --restore takes the old and the suffixed form.
Under --plan, a bad --tester-port reports tester-port-invalid through check_refuse and
tester-port-target goes through check_refuse in both modes, so the plan lists every refusal.
Without --plan the output is the usage block, byte for byte as before. webui-service.sh counts the
tester port's digits as written before stripping zeros, so 007333 refuses as serve does.
Round 2 adds subtests C1 (adopt takes the next free id) and C2 (a bad port is usage before any
pointer read) for Dewey's round 1 (#1678 comment 29352). Dewey approved round 2 in comment 29361.
serve --tester-port N starts a second listener on 127.0.0.1:N in the same
process, with a bus caller through TESTER_CLI. N is 1024..65535, not
--port and never 7332, and the flag needs the bus configured. The tester
listener takes only instances.set, routes.set and name.set; settings
save, project-file save, assistant stop, bus message and chat writes
refuse 403 with no bus call. It refuses the main listener's auth header,
and every other listener refuses any request carrying x-mosaic-tester.
/api/config answers tester: true and the page shows a tester banner with
the routes, instances and seat name editors only. tester-off and
tester-required map to 403 in a serve.mjs coreBusStatus wrapper, so
bus.mjs stays pinned (row 164).
The broker lets a tester read credentials (with #secretCheck), routes,
project.files and project.file; project.file.save stays human-only.
webui-service.sh takes --tester-port with serve's checks. stack-release
activate --tester-port N writes the flag to the pointer and the unit,
refuses tester-port-target when the target copy's Console lacks it, and
activate or rollback to such a copy drops the flag and says so in --plan.
docs/TOOLS.md rewords the stale row 175 two-phase step (#1673 F1).
Built by Rocko on 473258a51 (manifest a4c728fc...52c9), approved by Dewey
in round 2 (#1674 comment 29322) after round 1 changes (29309), Sage's
gate green on 473258a51 plus the patch in run 2. Run 1 failed T34's
failed-pm_launched subtest with capture-failed: activate and pm-on name
the activation by the second, so the two ran in one second and collided
on the rollback set. That race is row 169's (the base fails the same way
with the id pinned), and a follow-up row fixes it (Vikunja P19 391).
activate ID BUSINESS --pm-after-harness installs the bus without --pm
and writes a pointer with pm false and pmPending true. It refuses
pm-after-harness-target when the target has no harness kind, and
pm-after-harness-set when the business file already sets plugins.harness.
pm-on BUSINESS refuses not-pending and harness-unset, then reinstalls the
bus with --pm on the pointer's copy and runs the full gate. A failure puts
the phase-1 unit back (pm-on-failed, exit 1), or stops both units
(pm-on-down, exit 2).
A plain activate refuses pm-pending. status prints the pending state and
the next step. activate and rollback refuse business-unreadable when the
target's code can't read the business file's plug-ins.
Built by Darkwing on 4b738e2da (patch-id 17a90c4ad795, manifest
c6aaac1f...54af), approved by Filbert (#1666 comment 29263), Sage's gate
green on e7d47c346 plus the patch.
drop_in_override refuses drop-in-override when a unit path, a drop-in
directory or a .conf exists but can't be read, as install_guard in
bus-service.sh would after the stop. A unit path that isn't absolute
refuses too.
plan_store and the rollback --restore event count check is-active again
after reading the store the running bus holds. If the bus stopped
meanwhile, --plan prints "store schema: unknown" with no migration lines,
and rollback refuses restore-mismatch before anything stops.
The gate's copy-check step compares the check's bus line with the copy
this run installed and names both when they differ.
read_units returns 1 when the bus Environment= line or either unit's
ExecStart argv holds a quote or a backslash, so activate and rollback
refuse unit-flags-drift, adopt refuses check-failed and status prints
that it cannot read the units. Nothing parses systemd quoting.
Tests T28 to T31 in packages/cli/tests/stack-release.test.mjs.
New scripts/stack-release.sh with four verbs. adopt records the running
copy, the unit flags and the store schema in state/stack-active.json; it
stops nothing. activate ID BUSINESS [--pm] [--migrate] stops the units,
checks the stopped store, backs it up, migrates when the target's schema
differs, installs and starts the target, then gates on both units active,
a live bus host whose pid is the bus unit's MainPID with boot ok, Console
/healthz, stack-copy.sh check and one launched journal line. A failed
gate rolls back inside the same run and keeps the attempted store as
bus.post-attempt-<stamp>. rollback [--restore ACT [--events N]] goes back
to the log's previous copy and counts the events written after the
backup. status prints the pointer, the log tail and the copy check.
--plan prints what a run would do and writes nothing; it reads the store
schema from the store the running bus holds, or from a stopped store.
Exit codes: refusal 3, gate failure with rollback 1, rollback failed 2,
usage 4. Nothing is pruned.
stack-copy.sh gets a reachable function split from copy_commit and a
guard so its helpers can be sourced. TOOLS.md "Pinned stack update" and
the cli README pinned-stack paragraph describe the verbs.
Owner Darkwing, brief docs/plans/2026-10-18_stack-release.md (D1).
Reviewer Filbert: r1 approve (comment 28997) on 091693c5, r2 confirm-only
approve (29004) on candidate manifest c213a801...bd15c after row 125
moved the base under TOOLS.md and the cli README. Sage gate on 23622980f:
25 of 25 steps rc=0 on c96a95320 (15 packages + 10 scripts), node pass 2353 fail 0, selftests 422 passed 0 failed, 2026-10-18T20:32:13Z to 20:38:55Z, network sage-r162_default removed, 0 containers.
Nothing live. The 8e7ea89a1 window (repin-8e7ea89a1.md) runs these verbs
on Mos's go with Jason present.
Co-Authored-By: Claude Opus 5.5 <[email protected]>