Veefore docs

Data model

The tables, how they relate, and the structural rule about where an amount is allowed to exist.

One PostgreSQL database, nine migrations. Every tenant-scoped table carries an organization id, and every query in the data layer requires a tenant scope.

#By migration

#0001 Tenancy and people

organizations · workspaces · environments · users · members · roles · role_permissions · member_roles · sessions

The hierarchy everything else hangs off. environments holds gateway_mode, which is why promoting to ENFORCE is a tenancy operation.

#0002 Agents

agents · agent_identities · agent_passports · agent_capability_grants · capabilities

agent_identities carries the public key and a generation counter. Rotation bumps the generation, and an assertion signed with a superseded key is refused with IDENTITY_GENERATION_STALE rather than a generic failure — so the agent's logs say what actually happened.

#0003 Tools and credentials

tools · tool_agent_authorizations · resources · credentials · credential_leases

tool_agent_authorizations is the join that makes registration grant nothing. A tool existing is not a tool being callable.

credentials holds sealed material in a column the repository layer does not select into API responses. credential_leases records what was issued, by reference, never by value.

#0004 Policy and delegation

policies · policy_versions · policy_bindings · policy_test_cases · delegations · delegation_constraints

policy_versions stores the rule set as JSON, and that JSON is the policy — not a cache of something else. It is what the visual builder reads and writes.

policy_bindings carries the scope: organization, workspace, environment, agent or tool.

#0005 Risk, approval and trust

risk_configurations · risk_events · risk_signals · approvals · approval_decisions · trust_evidence · trust_providers

risk_signals stores each signal with its weight and its human-readable explanation. Storing the explanation rather than regenerating it means a six-month-old decision explains itself the same way it did at the time.

approvals is bound to the request that created it, which is what stops an approval being replayed against a different payload.

#0006 Actions and audit

actions · action_stage_results · audit_events

The two tables that grow without bound.

action_stage_results is one row per pipeline stage, which is what makes a decision decomposable after the fact.

audit_events carries the hash chain: each row's hash covers its content and the previous row's hash, keyed with VEEFORE_AUDIT_CHAIN_SECRET.

#0007 Security and protocols

incidents · emergency_actions · mcp_servers · mcp_tools · a2a_connections · registry_entries

emergency_actions stores the blast radius recomputed at execution time, so the evidence shows what was actually affected rather than what a preview predicted some minutes earlier.

mcp_tools carries an authorized flag per tool, separate from discovery.

#0008 Platform

webhook_subscriptions · webhook_deliveries · platform_events · api_keys · usage_records · plans · subscriptions · sso_connections · scim_configurations · network_rules · jobs

api_keys stores a hash. The plaintext is returned once at creation and never again.

jobs is the queue. Claiming is tenant-safe and guarded against overlapping poll ticks; abandoned jobs are reclaimed and dead-lettered after repeated failure.

#0009 Row-level security

No new tables. Applies PostgreSQL RLS policies to every tenant-scoped table when DATABASE_ENABLE_RLS=true.

Two independent isolation layers

The application layer requires a tenant scope on every query and refuses without one. RLS exists so a bug in that layer does not become a cross-tenant data leak.

Either alone would be sufficient. The point of defence in depth is that you do not have to be confident the first layer is perfect. → Tenant isolation

#The amount rule

This is enforced structurally by npm run scope:audit, not just documented.

No table may have a balance column. The audit asserts that no migration declares balance, available_balance, ledger_balance or funds. A wallet could be written without the word "wallet"; it could not be written without somewhere to put a balance.

An amount column may only exist on a policy or decision table. The audit parses every CREATE TABLE, finds those with an amount column, and fails if any is not on this list:

Table Role of the amount
actions What the agent asked to do. Decision context
approvals What a human approved. Decision context
delegations The ceiling granted. A policy input
delegation_constraints A narrower ceiling. A policy input
policy_versions A threshold in a rule. A policy input
policy_test_cases A test input
risk_events What was assessed. Decision context
plans · subscriptions · usage_records Veefore's own SaaS pricing

An amount column appearing anywhere else fails the build with the table named. That is what makes the exclusion structural rather than aspirational. → Scope

#Identifiers

ULID-based with a type prefix, so ids are sortable by creation time and self-describing. Full table in Conventions.

An id is not a credential. Ids appear in URLs, logs and error messages; acting as an agent requires its private key.

#Append-only in practice

actions and audit_events are only ever inserted. Nothing in the application updates or deletes a row in either, outside the retention process — which requires audit:configure_retention, is step-up gated, and writes a CRITICAL audit event.

That is what makes the hash chain meaningful. A chain over a mutable table would be a chain over something the application itself rewrites.

#Growth and retention

Both unbounded tables grow roughly in proportion to protected action volume.

Export before shortening retention. The export is watermarked and hashed, so the archive stays verifiable. → Audit and export

#The schema catalogue

packages/db/src/schema-catalog.ts describes every table and column in one place, used for validation and for the console's introspection. If you want the column-level truth rather than this summary, read it — it is the source the repositories are generated against.

Identity, authority, policy and enforcement for autonomous AI agents.