auth: records carry an account identity; set-identity and import set it, replace keeps it

Merged
IC
2mo ago
+2070-79

Two oauth records imported from a consumer's auth store serve account_id: null, and a quota consumer that labels accounts per provider collapses identity-less accounts of one provider into a single unlabelled row. The read surface already serves a stored RecordIdentity (the login path captures it); this adds the write side for records that arrive by import, without touching secret material.

  • ck auth import --account-id <id> [--email] [--org-name] | --clear-identity on every import source.
  • ck auth set-identity <credential-id> --account-id … | --clear — identity-only mutation: decrypts, attaches the label, re-seals the SAME OAuthCredential (pinned field-for-field with non-empty scopes and client_id), keeps handles, state and stale_pending, bumps record_version once, audited. Exists because import --replace reads a source file and would overwrite a vault-custodied token family with stale material.
  • import --replace without identity flags PRESERVES the existing record's identity at the store layer (both the offline path and the connected admin op), so an unattended re-import from a rotating source keeps the label; --clear-identity drops it; a corrupt existing record is still repaired, with the incoming identity only.
  • RecordIdentity::validate runs at every record write sink (create, CAS overwrite, identity-policy overwrite, set-identity): non-empty, no control chars, ≤256 bytes; email-without-account_id is normalised away per the existing with_identity rule.
  • ck auth usable shows account=<id|none> per oauth row; runbook updated.

Gate: bash scripts/gate.sh passes (workspace floor 484, debug profile); core 316, cli_admin 31+1 ignored, clippy, fmt. Mutations red-checked: preservation removed → sticky test red; store-level validation removed → connected-route test red; scopes cleared / client_id dropped in set-identity → OAuth equality red; stale_pending cleared → invariant red.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds account identity metadata to imported OAuth records and a set-identity command. Token-only import --replace now preserves existing identity instead of dropping it, while explicit identity flags override or clear it; set-identity changes metadata without replacing token material.

  • ck auth import accepts --account-id, --email, --org-name, and --clear-identity; email and organization name require an account ID.
  • Identity conflicts are refused: replace won't preserve a label when the incoming account ID conflicts, and a supplied --account-id that contradicts the incoming token's own claim is refused on create.
  • set-identity re-seals the same OAuth credential, keeps handles, lifecycle state, and stale_pending, increments record_version, and writes an audit event.
  • Identity validation runs at every record write, normalizes legacy email-only identities, and limits fields to non-empty values without control characters or over 256 bytes.
  • A separate admin operation makes older daemons refuse identity-aware replacement instead of silently dropping the label; the legacy admin.store wire shape stays byte-frozen.
  • ck auth usable displays account=<id|none> and renders invalid stored identity metadata as <invalid>.