Agent docs

The identity recipe

The owner asked for "an auth blueprint + recipe" because every app has a different auth model and…

The owner asked for "an auth blueprint + recipe" because every app has a different auth model and modules will sit on several clouds at once. This is the recipe: what to do on the module side, what to do on the app side, in what order, for a new module and for an existing service. canon/02 is the why; adr/002 is the decision; this is the checklist.

Audience: anyone adding identity to a module or wiring an app to call one.

Vocabulary, once#

  • caller: the thing calling (an app per environment, a service, a tool, an agent); verified
  • tenant: the org or workspace the work is for; on every request
  • scopes: what the caller may do at this module
  • attribution: who the app says it acts for; claimed, stored, searchable
  • verifier: the module's port that turns a bearer credential into a caller context; jwks, keys, or local

Users never reach a module.

For a new module#

  1. Add the verifier port with all four implementations from the template (jwks, keys, remote, local). Config selects one; local refuses to start on a public URL.
  2. Require Authorization: Bearer and X-Tenant-Id on every job route; /health, /build-info, /manifest stay open; /health/deep, stats need stats:read.
  3. Check tenant against the caller context's tenants, scope against the route, env claim against the module's own env. Reject with 401 (no or bad credential), 403 (credential fine, not allowed), never 404 for an auth failure.
  4. Stamp caller_id, env, tenant, attribution on the job; everything downstream inherits.
  5. Internal routes (queue callbacks) verify platform identity only: Cloud Tasks OIDC with the module URL as audience and the queue's service account as subject; arq trusts its Redis network. Never a static shared secret on a public URL.
  6. Register the module as an audience at the issuer (or, before the issuer, create its keys table) per environment. Register each expected caller with its tenants and scopes.
  7. Conformance rows: missing tenant rejected; wrong-env credential rejected; caller credential on /internal/* rejected; local verifier refuses on a public URL.

For an existing service (the services-api shape)#

  1. Keep the current credential working: the shared password becomes one row in a keys table with tenants: "*" and full scopes. Nothing breaks.
  2. Add X-Tenant-Id with a logging-only grace period, then require it.
  3. Issue a per-caller key to each known caller (each app per env, each tool), each with its real tenants and scopes; hand the shared password row a short expiry and watch its use drop to zero; delete it.
  4. When the issuer exists, switch the verifier to jwks; keep keys for any caller that has not moved; delete rows as callers move.
  5. Separate the environments' identities if they are shared (dev running as prod's service account is the known case).

For an app that calls modules#

  1. Get one client credential per environment at the issuer (or one key per module before it), scoped to the tenants the app serves and the modules it calls. Store it in the app's secret store, never in the frontend.
  2. Exchange it server-side for a short-lived token; cache until near expiry; never forward a user's session, cookie, or JWT to a module.
  3. Put the tenant on every request from the app's own verified org or workspace context (walmart's X-Org-Id after membership check; speedway's requireWorkspaceAccess); put the user and the workflow run in attribution.
  4. Translate the app's roles into scopes at call time (a member may submit and read; only an admin may cancel, if that is the app's policy). Roles stay in the app.
  5. Callback receiver: verify the HMAC signature with the per-caller callback secret the issuer hands out; dedup on event id.
  6. Local development: point at a local module with the local verifier's static token; nothing else about the integration changes.

For a developer or an agent calling directly#

The debugging path. A personal or per-tool credential at the issuer (or a key), scoped to dev tenants only, short expiry, revocable on its own. The /guide prose and the manifest are what an agent reads; the per-item log route is what it uses when something is wrong. Never the shared app credential, never a prod tenant from a laptop.

Do-nots#

  • Do not forward user credentials to a module.
  • Do not put tenant only in a self-declared tag.
  • Do not share a credential across callers or across environments.
  • Do not verify users in a module.
  • Do not use a static shared secret on an internal callback reachable from a public URL.
  • Do not create service-account keys to let a non-GCP caller in.
  • Do not answer 404 to an authorization failure.
@versable-git/ui · reference, canon, and method, read in place