Compare commits

...
Author SHA1 Message Date
jason.woltje cac7d0f67a row 273: T53 test 6 pins the usage line with --no-sso
The usage line gained --no-sso in the proof and sso bracket. T53 test 6
still matched the old bracket and failed the whole run on 978143bf4.
2026-10-21 21:32:11 -05:00
jason.woltje 978143bf42 row 273 notes: name the changed T57 item 3 assertion for review (sage-2321) 2026-10-21 20:34:59 -05:00
jason.woltje 6273b65e9d stack-release: a rollback that drops --sso drops --send; the --no-sso walk refuses an ok line with no activation (row 273, sage-2315)
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.
2026-10-21 20:34:11 -05:00
jason.woltje 5b5fefc8bc stack-release tests: T58 adds sage-2305 cases a and b (row 273)
(a) --sso on, then two re-pins that carry it, then --no-sso gives back the
arrival's ExecStart byte for byte. (b) every set in the log holds --sso, so
--no-sso refuses sso-off-flags before any unit stops.
2026-10-21 20:24:57 -05:00
jason.woltje 3730567146 stack-release: --no-sso drops --send in one place, send_for_target (row 273) 2026-10-21 20:20:41 -05:00
jason.woltje 7dc538d90a stack-release: activate --send and --no-sso (row 273, #1778)
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.
2026-10-21 20:19:48 -05:00
jason.woltje 0042f713dc queue: revs 3092-3102 (megavolt: row 236 claim; sage: row 273 activate --send/--no-sso added and briefed (#1778, mos-1246), row 274 FED R3 added and briefed (#1779), row 275 193b1b added and briefed (#1780), row 205 re-pinned to 193b1a with its piece, row 206 re-pinned and after 275) 2026-10-21 20:06:55 -05:00
jason.woltje f0d0b31e9b docs: briefs for the activate --send/--no-sso row, FED R3 and the 193b1 split; phase 2 plan F39 fixes
- docs/plans/2026-10-22_activate-send-nosso.md: the row Mos approved in mos-1246. Darkwing builds,
  Filbert reviews. --no-sso drops --send, --send refuses without --sso (F39), test-release.sh cases.
- docs/plans/2026-10-22_federation-r3.md: FED R3, the broker user principal, on 193b1a's candidate.
  Owner Morgana, reviewer Dewey. Its parents R1 and row 196 have landed, and the filing was owed.
- human-users-build.md: 193b1 split into 193b1a (message filter, chained on 196) and 193b1b (viewer
  verbs and board refusals, after FED R3 and R4). Mutant 3 moves to 193b2 (sage-2288, morgana-0063).
- sso-phase2-window.md draft 2: the tool gap paragraph names the approved row, and rollback step 1
  drops "--send follows the pointer", since --no-sso drops --send (mos-1246 items 2 and 3).
2026-10-21 19:42:01 -05:00
jason.woltje 92be0db025 docs: Q175 phase 2 window plan, draft 2 after mos-1245 (#385, Vikunja 412)
R1 same-copy rollback, 7247d80c7 named, placeholder stop, step 10 aimed at a
tester-owned thread, agent config writes. Names the tool gap: activate has no
--send and no --no-sso, and --expect-from is external-units only.
2026-10-21 19:33:57 -05:00
jason.woltje 02da6a2ae4 queue: revs 3088-3091 (sage: rows 205 and 236 brief re-pins to HEAD and briefed) 2026-10-21 19:30:48 -05:00
jason.woltje 2dd9c80c1c docs: Q175 phase 2 window plan, draft 1 for Mos (#385, Vikunja 412) 2026-10-21 19:21:29 -05:00
jason.woltje 137ddd0079 docs: BUILD-LOG entry for the row 254 landing and a Sage SESSIONS line (#1759) 2026-10-21 19:18:12 -05:00
jason.woltje e3c0446f55 queue: revs 3073-3087 (gizmoduck: row 264 claim, row 268 r2 back to in-progress and in-review on ecd9b35eb, request refused without a token; sage: 268 r2 review request as jarvis, rows 269-272 for 264b-264e added and briefed with issues #1774-#1777; webby: row 268 r2 APPROVE carried on ecd9b35eb) 2026-10-21 19:17:00 -05:00
jason.woltje dcd8d4ddaa queue: revs 3071-3072 (sage: row 254 landing note and done after 331597709 and eb9672c91, #1759 closed) 2026-10-21 18:58:46 -05:00
jason.woltje eb9672c910 row 254: README request-timeout sentence and a DEFERRED entry after the landing (#1759)
Doc-only commit after the row 254 landing. The library README now says
that a sync outlasting the server's 120-second hold gets request-timeout
and still finishes (Dewey N1, #1759 comment 30173; server.mjs sets the
120 s socket timeout and the broker promise runs on). DEFERRED records
Dewey N2: library-mapping-invalid, library-remote-invalid,
library-state-invalid and library-git still map to 502 until the Console
library routes row decides them. No test reads either file's content.
2026-10-21 18:55:26 -05:00
jason.woltje 3315977095 row 254: library.sync 130 s human client timeout, six library-* STATUS entries, channel-proof re-pin, README (#1759)
Candidate megavolt/row254 b31300d79 on c3d88840c, manifest be63cfbe
(7 paths). Dewey APPROVE r1, rev 3006 (#1759 c30173). Gate on Sage's
slot by Megavolt, 23:37:27-23:40:52Z on b31300d79: library 77/77, bus
628/628, webui 305 pass with 1 skip (editor rebuild needs esbuild
0.28.2), every scripts/test-*.sh rc 0; the human-cli timeout and
library-fetch-failed STATUS mutants killed by their behaviour tests.
Landed onto 41b0cf755 with the candidate's blobs and modes for the 7
paths; c3d88840c..41b0cf755 touches none of them. Merged-tree rerun of
library, bus, webui and cli on Sage's slot before the push.
2026-10-21 18:44:22 -05:00
jason.woltje 41b0cf7551 queue: revs 3060-3070 (gizmoduck: row 268 claim and in-review, review request refused without a token; sage: 268 review request as jarvis, notes on 216 and 220 pointing at RULINGS.md da9555ca2, row 264 to gizmoduck with reviewer duckworth and briefed; webby: row 268 r1 APPROVE on 9d9ee12c3) 2026-10-21 18:35:57 -05:00
jason.woltje 5f5b51dd65 sage: rulings for row 264 (T1 item 7 sessions) on Gizmoduck's phase A questions Q1-Q7
Five slices 264a-264e, all MVP; schema label by landing order across rows 236, 264a and 206; PRD 1.13
already landed; row 194 is the stop owner for 264d; one admission hook for row 191; Pi count exact with
Claude unknown until Vikunja 466; legacy single human until row 193 (gizmoduck-0154, #1769, Vikunja 463).
2026-10-21 18:34:12 -05:00
21 changed files with 2592 additions and 83 deletions
+2
View File
@@ -5219,3 +5219,5 @@ Before: managed project/thread verbs had no Console conversation area. After: fi
2026-10-21T22:28:56Z | Huey | T1 sessions design r2, scrooge-0006, mos-1235/1236, Vikunja 463 | Before: r1 347278044 lacked the exact per-session checkout/single-writer invariant and safe runner drain ordering, and admitted other role instances. After: F1 exact workspace path, fenced checkout setup, legacy shared-workspace quiescence, clear retains old dirty/untracked/unpushed checkout and creates fresh approved checkout, T2 mutant; F2 dequeue fence and generation-bound turn-boundary idle proof before runner signal, busy drain stays clearing, T4 cases; F3 THREAD_DEFINITION only per Mos scope ruling, no role eligibility change; N1 private titles/history vs owner-visible count/activity projection; N2 Claude offline adapter/managed-store-reader dependency flagged as MVP risk. Section 8 records Mos’s Q1–Q3 readings, no questions to Jason. Verification: 27 pinned references resolve, summary within 15 lines, source/delta consistency review, whitespace and installed queue guard checked. No code, queue/PRD edits, builds/suites/live changes/provider calls/credential reads.
2026-10-21T22:30:20Z | Huey | T1 sessions design r3, scrooge-0007 R1, Vikunja 463 | Before: r2 e19e30df2 nested new session checkouts under the retained legacy instance workspace, allowing project-name collision or historical parent inspection to reach live siblings. After: exact path is dataRoot/session-workspaces/business/instance/session, outside the legacy root; canonicalized ancestor/descendant overlap refuses; T2 nesting mutant covers historical mount/inspection and a legacy project named sessions. All other r2 content retained as approved, revision label updated. Verification: old nested path absent, manual R1-only delta, whitespace and installed queue guard checked; no code/builds/suites/live changes/provider calls/credential reads.
2026-10-22T00:18:12Z | Sage (Opus 5.5) | row 254 landing (#1759), library.sync human timeout | Before: the human transport gave library.sync the 5-second default, so a sync longer than that reported outcome-unknown, and six library-* codes reached the Console as 502. After: 331597709 lands Megavolt's candidate (7 paths, tree d49e4626f, manifest digest be63cfbe recomputed and matched). human-cli.mjs waits 130 s for library.sync, past the server's 120 s hold, and 5 s for other verbs. webui bus.mjs maps library-sync-busy, library-diverged and library-accept-stale to 409, library-accept-invalid to 400, and library-credential-missing and library-fetch-failed to 503. channel-proof re-pinned. Merged-tree gate on the landing commit: library 77/0, bus 628/0, webui 305/0 (1 skip), cli 749/0 (23:44:33-23:55:11Z). eb9672c91 adds the README request-timeout sentence (Dewey N1) and a DEFERRED entry for the four library codes still at 502 (Dewey N2, binds the Console library routes row). Overlap sweep before push: 268 rebased to r2 ecd9b35eb (README only, approval carried webby-0096), 194 rebased to b7. Done rev 3072, #1759 closed.
+113
View File
@@ -0,0 +1,113 @@
# Row 273 (#1778): activate --send and --no-sso
Darkwing, 2026-10-22. Brief: `docs/plans/2026-10-22_activate-send-nosso.md` (blob in f0d0b31e9), approved by
Mos in mos-1246 (F39). Base: origin/refactor 0042f713d. Reviewer: Filbert. Gate owner: Sage.
## What changed
Only `scripts/stack-release.sh` and its two node test files.
- `activate --send` puts `--send` into the run's Console flags after `--sso` and before the tester
flags, where webui-service.sh renders it. It refuses `send-needs-sso` unless those flags hold `--sso`,
given or carried. The plan line is `send: on; --send was given`, or `send: carried` when the pointer
already holds it. `check_target` lets it name the pointer's copy only when the pointer holds no `--send`,
so a second `--send` on the same copy refuses `already-active`.
- `activate --no-sso` refuses `sso-not-on` when the pointer holds no `--sso`. Otherwise it takes the
Console flags from a rollback set (below) and keeps the pointer's tester flags. `send_for_target`
drops `--send`, whether it came from the set or the pointer. It is the only place that does, so one
mutant there (M3) shows the drop is tested. The plan prints `sso: off; Console flags from <set>/<file>; restores <flags>`
and a send line, `send: dropped; --no-sso drops --send (F39)` or `send: off; --no-sso was given and the
pointer holds no --send`. The steps log gets `drop --send; --no-sso was given` and
`turn off --sso; the Console flags come from <set>/<file>`.
- Usage errors: `--send` with `--no-send`; `--no-sso` with `--sso`, `--send`, a proof pair or
`--no-proof`; either flag on any verb but activate; either flag under `--units external`.
`--no-sso --no-send` is allowed, since the brief doesn't forbid it and both drop the same flag.
- The set read that `sso_base` did inline for a rollback to a Console without `--sso` moved into
`set_flags`, which both paths call. Its three messages are unchanged, and T57 still pins them.
- `from_log` has a new mode, `sets`: the activation of each ok activate and rollback line, newest first,
stopping at the adopt line, which took no rollback set.
## The consequential choice: which set --no-sso reads
The brief says the flags come from "the rollback set of the activation that turned `--sso` on", and that
"a set whose Console already ran with `--sso`" refuses. Each set holds the Console unit from before its
run. So the activation that turned `--sso` on is the newest one whose set holds a Console without
`--sso`: every activation after it ran with `--sso`, and so did every set it left.
`no_sso_base` walks the log's sets newest first and stops at the first set whose Console has no `--sso`.
It refuses `sso-off-flags` at the first set it can't read (missing, failing its SHA256SUMS, or with no
argv the script can parse), since it can't tell whether that set was the one. If every set ran with
`--sso`, it refuses with `the Console in <set>/<file> already ran with --sso, and the log names no
earlier activation`. A log with no set at all refuses with `the log names no activation with a rollback
set`.
The tradeoff: a later same-copy re-pin that carried `--sso` (a `--no-send`, or a re-pin onto a newer
copy) doesn't break `--no-sso`, which the phase 2 window needs if a re-pin lands between step 5 and a
rollback. The cost is that "a set that ran with `--sso` refuses" now applies only when no older set
without `--sso` exists. A tampered set on the walk still refuses, even one newer than the set the walk
would have used. Filbert, please rule on this reading.
## Filbert's early look: B1 and N1 (sage-2315)
Filbert accepted the walk. Sage ruled both findings into this row.
- B1: row 200's rollback to a copy whose Console has no `--sso` took the flags from the set and kept
the pointer's `--send`. From an `--sso --send` pointer, which this row's `--send` can now make, that
gave a Console with `--send` and no `--sso`, which F39 b forbids. `send_for_target` now drops `--send`
whenever the run drops the pointer's `--sso`, so it is still the one place that drops it. The plan
line reads `send: dropped; the run drops --sso, and --send needs it (F39)`, and the steps log gets
`drop --send; the run drops --sso`. Row 200's sso line now names only the tester as following the
pointer.
- Changed existing assertion, for the review to approve by name: T57 item 3 (sage-1389), "the
plain case", ends with `assert.deepEqual(pointer(g).console,[...noSend(gbase),...pair(f)])`. It
used to expect `'--send'` at the end. The case seeds a legacy `--send` pointer, turns `--sso` on
(which carries `--send`), then rolls back to B, whose Console has no `--sso`. A carried `--send`
that ends without `--sso` breaks F39 b whatever its source, so the old pin asserted the wrong
outcome (Sage accepted this in sage-2321). Row 200's two sso plan strings also lose "--send and"
(`ROLLBACK_PLAN` and the round 3 named-instance case), since `--send` no longer follows the pointer
on that path.
- N1: the walk skipped an ok line with no activation. Skipping it could land on an older set that
isn't the right one, so it now refuses `sso-off-flags` with `an ok line in the log names no
activation`. The walk reads the log through `mapfile`. That gives no entry for an empty log, so a
log with no set still gets its own message.
## test-release.sh
The window plan says test-release.sh "gets cases for both". The brief lists it only as a file to run
whole. test-release.sh is release.sh's Docker selftest and doesn't call stack-release.sh, so the cases
live in the node tests and test-release.sh runs unchanged.
## Tests (T58 in stack-release.test.mjs)
- T1: `--sso --send` from B onto C1 with a tester port: plan, pointer, unit and steps. `--send` beside a
carried `--sso` re-pins C1 once; a second `--send` refuses `already-active`; C carries it.
- T2: `--send` without `--sso` refuses on another copy and on the pointer's copy, also when the pointer
carries `--send` alone; `--plan` lists the refusal.
- T2 (sage-2315 B1): `--sso --send` onto C1, then a rollback to B, whose Console has no `--sso`. The
pointer and the unit end with neither flag, and the plan and steps name the drop.
- T2 and T4 usage: every forbidden pairing and verb, checked against the full usage text.
- T3: from an `--sso --send` pointer, `--no-sso` on the same copy gives back the pre-`--sso` ExecStart
line byte for byte, without `--send`. A second case walks past a `--no-send` re-pin that ran with
`--sso` and keeps the pointer's tester port (7334), which differs from the set's (7333). A third
(sage-2305 a) turns `--sso` on, then re-pins twice with `--send` and `--no-send`, so the two newest
sets both hold `--sso`. `--no-sso` then gives back the arrival's ExecStart line byte for byte.
- T4 (sage-2305 b): two sets, both edited or written to hold `--sso` with sums intact, refuse
`sso-off-flags` through `refused()`.
- T4: `sso-not-on`; an ok line with no activation; a set that fails its sums, ran with `--sso`, has no
argv or is missing; a log with no set. Each runs through `refused()`, so nothing stops and nothing else changes.
- T5: stack-release-external.test.mjs adds `--send` and `--no-sso` to the row 232 usage list.
- T55 test 5 and the T57 usage test match the new usage line.
## Mutants
| # | Change | Expected killer |
|---|---|---|
| M1 | `set_flags` skips the SHA256SUMS comparison when `--no-sso` is given | T4 tampered set |
| M2 | `no_sso_base` skips the `sso-not-on` refusal | T4 `sso-not-on` |
| M3 | the `--no-sso` branch of `send_for_target` keeps `--send` | T3 |
| M4 | `--send` skips the `send-needs-sso` refusal | T2 |
| M5 | parse_opts drops the `--send`/`--no-send` rule | T2 and T4 usage |
| M6 | a rollback to a Console without `--sso` keeps `--send` (B1) | T2 (sage-2315 B1) |
| M7 | the walk skips an ok line with no activation (N1) | T4 no-activation line |
Runs and results go in the round's notes once Sage clears the slot.
+74
View File
@@ -0,0 +1,74 @@
# Row 254 build: library sync follow-up (C2-03a deferrals)
Row 254, #1759, reviewer Dewey, gate owner Sage. Brief: docs/plans/2026-10-21_library-sync-followup.md
(0de97f429), for the deferrals Dewey named in the row 222 approval (#1723 comment 30138). Built on
origin/refactor c3d88840c, which carries row 222 (0b33f5e9d) and row 196 (7247d80c7). Launch: sage-2163.
## What it does
1. **Client timeout.** `packages/bus/src/human-cli.mjs` gives `library.sync` a 130 s client timeout. Every
other verb keeps Client's 5 s default (`timeout: undefined` takes the default). The server holds a library
verb's socket for 120 s (`packages/bus/src/server.mjs`, the `socket.setTimeout(120_000)` branch), and the
fetch alone may take 100 s (GIT_LIMITS). So the client waits past the server and gets the server's own
answer, including its `request-timeout`, instead of giving up first.
- The Console has no library route, so there is no Console timeout to change (brief item 1, "when one
exists"). When a route is added, note that webui `humanCall` kills its child at 30 s by default. The
route would need `timeoutMs` above 130 s for library.sync.
- `tester-cli.mjs` is unchanged: library.sync is human-only, so the tester gets `human-required`.
2. **STATUS codes (215 N3).** `packages/webui/src/bus.mjs` maps six codes:
| Code | Status | Why |
|---|---|---|
| library-sync-busy | 409 | a sync is running; read status again |
| library-diverged | 409 | held for a human accept |
| library-accept-stale | 409 | the tip moved; read status again |
| library-accept-invalid | 400 | a malformed accept |
| library-credential-missing | 503 | the remote can't be reached, like no bus to ask |
| library-fetch-failed | 503 | the same |
Neither 503 is 502, the default for an unknown code, so dropping any one entry changes its status and the
test catches it.
3. **README (222 N4).** `packages/library/README.md` Sync section: the transport timeouts, that a client
timeout leaves the sync running, that a second call gets `library-sync-busy`, and that `library.status`
shows the result. It also says the Console has no library route yet.
- Correction to row 222's BUILD.md, "What it does": "`webui/src/cli.mjs`: the Console gets the call" was
not true. `cli.mjs` builds `librarySync` and passes it to `startServer`, and `serve.mjs` never reads it
(checked on c3d88840c: no library reference in serve.mjs).
## Channel proof re-pin
`packages/bus/tests/channel-proof.test.mjs` pins both changed files by hash. The test change is two baseline
values and a header comment, nothing else:
| File | Old | New |
|---|---|---|
| packages/bus/src/human-cli.mjs | 269c3a66008c0d57… | 9c9af66b98ea0a490c9a880fc81c3c62414a8ee0b5ba6478608771b8c4f87932 |
| packages/webui/src/bus.mjs | 934ab2f9b6bde72a… | e69d17d4385a4d190d15373ed1a26e270a5c6906eec9e61fcc2bb6b05849cabb |
human.mjs and transport.mjs are unchanged, and so are the entry-path test and the plug-in import ban. No
other test pins either file: `git grep` for both old hashes finds only queue.json history and old packets,
and the one 64-hex string in webui service.test.mjs is a made-up UID.
## Dewey's other 222 notes
None is a few lines in a file this row changes, so all three stay deferred:
- N1: a close waits on every entry left in a pass (runtime.mjs).
- N2: a held tip still gets its tree read, and a refused read hides the divergence (sync.mjs).
- N3: held states log every tick (runtime.mjs).
## Tests
- `packages/webui/tests/library-status.test.mjs` (new): each of the six codes maps to its status, and an
unknown code is 502.
- `packages/bus/tests/library-sync.test.mjs`, a new test. A stand-in bus socket answers every request after 6 s,
and the real human-cli.mjs runs two requests in parallel. library.sync gets the answer, and library.status
gets `outcome-unknown` at 5 s. It takes about 6 s.
- `packages/bus/tests/channel-proof.test.mjs`: the new baselines.
Planned mutants on Sage's slot:
1. Drop the human-cli timeout line: the library-sync test fails (sync gets outcome-unknown) and the
channel-proof hash fails.
2. Drop one STATUS entry (`library-fetch-failed`): library-status test fails, and so does the channel-proof hash.
Both files are hash-pinned, so channel-proof kills any edit to them. Each mutant is also expected to be killed
by its behaviour test, and the run lists every failing test so that can be checked.
+83
View File
@@ -0,0 +1,83 @@
# Row 264 (T1 item 7 sessions, #1769): Sage's rulings on the phase A questions
Sage, 2026-10-21T23:34Z. These answer Q1-Q7 in `agents/gizmoduck/work/queue-264/BUILD.md` on branch
`gizmoduck/row264` (8652158d3, on da9555ca2), sent as gizmoduck-0154. The brief is
`docs/plans/2026-10-21_t1-sessions.md` at blob ff0be0c79 (design r3, Mos's rulings mos-1235 in section 8).
None of these rulings changes what a requirement allows. They set order, labels and integration points the
brief leaves open.
## Q1: slices
Approved: five candidates, built in the order 264a, 264b, 264c, 264d, 264e. Each gets its own manifest,
its own Duckworth round and its own landing. Row 264 carries 264a. I add one row each for 264b-264e,
with a Gitea issue, in the next slot gap. Their `after` lists:
- 264b after 264a.
- 264c after 264b. It reads the per-session store, so it can't land before the store exists.
- 264d after 264b and row 194 (see Q4).
- 264e after 264a. Its clear and count parts switch on as 264c and 264d land. A control whose verb
hasn't landed refuses with a reason; it is never hidden as working.
All five are MVP. TARGET.md lines 57, 104 and 105 are the three things Jason asked for, and the slices
map onto them: 264a and 264b for line 57, 264c for line 104, 264d for line 105, 264e for all three as the
user sees them. None of them is cut by this ruling. Cutting one from 10-27 would change what the MVP
delivers, and that goes to Mos (mos-1202). I report the schedule risk on the standup, not as a question.
When 264a is approved I'll check whether a second seat can take 264c in parallel under Q177 routing.
## Q2: schema label
The label goes by landing order, not by row. Row 236 (#436b), row 264a and row 206 (193b2) all add a
bus migration on schema 3j. The first of the three to land takes 3j-3k. The next one rebases and takes
the next label, and so on. This replaces the line in the #436b brief
(`docs/plans/2026-10-20_agent-credentials.md`, "This row owns `SCHEMA_VERSION` 3k") and the 3k-3l
label in row 206's piece; both rows hear it from me directly.
Build 264a as `3j-3k.sql` now. A rebase onto another row's migration is a product-blob change, so the
carried gate is void (mos-1200): rerun the whole bus suite, schema.test.mjs with its step-count pins, and
the T46 restore-guard table lists. Before any re-pin that crosses the new label, a verified backup and a
`--migrate` step in the window plan (mos-1145), and the rollback refusal D9 asks for.
## Q3: PRD
Nothing to wait for. PRD 1.13 has landed on refactor with both replacements:
`docs/prd/mosaic-stack.md` REQ-ROLE-2 (assistant sessions, one holder each, the claim names organization,
thread, instance and session) and REQ-THREAD-2 (one or more independent sessions, saved default session).
264a builds to that text. If the code needs wording the PRD doesn't have, that goes to Ludwig through me.
## Q4: stop proof
Row 194's lifecycle path is the stop owner. The brief names it (F12 and D7 step 2, "the reviewed
lifecycle path"). Build 264d against `honker/row194-b5` at dfabbbe8a as a read-only reference, and rebase
onto 194's landing. 194 lands after its P3 check in the 10-24 window (mos-1161), so 264d lands after that.
Don't copy 194 code into 264d's manifest.
The runner's dequeue fence and generation-bound idle acknowledgement are new code in
`packages/harness/src/runner.mjs`, and they belong to 264d. Approved as a named review item. The existing
TERM-then-KILL handler stays as it is for every other caller. D7 forbids calling it while a child runs.
## Q5: kill checks
Approved, as D8 already says. Use today's launcher limit and revocation path. Add one shared admission
hook that every session start goes through. Row 191 (Rocko, in progress) plugs its kill checks into that
hook when it lands; whichever of 191 and 264b lands second does the wiring and runs both rows' kill tests.
No setting may turn the hook off, and a configured but unavailable check fails closed.
## Q6: Claude count
264c can land with Pi exact and Claude showing `unknown` with the Vikunja 466 reason. T3 passes for Pi
in 264c. The Claude half of T3 stays open, recorded on row 264 and on Vikunja 466 as a build dependency,
and T1 item 7 is not called done until it passes (D6, MVP risk N2). No count is inferred from prose,
token drops or errors, and no zero is shown without a validated scan.
## Q7: RBAC
Approved, as D4 says. Until row 193 lands, the legacy single human uses their own threads only, and
collaborator and share paths refuse. T5 in this row covers that case, forged identity and cross-instance
IDs. The A4 `session.activity` projection and the full owner, collaborator, viewer and share matrix wait
for 193. The row that lands second wires them and runs the full T5.
## Process
- Claim the row in the first slot gap. Tests and queue writes run only on my CLEAR.
- One branch per round. A rebase goes on a new branch name (mos-1086).
- Design notes stay in `agents/gizmoduck/work/queue-264/BUILD.md`.
+1
View File
@@ -1455,3 +1455,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-21T22:30:20Z | Huey (gpt-6.1-sol) | T1 item 7 r3, scrooge-0007 R1, Vikunja 463 | isolated session-workspaces root and nesting mutant recorded; R1-only confirmation requested, other r2 findings closed; doc-only checks
2026-10-21 | Ludwig | PRD 1.11-1.14 drafts (Vikunja 459, 462, 463, 464) | approved by Della, handed to Sage
2026-10-21T22:51:07Z | Sage (Opus 5.5) | PRD 1.13 and 1.14 landing with Gosalyn's C2-01c brief and Case 2 design (51fdfb7e2), rows 264-268 filed (#1769-#1773), Case 2 re-pin to a6ac627c8, Vikunja 463 closed | pushed 6ba8a582e..db1e7732a; requirements tests 3/3 and 3/3 and verbs 31/31 on 5f73b388c and db1e7732a
2026-10-22T00:18:12Z | Sage (Opus 5.5) | row 254 landing, rows 269-272 (264b-264e, #1774-#1777), 268 r2 request, row 191 publish and gate slot | 331597709 and eb9672c91 landed, queue e3c0446f5 pushed; rocko/row191-stoprun-dcd8-20261022 7aa18b0f6 and 81889b58d published as jarvis; 191 full gate on Rocko's slot from 00:17Z
+26 -7
View File
@@ -12,7 +12,7 @@ Single test files run freely, and suites and mutant batches need Sage's slot.
Boundaries for every row: no change to `~/.mosaic-dev`, `~/.config/mosaic-dev`, the live units, slot files, any
live access.json or `~/.mosaic`. No request to the live Console, mosaic.woltje.com or auth.diversecanvas.com.
No user thread goes live before 193b1's message filter has landed (mos-1021 B2, FED R8).
No user thread goes live before 193b1a's message filter has landed (mos-1021 B2, FED R8).
The owner-visible records are fixed by Mos (mos-1023 item 2) and named in one user sentence, which FED R6
reuses word for word: "The business owner sees your thread's agent's decisions, inbox entries, claims, tasks
@@ -34,20 +34,39 @@ gate, and the mutants are killed.
## Brief: 193b1, viewer verbs, board refusals and the message filter
Chained on FED R3, FED R4 and row 196's verifier. The bus gets the viewer verb list, per-grant level
checks, board reads refused to users, and the section 5.7 `trail` and `replies` filter with `#readable`
owner-only. No schema change. The governance pin test names each item in the user sentence above. Bus tests
and mutants 1, 3, 6, 10, 11 and 12 from section 8.
Split on 2026-10-22 (sage-2288, after morgana-0063) into 193b1a and 193b1b below. The text that stood here
chained the whole row on FED R3 and R4, which weren't filed. Only the viewer half needs them.
## Brief: 193b1a, the message filter
Chained on row 196's verifier (done). The bus gets the section 5.7 `trail` and `replies` filter over owner,
reader and tester sessions, with `#readable` owner-only. No schema change. A hidden `trail` subject returns
`[]`, exactly as an unknown id does, and `replies` returns `message-not-found` for both. One test per verb
compares the hidden response with the unknown-id response byte for byte. The owner-side governance pin test
names each item in the user sentence above. Bus tests and mutants 11 and 12 from section 8, plus mutant 1
as "`#readable` returns true" (the pin test kills it) and a mutant that makes a hidden subject
distinguishable from an unknown id. FED R3 builds on this row's candidate and extends the filter to user
sessions.
Gate: Duckworth approves the manifest. The bus suite and every `scripts/test-*.sh` are green on Sage's gate,
and the mutants are killed.
## Brief: 193b1b, viewer verbs and board refusals
Chained on 193b1a, FED R3 and FED R4. The bus gets the viewer verb list, per-grant `covers` level checks,
board reads refused to users, and the user-side governance pin test. Bus tests and mutants 6 and 10 from
section 8.
Gate: Duckworth approves the manifest. The bus suite and every `scripts/test-*.sh` are green on Sage's gate,
and the mutants are killed.
## Brief: 193b2, thread shares (schema 3l)
Chained on 193b1. `thread_shares`, the share verbs, the share rules of `#readable` and `#threadRead`, and the
Chained on 193b1a and 193b1b. `thread_shares`, the share verbs, the share rules of `#readable` and `#threadRead`, and the
section 5.5 share filters. Schema 3k to 3l (Sage, 2026-10-20: machine grant row a owns 3i to 3j and
the #436b build owns 3j to 3k, so this row builds on #436b's landing). The table is append-only, and row 181's restore guard and
`COUNTED` include it. Bus tests for shares and the migration, and mutants 2, 4, 7, 8 and 9 from section 8.
`COUNTED` include it. Bus tests for shares and the migration, and mutants 2, 4, 7, 8 and 9 from section 8,
plus mutant 3 (rule 3 dropped), moved here from 193b1 because only shares can kill it (sage-2288).
Going live needs a `--migrate` window with Jason. Sage takes it to Mos when this row is near.
Gate: Duckworth approves the manifest. The bus suite, the cli suite and every `scripts/test-*.sh` are green on
@@ -0,0 +1,54 @@
# Row: stack-release.sh activate --send and --no-sso, for the Q175 phase 2 window
Sage, 2026-10-22, for Mos's go mos-1246 (re sage-2285). Gitea #385, Vikunja #412. The window plan is
`docs/plans/2026-10-22_sso-phase2-window.md` (draft 2, 92be0db02).
## Brief: activate --send and --no-sso
Owner Darkwing. Reviewer Filbert (not Sage or Darkwing, mos-1246 item 1). Gate owner Sage. Base: origin/refactor
at this brief's commit. It lands and is re-pinned before the phase 2 window. That re-pin is Q173 work on Mos's
go, run by Jason or Mos. If the target copy's schema differs from 3j and needs `--migrate`, it waits for Jason.
Why: at 02da6a2ae, `scripts/stack-release.sh activate` has `--no-send` but no `--send`, and nothing turns
`--sso` off. Without `--sso`, `sso_base` carries it from the pointer, and `check_target` refuses the
pointer's copy as `already-active`. So the window can't turn send on with `--sso` (mos-1195), and its
same-copy rollback (mos-1245 R1) can't run.
Send and forward auth (mos-1246 F39): send went off on 10-20 (mos-0972) because, under forward auth, any
same-uid process can send as Jason through 127.0.0.1:7332. So send goes only with `--sso`.
What to build, in `scripts/stack-release.sh` only:
1. `activate --send` puts `--send` into RUN_CONSOLE where `webui-service.sh` renders it. It refuses unless
the run's Console flags hold `--sso`, given or carried. With `--no-send` it is a usage error. The plan's
send line reads `send: on; --send was given`, or `send: carried` when the pointer already holds it.
`check_target` lets `--send` name the pointer's copy when the pointer holds no `--send`.
2. `activate --no-sso` is allowed only when the pointer's Console flags hold `--sso`. Otherwise it refuses
with a named reason. It takes the Console flags from the rollback set of the activation that turned
`--sso` on (that set's `show-$CONSOLE_UNIT.txt`, checked against its SHA256SUMS), the way `sso_base`
already does for a rollback to a copy without `--sso`. It drops `--send`, and its plan line says so
(F39 a). The tester follows the pointer. A set it can't read, or a set whose Console already ran with
`--sso`, refuses before anything stops. `check_target` lets `--no-sso` name the pointer's copy. With
`--sso`, `--send`, a proof pair or `--no-proof`, it is a usage error. The plan prints
`sso: off; Console flags from <set>/<file>` and lists every flag it restores.
3. Both flags are refused under `--units external`, like the other unit-render flags (row 232).
4. The usage text and the header comment name both flags. No other behavior changes.
Tests, whole files on Sage's slot: `packages/cli/tests/stack-release.test.mjs`,
`packages/cli/tests/stack-release-external.test.mjs` for item 3, and `scripts/test-release.sh`.
- T1: `--sso --send` on a pointer without send. The plan shows `send: on`, and the unit and the pointer
gain `--send`.
- T2: `--send` without `--sso`, given or carried, refuses (F39 b). `--send --no-send` is a usage error.
`--send` on the same copy when the pointer already has send refuses as `already-active`.
- T3: from an `--sso --send` pointer, `--no-sso` on the same copy returns the unit and the pointer to the
pre-`--sso` flags byte for byte, with no `--send` (F39 a and c). The tester stays as the pointer had it.
- T4: `--no-sso` refusals, each before any unit stops: a pointer without `--sso`; a tampered set
(SHA256SUMS mismatch); a set whose Console ran with `--sso`; `--no-sso` combined with `--sso`, `--send`
or a proof flag.
- T5: `--units external` refuses both flags.
Mutants, each killed: drop the SHA256SUMS check in the `--no-sso` path; let `--no-sso` run with no `--sso`
in the pointer; let `--no-sso` keep `--send`; let `--send` run without `--sso`; let `--send` and
`--no-send` combine.
Gate: Filbert approves the manifest. The two touched test files run whole plus test-release.sh, and the
mutants are killed. No live activation in this row. The window uses it.
+39
View File
@@ -0,0 +1,39 @@
# Federation build row R3, the broker user principal (#414)
Sage, 2026-10-22. The design is Gizmoduck's `agents/gizmoduck/work/federation/DESIGN.md` at b93f098f0 on
`gizmoduck/federation-design` (r4.11, sha256 1bcee8df0a3a8628a6646535b756c936ca6d9e7bcfa7e1dd2bf1d3f01715c8e6).
Sections 3.5, 3.6 and 7 set R3's scope and test gate. Vikunja P19 #414. R3's parents, R1 (row 201) and row 196,
have landed. I owed this filing when they did and missed it (morgana-0063). Row 205 (193b1a) and 193b1b wait
on it.
## Brief: FED R3, the broker user principal
Owner Morgana, after 193b1a's candidate goes to review. Reviewer Dewey (section 7: Filbert or Dewey). Gate
owner Sage. Base: 193b1a's candidate (row 205), so R3 extends that row's `trail` and `replies` filter to user
sessions instead of re-cutting it. If 193b1a changes in review, R3 rebases onto a new branch name.
What to build, in `packages/bus/src/` (broker.mjs and a new `user-cli.mjs`) plus `packages/harness/src/runner.mjs`
for the header line:
1. The user proof: a path-bound `packages/bus/src/user-cli.mjs` with its own nonce variable
(`MOSAIC_BUS_USER_NONCE`), checked with the `user` kind of row 196's `verifyPrincipal` (section 3.6 checks
1-3). R3 consumes that kind. It doesn't re-implement the parent walk.
2. `bindUser({ business, user })` and the broker changes 1-7 in section 3.6, including the reply path (B6),
`#userThread`, `user-thread-human`, `#waiting` returning null for `to: 'user'`, and the runner header line
for `from_role` `user`, word for word.
3. The collaborator verb list from section 3.5, exact, with its filters. Every other verb fails
`access-refused` before its own checks. The grant is read per request through R1's loader and `covers`
with `level: 'collaborator'`.
4. The section 3.6 item 5 audit: every `s.human` and `s.tester` branch in broker.mjs, listed in the packet as
user refused, user treated as human, or not reachable for a user.
5. No revocation timer, `access.reload` or launcher change. Those are R4. No Console change. That is R6.
Tests, in `packages/bus/tests/` and the runner's tests: the R3 test gate in section 7, item by item. Mutants:
the five in section 7's R3 row, plus one that drops the 193b1a filter for user sessions (a user's `trail` of
a hidden subject differs from an unknown id's). The agent-parent, non-Console-parent and kind-confusion
mutants are row 196's and stay there.
Boundaries: no change to `~/.mosaic-dev`, `~/.config/mosaic-dev`, the live units or any live access.json. Tests
use scratch roots. Single test files run freely, and suites need Sage's slot.
Gate: Dewey approves the manifest. The bus and harness suites and every `scripts/test-*.sh` are green on
Sage's gate, and the mutants are killed.
+121
View File
@@ -0,0 +1,121 @@
# Plan: Q175 phase 2 window, the live Console switches to --sso
Sage, 2026-10-22. Draft 2 for Mos, after mos-1245. Sources: `agents/sage/work/q175-sso/DESIGN.md` sections 5
and 10 (phase 2 order, rollback, Jason-gated steps), Mos's C1 condition (mos-0977, b416d2ede), Jason's phase 2
answers (Q209, mos-1216, mos-1217), mos-1195, mos-1197 and mos-1245. Rows 192 (phase 1), 200 (`activate --sso`)
and its N1 follow-up have landed. Vikunja #412, Gitea #385.
## Blocked on a tool gap
Two steps need `scripts/stack-release.sh` flags that don't exist at 02da6a2ae:
- `activate` has `--no-send` but no `--send`. The live pointer holds no `--send`: the 10-20 19:29:56Z
re-pin of 490e2771d gave `--no-send`, and every activation since has carried it off. So step 5 can't turn
`--send` on (mos-1195).
- `activate` has no way to turn `--sso` off. Without `--sso`, `sso_base` carries it from the pointer
(`sso: carried`), and `check_target` refuses the pointer's own copy as `already-active`. The pre-window
flags (`--public-host`, `--auth-header`, `--allow-user`) have no activate option either. So R1's
same-copy rollback can't run.
The fix is one small row, approved in mos-1246: `activate --send` and `activate --no-sso`. `--no-sso`
takes the Console flags from the rollback set of the activation that turned `--sso` on, the way `sso_base`
already does for a rollback to a copy without `--sso`. It drops `--send`, and its plan line says so. `--send`
refuses unless the run's Console flags hold `--sso`, given or carried (F39). test-release.sh gets cases for
both. Darkwing builds it, and a seat other than Sage and Darkwing reviews it. Brief:
`docs/plans/2026-10-22_activate-send-nosso.md`. It lands and is re-pinned before the window, on Mos's go,
run by Jason or Mos. The steps below assume it, and draft 3 names the new copy.
## When
Only when Jason starts the window (mos-1197). Nothing below runs before his start. The 10-24 re-pin
condition is met: W12 passed on 7247d80c7 at 22:27:13Z (mos-1245). This plan names 7247d80c7, which already
carries phase 1, so its loader accepts a config with or without `console`. If another re-pin lands before
Jason starts, the plan names the copy live at that point, and step 3 runs against it.
## Who
- Sage or Darkwing runs the agent steps under Q173: backup first, STATE report after.
- Mos or Jason starts and restarts the Console (decision 102).
- Jason signs in, sends and signs out.
- Config writes 1 and 2 (steps 4 and 8) are an agent's, `console` key only, each with a backup first and
a validate after. Mos's message to Jason at the window start names them. If Jason says he'll write them
himself, steps 4 and 8 become his and nothing else changes (mos-1245 (a)).
## Values
- Provider: the existing Authentik `mosaic-stack` provider (A5). Issuer
`https://auth.diversecanvas.com/application/o/mosaic-stack/`. Redirect
`https://mosaic.woltje.com/auth/callback`. The Logout URI field holds `/auth/signed-out`.
- clientId `fvchAepp9ZGoVq8l7YKej3Gbh5dyf8vNv9ORpDaU` (mos-1217, not a secret).
- clientSecret `{ "slot": "console-sso-authentik" }`. The slot file is
`~/.config/mosaic-dev/secrets/mosaic-stack/console-sso-authentik`, set to 0600 by Mos at 21:25:06Z on 10-21
(A6).
- subjects: Jason's sub only, name `jason.woltje` (mos-1197). The loader refuses an empty list
(`sso-config.mjs:62`), and the Console reads the config only at start. So the first write carries one
placeholder subject. The real sub replaces it after his first sign-in, followed by a restart (A8).
## Steps
1. Backups: the bus store, the release pointer, the Console unit and config.json, each copied 0600 into
a new dated directory, with sha256 recorded. The pointer backup records the pre-window Console flags
the rollback restores.
2. Slot shape check (A6), without printing or copying the value: `stat` shows mode 0600 and the owner;
`wc -l` is 1; `tr -cd '\r' <slot | wc -c` is 0; the last byte is a newline (`tail -c1 | od -An -c`).
3. Straggler check (C1): list every running process, unit and timer that reads config.json, with the
copy it runs from (`/proc/<pid>/exe` and cwd links, `systemctl --user list-units`, `list-timers`). Each one
must run from 7247d80c7, or be shown never to re-read config. Step 4 waits until the list is clean.
4. Config write 1 (agent, backup from step 1): add `console.sso` with the values above and one subject
`{ "sub": "pending-first-sign-in", "name": "jason.woltje" }`. `scripts/mosaic-config.mjs validate`
reads it back before anything starts.
5. First `scripts/stack-release.sh activate 7247d80c7 mosaic-stack --sso --send --plan`. Its from line must
name 7247d80c7 (`--expect-from` is for `--units external` only, so the live user units refuse it as a
usage error). Then the same command without `--plan`, the same way earlier re-pins handled the
Console. The tester stays the private socket from row 196 (`--tester-socket`). The `--plan` output
names every flag it drops (`--public-host`, `--auth-header`, `--allow-user`) and every flag it carries.
The run stops if `--sso` or `--send` is missing or a forward-auth flag remains. Mos or Jason starts the
Console. No agent starts it.
6. The start line shows one provider, the issuer and `subjects 1`. No secret value, no sub.
7. Jason's first sign-in (A8). It refuses `subject-unknown`. An agent reads `sub=` from that one log line,
checks that `iss=` is the issuer above, and records that the refused sub is not the placeholder. The sub
goes into the window record, not into any message.
8. Config write 2 (agent, new backup first): replace the placeholder with Jason's sub, so the list holds
that one subject only. Validate. Mos or Jason restarts the Console. The start line shows `subjects 1`.
9. Jason signs in, sends one message and signs out.
10. Unsigned write refused (mos-1195), within Q173 (mos-1245 (c)): an agent acts as an anonymous client,
not as Jason. It POSTs one Console write to `127.0.0.1:7332` with no cookie and no auth header, and
the body names a tester-owned thread, so a wrong 2xx reaches no live seat. It expects 401
`sign-in required` with the bus event count unchanged, and it writes nothing else. No agent sends
`X-authentik-username` to the live Console (design section 8).
11. Forged header refused: on a scratch Console from the same copy (`serve --sso --port <scratch>`,
`MOSAIC_CONFIG` at a scratch config with a scratch dataRoot and a dummy slot, issuer an unreachable
loopback host, no `--send` and no tester), a request with `X-authentik-username` and no cookie
gets 401. Mos or Jason may run the same check on live instead (B6).
12. Sign-out landing (A5): after step 9, did Authentik land on `/auth/signed-out`, or did it refuse
`post_logout_redirect_uri`? `sso.mjs` deletes the session before redirecting, so a miss changes only the
landing page. On a miss, Mos turns it into one small ask to Jason, and the window still counts as passed.
13. STATE report: pointer, copy, schema, Console flags from the unit, the start line, results of steps 7-12,
backup paths and digests. Gitea #385 gets the STATE comment. The `--sso` flag carries forward on later
re-pins (mos-1197).
## Stops
- Any refusal in steps 2-6 or 8: stop, keep the backups, and roll back as below. Never edit around it.
- While the placeholder is the only subject, any sign-in that creates a session is a stop and a
rollback (mos-1245 (b)).
- Step 10: any result other than 401 with the event count unchanged is a stop, and it goes to Mos.
- Two failures in the window: stop and report to Mos (mos-0823).
## Rollback
1. Same-copy re-activate (mos-1245 R1): `scripts/stack-release.sh activate 7247d80c7 mosaic-stack --no-sso
--plan`, then the same without `--plan`. R1 asked for `--expect-from 7247d80c7`, but `--expect-from` is
for `--units external` only and the live units are user units, so the plan's from line must name
7247d80c7 instead. The `--plan` output must show the pre-window Console flags from the step 1 pointer backup: `--port 7332 --public-host
mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket`, exactly.
`--no-sso` drops `--send` (mos-1246 F39), so there's no `--send`. A plan that differs is a stop. Mos or Jason restarts the Console.
2. Restore config.json from the step 1 backup, which removes `console`. 7247d80c7 loads config with or
without the key, so the order of 1 and 2 doesn't matter on this copy.
3. STATE after the rollback reads config.json, the unit's Console flags and the pointer.
4. Last resort only, and it waits for Jason: `scripts/stack-release.sh rollback` goes to the previous
distinct copy, 4429a104f at schema 3i. The store has been at 3j since 21:59Z on 10-21, so that rollback
needs `--restore`, and any events after the backup make it Jason's call (header condition 3).
+8
View File
@@ -346,6 +346,14 @@ at every gate. Started 2026-09-12 during the control board MVP.
do nothing yet. The row that adds the Console library routes decides
whether the tester listener gets `library.sync`. Duckworth's view is
that it shouldn't, because sync is a human write. (2026-10-21)
- **Console library routes: four library codes still map to 502.**
(Dewey N2 on the row 254 manifest, #1759 comment 30173.) Row 254
gave six library codes a STATUS entry in `packages/webui/src/bus.mjs`.
`library-mapping-invalid`, `library-remote-invalid`,
`library-state-invalid` and `library-git` still fall through
`busStatus` to 502. The row that adds the Console library routes
decides their statuses and gives its `humanCall` for `library.sync`
a timeout over 130 seconds. (2026-10-21)
- **T27 sweep test leaves two `sleep 60` children running.**
(Darkwing row 251 slot, darkwing-0113 and darkwing-0117; Dewey on
r2, comment 30168.) The sweep test spawns two detached shells,
+16 -9
View File
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
<!-- mosaic-queue:begin -->
Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
Generated from `docs/plans/queue.json` revision 3102 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|---|---|---|---|---|---|---|---|---|
@@ -241,8 +241,8 @@ Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not e
| 202 | FED R5: mosaic access list, grant and revoke (#414) | quackerjack; reviewers duckworth | #1700 | in-review, round 2, request posted | — | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R5, mosaic access list, grant and revoke | Chained on row 201's candidate (Q177), not its done: claimable now, lands after 201 and rebases onto its landing (Sage) |
| 203 | FED R2: the federation key in the system config (#414) | quackerjack; reviewers webby | #1701 | briefed | — | Webby approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R2, the federation key in the system config | Chained on row 192's candidate (Q177): starts on 192's candidate, lands after 192 (Sage) |
| 204 | 193a: access level check widened to viewer (#1691) | morgana; reviewers duckworth | #1702 | done | — | Duckworth approves the manifest; business suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193a, the access level check widened to viewer | Landed d0bbc5c1c on refactor (base c9f374284). Patch-id 92c18aea matches git diff 44e36fc4c..f869fd062 on the 3 manifest paths. Manifest d38de8c9, Duckworth approve 29595. Gate 2789/0 on 4385b9ec1, mutants 7/7. Unpushed. |
| 205 | 193b1: viewer verbs, board refusals and the message filter (#1691; also after FED R3 and R4 when filed) | morgana; reviewers duckworth | #1703 | queued | 196 done | Duckworth approves the manifest; bus suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1, viewer verbs, board refusals and the message filter | — |
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | morgana; reviewers duckworth | #1704 | queued | 205 done, 234 done, 236 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | — |
| 205 | 193b1a: the message filter (#1703; FED R3 and 193b1b build on it) | morgana; reviewers duckworth | #1703 | briefed | 196 done | Duckworth approves the manifest; bus suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1a, the message filter | — |
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | morgana; reviewers duckworth | #1704 | queued | 205 done, 234 done, 236 done, 275 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | — |
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | morgana; reviewers duckworth | #1705 | queued | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | — |
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
@@ -252,11 +252,11 @@ Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not e
| 213 | Case 2 C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file | morgana; reviewers webby | #1714 | done | — | Webby approves the manifest. The business, bus and harness suites, `scripts/test-config.sh` and every `scripts/test-*.sh` are green on Sage's gate, and the `models` mutant is killed. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file | LANDED 361ba2387 (39 paths incl. 4 packet files). Patch-id f313e69b6f7e = c771a36f4..2b3e67e36; verify-commit ok, 35 manifest paths. Same combined gate as 195: business 200/0 bus 542/0 harness 140/0 cli 663/0, test-config 31/0 and all 10 scripts green, models mutant L1 killed. Webby N2 is row 235 acceptance (mos-1163). |
| 214 | Case 2 C2-11a editor wikilink node, after measuring whether [[ round-trips | rocko; reviewers filbert | #1715 | briefed | — | Filbert approves the manifest. The webui suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-11a editor wikilink node, after measuring whether [[ round-trips | — |
| 215 | Case 2 C2-04 the library entry in the business file, consumer skips, M9, mirror, library.file and library.status | herb; reviewers filbert | #1716 | done | — | Filbert approves the manifest. The business, seat, cli, project, bus and webui suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-04 the library entry in the business file, consumer skips, M9, mirror, library.file and library.status | LANDED e54e05463 on 6096b051b (sage-2094). Patch-id 2f68188b6c9d equals a36523713..dde3445ab; 118 paths, blobs and modes equal; tree 5b1613245 equals merge-tree. No path overlap with rows 66/243; no hash pins either way. Gate: Filbert APPROVE rev 2924; herb-0095 grant sage-2071 2634 pass 0 fail, 1 webui skip (editor rebuild needs esbuild 0.28.2, env, not a 215 path), accepted. |
| 216 | Case 2 C2-05a lookup index and queries | researcher; reviewers filbert | #1717 | briefed | 268 done | Filbert approves the manifest. The library suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05a lookup index and queries | — |
| 216 | Case 2 C2-05a lookup index and queries | researcher; reviewers filbert | #1717 | briefed | 268 done | Filbert approves the manifest. The library suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05a lookup index and queries | Rulings on phase A: agents/sage/work/queue-216/RULINGS.md (da9555ca2). r2 rebases on row 268's landing and adds the four PRD 1.14 playground tests. |
| 217 | Case 2 C2-06 the cited-reply guard, the page store and the three notices | launchpad; reviewers webby | #1718 | briefed | 268 done | Webby approves the manifest. The library, bus and harness suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-06 the cited-reply guard, the page store and the three notices | — |
| 218 | Case 2 C2-12 the setup sheet for Melanie and the survey run sheet | sage; reviewers webby | #1719 | done | — | Doc only. Webby approves it against C2-01's real flags. Before landing, grep `packages/*/tests` and `scripts/test-*.sh` to confirm no test reads either file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12 the setup sheet for Melanie and the survey run sheet | LANDED f8276c5eb (pushed, on Darkwing sessions b65f3a0e8). Webby APPROVE r1-r3 (revs 2848, 2856, 2860; r3 candidate 1a38baf5af2a = 89ba79d94). Patch-id 18ba1e40ad29 seat 5c6f73ed7..89ba79d94 equals landed b65f3a0e8..f8276c5eb; three blobs equal 89ba79d94. Gate: doc only, 0 test readers (control 3). Melanie handover waits on C2-06 and Jason (#419). |
| 219 | Case 2 C2-09a one thread per creative instance | rocko; reviewers dewey | #1720 | briefed | — | Dewey approves the manifest. The bus, cli and webui suites are green on Sage's gate, along with every `scripts/test-*.sh`. Watch for hash pins on `bus.js` and `broker.mjs` (`packages/bus/tests/channel-proof.test.mjs` pins a webui file). (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09a one thread per creative instance | — |
| 220 | Case 2 C2-10a no raw session view (M7) and no raw Discord engine (M8) | researcher; reviewers filbert | #1721 | briefed | — | Filbert approves the manifest. The conversation and discord suites and `scripts/test-discord.sh` are green on Sage's gate, along with every `scripts/test-*.sh`. No test reaches a Discord server. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10a no raw session view (M7) and no raw Discord engine (M8) | — |
| 220 | Case 2 C2-10a no raw session view (M7) and no raw Discord engine (M8) | researcher; reviewers filbert | #1721 | briefed | — | Filbert approves the manifest. The conversation and discord suites and `scripts/test-discord.sh` are green on Sage's gate, along with every `scripts/test-*.sh`. No test reaches a Discord server. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10a no raw session view (M7) and no raw Discord engine (M8) | Rulings on phase A: agents/sage/work/queue-220/RULINGS.md (da9555ca2). Scope adds packages/business/src/library-seats.mjs (imported by path, no index export), its test, and control-board serve.mjs. r2 building as a Haiku subagent. |
| 221 | Case 2 C2-20 Bean and Chet persona import, with a count-only dry run | gosalyn; reviewers webby | #1722 | done | — | Webby approves the manifest. The test file runs green on its own and the cli suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-20 Bean and Chet persona import, with a count-only dry run | LANDED 300766450, pushed. Cand 8f403d0d4, manifest 20f05e3a6ad5, 22 paths, verify-commit ok, patch-id dfbb754c6fb4 equals 62ddc96cc..8f403d0d4. Gate 11:07:50-11:19Z on b1fece6f0: test file alone 11/0, harness 140/0, cli 677/0, all scripts green. Brief Gate met: webby-0073 approve, test file alone, cli, scripts. No live run in the Gate; the import itself waits on Jason. N1 N2 N4 go to C2-20b, N3 to DEFERRED. |
| 222 | Case 2 C2-03a fetch-only sync | megavolt; reviewers dewey | #1723 | done | — | Dewey approves the manifest. The library and bus suites are green on Sage's gate, along with every `scripts/test-*.sh`, and the mutant is killed. A search of the test run's output for the fixture token finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-03a fetch-only sync | sage-2024 on megavolt-0089: core.createObject=rename in the fetch argv is accepted as a brief departure, since C2-04 inspectMirror refuses nlink 2. Dewey checks it. The askpass env keys, export SAFE in project/src/git.mjs, bus/tests/library-sync.test.mjs and the SURVEY-RUN user line are accepted. The 5 s client timeout residual goes in BUILD.md and Dewey rules fix or defer. Slot after 197 proof 3 and the 249 gate. |
| 223 | Case 2 C2-07 runner retry on a guard refusal, and the runner's notices | researcher; reviewers filbert | #1724 | briefed | — | Filbert approves the manifest. The harness suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-07 runner retry on a guard refusal, and the runner's notices | — |
@@ -272,7 +272,7 @@ Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not e
| 233 | Installer I3: stack-upgrade.sh root driver and the ops-01 host run sheet (MVP, Q187, Vikunja #432) | darkwing; reviewers filbert | #1735 | done | 232 done | Filbert approves the manifest. The test file, the cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) | `docs/plans/2026-10-20_installer-rows.md` § I3 the upgrade command and the host run sheet | Landed 57f59e0c3 on 5465fd52d (pushed 05:39Z). verify-commit matches the manifest (7 paths). Patch-id b16ac924 equals the candidate's (1a1dc80ca vs e65a3ab4d). Modes match. Sage's gate on the landing tree: cli 617/0, scripts/test-*.sh 585/0. Filbert N1, N4 and N5 go to row 240, N2 and N3 to DEFERRED. |
| 234 | Machine grant row a: host enrollment, store records and migration 3i-3j (#436a, design r6 f9039682e, lands by 10-25 before the .53/.54 installs) | gizmoduck; reviewers duckworth | #1740 | done | 210 done | Duckworth approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. The design's section 10 row a tests and mutants pass, the migration test included. (sage) (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row a: enrollment, store records and migration 3i-3j | LANDED c771a36f4 (pushed 06:16Z 10-21) on caa4b5944: r3 c783d5e39 41 paths via temp index, modes from ls-tree. patch-id 32bbbea2bd6f equals the candidate's; verify-commit ok 41 paths. Gate on the c783d5e39 tree 06:02:12Z-06:15:26Z: 15 packages 2890/0, ten scripts/test-*.sh 585/0. Duckworth r3 approve 29935 rev 2683. N10, N12 to row 235. |
| 235 | Machine grant row b: raise binding, executor, reconcile and revoke (#436, design r6 f9039682e, C1, N1, G27, G28) | gizmoduck; reviewers duckworth | #1741 | done | 234 done | Duckworth approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. Section 10's row b tests and mutants pass, G27, G28 and the upsert mutant included. (sage) (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row b: raise binding, executor, reconcile and revoke | LANDED b1fece6f0, pushed (rode Darkwing SESSIONS aa19a2464). Cand ed4193316, manifest dad377ca8fe4, 13 paths, verify-commit ok, patch-id c355d2fc457d. Gate 10:30:55-10:42:43Z: bus 596/0, business 200/0, cli 666/0, all scripts green. Brief Gate met: duckworth-0089 approve, upsert mutant equivalent, row b and creative tests and mutants on Gizmoduck slot runs (38 of 39 killed, B17 equivalent). |
| 236 | #436b Gitea grant path build: seat accounts and tokens through an approved grant, schema 3j-3k (design r5 1be535aca, due 10-27 at risk; live waits for Jason's section 8 approval and bootstrap step) | megavolt; reviewers dewey | #1742 | queued | 210 done, 234 done | Dewey approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. Section 9's mutants are killed, N9's restart test included. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: #436b Gitea grant path, build (schema 3j-3k) | — |
| 236 | #436b Gitea grant path build: seat accounts and tokens through an approved grant, schema 3j-3k (design r5 1be535aca, due 10-27 at risk; live waits for Jason's section 8 approval and bootstrap step) | megavolt; reviewers dewey | #1742 | in-progress | 210 done, 234 done | Dewey approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. Section 9's mutants are killed, N9's restart test included. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: #436b Gitea grant path, build (schema 3j-3k) | — |
| 237 | Row 199 follow-up N1: one decision publishes once (publish-busy or #spent recheck) | megavolt; reviewers duckworth | #1743 | done | 199 done | Duckworth approves the manifest. The bus suite and every scripts/test-*.sh are green on Sage's gate, and the mutant is killed. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: row 199 follow-up N1, one decision publishes once | Landed d7d943199 on refactor (local, push pending), patch-id c5f4beb4 = e42415935, manifest bb777cb0 blobs checked. Sage gate 01:31-01:44Z on 816d48b03+4 paths: M1 killed (bus 1/13, cli 1/8), every package suite and scripts/test-*.sh green (bus 478, cli 564). |
| 238 | row 200 follow-up N1: the sso-config hint carries --instance NAME (#1744) | launchpad; reviewers webby | #1744 | done | 200 done | Webby approves the manifest; stack-release.test.mjs whole, cli suite and every scripts/test-*.sh green on Sage's gate; the mutant killed (sage) | `docs/plans/2026-10-20_stack-release-sso.md` § Brief: row 200 follow-up N1, the sso-config hint names the instance | Landed d49b1f2bd (pushed 07:25Z) on 86249ac55: the 3 manifest paths from 4c90099ea. Patch-id b9d0bb0ee3c7 equals the candidate's; modes and blobs match; verify-commit ok (3 paths). Sage's combined 228+238 gate on c515273db plus both rows' 6 paths, 07:11:30Z to 07:23:34Z: seat 165/0, cli 640/0, all ten scripts/test-*.sh 585/0. Mutant (sso-config drops --instance) killed, survived none. Round 1 N1 stays on the #1744 carry list. |
| 239 | Row 194 follow-up: the credential revoke runs under the writer's lock, refusals after a revoke say so, and a stale-revision fire test (duckworth-0074, mos-1158) | honker; reviewers duckworth | #1745 | queued | 194 done | Duckworth approves the manifest. The bus, business, cli and webui suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) (sage) | `docs/plans/2026-10-20_agent-lifecycle.md` § Brief: row 194 follow-up, the revoke under the writer's lock and a fire test | — |
@@ -290,7 +290,7 @@ Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not e
| 251 | T27 (host-run.test.mjs:453) lists no identities right after ssh-add -t 1: print timing and agent state, then fix the cause at the source (Vikunja #453) | darkwing; reviewers dewey | #1756 | done | 197 done | reviewer approves the manifest; bus suite whole on Sage's slot (sage) | `docs/plans/2026-10-21_t27-ssh-add-lifetime.md` § Brief: T27 lists no identities right after ssh-add -t 1 | LANDED d37210288 on b79ad5f2c: candidate 27f01ef63 manifest f2bdd684, patch-id 440d2442c976 equal, blob diff empty, tree matches merge-tree. Dewey r2 approve rev 2996 (c30168). Gate: bus suite whole in a clean worktree, 627/627 at 21:30:33Z on Sage's slot. Overlap check vs 194/198/244/253/254 branches: none. DEFERRED sleep-orphan entry follows as a doc commit. |
| 252 | Console inbox Approve and Deny for grant requests (#435, J2) | gander; reviewers webby | — | briefed | — | Webby approves DESIGN.md then manifest; webui, bus, cli suites and every test-*.sh green on Sage's gate; DESIGN mutants killed; REQ-WEB-2 PRD amendment landed first (sage) | `docs/plans/2026-10-21_console-inbox-approve.md` § Gate | mos-1193. Chained on row 196's candidate (Q177): design starts now on Gander's row 196 build; code rebases onto 196's landing. Vikunja #435. |
| 253 | F27: case2-unit.sh and case1-accept.sh move to --tester-socket and refuse --tester-port with stack-release's migration wording; no .53/.54 copy with 196 until this lands (mos-1198 item 4) | gander; reviewers duckworth | #1758 | in-progress | 196 done | reviewer approves DESIGN.md then the manifest; cli and webui suites whole and every scripts/test-*.sh on Sage's slot; mutants per DESIGN.md; static check covers scripts and tests (sage) | `docs/plans/2026-10-21_tester-socket-scripts.md` § Brief: case2-unit.sh and case1-accept.sh move to --tester-socket (F27) | — |
| 254 | Library sync follow-up: client timeout, STATUS codes, README (222 deferrals, #1759) | megavolt; reviewers dewey | #1759 | in-review, round 1, request posted | 196 done | Dewey approves the manifest; bus, library and webui suites whole and every scripts/test-*.sh green on Sage's slot; the timeout and STATUS mutants killed (sage) | `docs/plans/2026-10-21_library-sync-followup.md` § Gate | Dewey's deferrals from 222 approval c30138. Chained after 196 (human-cli channel proof). Issue #1759. |
| 254 | Library sync follow-up: client timeout, STATUS codes, README (222 deferrals, #1759) | megavolt; reviewers dewey | #1759 | done | 196 done | Dewey approves the manifest; bus, library and webui suites whole and every scripts/test-*.sh green on Sage's slot; the timeout and STATUS mutants killed (sage) | `docs/plans/2026-10-21_library-sync-followup.md` § Gate | Landed 331597709 (candidate b31300d79 blobs and modes on 41b0cf755; patch-id 96216216 equal, range-diff differs only in the message) plus doc eb9672c91 (Dewey N1 README sentence closed; N2 four 502 codes to DEFERRED, binding the Console library routes row). Merged-tree rerun on Sage's slot 23:44-23:55Z: library 77/0, bus 628/0, webui 305/0 with 1 skip, cli 749/0. Megavolt's gate megavolt-0102 covers scripts and both mutants. |
| 255 | Bundled Pocket ID for users with no OIDC provider, design (Q203, #1760) | launchpad; reviewers webby | — | queued | 252 done | Webby approves DESIGN.md; design only, build rows follow after Sage checks it against Q203, O6, REQ-WEB-2 and REQ-INST-1 (sage) | `docs/plans/2026-10-21_bundled-pocket-id.md` § Gate | Vikunja 458, Gitea #1760, mos-1202 item 2. REQ-INST-2 lands in PRD 1.10 as post-v1 (mos-1204). Design only: no install, no download, no .53/.54, dragon-lin keeps Authentik. |
| 256 | the human deletes a creative instance; any other actor refused, fire unchanged (REQ-COS-3, before the v1 tag) | honker; reviewers webby | #1761 | briefed | 194 done | Webby approves the manifest; touched test files whole plus business, bus, cli and webui suites and every scripts/test-*.sh green on Sage's slot; both mutants killed (sage) | `docs/plans/2026-10-21_creative-delete-human.md` § Brief: the human deletes a creative instance (REQ-COS-3, after row 194) | — |
| 257 | Q198 S1: pressure parser and pure rules evaluator (post-v1) | morgana; reviewers filbert | #1762 | in-review, round 2, request posted | — | Reviewer approves the manifest; touched test files whole plus the package suite green on Sage's slot; both brief mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: Q198 S1 pressure parser and pure rules evaluator | — |
@@ -300,11 +300,18 @@ Generated from `docs/plans/queue.json` revision 3059 by `queue render`. Do not e
| 261 | Email E05: Graph source module against a test-local registry (post-v1) | unassigned | #1766 | queued | 259 done, 260 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E05 Graph source module against a test-local registry | — |
| 262 | Email E06: IMAP source module (post-v1) | unassigned | #1767 | queued | 261 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E06 IMAP source module | — |
| 263 | Email E07: address and rule evaluator (post-v1) | unassigned | #1768 | queued | 258 done, 259 done, 260 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E07 address and rule evaluator | — |
| 264 | T1 item 7: independent sessions, compaction counts and clear context | sage | #1769 | queued | — | The named reviewer approves the manifest. The design's T1-T7 acceptance tests run whole plus every touched suite on Sage's slot; T3 waits on Vikunja 466 (N2, Claude's compaction count format and the managed dataRoot reader). The design's section 6 mutants are killed. (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Vikunja 463 (T1 item 7, PRD 1.13 REQ-SESS-1 to 3). Acceptance test T3 depends on Vikunja 466 (N2), mos-1237. v1 under Q211, ahead of Q198 S1. Owner and reviewer to be named (Scrooge approved the design but has no agents/ seat). |
| 264 | T1 item 7: independent sessions, compaction counts and clear context | gizmoduck; reviewers duckworth | #1769 | in-progress | — | The named reviewer approves the manifest. The design's T1-T7 acceptance tests run whole plus every touched suite on Sage's slot; T3 waits on Vikunja 466 (N2, Claude's compaction count format and the managed dataRoot reader). The design's section 6 mutants are killed. (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Vikunja 463 (T1 item 7, PRD 1.13 REQ-SESS-1 to 3). Acceptance test T3 depends on Vikunja 466 (N2), mos-1237. v1 under Q211, ahead of Q198 S1. Owner and reviewer to be named (Scrooge approved the design but has no agents/ seat). |
| 265 | Case 2 C2-09b rooms: room.open, room.send with @tags, room.read | rocko; reviewers dewey | #1770 | queued | 219 done | Dewey approves the manifest. The brief's tests run whole plus the bus suite and every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09b rooms: room.open, room.send with @tags, room.read | — |
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | researcher; reviewers dewey | #1771 | queued | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | Haiku high subagent under decision 129. |
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | briefed | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | MVP first claim (mos-1240). Lands before C2-05a (216) and C2-06 (217) read classify; if either has a candidate first, the change goes into it too (gosalyn-0134). Code library-playground-other-universe accepted (sage). |
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | in-review, round 2, request posted | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | MVP first claim (mos-1240). Lands before C2-05a (216) and C2-06 (217) read classify; if either has a candidate first, the change goes into it too (gosalyn-0134). Code library-playground-other-universe accepted (sage). |
| 269 | 264b T1 item 7: per-session native store, workspace and single-writer fence | gizmoduck; reviewers duckworth | #1774 | briefed | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T1 run half, T2 workspace and path mutants; whichever of 191 and 264b lands second wires 191's admission checks (Q5). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 270 | 264c T1 item 7: compaction count, exact for Pi | gizmoduck; reviewers duckworth | #1775 | briefed | 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Test T3 Pi half; Claude shows unknown with the Vikunja 466 reason (Q6). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 271 | 264d T1 item 7: clear context through row 194's lifecycle stop | gizmoduck; reviewers duckworth | #1776 | briefed | 194 done, 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T4 and T6; rebased onto 194's landing with no 194 code copied (Q4). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 272 | 264e T1 item 7: mosaic session CLI and Console chooser | gizmoduck; reviewers duckworth | #1777 | briefed | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T5 legacy single human and T7 Console half; controls for unlanded verbs refuse with a reason (Q1, Q7). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 273 | Q175 phase 2: stack-release.sh activate --send and --no-sso (F39, mos-1246) | darkwing; reviewers filbert | #1778 | briefed | — | Filbert approves the manifest. stack-release.test.mjs and stack-release-external.test.mjs run whole plus scripts/test-release.sh on Sage's slot, and the brief's mutants are killed. (sage) | `docs/plans/2026-10-22_activate-send-nosso.md` § Brief: activate --send and --no-sso | — |
| 274 | FED R3: broker user principal (#414) | morgana; reviewers dewey | #1779 | briefed | — | Dewey approves the manifest. The bus and harness suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_federation-r3.md` § Brief: FED R3, the broker user principal | — |
| 275 | 193b1b: viewer verbs and board refusals (also after FED R4 when filed) | morgana; reviewers duckworth | #1780 | briefed | 205 done, 274 done | Duckworth approves the manifest. The bus suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1b, viewer verbs and board refusals | — |
<!-- mosaic-queue:end -->
+1644 -35
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -24,6 +24,9 @@ try {
const me = readProcess(process.pid);
const client = new Client({
path: process.argv[2],
// Row 254: library.sync fetches for up to 100 s and the server holds its socket for 120 s,
// so wait past that for the server's own answer. Every other verb keeps the 5 s default.
timeout: request.verb === 'library.sync' ? 130_000 : undefined,
human: {
business: request.business,
pid: me.pid,
+4 -2
View File
@@ -2,6 +2,8 @@
// Row 196 intentionally re-pins human.mjs for the distinct user nonce and shared human
// walk, and webui bus.mjs for initial-spawn nonce plus peer refusal status. Entry paths
// and the ban on channel plug-ins importing a proof caller are retained.
// Row 254 (#1759) re-pins human-cli.mjs for the 130 s library.sync client timeout and webui
// bus.mjs for six library-* STATUS entries; no proof or transport change.
import test from "node:test";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
@@ -12,14 +14,14 @@ import { Client } from "../src/client.mjs";
const baselines = {
"packages/bus/src/human.mjs": "0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079",
"packages/bus/src/human-cli.mjs": "269c3a66008c0d57a54b14b73cf988d5f61f5e815455486296716e98ce5e8073",
"packages/bus/src/human-cli.mjs": "9c9af66b98ea0a490c9a880fc81c3c62414a8ee0b5ba6478608771b8c4f87932",
"packages/cli/src/transport.mjs": "5129c44b4d9f642e825344df65c9bfb4bac4fc8d6b25343b42d39853dbd40774",
// Row 165 (#1662): seven STATUS entries for the role instance routes (organization-instance-exists,
// organization-unknown-instance, organization-credentials-out-of-scope, organization-launch-not-configured,
// organization-no-change, organization-project-mismatch, organization-unresolvable-instance), no proof or
// transport change. Row 155 (#1649) added eight for the assistant routes.
// Row 171 (#1668): project-name-is-repo maps to 409; no proof or transport change.
"packages/webui/src/bus.mjs": "934ab2f9b6bde72aafe7814758550c283eb25a4f3a23b284021b13718d67178f",
"packages/webui/src/bus.mjs": "e69d17d4385a4d190d15373ed1a26e270a5c6906eec9e61fcc2bb6b05849cabb",
};
const repoFile = path => new URL("../../../" + path, import.meta.url);
+36
View File
@@ -4,6 +4,9 @@ import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createServer } from 'node:net';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { Store } from '../src/store.mjs';
import { Broker, LIBRARY_VERBS } from '../src/broker.mjs';
import { startBroker } from '../src/runtime.mjs';
@@ -107,3 +110,36 @@ test('C2-03a runtime: the timer syncs each entry with a user at start and on eac
const mirror = library.mirrorPath({ business, project: 'books', dataRoot });
assert.equal((await library.readSyncState(mirror, { branch: 'main' })).lastError.code, 'library-fetch-failed');
});
// Row 254 (#1759): the human transport waits past the server's 120 s library hold for library.sync
// only. A stand-in bus answers each request after 6 s, past the 5 s default every other verb keeps.
test('row 254 human-cli: library.sync outlasts the 5 s client default, and library.status does not', { timeout: 20000 }, async t => {
const root = mkdtempSync(join(tmpdir(), 'bus-library-cli-'));
const socket = join(root, 'bus.sock'), timers = new Set();
const server = createServer(conn => {
let input = '';
conn.on('error', () => {});
conn.on('data', b => {
input += b;
if (!input.includes('\n')) return;
const { verb } = JSON.parse(input);
timers.add(setTimeout(() => { if (!conn.destroyed) conn.end(JSON.stringify({ ok: true, result: { verb } }) + '\n'); }, 6000));
});
});
await new Promise(r => server.listen(socket, r));
t.after(() => { for (const timer of timers) clearTimeout(timer); server.close(); rmSync(root, { recursive: true, force: true }); });
const cli = fileURLToPath(new URL('../src/human-cli.mjs', import.meta.url));
const run = verb => new Promise(resolve => {
const child = spawn(process.execPath, [cli, socket], { stdio: ['pipe', 'pipe', 'pipe'] });
let out = '', err = '';
child.stdout.on('data', b => { out += b; });
child.stderr.on('data', b => { err += b; });
child.on('close', status => resolve({ status, out, err }));
child.stdin.end(JSON.stringify({ business: 'acme', verb, args: { project: 'books' } }) + '\n');
});
const [sync, status] = await Promise.all([run('library.sync'), run('library.status')]);
assert.equal(sync.status, 0, sync.err);
assert.deepEqual(JSON.parse(sync.out), { verb: 'library.sync' });
assert.equal(status.status, 2);
assert.equal(status.err.trim(), 'outcome-unknown');
});
@@ -66,13 +66,13 @@ async function closed(e,x,a){
const storeSha=(e)=>sha(e.store);
const backupStore=(e,a)=>join(e.data,'backups','stack',a,'bus','bus.sqlite');
test('row 232: confirm, revert, gate, --to and --expect-from need --units external; the user-only flags are usage under it',async(t)=>{
test('row 232: confirm, revert, gate, --to and --expect-from need --units external; the user-only flags are usage under it, and so are row 273\'s --send and --no-sso',async(t)=>{
const {e,x}=await xseed(t);
const logs=logLines(e).length;
for(const a of [['confirm',C1,'acme'],['revert',C1,'acme'],['gate',B,'acme','--since','1'],['rollback','acme','--to',B],['activate',C1,'acme','--plan','--expect-from',B]]){
const r=await run(e,...a);assert.equal(r.code,4,a.join(' '));assert.match(r.err,/^usage: stack-release.sh adopt BUSINESS\n/);
}
for(const a of [['activate',C1,'acme','--pm'],['activate',C1,'acme','--tester-port','7444'],['activate',C1,'acme','--no-proof'],['activate',C1,'acme','--no-send'],['activate',C1,'acme','--sso'],['activate',C1,'acme','--proof-header','X','--proof-file','/p'],['rollback','acme'],['gate',B,'acme'],['activate',C1,'acme','--expect-from',B],['confirm',C1,'acme','--rollback',B],['confirm',C1,'acme','--cause','A5']]){
for(const a of [['activate',C1,'acme','--pm'],['activate',C1,'acme','--tester-port','7444'],['activate',C1,'acme','--no-proof'],['activate',C1,'acme','--no-send'],['activate',C1,'acme','--sso'],['activate',C1,'acme','--send'],['activate',C1,'acme','--no-sso'],['activate',C1,'acme','--proof-header','X','--proof-file','/p'],['rollback','acme'],['gate',B,'acme'],['activate',C1,'acme','--expect-from',B],['confirm',C1,'acme','--rollback',B],['confirm',C1,'acme','--cause','A5']]){
const r=await run(x,...a);assert.equal(r.code,4,a.join(' '));
}
assert.equal(logLines(e).length,logs);
+199 -7
View File
@@ -1258,7 +1258,7 @@ test('T53 row 187: activate sets, carries and drops the proof pair',async(t)=>{
['activate',C1,'acme','--plan','--proof-file',f]]){
const r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso\]/);
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso \| --no-sso\]/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
@@ -1417,7 +1417,7 @@ test('T55 row 189: activate --no-send drops --send, and rollback and pm-on carry
['rehearse','acme','--backup','/tmp/x','--port','7400','--no-send']]){
const r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\| --no-proof \| --sso\] \[--no-send\]/);
assert.match(usageText,/\| --no-proof \| --sso \| --no-sso\] \[--send \| --no-send\]/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
@@ -1465,7 +1465,7 @@ const DROPS='--public-host console.example --auth-header x-user --allow-user jas
const SHOW=`show-${CON}.txt`;
const setOf=(e,act)=>join(e.state,'stack-rollback',act);
const arrivalSet=(e,to)=>setOf(e,logLines(e).findLast((l)=>l.result==='ok'&&l.to===to&&l.from!==to).activation);
const ROLLBACK_PLAN=(set)=>`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${SHOW}, with --send and --tester-port as the pointer has them`;
const ROLLBACK_PLAN=(set)=>`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${SHOW}, with --tester-port as the pointer has it`;
const KEY_MSG=(e,to,op,why)=>`${e.cfg} holds a console key, and ${to}'s loader refuses it (${why}); remove the key first (restore the config.json backup from the window's step 1), then run the ${op} again`;
const RB_MSG=(why)=>`${B}'s Console does not support --sso, and the Console flags it ran with cannot be read: ${why}`;
// Rewrites a file in a rollback set (0700, files kept by cp -p) and, unless sums is false, its SHA256SUMS line.
@@ -1526,7 +1526,7 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac
['rollback','acme','--sso'],['pm-on','acme','--sso'],['status','acme','--sso'],['adopt','acme','--sso']]){
const r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso\] \[--no-send\]/);
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso \| --no-sso\] \[--send \| --no-send\]/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
@@ -1600,14 +1600,14 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac
assert.ok(conExec(e).endsWith(` --checkout ${repo} ${want.join(' ')}`),conExec(e));
assert.ok(stepText(e).includes(`Z drop --sso; ${B}'s Console does not support it, so the Console flags come from ${set}/${SHOW}\n`));
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
// The plain case: --send and the preview set and pair come back as they were.
// The plain case: the preview set and pair come back as they were. --send lost its --sso, so it goes (row 273, F39 b).
const g=await mk(t),gbase=await seedPreview(g,undefined,['--send']),f=await proofFile(g);
r=await prun(g,'activate',B,'acme',...pair(f));assert.equal(r.code,0,r.out+r.err);
await ssoConfig(g);
r=await prun(g,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
setKey(g,undefined);
r=await prun(g,'rollback','acme');assert.equal(r.code,0,r.out+r.err);
assert.deepEqual(pointer(g).console,[...noSend(gbase),...pair(f),'--send']);
assert.deepEqual(pointer(g).console,[...noSend(gbase),...pair(f)]);
for(const x of [e,g])await noLeak(x);
});
await t.test('item 3: a rollback set that is missing, fails its sums, holds no argv or already ran --sso refuses sso-rollback-flags',async(t)=>{
@@ -1679,7 +1679,7 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac
r=await prun(a,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
setKey(a,undefined);
const set=arrivalSet(a,C1);assert.ok(existsSync(join(set,show)),show);
assert.deepEqual(ssoLines(await planOnly(a,0,'rollback','acme')),[`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${show}, with --send and --tester-port as the pointer has them`]);
assert.deepEqual(ssoLines(await planOnly(a,0,'rollback','acme')),[`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${show}, with --tester-port as the pointer has it`]);
r=await prun(a,'rollback','acme');assert.equal(r.code,0,r.out+r.err);
assert.equal(pointer(a).copy,B);assert.deepEqual(pointer(a).console,want.slice(0,-1));
assert.ok(stepText(a).includes(`Z drop --sso; ${B}'s Console does not support it, so the Console flags come from ${set}/${show}\n`));
@@ -1695,6 +1695,198 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac
});
});
// Row 273 (#1778, mos-1246 F39): activate --send turns send on only beside --sso, given or carried, and
// activate --no-sso turns --sso off with the Console flags from the set of the activation that turned
// it on, dropping --send. The tester follows the pointer.
const SEND_MSG="--send needs --sso in the run's Console flags, given or carried; under forward auth any same-uid process could send (F39)";
const NOT_ON_MSG="the pointer's Console flags hold no --sso, so --no-sso has nothing to turn off";
const OFF_MSG=(why)=>`the Console flags from before --sso cannot be read: ${why}`;
const OFF_PLAN=(set,restores)=>`sso: off; Console flags from ${set}/${SHOW}; restores ${restores}`;
const NOTE=(to)=>`note: ${to} is the pointer's copy; the run re-pins it and only the Console flags change`;
test('T58 row 273: activate --send and --no-sso',async(t)=>{
await t.test('T1: --sso --send turns send on after --sso and before the tester port, and the plan says so',async(t)=>{
const e=await mk(t);await seedPreview(e,undefined,['--tester-port','7333']);await ssoConfig(e);
const want=['--port',e.port,'--business','acme','--sso','--send','--tester-port','7333'];
const out=await planOnly(e,0,'activate',C1,'acme','--sso','--send');
assert.deepEqual([...sendLines(out),...ssoLines(out)],['send: on; --send was given',`sso: on; drops ${DROPS}; carries --port ${e.port} --business acme --send --tester-port 7333`]);
assert.equal(out.find((l)=>l.startsWith('Console: ')),`Console: scripts/webui-service.sh install --copy ${C1} --copies ${COPIES} ${want.join(' ')}`);
let r=await prun(e,'activate',C1,'acme','--sso','--send');assert.equal(r.code,0,r.out+r.err);
assert.equal(pointer(e).copy,C1);assert.deepEqual(pointer(e).console,want);
assert.ok(conExec(e).endsWith(` --checkout ${repo} ${want.join(' ')}`),conExec(e));
const steps=stepText(e);
assert.ok(steps.includes('Z turn on --send; --send was given\n'),steps);assert.ok(steps.includes(`Z turn on --sso; drop ${DROPS}\n`),steps);
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
// The same copy with send on again is already active.
await refused(e,['activate',C1,'acme','--send'],'already-active',`${C1} is the pointer's copy`);
await noLeak(e);
});
await t.test('T1: --send beside a carried --sso re-pins the live copy once, and the plan names the carried --sso',async(t)=>{
const e=await mk(t);await seedPreview(e);await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme','--sso']);
const out=await planOnly(e,0,'activate',C1,'acme','--send');
assert.deepEqual([...sendLines(out),...ssoLines(out)],['send: on; --send was given','sso: carried']);
assert.ok(out.includes(NOTE(C1)),out.join('\n'));
r=await prun(e,'activate',C1,'acme','--send');assert.equal(r.code,0,r.out+r.err);
const l=logLines(e).at(-1);assert.deepEqual([l.op,l.from,l.to,l.result],['activate',C1,C1,'ok']);
const want=['--port',e.port,'--business','acme','--sso','--send'];
assert.deepEqual(pointer(e).console,want);assert.ok(conExec(e).endsWith(` --checkout ${repo} ${want.join(' ')}`),conExec(e));
assert.ok(stepText(e).includes('Z turn on --send; --send was given\n'));assert.doesNotMatch(stepText(e),/^\S+Z .*sso/m);
await refused(e,['activate',C1,'acme','--send'],'already-active',`${C1} is the pointer's copy`);
// Another copy carries both.
assert.deepEqual(sendLines(await planOnly(e,0,'activate',C,'acme','--send')),['send: carried']);
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
await noLeak(e);
});
await t.test('T2: --send without --sso, given or carried, refuses before any stop, and --plan lists it',async(t)=>{
const e=await mk(t);await seedPreview(e);
await refused(e,['activate',C1,'acme','--send'],'send-needs-sso',SEND_MSG);
await refused(e,['activate',B,'acme','--send'],'send-needs-sso',SEND_MSG);
const out=await planOnly(e,3,'activate',C1,'acme','--send');
assert.deepEqual(sendLines(out),['send: off']);
assert.deepEqual(out.filter((l)=>l.startsWith('refuses ')),[`refuses send-needs-sso: ${SEND_MSG}`]);
// A pointer that already holds --send without --sso still refuses a --send.
const g=await mk(t);await seedPreview(g,undefined,['--send']);
await refused(g,['activate',C1,'acme','--send'],'send-needs-sso',SEND_MSG);
await refused(g,['activate',B,'acme','--send'],'already-active',`${B} is the pointer's copy`);
for(const x of [e,g])await noLeak(x);
});
await t.test('T2 (sage-2315 B1): a rollback from an --sso --send pointer to a Console without --sso ends with neither flag',async(t)=>{
const e=await mk(t),base=await seedPreview(e);await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme','--sso','--send');assert.equal(r.code,0,r.out+r.err);
assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme','--sso','--send']);
const set=arrivalSet(e,C1);
setKey(e,undefined);
const out=await planOnly(e,0,'rollback','acme');
assert.deepEqual([...sendLines(out),...ssoLines(out)],['send: dropped; the run drops --sso, and --send needs it (F39)',ROLLBACK_PLAN(set)]);
assert.equal(out.find((l)=>l.startsWith('Console: ')),`Console: scripts/webui-service.sh install --copy ${B} --copies ${COPIES} ${base.join(' ')}`);
r=await prun(e,'rollback','acme');assert.equal(r.code,0,r.out+r.err);
assert.equal(pointer(e).copy,B);assert.deepEqual(pointer(e).console,base);
assert.doesNotMatch(conExec(e),/ --sso( |$)| --send( |$)/);assert.ok(conExec(e).endsWith(` --checkout ${repo} ${base.join(' ')}`),conExec(e));
assert.ok(stepText(e).includes('Z drop --send; the run drops --sso\n'),stepText(e));
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
await noLeak(e);
});
await t.test('T2 and T4: --send with --no-send, --no-sso with --sso, --send, a pair or --no-proof, and either flag on another verb, is a usage error',async(t)=>{
const e=await mk(t);await seedPreview(e);await ssoConfig(e);const f=await proofFile(e);
let r=await prun(e,'activate',C1,'acme','--sso','--send');assert.equal(r.code,0,r.out+r.err);
const usageText=(await prun(e,'--help')).err,n=calls(e).length,logs=logLines(e).length,before=fp(e);
for(const args of [['activate',C1,'acme','--send','--no-send'],['activate',C1,'acme','--no-send','--send','--plan'],['activate',C1,'acme','--sso','--send','--no-send'],
['activate',C1,'acme','--no-sso','--sso'],['activate',C1,'acme','--sso','--no-sso','--plan'],['activate',C1,'acme','--no-sso','--send'],
['activate',C1,'acme','--no-sso',...pair(f)],['activate',C1,'acme','--no-sso','--no-proof'],
['rollback','acme','--send'],['rollback','acme','--no-sso'],['pm-on','acme','--send'],['pm-on','acme','--no-sso'],['status','acme','--no-sso'],['adopt','acme','--send']]){
r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso \| --no-sso\] \[--send \| --no-send\]/);
assert.match(usageText,/\n--send needs --sso in the run's Console flags, given or carried\. --no-sso needs --sso in the\n/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
await t.test('T3: --no-sso on the same copy returns the unit and the pointer to the flags from before --sso, without --send',async(t)=>{
const e=await mk(t),base=await seedPreview(e,undefined,['--send','--tester-port','7333']);await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
const pre=conExec(e),want=noSend(base);
assert.ok(pre.endsWith(` ${base.join(' ')}`),pre);
r=await prun(e,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
const set=setOf(e,pointer(e).activation);
assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme','--sso','--send','--tester-port','7333']);
const out=await planOnly(e,0,'activate',C1,'acme','--no-sso');
assert.deepEqual([...proofLines(out),...sendLines(out),...ssoLines(out)],['proof: off','send: dropped; --no-sso drops --send (F39)',OFF_PLAN(set,want.slice(0,-2).join(' '))]);
assert.ok(out.includes(NOTE(C1)),out.join('\n'));
assert.equal(out[out.findIndex((l)=>l.startsWith('sso:'))+1],`Console: scripts/webui-service.sh install --copy ${C1} --copies ${COPIES} ${want.join(' ')}`);
r=await prun(e,'activate',C1,'acme','--no-sso');assert.equal(r.code,0,r.out+r.err);
const l=logLines(e).at(-1);assert.deepEqual([l.op,l.from,l.to,l.result],['activate',C1,C1,'ok']);
assert.equal(pointer(e).copy,C1);assert.deepEqual(pointer(e).console,want);
assert.equal(conExec(e),pre.replace(' --send --tester-port 7333',' --tester-port 7333'));
const steps=stepText(e);
assert.ok(steps.includes('Z drop --send; --no-sso was given\n'),steps);
assert.ok(steps.includes(`Z turn off --sso; the Console flags come from ${set}/${SHOW}\n`),steps);
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
// --sso is off now, so a second --no-sso has nothing to turn off.
await refused(e,['activate',C1,'acme','--no-sso'],'sso-not-on',NOT_ON_MSG);
await noLeak(e);
});
await t.test('T3: the set walk passes later re-pins that ran with --sso, and the tester follows the pointer',async(t)=>{
const e=await mk(t),base=await seedPreview(e,undefined,['--send','--tester-port','7333']);await ssoConfig(e);
// The activation that turns --sso on also moves the tester to 7334, so the set and the pointer differ.
let r=await prun(e,'activate',C1,'acme','--sso','--tester-port','7334');assert.equal(r.code,0,r.out+r.err);
const set=setOf(e,pointer(e).activation);
r=await prun(e,'activate',C1,'acme','--no-send');assert.equal(r.code,0,r.out+r.err);
assert.match(readFileSync(join(setOf(e,pointer(e).activation),SHOW),'utf8'),/ --sso /);
assert.deepEqual(pointer(e).console,['--port',e.port,'--business','acme','--sso','--tester-port','7334']);
const restores=base.slice(0,-3),want=[...restores,'--tester-port','7334'];
const out=await planOnly(e,0,'activate',C1,'acme','--no-sso');
assert.deepEqual([...sendLines(out),...ssoLines(out)],['send: off; --no-sso was given and the pointer holds no --send',OFF_PLAN(set,restores.join(' '))]);
r=await prun(e,'activate',C1,'acme','--no-sso');assert.equal(r.code,0,r.out+r.err);
assert.deepEqual(pointer(e).console,want);assert.ok(conExec(e).endsWith(` --checkout ${repo} ${want.join(' ')}`),conExec(e));
assert.doesNotMatch(stepText(e),/drop --send/);
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
await noLeak(e);
});
await t.test('T3 (sage-2305 a): after --sso, two later re-pins that carry it, then --no-sso restores the flags from before --sso byte for byte',async(t)=>{
const e=await mk(t),base=await seedPreview(e,undefined,['--tester-port','7333']);await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
const pre=conExec(e);assert.ok(pre.endsWith(` ${base.join(' ')}`),pre);
r=await prun(e,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
const set=setOf(e,pointer(e).activation);
// Two same-copy re-pins carry --sso, so the two newest sets both hold a Console with it.
r=await prun(e,'activate',C1,'acme','--send');assert.equal(r.code,0,r.out+r.err);
r=await prun(e,'activate',C1,'acme','--no-send');assert.equal(r.code,0,r.out+r.err);
const acts=logLines(e).filter((l)=>l.result==='ok'&&l.op==='activate').map((l)=>l.activation).slice(-2);
for(const a of acts)assert.match(readFileSync(join(setOf(e,a),SHOW),'utf8'),/ --sso /);
const out=await planOnly(e,0,'activate',C1,'acme','--no-sso');
assert.deepEqual(ssoLines(out),[OFF_PLAN(set,base.slice(0,-2).join(' '))]);
r=await prun(e,'activate',C1,'acme','--no-sso');assert.equal(r.code,0,r.out+r.err);
assert.deepEqual(pointer(e).console,base);assert.equal(conExec(e),pre);
r=await prun(e,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
await noLeak(e);
});
await t.test('T4 (sage-2305 b): a log in which every set holds a Console with --sso refuses before any unit stops',async(t)=>{
const e=await mk(t);await seedPreview(e,undefined,['--send']);await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
const first=setOf(e,pointer(e).activation);
r=await prun(e,'activate',C1,'acme','--no-send');assert.equal(r.code,0,r.out+r.err);
assert.match(readFileSync(join(setOf(e,pointer(e).activation),SHOW),'utf8'),/ --sso /);
// The set of the activation that turned --sso on now claims its Console ran with it too, sums intact.
setEdit(first,SHOW,' --public-host ',' --sso --public-host ');
const why=OFF_MSG(`the Console in ${join(first,SHOW)} already ran with --sso, and the log names no earlier activation`);
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',why);
assert.deepEqual((await planOnly(e,3,'activate',C1,'acme','--no-sso')).filter((l)=>l.startsWith('refuses ')),[`refuses sso-off-flags: ${why}`]);
await noLeak(e);
});
await t.test('T4: --no-sso refuses before any stop on a pointer without --sso, a tampered or missing set, a set that ran with --sso, an ok line with no activation, and a log with no set',async(t)=>{
const e=await mk(t);await seedPreview(e,undefined,['--send']);
await refused(e,['activate',B,'acme','--no-sso'],'sso-not-on',NOT_ON_MSG);
await refused(e,['activate',C1,'acme','--no-sso'],'sso-not-on',NOT_ON_MSG);
assert.deepEqual((await planOnly(e,3,'activate',C1,'acme','--no-sso')).filter((l)=>l.startsWith('refuses ')),[`refuses sso-not-on: ${NOT_ON_MSG}`]);
// One set, from the activation that turned --sso on.
await ssoConfig(e);
let r=await prun(e,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
const set=setOf(e,pointer(e).activation),show=join(set,SHOW),text=readFileSync(show,'utf8');
// An ok line with no activation stops the walk (sage-2315 N1); skipping it would reach the set above.
const logText=readFileSync(e.log,'utf8');
appendFileSync(e.log,JSON.stringify({at:'2026-10-22T00:00:00Z',op:'activate',from:C1,to:C1,backup:null,activation:null,result:'ok'})+'\n');
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG('an ok line in the log names no activation'));
writeFileSync(e.log,logText);
setEdit(set,SHOW,' --allow-user jason',' --allow-user mallory',false);
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG(`${show} does not match the set's SHA256SUMS`));
assert.deepEqual((await planOnly(e,3,'activate',C1,'acme','--no-sso')).filter((l)=>l.startsWith('refuses ')),[`refuses sso-off-flags: ${OFF_MSG(`${show} does not match the set's SHA256SUMS`)}`]);
setEdit(set,SHOW,' --allow-user mallory',' --sso --allow-user jason');
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG(`the Console in ${show} already ran with --sso, and the log names no earlier activation`));
setEdit(set,SHOW,' serve ',' ');
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG(`${show} holds no Console argv this script can read`));
rmSync(show);
await refused(e,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG(`${set} has no ${SHOW} and SHA256SUMS`));
writeFileSync(show,text);setEdit(set,SHOW,'\n','\n');
r=await prun(e,'activate',C1,'acme','--no-sso');assert.equal(r.code,0,r.out+r.err);
assert.doesNotMatch(conExec(e),/ --sso( |$)| --send( |$)/);
// A pointer with --sso and only the adopt line: no activation took a set.
const g=await mk(t);await seedPreview(g,undefined,['--sso']);
await refused(g,['activate',C1,'acme','--no-sso'],'sso-off-flags',OFF_MSG('the log names no activation with a rollback set'));
for(const x of [e,g])await noLeak(x);
});
});
// --- rehearse (D2, row 163; the brief's "Stack rehearsal on a scratch root") ---
// Copies of their own in RCOPIES: the real scripts/mosaic, config loader, business loader, roles and
// mosaic-config.mjs, with stand-ins for the bus host (cli.mjs), the Console, the Store and
+7
View File
@@ -165,6 +165,13 @@ lets go, so an object must outlive the next move of `served`. The bus host runs
with a `user`, once at start and then every 60 seconds; the slot is
`<configDir>/secrets/<business>/library-gitea`. `library.sync` is the human's bus verb for the same.
The human transport (`packages/bus/src/human-cli.mjs`) waits 130 seconds for `library.sync`, past the
server's 120-second hold, and 5 seconds for every other verb. A client that gives up before the answer
gets `outcome-unknown`, but the sync keeps running: a second call meanwhile gets `library-sync-busy`,
and `library.status` shows the result once it ends. A sync that outlasts the 120-second hold gets
`request-timeout` from the server and still finishes the same way. The Console has no library route yet: `cli.mjs`
passes `librarySync` to `startServer`, and `serve.mjs` doesn't read it.
## Tests
```sh
+5
View File
@@ -102,5 +102,10 @@ const STATUS = {
'organization-instance-exists': 409, 'organization-unknown-instance': 400,
'organization-credentials-out-of-scope': 400, 'organization-launch-not-configured': 400,
'organization-no-change': 400, 'organization-project-mismatch': 400, 'organization-unresolvable-instance': 400,
// Row 254 (#1759): library sync. A sync already running, a held divergence and a stale accept are
// the user's to resolve by reading library.status again; a missing credential or a failed fetch
// leaves the remote unreachable, like no bus to ask.
'library-sync-busy': 409, 'library-diverged': 409, 'library-accept-stale': 409, 'library-accept-invalid': 400,
'library-credential-missing': 503, 'library-fetch-failed': 503,
};
export const busStatus = code => STATUS[code] ?? 502;
@@ -0,0 +1,13 @@
// Row 254 (#1759): the library.sync refusal codes map to their HTTP status, not the 502 default.
import test from 'node:test';
import assert from 'node:assert/strict';
import { busStatus } from '../src/bus.mjs';
test('row 254: each library sync code maps to its status', () => {
const expected = {
'library-sync-busy': 409, 'library-diverged': 409, 'library-accept-stale': 409, 'library-accept-invalid': 400,
'library-credential-missing': 503, 'library-fetch-failed': 503,
};
for (const [code, status] of Object.entries(expected)) assert.equal(busStatus(code), status, code);
assert.equal(busStatus('library-not-a-code'), 502, 'an unknown code keeps the default');
});
+142 -21
View File
@@ -5,6 +5,8 @@
# Console tester listener flags follow target support (row 176, #1674); so does the row 178 proof
# pair, which activate sets or drops (row 187, #1685). activate --no-send drops --send (row 189, #1687).
# activate --sso swaps the preview set and the pair for the Console's own sign-in (row 200, #1698).
# activate --send turns send on, only beside --sso, and activate --no-sso turns --sso off with the
# Console flags from before it, dropping --send (row 273, #1778, mos-1246 F39).
# Design and tests: agents/darkwing/work/queue-162/BUILD.md.
#
# Who runs it. Decision 96 (Mos, mos-0716) lets this script stop and start the live units under
@@ -38,14 +40,14 @@ COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies"
BIN="$HOME/.npm-global/bin"
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
TIMEOUT=60 WANT_PM=0 AFTER_HARNESS=0 MIGRATE=0 PLAN=0 RESTORE="" EVENTS="" TESTER_PORT="" TESTER_SOCKET=0 EXPECTED_INTERPRETER=""
PROOF_HEADER="" PROOF_FILE="" NO_PROOF=0 NO_SEND=0 SSO=0 R_BACKUP="" R_PORT=""
PROOF_HEADER="" PROOF_FILE="" NO_PROOF=0 NO_SEND=0 SEND=0 SSO=0 NO_SSO=0 R_BACKUP="" R_PORT=""
UNITS=user TO_FLAG="" EXPECT_FROM="" SINCE="" ROLLBACK_OLD="" CAUSE=""
usage() {
cat >&2 <<'USAGE'
usage: stack-release.sh adopt BUSINESS
stack-release.sh activate ID BUSINESS [--pm | --pm-after-harness] [--migrate] [--tester-socket | --tester-port N (legacy copies)]
[--proof-header NAME --proof-file PATH | --no-proof | --sso] [--no-send] [--timeout S] [--plan]
[--proof-header NAME --proof-file PATH | --no-proof | --sso | --no-sso] [--send | --no-send] [--timeout S] [--plan]
stack-release.sh pm-on BUSINESS [--timeout S]
stack-release.sh rollback BUSINESS [--restore ACTIVATION [--events N]] [--timeout S] [--plan]
stack-release.sh status BUSINESS
@@ -61,7 +63,9 @@ every table in COUNTED.
--units external (row 232) reads root's system units and starts or stops none. adopt, activate,
rollback, status, confirm, revert and gate take it; rollback then needs --to ID, the previous copy.
activate and rollback --plan take --expect-from OLD. --pm, --pm-after-harness, --tester-port, the
proof flags, --no-proof, --no-send, --sso and pm-on are not for it.
proof flags, --no-proof, --send, --no-send, --sso, --no-sso and pm-on are not for it.
--send needs --sso in the run's Console flags, given or carried. --no-sso needs --sso in the
pointer's, and takes the Console flags from the rollback set of the activation that turned it on.
USAGE
exit "${1:-4}"
}
@@ -128,7 +132,9 @@ parse_opts() {
--proof-file) [ "$OP" = activate ] && [ $# -ge 2 ] && [ -n "$2" ] || usage; PROOF_FILE="$2"; shift 2 ;;
--no-proof) [ "$OP" = activate ] || usage; NO_PROOF=1; shift ;;
--no-send) [ "$OP" = activate ] || usage; NO_SEND=1; shift ;;
--send) [ "$OP" = activate ] || usage; SEND=1; shift ;;
--sso) [ "$OP" = activate ] || usage; SSO=1; shift ;;
--no-sso) [ "$OP" = activate ] || usage; NO_SSO=1; shift ;;
--migrate) [ "$OP" = activate ] || usage; MIGRATE=1; shift ;;
--restore) [ "$OP" = rollback ] && [ $# -ge 2 ] && [[ "$2" =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[1-9])?$ ]] || usage; RESTORE="$2"; shift 2 ;;
--backup) [ "$OP" = rehearse ] && [ $# -ge 2 ] && { [ "$2" = last ] || [[ "$2" =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[1-9])?$ ]] || [[ "$2" =~ ^/[A-Za-z0-9/._+-]*$ ]]; } || usage; R_BACKUP="$2"; shift 2 ;;
@@ -150,7 +156,8 @@ parse_opts() {
[ "$OP" != confirm ] && [ "$OP" != revert ] && [ "$OP" != gate ] && [ -z "$TO_FLAG" ] && [ -z "$EXPECT_FROM" ] || usage
else
[ "$OP" != rollback ] || [ -n "$TO_FLAG" ] || usage
[ "$TESTER_SOCKET" = 0 ] && [ "$WANT_PM" = 0 ] && [ -z "$TESTER_PORT" ] && [ -z "$PROOF_HEADER" ] && [ -z "$PROOF_FILE" ] && [ "$NO_PROOF" = 0 ] && [ "$NO_SEND" = 0 ] && [ "$SSO" = 0 ] || usage
[ "$TESTER_SOCKET" = 0 ] && [ "$WANT_PM" = 0 ] && [ -z "$TESTER_PORT" ] && [ -z "$PROOF_HEADER" ] && [ -z "$PROOF_FILE" ] && [ "$NO_PROOF" = 0 ] && [ "$NO_SEND" = 0 ] && [ "$SSO" = 0 ] &&
[ "$SEND" = 0 ] && [ "$NO_SSO" = 0 ] || usage
fi
[ "$OP" != gate ] || [ -n "$SINCE" ] || usage
[ -z "$EXPECT_FROM" ] || [ "$PLAN" = 1 ] || usage
@@ -167,6 +174,10 @@ parse_opts() {
[ "$NO_PROOF" = 0 ] || [ -z "$PROOF_HEADER" ] || usage
# --sso drops the pair itself, so it takes neither a pair nor --no-proof (row 200).
[ "$SSO" = 0 ] || { [ -z "$PROOF_HEADER" ] && [ "$NO_PROOF" = 0 ]; } || usage
# Row 273: --send and --no-send contradict. --no-sso takes its Console flags from a rollback set
# and drops --send, so it goes with no --sso, --send, pair or --no-proof.
[ "$SEND" = 0 ] || [ "$NO_SEND" = 0 ] || usage
[ "$NO_SSO" = 0 ] || { [ "$SSO" = 0 ] && [ "$SEND" = 0 ] && [ -z "$PROOF_HEADER" ] && [ "$NO_PROOF" = 0 ]; } || usage
local d
for d in "$COPIES" "$BIN"; do
case "$d" in /*) ;; *) usage ;; esac
@@ -225,7 +236,8 @@ for(const k of open.keys())process.stdout.write(k+"\n");' -- "$LOG"
# line that made the pointer's copy live, the backup a rollback would restore. `newest` prints the
# backup field of the newest line that has one, rehearse's --backup last (row 233). `arrival` prints the
# activation and `from` of the line that moved the stack onto the pointer's copy from another one,
# past any same-copy re-pin after it (row 200, sage-1389).
# past any same-copy re-pin after it (row 200, sage-1389). `sets` prints the activation of each
# activate and rollback, newest first, back to the adopt line, whose run took no rollback set (row 273).
from_log() {
[ -f "$LOG" ] || return 1
node -e '
@@ -234,12 +246,13 @@ const lines=require("fs").readFileSync(file,"utf8").split("\n").filter((l)=>l.tr
for(const l of lines.reverse()){
let o;try{o=JSON.parse(l)}catch{continue}
if(o.result!=="ok"||!["adopt","activate","rollback"].includes(o.op)||!o.to)continue;
if(mode==="sets"){if(o.op==="adopt")process.exit(0);process.stdout.write(`${o.activation??""}\n`);continue}
if(mode==="previous"&&o.to!==current){process.stdout.write(o.to+"\n");process.exit(0)}
if(mode==="backup"&&o.to===current){if(o.backup)process.stdout.write(o.backup+"\n");process.exit(o.backup?0:1)}
if(mode==="newest"&&o.backup){process.stdout.write(o.backup+"\n");process.exit(0)}
if(mode==="arrival"&&o.to===current&&o.from!==current){process.stdout.write(`${o.activation??""} ${o.from??""}\n`);process.exit(0)}
}
process.exit(1);' -- "$LOG" "$1" "$P_COPY"
process.exit(mode==="sets"?0:1);' -- "$LOG" "$1" "$P_COPY"
}
# --- Reading the units ---
@@ -323,15 +336,18 @@ in_chain() { local v; for v in "${T_CHAIN[@]}"; do [ "$v" != "$1" ] || return 0;
# The item 3 checks on the target copy, in order: copy, schema, already-active. An activate that
# sets a proof pair, drops the pointer's pair with --no-proof, drops the pointer's --send with
# --no-send, or turns on --sso, may name the pointer's copy: it re-pins the live copy with new
# Console flags (row 187, Part B's B3 and B-R1; row 189, mos-0972; row 200, the phase 2 window).
# --no-send, turns on --sso, turns on --send, or turns off --sso may name the pointer's copy: it
# re-pins the live copy with new Console flags (row 187, Part B's B3 and B-R1; row 189, mos-0972;
# row 200, the phase 2 window; row 273). --no-sso on a pointer without --sso gets past here and
# refuses sso-not-on in no_sso_base, which names the reason.
check_target() {
copy_exists "$TO" 2>/dev/null || refuse copy-missing "no copy $TO in $COPIES"
copy_ok "$TO" 2>/dev/null || refuse copy-unreachable "$TO is not reachable from refactor and origin/refactor"
target_schema "$TO" || refuse copy-schema-unknown "cannot read SCHEMA_VERSION and its chain from $COPIES/$TO/packages/bus/src/store.mjs"
[ "$TO" != "$P_COPY" ] || [ -n "$PROOF_HEADER" ] ||
[ "$TO" != "$P_COPY" ] || [ -n "$PROOF_HEADER" ] || [ "$NO_SSO" = 1 ] ||
{ [ "$NO_PROOF" = 1 ] && [[ " ${P_CONSOLE[*]} " == *" --proof-header "* ]]; } ||
{ [ "$NO_SEND" = 1 ] && [[ " ${P_CONSOLE[*]} " == *" --send "* ]]; } ||
{ [ "$SEND" = 1 ] && [[ " ${P_CONSOLE[*]} " != *" --send "* ]]; } ||
{ [ "$SSO" = 1 ] && [[ " ${P_CONSOLE[*]} " != *" --sso "* ]]; } ||
refuse already-active "$TO is the pointer's copy"
}
@@ -854,21 +870,17 @@ has_flag() { local f="$1"; shift; [[ " $* " == *" $f "* ]]; }
# skips sso_checks after a refusal here, so --plan lists that refusal alone.
sso_base() {
BASE_CONSOLE=("${P_CONSOLE[@]}") SSO_PLAN="" SSO_STEP="" SSO_NO_TARGET=""
if [ "$NO_SSO" = 1 ]; then no_sso_base; return 0; fi
has_flag --sso "${P_CONSOLE[@]}" || return 0
[ "$SSO" = 1 ] || SSO_PLAN="sso: carried"
[ "$OP" = rollback ] && ! sso_supported || return 0
local f="show-$CONSOLE_UNIT.txt" argv="" why="" act="" from="" set
local f="show-$CONSOLE_UNIT.txt" why="" act="" from="" set
read -r act from <<< "$(from_log arrival || true)"
set="$STATE/stack-rollback/$act"
if [ "$from" != "$TO" ]; then why="the log names no activation that moved the stack from $TO onto $P_COPY"
elif ! [[ "$act" =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[1-9])?$ ]]; then why="the activation $act that moved the stack onto $P_COPY names no rollback set"
elif [ ! -f "$set/$f" ] || [ ! -f "$set/SHA256SUMS" ]; then why="$set has no $f and SHA256SUMS"
elif [ "$(sum_of "$set" "$f")" != "$(sha256sum < "$set/$f" | cut -d' ' -f1)" ]; then why="$set/$f does not match the set's SHA256SUMS"
else
argv="$(argv_of "$(cat "$set/$f")")"
if ! unquoted "$argv" || ! serve_flags "$argv"; then why="$set/$f holds no Console argv this script can read"
elif has_flag --sso "${SERVE_FLAGS[@]}"; then why="the Console in $set/$f already ran with --sso"
fi
elif ! set_flags "$set"; then why="$SET_WHY"
elif has_flag --sso "${SERVE_FLAGS[@]}"; then why="the Console in $set/$f already ran with --sso"
fi
if [ -n "$why" ]; then
SSO_NO_TARGET=1
@@ -876,7 +888,8 @@ sso_base() {
return 0
fi
# --send and --tester-port follow the pointer, so a --no-send or a tester port set after that
# activation stays (row 189's rule that rollback carries --send off).
# activation stays (row 189's rule that rollback carries --send off). A --send carried here has
# lost its --sso, so send_for_target drops it (row 273, F39 b).
local i
BASE_CONSOLE=()
for ((i = 0; i < ${#SERVE_FLAGS[@]}; i++)); do
@@ -894,10 +907,79 @@ sso_base() {
--tester-socket) BASE_CONSOLE+=(--tester-socket) ;;
esac
done
SSO_PLAN="sso: dropped; $TO's Console does not support --sso, so the rollback takes the Console flags from $set/$f, with --send and --tester-port as the pointer has them"
SSO_PLAN="sso: dropped; $TO's Console does not support --sso, so the rollback takes the Console flags from $set/$f, with --tester-port as the pointer has it"
SSO_STEP="drop --sso; $TO's Console does not support it, so the Console flags come from $set/$f"
}
# Sets SERVE_FLAGS to the Console flags in rollback set $1's show-$CONSOLE_UNIT.txt, the Console
# unit as it was before that set's run, checked against the set's SHA256SUMS. Returns 1 with
# SET_WHY when the file is missing, fails its sum or holds no argv this script can read.
set_flags() {
local set="$1" f="show-$CONSOLE_UNIT.txt" argv
SET_WHY=""
if [ ! -f "$set/$f" ] || [ ! -f "$set/SHA256SUMS" ]; then SET_WHY="$set has no $f and SHA256SUMS"
elif [ "$(sum_of "$set" "$f")" != "$(sha256sum < "$set/$f" | cut -d' ' -f1)" ]; then SET_WHY="$set/$f does not match the set's SHA256SUMS"
else
argv="$(argv_of "$(cat "$set/$f")")"
unquoted "$argv" && serve_flags "$argv" && return 0
SET_WHY="$set/$f holds no Console argv this script can read"
fi
return 1
}
# Row 273 (#1778, mos-1246 F39 a): activate --no-sso turns --sso off. The Console flags come from the
# rollback set of the activation that turned --sso on. Each set holds the Console from before its
# run, so that activation is the newest one whose set holds a Console without --sso: every newer
# set ran with it. The walk reads the log's activations newest first, back to the adopt line, and
# refuses at the first set it can't read, since it can't tell whether that set is the one. --send
# is dropped (F39 a) and the tester follows the pointer. Sets BASE_CONSOLE, SSO_PLAN and SSO_STEP.
no_sso_base() {
local f="show-$CONSOLE_UNIT.txt" why="" act set="" found=0 i restored=""
local -a acts=()
if ! has_flag --sso "${P_CONSOLE[@]}"; then
check_refuse sso-not-on "the pointer's Console flags hold no --sso, so --no-sso has nothing to turn off"
return 0
fi
# An empty entry is an ok line with no activation; skipping it could land on an older set.
mapfile -t acts < <(from_log sets || true)
for act in "${acts[@]}"; do
set="$STATE/stack-rollback/$act"
if [ -z "$act" ]; then why="an ok line in the log names no activation"; break; fi
if ! [[ "$act" =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[1-9])?$ ]]; then why="the activation $act names no rollback set"; break; fi
set_flags "$set" || { why="$SET_WHY"; break; }
has_flag --sso "${SERVE_FLAGS[@]}" || { found=1; break; }
done
if [ -z "$why" ] && [ "$found" = 0 ]; then
if [ -z "$set" ]; then why="the log names no activation with a rollback set"
else why="the Console in $set/$f already ran with --sso, and the log names no earlier activation"
fi
fi
if [ -n "$why" ]; then
SSO_NO_TARGET=1
check_refuse sso-off-flags "the Console flags from before --sso cannot be read: $why"
return 0
fi
# A --send in the set stays here: send_for_target is the one place that drops it, so the
# restores list leaves it out.
BASE_CONSOLE=()
for ((i = 0; i < ${#SERVE_FLAGS[@]}; i++)); do
case "${SERVE_FLAGS[i]}" in
--tester-port) i=$((i + 1)) ;;
--tester-socket) ;;
*) BASE_CONSOLE+=("${SERVE_FLAGS[i]}")
[ "${SERVE_FLAGS[i]}" = --send ] || restored+="${restored:+ }${SERVE_FLAGS[i]}" ;;
esac
done
for ((i = 0; i < ${#P_CONSOLE[@]}; i++)); do
case "${P_CONSOLE[i]}" in
--tester-port) i=$((i + 1)); BASE_CONSOLE+=(--tester-port "${P_CONSOLE[i]:-}") ;;
--tester-socket) BASE_CONSOLE+=(--tester-socket) ;;
esac
done
SSO_PLAN="sso: off; Console flags from $set/$f; restores ${restored:-nothing}"
SSO_STEP="turn off --sso; the Console flags come from $set/$f"
}
# Row 200 (#1698): activate --sso drops the preview set and the proof pair, which serve refuses beside
# it, and puts --sso where webui-service.sh renders it: after the other flags, before --send and
# --tester-port. Sets SSO_DROP (the plan's dropped preview flags), PROOF_PLAN and PROOF_STEP.
@@ -1017,14 +1099,53 @@ process.exit(d!==null&&typeof d==="object"&&Object.hasOwn(d,"console")?0:1)' --
# Row 189 (mos-0972): --no-send drops --send from RUN_CONSOLE, so the unit and the pointer both lose
# it; without the flag the pointer's --send goes on unchanged. Rollback and pm-on never take it, so
# they carry the pointer as it is. Sets SEND_PLAN (the plan's send: line) and SEND_STEP.
# Row 273 (mos-1246 F39): under forward auth any same-uid process can send as the signed-in user
# through the Console's port, so --send turns send on only when the run's Console flags hold --sso,
# given or carried. --no-sso drops the pointer's --send, and so does a rollback to a Console without
# --sso.
send_for_target() {
local had=0 i
local -a without_send=()
local -a without_send=() with_send=()
for ((i = 0; i < ${#RUN_CONSOLE[@]}; i++)); do
if [ "${RUN_CONSOLE[i]}" = --send ]; then had=1; else without_send+=("${RUN_CONSOLE[i]}"); fi
done
SEND_STEP=""
if [ "$NO_SEND" = 1 ]; then
if [ "$NO_SSO" = 1 ]; then
RUN_CONSOLE=("${without_send[@]}")
if has_flag --send "${P_CONSOLE[@]}"; then
SEND_PLAN="send: dropped; --no-sso drops --send (F39)"
SEND_STEP="drop --send; --no-sso was given"
else
SEND_PLAN="send: off; --no-sso was given and the pointer holds no --send"
fi
elif has_flag --sso "${P_CONSOLE[@]}" && ! has_flag --sso "${RUN_CONSOLE[@]}"; then
# Row 273 (F39 b): a rollback to a Console without --sso drops it, and --send goes with it.
RUN_CONSOLE=("${without_send[@]}")
if [ "$had" = 1 ]; then
SEND_PLAN="send: dropped; the run drops --sso, and --send needs it (F39)"
SEND_STEP="drop --send; the run drops --sso"
else
SEND_PLAN="send: off"
fi
elif [ "$SEND" = 1 ]; then
if ! has_flag --sso "${RUN_CONSOLE[@]}"; then
SEND_PLAN="send: off"
check_refuse send-needs-sso "--send needs --sso in the run's Console flags, given or carried; under forward auth any same-uid process could send (F39)"
return 0
fi
if [ "$had" = 1 ]; then
SEND_PLAN="send: carried"
return 0
fi
# webui-service.sh renders --send after --sso, before the tester flags.
for ((i = 0; i < ${#RUN_CONSOLE[@]}; i++)); do
case "${RUN_CONSOLE[i]}" in --tester-port | --tester-socket) break ;; esac
with_send+=("${RUN_CONSOLE[i]}")
done
RUN_CONSOLE=("${with_send[@]}" --send "${RUN_CONSOLE[@]:i}")
SEND_PLAN="send: on; --send was given"
SEND_STEP="turn on --send; --send was given"
elif [ "$NO_SEND" = 1 ]; then
RUN_CONSOLE=("${without_send[@]}")
if [ "$had" = 1 ]; then
SEND_PLAN="send: dropped; --no-send was given"