Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a97cfc4f3 | ||
|
|
03fe321b12 |
No files matched your search
@@ -1,7 +1,6 @@
|
||||
# PRD version 1.20, draft 2: unified workspace
|
||||
|
||||
- Status: draft, not approved. Jason approves the exact text before it
|
||||
enters `docs/prd/mosaic-stack.md` as version 1.20.
|
||||
- Status: approved as version 1.20 on 2026-11-09; landed in docs/prd/mosaic-stack.md
|
||||
- Source: `docs/plans/2026-11-09_unified-workspace-owner-direction.md`,
|
||||
Jason's direction and answers of 2026-11-09.
|
||||
- Drafted by Mos (lead).
|
||||
|
||||
+126
-20
@@ -1,12 +1,11 @@
|
||||
# PRD: Mosaic Stack
|
||||
|
||||
- Status: approved, version 1.19. Jason approved the exact text in Q289.
|
||||
- Version 1.19 adds disk-space governance, launch-capacity holds,
|
||||
human-controlled retention rules, and the protected `data.delete.policy`
|
||||
route. It preserves per-batch `data.delete` hard waits. Jason approved
|
||||
the exact text at `e5400456` under REQ-WAIT-2: "1.18: yes" (DECISIONS
|
||||
Q289, relayed by Mos in mos-2300). Q288 approved the recurring-rule principle.
|
||||
- Prior approval history, through version 1.18: Jason approved draft 0.4 unchanged as
|
||||
- Status: approved, version 1.20. Jason approved draft 2 on 2026-11-09.
|
||||
- Version 1.20 adds the unified workspace: every organization visible at once,
|
||||
cross-organization views, three levels of agent configuration, per-org task
|
||||
stores behind the task tracker kind, system type plug-ins and self-service
|
||||
operation. It amends REQ-ASSIST-1, REQ-USER-1, REQ-TASK-1 and REQ-WEB-3.
|
||||
- Prior approval history, through version 1.19: 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
|
||||
@@ -64,7 +63,18 @@
|
||||
the approved row 255 design. The stack prints the providers entry and
|
||||
never writes config.json. What the requirement allows or forbids is
|
||||
unchanged (mos-1671, sage-4418). Doc-only, so it takes the 1.11 path:
|
||||
one seat drafts, a second reviews, Sage lands.
|
||||
one seat drafts, a second reviews, Sage lands. Version 1.19 adds
|
||||
disk-space governance, launch-capacity holds, human-controlled retention
|
||||
rules, and the protected `data.delete.policy` route. It preserves per-batch
|
||||
`data.delete` hard waits. Jason approved the exact text at `e5400456` under
|
||||
REQ-WAIT-2: "1.18: yes" (DECISIONS Q289, relayed by Mos in mos-2300).
|
||||
Q288 approved the recurring-rule principle. Jason approved the exact text in Q289.
|
||||
- Version 1.20 source: Mos asked Jason to approve draft 2 in
|
||||
`docs/plans/2026-11-09_prd-1-20-draft.md`, with the Montage name deferred.
|
||||
Jason replied on 2026-11-09 at 22:57Z: "The file looks fine other than that."
|
||||
The exception is the module name, which he had deferred at 19:26Z: "I don't
|
||||
like these module names either. It isn't clicking for me. That may need to be
|
||||
a later decision."
|
||||
- Version 1.19 source, Jason's DECISIONS Q287, verbatim:
|
||||
|
||||
> All of the learnings from this drive space issue need to be integrated into Mosaic Stack. We need watchers, cleanups, enforcement tooling and agents to check for space waste, etc.
|
||||
@@ -475,9 +485,11 @@ Added in version 1.2:
|
||||
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
|
||||
- **REQ-ASSIST-1.** The assistant is the user's main agent. Each user has one
|
||||
assistant across all of the organizations they
|
||||
belong to. It knows the user's organizations, projects and sessions and
|
||||
keeps one shared memory. It talks with the user and passes each
|
||||
organization's work to that organization's 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:
|
||||
@@ -522,6 +534,24 @@ Added in version 1.7:
|
||||
- Every hire and fire is logged with the chief's instance, its run and
|
||||
the reason.
|
||||
|
||||
### Agent levels
|
||||
|
||||
Added in version 1.20.
|
||||
|
||||
- **REQ-AGENT-1.** Agents are configured at three levels: the user's root,
|
||||
the organization and the project. A project's available agents are the
|
||||
union of the three.
|
||||
- **REQ-AGENT-2.** The user can see and change the configuration of agents,
|
||||
organizations, projects and sessions, and can override any setting at any
|
||||
time, through the write path (REQ-ROLE-3).
|
||||
- **REQ-AGENT-3.** The chief of staff hires and fires agents to meet the
|
||||
user's needs. At the organization and project levels it acts under
|
||||
REQ-COS-3. At the root level its hire or fire changes the user's root
|
||||
configuration, which every organization of that user sees, so it reaches
|
||||
outside the chief's own organization. A root-level hire or fire is a
|
||||
decision the user approves in the inbox (REQ-DEC-2) before it takes
|
||||
effect. The user can also change root-level agents directly.
|
||||
|
||||
### Variables
|
||||
|
||||
- **REQ-VAR-1.** Variables come in four layers: system, business, project
|
||||
@@ -879,8 +909,15 @@ consumed ordinary decision as standing permission.
|
||||
|
||||
### Tasks
|
||||
|
||||
- **REQ-TASK-1.** Tasks live in Vikunja, reached through `/api/v2` with a
|
||||
bot token for each role. Each task cites a requirement id. Each task
|
||||
- **REQ-TASK-1.** Each organization stores its own tasks, through the task tracker
|
||||
plug-in kind (REQ-PLUG-1). The default module is a store built into Mosaic
|
||||
Stack. Vikunja is an optional module, and when an organization selects it
|
||||
the 1.19 rules for Vikunja (bot tokens per role, one writing role per field,
|
||||
REQ-TASK-2 polling) apply to that organization. Every task tracker module
|
||||
supports create, read, update, archive and delete, ordering and grouping.
|
||||
Archiving or removing an organization removes its tasks without touching
|
||||
another organization's.
|
||||
Each task cites a requirement id. Each task
|
||||
field has one writing role, listed in the slice 1 brief's field table,
|
||||
and the assigned role writes the task's state. Since version 1.19, the
|
||||
scoped disk-service principal in REQ-DISK-12 may initialize incident
|
||||
@@ -1031,10 +1068,40 @@ Added in version 1.7:
|
||||
- The decision event records via=console and the (iss, sub) pair.
|
||||
- Without a signed-in session, the Console resolves nothing and shows
|
||||
the `mosaic decide` line instead.
|
||||
- **REQ-WEB-3.** The Console has controls to restart the bus and to
|
||||
reload the organization file. Each control is a gated, trailed action.
|
||||
A reload applies `launch.instances` changes to running sessions without
|
||||
a manual `systemctl restart`. (Version 1.4, decision 021af0c8.)
|
||||
- **REQ-WEB-3.** The Console has controls to restart itself, to restart an
|
||||
organization's bus, to reload the organization file, and to change the model
|
||||
an agent instance runs on and apply it. Each control is a gated, trailed
|
||||
action that a human takes. No agent can take it. A reload applies
|
||||
`launch.instances` changes to running sessions without a manual
|
||||
`systemctl restart`. On a dev instance (`--dev-local-owner`) the controls
|
||||
need no sign-in.
|
||||
|
||||
### Workspace
|
||||
|
||||
Added in version 1.20.
|
||||
|
||||
- **REQ-WS-1.** The WebUI shows every organization the user belongs to at
|
||||
all times. A collapsible Organizations item in the sidebar lists them.
|
||||
There is no organization switcher.
|
||||
- **REQ-WS-2.** The Console reads across the buses of the user's
|
||||
organizations to build shared views. Each organization keeps its own bus
|
||||
as the tenant boundary (REQ-ORG-1). A shared view shows only
|
||||
organizations the user belongs to.
|
||||
- **REQ-WS-3.** The main dashboard sums activity, work and metrics across
|
||||
all of the user's organizations and projects.
|
||||
- **REQ-WS-4.** Each organization has a dashboard with its docs, every
|
||||
configurable setting, its KPIs and metrics, and its active agent
|
||||
sessions.
|
||||
- **REQ-WS-5.** The projects list covers every organization. It sorts and
|
||||
filters by organization, status, name and activity. Each project shows
|
||||
its owning organization by icon, color and avatar.
|
||||
- **REQ-WS-6.** One inbox holds every item that needs the user, from every
|
||||
organization, project and session.
|
||||
- **REQ-WS-7.** One view shows activity across all organizations and
|
||||
projects. Organizations and projects can be open side by side.
|
||||
- **REQ-WS-8.** The tasks view aggregates every organization's tasks, with
|
||||
Kanban and list layouts, filtered by organization and project. It works
|
||||
the same for every task tracker module.
|
||||
|
||||
### Launching
|
||||
|
||||
@@ -1173,6 +1240,17 @@ Added in version 1.3 (Q155).
|
||||
changes it, not in one rewrite (Q155). Until it converts, Technical
|
||||
considerations lists where it is hard-coded.
|
||||
|
||||
### System types
|
||||
|
||||
Added in version 1.20.
|
||||
|
||||
- **REQ-SYS-1.** System types are a plug-in kind. Its modules include
|
||||
coding, creative writing, marketing, Mosaic Montage (the OpenMontage
|
||||
fork) and business management. An organization enables a list of them.
|
||||
- **REQ-SYS-2.** A project may use any system type its organization
|
||||
enables, and none is required. An agent working in a project gets the
|
||||
toolsets of the system types that project uses.
|
||||
|
||||
### Meta-harness
|
||||
|
||||
- **REQ-HARN-1.** The meta-harness generates each session's launch bundle
|
||||
@@ -1329,9 +1407,13 @@ Added in version 1.1 (decision 03130d08).
|
||||
- **REQ-ORG-2.** The data model is install, then organization, then
|
||||
projects, then threads, so v1 doesn't lock in one human per
|
||||
organization.
|
||||
- **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-1.** Users and role-based access control exist at install and
|
||||
organization level. An agent other than the assistant works in one
|
||||
organization at a time. Its memory, files and credentials are scoped to that
|
||||
organization, even when its definition comes from the user's root
|
||||
configuration. It reaches only that organization's allowed projects and
|
||||
threads. The assistant is the exception in REQ-ASSIST-1, and it reaches only
|
||||
organizations its user belongs to.
|
||||
- **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
|
||||
@@ -1462,6 +1544,15 @@ design `docs/plans/2026-10-21_t1-sessions.md`, 2c843d190 on `huey/t1-sessions-de
|
||||
with `processing-halted` and changes nothing. A clear never sets or
|
||||
clears the kill switch. (Design D8, test T6.)
|
||||
|
||||
### Accounts
|
||||
|
||||
Added in version 1.20.
|
||||
|
||||
- **REQ-ACCT-1.** A deployed system requires user sign-in (OIDC, Q203). A
|
||||
user creates organizations and belongs to any number of them.
|
||||
- **REQ-ACCT-2.** An organization's owner invites another user into it.
|
||||
The invited user joins with the grant the owner chose (REQ-USER-3).
|
||||
|
||||
### Creative library
|
||||
|
||||
Added in version 1.12 (Q176 Case 2, Q211). Drafted from the Case 2
|
||||
@@ -1788,6 +1879,17 @@ Added in version 1.11 (Q187, Q189, Q211):
|
||||
monitored coverage. Install and upgrade enable no deletion policies
|
||||
(REQ-DISK-1, REQ-DISK-4, REQ-DISK-10).
|
||||
|
||||
### Setup
|
||||
|
||||
Added in version 1.20.
|
||||
|
||||
- **REQ-SETUP-1.** Quick Setup leaves its project with its records
|
||||
initialized.
|
||||
- **REQ-SETUP-2.** After Quick Setup, the assistant offers an onboarding
|
||||
interview. It asks for the project's outcome and limits and drafts the
|
||||
north star and a first PRD. The user approves each through the existing
|
||||
approval path.
|
||||
|
||||
## Acceptance criteria and success measures
|
||||
|
||||
v1 is done when Jason gives one-sentence requests to the assistant, in a
|
||||
@@ -2367,6 +2469,10 @@ gate that slice.
|
||||
|
||||
## Risks and open questions
|
||||
|
||||
- The final name for the Mosaic Montage module is deferred to a later owner
|
||||
decision. Mosaic Montage remains a working name; the existing four-tile
|
||||
logo stays. (Version 1.20.)
|
||||
|
||||
- On each v1 instance, the stack runs as that instance's own OS user,
|
||||
without sudo (Q181). A managed role session runs in a sandbox
|
||||
(REQ-HARN-2): it writes only its workspace and run directory, sees no
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRequirementsHost } from '../../tasks/src/builtin.mjs';
|
||||
import { createRequirementCatalog } from './requirement-catalog.mjs';
|
||||
import { readPrdMaterial } from '../../project/src/prd.mjs';
|
||||
import { lstatSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { lstatSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Store } from './store.mjs';
|
||||
@@ -360,6 +360,11 @@ export async function startBroker({
|
||||
// Row 236 (#1742, #436b r5 section 5): each gitea grant record gives its agent a store reference to
|
||||
// gitea, and `held` names the files memory must not load until reconcile or a revoke.
|
||||
const { held } = storeRoot ? giteaStart(store, storeRoot, businesses, references) : { held: [] };
|
||||
// #1886 (F3 defect l): a store reference with no file yet reads as no key (design D3a), but Credentials
|
||||
// refuses a missing store root, and only issue() made it. Start makes it the same way issue() does when
|
||||
// any reference names the store. A root that exists with loose modes or as a symlink still refuses.
|
||||
if (storeRoot && Object.values(references).some(refs => Object.values(refs ?? {}).some(ref => ref?.store === true)))
|
||||
mkdirSync(storeRoot, { recursive: true, mode: 0o700 });
|
||||
credentials = new Credentials({ references, repoRoots: protectedRoots, dataRoot, storeRoot, held,
|
||||
modelAgents: proxy ? Object.entries(businesses).flatMap(([id, b]) => Object.keys(b.roles).map(role => id + '/' + role)) : [] });
|
||||
if (proxy) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// scratch root only.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
||||
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { startBroker } from '../src/runtime.mjs';
|
||||
@@ -18,7 +18,7 @@ const OTHER = 'fake_other_token_5555555555';
|
||||
const DATE = '2099-01-01';
|
||||
const role = { authority: { withinRole: ['message.send'], crossRole: [] } };
|
||||
|
||||
async function setup(t) {
|
||||
async function setup(t, { storeRoot = true } = {}) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'bus-cred-verbs-'));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
const rolesDir = rolesCopy(root);
|
||||
@@ -26,7 +26,7 @@ async function setup(t) {
|
||||
doc.roles.coder.credentials.gitea = { store: true, rotateBy: DATE };
|
||||
writeJson(join(root, 'businesses/demo.json'), doc);
|
||||
mkdirSync(join(root, 'data'), { mode: 0o700 });
|
||||
mkdirSync(join(root, 'secrets', 'agents'), { mode: 0o700 });
|
||||
if (storeRoot) mkdirSync(join(root, 'secrets', 'agents'), { mode: 0o700 });
|
||||
const store = (agent, service) => join(root, 'secrets', 'agents', 'demo', agent, service);
|
||||
// The bus's resolved business carries the same references; a store one names its derived file.
|
||||
const demo = {
|
||||
@@ -161,3 +161,29 @@ test('T5: issue, rotate, revoke and delete are live, rewrite the business file a
|
||||
for (const path of walk(c.root))
|
||||
for (const token of [TOKEN, ROTATED, OTHER]) assert.equal(readFileSync(path).includes(token), false, path);
|
||||
});
|
||||
|
||||
test('#1886: a store reference with no store root yet starts the bus, makes the root private and issues', async (t) => {
|
||||
const c = await setup(t, { storeRoot: false });
|
||||
const dir = join(c.root, 'secrets', 'agents');
|
||||
assert.equal(statSync(dir).mode & 0o777, 0o700);
|
||||
assert.equal(statSync(join(c.root, 'secrets')).mode & 0o077, 0);
|
||||
assert.deepEqual(c.status('coder'), { gitea: 'not-issued', vikunja: 'valid' });
|
||||
const issued = c.ask(c.human, 'credentials.issue', { agent: 'coder', service: 'gitea', token: TOKEN + '\n', date: DATE });
|
||||
assert.deepEqual(issued, { agent: 'coder', verb: 'issue', service: 'gitea', date: DATE, state: 'valid' });
|
||||
assert.equal(statSync(c.store('coder', 'gitea')).mode & 0o777, 0o600);
|
||||
});
|
||||
|
||||
test('#1886: a store root that exists with loose modes still refuses start', async (t) => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'bus-cred-loose-'));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
const rolesDir = rolesCopy(root);
|
||||
writeJson(join(root, 'businesses/demo.json'), { ...businessDoc(root, 'demo'), plugins: { harness: ['pi'] } });
|
||||
mkdirSync(join(root, 'data'), { mode: 0o700 });
|
||||
mkdirSync(join(root, 'secrets', 'agents'), { recursive: true, mode: 0o750 });
|
||||
chmodSync(join(root, 'secrets', 'agents'), 0o750);
|
||||
const demo = {
|
||||
id: 'demo', human: 'jason', arbiters: { technical: 'coder', delivery: 'coder' }, vars: {},
|
||||
roles: { coder: { ...role, credentials: { gitea: { store: true, file: join(root, 'secrets', 'agents', 'demo', 'coder', 'gitea'), rotateBy: DATE } } } },
|
||||
};
|
||||
await assert.rejects(startBroker({ dataRoot: join(root, 'data'), businesses: { demo }, organization: { dir: root, rolesDir, system: systemFor(root) } }), /credential-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, 101);
|
||||
assert.equal(list.requirements.length, 118);
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"prd": "docs/prd/mosaic-stack.md",
|
||||
"version": "1.19",
|
||||
"version": "1.20",
|
||||
"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 assistant session; the chief of staff may end a run, others need a decision" },
|
||||
@@ -10,10 +10,13 @@
|
||||
{ "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-1", "title": "One assistant per user across organizations, with shared memory, passes work to each 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-COS-3", "title": "The chief of staff hires and fires role instances without a decision, within fixed bounds; the user forbids per agent" },
|
||||
{ "id": "REQ-AGENT-1", "title": "Agents are configured at root, organization and project levels; a project's agents are their union" },
|
||||
{ "id": "REQ-AGENT-2", "title": "The user can see and change agent, organization, project and session configuration through the write path" },
|
||||
{ "id": "REQ-AGENT-3", "title": "The chief hires and fires at all levels; root-level changes need user approval" },
|
||||
{ "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" },
|
||||
@@ -37,7 +40,7 @@
|
||||
{ "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-1", "title": "Each organization stores tasks through a task tracker module; Vikunja is optional" },
|
||||
{ "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" },
|
||||
@@ -51,7 +54,15 @@
|
||||
{ "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-WEB-2", "title": "The Console resolves an open decision with one button per option, only for a human it signed in itself (OIDC)" },
|
||||
{ "id": "REQ-WEB-3", "title": "The Console restarts the bus and reloads the organization file, gated and trailed; reload applies launch.instances live" },
|
||||
{ "id": "REQ-WEB-3", "title": "Human-gated Console restart, bus restart, reload and agent model controls; dev-local owner needs no sign-in" },
|
||||
{ "id": "REQ-WS-1", "title": "The WebUI always shows all organizations in a collapsible sidebar, with no switcher" },
|
||||
{ "id": "REQ-WS-2", "title": "The Console builds shared views across the user's organization buses, subject to membership" },
|
||||
{ "id": "REQ-WS-3", "title": "The main dashboard sums activity, work and metrics across the user's organizations and projects" },
|
||||
{ "id": "REQ-WS-4", "title": "Each organization has a dashboard for docs, settings, KPIs, metrics and active sessions" },
|
||||
{ "id": "REQ-WS-5", "title": "The projects list spans organizations, sorts and filters, and shows ownership" },
|
||||
{ "id": "REQ-WS-6", "title": "One inbox holds items needing the user from all organizations, projects and sessions" },
|
||||
{ "id": "REQ-WS-7", "title": "One view shows cross-organization activity; organizations and projects open side by side" },
|
||||
{ "id": "REQ-WS-8", "title": "The tasks view aggregates every organization, with Kanban and list layouts and filters" },
|
||||
{ "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" },
|
||||
@@ -59,6 +70,8 @@
|
||||
{ "id": "REQ-LIMIT-4", "title": "Usage sources and limit checks are the first plug-in kinds; an unknown turn is logged and shown, never counted as zero" },
|
||||
{ "id": "REQ-PLUG-1", "title": "Every part the core calls out to is a plug-in kind; modules load from packages/plugins, switched on in the org file" },
|
||||
{ "id": "REQ-PLUG-2", "title": "New call-outs go through a plug-in kind from the start; a hard-coded part converts when a row changes it" },
|
||||
{ "id": "REQ-SYS-1", "title": "System types are a plug-in kind; organizations enable a list of modules" },
|
||||
{ "id": "REQ-SYS-2", "title": "Projects use enabled system types and their agents receive the corresponding toolsets" },
|
||||
{ "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" },
|
||||
@@ -78,7 +91,7 @@
|
||||
{ "id": "REQ-DISK-14", "title": "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-1", "title": "Users and RBAC at install and organization level; agents are scoped to one organization except the assistant" },
|
||||
{ "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; a thread holds independent assistant sessions, each with kept history" },
|
||||
@@ -91,6 +104,8 @@
|
||||
{ "id": "REQ-SESS-1", "title": "Each session shows its lifetime count of completed compactions on pi and Claude Code; an unproven count shows unknown" },
|
||||
{ "id": "REQ-SESS-2", "title": "Clear context retires a session after a safe stop and starts a fresh successor with none of the old context" },
|
||||
{ "id": "REQ-SESS-3", "title": "Every session start passes the launch checks: authority, route, revocation, kill switch, limits and admission" },
|
||||
{ "id": "REQ-ACCT-1", "title": "Deployed systems require sign-in; users create and belong to organizations" },
|
||||
{ "id": "REQ-ACCT-2", "title": "Organization owners invite users with a chosen grant" },
|
||||
{ "id": "REQ-LIB-1", "title": "A human-set library maps the user's repository onto universes, books, types, playgrounds, supporting and archive folders" },
|
||||
{ "id": "REQ-LIB-2", "title": "Scope comes from the path or a DOCX's source, lookups default to canon, and only canon files are ever 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" },
|
||||
@@ -102,6 +117,8 @@
|
||||
{ "id": "REQ-LIB-9", "title": "The Console's DOCX editor: only a human saves, each save is one commit, and the editor code is vendored and pinned" },
|
||||
{ "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" }
|
||||
{ "id": "REQ-INST-3", "title": "One-command install of a named instance from a profile, and one-command upgrade with backup and rollback" },
|
||||
{ "id": "REQ-SETUP-1", "title": "Quick Setup initializes the project records" },
|
||||
{ "id": "REQ-SETUP-2", "title": "The assistant offers an onboarding interview and drafts a north star and first PRD for user approval" }
|
||||
]
|
||||
}
|
||||
@@ -18,12 +18,12 @@ 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, 101);
|
||||
assert.equal(ids.length, 118);
|
||||
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.19']);
|
||||
assert.match(prd, /^- Status: approved, version 1\.19\. Jason approved the exact text in Q289\.$/m);
|
||||
assert.match(prd, /^- Prior approval history, through version 1\.18:/m);
|
||||
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.20']);
|
||||
assert.match(prd, /^- Status: approved, version 1\.20\. Jason approved draft 2 on 2026-11-09\.$/m);
|
||||
assert.match(prd, /^- Prior approval history, through version 1\.19:/m);
|
||||
});
|
||||
|
||||
test('the committed list loads frozen', () => {
|
||||
@@ -76,7 +76,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[101].id: REQ-ROLE-1 twice'));
|
||||
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[118].id: REQ-ROLE-1 twice'));
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user