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
| State | Meaning | What triggers it |
|---|---|---|
pending_scan | Just registered. No tools discovered yet. | Initial state after registration. |
needs_setup | Tools known. Setup checklist not all green. | First successful POST /rescan. |
ready | Launched. SDK runtime is enforcing the policy preview. | POST /activate. |
scan_failed | Discovery hit an error. | Failed POST /rescan. The last_scan_error column has the reason. |
State transitions
Allowed transitions:
pending_scan→needs_setup(successful rescan)pending_scan→scan_failed(failed rescan)needs_setup→ready(activation, all checks green)needs_setup→scan_failed(failed rescan)scan_failed→needs_setup(successful rescan)scan_failed→pending_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_setupANDscan_generation > 0. Means "we know your tools" without committing to launched. - Not launched —
state != ready. Means runtime enforcement isn't on yet.
The badges are UI computed, not stored on the RS row.
Related fields
| Column | What it tracks |
|---|---|
scan_generation | Bumped on every successful rescan. Per-tool records carry the generation they were last seen in. |
last_successful_generation | The highest scan_generation that produced valid tools. |
last_scan_status | success, failed, or in_progress. |
last_scan_error | Free-text error from the most recent failed scan. |
setup_completed_at | Timestamp of the most recent successful activation. NULL until first activation. |
setup_completed_by | Admin 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.
Related
- Launch the application — what activation does
- Monitor drift and runtime — Monitor tab UI for these events
- Resource servers API — endpoints that emit drift events