feat(bus): copy credential issue CB2, Gitea verified live (#1873) #1944

Merged
jason.woltje merged 2 commits from org-cb2 into refactor 2026-11-11 23:50:01 +00:00
Owner

Copy credential binding, slice CB2 (#1873): credentials.issue for an agent copy's bound Gitea service, verified live against Gitea before anything binds. Design: docs/plans/2026-11-11_copy-credential-binding-design.md (PR #1923, approved 11-11).

  • Gitea only. Vikunja refuses credential-scope-unverified; any other service refuses credential-unknown.
  • Pre-checks that write nothing: owner, service, rotation date (after today, at most 366 days), token format, a token no role or definition already holds, the record against the boot snapshot, read:user in the approved scopes, one https Gitea origin for the bound projects, the copy's bot login.
  • Order: stage (record inactive, token to the copy's slot), read back by dev/ino and content, live verification, session recheck, commit through the writer's fence, which re-reads the slot by identity. Any refusal after the stage unlinks the slot by identity and leaves the record pending.
  • Verification is GET-only with the token: version floor; token owner must be the dedicated bot copyBotLogin(orgIncarnation, copyId, service); semantic scope comparison (read narrower than write per category, categories independent; all, admin and unknown categories refuse); expiry before the rotation date refuses; the user is not admin; every reachable repository is a bound one, admin permission refuses, push on a read binding refuses, every bound repository is visible.
  • Bus start sweeps copy slots no binding grants and staged temp files, so a crash after the stage leaves no token on disk. A failed slot write removes only the temp file that call created.
  • No secret, path, request or token-derived digest in metadata, receipts or logs. Copies still cannot launch with a binding (CB3).

Tests: copy-grant (21), live Gitea 1.27.1 in Docker (copy-grant-docker, ran, not skipped), copy-grant-writer, credential-verbs and the store tests; each new test red without its change, plus 14 fix-round mutants all red. Lane gate 8087/2; the 2 are conversation tests needing python jsonschema, red on BASE too. Lead check: 106 files on 75f6d5533 (changed tests, organization suite, proxy-setup, every reader of copy-bindings, copy-grant, credentials, refusals and the Gitea grant, both live Gitea Docker tests), 1287/0.

Review: security (daybreak-blue) 1 P2, fixed (start sweep and failed-stage cleanup), re-check approve. Build (Opus) 5 P3, four fixed, one recorded (expiry is proven against the fake only; the 1.27.1 CLI cannot mint an expiring token).

Deferred: CB3 launch admission; Vikunja verification; a bounded mint path; live expiry acceptance; a fault seam for an fsync failure after rename; the sweep logs a slot and its temp file as two entries.

Copy credential binding, slice CB2 (#1873): `credentials.issue` for an agent copy's bound Gitea service, verified live against Gitea before anything binds. Design: docs/plans/2026-11-11_copy-credential-binding-design.md (PR #1923, approved 11-11). - Gitea only. Vikunja refuses `credential-scope-unverified`; any other service refuses `credential-unknown`. - Pre-checks that write nothing: owner, service, rotation date (after today, at most 366 days), token format, a token no role or definition already holds, the record against the boot snapshot, `read:user` in the approved scopes, one https Gitea origin for the bound projects, the copy's bot login. - Order: stage (record inactive, token to the copy's slot), read back by dev/ino and content, live verification, session recheck, commit through the writer's fence, which re-reads the slot by identity. Any refusal after the stage unlinks the slot by identity and leaves the record pending. - Verification is GET-only with the token: version floor; token owner must be the dedicated bot `copyBotLogin(orgIncarnation, copyId, service)`; semantic scope comparison (read narrower than write per category, categories independent; `all`, admin and unknown categories refuse); expiry before the rotation date refuses; the user is not admin; every reachable repository is a bound one, admin permission refuses, push on a read binding refuses, every bound repository is visible. - Bus start sweeps copy slots no binding grants and staged temp files, so a crash after the stage leaves no token on disk. A failed slot write removes only the temp file that call created. - No secret, path, request or token-derived digest in metadata, receipts or logs. Copies still cannot launch with a binding (CB3). Tests: copy-grant (21), live Gitea 1.27.1 in Docker (copy-grant-docker, ran, not skipped), copy-grant-writer, credential-verbs and the store tests; each new test red without its change, plus 14 fix-round mutants all red. Lane gate 8087/2; the 2 are conversation tests needing python `jsonschema`, red on BASE too. Lead check: 106 files on 75f6d5533 (changed tests, organization suite, proxy-setup, every reader of copy-bindings, copy-grant, credentials, refusals and the Gitea grant, both live Gitea Docker tests), 1287/0. Review: security (daybreak-blue) 1 P2, fixed (start sweep and failed-stage cleanup), re-check approve. Build (Opus) 5 P3, four fixed, one recorded (expiry is proven against the fake only; the 1.27.1 CLI cannot mint an expiring token). Deferred: CB3 launch admission; Vikunja verification; a bounded mint path; live expiry acceptance; a fault seam for an fsync failure after rename; the sweep logs a slot and its temp file as two entries.
jason.woltje added 2 commits 2026-11-11 23:49:58 +00:00
An owner-issued Gitea token for an agent copy is staged inactive in the
self-only store, proved live against the copy's Gitea (token scopes by
semantic comparison with all and admin refused, the copy's dedicated
per-org/copy/service account, every reachable repository, expiry), then
committed under the writer's generation, revision, decision, copyId,
incarnation and definition-pin recheck. The grant record holds only an
opaque grant id and rotate date. Vikunja refuses
credential-scope-unverified. Copies still cannot launch (CB3).
Security review P2: a hard crash after the stage left the staged token
on disk. sweepCopySlots runs at start, before Credentials loads, and
removes each copy service slot whose binding holds no grant, plus any
staged temp file. It never touches a role's slots, a slot a loaded
reference names, or an organization whose document cannot be read. A
stage write that fails removes its temp file, and its slot when the
rename has already happened.

Build review P3s: push is held to the binding's approved level, not the
token's. Tests now cover the boot-copy pre-checks, the format check
running before the stage, and stageCopy's reference refusal.
jason.woltje merged commit 6d0e9f89be into refactor 2026-11-11 23:50:01 +00:00
Sign in to join this conversation.