Credential management

The typed client separates team and deployment credential management. Final controller provisioning and native runner configuration remain integration requirements.

Save an existing controller credential without putting it in argv:

sparkwing profiles add --profile prod \
    --controller https://sparkwing.example.com --token-stdin

Removing a profile revokes a confirmed member credential unless --keep-token is explicit. Runner and deployment credentials require --keep-token because removing a saved connection must not revoke the machine that uses it.

Team credential creation accepts only --type member. Enroll runners through runners add, which writes a private credential file. Runner credentials remain visible to token inventory, rotation and revocation.

Team credential commands use the team token namespace:

sparkwing tokens list --profile prod
sparkwing tokens list --include-revoked --profile prod
sparkwing tokens list --token swk_example --profile prod
sparkwing tokens create --type member --principal alice --scope runs.read,logs.read --profile prod
sparkwing tokens rotate --token swk_example --grace 48h --profile prod
sparkwing tokens revoke --token swk_example --profile prod

Deployment credentials use a separate namespace and need its deployment grant:

sparkwing operator tokens list --profile prod
sparkwing operator tokens create --type service --principal launcher --scope claims.launch --profile prod

List JSON emits one metadata object per line. Plain lists emit public prefixes. Creation and rotation JSON contain the new bearer and metadata. Plain creation and rotation print the new bearer once. Treat either output as a secret. Revocation requires the server to confirm the selected prefix was revoked. Failures keep their structured error on stderr.

Omitting creation TTL selects the server default. Explicit --ttl 0 requests no expiry, subject to issuer policy. Rotation with zero or omitted TTL keeps the old remaining lifetime. Grace defaults to 24 hours. TTL and grace use nonnegative whole seconds. Creation and rotation return a bearer once and are never automatically replayed after a lost response.

The HTTP request carries grants and optional absolute expires_at. Credential metadata uses subject.kind and subject.id with RFC3339 timestamps. Creation returns the one-time bearer as capability. Rotation returns capability, new metadata, old_prefix and old_revoked_at. The CLI keeps its own display projection for list and creation output.

Runner enrollment uses the same typed team token manager. Its JSON result contains the runner name, profile, config path, credential metadata and service result. It never prints the bearer. Plain prints the runner name. Removal checks the enrolled controller, optional runner name and explicit credential kind before stopping its service and revoking the credential. Its enrollment-owned credential file must remain owner-only. A principal spelled agent:NAME does not establish runner kind.

The local runner file and service installer still need their final machine schema and native runtime integration. --reserved-for needs the team-admin provisioning request contract. It fails as policy_missing before enrollment.