Skip to main content
The auth service uses a dedicated auth schema in the existing Postgres instance. All tables are created by migration 004_auth_schema.sql.

Entity Relationship Diagram

┌──────────────────┐
│ public.customers │
└────────┬─────────┘
         │ 1:N

┌────────▼─────────┐      ┌──────────────────┐
│  signing_keys    │      │    app_tokens     │
│  (ES256 keys)    │      │  (qt_app_*)       │
└────────┬─────────┘      └────────┬──────────┘
         │                         │
         │    ┌────────────────────┘
         │    │ FK: app_token_id
         │    │ FK: signing_key_id
    ┌────▼────▼────┐
    │ bearer_tokens │
    │ (qt_bearer_*) │
    └───────┬───────┘
            │ FK: bearer_token_id
    ┌───────▼───────┐
    │ agent_tokens  │
    │ (qt_agent_*)  │
    └───┬───────┬───┘
        │       │ FK: parent_agent_token_id
        │  ┌────▼──────────┐
        │  │subagent_tokens│
        │  │(qt_subagent_*)│
        │  └───────┬───────┘
        │          │
   ┌────▼──────────▼────┐
   │  session_tokens    │
   │  (qt_session_*)    │
   └────────────────────┘

┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│  webhook_keys    │  │ override_tokens  │  │  revocation_log  │
│  (per customer)  │  │ (qt_override_*)  │  │  (append-only)   │
└──────────────────┘  └──────────────────┘  └──────────────────┘

┌──────────────────┐
│   audit_log      │
│  (auth events)   │
└──────────────────┘

Tables

auth.signing_keys

ES256 key pairs per customer. Private keys are encrypted at rest with AES-256-GCM.
ColumnTypeNotes
idUUIDPK, auto-generated
customer_idUUIDFK to public.customers(id)
key_typeTEXTAlways 'ES256' (CHECK constraint)
public_keyTEXTPEM-encoded EC public key
encrypted_private_keyTEXTBase64(nonce + AES-256-GCM ciphertext)
is_activeBOOLEANOnly one active key per customer
created_atTIMESTAMPTZAuto-set
rotated_atTIMESTAMPTZSet when deactivated during rotation

auth.app_tokens

Management-plane tokens. Stored as SHA-256 hashes (not JWTs).
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
token_hashTEXTSHA-256 hash, UNIQUE
nameTEXTHuman-readable label
scopesJSONBList of allowed management scopes
is_revokedBOOLEANSoft delete
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNULL = no expiry
last_used_atTIMESTAMPTZUpdated on each use

auth.bearer_tokens

Data-plane root tokens. JWTs signed with customer’s ES256 key.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
jtiTEXTJWT ID, UNIQUE
app_token_idUUIDFK to auth.app_tokens(id)
signing_key_idUUIDFK to auth.signing_keys(id)
environmentTEXTCHECK: development, staging, production
is_revokedBOOLEAN
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNOT NULL

auth.agent_tokens

Per-agent identity with embedded RBAC policy.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
jtiTEXTJWT ID, UNIQUE
bearer_token_idUUIDFK to auth.bearer_tokens(id)
agent_idTEXTCaller-defined agent identifier
agent_nameTEXTOptional human-readable name
rbacJSONBRBACPolicy structure
is_revokedBOOLEAN
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNOT NULL

auth.subagent_tokens

Delegated sub-agent tokens with permission narrowing.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
jtiTEXTJWT ID, UNIQUE
parent_agent_token_idUUIDFK to auth.agent_tokens(id)
agent_idTEXTSub-agent identifier
agent_nameTEXTOptional
delegation_depthINTCHECK: >= 1
rbacJSONBMust be subset of parent’s RBAC
is_revokedBOOLEAN
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNOT NULL

auth.session_tokens

Short-lived session binding with event counting.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
jtiTEXTJWT ID, UNIQUE
parent_token_idUUIDAgent or subagent token UUID
parent_token_typeTEXTCHECK: agent or subagent
session_idTEXTCaller-defined session ID
max_eventsINTDefault: 1000
event_countINTTracked in Redis, persisted on close
is_revokedBOOLEAN
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNOT NULL

auth.webhook_keys

Outbound webhook signing secrets.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
endpoint_urlTEXT
secret_hashTEXTSHA-256 hash of the signing secret
is_activeBOOLEAN
created_atTIMESTAMPTZ

auth.override_tokens

Ephemeral single-use override tokens.
ColumnTypeNotes
idUUIDPK
customer_idUUIDFK to public.customers(id)
jtiTEXTJWT ID, UNIQUE
event_idUUIDThe held event being decided on
allowed_decisionsJSONBe.g. ["approve", "reject"]
max_usesINTDefault: 1
use_countINTIncremented on each decision submission
reasonTEXTOptional context for the hold
is_revokedBOOLEAN
created_atTIMESTAMPTZ
expires_atTIMESTAMPTZNOT NULL

auth.revocation_log

Append-only log of all revoked tokens. Used to rebuild the bloom filter.
This table has no RLS — it needs cross-customer access for bloom filter rebuild.
ColumnTypeNotes
idBIGSERIALPK
jtiTEXTRevoked token’s JTI
token_typeTEXTCHECK: app, bearer, agent, subagent, session, override
customer_idUUID
reasonTEXTOptional
revoked_byTEXTWho triggered the revocation
created_atTIMESTAMPTZ

auth.audit_log

Auth-specific audit trail.
ColumnTypeNotes
idBIGSERIALPK
customer_idUUID
actionTEXTe.g. token_created, key_rotated
token_typeTEXTOptional
target_jtiTEXTJTI of the affected token
actor_jtiTEXTJTI of the token used to perform the action
ip_addressTEXTOptional
metadataJSONBAdditional context
created_atTIMESTAMPTZ

Indexes

IndexTableColumnsCondition
idx_auth_signing_keys_customersigning_keyscustomer_idWHERE is_active = TRUE
idx_auth_app_tokens_customerapp_tokenscustomer_idWHERE NOT is_revoked
idx_auth_app_tokens_hashapp_tokenstoken_hash
idx_auth_bearer_tokens_jtibearer_tokensjti
idx_auth_bearer_tokens_customerbearer_tokenscustomer_idWHERE NOT is_revoked
idx_auth_agent_tokens_jtiagent_tokensjti
idx_auth_agent_tokens_customeragent_tokenscustomer_idWHERE NOT is_revoked
idx_auth_subagent_tokens_jtisubagent_tokensjti
idx_auth_subagent_tokens_customersubagent_tokenscustomer_idWHERE NOT is_revoked
idx_auth_subagent_tokens_parentsubagent_tokensparent_agent_token_id
idx_auth_session_tokens_jtisession_tokensjti
idx_auth_session_tokens_customersession_tokenscustomer_idWHERE NOT is_revoked
idx_auth_webhook_keys_customerwebhook_keyscustomer_idWHERE is_active = TRUE
idx_auth_override_tokens_jtioverride_tokensjti
idx_auth_override_tokens_eventoverride_tokensevent_idWHERE NOT is_revoked
idx_auth_revocation_log_jtirevocation_logjti
idx_auth_audit_log_customeraudit_logcustomer_id, created_at DESC
All partial indexes use WHERE NOT is_revoked or WHERE is_active = TRUE to skip irrelevant rows.

Row-Level Security

RLS is enabled on all tables except revocation_log (needs cross-customer access for bloom filter rebuild). Policy pattern (same as the public schema):
CREATE POLICY customer_isolation_{table} ON auth.{table}
    USING (customer_id = current_setting('app.current_customer_id')::uuid);
The session factory sets app.current_customer_id via:
SET LOCAL app.current_customer_id = '{customer_id}';
Admin sessions bypass RLS by not setting this variable (uses the quint superuser role).