9.3 KiB
Brief: a credential store per agent, own git identity, pushes to a remote
Sage, 2026-10-20, for mos-0974 (MVP T1 item 6) and the MVP plan
(jason-master-restart/projects/mosaic-mvp/PLAN.md item 6, GAP-MAP G11 and G13; TARGET.md: "credentials store
available for all agents; secrets protected with RLS equivalent" and "Agent uses own credentials for git and
other actions"). Gitea #1697. Owner Megavolt (Opus 5.5, a new builder seat), reviewer Duckworth, gate owner
Sage. Base: refactor at the brief commit. Design first. The first deliverable is
agents/megavolt/work/queue-ROW/DESIGN.md.
This row draws a security boundary. Sage reviews the design's authority model, and Mos rules on it before any code is written.
What exists today (GAP-MAP, measured at 9ae8c73ac)
- G11. Credential references exist per instance for Gitea and Vikunja only
(
packages/business/src/credentials.mjs,packages/bus/src/credentials.mjs,vocabulary.mjs:103). One Claude env file is shared by every seat. - G13. Agents use their own Vikunja token (
packages/tasks/src/adapter.mjs). Git goes through the host socket (packages/cli/src/git-socket.mjs). Gitea tokens are loaded but never used. - The development repository's helper (
scripts/git-credential-mosaic.mjs, wired in.git/configfor git.mosaicstack.dev) pushes as one identity. Until this row lands, seats push only their own<seat>/*branches through it (mos-0981).
What the design must settle
- The store. Where each agent's credentials live, per instance, under the row 190 config canon. File modes, ownership and the read path. Which process may read which entry, and how the bus enforces that (the "RLS equivalent"). A process asking for another agent's entry is refused and logged.
- Kinds. Gitea (git over HTTPS, and the API), Vikunja, and the model provider. Say which kinds 10-27 needs
and which wait. The model provider entry must not copy jwoltje's
claude-session.env. If it needs a new provider account or subscription, flag it for Jason and don't pick one. - Git identity and push. Each agent commits with its own name and email and pushes with its own token to a remote it is granted. Which refs an agent may push (its own branches, never the default branch or another agent's), and where that's enforced: the Gitea side (branch protection, token scopes) and our side. How this fits the host git socket, or replaces it.
- Lifecycle. Issue, rotate (
rotateBy,expires), revoke and delete, and what row 194 (agent lifecycle) calls when an agent is hired or fired. A fired agent's credentials stop working at once. - Who does what. Creating a Gitea user or token, or any account outside the fleet, is Jason's (Q173 exclusions). The design lists each such step as a command or UI action for Jason. Agents never mint accounts.
- Migration. Today's seats (jarvis helper, seat tokens) keep working until each moves. The mos-0981 push rule ends only when Mos says so.
- What this row closes in G11 and G13, and what stays open.
Boundaries
- No credential value is read, printed, logged or committed. Tests use fake tokens in scratch roots and a fake Gitea.
- No change to
~/.mosaic-dev,~/.config/mosaic-dev, the live units, slot files,~/.mosaicor the live.git/configof the canonical checkout. - No request to git.mosaicstack.dev, tasks.woltje.com or any provider from a test.
Gate
Duckworth approves DESIGN.md, Mos rules on the authority model, then Duckworth approves the manifest. The bus,
business, cli and tasks suites and every scripts/test-*.sh are green on Sage's gate, and the design's
mutants are killed.
Brief: #436b Gitea grant path, build (schema 3j-3k)
Sage, 2026-10-20, for Vikunja P19 #436 (#436b), mos-1124 (Q190: each seat gets its own Gitea account through this row) and mos-1145 (Gitea moves to 10-27, at risk). Owner Megavolt, reviewer Dewey, gate owner Sage. Gitea #1742.
The design is agents/megavolt/work/436b/DESIGN.md r5 at 1be535aca on megavolt/row199, sha256
68df02cdf890564c6ed0a5426204114389ba7eb0eea1cf04f1bf5938b6de45f4, which Dewey approved in dewey-0763. Section 7
was approved by Duckworth at fa5658941 (duckworth-0067) and is unchanged since. It is not reopened here. The
build also takes agents/megavolt/work/436b/BUILD-CARRY.md at 3330c901b (Dewey's N9 and N10), and its BUILD.md
cites that file.
Scope: the Gitea half only. The binding (section 2), the executor and its admin credential (sections 3 and 4), failure and restart (section 5, with sage-1578's option (b): a step 0 refusal stays unspent only until the next start), revoke and sweep (section 6), the store records (section 7) and section 9's tests and mutants. The GitHub half (section 10, Bushroot) is not in this row.
Order and schema:
- It builds on row 210's landing (the design: code waits on 210) and on machine grant row a (row 234), whose
section 7 rule and
system/grantwriter it shares. - This row owns
SCHEMA_VERSION3k: its two tables go in3j-3k.sqlon row 234's landing. Row 206 (193b2) moves to 3k-3l and rebases onto this row's landing (Sage, 2026-10-20: row 205 is still queued behind the federation rows, so this row lands first).
Boundaries:
- Live use waits for Jason. He approves section 8's notes (the admin token can take over any account on that
Gitea) and does the one bootstrap step himself (3e): he creates
mosaic-grant, its TOTP and its token. No agent creates a Gitea account or token (Q188). - No test reaches git.mosaicstack.dev or any provider. Tests use a fake Gitea and fake tokens in scratch roots.
- Seats keep pushing as jason.woltje until this row is live and Jason grants their accounts (mos-1124).
Gate: Dewey approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on
Sage's gate, and section 9's mutants are killed, N9's restart test included.
Brief: row 199 follow-up N1, one decision publishes once
Sage, 2026-10-20, from Duckworth's row 199 round 1 note N1 (duckworth-0068, megavolt-0058). Owner Megavolt, reviewer Duckworth, gate owner Sage. Builds on row 199's landing. Gitea #1743.
One decision can push its commit to two fork refs, because checkPublish doesn't refuse while another ticket
holds the decision. Pick one fix and say why in BUILD.md: refuse publish-busy in checkPublish, or recheck
#spent in publishTarget. Add a test where two tickets race on one decision and exactly one push happens,
plus a mutant that removes the check.
Gate: Duckworth approves the manifest. The bus suite and every scripts/test-*.sh are green on Sage's gate,
and the mutant is killed.
Brief: row 237 follow-up, one decision lands once, with tests at the broker
Sage, 2026-10-21, from Megavolt's checkLand analysis (megavolt-0068, megavolt-0069) and sage-1651. Owner
Megavolt, reviewer Duckworth, gate owner Sage. Builds on row 237's landing d7d943199. Vikunja P19 441.
Landing already has three guards against one decision landing twice. land-busy at
packages/cli/src/git-socket.mjs:351 allows one landing per socket at a time. finishLand moves main by
compare-and-swap (packages/seat/src/working.mjs:391, refusing base-moved). recordLand reruns
#landFacts, which refuses decision-consumed (packages/bus/src/broker.mjs:945). So no end-to-end race
exists, and no code change is planned. What's missing is a test that pins each guard.
- In
packages/bus/tests/land.test.mjs, add a test where two tickets passcheckLandon one decision. The firstrecordLandsucceeds and the second refusesdecision-consumed, with nothing recorded for it. - Run three mutants and list them in BUILD.md: drop the
#spentline in#landFacts, drop the old value from the CASupdate-refinfinishLand, and drop theland-busythrow at git-socket.mjs:351. Each must be killed by a named test. If the CAS mutant survivespackages/seat/tests/working.test.mjs:555, add a test to working.test.mjs that kills it. Otherwise leave that file alone. - If a mutant survives with no test that can reach it, stop and send me the analysis. Don't change source to make a test reach it.
Gate: Duckworth approves the manifest. The bus, seat and cli suites and every scripts/test-*.sh are green
on Sage's gate, and the three mutants are killed.
Brief: row 237 follow-up N1, a retry after publish-timeout pushes once
Sage, 2026-10-21, from Duckworth's row 237 note N1 (#1743 comment 29810, duckworth-0069) and Megavolt's
row 237 BUILD.md residual 2. Owner Megavolt, reviewer Duckworth, gate owner Sage. Builds on row 237's landing
d7d943199. Vikunja P19 440.
The git socket gives up with publish-timeout (packages/cli/src/git-socket.mjs:433) while the bus
process's push may still be running (publishPush in packages/bus/src/runtime.mjs, entry running).
publishRefused then drops the ticket, so row 237's publish-busy check no longer holds the decision. A
retry to another branch can then push a second ref to the fork.
Pick one fix and say why in BUILD.md. Either keep the decision held until the bus's push ends, or kill the
push on timeout and wait for it to exit before the ticket goes. Add a test that starts a slow push, times
it out, retries to another branch, and finds exactly one ref on the fork. Add a mutant that removes the
fix. Don't change the publish-timeout refusal text.
Gate: Duckworth approves the manifest. The bus and cli suites and every scripts/test-*.sh are green on
Sage's gate, and the mutant is killed.