docs: PRD version 1.5 under Q167 and the task 314 acceptance run (row 135, #1629)

Version 1.5 records Jason's Q167 ruling and added text as its source
block. It replaces REQ-DEC-2 routing: a gated decision citing the
project's current spec commit goes to the domain arbiter, and only an
uncited decision or a hard wait goes to the human. It adds the hard
waits (REQ-WAIT-1 to 3, with data.delete, network.expose and
account.create), projects and the spec file (REQ-PROJ-1 to 4), agent
landing (REQ-LAND-1), approvals that outlive the run (REQ-DEC-5),
settled decisions under a new spec (REQ-DEC-6), the four workflow roles
and proactive contracts (REQ-ROLE-5, 6), staff requests (REQ-COS-2),
assistant summaries (REQ-ASSIST-3) and name addressing (REQ-MSG-2).

docs/acceptance/314.md is the run Jason does in the Stack once the
rows are live.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-16 13:09:43 -05:00
co-authored by Claude Opus 5.5
parent c07c87fc22
commit 7473dcd2a0
5 changed files with 412 additions and 9 deletions
+152
View File
@@ -0,0 +1,152 @@
# Acceptance run for task 314 (Q167)
Jason runs this inside the Stack once rows 135 to 143 and 147 to 149 are
live. It checks PRD version 1.5: one need goes from his spec to a landed
commit, and only hard waits reach his inbox.
`<business>` is the business the bus host runs. `<project>` is the
project the need belongs to, `stack` for this run. Every command runs on
the host, as Jason.
The need is a `mosaic version` command that prints the release and the
bus schema.
## Before the run
- `mosaic bus status` says the host is live.
- `mosaic inbox` shows no open decision. If one is open, resolve or
supersede it first, so step 6 starts from an empty inbox.
- The organization has security-reviewer and merge-gate instances, added
through the chief of staff's organization change with Mos's go, and
both are in `launch.instances`.
- `arbiters.technical` is `cto`, which step 5 assumes.
## Steps
1. Create the project repository, if it doesn't exist yet:
```
mosaic project init <business> <project>
mosaic project check <business> <project>
```
Pass: `check` reports the layout valid. The first commit's author is
Jason. (REQ-PROJ-1)
2. Open the spec in the Console editor and write it:
> North star: anyone can tell what is running in one command.
> Need: a `mosaic version` command that prints the release and the bus
> schema.
Before saving, open the same file in a second Console tab. Save in the
first tab. Pass: the save makes one local commit with Jason as author,
and the editor shows the new commit. Then edit and save in the second
tab, which still holds the old commit: the save is refused and writes
nothing. (REQ-PROJ-2, REQ-PROJ-3)
3. Tell the assistant about the need:
```
mosaic talk assistant "New need: the mosaic version command in spec/north-star.md. Start it."
```
Pass: `talk` prints only the assistant's reply to this message.
4. Watch the work start:
```
mosaic tasks
mosaic agents
mosaic trail <task>
```
Pass, all on the trail:
- pm makes the tasks and sends the chief of staff a staff request
naming the roles and counts (REQ-COS-2);
- the chief of staff launches a coder, a reviewer and a
security-reviewer through role.launch (REQ-ROLE-5);
- the coder pushes a work ref, and the reviewer and the
security-reviewer each approve it.
5. Watch it land:
```
mosaic trail <decision>
git -C <working repository> log --oneline -3 main
```
Pass:
- pm hands the approved candidate to merge-gate, and merge-gate
raises the `git.merge.protected` decision citing
`spec/north-star.md` at the commit from step 2;
- the CTO resolves it, not Jason (REQ-DEC-2);
- merge-gate lands it, the trail records the allowed action with the
landed commit, and the commit is on the working repository's `main`
(REQ-LAND-1).
6. Check the inbox, then ask for one hard wait on purpose:
```
mosaic inbox
mosaic talk assistant "Open a new account for the project on an outside service."
mosaic inbox
```
Pass: the first `inbox` is empty, so nothing from steps 3 to 5 reached
Jason. After the request, the inbox holds one decision for
`account.create`. Resolve it with the reject option. (REQ-WAIT-1)
7. Restart the host halfway through the next merge. Use a second small
change, for example "print the help when an option is unknown", and
restart once the CTO has resolved its merge decision and before
merge-gate lands it. Mos or Jason runs the restart:
```
mosaic bus stop
mosaic bus start <business> --pm
```
Pass: the merge lands from a new run of the same merge-gate instance
with the approval from before the restart, and no new decision is
raised.
(REQ-DEC-5)
8. Change the spec in the Console editor: also print the commit. Save.
Pass:
- the save makes a new commit by Jason;
- `mosaic trail` shows no decision settled under the step 2 commit
reopened (REQ-DEC-6);
- `mosaic trail` shows new raises citing the new commit, and no
raise citing the step 2 commit accepted after the save;
- pm and the CTO each answer the spec commit with an accept
(REQ-PROJ-4).
9. Change the spec in a way that breaks a settled decision, for example:
"the version command prints nothing and exits." Save.
Pass: pm or the CTO answers with a BLOCK naming the conflict, the
commit stays, and the assistant tells Jason about the block in its own
words. The assistant's message doesn't quote the chief of staff's text.
(REQ-PROJ-4, REQ-ASSIST-3) Then revert the spec in the editor with a
new commit.
10. Give the coder instance a name with the CLI command row 141 adds
(`docs/TOOLS.md` names it once that row lands), then send it a
message by that name:
```
mosaic agents
mosaic talk <name from the agents list> "Reply with your role name."
```
Pass: the reply comes from the instance with that name, and the trail
records both the name and the instance. (REQ-MSG-2)
## Result
The run passes when every step passes. Record the run with the date, the
release, the bus schema and the result of each step in Vikunja task 314.
A failed step names the requirement it checks, so the fix goes back to
that row.
+240 -4
View File
@@ -1,6 +1,6 @@
# PRD: Mosaic Stack
- Status: approved, version 1.4. Jason approved draft 0.4 unchanged as
- Status: approved, version 1.5. 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
@@ -10,8 +10,45 @@
decision 143). Mos reviewed the 1.3 text before it landed. Version 1.4
adds REQ-WEB-3 and REQ-INST-1, approved by Jason in decisions
021af0c8 (prd.approve, yes; request dfebf8ce) and 1ea053cc (prd.approve,
yes; request b28979c9). It is never edited in place. Changes are a new
yes; request b28979c9). Version 1.5 records Jason's Q167 ruling and
his added Q167 text, relayed by Mos on 2026-10-15. Under Q167 Jason's
confirmation of the text is the approval, so there is no in-Stack
prd.approve for it. It is never edited in place. Changes are a new
version.
- Version 1.5 source, Jason's words relayed by Mos on 2026-10-15:
- Q167: "Me having to approve every decision within the Stack is not
working for me. I gave a directive, I want it to be done." and "Use
the T3 seats to make these updates and changes, so I can have a
functional system."
- Q167, added text: "All north star specs should be visible and
modifiable by the user within each given Project. I need to be able
to read and make the changes ad-hoc without an agent when desired.
This will allow review and revise to occur outside the chat. The
agent will be able to see the changes of course. Changes made should
use git (at least locally) to maintain the change history.
Organization of data in a Project is critical. We need a data
structure with json files and templates for everything. Markdown
files with a WYSIWYG editor for the north star/spec files.
Documentation within the project. Consistent storage of docs, reqs,
decisions, etc. should be kept in the project for reference as
needed. We will need to have these agents for a coding workflow: -
coder - code review - security review - merge gate. The appropriate
agent profile will be assigned to the agents. The roles and tools
allow agentic control of the needed workflow steps. Autonomy within
the rails is encouraged. Agents are proactive. The rest of your
reading is accurate."
- On the revised row list (task 314, comment 225): "Your decisions look
good. Proceed."
- Jason's waits list, as Mos gave it: spending money, deleting data,
anything irreversible, new internet exposure, new external accounts,
messages to people outside the fleet, and force-push or history
rewrite. Mos's reading, Q5: "Q167 covers shrinking GATED_ONLY and the
role grants through rows 135 and 139." Sage's plan for the rows adds
that any change to the list itself, and any grant of a listed action
to a role, also goes to Jason.
- Mos's reading, Q4: agents land into the business working
repository's protected branch through the host. This repository's
canonical `refactor` branch stays the lead's landing.
- Version 1.3 source, Jason's words relayed by Mos:
- Q153: "2. agreed. Make it modular though. We should be able to
extend without code change on the core app." Mos's reading, which
@@ -158,6 +195,30 @@ parent requirement is refused.
assistant's name is set in the organization file. Jason's may be named
Mos.
Added in version 1.5 (Q167):
- **REQ-ROLE-5.** The coding workflow has four roles, each with its own
role file:
- coder, which writes the change;
- reviewer, which gives the code review verdict;
- security-reviewer, which reviews a candidate for security only and
approves or blocks with the reason. It has read-only tools and the
verdict, and no shell, no write tools and no push;
- merge-gate, which takes a candidate the reviewers approved, raises
the merge decision for it in the technical domain, so the CTO
decides, and lands it once the decision is resolved (REQ-LAND-1).
Otherwise it reports the refusal. Its one grant is
`git.merge.protected` as a cross-role action, which is what landing
checks. Raising a decision needs no grant. It has no write tools.
Because only the instance that raised an approval can consume it
(REQ-DEC-5), the role that lands is the role that raises.
Instances of these roles are added through the chief of staff's
organization change verb (REQ-ROLE-3).
- **REQ-ROLE-6.** Agents are proactive. Every role contract says: within
your grants, act on what you see without waiting to be asked, and
raise a decision or send a message for anything outside them.
Added in version 1.2:
- **REQ-COS-1.** The chief of staff runs the organization. It hires and
@@ -166,6 +227,10 @@ Added in version 1.2:
from the assistant and assigns it through the roles that do it. It
never talks to the user. Gated decisions still reach the human
through the inbox (REQ-DEC-2, REQ-DEC-4).
- **REQ-COS-2.** The PM asks the chief of staff for staff. A staff request
names the roles and how many of each. The chief of staff launches them
through role.launch, within the agent limits (REQ-LIMIT-1). (Version
1.5.)
- **REQ-ASSIST-1.** The assistant is the user's main agent. Each user has
one assistant role per organization. It talks with the user, and it
passes work for the organization to the chief of staff. It doesn't
@@ -176,6 +241,9 @@ Added in version 1.2:
chief of staff;
- only the user and the chief of staff may send to the assistant;
- every refused send is logged with its reason.
- **REQ-ASSIST-3.** The assistant tells the user what other roles sent it
in its own words. It summarizes and doesn't relay their text. (Version
1.5.)
### Variables
@@ -217,10 +285,21 @@ Added in version 1.2:
- its timestamps.
A correction is a new row.
- **REQ-DEC-2.** Routing follows the decision's class:
- **REQ-DEC-2.** Routing follows the decision's class and whether it
cites the project's spec (REQ-PROJ-2). Version 1.5 replaces the 1.0
text, which sent every gated decision to the human:
- routine and within-role decisions are logged;
- cross-role decisions go to the business's arbiter for that domain;
- gated decisions go to the human.
- a gated decision whose raise cites the project's current spec commit
goes to the business's arbiter for its domain, technical or delivery,
and not to the human;
- a gated decision with no spec citation goes to the human;
- a hard wait (REQ-WAIT-1) goes to the human whether or not it cites
the spec.
A raise that cites a spec commit other than the current one is
refused, and so is a raise citing a spec whose last commit isn't the
human's.
- **REQ-DEC-3.** A human resolution, by an authorized human, comes only from a `mosaic` CLI session
started outside any agent run. Agents reach the decision store only
through a broker. The broker stamps role and run from the launch record.
@@ -229,6 +308,49 @@ Added in version 1.2:
connector (#1509). Everything else goes into one daily digest at 08:00
Central. (Round 2, answers 2A and 3A; round 3, 4A and 5A.)
Added in version 1.5 (Q167):
- **REQ-DEC-5.** An approval outlives the run that raised it. It binds to
the decision and to the role instance and thread that raised it. A
later run of the same instance, on the same thread, may consume it
once. The first consuming action is
recorded, a second consume is refused, and another instance can't
consume it. Superseding a decision follows the same rule.
- **REQ-DEC-6.** A new spec commit never reopens a settled decision. Each
decision records the spec path and commit its raise cited. A decision
settled under one commit stays settled when a later commit lands, and
new raises cite the new commit.
### Hard waits
Added in version 1.5 (Q167).
- **REQ-WAIT-1.** These are the hard waits. Each always goes to the
human, and each maps to actions in the closed vocabulary:
| Jason's wait | Actions |
| --- | --- |
| Spending money | `spend` |
| Deleting data | `data.delete`, new in 1.5 |
| Anything irreversible | `data.delete`, `git.push.protected` and REQ-WAIT-3 |
| New internet exposure | `network.expose`, new in 1.5: a listener, port, tunnel, DNS record or proxy route reachable from outside the host. `deploy` stays a hard wait too. |
| New external accounts | `account.create`, new in 1.5, and `credential.mint` |
| Messages to people outside the fleet | `message.external` |
| Force-push or history rewrite | `git.push.protected`, which covers a push to a protected branch and any force push or history rewrite on any branch |
The always-gated lists in the broker and the business vocabulary hold
exactly these actions. `git.merge.protected`, `policy.change`,
`prd.approve` and `role.revoke` leave those lists and route by
REQ-DEC-2. REQ-ROLE-2 still requires a resolved decision for a revoke.
- **REQ-WAIT-2.** No role file may grant a hard-wait action, at any
class, and the validator refuses a file that does. A change to the
hard-waits list, or a grant of a hard-wait action to a role, is itself
a hard wait. The list changes only by a reviewed commit to the
vocabulary, under a new PRD version that Jason approves.
- **REQ-WAIT-3.** An action added to the vocabulary is a hard wait until a
PRD version says it isn't. The reviewed commit that adds it names which
it is.
### Messages
- **REQ-MSG-1.** Messages are addressed to a role. They are append-only
@@ -237,6 +359,13 @@ Added in version 1.2:
the assistant sends work to the chief of staff, the reply comes back to
the same thread. T3 and Discord carry messages; the address is still
the role.
- **REQ-MSG-2.** Messages can also be addressed by agent name. A role
instance may have a name, unique in its business, that equals no
instance key and no role name. A send takes a name or a role. A name
resolves to its instance, the message records both, and a role still
delivers as REQ-MSG-1 says. The human sets names through the CLI. The
chief of staff sets them once its role file grants that within-role.
(Version 1.5.)
### Tasks
@@ -257,6 +386,70 @@ Added in version 1.2:
- **REQ-TASK-4.** The queue keeps the agents' lock, review rounds and
audit log. No field syncs in both directions.
### Projects
Added in version 1.5 (Q167).
- **REQ-PROJ-1.** Each project keeps its records in its own local git
repository, separate from its code checkout, with no remote. The
layout is fixed and versioned: `project.json`, `spec/north-star.md`,
`docs/`, `requirements/` and `decisions/` with one JSON file per
record, and `templates/` with a template for each kind. Each JSON kind
has a schema, and a record that fails it is refused. `mosaic project
init` creates the repository with its first commit as the business's
human.
- **REQ-PROJ-2.** The north-star spec is `spec/north-star.md` in the
project repository. Only the human commits to it. Agents read it and
never write it. The spec counts only when its last commit's author is
the business's human. Agents write docs, requirements and decisions as
agent-authored commits, through the layout's validators.
- **REQ-PROJ-3.** The Console has an editor for the spec and the
Markdown files in `docs/`. The user opens a file at its current commit,
edits its Markdown source beside a rendered preview, and saves it as
one local commit with the human as author. A save made from a commit
older than the file's last commit is refused and writes nothing. The
preview runs no script from the file and is built from code in this
repository. A WYSIWYG editor may replace the source view only with a
library that is vendored and pinned with a permissive license and a
recorded hash for each file, loads nothing at runtime, and needs no
change to the Console's content security policy. No library passed
that for 1.5.
- **REQ-PROJ-4.** Agents review the human's spec commits. Each human
commit to the spec saved through the Console editor (REQ-PROJ-3) sends
one notice to the PM and the CTO. A commit the human makes with git
outside the Console sends no notice yet. They check
the change against settled decisions and work in flight, and each
answers with an accept or a BLOCK that names the conflict. A BLOCK
doesn't revert the commit. It goes to the assistant through the chief
of staff (REQ-ASSIST-2), and the assistant tells the user (REQ-ASSIST-3).
### Landing
Added in version 1.5 (Q167, Mos's reading Q4).
- **REQ-LAND-1.** Agents land approved merges into the business working
repository. The run that holds an approved `git.merge.protected`
decision asks the host to land the work ref the decision names into
the working repository's `main`. The host runs the landing checks and
the project's gate:
- the decision is resolved with the approval option;
- the ref is at the reviewed commit, and a verdict approves it;
- the decision isn't superseded;
- the base hasn't moved.
A refusal is logged on the task's trail with its code, and a project
with no gate is refused. A landing is recorded as the allowed action
with the landed commit, and it consumes the decision (REQ-DEC-5). This
repository's canonical branch is never a target.
Only a role whose role file grants `git.merge.protected` as a
cross-role action can land. A merge decision raised by a role without
that grant is gated, and its approval never lands, whether the arbiter
or the human gave it. So an arbiter's judgment can't move landing to a
role REQ-ROLE-5 didn't give it. The land operation refuses a gated
decision; the check ships with whichever of rows 137 and 139 lands
second.
### Interface
- **REQ-CLI-1.** `mosaic` is the front door. From it Jason can:
@@ -486,6 +679,10 @@ itself from them, five pieces in a row. For each piece:
(Round 1, answer A; the assistant and threads from version 1.2.)
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.
## Technical considerations
- Vikunja v2.7.0 is AGPL-3.0. Its API tokens can't create users or mint
@@ -623,6 +820,45 @@ itself from them, five pieces in a row. For each piece:
the requirement only.
- REQ-WEB-2 is held for the Console decide buttons amendment
(vikunja:19/266), so its id is not reused here.
- Version 1.5:
- The 1.0 text of REQ-DEC-2 was: "Routing follows the decision's
class: routine and within-role decisions are logged; cross-role
decisions go to the business's arbiter for that domain; gated
decisions go to the human." On 2026-10-15 the Stack routed 16
decisions, and all 16 went to Jason. 9 of them were `policy.change`
or `prd.approve`, which no role file could grant.
- Before 1.5 the always-gated lists, `GATED` in
`packages/bus/src/broker.mjs` and `GATED_ONLY` in
`packages/business/src/vocabulary.mjs`, held nine actions:
`credential.mint`, `git.merge.protected`, `git.push.protected`,
`deploy`, `spend`, `message.external`, `policy.change`,
`prd.approve` and `role.revoke`. Row 139 (#1633) changes them to
REQ-WAIT-1's list and adds `data.delete`, `network.expose` and
`account.create`.
- The arbiters are the organization file's `arbiters.technical` and
`arbiters.delivery` (`packages/bus/src/broker.mjs`).
- Where each requirement is built, all under Vikunja P19 task 314:
REQ-DEC-5 is row 136 (#1630); REQ-PROJ-1 is row 147 (#1641);
REQ-PROJ-2 and REQ-DEC-6 are row 138 (#1632); REQ-COS-2 is row 140
(#1634). Those are landed. REQ-LAND-1 is row 137 (#1631);
REQ-DEC-2 and REQ-WAIT-1 to REQ-WAIT-3 are row 139 (#1633);
REQ-MSG-2 is row 141 (#1635); REQ-PROJ-4 and REQ-ASSIST-3 are row 143
(#1637); REQ-PROJ-3 is row 148 (#1642); REQ-ROLE-5 and REQ-ROLE-6
are row 149. This amendment adds the requirements only.
- The spec counts as confirmed when its last commit's author name is
the business's `human` (`packages/project/src/read.mjs`). The broker
refuses a raise that cites another commit with `stale-spec`, and an
unconfirmed spec with `spec-unconfirmed`. Until `mosaic project
init` runs for a project, spec reads and record writes for it are
refused.
- `git.push.protected` covering force pushes on any branch is new in
1.5. Today the host refuses a non-fast-forward push to the working
repository (`packages/seat/src/working.mjs`), so no role can force
push. REQ-WAIT-1 makes any future path to one a hard wait.
- REQ-PROJ-4 covers Console saves only (row 143). Noticing spec commits
made with git outside the Console is a later row.
- REQ-LAND-1's refusal of a gated merge decision is pinned in rows 137
and 139. Row 139 carries it, or row 137 if 139 lands first.
## Risks and open questions
+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, 43);
assert.equal(list.requirements.length, 58);
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
+16 -1
View File
@@ -1,14 +1,18 @@
{
"prd": "docs/prd/mosaic-stack.md",
"version": "1.4",
"version": "1.5",
"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" },
{ "id": "REQ-ROLE-3", "title": "The user and the chief of staff write the organization file; the chief of staff's writes go through a logged broker verb" },
{ "id": "REQ-ROLE-4", "title": "The first organization is Mosaic Stack: chief of staff, assistant, PM, CTO, coder and reviewer" },
{ "id": "REQ-ROLE-5", "title": "Q167 adds security-reviewer and merge-gate; merge-gate raises the merge in the technical domain and lands it" },
{ "id": "REQ-ROLE-6", "title": "Agents are proactive: each role contract says to act within its grants without waiting to be asked" },
{ "id": "REQ-COS-1", "title": "The chief of staff runs the organization: hires, terminates, launches; never talks to the user" },
{ "id": "REQ-COS-2", "title": "The PM asks the chief of staff for staff; a staff request names the roles and how many of each" },
{ "id": "REQ-ASSIST-1", "title": "The assistant is the user's main agent and passes organization work to the chief of staff" },
{ "id": "REQ-ASSIST-2", "title": "The broker limits the assistant to the user and the chief of staff in both directions; refusals logged" },
{ "id": "REQ-ASSIST-3", "title": "The assistant tells the user what other roles sent it in its own words and does not relay their text" },
{ "id": "REQ-VAR-1", "title": "Variables in four layers, declared in code; limits intersect; unknown keys refused" },
{ "id": "REQ-VAR-2", "title": "One system config, never auto-written; secrets only as references the broker reads" },
{ "id": "REQ-CRED-1", "title": "Each role instance has its own Gitea and Vikunja token, held by the broker" },
@@ -17,11 +21,22 @@
{ "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-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" },
{ "id": "REQ-WAIT-1", "title": "Hard waits always go to the human: spend, delete, irreversible, exposure, accounts, external messages, protected push" },
{ "id": "REQ-WAIT-2", "title": "No role file grants a hard wait; changing the list or granting a listed action is itself a hard wait" },
{ "id": "REQ-WAIT-3", "title": "An action added to the vocabulary is a hard wait until a PRD version says it is not" },
{ "id": "REQ-MSG-1", "title": "Messages are addressed to a role, append-only, delivered to the current holder; assistant messages name a thread" },
{ "id": "REQ-MSG-2", "title": "Messages can be addressed by an instance name unique in its business; the message records the name and the instance" },
{ "id": "REQ-TASK-1", "title": "Tasks live in Vikunja, each citing a requirement id; the verbs enforce field writers" },
{ "id": "REQ-TASK-2", "title": "Polling is the source of truth: a 30-second board poll and an hourly reconcile" },
{ "id": "REQ-TASK-3", "title": "The installer points at an existing Vikunja or deploys the unmodified upstream image" },
{ "id": "REQ-TASK-4", "title": "The queue keeps the agents' lock, review rounds and audit log; no two-way sync" },
{ "id": "REQ-PROJ-1", "title": "Each project keeps its records in its own local git repository, with no remote and a fixed, validated layout" },
{ "id": "REQ-PROJ-2", "title": "Only the human commits the north-star spec; it counts only when its last commit is by the business human" },
{ "id": "REQ-PROJ-3", "title": "The Console edits the spec and docs Markdown as source with a preview; a stale save is refused" },
{ "id": "REQ-PROJ-4", "title": "Each human spec commit saved in the Console sends one notice to the PM and the CTO for review" },
{ "id": "REQ-LAND-1", "title": "A resolved merge decision lands into the working repository main through the host, only for a granted role" },
{ "id": "REQ-CLI-1", "title": "mosaic is the front door: talk to the assistant in a thread, the inbox, tasks, agents and trails" },
{ "id": "REQ-CLI-2", "title": "The assistant session mosaic talks to is launched and owned by the stack, not T3" },
{ "id": "REQ-WEB-1", "title": "The WebUI shows the CLI's data, a chat area of projects and threads, and Settings for agent limits" },
+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, 43);
assert.equal(ids.length, 58);
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.4']);
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.5']);
});
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[43].id: REQ-ROLE-1 twice'));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[58].id: REQ-ROLE-1 twice'));
} finally {
rmSync(dir, { recursive: true, force: true });
}