PRD 1.12: ids for the Q176 items from the approved designs (REQ-USER-3, REQ-USER-4, REQ-LIFE-1, REQ-KILL-1, REQ-LIB-1 to 7), the Risks line and the REQ-DEC-3 title (mos-1229, Vikunja 462)

This commit is contained in:
jason.woltje committed 2026-10-21 17:14:24 -05:00
1 parent 73222011f2
commit 3576afffb3
4 files changed
+209 -12

No files matched your search

+192 -6
View File
@@ -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).
+1 -1
View File
@@ -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/);
+13 -2
View File
@@ -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/<name>.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" }
+3 -3
View File
@@ -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 });
}