Files

12 KiB

Row 236 build notes: #436b Gitea grant path (#1742, schema 3j-3k)

Builder Megavolt, reviewer Dewey, gate owner Sage. Draft; the results sections are filled in after the runs on Sage's gate.

Sources

  • Brief: docs/plans/2026-10-20_agent-credentials.md, "Brief: #436b Gitea grant path, build (schema 3j-3k)", blob 9259ba2a1.
  • Design: agents/megavolt/work/436b/DESIGN.md r5 at 1be535aca (sha256 68df02cd…f4), unedited. Scope: sections 2 to 7, and section 9's X-G1 to X-G15 and mutants. Section 10 (GitHub half) is out of scope.
  • Build carry: agents/megavolt/work/436b/BUILD-CARRY.md at 3330c901b (N9, N10).
  • Schema label 3j-3k per sage-2237. If 264a or 206 lands first, this rebases onto a new branch name and reruns the whole bus suite, the schema.test counts and the T46 lists.
  • Rebases: onto 86f95bb27 after row 273 (sage-2403, no conflicts), then onto 0fa7bf421 after row 191 (sage-2452, sage-2457). The 191 rebase took three unions in commit 1, 191's processing first and then the grant options: the Broker constructor (broker.mjs:317), the runtime's Broker options (runtime.mjs:173) and the cli case line in scripts/mosaic (191's halt|resume|processing| plus grants).

What changed

  • packages/bus/src/gitea-grant.mjs (new): the grant body check, the admin slot (set, rotate, remove), the admin and seat API calls, the start-time scan (giteaStart, openGiteaGrants, tokenUnknown).
  • packages/bus/src/broker.mjs: the gitea executor (steps 0 to 8), settle, restart reconcile, revoke and fire, sweep, status, and the grants.* verbs.
  • packages/bus/src/credentials.mjs: held pairs at start; the register and claim paths the executor uses.
  • packages/bus/src/migrations/3j-3k.sql, schema.sql, store.mjs: gitea_users and gitea_grants, new tables only.
  • packages/bus/src/runtime.mjs, server.mjs, human-cli.mjs: wiring of the start scan and the verbs.
  • packages/cli/src/grants.mjs (new), cli.mjs, scripts/mosaic: mosaic grants.
  • Tests: gitea-grant.test.mjs, gitea-migration.test.mjs, gitea-grant-docker.test.mjs (X-G3), fake-gitea-api.mjs, fixtures/schema-3j.sql (02da6a2ae's schema.sql), cli/tests/grants.test.mjs, and the pin updates in the migration tests, schema.test.mjs, channel-proof.test.mjs and stack-release.test.mjs (T46).
  • READMEs of bus and cli.

Choices and assumptions

Each is a reading of the design where it left room. Dewey may rule otherwise.

  1. Admin rotation is due once today is past rotateBy (the date itself is still good).
  2. A seat-token rotation moves the record at step 8, after the old token's delete (step 7).
  3. A step 7 failure leaves the grant applied, with the old id in serviceLeft and the notice "The previous token X is still live".
  4. A failure after the commit step's rename returns settle-pending; the next start settles it.
  5. tester-cli keeps its 5 s timeout; the grant verbs answer at once and the executor runs after.
  6. A revoke of a token-unknown agent takes both ids from that decision's last commit step.
  7. A user adopted at restart is upserted in the same transaction as its grant.failed interrupted.
  8. #giteaUnlink (first-grant commit undo) uses a temporary reference dated 9999-12-31, so it is never due while it lives.
  9. X-G12's org-team case: the fake models team membership as collaborator reach on the repository, which is what /user/repos shows (F11, F12).
  10. X-G15's look-alikes are stored under action.refused and under a non-grant kind, since the 3j trigger refuses the human's NULL pair on action.allowed. The trigger cases of X-G13 (session, NULL pair, grant.host.enrolled) are in grant-migration.test.mjs.
  11. Step 6 faults: a pre-rename failure is injected by making gitea.tmp a directory; a step 8 store fault by a TEMP trigger on gitea_grants.
  12. The credentials-verb revoke close is tracked, so giteaIdle() waits for it.
  13. openGiteaGrants gives agent: null for a spend with no step yet, so status.running can show a null agent; the CLI prints it empty.
  14. N9: a restart ignores a settle-pending commit with a grant.revoked after it and ends it revoked with no message (notify false). A 404 on a delete by id counts as done.
  15. N10: serviceLeft is a list in every body and answer.
  16. A restart claims the agent's key for each open grant it undoes. One executor runs per agent (grant-busy), so a killed child leaves at most one open grant per agent. If a store ever held two, the second's claim fails and the undo runs with no admin: its token is left and named in serviceLeft, never deleted unclaimed.
  17. X-G7 changes the binding with a fixture decision whose hash differs (row 234's G9 pattern), since the broker keeps its own copy of the business. The admin url check takes row 199's parseGiteaUrl, which accepts the origin with or without a trailing slash.
  18. r2, Dewey B1 (dewey-0781): step 8 and both settle branches (forward, back) check for a grant.revoked of the agent after the decision's raise, in the same transaction as their write. A revoke that came while step 7's or the settle's delete awaited took the record and both tokens, so the grant ends revoked with the human's notice, as a revoke of a running executor ends it (X-G11), and no record is written back. N9's restart case stays silent: a restart never settles a revoked rotation.
  19. r2, Dewey B2: a sweep retry of tokens or login checks the user's id first, as the revoke close does, against the gitea_users row (it survives a fire). A 404 skips with gitea-user-gone, a 200 with another id with gitea-user-foreign, any other status leaves the entry. Skips go in grant.swept and the sweep answer as skipped: [{agent, user, id, reason}]; a skipped entry is not left, so the next sweep does not retry it.
  20. r3, Dewey N5 (dewey-0783): the settle's 401 branch and its grant-token-unknown branch take the same revoke check. A revoke during the whoami ends the grant revoked, not grant-lost, and the token-unknown branch neither holds the agent again nor writes grant-token-unknown, so no start names a file the revoke unlinked.
  21. sage-2453, ruling (a) on megavolt-0123: 191's #processingGate refuses every agent verb off its read list while processing is halted. grants.status is a read like hosts.status, and the kill-switch plan (docs/plans/2026-10-20_kill-switch.md:16) says "Reads and the human's own verbs, including the clear, still work", so it joins the list. This touches row 191's list (one entry). An agent's grants.revoke stays refused while halted, as hosts.revoke does.

Named for DEFERRED (r2, Dewey N1-N4)

None is a small local change, so none is in r2. sage-2388: all four DEFERRED; N3 and N4 get a bus README sentence each in r3 (a left admin token is deleted by hand at Gitea; a running executor keeps the admin api it started with).

  • N1: an admin rotate cut between the mint and the slot write leaves a token at Gitea that no event names. A grant.admin rotate-begin {oldName} before the mint would close it; that is a new event shape.
  • N2: adminTokenLeft is unknown when /api/v1/token gives no name. Which 1.27 F-fact shows the name needs a check against the container, not a code change.
  • N3: admin set or remove records adminTokenLeft: null, so a left token drops out of status. Carrying it forward lets rotate --finish delete by name under a slot of another account or url; refusing remove while one is left blocks the human's remove when Gitea is down. Either needs a ruling.
  • N4: rotate or remove while a gitea claim is held. Refusing it blocks the human's remove during a run; the running executor keeps the api it began with. Needs a ruling or a line in the README.

Q188

No test creates a grant except through fixtures or the human's yes on a scratch store. No test reaches git.mosaicstack.dev, tasks.woltje.com or any provider. Unit tests use the fake Gitea on 127.0.0.1; X-G3 uses a throwaway gitea/gitea:1.27.1 container from the local image (never pulled), removed after. Every token is a fake or a scratch-container token; values are compared with assert.ok, never printed.

Runs

Sage's CLEAR (sage-2554), 10-22 09:00-09:28Z, one slot script that stops at the first red. Logs are in scratch/megavolt-r236/logs/ (slot-2c.txt, slot-gate.txt, slot-mutants.txt, slot-all.txt).

  • Section 2c files whole, in the seat worktree, TMPDIR under /mnt/storage/scratch/tmp:
    • bus (gitea-grant, the migration tests, schema, channel-proof, chief-of-staff, grant-host, project-runtime): 131 pass, 0 fail, 0 skipped;
    • cli (grants, stack-release): 224 pass;
    • route (grants, access): 28 pass;
    • docker (gitea-grant-docker): 1 pass.
  • The whole gate, in a detached worktree at bd5f11ded:
    • packages/bus/tests/*.test.mjs 765 pass, packages/business/tests/ 228 pass, packages/cli/tests/*.test.mjs 832 pass, each 0 fail and 0 skipped;
    • every scripts/test-*.sh rc=0: auth, conductor, config, discord, extension-package, foundation, land-work, queue (156 pass), release, task.
    • Named skips: test-queue line 34 (queue verify and render --check, because the gate worktree isn't the queue's canonical root), and test-task lines 87 and 93 (live provider cases, row 244: no provider call in the gate).
  • The gate worktree was clean after the run. GATE_PASSED at 09:23:11Z.

Mutants

(section 9's list, each with the test that kills it; filled in after the runs)

Result, at bd5f11ded after the gate (r236-mutants.sh): the base (gitea-grant, gitea-migration, grant-migration, schema; cli grants) was green, then all 40 killed and none survived: M1-M37, N9a, N9b and M38. Each mutant's restore left the worktree clean. SLOT_GREEN at 09:28:50Z.

A static pass over the 39 mutants (all apply, problems=0) found five with no test to kill them. Four tests were added before any mutant run:

  • M8, M9 (user or mint step written after its call) and M16 (grant.result written before its call): "X-G6 and X-G8: the user and mint steps are written before their calls, and each result after its call". It reads the store from the fake while user-create and token-create are at Gitea, and checks every result's status.
  • M29 (fork parent not checked): "X-G4: a fork by the seat user of another repository than the project refuses fork-foreign".
  • M28 (memory and file revoked after the Gitea delete): "X-G11: a revoke unloads memory and removes the file before its Gitea delete", which holds the DELETE open and reads the file and state.
  • M37 (evidence without its kind filter): "X-G15: a look-alike spend under another kind with the grant writer pair is not an open grant".

r2 tests, each red on 8bf442be9 by reading:

  • B1: "X-G11: a revoke during a rotation step 7 ends it revoked, and step 8 writes no record back" and "X-G11: a revoke while a sweep settles a rotation, forward or back, ends it revoked with no record". On 8bf442be9 step 8 and the settle write the record and grant.applied (forward) or grant.failed interrupted settled:back after the revoke deleted it.
  • B2: "X-G14: a sweep retries a fire only for the user it fired: a remade or gone user is skipped and named". On 8bf442be9 the retry fires the remade user (token list, delete, PATCH) and has no skipped.
  • N5 (r3, sage-2388): "X-G11: a revoke during a settle whoami that finds a foreign token ends it revoked, not held" and "X-G11: a revoke during a settle whoami that gets a 401 ends it revoked, not lost". On 77dd43c9e they end grant-token-unknown (the agent back in tokenUnknown) and grant-lost.
  • Choice 21 (sage-2453): "kill switch (plan :16): while halted the chief still reads grants.status, and an agent grants.revoke refuses". M38 drops 'grants.status' from the read list; the chief's status then refuses processing-halted and the test fails.