25 KiB
PRD 1.8 draft: trusted actions within role guardrails, the manual mode, and grants the chief of staff
requests and the user approves
Draft by Honker for Vikunja #434, under Mos's assignment mos-1117 (through Sage, sage-1525). Not approved.
It changes nothing until Jason approves it as a prd.approve decision. Sage opens that decision and sends
Mos its id. Base: docs/prd/mosaic-stack.md version 1.6 at 008746b6e, plus the version 1.7 text Jason
approved (PRD-1.7-DRAFT.md r2, 9ba31b5b3, decision d52febb5, mos-1089). Where 1.7 is not yet written into
the PRD file, this draft's anchors name 1.7's sections.
Revision 2 makes Mos's edits a to f (mos-1124, through Sage, sage-1534) and fixes Duckworth's B1 and B2
with notes N1 to N6 (duckworth-0057) on r1 at 3c30f1036. Revision 3 fixes Duckworth's C1 and takes
notes N1 and N2 (duckworth-0058) on r2 at 60f5ca8fa, with Gizmoduck's enrollment wording
(gizmoduck-0067). Revision 4 makes Mos's rulings on duckworth-0057 (mos-1129, through Sage, sage-1538)
on r3 at d16ccdb91: only an Approve creates a grant, one decision for a grant that makes an account, and
the arbiter route for host.sudo. Revision 5 fixes two stale notes from Duckworth's r4 check
(duckworth-0061 N1), with no change to the requirement text. Revision 6 makes Mos's wording for those
notes and adds two items for Jason at approval (mos-1138, through Sage, sage-1548), again with no change
to the requirement text.
For Jason at approval: "The main agent or chief of staff" in Q10 is read as one role under two names, the chief of staff. So only the chief raises a grant request, and your assistant passes your own request to the chief. That reading is Mos's (mos-1124). To change it, say so when you approve, and REQ-GRANT-1 changes before the text goes into the PRD.
Also for Jason at approval:
- Only an Approve creates a grant. A grant written into the business file by hand makes nothing. An agent that runs as the same OS user as the stack can still write the stack's own store. That is the B7 residual (Q182 on dragon-lin, #423 on .53 and .54), and this version doesn't close it.
- Who decides a root command on another host changes. Once
host.sudois routed, a raise for it that cites the spec and isn't within-role goes to the arbiter for its domain, as version 1.7 set forgit.push.remote, not to you. The grant and its allowlist still limit the command. In manual mode the decision comes to you (REQ-TRUST-2). - Revoke: Q188 says "you, Mos or the tester". "Mos" is read as your assistant (REQ-ROLE-4), limited to grants in its own organization, and the tester may revoke only on a dev instance. That reading is Mos's (mos-1124 d, mos-1132). To change it, say so when you approve, and REQ-GRANT-4 changes before the text goes into the PRD.
- The broker enforces only that the chief of staff raised a grant request. The rest of the chain of command is the messaging lists and the role contracts, not a broker check (REQ-GRANT-1).
What it changes
Today an agent acts without a raise only on actions its role file grants within-role, and the role
files grant few. Every other use of a tool or command waits on a decision. Row 210 builds host.sudo as a
hard wait, so Jason would decide each command run on another machine. Grants themselves are hand work:
Jason makes every token and key from a runbook (REQ-CRED-1).
Jason's ruling Q188 turns this around. Roles set what each agent may do, and by default agents do it at will within their sandbox and guardrails. A per-use raise happens only in a manual mode. No agent creates a grant. The chief of staff brings a grant request to the user's inbox, the user clicks Approve or Deny, and protected stack code does every step of making the grant. The user's own tokens are entered once in the WebUI and never reach an agent.
This version adds:
- Trusted actions within role guardrails as the default mode (REQ-TRUST-1).
- Manual mode, a list of actions that each need a decision per use: who sets it, where it lives and what it changes (REQ-TRUST-2).
- Grant requests, raised only by the chief of staff through the chain of command (REQ-GRANT-1).
- Approve and the protected grant code, with three grant methods: machine key, Gitea and GitHub (REQ-GRANT-2).
- The user's tokens, entered in the WebUI and used only by the grant code (REQ-GRANT-3).
- Row 210's REQ-CRED-4, generalized from
host.sudoto every role-listed action that uses a grant (REQ-CRED-4). - Instant revoke (REQ-GRANT-4).
The hard waits stay with the user (Mos, mos-1117): spending money, deleting data, new external accounts,
messages outside the fleet and the rest of REQ-WAIT-1. Approving a grant is the user resolving one of
those hard waits, credential.mint. A grant that makes an account, such as a seat's own Gitea user
(Q190), is the same one decision: its method says it makes an account, and the inbox shows the account
name before Approve. So no hard wait moves to an agent.
When this version lands, reviewed commits:
- add the manual-mode list to the organization file schema, written only on the human's path (REQ-TRUST-2), and make the broker classify a listed action as needing a decision for each use, before any other routing.
- add the grant request: a decision the chief of staff raises with action
credential.mint, naming the agent, the method and target, the exact rights and, when the method makes an account, the account name (REQ-GRANT-1). The broker refuses acredential.mintraise from any other role. Anaccount.createraise isn't a grant request and routes as it does in version 1.7, such as a PM's sign-up for a service. - add the grant code that runs on a resolved Approve, one method at a time, and the grant record it writes (REQ-GRANT-2, Vikunja #435 and #436).
- add the user's token store, written from the WebUI (or the CLI until the Console knows the signed-in human) and read only by the grant code (REQ-GRANT-3).
- move
host.sudointo bothROUTEDlists, as row 199 does forgit.push.remote, and route it under REQ-CRED-4, with the arbiter route version 1.7 set forgit.push.remote. Which definitions list it within-role is a separate reviewed role commit inroles/. - update the role contracts that list the hard waits:
roles/chief-of-staff.md:75raises grant requests with the REQ-GRANT-1 fields, androles/pm.md:55asks the chief of staff for a grant instead of raisingcredential.mintitself (line numbers on origin/refactor). Other roles that list the hard waits get the same line as the PM. A role's messaging list carries the chain of command.
Text for the PRD
Status line, appended to the current paragraph:
Version 1.8 records Jason's ruling Q188 of 2026-10-20: trusted actions within role guardrails by default, a manual mode for per-use raises, and grants the chief of staff requests and the user approves, approved by Jason in decision <prd.approve decision id>.
Source, two new bullets above "Version 1.7 source":
- Version 1.8 source, Jason's ruling Q188 (DECISIONS, 2026-10-20), his answers to Q8 and Q10, in part. Q8: "I would rather grant the grant right to an agent that runs with strict guardrails. That agent presents the final authorization for the user. Upon approval, the grant is created programmatically. I see this as: User configures their PATs, API tokens, authorizations within the WebUI. The agent presents the request for grant with X rights for another agent. The user reviews, then clicks Approve/Deny. Click of Approve fires off protected mechanical methods to handle the grants on Github, Gitea, .... services as needed. The tooling would need to exist and must be guarded." And "Disallowing the agents to create grants autonomously is a necessary evil for better security. Requiring the user to manually perform the steps is a no-fly." Q10: "Agents should have tools and command access based on roles. Those roles dictate the allowed tools and commands. The use of a tool or command should not be raised to the user unless the system is configured with a manual mode that requires user interaction. By default I want to have the agents capable of running commands and tools at will within their sandbox/guardrails. This allows autonomous actions by agents within their lanes. Perhaps a "Trusted actions within approved guardrails" methodology." And "The main agent or chief of staff would be responsible for surfacing the request to the user's inbox for approval. Not all agents are capable or authorized to request directly. It must follow the chain of command properly." He agreed with Mos's Q10 points 3 to 6: protected stack code does every step on Approve, his tokens are entered in the WebUI and agents never see them, a command on the approved list runs without a click and any other is refused, and he, Mos or the tester can revoke a grant at once.
- Version 1.8 source, Jason's ruling Q190 (DECISIONS, 2026-10-20), his answer "Q12: O1": each agent seat gets its own account on his Gitea, created through the Q188 approval path once the Gitea grants exist.
Under "Decisions", after REQ-DEC-6, a new section:
Trusted actions
Added in version 1.8 (Q188).
- REQ-TRUST-1. Agents act within their role guardrails without asking. The default mode is "trusted actions within approved guardrails". An action a role file grants within-role is done at will, inside the agent's sandbox, its tool limit and its grants, and is logged. It raises nothing to the user. An action the file doesn't grant within-role still routes by REQ-DEC-2, and a hard wait still goes to the user (REQ-WAIT-1). Only a reviewed role change adds an action to a role file, and REQ-WAIT-2 still keeps every hard wait out of every role file.
- REQ-TRUST-2. The user can put chosen actions in manual mode. The organization file holds the list of actions in manual mode, empty by default. While an action is on the list, every use of it by any role becomes a decision for that use, raised to the user, and the action waits until the user resolves it. A decision denied, or not resolved, refuses the action. Each decision is used once. For a command on another host, the decision names the host, the command and its input. Actions not on the list are unchanged. Only the user changes the list, through the WebUI or the CLI. The chief of staff's organization writes (REQ-ROLE-3) can't change it, and no agent can. Every change is logged with who made it. A change applies to the next use, and a run already in progress keeps no exemption.
Under "Credentials", after REQ-CRED-3 (version 1.7):
Added in version 1.8 (Q188):
REQ-CRED-4. An action a role definition lists within-role, and that uses a grant, runs without a decision for each use only when all of these hold:
- the definition declares the credential kind the action uses;
- a grant names that agent, and that host or service;
- for a command, the command matches the grant's allowlist. A grant lists exact argv prefixes. A wildcard grant is refused;
- the agent's key or token for that grant is issued and not revoked.
A command off the allowlist is refused, and adding one takes a new grant request (REQ-GRANT-1). An action whose definition doesn't list it within-role still needs a decision for each use. Only a reviewed role change adds an action to a definition.
host.sudoroutes under this requirement.REQ-GRANT-1. No agent creates or writes a grant. A grant request follows the chain of command, and only the chief of staff raises it to the user's inbox. Any other role that needs a grant for itself or for an agent it directs asks the chief of staff, through its chain: a worker role through its PM, the PM and the CTO directly. The assistant passes the user's own request to the chief of staff (REQ-ASSIST-1). The request names the agent, the grant method and its target, the exact rights, and a rotate-by or expiry date. The chief raises one only for an agent whose reviewed role definition allows that kind of credential, and the broker refuses any other. A grant can't name a wildcard, a shell or an empty command. The request is a decision with the action
credential.mint, so it goes to the user as a hard wait (REQ-WAIT-1). A grant whose method makes an account, such as a seat's own Gitea user (Q190), is the same one decision: the method in the grant says it makes an account, the request names the account, and the inbox shows the account name before Approve. The user's one resolution covers the account and its credential, so the hard-waits list doesn't change. Anaccount.createraise isn't a grant request and routes as version 1.7 has it. The broker checks only that the chief of staff raised the request. The chain of command before that is in the messaging lists and the role contracts, not a broker check.REQ-GRANT-2. The user approves or denies each grant request in the inbox. Approve is the only thing that creates a grant, and the code uses a grant only when it has a matching Approve record. No agent creates a grant. A grant written into the business file by hand makes nothing. Row 210's hand-written grants stay a test path until Vikunja #436a moves
host.runto grants made by Approve. On Approve, protected stack code does every step, not an agent and not the user: it records the grant, makes the key or token, installs it on the machine or creates it on the service, and logs each step. On Deny nothing is made, and the denial is logged. The grant methods are:
- machine key: a key for one agent on one enrolled host, with the command allowlist of REQ-CRED-4. A host's identity comes from the host side when the user approves its enrollment, never from a first connection. A host user with sudo receives a grant only after the user approved that user by name;
- Gitea: a token for one agent, and for an agent seat its own Gitea user (Q190), with an expiry. The all scope and the admin scope are never granted;
- GitHub: a grant for one agent.
The Gitea and GitHub rights units come in a later design that Jason approves.
The approval binds the exact rights the user saw and the target or its enrolled identity. A change to the agent, its definition, the target, the target's identity or the rights after the raise voids it. An approval is used once. A grant the code couldn't finish is undone at once on the stack's side, so it is never used. What it left on the host or service stays visible as pending until it is removed, as for a revoke (REQ-GRANT-4). A new grant needs a new approval, never an automatic retry. The grant code runs only on a request the user resolved. A request resolved by an arbiter, the tester or any agent makes nothing. A
credential.mintdecision raised before version 1.8, or without the fields of REQ-GRANT-1, makes nothing when it is approved. Until the Console knows the signed-in human (Vikunja #412), Approve ismosaic decidein the CLI (REQ-DEC-3).REQ-GRANT-3. The user enters their own personal access tokens and API tokens in the WebUI, or the CLI. Only the grant code reads them, to make grants. No agent ever sees a token value or a key's private half. Values appear in no event, log, message, prompt or agent file, and no agent can read them back (REQ-VAR-2). Until the Console knows the signed-in human, they are entered through the CLI.
REQ-GRANT-4. A grant can be revoked at once by the user, by the assistant for a grant in its own organization, or by the tester on a dev instance (Q173). A revoke needs no decision and no new trust. It takes effect at once on the stack's side, without waiting on the network, so the next use is refused. Removing the key or token at the host or service follows. Until that removal is done it stays visible as pending. A revoke while the grant is being made stops it, and nothing it made stays usable. Every revoke is logged with who made it.
REQ-CRED-1, appended:
Since version 1.8, a token or key for a grant is made by the grant code on the user's Approve (REQ-GRANT-2), not by hand. Tokens that exist stay as they are.
REQ-WAIT-3, appended:
host.sudowas added as a hard wait and routes under REQ-CRED-4 since version 1.8.
Under "Risks and open questions", a new bullet:
- Trusted mode lets an agent run its allowed commands at will. On another host, an allowed command that writes a file as root or starts a shell can plant a key, and the seat sandbox doesn't confine the network, so root can outlive the grant and a revoke. The allowlist narrows this. A seat sandbox with its own network namespace closes it. Until then the host's own audit log is the check after the fact (row 210 design section 6).
Notes for the reviewer and for Mos
- REQ-TRUST-1 restates what REQ-DEC-2 already does for within-role actions ("routine and within-role decisions are logged") and names it as the default mode. It grants nothing new by itself. What widens is the role files, one reviewed commit at a time.
- The hard waits stay with the user (mos-1117). REQ-GRANT-1 makes the grant request a
credential.mintdecision, already a hard wait under REQ-WAIT-1, so Approve is the user resolving it. REQ-WAIT-2 stands: no role file grantscredential.mint. The grant code acts on the user's resolution, not on a role grant. A grant that makes an account, such as a seat's Gitea user under Q190, is the same onecredential.mintdecision. So 1.8 doesn't change the hard-waits list (mos-1124 a, mos-1129 N4). - Manual mode: Sage asked who sets it, where it lives and what it changes. Mos accepted these choices
(mos-1124 b), and Jason can change them: a list of actions in the organization file, empty by
default, changed only by the user, outside the chief's organization writes. A list rather than one
switch follows Gizmoduck's #436 draft section 7 (gizmoduck/machine-grant-design
f6b6a04b6), so Jason can make host commands manual without making every action manual. Listing every within-role action gives the whole-organization manual mode. Row 210's per-callhost.rundecision, bound to host, argv and stdin and spent once, is this mode's decision for a host command (Q188, Mos's reading). - Gizmoduck's draft proposes REQ-CRED-5 (section 8) for the machine grant. Its content is in REQ-GRANT-1 to REQ-GRANT-4 here, generalized to all three methods, so 1.8 has no REQ-CRED-5. Their bounds in gizmoduck-0062 items 4a to 4g are in the text: the chief raises only for an allowed definition, the approval binds the rights shown, it is used once with no automatic retry, no value is ever seen, revoke is at once on the stack's side with the host or service removal visible until done, every grant has a rotate-by or expiry date, and the host identity comes from the host side. Duckworth has reviewed their design r1 to r4 (duckworth-0056 and duckworth-0060), so a later change there may need a change here.
- B1 (duckworth-0057, mos-1129): a grant the code couldn't finish is undone at once on the stack's side. What it left on the host or service stays visible as pending until it is removed, as for a revoke.
- B2 (duckworth-0057, mos-1129): Mos chose Duckworth's second option. Only an Approve creates a grant,
and the code uses a grant only when it has a matching Approve record. A grant written by hand makes
nothing. Jason's Q188 gives the reason: "Disallowing the agents to create grants autonomously is a
necessary evil for better security" and "Upon approval, the grant is created programmatically." Row
210's hand-written grants stay a test path until #436a moves
host.runto grants made by Approve. r2 and r3 had a hand path in the business file, and r4 takes it out, with r3's load check on hand grants (duckworth-0058 N1) and the note that a revoke never rewrites the user's file (duckworth-0058 N2). The same-uid write to the stack's own store stays the B7 residual, and the top of this draft names it for Jason. - C1 (duckworth-0058, sage-1538):
account.createis the hard wait for any new external account, not only a grant (REQ-WAIT-1,vocabulary.mjs:43-46,roles/pm.md:55). It stays as version 1.7 has it. Onlycredential.mintis chief-only, and a grant that makes an account is acredential.mintdecision (N4), so a PM's sign-up for a service routes as before. - Gizmoduck's machine-grant r2 (
eb4c756a1, gizmoduck-0067): enrollment is a request the user approves and the stack runs (mos-1120), and a host user with sudo is enrolled by name, the user's Approve covering that user (mos-1125). REQ-GRANT-2's machine key says both. Their design r3 and r4 (1c5705d6dsection 6) give revoke to the user, the assistant in its own organization and the tester on a dev instance, as REQ-GRANT-4 does (mos-1124 d). - N1 (duckworth-0057, mos-1129): the approval binds the target or its enrolled identity.
- N2 (duckworth-0057, mos-1129): a revoke during creation stops it, and nothing it made stays usable.
- N3 (duckworth-0057, mos-1129): landing step 6 lists the role contract updates (
roles/pm.md:55,roles/chief-of-staff.md:75). Acredential.mintdecision raised before 1.8, or one without the grant fields, makes nothing when it is approved. - N4 (duckworth-0057, mos-1129): a decision carries one action (
a.action, broker.mjs:1678 on origin/refactor). A grant that makes an account is onecredential.mintdecision: the method in the grant says it makes an account, and the inbox shows the account name before Approve. The user clicks once, and there is no separateaccount.createaction for it. Gizmoduck's machine-grant r2 reads it the same way. - N5 (duckworth-0057, mos-1129):
host.sudokeeps the arbiter route version 1.7 set forgit.push.remote. A raise for it that cites the spec and isn't within-role goes to the arbiter for its domain (broker.mjs:1677-1678 on origin/refactor), and the grant and its allowlist still limit the command. In manual mode the decision goes to the user. The top of this draft names this for Jason as a change in who decides a root command on another host. - N6 (duckworth-0057, mos-1129): the broker enforces only that the chief of staff raised a grant request. The rest of the chain of command is the messaging lists and the role contracts (step 6 of "When this version lands"). REQ-GRANT-1 says so.
- Approving a grant doesn't by itself let a command run without a click. That needs REQ-CRED-4 and
host.sudoin the routed lists, step 5 of "When this version lands" (gizmoduck-0062 item 4i). - Which roles may request: Q188 says "Not all agents are capable or authorized to request directly. It must follow the chain of command properly." The text makes the chief of staff the only role that raises, the PM and the CTO ask it directly, and worker roles ask through their PM. Jason's words also name "the main agent", but REQ-ASSIST-1 keeps the assistant out of directing agents, so it passes the user's request to the chief rather than raising one. Mos accepted this (mos-1124 c) and reads "the main agent or chief of staff" as one role under two names. That reading is Mos's, so the top of this draft puts it to Jason at approval.
- REQ-CRED-4 keeps row 210's conditions (origin/gizmoduck/row210-on-199-r2, agents/gizmoduck/work/queue-210/DESIGN.md:376-392): the definition declares the kind, the grant names the agent, an exact argv-prefix allowlist with wildcards refused, the key issued, and only a reviewed role change adds the action. Row 210 put the grant in a hand-edited business file, which stays a test path until #436a. Under 1.8 the grant is what Approve records, so "the business file grants that agent that host" becomes "a grant names that agent and that host".
- Revoke: Q188 says "you, Mos or the tester". Mos is Jason's assistant (REQ-ROLE-4), so the text says the assistant, limited to grants in its own organization, and the tester only on a dev instance (Q173). Mos accepted this with those limits (mos-1124 d).
- Approve in the Console needs the Console to know the signed-in human (Q175 phase 2, #412). Until then
Approve is
mosaic decide, which REQ-DEC-3 already allows (mos-1124 e). This draft doesn't take REQ-WEB-2, which stays with the Console decide buttons amendment (Vikunja 19/266). That amendment, or a later version, moves Approve into the Console. - Roles already use a scoped Gitea token (
<read|write>:<category>, all and admin never granted,packages/business/src/vocabulary.mjs:105-106on origin/refactor). It is a likely Gitea unit, not a settled one. Only the machine key is designed so far. Gitea and GitHub are a later #436 draft, and minting a Gitea token needs an admin credential the user enters, which is a design of its own (Gizmoduck's draft section 11). GitHub's rights unit isn't settled. So the text leaves both units to a later design Jason approves (mos-1124 f), and keeps only the bounds: an expiry, and never the all or admin scope. The PRD names outcomes and checks, not the grant code's steps, as REQ-CRED-3 does. - Jason expects the inbox Approve and the Gitea and GitHub grants by 10-27 (Q188 point 6). That is a delivery date, so it is in the source and not in the requirement text.
- This draft follows the PRD's own style: plain words, no semicolons in new sentences, and the status paragraph never edited in place except to append.