plans: #436b design r5 on dewey-0761: revoke on an open rotation deletes both previousTokenId and the minted tokenId, failures to serviceLeft (X-G5); N8 step 0 refuses grant-token-unknown while held; section 7 unchanged; no code change

This commit is contained in:
2026-10-20 18:35:14 -05:00
parent 3e66f5acfd
commit 1be535aca9
+21 -6
View File
@@ -1,4 +1,4 @@
# Gitea grant path, design draft r4 (Vikunja #436b, Gitea #1697 follow-up)
# Gitea grant path, design draft r5 (Vikunja #436b, Gitea #1697 follow-up)
Author: Megavolt. Reviewer: Dewey. GitHub half: Bushroot. Base: Gizmoduck's #436a r6 (f9039682e, final,
`agents/gizmoduck/work/machine-grant/DESIGN.md` on origin/gizmoduck/machine-grant-design) and row 199's
@@ -7,6 +7,16 @@ DESIGN.md section 7a (e10e03f6b). Mos rulings used here: mos-1130 and mos-1136.
This draft builds nothing. It says how an Approve in Jason's inbox makes an agent's Gitea user, its token and
its forks with no manual step, and it answers mos-1130 item 3 (a to e) on the executor's admin credential.
## r5 changes (dewey-0761 on r4 3e66f5acf)
Section 7 is unchanged from fa5658941.
- Item 1 (blocks). A revoke on an open rotation deletes both tokens. When the last `grant.step` is a
rotation's `commit` with no settle after it (settle-pending or `grant-token-unknown`), section 6 step 3
deletes `previousTokenId` and the minted `tokenId`, each failure to `serviceLeft`. Section 5's
`grant-token-unknown` text now says both (X-G5).
- N8. Step 0 refuses `grant-token-unknown` while the agent is held, so a revoke is the only way out.
## r4 changes (dewey-0759 on r3 fa5658941, sage-1578, duckworth-0067)
Section 7 is unchanged from fa5658941, the text Duckworth approved and Gizmoduck cites.
@@ -336,7 +346,8 @@ nothing.
starts step 0 again, claim included. That holds only while the bus that refused is up: at the next start,
#436a r6 X3's reconcile spends every resolved-yes grant decision with no `action.allowed` as
`interrupted`, a step 0 refusal included, and the chief raises again (D5, sage-1578). `status` lists each
such open decision with `spentAtNextStart: true`. While a start or another `grants run` holds the claim it refuses
such open decision with `spentAtNextStart: true`. While the agent is held (`grant-token-unknown`, section
5), step 0 refuses `grant-token-unknown`, so a revoke is the only way out (N8). While a start or another `grants run` holds the claim it refuses
`grant-busy` before any call, as #436a X2's `host-busy` does. It refuses `decision-consumed` for a spent
decision. No other path starts it. The tag is #436a X2 step 2's, over business, agent and `"gitea"`.
1. **Recheck and consume**, in one transaction: `#consumeGrant` with `operation: 'grant.gitea'`, the
@@ -405,7 +416,7 @@ two-scope seat token; op `token-whoami`). The answer is an `id`, never a value.
'grant-token-unknown', grantDecision, tokenId}`, where `tokenId` is the id Gitea gave, and Jason gets a
message. `status` shows `tokenUnknown`. At each start, before `Credentials` loads, the reconcile passes such
`(agent, "gitea")` pairs as `held` (a `Credentials` option built in #436b's row, like `reload`), so memory
never loads that file. A revoke (section 6) clears it: it unlinks the file and deletes the recorded token.
never loads that file. A revoke (section 6) clears it: it unlinks the file and deletes both recorded tokens.
The unknown token is Jason's to look at.
- Anything else is `settle-pending`, and nothing is deleted: 403, 404, 5xx, a timeout, Gitea unreachable, or
C1 to C4 failing first. The last `grant.step` stays `commit`, the
@@ -455,7 +466,11 @@ dev only):
1. 199 `revoke`: memory, abort a use or the executor in flight, unlink the file.
2. One store transaction deletes the `gitea_grants` record and records `grant.revoked`.
3. After C1 to C4, `DELETE /users/{user}/tokens/{tokenId}` with the admin token. If Gitea can't be reached,
`grant.revoked {serviceLeft: <tokenId>}`, and `mosaic grants sweep gitea` clears it later.
`grant.revoked {serviceLeft: <tokenId>}`, and `mosaic grants sweep gitea` clears it later. When the last
`grant.step` is a rotation's `commit` with no settle after it (settle-pending, or `grant-token-unknown`),
two tokens are live and the record may name either (section 5), so the revoke deletes both
`previousTokenId` and the minted `tokenId`, each failure listed in `serviceLeft` (dewey-0761). The id
`grant-token-unknown` named is not one this stack minted; it stays for Jason.
That closes 199 D12's gap: the token is gone at Gitea without Jason's A5 step.
@@ -569,7 +584,7 @@ or any other live service. No token value is printed.
| X-G2 | `set` refuses a version below 1.27.0 (fake reports 1.26.4, 1.27.0-rc0, and 1.9.0 against 1.27.0 as a string trap), a non-admin, a login mismatch, an account that reaches a repository, a token with another scope or `all`, and a second `url`. It writes nothing on any refusal |
| X-G3 | on scratch 1.27.1, end to end: user made with the nonce and its `id` recorded, token minted with the two scopes, forks made, step 5 passes, `issue` writes the 0600 file, `grant.applied`, then revoke deletes the token at Gitea (gone from its list) |
| X-G4 | an existing user with no record, a recorded name whose `id` differs (deleted and remade, and a case variant of the name), a site-admin user and a disabled user each refuse before any token call |
| X-G5 | each failure row of section 5 leaves the stated state, rotation rows included. A failed delete leaves `serviceLeft` |
| X-G5 | each failure row of section 5 leaves the stated state, rotation rows included. A failed delete leaves `serviceLeft`. A revoke while a rotation's `commit` is settle-pending, and one after `grant-token-unknown`, deletes both `previousTokenId` and the minted `tokenId` at the fake, and a failure of either lands in `serviceLeft`. A new grant for a held agent refuses `grant-token-unknown` at step 0, before any Gitea call |
| X-G6 | a restart after `user` adopts a user whose `full_name` ends with ` [mosaic <nonce>]` (16 lowercase hex), whose lowercased login equals `gitea.user` lowercased and who isn't an admin; does nothing on 404; and reports `gitea-user-orphan` for a different `full_name`, a different login or an admin. A restart after `mint`, `minted` and `commit` of a first grant leaves no token at Gitea, no file and no record. A restart after `commit` of a rotation settles forward when the file holds the new token and back when it holds the previous one, ends `grant-lost` only on a 401, and stays `settle-pending` with nothing deleted on 403, 404, 500, a timeout and an unreachable fake. On 200 with an unknown `id` it drops the credential from memory, deletes nothing at Gitea, keeps the file, ends `grant-token-unknown`, and a second restart still doesn't load the file; a revoke then clears it |
| X-G7 | an identity, publish, upstream or admin slot change between raise and Approve refuses `decision-mismatch` before any Gitea call. An admin rotate voids open grant decisions and `status` lists them |
| X-G8 | every admin-token and seat-token call writes `grant.call` before it and `grant.result` after it, paired by `callId`. A crash between them is listed by reconcile. No record body holds a token |
@@ -593,7 +608,7 @@ tokens on a settle 403 or 5xx; compare step 5 against `target.projects` instead
of `/user/repos`; let `grants run` skip the claim; send the admin token in a `token` header on the mint route; revoke the
remote token before the local steps; skip the fork `parent` check; skip step 5's reach check; delete only the
recorded token on fire; let `grants run` accept an agent; keep the file in use on a settle 200 with an
unknown `id`; load a `held` pair at start; drop the `kind` filter from the restart reconcile.
unknown `id`; load a `held` pair at start; revoke only `tokenId` on an open rotation; let step 0 pass a held agent; drop the `kind` filter from the restart reconcile.
## 10. Shared with Bushroot's GitHub half (proposed in megavolt-0036, answer pending)