sage: briefs for P19 380 (tester refusal, #1667, row 170) and P19 379 (project names vs repos, option B, #1668, row 171), for Mos's approval

This commit is contained in:
jason.woltje committed 2026-10-19 13:18:59 -05:00
1 parent 42817065e1
commit 97cc7f0a32
2 files changed
+171

No files matched your search

@@ -0,0 +1,92 @@
# Brief: a thread project named like a business-file repo
P19 379, Gitea #1668, queue row 171. This brief comes from Mos's finding F2 in the 8e7ea89a1 window (mos-0844) and
Mos's ruling mos-0845 D1. Owner Rocko, reviewer Dewey, gate owner Sage. Line numbers are at origin/refactor 42817065e.
## Decision needed first: this brief departs from D1
mos-0845 D1 gave a go to the "one-registry design" in Sage's F2 recommendation: the bus lists the business file's
projects in `project.list`, and `thread.create` accepts their ids. When Sage wrote that recommendation, Sage hadn't
checked three things, and each of them is a reason against it:
1. PRD 1.2 (`docs/prd/mosaic-stack.md:726-730`) says the business file's `projects` key names git repositories to
clone, while a REQ-ORG-2 project is the user's container for threads. Row 121 kept the two apart and linked them by
`repo` (`packages/bus/src/broker.mjs:2244-2245`: "`repo` names a project from the business file; the rename to repos
is a P19 follow-up").
2. REQ-THREAD-2 (prd:673-678) has the user create projects from the CLI or the WebUI. Only Jason edits the business
file, so if the business file were the registry, users couldn't create projects at all.
3. `threads.project` is `REFERENCES projects(id)` (`packages/bus/src/migrations/3d-3e.sql:17`). Accepting
business-file ids in `thread.create` would need a schema migration, a re-pin with `--migrate` and downtime.
Sage recommends option B below in place of D1's one-registry design. Mos rules on it. If Mos keeps D1, this brief gets
rewritten as a schema and PRD change for Jason, and none of the B text below applies.
## Option B: keep the two apart and close the name trap
### The problem B fixes
The live store's `projects` table is empty, and the business file names `stack`. So:
- `mosaic threads --project stack` and `mosaic thread create stack <name>` refuse "no project is named or numbered"
(`packages/cli/src/thread.mjs:41-45`), which doesn't say why.
- `mosaic project create stack` succeeds with no repo and makes a thread project called `stack` that has nothing to do
with the `stack` repo (`broker.mjs:2246-2262`).
### The change
Bus, `project.create` (`broker.mjs:2246`):
- If `a.name` is a key of the business file's `projects` (`b.projects`), the create needs `a.repo` equal to that same
key. Otherwise refuse `project-name-is-repo`. `mosaic project create stack --repo stack` makes the thread project for
the `stack` repo, and `mosaic project create stack` refuses.
- The check comes after `unknown-repo` and before `project-exists`, and nothing gets written when it refuses.
- Rows already in a store aren't checked again. The live table is empty.
CLI:
- `resolveProject` (`thread.mjs:41-45`): when no store project matches and `ref` is a key of the business file's
`projects`, the refusal says
`"<ref>" is a repository in the business file, not a thread project yet; mosaic project create <ref> --repo <ref> makes one`.
The CLI reads the business file with the `loadBusiness` that `packages/cli/src/project.mjs:1` already imports. If the
file doesn't load, the old message stands, and the CLI does no other read.
- `project create` prints the bus refusal as `"<name>" is a repository in the business file; add --repo <name> to make
its thread project, or pick another name`, exit 2.
WebUI:
- `packages/webui/src/bus.mjs` `STATUS` (:85-104) maps `project-name-is-repo` to 409. That file is pinned in
`packages/bus/tests/channel-proof.test.mjs:18`, so the same diff updates the pin and adds a comment line, as rows 155
and 165 did. The chat create form (`packages/webui/src/chat.mjs:19`) shows the bus code as it shows the others.
Docs:
- `packages/cli/README.md` and the `project create` usage line (`project.mjs:12-13`) say that a thread project named for
a repo carries that repo.
- The PRD isn't edited. Its 1.2 bullet already allows keeping the two apart, and any PRD wording is Jason's.
### Out of scope
- Renaming the business file's `projects` key to `repos`. That's a business-file schema change, it needs Jason, and it
gets its own P19 task if Mos wants one.
- Any store migration, any edit to the live business file, and any run against the live stack.
- `mosaic project init|check`, which works on repos on disk.
### Tests
Every item gets its own test, plus a mutant that removes the new check and fails it.
1. Bus: `project.create` with a name that's a business-file project key and no `repo` refuses `project-name-is-repo`
and writes no row. With a different `repo` key it refuses the same way. With `repo` equal to the name it succeeds.
2. Bus: a name that isn't a business-file key keeps today's behavior, both with and without `repo`.
3. CLI: `threads --project stack` with an empty store and a business file naming `stack` prints the repository line,
exit 2. With a business file that doesn't load, it prints the old line.
4. CLI: `project create stack` prints the refusal line and exits 2.
5. WebUI: `busStatus("project-name-is-repo")` is 409, and channel-proof passes with the new pin.
Run every changed test file singly before in-review, and grep the tests for the path and any 64-hex pin of each changed
file.
### Acceptance
- Every test and mutant above.
- The `packages/bus`, `packages/cli`, `packages/webui` and `packages/plugins` suites and every `scripts/test-*.sh`
green on Sage's gate. The reviewer approves the candidate manifest.
- The diff touches only `broker.mjs`, `packages/cli/src/thread.mjs`, `packages/cli/src/project.mjs`,
`packages/webui/src/bus.mjs`, their tests, `channel-proof.test.mjs` and `packages/cli/README.md`.
- The window plans keep reading `threads --business mosaic-stack --json` and expecting `[]` until this row lands and
reaches the live stack by a re-pin.
@@ -0,0 +1,79 @@
# Brief: a tester message to a role nobody runs is refused, not queued
P19 380, Gitea #1667, queue row 170. This brief follows Mos's ruling mos-0845 D2, made on Mos's finding F3 from the
8e7ea89a1 window (mos-0844). Owner Dewey, reviewer Filbert, gate owner Sage. Line numbers are at
origin/refactor 42817065e.
## The problem
When nobody holds a role, a tester's `message.send` to that role is stored anyway, and the message waits for a claim
(`#route`, `packages/bus/src/broker.mjs:2482-2485`). `#waiting` (:2495-2504) tells launch.by nothing about a
tester's message, since a PM test authorizes nothing. So the message sits until the role is next launched, and then that
run reads a stale test as its first input. The 8e7ea89a1 window did exactly this. Message 02f0e5d6
(`[mos test] re-pin 8e7ea89a1`) is still queued for pm.
mos-0845 D2: a tester message to a role with nobody running is refused, not queued, and the refusal comes before the
message is stored.
## The change
### Bus, `message.send` (`broker.mjs:1790-1872`)
- When the caller is the tester (`s.tester`), and after `#threadFor` (:1850) has picked the thread, refuse
`tester-target-not-running` if `this.#recipient(s.business, to, thread)` is null. The refusal must come before the
`INSERT INTO messages` (:1852). No messages row, no delivery row and no notice get written.
- The lookup is the same `#recipient` that `#route` and `#waiting` use, so a run that is launched but hasn't claimed yet
counts as no holder, as it does in `#waiting`.
- A holder that releases between this check and the insert leaves the message waiting like any other message. The
brief accepts that race and doesn't add a lock.
- Human and agent sends don't change. A human's message to a stopped role still stores, waits and tells launch.by
(row 146).
- Update the `#waiting` comment (:2486-2494). Its "a tester's" case can no longer reach `#waiting` with no holder.
### CLI
- Leave `packages/cli/src/transport.mjs` alone. Row 164 freezes its bytes
(`packages/bus/tests/channel-proof.test.mjs:13`), and its `EXIT` map sends an unlisted code to 2.
- In `talk` (`packages/cli/src/cli.mjs:353`), the `catch` around the send (:377-384) turns a `--tester` send that the
bus refuses with `tester-target-not-running` into
`CliError("<role> isn't running, so the tester's message was refused and nothing was stored", 3)`. Exit 3 matches
the other tester refusals (`tester-off`, `tester-refused`).
- Remove the `!w.told` line in `waitingLines` (:342-343). The bus no longer returns a tester's message as waiting, and
since row 85 the CLI and the bus run from the same tree (the human and tester proofs are path-bound), so no older bus
can answer this CLI.
- `packages/cli/README.md:130-131` and the `--tester` paragraph (:162-171) say the new behavior.
## Out of scope
- Message 02f0e5d6 and every other stored message. Any store edit waits for Jason (mos-0845 D2).
- Expiry of messages that are already waiting.
- Any change to the human's or an agent's send, to `thread.send` (the tester has none), or to the launcher.
- Any run against the live stack.
## Tests
Every item gets its own test, plus a mutant that removes the new check and fails it.
1. Bus (`packages/bus/tests/tester.test.mjs`): a tester `message.send` to a role with no claim refuses
`tester-target-not-running`. The `messages` and `deliveries` row counts don't change. No `_bus` notice is written.
2. Bus: the same send succeeds and routes once a run holds the role with a claim. A launched but unclaimed run still
refuses.
3. Bus: a human `message.send` to the same stopped role still stores the message, returns `waiting` with `told: true`,
and writes launch.by's notice. This is the row 146 behavior, unchanged.
4. CLI (`packages/cli/tests/tester.test.mjs:130-136`, replaced): `talk --tester` to a stopped role exits 3, prints the
refusal line, prints no `sent` line, and stores no message.
5. CLI: a `--tester` send refused with any other code keeps its old message and exit, and a human send refused with
`tester-target-not-running` (a stand-in transport) is not rewritten by the new branch.
One mutant deletes the bus check and must fail items 1, 2 and 4. A second deletes the CLI branch and must fail item 4.
Run every changed test file singly before in-review. Grep the tests for the path and a 64-hex pin of each changed file.
At 42817065e no test pins `broker.mjs` or `packages/cli/src/cli.mjs`, and `transport.mjs` stays untouched.
## Acceptance
- Every test and mutant above.
- `packages/bus` and `packages/cli` suites and every `scripts/test-*.sh` green on Sage's gate. Filbert approves the
candidate manifest.
- The diff touches only `packages/bus/src/broker.mjs`, `packages/cli/src/cli.mjs`, their tests and
`packages/cli/README.md`. `docs/TOOLS.md` has no tester talk line at 42817065e (its :431 covers only the switch).
- The change reaches the live stack only through a later re-pin. This row doesn't schedule one.