plans: row 199 design r3, Mos rulings mos-1027 (forks, git.push.remote as a hard wait, tester status and revoke only) (#1697)
This commit is contained in:
1 parent
e806656b1d
commit
c2c729ddf0
1 file changed
+37
-19
@@ -7,6 +7,9 @@ No credential value was read to write this. Evidence is code and docs only.
|
||||
r2 answers duckworth-0013 (review at 6ab9cd927 on duckworth/reviews): B1 in D3a, T1 and T5, B2 in D9 step 5
|
||||
and T3, N1 in section 6 and Q-M3, N2 in section 6, N3 in D9 step 3, N4 in D9a and T3, N5 in E5 and D9b.
|
||||
|
||||
r3 records Mos's rulings (mos-1027) in section 9. The Q-M2 hard wait changes D9 step 1, D9b and T3. Q-M1
|
||||
drops O2 (D10, A3, R2).
|
||||
|
||||
## 0. Terms
|
||||
|
||||
- **Stack instance**: one Mosaic Stack deployment (row 190). It has one OS user, one config dir
|
||||
@@ -115,13 +118,21 @@ commits to that user. Agents without `gitea` keep E7.
|
||||
D9. **Push to a remote: a new git socket op, `publish`.** No new socket, no bundle, and no token in the sandbox.
|
||||
|
||||
```
|
||||
publish {"cap", "op": "publish", "task_ref", "project", "branch"}
|
||||
publish {"cap", "op": "publish", "task_ref", "project", "branch", "decision"}
|
||||
-> {"ok": true, "result": {"remote", "ref", "commit", "previous"}} or a refusal
|
||||
```
|
||||
|
||||
The host checks, in order, and each refusal goes through `broker.refuse()`:
|
||||
|
||||
1. Identity from the cap (E5). The role holds the new action `git.push.remote` (`remote-not-allowed`).
|
||||
1. Identity from the cap (E5), then the broker's `#checkAuthority(s, "git.push.remote", {decision, target:
|
||||
task_ref})`, as `checkLand` does for a merge (`broker.mjs:907-913`). `git.push.remote` is a new action in
|
||||
ACTIONS and is not in ROUTED (REQ-WAIT-3, `vocabulary.mjs:52-55`, `broker.mjs:42-69`), so it is a hard wait
|
||||
and classifies `gated` for every role. Every publish therefore needs a decision the human resolved for
|
||||
this task ref, and the decision names the commit. A decision is spent once (`decision-consumed`), and a
|
||||
commit other than the task's latest recorded push refuses `ref-moved` (`#latestPush`, `broker.mjs:936`).
|
||||
No role definition grants the action yet. When Jason's PRD version routes it (Sage, Vikunja #420), a
|
||||
reviewed commit adds it to both ROUTED lists and to coder's `withinRole`. From then on a coder publishes
|
||||
without a decision, and `decision` is optional for a within-role class only.
|
||||
2. The project is in the agent's grant, `roles.<agent>.publish: [project]`, and the business file names the
|
||||
project's remote, `projects.<project>.remote: "https://..."` (`remote-not-granted`). The remote's scheme is
|
||||
https and its host and port equal `identity.gitea.url`'s (`remote-host-mismatch`). The default https port
|
||||
@@ -159,21 +170,23 @@ D9b. **How `publish` reaches the broker and the store.** The git socket runs in
|
||||
(`cli.mjs:504-506`) and reaches the broker only through the host's op table (`bus/src/runtime.mjs:222-246`).
|
||||
`publish` adds three entries there, each wrapped in `refusing()`:
|
||||
|
||||
- `checkPublish(cap, args)`: steps 1 to 3. It returns `{remote, user, email, source}` and no token.
|
||||
- `checkPublish(cap, args)`: steps 1 to 3, including the decision check. It returns `{ticket, remote, user,
|
||||
email, source}` and no token. The ticket holds the session and the decision until `recordPublish`, the
|
||||
same way `checkLand` holds a landing.
|
||||
- `withGitea(cap, fn)`: `credentials.useFor(broker.session(cap), "gitea", fn)`. `fn` gets the token and runs
|
||||
step 5. The token never leaves that call.
|
||||
- `recordPublish(cap, result)`: step 6.
|
||||
- `recordPublish(ticket, result)`: step 6, and it spends the decision.
|
||||
|
||||
The op table gets no entry that returns a token.
|
||||
|
||||
D10. **Which refs.** Our side: only `refs/heads/<gitea.user>/*`, set by the host (D9 step 3). The default
|
||||
branch and other agents' prefixes can't be named. Gitea side, two options (Q-M1):
|
||||
|
||||
- **O1. A fork per agent (recommended).** The agent's Gitea user has read on the upstream repo and owns a fork.
|
||||
- **O1. A fork per agent. Ruled by Mos (mos-1027).** The agent's Gitea user has read on the upstream repo and owns a fork.
|
||||
Its token can write only repos the user owns or collaborates on, so Gitea itself stops any push to upstream
|
||||
or to another agent's fork. No branch rules are needed per agent, so none can drift. The cost: work reaches
|
||||
upstream as a pull request from the fork, opened by a human or a later API verb.
|
||||
- **O2. One shared repo.** The agent is a Write collaborator. Upstream's default and release branches get
|
||||
- **O2. One shared repo. Not chosen.** The agent is a Write collaborator. Upstream's default and release branches get
|
||||
branch protection with no agent on the push list, plus a rule `<user>/*` per agent. Gitea doesn't stop agent
|
||||
A from creating an unprotected name outside `A/*` unless a catch-all rule outranks the per-agent ones. That
|
||||
depends on Gitea's rule priority, which I haven't verified on our version (probe P1). Every hire adds a rule.
|
||||
@@ -228,8 +241,7 @@ Agents never mint accounts or tokens. Jason's steps, per agent that needs `gitea
|
||||
`gitea.user`. The email is the agent's `identity.email`.
|
||||
- A2. Create its token: sign in as that user, Settings, Applications, Generate New Token, scopes
|
||||
`write:repository` and `read:user`, nothing else.
|
||||
- A3. Under O1, create the fork as that user. Under O2, add the user as a Write collaborator and add the branch
|
||||
rules.
|
||||
- A3. Create the fork of each granted repo as that user (O1).
|
||||
- A4. On the stack instance's VM, as its OS user: `mosaic credentials issue AGENT gitea --rotate-by DATE`, with
|
||||
the token pasted into stdin.
|
||||
- A5. On fire or rotate: delete the token in Gitea (Settings, Applications), or disable the user.
|
||||
@@ -248,17 +260,19 @@ Vikunja bot accounts and tokens are already Jason's (tracker bots). Only A4 chan
|
||||
|
||||
## 9. Questions
|
||||
|
||||
For Mos (the authority model):
|
||||
Mos ruled on the authority model in mos-1027 (2026-10-20T19:27Z):
|
||||
|
||||
- Q-M1. Gitea side: O1 forks (recommended) or O2 one repo with branch rules.
|
||||
- Q-M2. A new role action `git.push.remote`, added to the coder definition in `roles/` (a reviewed role
|
||||
change). The agent pushes its own branch to a granted remote, and nothing is automatic.
|
||||
- Q-M3. Issue, rotate, revoke and delete are human-only. The tester gets `status` and `revoke` on the dev
|
||||
instance only (duckworth N1). The chief gets none of them.
|
||||
- Q-M4. The model credential stays per stack instance for 10-27, and per-agent model tokens wait for a proxy
|
||||
- Q-M1. O1, one fork per agent.
|
||||
- Q-M2. `git.push.remote` and its bounds in D9 to D11 are approved. It lands as a hard wait: out of ROUTED,
|
||||
and no role grants it within-role (D9 step 1). Sage puts its routing in the PRD version draft going to
|
||||
Jason with `role.hire` and `role.fire` (Vikunja #420). After his approval, a reviewed commit routes it and
|
||||
grants it to coder.
|
||||
- Q-M3. Issue, rotate and delete are human-only, and the chief gets none of them. Under Q173 the dev
|
||||
instance's tester gets `status` and `revoke` only.
|
||||
- Q-M4. The model credential stays per stack instance for 10-27. Per-agent model tokens wait for a proxy
|
||||
row.
|
||||
|
||||
For Jason, through Mos:
|
||||
Open for Jason. Mos takes these to him one at a time:
|
||||
|
||||
- Q-J1. The model account for Case 1 and Case 2: existing subscription or a new account.
|
||||
- Q-J2. Gitea users and tokens for the agents that need them (A1 to A3).
|
||||
@@ -281,7 +295,10 @@ All tests use scratch roots, fake tokens and a fake Gitea: `git http-backend` be
|
||||
- T2. `useFor` keys from the cap only. A request naming another agent is refused and leaves one
|
||||
`action.refused`.
|
||||
- T3. `publish`: happy path lands `refs/heads/<user>/<branch>` on the fake remote, a fork that shares root's
|
||||
history. Refusals for each step in D9, one test each, including `work-not-yours`. A force-push attempt is
|
||||
history. Refusals for each step in D9, one test each, including `work-not-yours`. Until routing, a publish with
|
||||
no decision refuses, one with a decision for another task or commit refuses, and a second publish on a
|
||||
spent decision refuses `decision-consumed`. A test pins that `git.push.remote` is in neither ROUTED list
|
||||
and that no `roles/*.json` grants it. A force-push attempt is
|
||||
refused by git's non-fast-forward. A fake remote that answers 302 to a second 127.0.0.1 port: the push
|
||||
fails, and the second port receives no request with an Authorization header. A remote on the right host
|
||||
and a different port is refused at step 2. A first publish to an empty remote and to an unrelated remote
|
||||
@@ -297,12 +314,13 @@ Mutants to kill: drop the cap-derived key in `useFor`, let the request name the
|
||||
the refspec, skip the identity check, put the token in the URL, revoke file before memory, skip the dir mode
|
||||
check, allow a `{file}` reference into another agent's dir, make ENOENT on a `store` path fatal again, make
|
||||
ENOENT on a `{file}` path silent, a helper that answers any host, drop `http.followRedirects=false`, drop the
|
||||
`work-not-yours` check, drop the `remote-unrelated` check.
|
||||
`work-not-yours` check, drop the `remote-unrelated` check, add `git.push.remote` to ROUTED, skip the spent
|
||||
check on the decision.
|
||||
|
||||
## 12. Risks
|
||||
|
||||
- R1. Backups: if a backup tool copies `<configDir>`, it copies tokens. The build checks row 185's backup set
|
||||
and says what it includes.
|
||||
- R2. P1 (Gitea rule priority) is unverified. It matters only under O2.
|
||||
- R2. Withdrawn. O1 was ruled, so probe P1 isn't needed.
|
||||
- R3. Outbound HTTPS from the bus host to Gitea is new network reach for the host process. On the VMs this is
|
||||
Jason's network to confirm.
|
||||
Reference in new issue
Block a user