git wrappers: --login resolves a PRINCIPAL not a store — slot-store fallback + authenticated-principal verification (#1280 follow-up) #1303

Open
opened 2026-08-18 03:47:04 +00:00 by fargo · 0 comments
Collaborator

Follow-up to #1280/#1291 — --login resolves a PRINCIPAL, not a store; add slot-store fallback + authenticated-principal verification

The gap that remains after #1291

#1291 (unmerged) makes all five write wrappers identity-first and adds --login where missing, but --login still resolves ONLY against tea's config store (get_gitea_token_for_login~/.config/tea/config.yml). The fleet's per-seat credentials live in the slot store (~/.config/mosaic/secrets/gitea-tokens/gitea-{host}-<identity>.token). Two stores, one reachable by explicit operator intent.

Measured live (fred, 2026-08-17/18, deployed copy on sb-it-1-dt): the only tea login for mosaicstack on this workstation is mosaicstack-mos-dt-0 — a seat retired 2026-08-11. An operator who says --login fargo gets a fail-loud refusal while gitea-mosaicstack-fargo.token sits in the fleet store. The explicit-intent path is weaker than the ambient-env path (#1291's MOSAIC_GIT_IDENTITY reads the slot store). That is backwards.

Also measured by fred on the deployed copy (half is deployed-drift — #1291's repo copy fixes these once merged and deployed; recorded here so the follow-up verifies post-deploy): issue-create.sh has no --login (first-match resolver returns the retired seat); pr-review.sh comment action has no override; issue-comment.sh is the reference implementation.

The contract (ruled by fargo per fred's delegation — ruling posted on #1291)

--login <name> resolution becomes, in order:

  1. tea store, host-bound, as today. Tea login names remain valid; a tea login is a credential whose principal MAY differ from its login name (e.g. fred-ms → user fred) — tea's own mapping stands for this arm.
  2. slot store fallback: gitea-{usc,mosaicstack}-<name>.token. A name with no tea login but a slot resolves.
  3. Else fail loud, naming BOTH stores checked (so the operator learns there are two, not that one is empty).

Plus the closing half that makes store ambiguity moot: verify the authenticated principal after resolving the token (GET /user):

  • Slot-store arm REQUIRES authenticated login == requested name. Slot names are principal names by construction; a mismatch is a mis-provisioned token, and using it would be the silent-wrong-principal defect this family exists to kill.
  • Tea arm keeps tea semantics but the wrapper's success line REPORTS the authenticated principal, so the operator always sees who actually acted.

Under this rule a name collision between the stores cannot mis-attribute: whichever store's token authenticates as the requested principal is correct by definition, and a wrong token fails the check instead of passing green.

Tests

  • --login with a tea login only → resolves, principal reported (tea arm).
  • --login with a slot only (no tea entry) → resolves from slot, authenticated-login match asserted.
  • --login with a slot whose token authenticates as a DIFFERENT user → nonzero, names the mismatch.
  • --login unknown to both stores → nonzero, names both store paths.
  • Host binding preserved both arms (cross-host slot/login still refuses).
  • Sabotage control per fleet standard (remove the verification half → its tests redden; remove the fallback → its tests redden; restore byte-identical).

Scope fence

resolve_gitea_principal + the gitea_resolve_api_for_login call sites + get_gitea_token_for_login slot fallback, with tests. NOT: tea login provisioning (operator lane), the global mosaic.gitIdentity (fenced), the deployed-copy drift (fixes itself when #1291 merges + deploys; verify post-deploy).

Related, named in the ruling

The general shape: wrapper behavior depends on cwd/host state with the requirement unstated and failures naming symptoms (instances: #1293 pre-fix wrapper from a next worktree; fred's issue-comment aimed at the wrong repo by cwd, surfacing as HTTP 500). Fix direction — wrappers STATE their resolution (acting principal + repo) before writing — is a separate card if wanted, not this one.

## Follow-up to #1280/#1291 — `--login` resolves a PRINCIPAL, not a store; add slot-store fallback + authenticated-principal verification ### The gap that remains after #1291 #1291 (unmerged) makes all five write wrappers identity-first and adds `--login` where missing, but `--login` still resolves ONLY against tea's config store (`get_gitea_token_for_login` → `~/.config/tea/config.yml`). The fleet's per-seat credentials live in the slot store (`~/.config/mosaic/secrets/gitea-tokens/gitea-{host}-<identity>.token`). Two stores, one reachable by explicit operator intent. Measured live (fred, 2026-08-17/18, deployed copy on sb-it-1-dt): the only tea login for mosaicstack on this workstation is `mosaicstack-mos-dt-0` — a seat retired 2026-08-11. An operator who says `--login fargo` gets a fail-loud refusal while `gitea-mosaicstack-fargo.token` sits in the fleet store. The explicit-intent path is weaker than the ambient-env path (#1291's `MOSAIC_GIT_IDENTITY` reads the slot store). That is backwards. Also measured by fred on the deployed copy (half is deployed-drift — #1291's repo copy fixes these once merged and deployed; recorded here so the follow-up verifies post-deploy): `issue-create.sh` has no `--login` (first-match resolver returns the retired seat); `pr-review.sh` comment action has no override; `issue-comment.sh` is the reference implementation. ### The contract (ruled by fargo per fred's delegation — ruling posted on #1291) `--login <name>` resolution becomes, in order: 1. **tea store**, host-bound, as today. Tea login names remain valid; a tea login is a credential whose principal MAY differ from its login name (e.g. `fred-ms` → user `fred`) — tea's own mapping stands for this arm. 2. **slot store fallback**: `gitea-{usc,mosaicstack}-<name>.token`. A name with no tea login but a slot resolves. 3. Else **fail loud**, naming BOTH stores checked (so the operator learns there are two, not that one is empty). Plus the closing half that makes store ambiguity moot: **verify the authenticated principal after resolving the token** (`GET /user`): - Slot-store arm REQUIRES authenticated login == requested name. Slot names are principal names by construction; a mismatch is a mis-provisioned token, and using it would be the silent-wrong-principal defect this family exists to kill. - Tea arm keeps tea semantics but the wrapper's success line REPORTS the authenticated principal, so the operator always sees who actually acted. Under this rule a name collision between the stores cannot mis-attribute: whichever store's token authenticates as the requested principal is correct by definition, and a wrong token fails the check instead of passing green. ### Tests - `--login` with a tea login only → resolves, principal reported (tea arm). - `--login` with a slot only (no tea entry) → resolves from slot, authenticated-login match asserted. - `--login` with a slot whose token authenticates as a DIFFERENT user → nonzero, names the mismatch. - `--login` unknown to both stores → nonzero, names both store paths. - Host binding preserved both arms (cross-host slot/login still refuses). - Sabotage control per fleet standard (remove the verification half → its tests redden; remove the fallback → its tests redden; restore byte-identical). ### Scope fence `resolve_gitea_principal` + the `gitea_resolve_api_for_login` call sites + `get_gitea_token_for_login` slot fallback, with tests. NOT: tea login provisioning (operator lane), the global `mosaic.gitIdentity` (fenced), the deployed-copy drift (fixes itself when #1291 merges + deploys; verify post-deploy). ### Related, named in the ruling The general shape: wrapper behavior depends on cwd/host state with the requirement unstated and failures naming symptoms (instances: #1293 pre-fix wrapper from a next worktree; fred's issue-comment aimed at the wrong repo by cwd, surfacing as HTTP 500). Fix direction — wrappers STATE their resolution (acting principal + repo) before writing — is a separate card if wanted, not this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1303