PRD 1.9: REQ-WEB-2, Console decide buttons for a human the Console signed in, and the REQ-DEC-3 amendment (mos-1202)

Jason approved the buttons in decision 00ed58e2 (vikunja:19/266) and chose
O6 in Q203 (OIDC-only), relayed by Mos in mos-1202 with "Land PRD 1.9 with
REQ-WEB-2 as written". The wording is sage-2067's draft. requirements.json
goes to 1.9 with 69 ids; tasks suite 97/0 and bus requirements.test.mjs 3/0
on Sage's slot.
This commit is contained in:
jason.woltje committed 2026-10-21 15:19:43 -05:00
1 parent 0de97f429d
commit f48725cfc1
4 files changed
+48 -8

No files matched your search

+42 -3
View File
@@ -1,6 +1,6 @@
# PRD: Mosaic Stack
- Status: approved, version 1.8. Jason approved draft 0.4 unchanged as
- Status: approved, version 1.9. 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
@@ -23,7 +23,19 @@
guardrails by default, a manual mode for per-use raises, and grants the
chief of staff requests and the user approves. Jason approved it in T3,
relayed by Mos on 2026-10-21 (DECISIONS Q192), so there is no in-Stack
prd.approve for it.
prd.approve for it. Version 1.9 adds REQ-WEB-2 and amends REQ-DEC-3:
the Console decide buttons Jason approved in decision 00ed58e2
(vikunja:19/266), signed in as his Q175 ruling set, and OIDC-only as he
chose in Q203, relayed by Mos on 2026-10-21 (mos-1202).
- Version 1.9 source, Jason's answer to Q203 in T3, relayed by Mos on
2026-10-21 (DECISIONS Q203, mos-1202): "Let's use O6. If a user does
not have an OIDC provider to configure, we will need to use a bundled
Pocket ID instances to handle this traffic." O6, as Mos put it to him:
"keep the REQ-WEB-2 wording OIDC-only. Identity is the (issuer,
subject) pair from the Console's own verified OIDC sign-in. Local
accounts come from a provider, and the Console never holds passwords."
The second sentence is not part of 1.9. It becomes its own requirement
in a later version (vikunja:19/458).
- Version 1.8 source, Jason's approval in T3, relayed by Mos on 2026-10-21
(DECISIONS Q192): "PRD 1.8: I am preemptively approving it. Do not
surface in the Mosaic Stack board. Just proceed."
@@ -511,7 +523,8 @@ Added in version 1.8 (Q188):
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
or a Console session signed in under REQ-WEB-2, both started outside
any agent run. Agents reach the decision store only
through a broker. The broker stamps role and run from the launch record.
- **REQ-DEC-4.** A gated decision that blocks work reaches an authorized human
right away: in the CLI inbox, plus a Discord DM sent through the existing
@@ -744,6 +757,18 @@ Added in version 1.7:
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).
- **REQ-WEB-2.** The Console resolves an open decision with one button
per option, only for a human it signed in itself, recorded via=console.
(Version 1.9, decision 00ed58e2, Q175 and Q203.)
- Identity is the (iss, sub) pair the Console verified from its own
OIDC session after `--sso` sign-in (Q175), and that pair must map to
an authorized human. The Console takes no identity from a proxy
header and none from a shared secret.
- A request that carries an agent session or a seat credential is
refused.
- 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
@@ -1155,6 +1180,20 @@ only hard waits in his inbox.
Until row 155 lands, the assistant's runs hold thread claims that
the end and revoke paths don't read, so neither can stop one. The
human stops one with `mosaic stop <run>`.
- Version 1.9:
- The 1.8 text of REQ-DEC-3 began: "A human resolution, by an
authorized human, comes only from a `mosaic` CLI session started
outside any agent run."
- The first text on vikunja:19/266 had the Console trust the
`X-authentik-uid` header. Q175 replaced that: the Console runs its
own OIDC sign-in and takes no identity from a proxy header. Q203 (O6)
keeps the requirement OIDC-only, so the Console holds no passwords.
- The requirement covers every open decision. The first build, row
252 (#1753), covers grant requests only (Q188). It resolves through
the human boundary row 196 (#1694) builds.
- The live Console runs without `--sso` and without `--send`, so it
resolves nothing until the Q175 phase 2 window turns both on and a
re-pin carries row 252. This amendment adds the requirement only.
## 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, 68);
assert.equal(list.requirements.length, 69);
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
+2 -1
View File
@@ -1,6 +1,6 @@
{
"prd": "docs/prd/mosaic-stack.md",
"version": "1.8",
"version": "1.9",
"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" },
@@ -50,6 +50,7 @@
{ "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-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-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" },
+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, 68);
assert.equal(ids.length, 69);
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.8']);
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.9']);
});
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[68].id: REQ-ROLE-1 twice'));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[69].id: REQ-ROLE-1 twice'));
} finally {
rmSync(dir, { recursive: true, force: true });
}