@@ -5003,3 +5003,5 @@ Before: usage_records skipped Store timestamp verification; Claude project-direc
Before: managed project/thread verbs had no Console conversation area. After: fixed human-transport chat adapter/HTTP routes, named project/thread forms, isolated conversations/drafts, explicit Resume, paging and signed-in sends. sage-0724 keeps channels out of scope. Rebuilt on row124 landing70504d23a, preserving usage hunks. Gate: webui177 + bus threads14 + CLI parity11 =202 passed; 9 named-navigation and 20 strengthened HTTP checks reran as subsets. 64/64 mutants killed; first array-guard survivor led to missing project/thread response assertions, now killed with source unchanged. Existing navigation expectations updated with named items per sage-0747. Packet agents/rocko/work/queue-129/r1/, candidate cfbadbd49902f5f3740392fec9218279adc2a661cfb28341fba02790f327232b, packet c674fb259c377cb3b60aaa42fd348e9da7b722ae3aa9e8fb182f99cbfa7dfe02. All15 staged/working/reconstructed hashes and118 packet entries verified; scan clear, process sweep empty. In-review1767-1768; expected no-token exit1. Slot returned, no Docker/live action; Filbert review and Sage/Mos gates remain.
2026-10-18 16:42:30 UTC | Rocko | row 164 #1661 design stage | Before: no channel kind; row 129 in review. After: agents/rocko/work/queue-164/DESIGN.md answers all five brief questions with file:line evidence, proposes interface v1 dispatch and a core management recovery path independent of channel switches, inventories proof reads/callers and unchanged byte baselines, and names cut-over and parity-fixture decisions for Sage/Mos. Design only; no claim, product code, tests or live action.
2026-10-18 16:46:44 UTC | Rocko | row 164 design revision | DESIGN.md and pre-build BUILD.md record Sage/Mos conditions and human-only enablement right after activate, followed by CLI thread and Console chat smoke. Loader inspection found no one-call matched snapshot API; propose synchronous snapshot(kind), no dispatch lock. Existing chat-http owns status/message mapping; propose its local change instead of serve.mjs. No product edits or test runs.
`call(verb, args)` is a core-created, already business-bound human transport.
The module returns its resolved bus result unchanged, or propagates its refusal
unchanged. It receives no capability, proof, human/tenant selector, socket path,
process API or configuration writer. It must not import a transport or start a
process. The shipped modules only validate their supported operation and forward
it to `call`; presentation, HTTP guards, thread resolution and paging remain in
their current core surfaces. These are trusted in-tree modules, not a sandbox or
an authorization boundary (`loader.mjs:1`, `:41`; PRD `docs/prd/mosaic-stack.md:598`).
Supported operations are `project.list`, `project.create`, `thread.list`,
`thread.create`, `thread.messages`, `thread.send` and `thread.open`. The CLI
module also forwards `message.receive` and `message.read` for the existing
thread talk/follow loop only. These exports support future surfaces without
requiring surface-specific function names in the kind. An unsupported verb
refuses `channel-operation`; no arbitrary verb fallback. Existing REQUEST
construction stays in CLI `cli.mjs:354` and row-129 `chat.mjs:11`.
Core helper, new `packages/plugins/src/channel.mjs`:
```js
createChannelCaller({name,call,log})// returns async (verb, args = {}) => result
```
It reads current settings with the existing core `call('plugins', {})`, validates
that response and projects just its channel key into `createLoader` (or `{}`
when absent), then selects the requested name from
the matching `loaded().channel` / `modules('channel')` snapshot, and invokes
`dispatch`. The name is a core literal (`cli` or `webui`), never browser input.
Take names and modules as one atomic snapshot, then release selection before
invoking dispatch. Never hold a lock over dispatch: a slow thread.send must not
block project.list (sage-0766). The existing loader has only separate synchronous
getters, `loaded()` and `modules(kind)` (`loader.mjs:148`–`:157`); it does not
currently return matched pairs in one call. Proposed for Sage/Mos to choose:
add synchronous `snapshot(kind)` returning the current immutable list of
`{ name, module }` entries. Those entries and their array are already frozen
(`loader.mjs:71`, `:136`). The getter performs no await or callbacks. The helper
captures that one return value, selects the requested entry synchronously, and
invokes dispatch without a lock. No edits to path, version or last-good behavior.
This adds `src/loader.mjs` and its snapshot test to the proposed scope; no code
starts until Sage accepts the loader API choice and Mos accepts the design.
Keep a loader per caller; a malformed settings response refuses rather than
silently inventing configuration. Read settings again for each chat operation,
including the thread receive loop, so a valid off switch takes effect on the
next operation. An operation already admitted may finish once. A loader refusal
uses the existing last-good policy; first-load refusal has no enabled modules.
Log refusal metadata without message bodies or proofs.
Projecting the channel key avoids loading unrelated usage/search modules into
the human CLI or Console; it does not change the organization document.
| Surface | Existing seam | Proposed routing |
|---|---|---|
| CLI project create/list | `cli.mjs:541` | Pass a channel caller into `projectBusCommand`; local init/check remain core. |
| CLI threads and thread create/read/open | `cli.mjs:542`, `:543`; `thread.mjs:40`, `:53`, `:72` | Pass the channel caller through the current command/helper signatures. Preserve resolution, formatting and 200-row paging. |
| CLI talk/follow with --thread | `cli.mjs:345`, `:349`, `:354`, `:382`, `:390` | Use the channel caller for this flow, including its receive/read operations. Non-thread talk/follow are unchanged. |
| WebUI chat | row-129 `chat.mjs:3`–`:12` | Wrap the supplied core call once with the webui channel caller; preserve all seven public method signatures. |
| Administrative CLI and other Console views | `cli.mjs:540`, `:544`–`:546`; row-129 webui `cli.mjs:59`–`:65` | Retain their existing raw core call. Never wrap the common transport factory globally. |
Both modules load only through `createLoader`; no core import names either
module. `kinds.mjs:9` is the registry; `loader.mjs:41`–`:69` already supplies
name/path/symlink/export/version checks. `hasModule` uses the same registry
(`loader.mjs:77`), so the existing organization setter can validate the new names.
## 2. Off behavior and the recovery path
The setting is, for example, `"plugins": { "channel": ["cli", "webui"] }`.
Only `cli` disables web chat; only `webui` disables the CLI's project/thread
surface; `[]` disables both. Removing the channel key also disables both. No
implicit default: an absent key means off (`loader.mjs:117`, plugins README:63).
A disabled CLI channel refuses `channel-disabled`, exit 3, before a project,
thread, delivery or read operation. A disabled WebUI channel returns HTTP 503,
`error: "channel-disabled"`, with no successful send receipt and no chat bus
operation. Both refusals include fixed, operator-facing recovery text:
"Run mosaic plugins to read the revision, then mosaic plugins set --file PATCH
with plugins.channel set to [\"cli\",\"webui\"]." Include the actual CLI
business selector when available. Assert the recovery text in both tests.
Do not edit proof transports.
The HTTP mapping actually belongs to row-129 `src/chat-http.mjs:12`–`:22`:
its closed STATUS table and safeMessage function decide the status and message;
its catch ignores arbitrary transport error text (`:114`–`:119`). `chat.mjs`
alone cannot deliver a new status or reflected message through that contract.
Propose adding only channel-disabled:503 and its fixed recovery message in
chat-http.mjs, with the seam adapting the refusal to BusReadError. This avoids
serve.mjs entirely and does not conflict with row 154's static routes. This is a
correction to the initial design's suggested serve.mjs mapping, for Sage's read.
The chat page can still render its normal refusal state; other Console routes,
Settings, and core human administration stay available. An empty channel list
does not stop the bus or revoke the human session.
In particular, **`mosaic plugins` and `mosaic plugins set` do not use a channel**.
The current dispatcher already gives them the raw `callFor` (`cli.mjs:546`),
and `plugins.mjs:34`–`:37` calls `plugins` / `plugins.set` directly. The broker
requires a proved human and its configured organization adapter
(`broker.mjs:1133`–`:1165`), independent of any channel selection.
`plugin-settings.mjs:34`–`:60` applies a per-kind patch with revision checking
and the existing organization writer. None of that path changes.
Exact operator recovery, with both channels off:
1. From a real human shell outside an agent, run
`mosaic plugins --business BUSINESS --json` and obtain its `revision`.
The submitted process is read again before success (`human.mjs:89`–`:95`).
The proof also consumes the existing in-memory launch PID/start records and
resolved business human (`runtime.mjs:115`–`:121`); it opens no organization,
wrapper, shim-source, credential or system-config file to prove a request.
In particular, the verifier compares a **literal argv path**, not a file hash
(`human.mjs:70`). Hash checks below are regression evidence, not a new proof rule.
Unchanged core source and direct production callers:
| File | Current role/caller, unchanged |
|---|---|
| `packages/bus/src/human.mjs` | `verifyHuman` called by runtime only; exported `readProcess` also already used by human-cli, tester and tester-cli. No new caller. |
| `packages/bus/src/human-cli.mjs` | Self re-executes its own path (`:8`–`:12`); existing CLI transport and Console humanCall spawn it. No new caller. |
| `packages/cli/src/transport.mjs` | `HUMAN_CLI` resolved beside the running tree (`:12`), humanTransport at `:55`; existing CLI callers stay in place. |
| `packages/webui/src/bus.mjs` | Imports the same `HUMAN_CLI` (`:12`); existing humanCall spawn (`:27`–`:31`) and busCaller (`:60`). No new caller. |
| `packages/bus/src/runtime.mjs` | Same verifier import/call and same own-tree shim path (`:115`–`:116`). No edit. |
| `packages/bus/src/server.mjs` | Same authenticateHuman admission (`:62`–`:64`), capability envelope and refusals. No edit. |
Recorded unchanged byte baselines for later comparison:
| `broker.mjs:530` (brief :526) | Protected role-end authorization still recognizes the human decision's `via === 'cli'`. |
| `broker.mjs:1887` (brief :1867) | Launch revoke/restore event still records `via: 'cli'`. |
| `landing.mjs:107` | Human resolution still requires `res.via === 'cli'` plus the existing actor/answer checks. |
Also leave broker `#human` (`:330`) and `bindHuman` (`:472`) unchanged. A module
does not set `via`, bind a session or supply the owner. No schema change or
existing decision/event rewrite is needed.
## 5. Discord is not connected
No Discord module, connector edit, binding/token read or new service. A future
`packages/plugins/channel/discord/index.mjs` can export `interfaceVersion = 1`
and `dispatch(request, { call })` with the same bus-operation/result contract.
That future row must provide an authenticated core caller bound to the right
human/business and map Discord interaction to the fixed operations; a module
cannot manufacture proof. This interface does not authorize that future ingress.
Its authentication, subscriptions and deployment are separate decisions; merely
adding a module of this kind does not require a kind-version bump.
## Proposed owned files, tests and mutants
Product: `packages/plugins/src/kinds.mjs`, proposed `src/loader.mjs` snapshot getter, new `src/channel.mjs`, new
`channel/cli/index.mjs` and `channel/webui/index.mjs`; CLI `src/cli.mjs`
and, only if passing the caller needs it, `src/thread.mjs`; WebUI `src/chat.mjs`
and proposed `src/chat-http.mjs` (only channel-disabled status/recovery text).
No serve.mjs edit planned. Docs:
`packages/plugins/README.md`, `docs/prd/mosaic-stack.md:822` and operator cut-over
notes in this packet. No public/ UI changes planned. New helper, snapshot getter
and chat-http mapping are explicitly proposed additions to the brief's call sites.
| Acceptance/guard | Named test file and killing assertion | Mutant |
|---|---|---|
| Both shipped modules load and forward through loader | new `packages/plugins/tests/channel.test.mjs`: each named module loaded, exact verb/args/result/refusal preserved | Remove registration, either export, or bypass dispatch; separate mutants. |
| Bad name, missing directory, symlink, missing export, wrong version | same new file, scratch loader tree following `tests/helpers.mjs:19` | One disabled guard per refusal, exercised specifically for channel. |
| Nonblocking matched snapshot | new plugins `channel.test.mjs`: names/modules stay paired across a switch; a blocked send does not prevent a concurrent list completing | Separate getters across an await; hold a selection lock across dispatch. |
| Only fixed operations | same file: unsupported verb refuses without calling transport | Remove operation allowlist. |
| CLI parity through actual loaded cli module | `packages/cli/tests/thread-cli.test.mjs`, fixture change described above; all current assertions retained; new `channel-cli.test.mjs` verifies module path | Bypass dispatch; change thread verb, arguments or result; each named killer retained in build table. |
| WebUI parity through actual loaded webui module | row-129 `packages/webui/tests/chat.test.mjs`, fixture change described above; retain `chat-browser.test.mjs` unchanged | Bypass module; change fixed REQUEST or mapped verb. |
| Independent off behavior | new `channel-cli.test.mjs` and `packages/webui/tests/channel-chat.test.mjs`: off CLI exit 3, off webui 503, both name recovery, zero chat/delivery/read calls; other channel still works | Ignore off selection; use wrong module; allow receive/read after off. |
| Both off and missing key recover | new `packages/cli/tests/channel-recovery.test.mjs`: real fixture organization writer + socket proof, plugins read/set work, next chat succeeds; stale revision refuses | Gate plugins management behind channel; cache selection forever; invent missing-key defaults. |
| Proof path, bytes, import/caller set unchanged | new `packages/bus/tests/channel-proof.test.mjs`; compare four baselines, source imports and actual fixture socket admission; direct no-proof request refuses | Substitute shim path; skip authenticateHuman; introduce module proof import/spawn. Mutants run only in detached fixtures. |
During implementation list each actual mutant and exact killing test in BUILD.md;
the table above is the design inventory, not a claim that any test has run.
Dewey reviews; Sage gates plugins, CLI, bus, WebUI and all shell suites. Request
the test slot before any run. Claim only after 129 is done and this design is
accepted by Sage and Mos. No live settings change or re-pin is part of the build.
## Design revision — sage-0766 / sage-0768
Sage accepted absent-key-off and the fixture-only exception. Mos subsequently
accepted the original design with those conditions (sage-0769 / mos-0768). Added operator recovery text, removed dispatch locking,
and proposed one matched snapshot getter because the loader has no such API.
HTTP inspection found that the existing chat-http.mjs table owns the mapping,
so propose that local edit instead of serve.mjs. Recheck every row-129 reference
against its landed tree after its test-only round 2. Row 164 remains unclaimed.
2026-10-18 16:46:44 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 164 #1661 design revision | recorded sage-0766 and Mos acceptance via sage-0769; recovery text, fixture-only additions and Jason-after-activate cut-over; proposed atomic snapshot getter and chat-http-local 503 mapping to Sage; row 129 r2 first, no code/tests.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.