Skip to main content

Resource server states and drift events

Reference page for the two state structures that the launch lifecycle and Monitor tab rely on.

State machine

A resource server has a state column (text enum) that flips through a small set of values during setup and operation:

                ┌─────────────────┐
│ pending_scan │ ← created via POST /authsec/resource-servers
└────────┬────────┘
│ POST /rescan succeeds

┌─────────────────┐
│ needs_setup │ ← tools discovered, setup checklist has gaps
└────────┬────────┘
│ POST /activate (every checklist item green)

┌─────────────────┐
│ ready │ ← live; SDK enforcement active
└─────────────────┘

┌─────────────────┐
│ scan_failed │ ← POST /rescan errored; admin must address + retry
└─────────────────┘
(sink state until next successful rescan)

State values

StateMeaningWhat triggers it
pending_scanJust registered. No tools discovered yet.Initial state after registration.
needs_setupTools known. Setup checklist not all green.First successful POST /rescan.
readyLaunched. SDK runtime is enforcing the policy preview.POST /activate.
scan_failedDiscovery hit an error.Failed POST /rescan. The last_scan_error column has the reason.

State transitions

Allowed transitions:

  • pending_scanneeds_setup (successful rescan)
  • pending_scanscan_failed (failed rescan)
  • needs_setupready (activation, all checks green)
  • needs_setupscan_failed (failed rescan)
  • scan_failedneeds_setup (successful rescan)
  • scan_failedpending_scan — not allowed; once tools have been discovered, the RS stays in the post-discovery world

ready does not transition back. There's no Un-activate. To effectively shut down a resource server, rotate the introspection secret (SDK calls start failing) or soft-delete it.

UI badges

The Applications screen shows two badges per RS:

  • Discovered — derived from state = needs_setup AND scan_generation > 0. Means "we know your tools" without committing to launched.
  • Not launchedstate != ready. Means runtime enforcement isn't on yet.

The badges are UI computed, not stored on the RS row.

ColumnWhat it tracks
scan_generationBumped on every successful rescan. Per-tool records carry the generation they were last seen in.
last_successful_generationThe highest scan_generation that produced valid tools.
last_scan_statussuccess, failed, or in_progress.
last_scan_errorFree-text error from the most recent failed scan.
setup_completed_atTimestamp of the most recent successful activation. NULL until first activation.
setup_completed_byAdmin user UUID who clicked Activate.

Drift events catalog

The resource_server_drift_events table records every post-activation change to the live policy. Five event types today.

Schema

CREATE TABLE resource_server_drift_events (
id UUID PRIMARY KEY,
rs_id UUID NOT NULL REFERENCES resource_servers(id),
event_type TEXT NOT NULL,
event_payload JSONB NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
occurred_by UUID,
dismissed_at TIMESTAMPTZ,
dismissed_by UUID
);

Event types

scope_deleted

Fired when an admin deletes a scope after the application is in ready state.

{
"prior_scope_id": "rs-xyz:tools:write",
"prior_scope_name": "Write tools",
"tools_affected": 12
}

Why it matters: tokens already issued with the deleted scope still pass validation until they expire. New tokens won't carry it. Tools previously mapped to it become unmapped — and therefore denied.

tool_unmapped

Fired when a tool's scope mapping is removed (returned to unmapped state) after activation.

{
"tool_name": "actions_create_pr",
"prior_scope_id": "rs-xyz:tools:write"
}

Why it matters: at the next SDK policy refresh, this tool starts returning 403 to everyone.

default_role_disabled

Fired when the access policy on the Access tab is switched off (enabled = false).

{
"prior_default_role": "rs-xyz:viewer"
}

Why it matters: new users who log in get no role assignments. Existing role bindings persist; only new users are affected.

default_role_changed

Fired when the default role is swapped for a different one.

{
"prior_default_role": "rs-xyz:viewer",
"new_default_role": "rs-xyz:limited"
}

Why it matters: previously-onboarded users keep their old role. The change only applies to users who log in after the swap.

secret_rotated

Fired when POST /rotate-introspection-secret is called.

{
"rotated_by": "admin-user-uuid",
"rotated_at": "2025-10-30T14:22:13Z"
}

Why it matters: SDK instances using the old secret start failing introspection until they're restarted with the new credentials. Plan ahead.

Dismissal

Drift events are persistent — dismissal doesn't delete the row, it marks it acknowledged. POST /authsec/resource-servers/:id/drift-events/:event_id/dismiss sets dismissed_at and dismissed_by. The Monitor tab can filter to show only undismissed events.

Audit history is preserved regardless of dismissal — the row stays in the database forever.