Compare commits

...
Author SHA1 Message Date
jason.woltje 67640f892f morgana: row 205a build (sage-2288): trail and replies hide other users' thread messages from owner, reader and tester; hidden answers as unknown; #readable owner-only; design d2 rulings 2026-10-21 19:43:21 -05:00
jason.woltje 099a892dd8 morgana: row 205 design d1 (sage-2280): FED R3 unfiled, so split 205a message filter now and 205b viewer verbs after R3; mutant 3 to 193b2; hidden trail subject returns [] 2026-10-21 19:36:04 -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
18 changed files with 1953 additions and 40 deletions

No files matched your search

+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.
+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.
+63
View File
@@ -0,0 +1,63 @@
# Row 205a (193b1a, #1703): the trail and replies message filter
Design: DESIGN.md d2 in this directory, with rulings in section 7 (sage-2288). Base: refactor 02da6a2ae. No schema
change. Nothing has run yet: every test run waits for Sage's CLEAR.
## Changes
`packages/bus/src/broker.mjs`:
- `#hiddenMessage(s, id)`: the message's thread (`message_threads` joined to `threads`) is owned by a `user:`
other than `#owner(s)`, and `#readable(s, t)` is false. A message with no thread, or in the business human's
or the tester's thread, is never hidden. A reader session owns nothing, so every user thread is hidden from it.
The tester reads through its reader view, and `#owner` still gives `tester`.
- `#readable(s, t)` returns false. Rule 1 (a share in force) can't hold before 193b2's schema 3j, so nobody
reads another's thread. 193b2 adds the share rule and rules 2-4 here.
- `#trail`: a hidden subject returns `[]` before anything is read, exactly as an unknown id does (ruling 3).
After the rows are collected, it drops hidden `messages` rows, `deliveries` rows of a hidden message, and
`events` rows whose subject is a hidden message. Decisions, decision events, task snapshots, run events and
claims stay.
- `replies`: a hidden id fails `message-not-found`, as an unknown id does. Hidden replies are left out in SQL
(`NOT EXISTS` a thread whose owner starts with `user:` and isn't the caller), so the `REPLIES` limit counts
only visible rows.
- The SQL clause doesn't call `#readable`. That is exact while `#readable` is false. 193b2 must add the share
exception to the clause and to `#readable` together.
`packages/bus/tests/message-filter.test.mjs` (new). It writes a `user:alice` thread and its messages
straight to the store, as R3's `thread.send` and the run's `to: 'user'` reply will write them, because nothing
makes a user session before R3.
- `trail` on a hidden id equals `trail` on an unknown id, byte for byte, for the owner, a reader and the tester.
- `replies` on a hidden id equals `replies` on an unknown id (code and message), for all three.
- `trail` on a user-thread run's decision shows the decision and the owner's message, and drops the user
thread message, its delivery and its event.
- `replies` with `REPLIES` hidden replies ahead of a visible one returns the visible one.
- Messages in the business human's and the tester's threads stay visible on both verbs (row 177 control).
- The governance pin, one assertion per item in the user sentence: inbox entry, claim, task, project file and
the closed decision in `decisions {list: 'recent'}` show to the owner. `trail` and `replies` hide the user
message, and the decision's trail carries no user body.
## Mutants (each must fail its named test in message-filter.test.mjs)
| Mutant | Edit | Named test |
|---|---|---|
| M1 | `#readable` returns true | governance pin: the owner sees decisions, inbox entries, claims, tasks and project files, but not the user messages |
| M11 | the trail drop loop iterates nothing | trail on a decision keeps the decision and drops the user thread messages, their deliveries and their events |
| M12 | the replies `NOT EXISTS` clause removed | replies leaves out other users thread messages and still fills a page |
| M13 | trail fails `message-not-found` for a hidden subject | trail on a hidden message id answers exactly as on an unknown id, for the owner, a reader and the tester |
| M14 | replies returns `[]` for a hidden id | replies on a hidden message id answers exactly as on an unknown id, for the owner, a reader and the tester |
## Gate
The kit is /mnt/storage/scratch/morgana-r205 (`slot205.sh`, `mutate.py`, `busy.sh`), run as
`bash busy.sh && CAND=<sha> bash slot205.sh` on Sage's CLEAR. It runs these files whole:
- message-filter;
- the bus files that call trail or replies: replies, tester, tester-threads, tester-writes, broker, decisions,
decision-close, end-launch and task-read.
Then it runs the bus suite, then M1 and M11-M14, each on its named test and restored after each. It uses umask
022 and TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/g205XXXX`. It refuses unless HEAD is the candidate
and the tree is clean, and it stops after two unexpected failures (mos-0823).
Sage's gate adds every `scripts/test-*.sh`. The webui tests that read trail or replies run there.
Static check: `node --check` on broker.mjs and the new test, run from `/`. Both parse. All five mutant anchors
match exactly once.
+173
View File
@@ -0,0 +1,173 @@
# Row 205 (193b1, #1703): viewer verbs, board refusals and the message filter
Owner Morgana, reviewer Duckworth, gate owner Sage (sage-2280). Brief: `docs/plans/2026-10-20_human-users-build.md`,
"Brief: 193b1, viewer verbs, board refusals and the message filter" (blob a73e1bba0, rev 3088). Design:
row 193 DESIGN.md r5 (e2d161e2e, Duckworth approved), sections 4.3, 5.4, 5.5, 5.7, 8 and 9. Base: refactor
02da6a2ae. Draft 2: Sage's rulings (sage-2288) are in section 7. Sections 1-6 are d1 as sent (morgana-0063).
## 1. The dependency gap
The brief chains 193b1 on FED R3, FED R4 and row 196's verifier. Only row 196 exists (7247d80c7). On 02da6a2ae:
- R3 and R4 aren't filed. The FED brief (`docs/plans/2026-10-20_federation-r1-r2-r5.md`:12-13) files R3 "after
R1 and row 196" and R4 after R3, R5 and 193a. No `*/fed-r3` branch is on origin.
- Row 196's `verifyPrincipal` returns the `user` kind, and server.mjs accepts the `user` envelope, but
runtime.mjs wires only `authenticateHuman` and `authenticateTester`. The broker has no `bindUser`, no
`s.user`, no per-request grant read and no collaborator verb list. FED 3.6 and row R3 own all of these.
- So nothing makes a user session today, and no thread is owned by `user:<id>` except through a direct
store write.
193b1 has two halves that depend on R3 in different ways.
| Item | Needs a user session (R3) | Testable on 02da6a2ae |
|---|---|---|
| Viewer verb list (4.3) | yes: it extends R3's collaborator list | no |
| Per-grant level checks via `covers(..., {level})` | yes: R3's grant read | no |
| Board reads refused to users (5.5) | yes | no |
| `trail` and `replies` filter (5.7) | no: it filters owner, reader and tester sessions | yes, with seeded `user:<id>` threads |
| `#readable` owner-only | no | yes |
| Governance pin test (owner side) | no: the owner reads decisions, inbox, agents, tasks and `project.file` | yes, with a seeded user-thread run |
| Mutants 6 (viewer `thread.send`), 10 (user allowed `trail`) | yes | no |
| Mutants 11, 12 (trail, replies filter removed) | no | yes |
| Mutants 1, 3 (`#readable` rules 1 and 3 dropped) | see 4 | see 4 |
## 2. Recommendation: split 205 into 205a now and 205b after R3
- **205a, the message filter.** `trail` and `replies` hide messages in other users' threads from the owner,
reader sessions and the tester, with `#readable` owner-only. Plus the owner-side governance pin test and
mutants 11 and 12. No R3 dependency, no schema change.
- **205b, viewer verbs and board refusals.** Built on R3's candidate, as Q177 chains a row on its parent's
candidate. The viewer list, the level checks, users refused on board reads, the user-session half of the
pin test, mutants 6 and 10.
Why split. The filter is the item the boundary orders first: "No user thread goes live before 193b1's
message filter has landed" (mos-1021 B2, FED R8). Landing it before R3 means R3 and every later row can't
create a user thread that the owner can read on `trail`. Waiting for R3 holds the filter behind a row that
isn't filed. The other option, building all of 193b1 against R3's named interface as 193a did with R1,
doesn't work here: 193a only called R1's exported `covers`, while 193b1 would have to write `bindUser`, the
grant read and the collaborator list itself, which is R3's scope and owner.
Tradeoff: two rows and two reviews in place of one, and 205b rebases onto R3. The split is a brief change,
so it's Sage's to rule.
## 3. 205a design
### 3.1 The hidden-message check
One broker method, `#hiddenMessage(s, id)`:
```
SELECT t.owner FROM message_threads mt JOIN threads t ON t.id = mt.thread
WHERE mt.message = ? AND t.business = ?
```
A message is hidden when that owner starts with `user:`, isn't `#owner(s)`, and `#readable(s, t)` is false.
A message with no thread row, or in a thread owned by the business human or `tester`, is never hidden, so
row 177's behaviour is unchanged. A reader session has no `#owner`, so every user thread's messages are
hidden from it.
`#readable(s, t)` in 205a returns false. Before 193b2's schema 3j there is no `thread_shares` table, so rule 1
(a share in force) can't hold, and the function is false for anyone but the thread's owner (5.7: "b1 hides
every message in another user's thread"). It is written as its own method so 193b2 adds the share rule and
rules 2-4 in one place.
### 3.2 `trail`
- If `subject` is a hidden message id, return `[]`. Design 5.7 says `message-not-found`, "as an unknown id
does", but `trail` on an unknown id returns `[]` today (it has no not-found check). A different code for a
hidden id would confirm the id exists, which is what 5.7 wants to prevent, so I follow its reason over its
code. Question 3.
- After `#trail` collects its rows, drop: `messages` rows that are hidden, `deliveries` rows whose `message`
is hidden, and `events` rows whose `subject` is a hidden message id. This covers each way `#trail` reaches
a message: by id, by `decision`, and through `task.created` → `human.input` → `body.message`.
- Decision rows, decision events, task snapshots, run events and role claims stay. 5.7 puts them on the
governance side.
### 3.3 `replies`
A hidden `id` fails `message-not-found`, and hidden messages are dropped from the result. The `LIMIT`
applies before the filter today; the filter moves into the SQL (`AND NOT EXISTS` a thread owned by another
`user:`) so a page of hidden replies doesn't return short. For 205a that SQL form is exact because
`#readable` is false; 193b2 adds the share exception to the same clause.
### 3.4 Tests (packages/bus/tests/message-filter.test.mjs, new)
Fixtures seed a project, a thread owned by `user:alice` with messages in `message_threads`, a delivery and an
event on one of them, and a decision raised by that thread's run that cites the message. Sessions: the
business human, a reader session and the tester.
- `trail` on a hidden message id: `[]` for all three sessions, the same as an unknown id.
- `trail` on the decision: the decision row and its events show; the user's message, its delivery and its
events don't.
- `trail` on a human-owned thread's message: unchanged (row 177 control).
- `replies` on a hidden id: `message-not-found`. `replies` on a visible id with hidden and visible replies:
only the visible ones, and a full page when hidden replies come first.
- Governance pin, owner side: for the user-thread run, `decisions {list: 'recent'}`, `inbox`, `agents`,
`tasks` and `project.file` each show its item, and `trail` and `replies` hide the user's message body.
Each item in the user sentence is one named assertion.
### 3.5 Mutants
| Mutant | Edit | Named test |
|---|---|---|
| 11 | the `trail` filter removed | trail on the decision hides the user's message, delivery and events |
| 12 | the `replies` filter removed | replies hides other users' thread messages |
| 11b | the hidden-subject early return removed | trail on a hidden message id returns [] |
| 1 | `#readable` returns true | governance pin: trail and replies hide the user body |
## 4. Mutants 1 and 3
Design section 8 lists mutants 1 (rule 1 dropped) and 3 (rule 3 dropped) for b1. With no shares table,
rule 1 is always false, so `#readable` is false whatever rules 2-4 say. Dropping rule 3 changes nothing a
test can see until 193b2 adds shares, and rules 2 and 3 need R3's grant read. So:
- Mutant 1 in 205a is "`#readable` returns true", killed by the pin test above.
- Mutant 3 moves to 193b2, where rule 1 can hold and rule 3 decides.
This is a second brief change for Sage to rule on.
## 5. Gate (Sage's slot, on CLEAR only)
The brief's gate: Duckworth approves the manifest; the bus suite and every `scripts/test-*.sh` green on
Sage's gate; the mutants killed. The new test file whole, the touched existing files whole, then the bus
suite, then each mutant on its named test with a restore check. umask 022, TMPDIR from
`mktemp -d /mnt/storage/scratch/tmp/gXXXXXX`. No test touches the live data root, config, units, slot
files, any live access.json or `~/.mosaic`.
## 6. Questions for Sage
1. Split 205 into 205a (filter, now) and 205b (viewer verbs and board refusals, after R3's candidate)?
2. Mutant 1 as "`#readable` returns true" in 205a, and mutant 3 moved to 193b2?
3. A hidden `trail` subject returns `[]`, the same as an unknown id, in place of 5.7's `message-not-found`
(3.2)? `replies` already fails `message-not-found` on an unknown id, so there the code stays.
## 7. Rulings (sage-2288, re morgana-0063)
1. **Split approved.** 205a is built now: the 5.7 `trail` and `replies` filter over owner, reader and tester
sessions, `#readable` owner-only, the owner-side governance pin test, and mutants 11 and 12. 205b comes
after R3's candidate and R4: viewer verbs, `covers` level checks, board reads refused to users, the
user-side pin, and mutants 6 and 10. 205a's base is origin/refactor 02da6a2ae or later. R3 builds on
205a's candidate and extends the filter to user sessions, so the filter isn't written twice.
2. **Mutant 1** in 205a is "`#readable` returns true", killed by the pin test. **Mutant 3** moves to 193b2
(row 206). Sage changes the brief to match.
3. **Hidden equals unknown.** A hidden `trail` subject returns `[]`, exactly as an unknown id does. `replies`
keeps `message-not-found` for both. The point of 5.7 is that a caller can't tell hidden from unknown.
So 205a adds:
- one test per verb comparing the hidden response with the unknown-id response byte for byte
(`JSON.stringify` of the result, or of the error's code and message), for each of the three sessions;
- a mutant M13 that makes the hidden path distinguishable: `trail` fails `message-not-found` for a
hidden subject. The `trail` equality test must kill it. M14 does the same for `replies`: a hidden id
returns `[]` instead of failing. The `replies` equality test must kill it.
### 7.1 205a mutant table (replaces 3.5)
| Mutant | Edit | Named test |
|---|---|---|
| M1 (design 1) | `#readable` returns true | governance pin: the owner sees decisions, inbox entries, claims, tasks and project files, but not the user's messages |
| M11 (design 11) | the `trail` filter of hidden rows removed | trail on the decision hides the user's message, its delivery and its events |
| M12 (design 12) | the `replies` filter removed | replies drops other users' thread messages and still fills a page |
| M13 | `trail` fails `message-not-found` for a hidden subject | trail on a hidden id answers exactly as on an unknown id |
| M14 | `replies` returns `[]` for a hidden id | replies on a hidden id answers exactly as on an unknown id |
Queue: Sage re-pins 205 to the split brief and adds 205b and the R3 row at the next gap. I claim after
that. No run before Sage's CLEAR.
+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
@@ -0,0 +1,93 @@
# Plan: Q175 phase 2 window, the live Console switches to --sso
Sage, 2026-10-22. Draft 1 for Mos. 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 and mos-1197. Rows 192 (phase 1), 200 (`activate --sso`) and
its N1 follow-up have landed. Vikunja #412, Gitea #385.
## When
After the 10-24 re-pin is verified, and only when Jason starts the window (mos-1197). Nothing below runs
before his start. The copy pinned at 10-24 already carries phase 1, so its loader accepts a config with or
without `console`.
## Who
- Sage or Darkwing runs the agent steps under Q173: backup first, STATE report after.
- Mos or Jason starts and restarts the Console.
- Jason signs in, sends and signs out.
- The config.json writes are Jason's or, on his window start, an agent's for the `console` key only
(design section 10 step 4). config.json is user-authored, so Q173 doesn't cover these writes. Jason's go
has to name them, and the window doesn't start without it.
## 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.
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 the 10-24 copy, or be shown never to re-read config. Step 4 waits until the list is clean.
4. Config write 1 (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 <10-24 copy> mosaic-stack --sso --plan --expect-from <10-24 copy>`.
Then the same command without `--plan`, the same way earlier re-pins handled the Console. `--send`
carries over from the pointer, since nothing passes `--no-send` (mos-1195). The tester is the private
socket from row 196 (`--tester-socket`), not design section 5's `--tester-port 7333`. The `--plan` output
names every flag it drops (`--auth-header`, `--allow-user`, any proof pair) 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
(decision 102). 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
and checks that `iss=` is the issuer above. The sub goes into the window record, not into any message.
8. Config write 2 (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): an agent POSTs one Console write to `127.0.0.1:7332` with no cookie
and no auth header. Expect 401 `sign-in required`, and the bus event count stays unchanged. 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.
- Two failures in the window: stop and report to Mos (mos-0823).
## Rollback
1. Restore config.json from the latest backup, which removes `console` (or holds the placeholder state).
2. Then roll the pointer back with `scripts/stack-release.sh rollback` (previous distinct copy). An older
loader refuses a config that holds the key, so rolling back in the other order stops the bus and seats.
3. STATE after a rollback reads config.json and confirms the key is absent when the target copy predates
phase 1.
## Open for Mos
- Does Jason's window start cover both config writes (steps 4 and 8) by an agent, or does he write them?
- Does the placeholder sub in step 4 work for Mos? The only alternative is Jason giving his sub before
the window, and A8 chose the log line instead.
- Under step 10, is a loopback POST with no cookie to the live Console within Q173?
+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,
+12 -8
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 3091 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,7 +241,7 @@ 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 | — |
| 205 | 193b1: viewer verbs, board refusals and the message filter (#1691; also after FED R3 and R4 when filed) | 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: 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) | — |
| 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 | — |
@@ -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 | briefed | 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,15 @@ 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 | — |
<!-- mosaic-queue:end -->
+1151 -29
View File
File diff suppressed because it is too large. Load diff
+36 -1
View File
@@ -463,6 +463,22 @@ export class Broker {
#owner(s) {
return s.tester ? 'tester' : s.human;
}
// Row 205 (193b1a, #1703, row 193 design 5.7): a message in a thread a user other than the caller
// owns is hidden from trail and replies unless #readable holds. The business human's and the
// tester's threads are never hidden, so row 177 is unchanged. A reader owns nothing.
#hiddenMessage(s, id) {
const t = this.#store.get(
'SELECT t.* FROM message_threads mt JOIN threads t ON t.id=mt.thread WHERE mt.message=? AND t.business=?',
id,
s.business,
);
return Boolean(t) && t.owner.startsWith('user:') && t.owner !== this.#owner(s) && !this.#readable(s, t);
}
// Row 193 design 5.4. Rule 1 needs a share in force, and there is no thread_shares table before
// 193b2 (schema 3j), so nobody reads another's thread yet. 193b2 adds the share rule and rules 2-4.
#readable(s, t) {
return false;
}
#agent(s) {
if (s.tester) fail('tester-refused');
if (s.reader) fail('read-only');
@@ -3562,10 +3578,15 @@ export class Broker {
identifier(a.id);
if (!this.#store.get('SELECT id FROM messages WHERE id=? AND business=?', a.id, s.business))
fail('message-not-found');
// Row 205 (design 5.7): a hidden id fails as an unknown one does. Hidden replies are left out
// in the query, so a page of them doesn't come back short; #readable is false until 193b2,
// which adds the share exception to this clause too.
if (this.#hiddenMessage(s, a.id)) fail('message-not-found');
return this.#store.all(
`SELECT * FROM messages WHERE business=? AND in_reply_to=? ORDER BY seq LIMIT ${REPLIES}`,
`SELECT * FROM messages m WHERE business=? AND in_reply_to=? AND NOT EXISTS(SELECT 1 FROM message_threads mt JOIN threads t ON t.id=mt.thread WHERE mt.message=m.id AND substr(t.owner,1,5)='user:' AND t.owner IS NOT ?) ORDER BY seq LIMIT ${REPLIES}`,
s.business,
a.id,
this.#owner(s) || null,
);
}
default:
@@ -3814,6 +3835,8 @@ export class Broker {
for (const m of rows) this.#delivery(m.id, 'routed', h);
}
#trail(s, subject) {
// Row 205 (design 5.7, sage-2288): a hidden subject answers exactly as an unknown id does.
if (this.#hiddenMessage(s, subject)) return [];
const rows = new Map();
const add = (table, list) => {
for (const row of list) {
@@ -3895,6 +3918,18 @@ export class Broker {
this.#store.all('SELECT * FROM role_claims WHERE business=? AND holder_run=?', s.business, run),
);
}
// Row 205 (design 5.7): drop hidden messages, their deliveries and the events about them.
// Decision rows and their events stay: the run's governance is the owner's to see.
const hidden = new Set(
[...rows.values()].filter((r) => r.table === 'messages' && this.#hiddenMessage(s, r.id)).map((r) => r.id),
);
for (const [key, r] of rows)
if (
(r.table === 'messages' && hidden.has(r.id)) ||
(r.table === 'deliveries' && hidden.has(r.message)) ||
(r.table === 'events' && hidden.has(r.subject))
)
rows.delete(key);
return [...rows.values()].sort(
(a, b) => a.at.localeCompare(b.at) || a.table.localeCompare(b.table) || a.seq - b.seq,
);
+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');
});
+189
View File
@@ -0,0 +1,189 @@
// Row 205 (193b1a, #1703, row 193 design 5.7, sage-2288): a user's messages are private from the
// business owner, reader sessions and the tester on trail and replies; the run's governance is not.
// Nothing makes a user session before FED R3, so a user's thread and its messages are written to the
// store as R3's thread.send and the run's reply will write them.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { Store } from '../src/store.mjs';
import { Broker, REPLIES } from '../src/broker.mjs';
import { fixture } from './spec-fixtures.mjs';
const AT = '2026-10-21T00:00:00.000Z';
const within = (grants) => ({ authority: { withinRole: grants, crossRole: [] } });
const options = [{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }];
// A thread owned by `owner`, on the business's assistant instance.
function threadOf(store, project, owner, name) {
const id = randomUUID();
store.run(
'INSERT INTO threads(id,at,business,project,instance,owner,name) VALUES(?,?,?,?,?,?,?)',
id, AT, 'demo', project, 'assistant', owner, name,
);
return id;
}
// A message with its thread row, its routed delivery and one event about it.
function put(store, { thread = null, from_role, from_run, to_role, in_reply_to = null, decision = null, body }) {
const id = randomUUID();
store.run(
'INSERT INTO messages(id,at,business,from_role,from_run,to_role,class,in_reply_to,decision,corrects,body) VALUES(?,?,?,?,?,?,?,?,?,?,?)',
id, AT, 'demo', from_role, from_run, to_role, 'INFO', in_reply_to, decision, null, body,
);
if (thread) store.run('INSERT INTO message_threads(message,thread) VALUES(?,?)', id, thread);
store.run('INSERT INTO deliveries(message,at,op,holder_run,transport,address) VALUES(?,?,?,?,?,?)', id, AT, 'routed', null, null, null);
store.run(
'INSERT INTO events(id,at,business,kind,actor_role,actor_run,subject,body) VALUES(?,?,?,?,?,?,?,?)',
randomUUID(), AT, 'demo', 'action.allowed', from_role, from_run, id, '{}',
);
return id;
}
// From alice to the instance, as R3's thread.send writes it.
const fromAlice = (store, thread, body, extra = {}) =>
put(store, { thread, from_role: 'user', from_run: 'user:alice', to_role: 'assistant', body, ...extra });
// The run's reply to alice, as R3's `to: 'user'` path writes it.
const toAlice = (store, thread, body, extra = {}) =>
put(store, { thread, from_role: 'assistant', from_run: 'assistant-run', to_role: 'user', body, ...extra });
function setup(t) {
const root = mkdtempSync(join(tmpdir(), 'bus-message-filter-'));
const store = new Store(root);
t.after(() => {
store.close();
rmSync(root, { recursive: true, force: true });
});
const b = new Broker({
store,
businesses: {
demo: {
id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, vars: { 'bus.tester': true },
roles: { chief: { definition: 'chief-of-staff', ...within(['message.send']) }, assistant: { definition: 'assistant', ...within(['message.send']) } },
},
},
});
const caps = {
human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }),
reader: b.bindReader({ business: 'demo' }),
tester: b.bindTester({ business: 'demo' }),
};
const call = (who, verb, args = {}) => b.request(caps[who], { verb, args });
const project = call('human', 'project.create', { name: 'shared work' }).id;
const U = threadOf(store, project, 'user:alice', 'alice');
return { b, store, caps, call, project, U };
}
const sessions = ['human', 'reader', 'tester'];
// What a caller sees of a call: its result, or its refusal's code and message.
const answer = (fn) => {
try {
return JSON.stringify({ result: fn() });
} catch (e) {
return JSON.stringify({ code: e.code, message: e.message });
}
};
test('trail on a hidden message id answers exactly as on an unknown id, for the owner, a reader and the tester', (t) => {
const { store, call, U } = setup(t);
const A = fromAlice(store, U, 'alice: my salary is private');
const unknown = randomUUID();
for (const who of sessions) {
const hidden = answer(() => call(who, 'trail', { subject: A }));
assert.equal(hidden, answer(() => call(who, 'trail', { subject: unknown })), who);
assert.equal(hidden, JSON.stringify({ result: [] }), who);
}
});
test('replies on a hidden message id answers exactly as on an unknown id, for the owner, a reader and the tester', (t) => {
const { store, call, U } = setup(t);
const A = fromAlice(store, U, 'alice: hello');
toAlice(store, U, 'hello alice', { in_reply_to: A });
const unknown = randomUUID();
for (const who of sessions) {
const hidden = answer(() => call(who, 'replies', { id: A }));
assert.equal(hidden, answer(() => call(who, 'replies', { id: unknown })), who);
assert.equal(JSON.parse(hidden).code, 'message-not-found', who);
}
});
test('trail on a decision keeps the decision and drops the user thread messages, their deliveries and their events', (t) => {
const { b, store, call, U } = setup(t);
const run = b.bindLaunch({ business: 'demo', role: 'assistant', run: 'assistant-run', harness: 'pi', thread: U });
b.request(run, { verb: 'role.claim' });
const d = b.request(run, {
verb: 'decision.raise',
args: { action: 'deploy', question: 'Ship it?', options, recommendation: 'yes', blocking: false },
}).id;
const hidden = toAlice(store, U, 'alice, I asked the owner about shipping', { decision: d });
const shown = put(store, { from_role: 'assistant', from_run: 'assistant-run', to_role: 'human', body: 'Ship it, owner?', decision: d });
for (const who of sessions) {
const rows = call(who, 'trail', { subject: d });
assert.ok(rows.some((r) => r.table === 'decisions' && r.id === d), `${who}: the decision row shows`);
assert.ok(rows.some((r) => r.table === 'messages' && r.id === shown), `${who}: the owner's message shows`);
assert.ok(rows.some((r) => r.table === 'deliveries' && r.message === shown), `${who}: its delivery shows`);
assert.ok(rows.some((r) => r.table === 'events' && r.subject === shown), `${who}: its event shows`);
assert.equal(rows.filter((r) => r.table === 'messages' && r.id === hidden).length, 0, `${who}: no hidden message`);
assert.equal(rows.filter((r) => r.table === 'deliveries' && r.message === hidden).length, 0, `${who}: no hidden delivery`);
assert.equal(rows.filter((r) => r.table === 'events' && r.subject === hidden).length, 0, `${who}: no hidden event`);
assert.equal(JSON.stringify(rows).includes('alice, I asked'), false, `${who}: the body appears nowhere`);
}
});
test('replies leaves out other users thread messages and still fills a page', (t) => {
const { store, call, U } = setup(t);
const P = put(store, { from_role: 'human', from_run: 'human-cli', to_role: 'chief', body: 'Status, everyone?' });
for (let i = 0; i < REPLIES; i++) toAlice(store, U, `hidden ${i}`, { in_reply_to: P });
const shown = put(store, { from_role: 'chief', from_run: 'chief-run', to_role: 'human', body: 'All green.', in_reply_to: P });
for (const who of sessions) assert.deepEqual(call(who, 'replies', { id: P }).map((m) => m.id), [shown], who);
});
test('messages in the business human and tester threads are not hidden (row 177 unchanged)', (t) => {
const { store, call, project } = setup(t);
const H = threadOf(store, project, 'jason', 'mine');
const T = threadOf(store, project, 'tester', 'probe');
const h = put(store, { thread: H, from_role: 'human', from_run: 'human-cli', to_role: 'assistant', body: 'from jason' });
const x = put(store, { thread: T, from_role: 'tester', from_run: 'tester-cli', to_role: 'assistant', body: 'from tester' });
const hr = put(store, { thread: H, from_role: 'assistant', from_run: 'r', to_role: 'human', body: 'to jason', in_reply_to: h });
const xr = put(store, { thread: T, from_role: 'assistant', from_run: 'r', to_role: 'tester', body: 'to tester', in_reply_to: x });
for (const who of sessions) {
for (const id of [h, x]) assert.ok(call(who, 'trail', { subject: id }).some((r) => r.table === 'messages' && r.id === id), `${who} ${id}`);
assert.deepEqual(call(who, 'replies', { id: h }).map((m) => m.id), [hr], who);
assert.deepEqual(call(who, 'replies', { id: x }).map((m) => m.id), [xr], who);
}
});
// Row 193's user sentence, one assertion per item: "The business owner sees your thread's agent's
// decisions, inbox entries, claims, tasks and project files, but not your messages."
test('governance pin: the owner sees decisions, inbox entries, claims, tasks and project files, but not the user messages', async (t) => {
const f = await fixture(t, { roles: { assistant: { definition: 'assistant', ...within(['message.send', 'record.write']) } } });
const project = (await f.call(f.human, 'project.create', { name: 'alice work' })).id;
const U = threadOf(f.store, project, 'user:alice', 'alice');
const run = f.broker.bindLaunch({ business: 'demo', role: 'assistant', run: 'assistant-run', harness: 'pi', thread: U });
await f.call(run, 'role.claim');
const A = fromAlice(f.store, U, 'alice: please write up my plan');
toAlice(f.store, U, 'alice, done', { in_reply_to: A });
await f.call(run, 'record.write', { project: 'stack', path: 'docs/alice-plan.md', record: '# Plan\n' });
const d = (await f.call(run, 'decision.raise', { action: 'deploy', question: 'Ship the plan?', options, recommendation: 'yes', blocking: false })).id;
f.store.run(
'INSERT INTO task_snapshots(at,business,task_ref,updated,digest,fields,source,via,read_at,role,run) VALUES(?,?,?,?,?,?,?,?,?,?,?)',
AT, 'demo', 'vikunja:3/47', AT, 'a'.repeat(64), JSON.stringify({ bucket: 1, done: false }), 'self', null, null, 'assistant', 'assistant-run',
);
const inbox = await f.call(f.human, 'inbox');
assert.ok(inbox.some((x) => x.id === d), 'inbox entries: the run raise is in the owner inbox');
const agents = await f.call(f.human, 'agents');
assert.ok(agents.some((c) => c.role === 'assistant' && c.holder_run === 'assistant-run'), 'claims: the run claim shows');
const tasks = await f.call(f.human, 'tasks');
assert.ok(tasks.some((x) => x.task_ref === 'vikunja:3/47' && x.run === 'assistant-run'), 'tasks: the run task write shows');
const file = await f.call(f.human, 'project.file', { project: 'stack', path: 'docs/alice-plan.md' });
assert.equal(file.content, '# Plan\n', 'project files: the run record shows');
await f.call(f.human, 'decision.resolve', { id: d, choice: 'yes' });
const recent = await f.call(f.human, 'decisions', { list: 'recent' });
assert.ok(recent.some((x) => x.id === d), 'decisions: the closed raise shows in recent');
assert.deepEqual(await f.call(f.human, 'trail', { subject: A }), [], 'messages: trail hides the user message');
await assert.rejects(Promise.resolve().then(() => f.call(f.human, 'replies', { id: A })), { code: 'message-not-found' }, 'messages: replies hides it');
const trail = await f.call(f.human, 'trail', { subject: d });
assert.ok(trail.some((r) => r.table === 'decisions' && r.id === d), 'decisions: the trail keeps the decision');
assert.equal(JSON.stringify(trail).includes('alice'), false, 'messages: no user body on the decision trail');
});
+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');
});