Checked against all 31
supabase/migrations/*.sql files, read in order.
Several tables were altered by later migrations, so the shapes below are the
final, cumulative schema, not only what the first CREATE TABLE said. See
Storage first for the conceptual model.DATABASE_URL), not through
PostgREST. A direct connection bypasses Row Level Security the same way a table owner does,
so RLS below matters for what a different credential (for example Supabase’s anon role)
could see, not for what the application’s own connection can.
Quick index
Not covered here:
supabase_migrations.schema_migrations, the Supabase CLI’s own tracking
table in a different schema (see the migration sync query below).
Table by table
business_transactions
The durable record of a request exactly as submitted to POST /execute, before any
decision. Created by 20260629013035_initial_schema.sql and never altered.
status is never updated after receipt. The current outcome of a transaction lives in
executions. RLS enabled, no policies.
executions, verifications, receipts, overrides
These four share one shape: an id primary key (execution_id, verification_id,
receipt_id, override_id), a business_transaction_id foreign key with
ON DELETE RESTRICT, one JSONB payload column (execution_json, verification_json,
receipt_json, override_json), a timestamp (created_at, verified_at, issued_at,
created_at), and a seq BIGSERIAL added by 20260711120000 as an exact insertion order,
because millisecond timestamps can tie under fast concurrent appends. All created by
20260629013035_initial_schema.sql, indexed on business_transaction_id and the timestamp,
RLS enabled.
- A transaction can have several
executionsrows: it is an append only sequence, not one mutable status. overridesis expected to stay empty:OverrideServiceis deliberately not reachable from the API.
execution_trust_records
The final, signed Execution Trust Record, the artifact an independent verifier checks. Not
every transaction gets one: a refused decision gets a refusal_records row instead.
Created by 20260629013035_initial_schema.sql (trust_record_id primary key,
business_transaction_id TEXT NOT NULL UNIQUE with a restricting foreign key,
transaction_json, trust_record_hash, created_at, updated_at). Altered twice:
20260702183000addedsignature_json JSONB(nullable, GIN indexed), storing the record’s own signature.20260907120000addedauthorization_json JSONB(GIN indexed, theSignedExecutionAuthorization),schema_version INTEGERandsignatures_json JSONB(hybrid signatures; without it a hybrid record would lose its second signature on the way through this table).
refusal_records
The signed record of a refused decision. RuntimeEngine writes one for every
decision that is not APPROVED: a policy rule rejection, a policy approval check failure, a
capability binding violation, a signal to intent binding violation, or a signal state or
approval signal mismatch. Caller authentication failures go to caller_audit_events
instead, and errors where the policy could not be evaluated at all write nothing here.
Created by 20260802120000_add_refusal_records.sql: refusal_record_id primary key,
business_transaction_id TEXT NOT NULL UNIQUE with a restricting foreign key,
decision_json NOT NULL, evaluated_intent_json NOT NULL, binding_violations_json
(nullable), submitted_by (nullable), refusal_record_hash TEXT NOT NULL,
signature_json JSONB NOT NULL. Signed from creation. Indexed on created_at, RLS enabled.
policy_content_hash TEXT (nullable) was added by 20261006120000_add_refusal_policy_content_hash.sql. It holds the hash of the policy content that made the decision, the same value an Execution Trust Record carries. Refusal Records written before that migration have no hash and still verify.
execution_intents
The signed Execution Intent written before an action is released to a connector. If it cannot be stored, nothing is released and the caller gets 503 EXECUTION_INTENT_UNAVAILABLE. Created by 20260921120000_add_execution_intents.sql.
- Identity and binding:
intent_idprimary key,business_transaction_id TEXT NOT NULL UNIQUEwith a restricting foreign key,decision_id,authorization_id,policy_name,policy_version,business_transaction_hash,action,target,intent_hash,signature_json JSONB,created_at(allNOT NULL);policy_content_hash,signals_hash,submitted_by,granted_capability(nullable). - Lifecycle:
stateis one ofPREPARED,RELEASED,FINALIZED,ERRORED,RESOLVED(defaultPREPARED);released_context_json,released_at,finalized_at,finalization_mode(INLINEorREPAIRED),trust_record_id,failure_reason. - Resolution by an operator:
resolution(NOT_EXECUTEDorEXECUTED),resolution_note,resolved_by,resolved_at. A check constraint requires resolution, note and time wheneverstateisRESOLVED.
created_at covers rows not yet FINALIZED or RESOLVED: these are the
actions that were, or may have been, released without a finished record. RLS enabled.
caller_audit_events
Every caller authentication and scoping decision; it is written on every authenticated
request. Created by 20260718090000_add_nonce_and_caller_audit_tables.sql and altered by
seven later migrations. Final shape:
Allowed
type values, after the widenings in 20260812120000, 20260816120000,
20260818130000, 20260824090000 and 20260911090000: caller.authenticated,
caller.rejected, caller.capability_denied, caller.principal_denied,
caller.non_human_denied, caller.structural_rejected, caller.capability_granted.
RLS enabled.
execution_audit_events
Execution control lifecycle events. It is the only table written by code outside this
repository: example-agent writes to it directly, correlated by
business_transaction_id (authorization_id is internal to Parmana and never crosses to the
agent). Created by 20260914120000_add_execution_audit_events.sql, then:
20260914130000addedbusiness_transaction_id TEXT(nullable, indexed) as the cross service correlation key, and madesignature_json,chain_hashandchain_positionnullable, because the agent holds only Parmana’s public key and cannot sign or chain.20260914140000widenedtypeto addauthorization.verified, the event the agent records after it verifies Parmana’s signature.
id BIGSERIAL primary key, type (session.created, execution.completed,
execution.rejected, authorization.verified), occurred_at TIMESTAMPTZ NOT NULL,
connector_id, authorization_id, session_id (all TEXT NOT NULL), action, reason
(rejections only, never a credential), credential_id, gateway_id, signature_json,
chain_hash, previous_chain_hash, chain_position, inserted_at,
business_transaction_id. Chained per authorization_id. Indexed on occurred_at,
authorization_id, connector_id, business_transaction_id. RLS enabled.
policies
Policy content keyed by (policy_name, policy_version), with content_json JSONB NOT NULL
and updated_at. Created by 20260916060000_add_policies_table.sql. It exists because
Vercel functions run on a read only filesystem, so approving a pending policy change there
cannot write a file; SupabasePolicyRepository stores the approved content here instead.
RLS enabled.
pending_policy_changes
A proposed policy change awaiting a checker’s approval or rejection. Created by
20260818120000_add_policy_governance_tables.sql: pending_policy_change_id primary key,
policy_name and policy_version (TEXT NOT NULL), proposed_content_json JSONB NOT NULL, proposed_by TEXT NOT NULL, proposed_at TIMESTAMPTZ NOT NULL, status (one of
PENDING_APPROVAL, APPROVED, REJECTED), reason TEXT NOT NULL, resolved_by,
resolved_at, rejection_reason.
The partial unique index ux_pending_policy_changes_open on (policy_name, policy_version)
where status = 'PENDING_APPROVAL' enforces at most one open change per policy version in
the database itself. Indexed on status, RLS enabled.
approval_issuers and approval_issuer_changes
Approver keys added without a deploy (Manage approvers). Created by
20260929120000_add_approval_issuers.sql.
approval_issuer_changes: change_id primary key, action (add or revoke),
approver_id, key_id, public_key_pem (present exactly when action = 'add'), reason,
proposed_by, proposed_at, status (PENDING_APPROVAL, APPROVED, REJECTED),
resolved_by (never equal to proposed_by, a check constraint), resolved_at,
rejection_reason. The partial unique index ux_approval_issuer_changes_open on
(approver_id, key_id) where status = 'PENDING_APPROVAL' allows one open change per key.
approval_issuers: primary key (approver_id, key_id), public_key_pem, revoked,
added_by_change_id and revoked_by_change_id (restricting foreign keys to
approval_issuer_changes), added_at, revoked_at. A check constraint keeps revoked true
exactly when revoked_by_change_id is set, so a key is never revoked without a change behind
it. Rows are never deleted. RLS enabled on both.
Who can sign approvals today, and since when:
external_connectors and external_connector_changes
External connectors registered without a deploy: a capability bound to an operator’s
HTTPS endpoint and to the policy that governs it, through /external-connectors. Created by
20260930120000_add_external_connectors.sql.
external_connector_changes: change_id primary key, action (register or revoke),
capability, and for a register only endpoint_url, policy_name, allowed_parameters
(TEXT[]) and timeout_ms (1000 to 30000); a check constraint requires all four on a register
and none on a revoke. Then reason, proposed_by, proposed_at, status (PENDING_APPROVAL,
APPROVED, REJECTED), resolved_by (never equal to proposed_by, a check constraint),
resolved_at, rejection_reason. The partial unique index ux_external_connector_changes_open
on capability where status = 'PENDING_APPROVAL' allows one open change per capability.
external_connectors: registration_id primary key, which is the approved register change’s
change_id (a restricting foreign key), capability, endpoint_url, policy_name,
allowed_parameters, timeout_ms, status (active or revoked), registered_at,
revoked_by_change_id (a restricting foreign key) and revoked_at. A check constraint keeps
status revoked exactly when revoked_by_change_id is set. The partial unique index
ux_external_connectors_active on capability where status = 'active' allows one active
registration per capability. Rows are never deleted. RLS enabled on both.
Which endpoints are registered, and who asked and approved:
policy_change_approval_records
The signed record of a checker’s decision on a pending change. Created with
pending_policy_changes: policy_change_approval_record_id primary key,
pending_policy_change_id (a restricting foreign key to pending_policy_changes),
policy_name, policy_version, proposed_by, approved_by, proposed_at, approved_at,
content_hash_before (nullable), content_hash_after TEXT NOT NULL,
signature_json JSONB NOT NULL. 20260907130000 added previous_record_hash (nullable),
which chains the approval history itself; content_hash_after only proves the live policy
matches what was approved. Indexed on (policy_name, policy_version, approved_at DESC).
This is the only table with a real RLS policy: 20260818150000 grants the anon role read
only SELECT here, for the CI job that checks policy approvals with SUPABASE_ANON_KEY.
Never write rows here by hand.
consumed_nonces, consumed_approval_nonces, consumed_policy_change_step_up_nonces
Three deliberately separate tables, not duplication. Each backs a distinct nonce
namespace (a gateway envelope, an approval artifact, a checker’s step up key) issued by
different parties; sharing one table would let a collision between unrelated namespaces
falsely report “already consumed”.
Created by
20260718090000_add_nonce_and_caller_audit_tables.sql,
20260805180000_add_consumed_approval_nonces.sql and
20260818140000_add_consumed_policy_change_step_up_nonces.sql. Never altered, RLS enabled.
challenge_records
Investigation records for a disputed claim, and the only mutable table: status,
investigation_steps_json, finding_json, outcome_json and disclosure_json are updated
as an investigation proceeds. Created by 20260803150000_add_challenge_records.sql:
challenge_record_id primary key, status (open, investigating, resolved),
claim_challenged TEXT NOT NULL, source_json JSONB NOT NULL,
investigation_steps_json JSONB NOT NULL DEFAULT '[]', the three nullable JSONB result
columns, supersedes TEXT (deliberately no foreign key), created_at, updated_at.
Indexed on status and created_at, RLS enabled.
Deliberately unsigned: the party that writes it is the party that could misrepresent it, so
a signature would prove the bytes were not altered without answering the real trust
question. Append only history inside a record is enforced by
PostgresChallengeRecordRepository.append, not by the database.
rate_limit_counters
Fleet wide rate limit counters shared by every process on the same database, the durable
counterpart to the in process store of express-rate-limit. Created by
20260910120000_add_rate_limit_counters.sql: key TEXT PRIMARY KEY (prefixed per limiter,
execute: plus a caller id or health: plus a client IP, so the two limiters never share a
key), count INTEGER NOT NULL, reset_time TIMESTAMPTZ NOT NULL (indexed). An expired row
resets on its next hit, so no cleanup job is needed.
The only table without RLS, deliberately: it holds no business or trust data and has no
dashboard access path.
handbook_download_leads
The email address given before a handbook PDF download (packages/api/src/routes/handbook.ts).
Created by 20260916150000_add_handbook_download_leads.sql: handbook_download_lead_id TEXT
primary key, email TEXT NOT NULL, captured_at TIMESTAMPTZ NOT NULL DEFAULT now()
(indexed descending). RLS enabled.
Common SQL for troubleshooting, audit, and verification
Every example assumes apsql or pg session on DATABASE_URL. Replace $1, $2 with real
values.
Trace one transaction across every table
Everything that happened for onebusiness_transaction_id:
example-agent had recorded authorization.verified
for a transaction that Parmana’s own logs only showed as a generic 500.
Find released actions without a finished record
Trace one caller’s audit history
Walk a caller’s audit chain
A break between one row’schain_hash and the next row’s previous_chain_hash means a gap
or an altered row.
Capability grants and denials in the last hour
Recent refusal reasons
Check a rate limiter’s current state for a caller
Confirm a nonce was consumed
Audit the maker and checker approval queue
Row counts (quick health snapshot)
Migration sync (files against the tracking table)
List live tables and their RLS status
Check a table’s actual CHECK constraints
Several constraints were widened more than once, so do not trust the firstCREATE TABLE
alone:
Find foreign keys pointing at a table (before ever dropping one)
Next
Storage
The conceptual model this reference builds on.
Caller Audit Trail
The concept
caller_audit_events backs.