docs: PRD 1.2, chief of staff, assistant, threads and agent limits (Q147-Q151)
PRD version 1.2, approved by Jason through Mos on 2026-10-14 (lead
decisions 141 and 142). Adds REQ-COS-1, REQ-ASSIST-1/2, REQ-LIMIT-1/2/3,
REQ-USER-2 and REQ-THREAD-2, rewrites the role, launch, message, CLI and
WebUI requirements, and quotes Q147 to Q151 in the source line.
REQ-LIMIT-2 marks the token unit for the daily quota as a draft position.
requirements.json moves to 1.2 with 38 ids; the tasks and bus id-count
tests follow. Full gate green at bea5dfa47 plus these four files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+214
-25
@@ -1,9 +1,28 @@
|
||||
# PRD: Mosaic Stack
|
||||
|
||||
- Status: approved, version 1.1. Jason approved draft 0.4 unchanged as
|
||||
- Status: approved, version 1.2. Jason approved draft 0.4 unchanged as
|
||||
1.0 on 2026-10-05 (lead decision 61). He approved version 1.1, the
|
||||
Organization requirements, in decision 03130d08-ddce-480d-a601-e59f64744fc3
|
||||
on 2026-10-14. It is never edited in place. Changes are a new version.
|
||||
on 2026-10-14. He approved version 1.2, the chief of staff, the
|
||||
assistant, threads and agent limits, through Mos on 2026-10-14 (lead
|
||||
decisions 141 and 142). It is never edited in place. Changes are a new
|
||||
version.
|
||||
- Version 1.2 source, Jason's words relayed by Mos:
|
||||
- Q147 (full text in lead decision 141): "COS should focus on COS
|
||||
duties. This will include hire/terminate authority for agents, org
|
||||
structure, etc. ... This necessitates the creation of a new role for
|
||||
user interaction. ... The user should be allowed to have 1 - N
|
||||
sessions with this agent to allow for topics of focus in a given
|
||||
thread. ... The interaction agent in each session is the user's main
|
||||
agent."
|
||||
- Q148, Mos's read-back of Q147: "Q148: Yes".
|
||||
- Q149, on the 1.2 draft: "approved as long as the agent limits are
|
||||
tunable via "Settings" in the WebUI and via the CLI tooling."
|
||||
- Q150: "No hard cap on agent limits. All tunable." The five kinds of
|
||||
limit are quoted under Launching.
|
||||
- Q151, Mos's read-back of Q150, with the default that launches over a
|
||||
limit are refused and logged and a run that hits its token limit is
|
||||
stopped: "Q151: yes".
|
||||
- Owner: Jason. Sage writes it from the PRDY interview.
|
||||
- Template: PRDY "software" (`v1/packages/prdy/src/templates.ts`), filled
|
||||
by hand until PRDY is ported.
|
||||
@@ -89,21 +108,49 @@ parent requirement is refused.
|
||||
- the credentials it needs, by service and scope.
|
||||
|
||||
Any action the file doesn't list is gated.
|
||||
- **REQ-ROLE-2.** Each role instance has one holder at a time. A claim
|
||||
takes a lock and logs it, and releasing the role or ending the run frees
|
||||
it. Revoking a stuck holder is gated, and the revoke cites a resolved
|
||||
decision.
|
||||
- **REQ-ROLE-2.** Each role instance has one holder at a time. The
|
||||
assistant is the exception: it has one holder per thread at a time. A
|
||||
claim takes a lock and logs it, and releasing the role or ending the run
|
||||
frees it. The chief of staff may end another role's run, and that act is
|
||||
logged with its reason. Anyone else who revokes a stuck holder needs a
|
||||
resolved decision, and the revoke cites it.
|
||||
- **REQ-ROLE-3.** An organization file declares the role instances,
|
||||
the arbiters and the credential references. The user writes it, the stack
|
||||
never writes it, and a missing or invalid file fails closed.
|
||||
- **REQ-ROLE-4.** The first organization is Mosaic Stack, with four role
|
||||
the arbiters and the credential references. The user and the chief of
|
||||
staff write it. The chief of staff's writes go through a broker verb
|
||||
that logs each change. No other role writes it. A missing or invalid
|
||||
file fails closed. (Q133, confirmed as Q140: "We need the human and the
|
||||
Chief of Staff to be able to edit the Business file(s)".)
|
||||
- **REQ-ROLE-4.** The first organization is Mosaic Stack, with six role
|
||||
instances:
|
||||
- chief of staff;
|
||||
- assistant;
|
||||
- PM, held by Sage;
|
||||
- CTO, held by Darkwing;
|
||||
- coder;
|
||||
- reviewer.
|
||||
|
||||
The schema can also declare CEO, CFO and the other C-suite roles.
|
||||
The schema can also declare CEO, CFO and the other C-suite roles. The
|
||||
assistant's name is set in the organization file. Jason's may be named
|
||||
Mos.
|
||||
|
||||
Added in version 1.2:
|
||||
|
||||
- **REQ-COS-1.** The chief of staff runs the organization. It hires and
|
||||
terminates role instances, owns the organization's structure and is
|
||||
the only role that launches sessions (REQ-LAUNCH-1). It takes work
|
||||
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-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
|
||||
direct other agents, create or assign tasks, or launch sessions.
|
||||
- **REQ-ASSIST-2.** The broker limits the assistant's messages, not its
|
||||
contract alone:
|
||||
- the assistant may send only to the user, in the thread, and to the
|
||||
chief of staff;
|
||||
- only the user and the chief of staff may send to the assistant;
|
||||
- every refused send is logged with its reason.
|
||||
|
||||
### Variables
|
||||
|
||||
@@ -159,9 +206,12 @@ parent requirement is refused.
|
||||
|
||||
### Messages
|
||||
|
||||
- **REQ-MSG-1.** Messages are addressed to a role, not a thread. They are
|
||||
append-only and go to whoever currently holds the role. T3 and Discord
|
||||
carry messages; the address is still the role.
|
||||
- **REQ-MSG-1.** Messages are addressed to a role. They are append-only
|
||||
and go to whoever currently holds the role. A message to or from the
|
||||
assistant also names its thread and goes to that thread's holder. When
|
||||
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.
|
||||
|
||||
### Tasks
|
||||
|
||||
@@ -185,26 +235,85 @@ parent requirement is refused.
|
||||
### Interface
|
||||
|
||||
- **REQ-CLI-1.** `mosaic` is the front door. From it Jason can:
|
||||
- talk to the PM;
|
||||
- talk to the assistant in a project thread they choose or create;
|
||||
- see the decision inbox and resolve decisions;
|
||||
- see tasks and agents;
|
||||
- follow the trail of any piece of work.
|
||||
- **REQ-CLI-2.** The PM session that `mosaic` talks to is one the stack
|
||||
- **REQ-CLI-2.** The assistant session that `mosaic` talks to is one the stack
|
||||
launches and owns, running Pi or Claude Code without a window. The
|
||||
product doesn't depend on T3. (Round 2, answer 5A.)
|
||||
- **REQ-WEB-1.** The WebUI shows the same data as the CLI: conversations,
|
||||
the inbox, tasks, agents and trails.
|
||||
the inbox, tasks, agents and trails. It has a chat area that lists the
|
||||
user's projects and the named threads in each, with one conversation
|
||||
view per thread where the user talks to the assistant. The user creates
|
||||
and names projects and threads there without a terminal. Its Settings
|
||||
view shows every agent limit, the usage counted against each, and the
|
||||
model each role instance runs, and lets the user change them
|
||||
(REQ-LIMIT-1 to REQ-LIMIT-3).
|
||||
|
||||
### Launching
|
||||
|
||||
- **REQ-LAUNCH-1.** The PM launches role sessions within limits written in
|
||||
the organization file:
|
||||
- **REQ-LAUNCH-1.** The chief of staff launches role sessions within the
|
||||
agent limits written in the organization file (REQ-LIMIT-1 to
|
||||
REQ-LIMIT-3):
|
||||
- role instances only;
|
||||
- at most 4 Opus and 4 Sonnet sessions at once;
|
||||
- every launch logged;
|
||||
- a launch that would exceed any agent limit is refused, and the
|
||||
refusal is logged with the limit it hit;
|
||||
- every launch is logged with its harness, provider, model, and the
|
||||
account or key it runs on;
|
||||
- revocable with one word.
|
||||
|
||||
(Round 2, answer 6A.)
|
||||
Assistant thread sessions are the exception to who launches. The stack
|
||||
starts one when the user opens or writes to a thread, and ends it after
|
||||
the thread has been idle for the time the organization file sets. A
|
||||
running thread session counts against the limits like any other. An
|
||||
idle thread holds no session and counts against none. The stack starts
|
||||
the chief of staff itself. (Round 2, answer 6A; Q147; Q150.)
|
||||
|
||||
Added in version 1.2. Jason, Q150, verbatim: "No hard cap on agent
|
||||
limits. All tunable. Agent limit: A parallel execution limit arbitrarily
|
||||
set by the user or COS. This limit can apply to: overall executing agent
|
||||
limit across all models; provider parallel agent limit; provider parallel
|
||||
agent limit by model; daily quota limit based on OAuth account limit or
|
||||
CLIProxyAPI config in Mosaic Stack; absolute token limit per agent,
|
||||
model, provider, or API key".
|
||||
|
||||
- **REQ-LIMIT-1.** Agent limits are settings with no ceiling in code. The
|
||||
user and the chief of staff view and change them from the WebUI's
|
||||
Settings view and from a `mosaic` command, without editing a file. The
|
||||
chief of staff changes them through its organization-file broker verb
|
||||
(REQ-ROLE-3). The same places view and change the model each role
|
||||
instance runs. The running-session limits are:
|
||||
- the total number of sessions running at once, across all models;
|
||||
- the number running at once per provider;
|
||||
- the number running at once per provider and model. The default is 4
|
||||
Opus and 4 Sonnet, as a default that can be changed, not a cap.
|
||||
|
||||
Any kind may be left unset, which means no limit of that kind. A value
|
||||
is a whole number of 0 or more, and 0 stops new launches of that kind.
|
||||
A change is checked by the same rules as the organization file, then
|
||||
written to it and logged with who made it and the old and new values.
|
||||
An invalid value is refused, and the old value stays. A change applies
|
||||
to the next launch without a restart, and lowering a limit doesn't end
|
||||
sessions already running.
|
||||
- **REQ-LIMIT-2.** A daily quota per OAuth account, or per client key of
|
||||
the CLIProxyAPI that Mosaic Stack configures. The stack counts the
|
||||
tokens its sessions use against the account or key each ran on, and the
|
||||
day resets at a time and time zone set with the quota. When an account
|
||||
or key reaches its quota, new launches on it are refused, and running
|
||||
sessions on it stop at the end of their current turn. Both are logged.
|
||||
The count covers sessions the stack ran. Use of the same account
|
||||
outside the stack isn't counted, and Settings says so. Measuring the
|
||||
quota in tokens is a draft position: Q150 names no unit, and OAuth
|
||||
plans meter by usage windows, not daily tokens.
|
||||
- **REQ-LIMIT-3.** A token limit per role instance, model, provider or API
|
||||
key. Each limit counts the tokens its subject has used since the limit
|
||||
was set or last reset, and the user and the chief of staff can reset
|
||||
it. A count is the sum of every kind the harness reports (input,
|
||||
output, cache write and cache read), and Settings shows each kind. A
|
||||
launch whose subject is at its limit is refused, and a running session
|
||||
that reaches a limit is stopped at the end of its current turn. Both
|
||||
are logged with the limit and the count.
|
||||
|
||||
### Meta-harness
|
||||
|
||||
@@ -246,20 +355,30 @@ Added in version 1.1 (decision 03130d08).
|
||||
- **REQ-USER-1.** Users and role-based access control exist at install
|
||||
and organization level. An agent belongs to one organization and
|
||||
reaches only its allowed projects and threads.
|
||||
- **REQ-USER-2.** Only the user who owns a thread reads it or writes to
|
||||
it, plus that user's assistant and the chief of staff's replies on work
|
||||
the thread sent. This fills in REQ-USER-1's "threads" for the chat
|
||||
area. Added in version 1.2.
|
||||
- **REQ-THREAD-1.** Threads live under projects.
|
||||
- **REQ-THREAD-2.** The user creates projects and names threads inside
|
||||
them, from the CLI or the WebUI. Each thread is a separate assistant
|
||||
session. A thread's messages are kept. When the user comes back to a
|
||||
thread, after an idle stop or a restart, the assistant picks up where
|
||||
it left off with that history. One thread's conversation never appears
|
||||
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.
|
||||
|
||||
## Acceptance criteria and success measures
|
||||
|
||||
v1 is done when 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:
|
||||
v1 is done when Jason gives one-sentence requests to the assistant, in a
|
||||
thread, from the CLI or the WebUI, and Mosaic Stack builds pieces of
|
||||
itself from them, five pieces in a row. For each piece:
|
||||
- only gated decisions reach Jason;
|
||||
- every step leaves a trail in the CLI and the WebUI.
|
||||
|
||||
(Round 1, answer A.)
|
||||
(Round 1, answer A; the assistant and threads from version 1.2.)
|
||||
|
||||
## Technical considerations
|
||||
|
||||
@@ -280,6 +399,70 @@ piece:
|
||||
DELETE and REPLACE. Run records stay write-once files.
|
||||
- None of the three harnesses' hooks fails closed by default except Pi's
|
||||
`tool_call`. Source: `agents/filbert/work/meta-harness-survey-2026-10-04.md`.
|
||||
- Version 1.2:
|
||||
- The organization file's `projects` key names git repositories to
|
||||
clone (`packages/seat/src/checkout.mjs:282-296`). A REQ-ORG-2 project
|
||||
is the user's container for threads. The build has to keep the two
|
||||
apart, or define one in terms of the other, before the chat area
|
||||
stores projects.
|
||||
- Threads change the bus schema: messages, claims and deliveries gain a
|
||||
thread. The bus refuses a database whose schema doesn't match
|
||||
(`store.mjs:113-115`), so this needs a migration and a re-pin with
|
||||
downtime.
|
||||
- Claude Code resumes a conversation only inside one launch today, because
|
||||
its session directory and HOME are per run (`launcher.mjs:204-206`,
|
||||
`sandbox.mjs:157`). REQ-THREAD-2 needs a per-thread session store that
|
||||
outlives the run, or a rebuild of context from the thread's messages.
|
||||
- Limits today: `launch.max` is keyed by model family, and a launch's
|
||||
family is the one key its model name contains
|
||||
(`packages/seat/src/session.mjs:52-57`). Each count must be 0 to
|
||||
`LAUNCH_CEILING = { opus: 4, sonnet: 4 }`
|
||||
(`packages/business/src/vocabulary.mjs:86`, `business.mjs:129-137`).
|
||||
The launcher reads the file once at start (`launcher.mjs:123`), so a
|
||||
change needs a bus restart. REQ-LIMIT-1 removes the ceiling, replaces
|
||||
family matching with a model table that names each model's provider,
|
||||
and re-reads limits at each launch. Jason's file needs a one-time
|
||||
migration from `{ opus: 4, sonnet: 4 }`.
|
||||
- The stack doesn't know a session's provider today. The only provider
|
||||
setting is the system-level `execution.provider` (`vars.mjs:84-87`),
|
||||
read by `sessionModel` (`packages/harness/src/bundle.mjs:49-55`).
|
||||
Claude Code sessions reach whatever `ANTHROPIC_BASE_URL` points at, or
|
||||
Anthropic when it's unset.
|
||||
- Tokens are on disk today, and nothing reads them:
|
||||
- Claude Code: the adapter uses `--output-format text`
|
||||
(`adapters/claude/adapter.sh`), so the runner sees only the answer.
|
||||
Claude Code's own transcript, in the run's HOME at
|
||||
`home/.claude/projects/<dir>/<session id>.jsonl`, has `model` and
|
||||
`usage` (input, output, cache write, cache read tokens) on each
|
||||
answer line. Checked on run re32fc7bd8143, model claude-sonnet-5-5.
|
||||
- Pi: the session file in the run's session directory has `provider`,
|
||||
`model` and `usage` (input, output, cacheRead, cacheWrite,
|
||||
totalTokens, cost) on each answer line. Checked on a Researcher
|
||||
session, provider zai.
|
||||
- The launch log has harness, model and family per launch, and no
|
||||
tokens (`launcher.mjs:269`, `:294`).
|
||||
- Today every Claude Code session runs on one credential, which the bus
|
||||
unit's environment file sets (`SESSION_CREDENTIALS`,
|
||||
`packages/seat/src/session.mjs:76`, decision 119). The stack has no
|
||||
name for that account. A quota per account needs the organization file
|
||||
to name each account and its credential reference (never the secret,
|
||||
invariant 3), and the launch to pick one and log its name. More than
|
||||
one account at a time needs the bus to hand a credential to each
|
||||
session, which is the open question in the #237 design row.
|
||||
- CLIProxyAPI, from Researcher's stand-in test (commit 587e64a27,
|
||||
`agents/researcher/work/i1-proxy-test/REPORT.md`): usage counters are
|
||||
kept per upstream credential, not per client key. Per-request events
|
||||
name the client key, but they last 60 seconds, a read removes them, and
|
||||
they carry the full plaintext key. All of it is behind the management
|
||||
API, which no agent calls. The stand-in didn't model token counts.
|
||||
So the stack can't read per-key usage from the proxy today. It counts
|
||||
its own sessions' tokens instead, and a per-key quota needs the stack
|
||||
to issue the keys (#237).
|
||||
- Token limits stop a session at the end of a turn, because the runner
|
||||
learns usage only when the turn ends. A long turn can go past its
|
||||
limit. Stopping inside a turn needs the runner to read streamed usage
|
||||
from each harness, which changes the adapter contract (stdout is the
|
||||
answer only, `adapters/README.md`).
|
||||
|
||||
## Risks and open questions
|
||||
|
||||
@@ -292,6 +475,12 @@ piece:
|
||||
overwritten in an owned field (lead decision 51).
|
||||
- The Vikunja owner password and Gitea token creation are gated. Jason
|
||||
holds them.
|
||||
- With no ceiling in code, the chief of staff can raise its own launch
|
||||
capacity and spend (REQ-LIMIT-1, Q150). The controls are the change
|
||||
log, with who made each change and the old and new values, and the
|
||||
user's power to change it back.
|
||||
- A quota or token limit counts only what the stack ran. It can't see the
|
||||
provider's own remaining quota or use of the same account elsewhere.
|
||||
|
||||
## Milestones
|
||||
|
||||
|
||||
@@ -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, 30);
|
||||
assert.equal(list.requirements.length, 38);
|
||||
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
|
||||
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
|
||||
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"prd": "docs/prd/mosaic-stack.md",
|
||||
"version": "1.1",
|
||||
"version": "1.2",
|
||||
"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; a claim locks and logs; revoking a holder is gated" },
|
||||
{ "id": "REQ-ROLE-3", "title": "A user-written organization file declares instances, arbiters and credential references" },
|
||||
{ "id": "REQ-ROLE-4", "title": "The first organization is Mosaic Stack: PM, CTO, coder and reviewer" },
|
||||
{ "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-COS-1", "title": "The chief of staff runs the organization: hires, terminates, launches; never talks to the user" },
|
||||
{ "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-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" },
|
||||
@@ -14,22 +17,27 @@
|
||||
{ "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-MSG-1", "title": "Messages are addressed to a role, append-only, delivered to the current holder" },
|
||||
{ "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-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-CLI-1", "title": "mosaic is the front door: talk to the PM, the inbox, tasks, agents and trails" },
|
||||
{ "id": "REQ-CLI-2", "title": "The PM session mosaic talks to is launched and owned by the stack, not T3" },
|
||||
{ "id": "REQ-WEB-1", "title": "The WebUI shows the same data as the CLI" },
|
||||
{ "id": "REQ-LAUNCH-1", "title": "The PM launches role sessions within organization-file limits, logged and revocable" },
|
||||
{ "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" },
|
||||
{ "id": "REQ-LAUNCH-1", "title": "The chief of staff launches role sessions within the agent limits, logged and revocable; threads start on demand" },
|
||||
{ "id": "REQ-LIMIT-1", "title": "Agent limits have no code ceiling and are set from WebUI Settings and mosaic: total, per provider, per model" },
|
||||
{ "id": "REQ-LIMIT-2", "title": "A daily quota per OAuth account or CLIProxyAPI client key, counting only the stack's own use" },
|
||||
{ "id": "REQ-LIMIT-3", "title": "A token limit per role instance, model, provider or API key, reset by hand, stopping at the end of a turn" },
|
||||
{ "id": "REQ-HARN-1", "title": "The meta-harness generates each session's launch bundle from role and variables" },
|
||||
{ "id": "REQ-HARN-2", "title": "Credential scope, verbs, the tool limit and a container enforce the rules" },
|
||||
{ "id": "REQ-EVT-1", "title": "Every slice 1 step emits an event; measures come from events" },
|
||||
{ "id": "REQ-ORG-1", "title": "Business becomes organization; an install holds 1-N, each with its own bus as tenant boundary" },
|
||||
{ "id": "REQ-ORG-2", "title": "Data model: install > organization > projects > threads" },
|
||||
{ "id": "REQ-USER-1", "title": "Users and RBAC at install and organization level; an agent reaches only its allowed projects and threads" },
|
||||
{ "id": "REQ-USER-2", "title": "Only a thread's owning user, their assistant and the chief of staff's replies reach the thread" },
|
||||
{ "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)" }
|
||||
]
|
||||
|
||||
@@ -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, 30);
|
||||
assert.equal(ids.length, 38);
|
||||
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.1']);
|
||||
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.2']);
|
||||
});
|
||||
|
||||
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[30].id: REQ-ROLE-1 twice'));
|
||||
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[38].id: REQ-ROLE-1 twice'));
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user