diff --git a/docs/plans/2026-10-04_slice-1.md b/docs/plans/2026-10-04_slice-1.md new file mode 100644 index 00000000..ae0773a3 --- /dev/null +++ b/docs/plans/2026-10-04_slice-1.md @@ -0,0 +1,540 @@ +# Slice 1: the first working system (2026-10-04) + +Status: written by Sage, lead, for Jason's acceptance. Each `##` section +below is the brief for one queue row, in the shape set by +`docs/plans/BRIEF-TEMPLATE.md`. This first section covers what all the +rows share. + +## What slice 1 is + +The foundation direction page (`docs/plans/2026-10-04_foundation-direction.md`, +lead decisions 43 and 44) describes it. Jason opens `mosaic` in a terminal +and talks to the PM role. The PM files a task in Vikunja, linked to a +PRD requirement, and assigns it to the coder role. The stack launches +that agent with the role's contract, variables and credentials. The agent +works. A gated question reaches Jason's inbox, and he answers it in one +word. The task is reviewed and closed. The CLI and the WebUI both show +the trail, and every step is an event in the bus. + +Acceptance is the PRD's: Jason gives one-sentence requests in the CLI, +and Mosaic Stack builds pieces of itself from them, five pieces in a row. +For each piece, only gated decisions reach Jason, and every step leaves a +trail in the CLI and the WebUI (row S7). + +### Sources + +- PRD: `docs/prd/mosaic-stack.md`, draft 0.4. Every row cites its + requirement ids. If an id changes before Jason approves the PRD, Sage + updates this brief and re-pins the rows. +- Data model: `agents/darkwing/work/slice1-data-model-2026-10-04.md`, + plus addendum A and addendum B. Where they disagree, B wins over A, + and A wins over the note. +- Vikunja facts: `agents/researcher/work/2026-10-04_vikunja-pocketid.md` + and `agents/researcher/work/2026-10-04_vikunja-probes.md`. +- Harness facts: `agents/filbert/work/meta-harness-survey-2026-10-04.md`. +- Schema: `agents/darkwing/work/slice1-proto/schema-v2.sql`, which + becomes schema v3 with addendum B section 5 (lead decision 52, B3) + before row S2 starts. +- Rulings: lead decisions 43 to 53. + +### What is not a wall in slice 1 + +Agents run as Jason's OS user (PRD round 3, 3A). Any agent process can +read or write anything Jason's user can, including the bus file, the +broker's socket and the token files. The broker, the verbs and the hooks +are rules a well-behaved agent follows. They don't stop a hostile one. Two +things do hold in slice 1: +- agents never receive a service token, so a role can act in Gitea or + Vikunja only through the broker, which checks its authority map; +- the harness's tool limit, on the harnesses where row S0 proves it. + +Containers come after slice 1. No document in this slice may call a hook +or a broker check "enforcement" without that qualifier. + +Model-provider login (Claude, the Pi providers) stays as it is today, +because slice 1 has no container to isolate it in. REQ-CRED-1 and +REQ-CRED-2 cover the service tokens, Gitea and Vikunja. + +### Field table + +From addendum B section 4. The broker's task verbs enforce it (REQ-TASK-1). + +| Field | Writer | Action | Vikunja call | +|---|---|---|---| +| title, description | pm at create; after assignment only through a resolved `task.scope.change` | `task.create`, `task.scope.change` | `POST /projects/{p}/tasks`; `PATCH /tasks/{t}` | +| due date, priority | pm | `task.schedule`, `task.priority.change` | `PATCH /tasks/{t}` | +| percent done | the assigned role | `task.update.assigned` | `PATCH /tasks/{t}` | +| state (bucket) other than `done` | the assigned role | `task.update.assigned` | `PUT /projects/{p}/views/{k}/buckets/{b}/tasks` | +| `done` | pm, after a review verdict | `task.close` | the same move, into the `done` bucket | +| assignee | pm | `task.assign`, `task.reassign` | `POST /tasks/{t}/assignees`; `DELETE /tasks/{t}/assignees/{u}` | +| labels | pm | `task.create`, `task.schedule` | `POST /tasks/{t}/labels`; `DELETE /tasks/{t}/labels/{l}` | +| relations | pm | `task.create`, `task.schedule` | `POST /tasks/{t}/relations`; `DELETE /tasks/{t}/relations/{kind}/{other}` | +| comments | any role on the task, append only | `task.update.assigned`, `message.send` | `POST /tasks/{t}/comments` | + +A `PATCH` body carries only `title`, `description`, `due_date`, +`priority` and `percent_done`, and only the keys the action owns. Vikunja +answers 200 to a `bucket_id` in a `PATCH` and doesn't move the task, so +the broker refuses that key before sending. Reopening is a person's act. + +### Rows and order + +| Row | Piece | Owner | Reviewer | After | +|---|---|---|---|---| +| S0 | Harness probe matrix | filbert | darkwing | none | +| SR | Install runbook for slice 1 identities | sage | darkwing | none | +| S1 | Roles v2, business and project files, variable layers | darkwing | filbert | none | +| S2 | The bus and the broker core | rocko | darkwing | schema v3 | +| S3 | Tasks: the Vikunja adapter, broker task verbs and sync | darkwing | filbert | S1, S2, SR | +| S4 | The `mosaic` CLI: inbox, decide, tasks, agents, trail | rocko | filbert | S2, S3 | +| S5 | WebUI: inbox, tasks, agents and trails; CHAT-03 Gate E | dewey | darkwing, filbert | S4 | +| S6 | Meta-harness and launching; the PM moves off T3 | filbert | darkwing | S0, S1, S3 | +| S7 | Acceptance: five pieces in a row | sage | none | S5, S6 | + +Rows S0, SR and S1 start at once. S2 starts once Darkwing lands schema v3 +in `slice1-proto/`. Every row's gate is a reviewer's approval on the +row's issue plus the named suites green on an index export, and Sage +commits. Row S7's gate is Jason's. + +Every row emits the events its steps produce (REQ-EVT-1), using the +closed kind list in the schema. A row that needs a new kind changes the +schema through row S2's owner and the CTO's review. + +## Slice 1 S0: harness probe matrix (Pi and Claude Code hooks, pinned versions) + +### Problem + +REQ-HARN-2 says a probe run against the live harnesses must pass before +any build relies on a block. Filbert's survey (section 9) checked Pi's +`tool_call` block and Claude's hook docs against source and docs, but ran +nothing. It also found that Claude command hooks at a mistyped path leave +the gate silently off, and that a Codex hook that crashes doesn't block. +Row S6 can't choose which layer stops what until these are measured. + +### Owner and reviewer + +- Owner: filbert, who wrote the survey. +- Reviewer: darkwing. + +### Files owned + +- `agents/filbert/work/slice1-probes/` (scripts, outputs, the matrix). +- Nothing outside it. The probes run in a scratch directory under + `$HOME`, not `/tmp`, with no service tokens and no network beyond the + model providers the harnesses already use. + +### What ships + +A matrix for Pi 0.85.1 and Claude Code 2.1.289, each at the pinned +version, with one row per case: +1. a gate that blocks; +2. a gate that crashes (throws, or exits nonzero other than the block + code); +3. a gate at a missing path; +4. a gate that times out; +5. a `bash` route to the same action as a blocked tool; +6. for Claude Code, an Agent SDK callback hook that throws (the survey's + "not established"). + +Each cell records the command, the exact outcome (tool ran or didn't, +the message the model saw) and whether the harness fails closed. The +matrix ends with one line per case saying which layer slice 1 can rely on: +the tool limit, the hook, or neither. Codex is out of scope until after +v1. + +### Out of scope + +- Building the generator or any adapter (row S6). +- The Vikunja points still unverified in addendum B section 7. Row S3 + probes those before relying on them. + +### Gate + +Darkwing approves the matrix on the row's issue. Sage reads the +"rely on" lines and copies them into row S6's brief section before S6 +starts. + +## Slice 1 SR: install runbook for slice 1 identities + +### Problem + +REQ-CRED-1 (round 3, 1A and 2A) has Jason create each role's tokens by +hand. No runbook exists, and the identities are new: four Gitea bot users, +four Vikunja `bot-` users and one read-only `bot--sync`. +Vikunja tokens must expire, and Vikunja accepts a past expiry date. +Gitea tokens don't expire. + +### Owner and reviewer + +- Owner: sage. +- Reviewer: darkwing, for the Vikunja scope map. + +### Files owned + +- `docs/guides/slice-1-identities.md`. +- A business file template, `templates/business/mosaic-stack.example.json`, + with credential references only, coordinated with row S1's schema. + +### What ships + +A runbook Jason follows in about 20 minutes: +- creating pm-bot, cto-bot, coder-bot and reviewer-bot in Gitea, each + with a token at the narrowest scope that does that role's work, and + each token written to a 0600 file outside the repository; +- creating the Vikunja owner account (a deployed bundled instance or an + existing one, REQ-TASK-3), the four role bots and the sync bot, the + project shares (write for roles, permission 0 for sync), and one token + per bot with the scope map from addendum B section 2 and an explicit + expiry date; +- the business file entries that reference each token file; +- how to check each token with `stat` only, and a `mosaic` check that + calls the broker's startup scope probe once row S3 exists; +- rotation: what to do before a Vikunja token expires, and the Gitea + rotation step with a record. + +No token value appears in the runbook, a command line or a URL. + +### Out of scope + +- Any script that creates users or tokens. Minting is out of v1. +- Running the runbook. Jason does that, and row S3's live tests wait for + it. + +### Gate + +Darkwing approves the scope tables. Jason runs it, and the broker's +startup probe passes for every identity. + +## Slice 1 S1: roles v2, business and project files, variable layers + +### Problem + +`roles/` holds `conductor-policy.json` and `researcher.json` at version +1, with tool and network ceilings only. No business roles exist, no +authority map, no credential needs, and no variable layers beyond +`~/.config/mosaic-dev/config.json`. REQ-ROLE-1 to 4 and REQ-VAR-1 and 2 +need all of these. + +### Owner and reviewer + +- Owner: darkwing (CTO), who designed them (note sections 1 and 2). +- Reviewer: filbert. + +### Files owned + +- `roles/pm.json`, `roles/cto.json`, `roles/coder.json`, + `roles/reviewer.json` (new, version 2). +- The role validator and `resolve-role` in `scripts/mosaic-task.mjs`, to + accept version 2 while version 1 files keep working. +- `packages/business/` (new): the business file and project file + loaders, the variable key registry (type, allowed layers, merge rule) + and the resolver. +- `contracts/` only if the note's closed action vocabulary must be + image-baked. Darkwing says which in the build packet, and a contract + change needs Sage's review. +- `docs/TOOLS.md` entries for any new command. + +### What ships + +- The role schema v2: contract, authority map over the closed vocabulary + (unlisted means gated), credential needs by service and scope, and the + version 1 ceilings kept. +- The business file at `~/.config/mosaic-dev/businesses/.json`: + role instances (PM held by Sage, CTO by Darkwing, coder, reviewer), + arbiters, credential references, business variables, and the `launch` + block (lead decision 49). The stack reads it and never writes it. A + missing or invalid file fails closed. +- The project file at `/.mosaic/project.json`. +- The resolver: most specific layer wins for plain values, limits + intersect, an unknown key or a key at a layer it isn't allowed in is + refused. +- Tests in `packages/business/tests/`, plus `test-config.sh` and + `test-task.sh` green. + +### Out of scope + +- Role claims and the one-holder rule (row S2, they live in the bus). +- Launching anything (row S6). + +### Gate + +Filbert approves on the row's issue. Suites green: `packages/business` +tests, `test-config.sh`, `test-task.sh`, `test-conductor.sh`. + +## Slice 1 S2: the bus and the broker core (decisions, messages, role claims, events) + +### Problem + +Decisions don't exist as objects. Escalations are chat lines, the +waiting-on-jason state and "Input needed:" lines. Messages are addressed +to T3 thread ids, not roles. REQ-DEC-1 to 3, REQ-MSG-1, REQ-ROLE-2 and +REQ-EVT-1 need a store and a single writer. + +### Owner and reviewer + +- Owner: rocko. It needs no credentials: the broker's token handling is + built and tested here with fixture tokens, and real tokens arrive in + row S3. +- Reviewer: darkwing, who wrote the schema and the prototype. + +### Files owned + +- `packages/bus/` (new): the store over `node:sqlite`, the broker + process, its local socket protocol, the verbs, tests. +- `/bus/` at runtime, created by the broker on first start, + 0700. + +### What ships + +- `bus.sqlite` from schema v3, with the open-time digest check, WAL, and + every table append-only. +- The broker: the only writer. It stamps role and run on every write + from the launch record. It holds tokens in memory only, read from the + business file's references, and never logs or writes them (REQ-VAR-2). +- Verbs: raise, route and resolve a decision; send a message to a role + and deliver it to the current holder; claim and release a role + instance; emit an event. A verb whose action is gated refuses unless + it cites a resolved decision. +- Routing by class (REQ-DEC-2): routine and within-role are logged; + cross-role goes to the business's arbiter; gated goes to the human. +- Human resolution only from a CLI process outside any agent run + (REQ-DEC-3). `launch.revoked` and `launch.restored` are written only on + that path (lead decision 50). +- Tests covering every refusal the prototype shows, plus the broker + protocol, on Node 24 and Node 26. + +### Out of scope + +- Vikunja (row S3). The `task_snapshots` table ships in the schema, and + row S3 writes it. +- Delivery to Discord and the digest (row S4 owns the outbound side). + +### Gate + +Darkwing approves on the row's issue. Suites green: `packages/bus` tests +on Node 24 and Node 26, and every `scripts/test-*.sh`. + +## Slice 1 S3: tasks (the Vikunja adapter, broker task verbs, sync) + +### Problem + +Tasks live in the queue, which is built for agents and has no dates, +assignees Jason can see, or UI. REQ-TASK-1 to 4 put tasks in Vikunja +through the broker. The probes showed what Vikunja actually does: wrong +scope names in addendum A, a cursor that misses column moves and +deletions, `If-Match` not enforced, and 401 for both an expired token and +one out of scope. + +### Owner and reviewer + +- Owner: darkwing, who ran the probes behind addendum B. +- Reviewer: filbert. + +### Files owned + +- `packages/tasks/` (new): the Vikunja v2 client, the broker's task + verbs, the poller, the reconcile and the startup checks. +- `packages/bus/` only to register the task verbs, coordinated with row + S2's owner. +- `packages/tasks/deploy/vikunja/`: the bundled option, the unmodified upstream + image pinned by digest, bound to 127.0.0.1 (REQ-TASK-3). No Vikunja code + enters the repository. + +### What ships + +- Before relying on them, probes of addendum B section 7's open points + on a scratch instance, recorded in the build packet. The first is + which labels `GET /labels` shows a bot. Until that's answered, the + business file lists label ids. +- Task verbs that follow the field table, refuse any other `PATCH` key, + compare before writing, and write only with the acting role's token. +- Sync with the read-only sync bot: every 30 seconds, the open-task + kanban listing plus the `updated` cursor; an hourly full reconcile; + `task_snapshots` rows for every read and write; `task.changed.external` + for a person's edit. +- Startup checks: each token's scopes probed against a task id that + doesn't exist, `expires_at` checked locally, the kanban view's + `done_bucket_id` checked. Any failure fails closed. +- A task can't be created without a requirement id (REQ-TASK-1). The + queue keeps the agents' lock, review rounds and audit log, and no field + syncs both ways (REQ-TASK-4). +- Tests against a recorded fake of the v2 routes, plus one live run + against the instance from row SR. + +### Out of scope + +- Webhooks. Slice 1 has none. +- Pocket ID and SSO (after v1). + +### Gate + +Filbert approves on the row's issue. Suites green: `packages/tasks` +tests, `packages/bus` tests, every `scripts/test-*.sh`, and the live run's +log in the packet with no token value in it. + +## Slice 1 S4: the `mosaic` CLI (inbox, decide, tasks, agents, trail) + +### Problem + +Jason has no front door (REQ-CLI-1). `scripts/mosaic` dispatches to +`seat` and `queue` only. Blocking gated decisions have no route to him, +and nothing sends a digest (REQ-DEC-4). + +### Owner and reviewer + +- Owner: rocko. +- Reviewer: filbert. + +### Files owned + +- `scripts/mosaic` (the dispatcher). +- `packages/cli/` (new): `mosaic inbox`, `mosaic decide