diff --git a/docs/prd/mosaic-stack.md b/docs/prd/mosaic-stack.md index 5f5247dfd..0a8737963 100644 --- a/docs/prd/mosaic-stack.md +++ b/docs/prd/mosaic-stack.md @@ -1,6 +1,6 @@ # PRD: Mosaic Stack -- Status: approved, version 1.10. Jason approved draft 0.4 unchanged as +- Status: approved, version 1.11. 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 @@ -29,6 +29,24 @@ chose in Q203, relayed by Mos on 2026-10-21 (mos-1202). Version 1.10 adds REQ-INST-2, post-v1: the second sentence of Jason's Q203 answer, with Mos's reading, relayed by Mos on 2026-10-21 (mos-1202, mos-1204). + Version 1.11 records Jason's ruling Q211 of 2026-10-21: v1 is the 10-27 + goal. It updates the lines that the 10-27 rulings settle and adds + REQ-INST-3. 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. +- 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 + goal. The PRD may need to be updated to reflect this change. The PRD can + be considered partly stale." Mos's reading, which this version follows: + - The Console's own OIDC sign-in (REQ-WEB-2) is v1. + - v1 is the 10-27 goal: Q176 (the Given list and Cases 1 and 2) plus the + later rulings that put items on the 10-27 path, such as Q179, Q184, + Q187, Q188, Q189 and Q190. Where the PRD and those rulings differ, the + rulings win. + - The answer adds to v1 and removes nothing. Cutting a v1 item goes to + Jason. + - Rulings that keep an item off the 10-27 path still hold: Q186, Q198's + timing, Q203's dragon-lin line and Q208. REQ-INST-2 stays post-v1. - Version 1.10 source, the second sentence of Jason's Q203 answer quoted under 1.9 below, Mos's reading of it in mos-1202, and his ruling in mos-1204 on 2026-10-21 to add it as drafted and mark it post-v1 the @@ -190,13 +208,16 @@ and outstanding decisions don't collect anywhere. ## Users -For now the only user is Jason. The stack must still be installable by -outside users later, which means: +v1 has two users, each on their own instance: Jason on Case 1, the +software factory, and Melanie on Case 2, the creative writing hub (Q176, +Q211). The stack must still be installable by outside users later, which +means: - an installer; - no paths or names specific to Jason; - configuration through the variable layers. -(Round 1, answer D.) +(Round 1, answer D. The 1.10 text began "For now the only user is +Jason.") ## Problem statement @@ -228,14 +249,28 @@ decides it later. Self-hosted, on Jason's machines and homelab (round 1, "A first"). Cloud workers aren't ruled out later. +v1 runs as two instances on VMs on the PVE node: Case 1 on VM 1008 and +Case 2 on VM 1012. Each instance runs under its own OS user without sudo. +Jason's workstation, dragon-lin, is for dev work only. (Q180, Q181, Q211.) + ### Out of scope for v1 -Round 2, "all": -- businesses other than Mosaic Stack (SetSpark joins after v1); -- minting credentials (v1 hands out tokens Jason creates); +Round 2, "all", as version 1.11 updates it (Q211): +- businesses other than Mosaic Stack on Case 1 (SetSpark joins after + v1). Case 2's hub runs its own organization on its own instance, and + that is v1 (Q176); - Codex (v1 covers Pi and Claude Code, and Codex comes next); -- Pocket ID and SSO; -- access from beyond localhost. +- the bundled Pocket ID (REQ-INST-2, Q203). + +Version 1.11 moves three round 2 items into v1: +- Minting credentials. On the user's Approve, protected stack code makes + the key, the token or a seat's own Gitea user (REQ-GRANT-2; Q188, Q190). +- SSO. The Console runs its own OIDC sign-in (REQ-WEB-2; Q175, Q211). +- Access from beyond localhost. Each case runs on its own VM, and people + get access across the two instances (Q176, Q179, Q180). The Console's + own sign-in (Q175) comes first for any instance that a second user or + another instance can reach (Q176). A new listener reachable from outside + the host is still a `network.expose` hard wait (REQ-WAIT-1). ## Requirements @@ -401,6 +436,10 @@ Added in version 1.7: Since version 1.8, a token or key for a grant is made by the grant code on the user's Approve (REQ-GRANT-2), not by hand. Tokens that exist stay as they are. + + Since version 1.11, Gitea follows Q190: each agent seat gets its own + Gitea user through a grant (REQ-GRANT-1, REQ-GRANT-2), not a bot user + per role made by hand. Vikunja is unchanged. - **REQ-CRED-2.** A session that finds only founder credentials stops. Added in version 1.7: @@ -965,7 +1004,12 @@ Added in version 1.1 (decision 03130d08). in another's. Added in version 1.2. - **REQ-TPL-1.** Templates exist for agent roles and for organization types. -- **REQ-FED-1.** Installs can federate with each other. This is post-v1. +- **REQ-FED-1.** Installs can federate with each other. Person-to-person + federation is v1: a person gets access to another instance by signing in + to its Console (REQ-WEB-2), the broker enforces the grants that give + that access, and the access can be revoked. Agent-to-agent federation is + post-v1. (Version 1.11; Q179, Q180, Q211. The 1.10 text ended "This is + post-v1.") ### Installer @@ -988,6 +1032,20 @@ Added in version 1.4 (decision 1ea053cc). - Pocket ID is passkey-only for people. Agents keep per-role service tokens. +Added in version 1.11 (Q187, Q189, Q211): + +- **REQ-INST-3.** Install and upgrade are one command each. + - Install sets up a fresh host as a named instance with its config. The + per-case setup is installer config, a profile per case, not one-off + scripts. + - Upgrade backs up, moves to the new release, and rolls back if its + checks fail. + - Both are proven on a throwaway VM on the PVE node, install, upgrade to + the next release, then rollback, before either v1 VM gets the stack. + - Both v1 profiles enable the Console unit, so the Console starts at + install and at every boot (Q189). dragon-lin is unchanged. + - The container-based install comes after v1. + ## Acceptance criteria and success measures v1 is done when Jason gives one-sentence requests to the assistant, in a @@ -1002,6 +1060,11 @@ Version 1.5 adds Jason's Q167 acceptance run, `docs/acceptance/314.md`. It passes when one need goes from Jason's spec to a landed commit with only hard waits in his inbox. +Version 1.11 adds the 10-27 goal (Q176, Q211). v1 is also done only when +both instances are installed and handle their cases: Case 1, the +software factory, and Case 2, the creative writing hub. 2026-10-27 is a +latest-by date and a hard target (Q176). + ## Technical considerations - Vikunja v2.7.0 is AGPL-3.0. Its API tokens can't create users or mint @@ -1214,10 +1277,27 @@ only hard waits in his inbox. re-pin carries row 252. This amendment adds the requirement only. - Version 1.10: - REQ-WEB-2 (1.9) uses the Console's own OIDC sign-in from Q175. The - out-of-scope list above still names 'Pocket ID and SSO'. Jason hasn't - ruled on that line (vikunja:19/459). + out-of-scope list above then still named 'Pocket ID and SSO'. Jason + ruled on that line in Q211, and version 1.11 updates it + (vikunja:19/459). - The design row for REQ-INST-2 is row 255 (#1760). This amendment adds the requirement only. +- Version 1.11: + - The 1.10 out-of-scope list, under Round 2, "all", was: businesses + other than Mosaic Stack (SetSpark joins after v1); minting credentials + (v1 hands out tokens Jason creates); Codex (v1 covers Pi and Claude + Code, and Codex comes next); Pocket ID and SSO; access from beyond + localhost. + - Q211 names Q176's Given list and Cases 1 and 2 as v1. They are + tracked on Vikunja #414, #415 and #416. This version doesn't add + requirement ids for the items in them that the PRD lacks. + - The Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG + editor (Q195, Q196) is an add-on that doesn't gate it. + - Today the stack has an upgrade tool with activate, rollback and a + practice run, and nothing that sets up a fresh host (Q187). The + installer row is Vikunja #432, and the throwaway VM proof is #433. + - REQ-INST-3 replaces the hand start of the Console for the two v1 VMs + only (Q189, mos-1097 item 1). ## Risks and open questions diff --git a/packages/bus/tests/requirements.test.mjs b/packages/bus/tests/requirements.test.mjs index 8e093e7aa..f0b8733a9 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, 70); + assert.equal(list.requirements.length, 71); 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 335ee6439..2a9a56e29 100644 --- a/packages/tasks/requirements.json +++ b/packages/tasks/requirements.json @@ -1,6 +1,6 @@ { "prd": "docs/prd/mosaic-stack.md", - "version": "1.10", + "version": "1.11", "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" }, @@ -69,8 +69,9 @@ { "id": "REQ-THREAD-1", "title": "Threads live under projects" }, { "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 (post-v1)" }, + { "id": "REQ-FED-1", "title": "Federation between installs: person-to-person in v1, agent-to-agent post-v1" }, { "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-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 8d57802ac..7959549ad 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, 70); + assert.equal(ids.length, 71); 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.10']); + assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.11']); }); 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[70].id: REQ-ROLE-1 twice')); + assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[71].id: REQ-ROLE-1 twice')); } finally { rmSync(dir, { recursive: true, force: true }); }