Data Dictionary¶
What data SCP and its collectors actually hold — every table backing the dashboard, plus the three activity-collector schemas that feed the Agent Activity tab. Written by hand against the current schema/model files on 2026-07-26; it is a snapshot, not a generated artifact, so it can drift if the schema changes without this page being updated. See API Endpoints for how each of these gets read or written.
Scope for this pass: control-plane Postgres tables that back the dashboard's Registry/Agent Activity/Compliance tabs, plus the collector SQLite schema. Supporting tables that exist but aren't surfaced in the current dashboard (rules engine, telemetry billing, deployments/usage/ adherence) are listed briefly at the end for completeness, not documented field-by-field.
Agent Registry¶
Source: control-plane/database/schemas/agent_registry.sql, control-plane/shared/models/agents.py.
agents¶
| Field | Type | Notes |
|---|---|---|
agent_id |
text, PK | e.g. agent:prior-auth-prod |
name |
text | Human-readable name |
description |
text, nullable | |
role_id |
text | Reference to a role in the context graph |
allowed_intents |
jsonb | Array of {task_type, scope?} — what task types this agent may request context for |
overrides |
jsonb | SCD IDs always included in this agent's context |
restrictions |
jsonb | SCD IDs never included in this agent's context |
status |
text | active | suspended | revoked |
owner_name |
text, nullable | HITL accountability field (migration 005) — who's accountable for this agent's operation: a Propel workflow owner, or whoever provisioned it. Distinct from created_by below. |
owner_verified |
boolean, default false | true only once owner_name is confirmed against a real identity provider (SSO). false = manually asserted — must be disclosed as such in the UI, never presented as system-verified. This is the field blocked on Dan's SSO/SCIM work. |
created_by |
text | Who registered this agent (context/bundle provenance — not the same concept as owner_name) |
created_at / updated_at |
timestamptz | |
organization_id |
text, nullable | Reserved for future multi-tenancy |
agent_api_keys¶
| Field | Type | Notes |
|---|---|---|
key_id |
text, PK | |
agent_id |
text, FK → agents | |
key_prefix |
text | First 8 chars, for lookup |
key_hash |
text | bcrypt/argon2 hash — plaintext key is never stored |
name |
text, nullable | e.g. "Production Key" |
scopes |
jsonb, nullable | Subset of agent's allowed_intents, or null = full permissions |
is_active |
boolean | Legacy flag, kept for backward compatibility |
status |
text | active | revoked | expired — authoritative field |
created_at, expires_at, last_used_at, revoked_at |
timestamptz, nullable | |
revoked_by |
text, nullable | Actor who revoked the key |
agent_context_requests — the audit trail¶
Append-only, hash-chained, and enforced immutable at the database level (trigger blocks UPDATE/ DELETE for all roles including superusers; row-level security has no UPDATE/DELETE policy at all). This is the record an auditor would actually be shown.
| Field | Type | Notes |
|---|---|---|
request_id |
text, PK | |
agent_id |
text, FK → agents | |
task_type |
text | What was requested |
request_params |
jsonb, nullable | |
intent_valid |
boolean | Whether the request was allowed |
validation_errors |
jsonb, nullable | Why, if not valid |
context_bundle_ids / context_scd_ids |
jsonb, nullable | What was actually returned |
processing_time_ms |
integer, nullable | |
requested_at |
timestamptz | |
chain_sequence |
bigserial | Strict insertion order for chain traversal |
previous_hash |
text | SHA256 of the prior record (or genesis hash for the first) |
record_hash |
text | SHA256(chain_sequence \|\| request_id \|\| agent_id \|\| task_type \|\| intent_valid \|\| requested_at \|\| previous_hash) |
GET /api/audit/verify walks this chain and recomputes every hash to detect tampering — see
API Endpoints.
MCP Server Registry¶
Source: control-plane/database/schemas/mcp_servers.sql.
mcp_servers¶
Same HITL-ownership pattern as agents, deliberately a separate table (a connector isn't an
agent).
| Field | Type | Notes |
|---|---|---|
server_id |
text, PK | |
name |
text | |
description |
text, nullable | |
status |
text | active | inactive | deprecated |
owner_name |
text, nullable | Same accountability concept as agents.owner_name |
owner_verified |
boolean, default false | Same SSO-pending caveat as agents.owner_verified |
created_by |
text | |
created_at / updated_at |
timestamptz |
Bundle Registry & Context Service¶
Source: control-plane/database/schemas/index_ledger.sql, context_service.sql.
bundles¶
| Field | Type | Notes |
|---|---|---|
bundle_id, version |
text, composite PK | |
bundle_type |
text | project | meta | standards | domain | concern |
schema_version |
text | |
created_by |
text | |
description |
text, nullable | |
manifest |
jsonb | Complete bundle manifest — SCDs and imports |
validation_status |
text | pending | valid | invalid |
validation_errors |
jsonb, nullable | |
created_at, published_at |
timestamp, published_at nullable until published |
|
is_active |
boolean |
tags (Context-as-a-Service)¶
Mutable pointers to bundle versions (Docker-tag style): tag_name (PK), bundle_id,
resolved_bundle_ids (flattened transitive import graph), cached context_yaml, description,
timestamps.
Supporting tables not detailed here: context_service_events (append-only audit log of tag/
subscription lifecycle events), active_subscriptions (live agent↔tag subscriptions for
streaming context).
Observed Activity Log (Collector Store)¶
Source: integrations/cowork-telemetry/store/schema.sql. This is a separate SQLite database,
not part of the control-plane Postgres — deliberately standalone (see FR-005/FR-006), read via its
own API on port 7002 (see Activity Ingestion API).
Three independent collectors write into the same table, distinguished by source_id.
observed_activity_log¶
| Field | Type | Notes |
|---|---|---|
event_id |
text, PK | |
source_id |
text | claude-enterprise-cowork | claude-code-cli | claude-enterprise-chat |
observed_at |
text (ISO 8601) | From the event's own timestamp |
correlation_id |
text, nullable | Cowork's prompt.id (UUID v4), where applicable |
session_id |
text, nullable | |
actor_identity |
text, nullable | Stable, non-PII user identifier (user.account_uuid / actor.user_id) — not the email address. This is the field SSO/SCIM would resolve to a verified NexTern identity once connected. |
event_name |
text | Raw/inferred source event name (e.g. user_prompt, tool_result, api_request) |
event_type |
text | Normalized: prompt | tool_call | mcp_call | model_call | approval | error | unknown |
tool_name, mcp_server_scope |
text, nullable | |
success |
text ("true"/"false"), nullable | tool_result events only |
decision, decision_source |
text, nullable | tool_decision (human approval) events only |
model, cost_usd, input_tokens, output_tokens, duration_ms |
nullable | api_request/api_error events only |
error_message |
text, nullable | |
redaction_masked_keys, redaction_masked_count |
text/integer, nullable | What the collector masked before this row was ever written |
raw_attributes |
text (JSON) | Full already-redacted attribute map, for drill-down |
ingested_at |
text | When the watcher/poller wrote the row |
activity_sources¶
One row per collector: source_id (PK), description, coverage_json (the machine-readable
manifest below), first_event_at, last_event_at, event_count.
What each collector actually captures — and what it redacts¶
This is the part that matters most for an auditor asking "what PII touches storage." All three write into the same table above; their capture/redaction posture differs significantly.
claude-enterprise-cowork (watcher.py, tails an OTel collector's file export)
- Captures: user prompts (event inferred, value hashed by the collector before reaching this store), tool/MCP invocations (name, success, duration, MCP scope), human approval decisions, per-request model/tokens/cost/duration/errors.
- Redacted at the collector, upstream of this store, per
otel-collector-config.yaml: full prompt text, tool input/parameters, user email, workspace path — this store never sees those in plaintext. - Does not capture: anything from a session where the admin hasn't configured an OTLP endpoint in Cowork Admin settings; sessions already open when that config was saved; traffic dropped by network egress allowlisting.
- Caveat: event-type classification is inferred from which attributes are present, not read from
a confirmed "event name" wire field (Anthropic's docs don't specify one) — flagged
unknownrather than guessed when no pattern matches.
claude-code-cli (watcher_claude_code.py, tails a second, independent OTel collector)
- Captures: prompt/response length only (text redacted unless an admin explicitly sets
OTEL_LOG_USER_PROMPTS/OTEL_LOG_ASSISTANT_RESPONSES— not recommended, and this deployment's setup instructions say not to), tool results and human accept/reject decisions, per-request model/tokens/cost/duration/errors, permission-mode changes, MCP connection status, plugin events, model refusals (category redacted unlessOTEL_LOG_TOOL_DETAILSis set). - Redacted:
user.emailis always masked by this collector regardless of the above env vars — defense-in-depth on top of Claude Code's own default redaction. - Does not capture: prompt/response text, tool inputs, file paths, raw API bodies (redacted by Claude Code itself by default — this adapter deliberately doesn't opt back in); Claude Code's separate metrics signal (only logs/events are ingested); anything from a session predating the telemetry env vars or outside the MDM rollout.
- Caveat: not yet validated against a real Claude Code OTel export as of last verification (2026-07-24) — event-name inference is built from documented attribute signatures, unconfirmed against real wire data.
claude-enterprise-chat (poller_compliance.py, polls Anthropic's Compliance API directly —
no OTel collector in this path)
- Captures: chat lifecycle (created/viewed/updated/deleted/access-denied/settings/snapshot/
sharing), project lifecycle, project/chat document and file events, artifact events — scoped via
a deliberate allowlist of ~40
claude_chat_*/claude_project_*/claude_file_*/claude_artifact_*activity types out of the "hundreds" the Compliance API can return (auth, admin, SCIM sync, etc. are out of scope by design — this is AI-usage governance visibility, not a general security SIEM feed). - Redacted:
actor.email_address,actor.unauthenticated_email_address, andactor.ip_addressare hashed (hmac-sha256, same key the OTel collectors use) before being written anywhere, includingraw_attributes— this API would otherwise hand back plaintext PII directly.actor_identityusesactor.user_id(Anthropic's documented stable, non-PII join key), never the email. - Retention/backfill: Anthropic retains Activity Feed records for 6 years and supports full
backfill — this poller deliberately does not auto-backfill on first run (seeds from "now"
forward) unless
BACKFILL_ON_FIRST_RUN=1is set; that's an operational choice here, not an API limitation.
Each collector's manifest above is generated by the collector itself (COVERAGE_MANIFEST in its
source) and served live at GET /api/activity-sources/{source_id}/coverage — so this table is a
snapshot of something that's also independently machine-readable at runtime, not the only copy of
this information.
Supporting tables (not currently surfaced in the dashboard)¶
Exist in the schema, used internally, not documented field-by-field here:
- Rules Engine (
rules.sql):rule_schemas,rule_sets,rules,rule_history— customer- defined validation rules referenced by bundles. - Telemetry/billing (
telemetry.sql):telemetry_reports,telemetry_queue— SCP's own phone-home usage telemetry for billing. Explicitly privacy-first by design: aggregate counts only, no PII, no agent names, no SCD content, no request parameters. - Deployment/usage/adherence (
index_ledger.sql):events,deployments,ai_services,access,usage_events,adherence_reports,validations— bundle lifecycle and adherence tracking; perapi-endpoints.md's API Status table, analytics on top of these tables is still TBD.
Next steps¶
- See API Endpoints for how each table is read/written.
- See
demo/docs/audit-checklist-mapping.md(nextern-2h26 repo) for how this data maps to a real external audit checklist.