diff --git a/docs/prd/mosaic-stack.md b/docs/prd/mosaic-stack.md index c7cc97695..80abaa92f 100644 --- a/docs/prd/mosaic-stack.md +++ b/docs/prd/mosaic-stack.md @@ -1,6 +1,6 @@ # PRD: Mosaic Stack -- Status: approved, version 1.11. Jason approved draft 0.4 unchanged as +- Status: approved, version 1.12. Jason approved draft 0.4 unchanged as 1.0 on 2026-10-05 (lead decision 61). They approved version 1.1, the Organization requirements, in decision 03130d08-ddce-480d-a601-e59f64744fc3 on 2026-10-14. They approved version 1.2, the chief of staff, the @@ -33,7 +33,16 @@ goal. It updates the lines that the 10-27 rulings settle and adds REQ-INST-3. Mos's reading of Q211 sets the path: one seat drafts, a second seat reviews, and Sage lands it. So there is no in-Stack - prd.approve for it. + prd.approve for it. Version 1.12 adds requirement ids for the Q176 + items the PRD lacked, drafted from the approved designs, and updates + two stale lines. It takes the same path as 1.11 (mos-1229, + vikunja:19/462). +- Version 1.12 source, Mos's answers to the 1.11 questions in mos-1229 on + 2026-10-21: "M2: agreed. 1.11 adds no ids for the Given list or Case 2 + items. You draft 1.12 from the approved #414 and #416 designs as soon as + Della approves 1.11. Vikunja 462." and "M4: fold both items into 1.12, + Vikunja 462." The two M4 items are the slice 1 Risks line and the + REQ-DEC-3 title in `packages/tasks/requirements.json`. - Version 1.11 source, Jason's answer to Q23 in T3, relayed by Mos on 2026-10-21 (DECISIONS Q211, mos-1227): "It appears that I have expanded the requirements for v1. My recent goal for the 10-27 release is the @@ -1012,6 +1021,130 @@ Added in version 1.1 (decision 03130d08). post-v1. (Version 1.11; Q179, Q180, Q211. The 1.10 text ended "This is post-v1.") +Added in version 1.12 (Q176, Q211). Each one names the approved design it +is drafted from and the Q176 item it covers. + +- **REQ-USER-3.** An organization can have more than one human. The + organization file's human is the owner. The owner adds other humans as + users and gives each a grant per project and instance, at one of two + levels. A collaborator creates threads and sends in them. A viewer only + reads threads shared with it. Only the owner adds or removes users, + changes grants and restructures the organization. The bus takes a + human's identity only from something it verifies, such as the Console's + own OIDC sign-in, never from a process's own claim. (Row 193 design r5, + e2d161e2e on `morgana/row193`, approved by Duckworth in duckworth-0018, + sections 3 and 4. Q176 Given "RBAC enforcement"; Case 1 "Additional + users can be configured for access".) +- **REQ-USER-4.** A thread is private to the user who owns it unless that + user shares it. A share is read-only, to a named user, to the + organization's owner, or to everyone granted on the thread's project. + REQ-USER-2 still holds for writes. A share never widens a grant: it + counts only while the reader's grant still covers the thread. A revoke, + an expiry or an unshare ends the reader's access on the next request. A + user's messages stay private from the owner. What the thread's agent + does with the owner's authority stays visible to the owner: its + decisions, inbox entries, claims, tasks and project files. (Row 193 + design r5, sections 5.3, 5.6 and 5.7. Q176 Case 1 "Shared and private + agent sessions available per user; RBAC".) +- **REQ-LIFE-1.** The user creates, launches, terminates, regenerates and + deletes agents whenever they choose, from the CLI and from the Console. + Regenerate starts a fresh session with the same identity and role. + Each verb is a human verb behind the human proof, and it acts on its + own instance only. Every launch goes through the one launch path, so the + agent limits and the kill switch (REQ-KILL-1) apply. Delete leaves run + records write-once. Fire and delete revoke the agent's credentials + first, and a failed revoke stops the verb and leaves the organization + file unchanged. Create, delete, hire, fire and veto changes take effect + without a bus restart. Until REQ-USER-3 is built, these verbs are + owner-only. (Row 194 design r4, 7d0788236 on `honker/row194`, approved + by Webby in e1e64ff01, sections D1, D3 and D4, and the brief's items 7 + and 8. Q176 Given "User and main agent (chief of staff, or other main) + can create and spawn agents" and "User maintains ability to terminate, + regenerate, or create agents via WebUI or CLI ad-hoc".) +- **REQ-KILL-1.** One CLI command and one Console button stop all + processing on an instance at once, and it stays stopped until a human + clears it: + - agent verbs that start or continue work refuse with a fixed code. + Reads and the human's own verbs, the clear included, still work; + - every running managed session and task run gets SIGTERM, then SIGKILL + after a bounded wait, and each stop is an event; + - Discord stops and can't restart; + - every launch path refuses, the chief of staff's hires included. + + The switch survives a bus restart and a reboot, and it stops one + instance only. Only a human turns it on or clears it, through the human + proof, and each act is one event with who and when. (Row 191 design r2, + approved by Dewey in comment 29473, on the brief + `docs/plans/2026-10-20_kill-switch.md`. Q176 Given "'Kill switch' is + available to stop all processing immediately".) + +### Creative library + +Added in version 1.12 (Q176 Case 2, Q211). Drafted from the Case 2 +design r11, fb20a61b4 on `gosalyn/case2-design`, approved by Webby in +82f50d127, with the rulings recorded after r11 up to 624212842. The +library is the user's own writing repository. + +- **REQ-LIB-1.** A library project maps the user's writing repository + onto universes, books, entity types such as character and place, the + manuscript, and playgrounds. The mapping sits in the organization file + and fits the user's own folder layout. Only the human changes it, and + an agent's organization write that changes it is refused. So + `universe.book.{character,place,...}` reads as universe, book folder, + type folder. (Design 1.1 and M9. Q176 Case 2 "Data is stored in + little-endian organizational scheme".) +- **REQ-LIB-2.** Playgrounds are never confused with canon. A file's scope + comes from its path alone: a Markdown file under a playground prefix is + playground, any other Markdown file in a book folder is canon, and + everything else is unmapped and never returned. Lookups return canon + unless the caller asks for playground. An agent can't cite a playground + file as a fact, a finding or a correction. The Console shows each + cite's scope. (Design 1.2, 5.2 and 7.4. Q176 Case 2, the playground + items.) +- **REQ-LIB-3.** Universes stay separate. Every lookup names one universe, + and its results never leave it. A crossover is a hybrid universe that + only a human creates, by editing the organization file. It names two or + more source universes. Its lookups see its own files plus the sources' + canon, minus the exclusions the human sets. Exclusions and pinned values + are checked in code. The continuity agent reads the human's other + constraints and cites them. (Design 1.4 and 3. Q176 Case 2, the + crossover items.) +- **REQ-LIB-4.** Git holds every change. The user commits and pushes with + their own tools, today Obsidian with obsidian-git. The bus keeps a + mirror of the remote, fetched with a read-only token every 60 seconds + and before any lookup when the last fetch is older than that. It only + fast-forwards. A force-push is held as `library-diverged` until a human + accepts it. Agents read only the mirror's served commit and never hold + a copy of their own, so they see the user's push within about a minute. + Bus writes and pushes come in later rows (C2-02, C2-03b). (Design D5, + D6, 7.1, 7.2 and 7.5. Q176 Case 2 "Git is used for all changes -- local + and remote" and the sync items.) +- **REQ-LIB-5.** Agents check the library through typed lookup tools: + find, get, search, manuscript and universe. Each answer names the + commit it read. Get returns a file's links out and links in, so the + connections are visible to agents. The user sees them in Obsidian's + graph and backlinks. (Design 3 and 7.1. Q176 Case 2 "The agents have + tools to check character, place, and other data", the visualization + item and "Connections are always visible".) +- **REQ-LIB-6.** The creative roles are ideas, research, proofreader, + fact-checker and continuity, plus a companion role that the user's own + named agents run as. No creative agent can write to the user's + repository. Its tools are the lookups and a message to the human, and + research and companion also fetch web pages through the broker. Every + message an agent sends the user is a short list of items. Each item + cites a line of the repository at a commit, or a page the agent + fetched, and the broker checks every cite before it stores the message. + The user sees only stored messages, on every channel. (Design 4, 5.1, + 5.2 and 5.3; Q180 for Bean and Chet. Q176 Case 2, the agent list and + "User sets strict guardrails for agent capabilities".) +- **REQ-LIB-7.** The user talks to several creative agents in one chat. A + room holds one thread per creative agent in a project. A message goes + to every agent in the room, or only to the agents named by @tags at its + start. An unknown tag is refused, and nothing is sent. The room shows + the threads as one timeline, and each agent sees only its own thread. + (Design D4 and 6, row C2-09b. Q176 Case 2 "converse with N agents in + one chat session with @tagged messages".) + ### Installer Added in version 1.4 (decision 1ea053cc). @@ -1302,13 +1435,66 @@ latest-by date and a hard target (Q176). - Which OIDC provider the two v1 VMs use is open (vikunja:19/461). REQ-INST-2 moves into v1 if Jason picks the bundled Pocket ID for either instance. +- Version 1.12: + - The 1.11 Risks line was: "In slice 1, agents run as Jason's OS user + (round 3, 3A). They can write anything that user can write. Until + seats run in a container, which comes next, the broker is a rule they + follow, not a wall. What does hold in slice 1 is that agents never + hold service tokens." + - The REQ-DEC-3 title in `packages/tasks/requirements.json` was + "Authorized-human resolutions come only from a mosaic CLI session + outside any agent run". It left out the Console session that 1.9 + added. 1.12 changes the title only. The requirement text is + unchanged. + - Q176 items the PRD already covered, so 1.12 adds no id for them: + projects and threads (REQ-ORG-2, REQ-THREAD-1, REQ-THREAD-2); + federation and shared access across instances (REQ-FED-1); custom and + templated roles assigned to agents (REQ-ROLE-1, REQ-ROLE-3, + REQ-TPL-1); the credential store (REQ-CRED-1 to REQ-CRED-4, + REQ-GRANT-1 to REQ-GRANT-4); each agent's own git credentials + (REQ-LAND-2, REQ-CRED-1); the chief of staff's hire and fire with + the user's per-agent forbid, and restructuring (REQ-COS-3, + REQ-ROLE-3, REQ-USER-3); the main user and the assistant + (REQ-ASSIST-1). + - Q176 items with no approved design, so 1.12 adds no id for them: + several independent sessions per thread, a compaction count, and + clearing an agent's context to start a new session. They are MVP + plan T1 item 7, which has no queue row yet. Several sessions per + thread differs from REQ-ROLE-2 and REQ-THREAD-2, where each thread is + one assistant session. Row 194 refuses regenerate for a thread + instance and leaves clearing its context to that item. + - Writing in the WebUI: Q176 allows "WYSIWYG, Obsidian, or some other + method". The Case 2 design keeps Obsidian (design 7.1). The WYSIWYG + editor is the Q195 and Q196 add-on and doesn't gate 10-27. + - The Case 2 design's playground prefixes all sit inside a book folder + (design 1.1). Q176 also asks for universe-level playgrounds "where + new universes can be created". The design doesn't state one, so + REQ-LIB-2 doesn't name the level. + - The design's 10-27 slice (design 9.2) leaves out rooms with @tags + (C2-09b, C2-10c), hybrid universes (C2-15), bus writes and the + Console editor (C2-02, C2-03b, C2-11a, C2-11b, C2-19). Q211 keeps + every Q176 item in v1, and only Jason cuts one. + - REQ-PROJ-1's "no remote" covers project record repositories. A + library project is a separate kind with no record repository, and its + mirror fetches from the remote (design 2.1, 7.2). + - REQ-LIB-6 has a stated residual: an `idea` item carries free text, + up to 5 items of 200 characters per reply, and code can't tell story + text from advice (design 5.1). Accepting it is Jason's or Melanie's + (design section 10, step 8). + - Where each id is built: REQ-USER-3 and REQ-USER-4 are rows 193a to + 193d (#1691), with FED R1 to R6. REQ-LIFE-1 is row 194 (#1692) and + its follow-up row 239. REQ-KILL-1 is row 191 (#1689). The REQ-LIB ids + are the C2 rows in `docs/plans/2026-10-20_case2-rows.md` (#416). This + amendment adds the requirements only. ## Risks and open questions -- In slice 1, agents run as Jason's OS user (round 3, 3A). They can - write anything that user can write. Until seats run in a container, - which comes next, the broker is a rule they follow, not a wall. What - does hold in slice 1 is that agents never hold service tokens. +- On each v1 instance, agents run as that instance's OS user, without + sudo (Q181). They can write anything that user can write. Until seats + run in a container, which comes after v1, the broker is a rule they + follow, not a wall. What does hold is that agents never hold service + tokens (REQ-CRED-1). On dragon-lin, dev agents run as Jason's OS user, + and Jason accepts that risk (Q182). (Version 1.12.) - Vikunja doesn't enforce `If-Match`. The broker compares before it writes, and a person editing in the same moment can still be overwritten in an owned field (lead decision 51). diff --git a/packages/bus/tests/requirements.test.mjs b/packages/bus/tests/requirements.test.mjs index f0b8733a9..2f162a186 100644 --- a/packages/bus/tests/requirements.test.mjs +++ b/packages/bus/tests/requirements.test.mjs @@ -45,7 +45,7 @@ test('requirements returns the committed list to a holding role session, and no const pm = agent('pm'); const list = call(b, pm, 'requirements'); assert.deepEqual(list, loadRequirements()); - assert.equal(list.requirements.length, 71); + assert.equal(list.requirements.length, 82); assert.deepEqual(call(b, agent('coder'), 'requirements'), list); assert.throws(() => call(b, human, 'requirements'), /agent-required/); assert.throws(() => call(b, reader, 'requirements'), /read-only/); diff --git a/packages/tasks/requirements.json b/packages/tasks/requirements.json index 2a9a56e29..294dd7575 100644 --- a/packages/tasks/requirements.json +++ b/packages/tasks/requirements.json @@ -1,6 +1,6 @@ { "prd": "docs/prd/mosaic-stack.md", - "version": "1.11", + "version": "1.12", "requirements": [ { "id": "REQ-ROLE-1", "title": "Each role is a reviewed roles/.json: contract, authority map, credentials" }, { "id": "REQ-ROLE-2", "title": "One holder per role instance, one per thread for the assistant; the chief of staff may end a run, others need a decision" }, @@ -26,7 +26,7 @@ { "id": "REQ-GRANT-4", "title": "The user, the assistant in its organization or the tester on a dev instance revokes a grant at once" }, { "id": "REQ-DEC-1", "title": "Every decision is an append-only record: raiser, class, options, resolver, times" }, { "id": "REQ-DEC-2", "title": "Decisions route by class: logged, to the domain arbiter, or to the human" }, - { "id": "REQ-DEC-3", "title": "Authorized-human resolutions come only from a mosaic CLI session outside any agent run" }, + { "id": "REQ-DEC-3", "title": "Authorized-human resolutions come only from a mosaic CLI or signed-in Console session outside any agent run" }, { "id": "REQ-DEC-4", "title": "A blocking gated decision reaches an authorized human at once; the rest go in a daily digest" }, { "id": "REQ-DEC-5", "title": "An approval outlives its run, binds to the decision, instance and thread, and is consumed once" }, { "id": "REQ-DEC-6", "title": "A spec commit never reopens a settled decision" }, @@ -70,6 +70,17 @@ { "id": "REQ-THREAD-2", "title": "The user creates projects and named threads; each thread is its own assistant session with kept history" }, { "id": "REQ-TPL-1", "title": "Templates for agent roles and for organization types" }, { "id": "REQ-FED-1", "title": "Federation between installs: person-to-person in v1, agent-to-agent post-v1" }, + { "id": "REQ-USER-3", "title": "Several humans per organization: the owner, and users with collaborator or viewer grants per project and instance" }, + { "id": "REQ-USER-4", "title": "A thread is private to its user unless shared read-only; a share never widens a grant and ends on revoke" }, + { "id": "REQ-LIFE-1", "title": "The user creates, launches, terminates, regenerates and deletes agents from the CLI and the Console" }, + { "id": "REQ-KILL-1", "title": "One human command or Console button stops all work, runs, Discord and launches on an instance until a human clears it" }, + { "id": "REQ-LIB-1", "title": "A library maps the user's writing repository onto universes, books, types and playgrounds; only the human changes it" }, + { "id": "REQ-LIB-2", "title": "Scope comes from the path; lookups default to canon and a playground file is never cited as canon" }, + { "id": "REQ-LIB-3", "title": "Universes stay separate; a crossover is a hybrid universe only a human creates, reading its sources' canon" }, + { "id": "REQ-LIB-4", "title": "Git holds every change; the bus fast-forwards a read-only mirror every 60 seconds and agents read only that" }, + { "id": "REQ-LIB-5", "title": "Agents check the library through typed lookup tools that name the commit and show links out and in" }, + { "id": "REQ-LIB-6", "title": "Creative agents can't write the repository; every message to the user is short cited items the broker checks" }, + { "id": "REQ-LIB-7", "title": "One chat room with several creative agents, addressed by leading @tags; an unknown tag sends nothing" }, { "id": "REQ-INST-1", "title": "The installer offers optional components: pinned, health-checked, removable, switched on in config; first is SearXNG" }, { "id": "REQ-INST-2", "title": "With no OIDC provider to configure, the installer deploys a bundled Pocket ID, Q203 (post-v1)" }, { "id": "REQ-INST-3", "title": "One-command install of a named instance from a profile, and one-command upgrade with backup and rollback" } diff --git a/packages/tasks/tests/requirements.test.mjs b/packages/tasks/tests/requirements.test.mjs index 7959549ad..a4fe63bab 100644 --- a/packages/tasks/tests/requirements.test.mjs +++ b/packages/tasks/tests/requirements.test.mjs @@ -18,10 +18,10 @@ test("the list holds the PRD's requirement ids, in its order, at its version", ( const ids = [...prd.matchAll(/^- \*\*(REQ-[A-Z]+-[0-9]+)\.\*\*/gm)].map((m) => m[1]); // Any other mention of a bold id would be a bullet this pattern missed. assert.equal(ids.length, [...prd.matchAll(/\*\*REQ-/g)].length); - assert.equal(ids.length, 71); + assert.equal(ids.length, 82); assert.deepEqual(list.requirements.map((r) => r.id), ids); assert.match(prd, new RegExp(`^- Status: approved, version ${list.version.replace('.', '\\.')}\\.`, 'm')); - assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.11']); + assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.12']); }); test('the committed list loads frozen', () => { @@ -74,7 +74,7 @@ test('the schema refuses any other shape', () => { const twice = raw(); twice.requirements.push(structuredClone(twice.requirements[0])); writeFileSync(join(dir, 'twice.json'), JSON.stringify(twice)); - assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[71].id: REQ-ROLE-1 twice')); + assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[82].id: REQ-ROLE-1 twice')); } finally { rmSync(dir, { recursive: true, force: true }); }