plans: Case 2 slice row briefs plus rows A and B, from Gosalyn's f325657c7 on gosalyn/case2-design (#416, design r8, C2-13 copy id and profile asks for row 195, C2-20 count-only note, row A ZAI_API_KEY per mos-1086/mos-1089, row B #428 after 10-27)

This commit is contained in:
jason.woltje committed 2026-10-20 15:41:39 -05:00
1 parent 628e2ca5cd
commit 49d1f3a231
1 file changed
+933
+933
View File
@@ -0,0 +1,933 @@
# Case 2 slice rows: the creative writing hub (#416, T2)
Gosalyn, 2026-10-20 (from `date -u`), for sage-1329. Every section follows `docs/plans/BRIEF-TEMPLATE.md`.
The design they build is `agents/gosalyn/work/case2/DESIGN.md` on `gosalyn/case2-design`. Webby
approved r5 at e1635308b in webby-0021, with notes at dba33aaf6. r6 and r7 change only sections 9 and 10
and C2-13's row (mos-1057, Q183, webby-0023), and that diff is with Webby. Below, "the design" means that
file, with its section number. A builder follows the design and doesn't reopen a decision in it. A
departure goes to Sage before the build continues.
These are the slice rows from design 9.2, plus C2-11a, which the design starts in the first wave. The
other rows (C2-02, C2-03b, C2-09b, C2-10c, C2-11b, C2-15 to C2-19) get briefs when a seat is free for
them. Owners and models come from design section 8 (Q177, Q178). Where the design names only a model,
Sage picks the seat. The reviewers are my proposals, and Sage can change any of them with `queue set`.
Sage owns every gate.
Base: `refactor` at ae3c36f24. The design cites 9ae8c73ac. `git diff --stat 9ae8c73ac ae3c36f24 --
packages adapters roles scripts` touches only `scripts/stack-release.sh` and its test, and no cited line
is in either.
Order (design 8, "Parallel waves"):
1. Start now: C2-01, C2-08, C2-11a.
2. On C2-01's candidate: C2-04, C2-05a, C2-06, C2-12. On C2-08's: C2-09a, C2-10a, C2-20.
3. Then C2-03a and C2-07. Then C2-05b, C2-10b and C2-13.
4. Last: C2-14.
A row whose parent hasn't landed builds on the parent's candidate and rebases onto its landing (Q177).
Rules every row keeps:
- No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, a live unit, a slot file, Melanie's books
repo, git.mosaicstack.dev or any Discord server. Tests use scratch roots, scratch config dirs and a
local bare repo the test builds in her layout's shape: spaces and capitals in folder names, a `.canvas`
file and an unmapped top-level note.
- No row reads her text. Fixtures are made up. A marker string in a fixture body stands in for her
text wherever a test checks that output holds none.
- No credential value in any output, test, log or message (CONSTITUTION gate 13). Tests use a fixture
token file with a made-up value.
- No live stack change. Nothing here re-pins, restarts or activates anything. C2-13's run on the host
needs Jason's go (design 10).
- Before in-review, grep `packages/*/tests` and `scripts/test-*.sh` for each changed path and for any
64-hex pin of it, and run every changed or touched test file on its own. Suites need Sage's slot.
- A refusal code named in the design is the code the row ships. A new code goes to Sage first.
---
## C2-01 packages/library: mapping, scope, reader, resolver, R-rules, renderItems, survey
### Problem
Nothing in the stack reads a writing repo by a mapping of folders to books, types, canon and playground.
`packages/project/src/read.mjs:19-26` reads blobs from a record repo but accepts executables (`:21`) and
knows no scope. Design 1.1 to 1.3 and 7.4 set out what Case 2 needs, and every later row builds on it.
### Owner and reviewer
- Owner: Gosalyn, Opus 5.5.
- Reviewer: Webby, who reviewed the design.
- After: none.
### Files owned
- `packages/library/` (new): `package.json`, `src/mapping.mjs`, `src/scope.mjs`, `src/reader.mjs`,
`src/links.mjs`, `src/problems.mjs`, `src/render.mjs`, `src/survey.mjs`, `src/index.mjs`, `tests/*.test.mjs`,
`tests/fixtures/`, `README.md`.
- `scripts/stack-copy.sh`: add `packages/library/src` to `COPY_PATHS`. Without it the live copy has no
library code, and no test catches the gap today.
- `packages/cli/tests/stack-copy.test.mjs`: add `packages/library/src/index.mjs` to the list at `:37`.
- `scripts/mosaic` or `packages/cli/src`: only the dispatch line for `library survey`.
### What ships
- The mapping schema of design 1.1, including `library-playground-outside-book` and the rule that no
playground prefix holds another. Business file loading wires it in C2-04, so here it is a pure
validator.
- The scope rule of design 1.2. Only `.md` files are playground or canon.
- The commit reader of design 1.3: `git ls-tree -r -z` and `cat-file` on a commit, never a working tree,
through the same `git()` and `gitEnv()` as `packages/project`. R1 to R7 as problems with path and line.
- The link resolver, in the order 1.3 gives: repo root, `vaultPrefix/` stripped, relative to the linking
file, then shortest path. `appears_in` and `relationships` wikilinks are read as links.
- `renderItems(body)` (7.4), one pure function for the item kinds and the three notices.
- `library survey <repo> [--mapping <file>]`, which prints folder names, file counts, front matter key
sets and link-form counts, and never text.
Tests: one case per R-rule and per resolver step. A link into another repo resolves to nothing. An
`appears_in` wikilink is indexed. `renderItems` has a snapshot for each kind and each notice. The survey
prints names, counts, key sets and link-form counts, and a marker string in a fixture body never
appears in its output. The stack-copy test lists the new path. The library tests and
`packages/cli/tests/stack-copy.test.mjs` pass.
### Out of scope
Business file loading and consumer skips (C2-04), sync (C2-03a), the index and queries (C2-05a), the
guard (C2-06). The survey run on the host is design 10 step 7, after the token slot exists.
### Gate
Webby approves the manifest. The library tests and the cli suite are green on Sage's gate, along with
every `scripts/test-*.sh`.
---
## C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file
### Problem
The stack has no role that may only read and answer in cited items, no way to carry Bean's and Chet's
personas, and several paths by which a non-human actor could change a creative instance (design 4, B3).
Both adapters pass the whole prompt on the command line (`adapters/claude/adapter.sh:79`, `:82`,
`adapters/pi/adapter.sh:106`, `:121`), so any local user can read it in `/proc/<pid>/cmdline`.
### Owner and reviewer
- Owner: an Opus 5.5 seat that Sage picks.
- Reviewer: Webby.
- After: none.
### Files owned
- `roles/companion.json`, `.md`, `roles/ideas.*`, `roles/research.*`, `roles/proofreader.*`,
`roles/fact-checker.*`, `roles/continuity.*` (new).
- `packages/business/src/vocabulary.mjs`: `THREAD_DEFINITIONS`, `END_PROTECTED` as `THREAD_DEFINITIONS`,
`CREATIVE_DEFINITIONS`, `CREATIVE_KINDS`, `CREATIVE_TOOLS`.
- `packages/business/src/role.mjs` (M1), `vars.mjs` (`persona.name`, `persona.sha256` in `REGISTRY`),
`launch-settings.mjs` (the `instances` and `models` checks and the `setRoutes` branch),
`instance-settings.mjs` (the creative branch).
- `packages/bus/src/broker.mjs`: M6 `decision-not-allowed` only.
- `packages/harness/src/bundle.mjs`: the persona file read and checks, the fenced block in `prompt()`, and
the `webPlan` refusal.
- `packages/harness/src/tools.mjs`: `raise_decision` left out for creative definitions.
- `adapters/claude/adapter.sh`, `adapters/pi/adapter.sh`, `adapters/README.md` (`:89`, `:107`).
- Tests in `packages/business/tests`, `packages/bus/tests`, `packages/harness/tests`.
### What ships
Design 4, 5.1 (M1, M2, M6, M10) and 5.3:
- The six role files, version 2, with the tools, authority, messaging and network of the design 4 table.
- The constants. `THREAD_DEFINITIONS` replaces `THREAD_DEFINITION` only where it means "may hold a
thread", and that use is C2-09a's. The `assistants` lines at `launch-settings.mjs:140`, `:186` and
`:191`, and `tools.mjs:186`, stay `THREAD_DEFINITION`.
- The persona file at `<configDir>/personas/<business>/<instance>.md`, read with `O_NOFOLLOW`. The
bundle refuses a non-regular file, another uid's, any group or other bit, more than 8,000 characters,
a digest off the var (`persona-mismatch`), and `</persona-file>` or a line starting `# ` or `## `
(`persona-invalid`). The block goes between the contract and "## This session", fenced as in design 4.
- One actor rule on all five write paths (design 4): a write that touches a `CREATIVE_DEFINITIONS`
instance is accepted only when its actor is the business file's `human`, otherwise
`organization-creative-human-only`. The `instances` key of `limits.set` and `organization.set` refuses
a creative instance for every actor with `organization-creative-instance`.
- Both adapters pass the prompt file, not its text. Claude uses `--system-prompt-file`. Pi gets the path
as `--system-prompt`, and its adapter checks `[ -r ]` first and refuses otherwise, because pi 0.85.1
uses the path string itself as the prompt when the file is missing or unreadable
(`resource-loader.js:21`, `:24-27`, `:30`).
Tests, all named in the design 8 C2-08 row:
- Role, gate and messaging tests. A creative raise is refused and no decision row appears.
`harness-builtin` is refused for `research` and `companion`. `scripts/test-config.sh` passes.
- Persona vars are refused at other layers, when malformed and on a non-creative definition. Each
persona file refusal above has a test. A prompt snapshot pins contract, persona, session, in that
order.
- M10:
- Each path refuses with the file unchanged, and the human's `instances.set` and `routes.set` on
`bean` work.
- The human's `limits.set` change to Bean's `launch.models` key works, next to the chief of staff's
`organization.set` refusal of the same change.
- Tester cases run through the broker with a bound tester session, and chief of staff cases with a
role session.
- A mutant without the `models` check fails.
- No `instances` read, `changes.jsonl` line or adapter argv holds a marker string from a fixture
persona.
- Adapter tests in `claude-session.test.mjs` and `pi-session.test.mjs`: argv holds the prompt path and no
marker from the prompt. Pi's adapter refuses a missing prompt file and an unreadable one.
- Business file, `.history` and `changes.jsonl` modes after an `instances.set` write.
### Out of scope
Thread creation per instance and the launcher (C2-09a), the lookup tools (C2-05b), the cited-reply guard
(C2-06). Checking the host's claude for `--system-prompt-file` is C2-13's precheck. If the C2-20 dry run
finds heading lines in a real persona, the fence rule changes in a follow-up to this row, reviewed,
before the import (design 9.1).
### Gate
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.
---
## C2-11a editor wikilink node, after measuring whether [[ round-trips
### Problem
The Console's editor opens a file in visual mode only if `roundTrip(text) === text`
(`packages/webui/src/public/bus.js:919-920`). Its commonmark rules (`packages/webui/editor/model.mjs:119-120`)
have no wikilink rule, and nobody has measured whether the serializer escapes `[[`. If it does, every
note with a wikilink opens in Source.
### Owner and reviewer
- Owner: Rocko, gpt-6.1-sol.
- Reviewer: Filbert.
- After: none.
### Files owned
`packages/webui/editor/model.mjs`, the editor's bundle output if the build commits one, and
`packages/webui/tests/` editor test files.
### What ships
First a test that measures today's behaviour on `[[a]]`, `[[a|b]]`, `[[a#h]]`, in a list item, and next to
front matter, with the result recorded in the manifest. Then a wikilink node that keeps each form byte
for byte. `roundTrip` tests cover plain, labelled, heading, in a list, and next to front matter.
### Out of scope
Library pages and saving (C2-11b), link clicking to open a target (C2-11b), any `documentPath` change.
### Gate
Filbert approves the manifest. The webui suite is green on Sage's gate, along with every
`scripts/test-*.sh`.
---
## C2-04 the library entry in the business file, consumer skips, M9, mirror, library.file and library.status
### Problem
The business file accepts only `root` and `repo` per project (`packages/business/src/business.mjs:186-187`),
and several consumers assume every project has a root (design 2.1). Nothing stops an organization write
from changing `projects`, and nothing creates a mirror.
### Owner and reviewer
- Owner: Rocko, gpt-6.1-sol.
- Reviewer: Filbert.
- After: C2-01.
### Files owned
- `packages/business/src/business.mjs`, `organization.mjs` (M9 in `organizationWriter.write`, `:177`),
`project.mjs`, `cli.mjs`, `resolve.mjs`, `instance-settings.mjs` (only the skip at `:142`).
- `packages/seat/src/checkout.mjs` (`:310`, `:319`), `packages/cli/src/launcher.mjs` (`:353`).
- `packages/project/src/paths.mjs` (the overlap loop at `:41-44`, and the mirror path).
- `packages/bus/src/broker.mjs`: `library.file` and `library.status` only.
- `packages/webui/src/cli.mjs`: dispatch lines for the two verbs.
- Tests in the matching `tests/` directories.
### What ships
The `kind: "library"` entry of design 1.1, validated by C2-01's schema. Each consumer in design 2.1
skips it, with its own test. M9: `organization-projects-protected` for any organization write whose
`projects` differ from the file's in canonical JSON. The bare mirror is created at the `projectRepo` path
with no remote in its config. `library.file` (read at the served commit) and `library.status` (reads the
sync state, empty until C2-03a) are human only, refused from an agent session with `human-required`
(M4).
Tests: business file tests. One test per consumer skip. A launcher test that the workspace has no clone.
The M9 test calls `write` directly, once with a changed playground prefix and once with a changed hybrid
entry, and the file and `changes.jsonl` are unchanged. Each verb from an agent session refuses.
### Out of scope
Fetching (C2-03a). Bus writes (C2-02). The Console's library pages (C2-11b).
### Gate
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`.
---
## C2-05a lookup index and queries
### Problem
Agents need typed lookups over her repo that keep universes apart and default to canon (design 3). The
reader from C2-01 has no index or query layer.
### Owner and reviewer
- Owner: a Haiku 5.5 subagent at high effort, dispatched by Sage (lead decision 129).
- Reviewer: Filbert.
- After: C2-01.
### Files owned
`packages/library/src/index-build.mjs`, `packages/library/src/query.mjs`, their exports in
`packages/library/src/index.mjs`, and `packages/library/tests/` query test files.
### What ships
An index per commit (types, names, links, backlinks), cached in memory by commit id. Queries for find, get,
search, manuscript and universe, with the arguments and result shapes in the design 3 table. `universe`
is required. Results never leave it, except a hybrid's declared sources minus exclusions. Every result
carries `scope`. Unmapped files never appear. `links_out` and `links_in` drop other-universe and unmapped
targets. Results page under `REPLY_CAP` (`packages/harness/src/tools.mjs:271-280`) and are never cut.
Tests: isolation, scope labels on every query, unmapped files never returned, paging under `REPLY_CAP`,
and a rebuild time on a 3,000-file fixture recorded in the manifest.
### Out of scope
Broker verbs and typed tools (C2-05b). Hybrid entries in the business file (C2-15). The query code takes
hybrids as a shape, and its tests use a hand-built hybrid mapping.
### Gate
Filbert approves the manifest. The library suite is green on Sage's gate, along with every
`scripts/test-*.sh`.
---
## C2-06 the cited-reply guard, the page store and the three notices
### Problem
Today a creative agent's free text would reach her as a stored message. Jason's ruling says agents never
write prose for her and always refer to the source (Q176). Design 5.2 puts one guard in the broker before
every agent-to-human message in a library business.
### Owner and reviewer
- Owner: an Opus 5.5 seat that Sage picks.
- Reviewer: Webby.
- After: C2-05a.
### Files owned
- `packages/library/src/guard.mjs` and its export.
- `packages/bus/src/broker.mjs`: the pre-check on `message.send` to `human`, and the page store beside the
web verbs (`:1516`, `:1521`).
- `packages/bus/src/web.mjs`, only if the page store needs the fetched text passed out.
- Tests in `packages/library/tests` and `packages/bus/tests`.
### What ships
The guard as design 5.2 sets it out: the JSON body, the M5 limits, the checks and refusal codes of the
5.2 table, and the three notices. It runs before the insert, outside the SQL transaction, as
`#requestProject` does (`broker.mjs:1378-1430`). It doesn't look at the sender's definition to decide
whether to run. The page store keeps the last 20 broker fetches per session, 200 KB each, in memory,
dropped when the session ends.
Tests: one pass and one refusal for each check in M5 and 5.2, each limit refused one past its boundary
and accepted at it. Each goes through the runner's RESULT path and through `send_message`, and once
with a non-creative sender in a library business. The `messages` count is unchanged on each refusal.
### Out of scope
The runner retry and the runner's notices (C2-07). The Console's rendering (C2-10b). Any classifier of
prose. The idea-text residual is Jason's or Melanie's to accept (design 5.1, 10 step 8).
### Gate
Webby approves the manifest. The library, bus and harness suites are green on Sage's gate, along with
every `scripts/test-*.sh`.
---
## C2-12 the setup sheet for Melanie and the survey run sheet
### Problem
Melanie keeps writing in Obsidian, and she needs to know what the agents read, how a playground folder
works, what changes for Bean and Chet, and two surprises (design 7.1, N2, N4). Whoever runs the survey
on the host needs exact steps.
### Owner and reviewer
- Owner: gpt-6-luna.
- Reviewer: Webby.
- After: C2-01.
### Files owned
`docs/case2/SETUP-SHEET.md` and `docs/case2/SURVEY-RUN.md` (new).
### What ships
The setup sheet in plain words:
- Which folders the agents read, and that a folder named in `playground` is her playground.
- How to add a graph colour group for that path in her own vault.
- That moving a note into a book folder makes it canon, so agents may cite it as fact.
- That a short link to a name that also exists in another of her repos can point somewhere else for the
agents, and that a full-path link avoids this.
- What changes for Bean and Chet: short cited items, no passages (design 4).
The survey run sheet gives the exact `library survey` command with C2-01's real flags, and states what
the output holds: names and counts only.
### Out of scope
Sending it to her. Design 10 step 7 makes that Jason's call.
### Gate
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.
---
## C2-09a one thread per creative instance
### Problem
`thread.create` picks the single `assistant` instance and refuses more than one
(`packages/bus/src/broker.mjs:2331-2334`). The same rule sits in `#threaded` (`:290-291`) and the launcher
(`packages/cli/src/launcher.mjs:135`, `:236-238`). So she can't open a thread with Bean beside one with
Chet.
### Owner and reviewer
- Owner: Rocko, gpt-6.1-sol.
- Reviewer: Dewey.
- After: C2-08.
### Files owned
`packages/bus/src/broker.mjs` (`#threaded`, `thread.create`), `packages/cli/src/launcher.mjs`,
`packages/webui/src/public/bus.js` and `serve.mjs` (the new-thread control only), and tests in
`packages/bus/tests`, `packages/cli/tests` and `packages/webui/tests`.
### What ships
Design 6, C2-09a: `#threaded` and the launcher read `THREAD_DEFINITIONS`. `thread.create` takes
`instance`, which must be a `THREAD_DEFINITIONS` instance. With no `instance` and exactly one such
instance, today's behaviour holds. With more than one, it refuses `thread-instance-ambiguous`. The
Console's new-thread control lists thread instances by `persona.name` or instance name and passes the
one she picks. No schema change.
Tests: a creative instance's thread starts a run, `thread.create` with `instance` binds it, and two thread
instances with no `instance` still refuse.
### Out of scope
Rooms and @tags (C2-09b, C2-10c).
### Gate
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).
---
## C2-10a no raw session view (M7) and no raw Discord engine (M8)
### Problem
The Console's conversation view shows a model's raw text from session files
(`packages/conversation/src/reader.mjs:225-240`, `events.mjs:58`, `:82`), so a refused answer would still
show. The Discord connector posts its engine's output and the broker never sees it
(`packages/discord/src/connector.mjs:381-387`).
### Owner and reviewer
- Owner: a Haiku 5.5 subagent at high effort, dispatched by Sage.
- Reviewer: Filbert.
- After: C2-08.
### Files owned
`packages/conversation/src/reader.mjs`, `packages/discord/src/cli.mjs` (`check` and `run` only), and
tests in `packages/conversation/tests` and `packages/discord/tests`.
### What ships
M7 and M8 as design 5.1 states them. On a business with a library project, `catalogue()` leaves creative
sessions out and `open()` refuses them with `conversation-not-listed`. `discord check` and `discord run`
refuse a binding whose engine isn't `mosaic-bus` with `discord-engine-not-allowed`, and also refuse a
`mosaic-bus` binding with `tools`.
Tests: a creative session file under the root isn't listed and won't open by id, and an assistant session
still lists. Both Discord verbs refuse on a library business, and a pi binding on a business without a
library still checks clean.
### Out of scope
The relay engine itself (C2-18).
### Gate
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.
---
## C2-20 Bean and Chet persona import, with a count-only dry run
### Problem
Bean's and Chet's personas live in her two Hermes profiles on 10.1.1.54. The hub needs each one as a
0600 persona file with its digest in the business file (design 4), and nothing should read her memory,
sessions or messages to get them (design 9.1).
### Owner and reviewer
- Owner: Gosalyn, Opus 5.5. The script runs only under Jason's go, by whoever he names.
- Reviewer: Webby.
- After: C2-08.
### Files owned
`scripts/case2-persona-import.mjs` (new), `packages/cli/tests/case2-persona-import.test.mjs` (new) and its
fixture tree.
### What ships
A script that reads only the persona definition of each of the two Hermes profiles. It confirms profile
to name from each profile's own configured name, prints the pairing, and stops if a profile names
neither Bean nor Chet. Per-channel prompts are listed by name and size only. It writes each persona to
a 0600 staging file. A second step, run as `<user>`, installs it at
`<configDir>/personas/<business>/<instance>.md` and prints `persona.name` and `persona.sha256`.
Otherwise it prints names, byte counts and sha256 only. `--dry-run` writes nothing and prints only counts
of files, lines, and lines starting `# ` or `## `.
Build note (gosalyn-0030, mos-1087). The dry run also counts lines that name a Hermes tool or channel,
so Jason knows before the import whether a persona refers to things Bean and Chet won't have in
Mosaic. The words come from a fixed list in the script: the tool names in the hermes-agent checkout at
df5700ebe, read from its code and not her profiles, plus `Hermes` and `Discord`. It prints one count
per persona file and never the line, its number or the matched word. A nonzero count changes nothing
in the import. It goes to Jason with the other counts.
Tests, on fixture Hermes profile trees:
- The right fields are taken.
- Memory and session files are never opened, shown by a read trace on the fixture.
- A profile with no persona field refuses, and a persona that fails C2-08's fence check refuses.
- Output holds no persona text. Each written file is 0600 and its digest matches.
- `--dry-run` writes nothing, and prints the right counts and no marker string for a fixture with two
heading lines.
- `--dry-run` counts three lines that name a listed Hermes tool or channel in a fixture. Its output
holds none of the matched words.
### Out of scope
Running it. Design 10 step 6 and 9.1 make the dry run, the import and the pairing check Jason's go. If
the dry run counts heading lines, the C2-08 follow-up changes the fence rule before the import, and her
text is rewritten only if she says so through Jason.
### Gate
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`.
---
## C2-03a fetch-only sync
### Problem
The mirror from C2-04 holds nothing until something fetches her repo, safely and read-only, without the
token ever reaching argv, config, logs or replies (design 7.2, CONSTITUTION gate 13).
### Owner and reviewer
- Owner: an Opus 5.5 seat that Sage picks.
- Reviewer: Dewey.
- After: C2-04.
### Files owned
`packages/library/src/sync.mjs`, `packages/library/bin/askpass` (new), `packages/bus/src/broker.mjs`
(`library.sync`, the 60-second timer and the library lock), `packages/webui/src/cli.mjs` (dispatch line),
and tests in `packages/library/tests` and `packages/bus/tests`. If the helper sits outside
`packages/library/src`, add its path to `COPY_PATHS` in `scripts/stack-copy.sh`.
### What ships
Design 7.2's slice:
- `syncCommand(kind, url)`: its argv is `SAFE`, exactly one `-c protocol.https.allow=always`, `-C
<mirror>`, then the command. Its env is `gitEnv()` plus `GIT_ASKPASS` naming a helper outside the
mirror, which reads the `library-gitea` slot.
- An `https://` URL only.
- The served ref `refs/heads/served`, and `library-diverged`, held until a human `library.sync --accept
<commit>`.
- The sync state file, written atomically inside the mirror, and the 60-second timer.
- `library.sync`, human only. A test reaches a local bare repo only through the module parameter that
replaces `syncCommand`.
Tests: the C2-03a tests in design 7.2. A mutant that sets `protocol.allow=always` fails the argv test.
### Out of scope
Push and every bus write (C2-03b, C2-02). Creating the token slot, which is design 10 step 4.
### Gate
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.
---
## C2-07 runner retry on a guard refusal, and the runner's notices
### Problem
When `message.send` refuses, the runner logs it and marks the message read, so the answer is lost
(`packages/harness/src/runner.mjs:451-456`). Its empty and failed turn texts (`:449`) wouldn't pass the
guard.
### Owner and reviewer
- Owner: a Haiku 5.5 subagent at high effort, dispatched by Sage.
- Reviewer: Filbert.
- After: C2-06.
### Files owned
`packages/harness/src/runner.mjs` and `packages/harness/tests/runner.test.mjs`.
### What ships
On a refusal with a guard code, a new turn in the same session with the code and reason, at most twice,
then `{"items": [], "notice": "no-cited-answer"}`. In a library business, `no-answer` and `turn-failed`
replace the two texts at `:449`. The failure reason stays in the run log.
Tests: a fake harness answers free text, then a valid reply, and exactly one message is stored. An empty
turn and a failed turn store the fixed notices.
### Out of scope
The guard itself (C2-06).
### Gate
Filbert approves the manifest. The harness suite is green on Sage's gate, along with every
`scripts/test-*.sh`.
---
## C2-05b broker lookup verbs and typed tools
### Problem
The queries from C2-05a aren't reachable from an agent session. Design 3 and M3 call for typed tools
that the broker re-checks on every call.
### Owner and reviewer
- Owner: Rocko, gpt-6.1-sol.
- Reviewer: Dewey.
- After: C2-05a, C2-03a.
### Files owned
`packages/bus/src/broker.mjs` (the lookup verbs), `packages/harness/src/tools.mjs` (the five typed tools),
and tests in `packages/bus/tests` and `packages/harness/tests`.
### What ships
The five lookup tools of design 3, for every agent session in a business with a library project. Each
read is at the served commit, and a read whose last fetch is older than 60 seconds syncs first. The
verbs refuse `library-not-configured` in a business without a library and refuse a missing `universe`.
The creative tool set is exact: the lookups and `send_message`, with no write tool and no
`raise_decision`.
Tests: broker verb tests and a snapshot of typed tool names per creative role (M3).
### Out of scope
The guard (C2-06). Write verbs (C2-02, C2-19).
### Gate
Dewey approves the manifest. The bus and harness suites are green on Sage's gate, along with every
`scripts/test-*.sh`.
---
## C2-10b Console chat: cited items and the status line
### Problem
The Console would show a cited reply as raw JSON, and she can't see when the hub falls behind her repo
(design 7.4).
### Owner and reviewer
- Owner: a Haiku 5.5 subagent at high effort, dispatched by Sage.
- Reviewer: Dewey.
- After: C2-06, C2-09a, C2-03a.
### Files owned
`packages/webui/src/public/bus.js`, its stylesheet, `packages/webui/src/serve.mjs` (a `library.status`
route only), and tests in `packages/webui/tests`.
### What ships
Design 7.4. The chat view renders a cited reply through `renderItems`: each item's kind, text and cites,
with a scope badge (canon, playground or web) and "changed since" when the file's last commit is newer
than the cited one. Each notice shows its fixed line. A status line reads `library.status`: the served
commit and its age, `library-diverged`, a fetch error, and the count of R-problems with a link to the
list.
Tests: webui tests for items, scope badges, notices, "changed since" and the status line.
### Out of scope
The room view (C2-10c), library pages (C2-11b). Her sign-in from her machine waits on SSO (#412) or
Jason's interim ruling (design 10 step 5).
### Gate
Dewey approves the manifest. The webui suite is green on Sage's gate, along with every
`scripts/test-*.sh`. Grep for hash pins on `bus.js` and `serve.mjs` before in-review.
---
## C2-13 install script and precheck for the host
### Problem
The hub runs on 10.1.1.54 as `mosaic` under `/srv/mosaic`, as `mosaic-stack.service` with
`ProtectHome` (design 9, Q181). Nothing installs a library business there, or checks the host before it
starts.
### Owner and reviewer
- Owner: Gosalyn, Opus 5.5. The script runs only under Jason's go, by him or his delegate (design 10).
- Reviewer: Webby.
- After: C2-04, C2-03a, C2-20.
### Files owned
`scripts/case2-install.sh`, `scripts/case2-precheck.sh` and `scripts/case2-unit.sh` (new), the template
`packages/cli/systemd/mosaic-stack.service.in` (new), and `packages/cli/tests/case2-install.test.mjs`
(new).
### What ships
The install does: bootstrap, release package and activate, the business file with the mapping, `bean`
and `chet` with their persona vars and files, any specialists she picks, her human id, the token slot
path. It runs as `<user>` through `sudo -u mosaic -H` from `localadmin`, and `mosaic` gets no key. It
doesn't create `<user>` and writes no unit file. Root does those in design 10 steps 2a and 2c. Every
host value is an input, so a later host move changes only inputs.
Row 195 runs these scripts by input for Case 1 on 10.1.1.53 (darkwing to gosalyn, design at
`agents/darkwing/work/queue-195/DESIGN.md` section 2). They don't copy them. So the install and the
precheck take `--profile factory|library`, default `library`:
- `library` is everything this brief lists.
- `factory` skips the library mapping, the persona files and digests, the token slot, and
`library.sync` and `library.status`. It requires `bus.tester` on, because Case 1's acceptance uses the
tester.
- `factory` refuses a business file that has a `kind: "library"` project, with
`profile-library-business`. So Case 2 can't reach a tester-on check through a wrong flag.
- `library` refuses with `bus.tester` on, as before.
`case2-unit.sh` is the 2c step. Root runs it from a root-owned checkout of the reviewed commit outside
`/srv/mosaic`, never from the release, because `mosaic` can write the release. It does the following:
- It renders the unit from the checkout's template, with the user, the home and the copy id as inputs.
- It refuses unless `User=`, `Group=`, `ExecStart=` and the hardening lines match fixed values. The
copy id is the one part of `ExecStart=` that varies, so a re-pin to a new copy can re-render the
unit (row 195). It must be exactly 40 lowercase hex characters naming a copy that holds `scripts/mosaic`,
directly under `<home>/.local/share/mosaic-stack/copies`. That is the rule `copy_root` uses
(`scripts/stack-copy.sh:6`, `:20`). Anything else refuses before the render.
- It refuses any `PermissionsStartOnly=`, and any `Exec*=` value whose leading run of prefix
characters (`@`, `-`, `:`, `+`, `!`) holds a `+` or `!`. systemd takes those prefixes in any order,
so `-+/srv/mosaic/x` runs as root even with `User=mosaic`. The named-line check doesn't read these
lines (webby-0026 N1, webby-0027, defence in depth).
- It writes `/etc/systemd/system/mosaic-stack.service` as `root:root` 0644. It never uses
`systemctl link`, and it refuses a symlink at the unit path.
- On a re-install, it refuses an installed unit that differs from the render.
- It reads nothing from under `<user>`'s home.
The precheck refuses unless all of these hold:
- The config validates.
- `sudo -n true` fails as `<user>`, and `id -G` prints exactly one entry, its primary group. A system
user always has one, so the check is for supplementary groups. `bus.tester` is not on.
- The business file, `.history`, `changes.jsonl` and persona files have the modes in design 4, and
each persona digest matches.
- The run root is 0700.
- `node --version` is 24 or later, because every package's `engines` field says `>=24` (row 195: .53
has v22.22.2).
- `bwrap --unshare-all --ro-bind / / true` exits 0 as `<user>`. Without it no session starts. Row 195
found no bwrap on .53, and `kernel.apparmor_restrict_unprivileged_userns=1`, and .54 may be the same.
On a refusal, the precheck prints the sysctl's value and whether `bwrap` is on PATH.
- One marker prompt run through the host's claude with `--system-prompt-file` shows that it replaces
the default prompt. The claude version is printed.
- `library.sync` fetches her repo read-only, and `library.status` lists R-problems.
- No Discord binding exists.
It also prints free memory and verifies HOME is `/srv/mosaic`.
Tests run against a scratch data root and a local bare repo. Each precheck refusal has a case. The
claude check runs against a fake claude that appends rather than replaces, and that case refuses.
`case2-unit.sh` runs against a scratch unit directory. These cases refuse:
- an installed unit edited to `User=root`
- an edited `ExecStart=`
- a template with a hardening line removed
- a symlink at the unit path
- a template with `ExecStartPre=+/srv/mosaic/x`
- a template with `ExecStartPre=-+/srv/mosaic/x`
- a copy id that is short, uppercase, holds `/` or `..`, or names no directory
Precheck cases for the new lines: a fake `node` printing v22 refuses, and a fake `bwrap` that exits 1
refuses. `--profile factory` passes with the tester on and no library items, and refuses a business
file with a library project. `--profile library` refuses with the tester on.
A clean render writes a 0644 file. A test checks the script for `systemctl link`.
### Out of scope
Running it on the host. Design 10 steps 2 to 7 are Jason's. Step 1 no longer waits on #421 (Q183), and
Mos lifted its hold (mos-1074). The root steps 2a and 2c are ops-01's, over ssh as `localadmin`,
so the install waits on #423 step 1 and on nothing else in #423. The Gitea bot account and the token slot are steps 3 and 4. Stopping Hermes
is step 9 and her choice.
### Gate
Webby approves the manifest. The test file and the cli suite are green on Sage's gate, along with every
`scripts/test-*.sh`. The run on the host is a separate window with Jason's go.
---
## C2-14 end-to-end acceptance on a scratch root
### Problem
Each slice row tests its own part. Nothing yet shows the whole path from her push to a checked reply in
the Console.
### Owner and reviewer
- Owner: an Opus 5.5 seat that Sage picks.
- Reviewer: Dewey, who also runs the script.
- After: every other slice row above.
### Files owned
`scripts/test-case2.sh` (new) and its fixtures.
### What ships
One script on a scratch data root and a bare repo in her layout. It syncs, asks a creative agent in its
thread through a fake harness, and stores a reply whose cites verify. It refuses a playground cite on a
`fact`, holds a force-push as `library-diverged`, and checks that the Console renders the reply as
items.
### Out of scope
Any live host or real model call.
### Gate
Dewey runs the script and approves. It is green on Sage's gate with every `scripts/test-*.sh`.
---
## Brief: row A, ZAI_API_KEY in the session env allowlist
### Problem
For 10-27, Bean and Chet both run pi with provider `zai` on `glm-5.3` (Q186, mos-1090, sage-1469). Pi
reads the zai key from `ZAI_API_KEY` (pi-ai `env-api-keys.js:89`, with `glm-5.3` in `data/zai.json`).
`ENV_ALLOW` (`packages/seat/src/session.mjs:83-88`) doesn't hold that name, so `sessionEnv` (`:97-99`)
drops it. A pi launch has no credential check (`packages/plugins/harness/pi/index.mjs:42`
`countsAccount: false`, `packages/cli/src/launcher.mjs:299`), so today a zai role launches and fails at
its first turn, as `packages/cli/README.md:439-444` says.
### Owner and reviewer
- Owner: a Haiku 5.5 subagent at high effort, dispatched by Sage (lead decision 129). Size XS.
- Reviewer: Webby.
- After: nothing. It doesn't wait on any Case 2 row.
### Files owned
`packages/seat/src/session.mjs`, `packages/seat/tests/session.test.mjs` and `packages/cli/README.md`.
### What ships
- `"ZAI_API_KEY"` joins the provider line of `ENV_ALLOW` (`session.mjs:86`), after `OPENROUTER_API_KEY`.
Nothing else in the list changes. `SESSION_CREDENTIALS` and `hasSessionCredential` (`:94-95`) don't
change. They are Claude Code's login sets, and a zai key isn't one.
- `packages/cli/README.md`, after "A Pi session gets the same variables through the allowlist." (`:435`),
one sentence: a Pi session on provider `zai` logs in with `ZAI_API_KEY`, which the allowlist passes. Like
every allowlist variable, it reaches every session the host starts, Claude Code ones included, and a
shell inside any of them can read it.
- One test in `session.test.mjs`: `ENV_ALLOW` includes `ZAI_API_KEY`, and
`sessionEnv({ ZAI_API_KEY: "fixture-zai", ZAI_TOKEN: "t" }, run)` returns `ZAI_API_KEY` with the fixture
value and no `ZAI_TOKEN`. The existing allowlist test (`:51-59`) stays as it is.
- One mutant: the same file with `"ZAI_API_KEY"` removed from `ENV_ALLOW`. The new test fails on it.
### Out of scope
Placing a key anywhere. The hub's own key on the Z.ai account is design 10 step 2, and it is Jason's.
Any pi launch or live check: running a pi role waits on #383. Chet's Codex login (row B, #428). A
credential check for pi launches.
### Gate
Webby approves the manifest. The mutant is killed. The seat suite and the cli suite are green on
Sage's gate with every `scripts/test-*.sh`.
---
## Brief: row B, a per-instance pi agent dir for Chet's Codex login (#428)
### Problem
After 10-27, Chet moves from Bean's Z.ai account to his own ChatGPT/Codex login (Q186). Pi's
`openai-codex` provider logs in with OAuth and a refresh token, and keeps both in `auth.json` under
`PI_CODING_AGENT_DIR` (pi `config.js:405-438`). A role session's `HOME` is the run's own, so pi never
sees an agent dir from the host (`packages/cli/README.md:439-441`), and `ENV_ALLOW` doesn't pass
`PI_CODING_AGENT_DIR`. So no env variable can carry this login. Each instance needs its own agent dir
that survives runs, because a refresh rewrites the file.
Chet's Hermes profile uses the same login today. Until Hermes stops, two clients would hold one
refresh token. If a refresh by one revokes the other's, one of them drops out mid-session.
### Owner and reviewer
- Design: Gosalyn owns it (sage-1469). The build owner and reviewer are named when the design is
approved.
- Starts after 10-27. Nothing in this row is built or tested before then.
### Files owned
None yet. The design names them.
### What ships
The design first. It settles where the dir lives and who can read it, how the launcher mounts it into
the sandbox writable for one instance only, how a refresh write survives the run, what a shell in the
session can read, and how a launch with no login refuses. Build rows follow from it.
### Out of scope
Bean's model path. Any change to row A. Any change to Hermes or her server.
### Gate
A live check, run with Jason's or Mos's go, showing that two clients on one ChatGPT login don't revoke
each other's refresh tokens. Until that check passes, no build row for this design starts.