Skip to main content

Data Model Reference

Source files: src/qtg/infrastructure/db/models.py, session.py
Defines the full data model of the qtg v3 runtime. The current schema has roughly 54 tables across two logical schemas; the ERD below shows a simplified view of the core movement + registry structure documented in detail in this page.
  • Plan Template layer — design-time definition of the movement plan
  • Request layer — runtime instance
  • Audit layer — events / artifacts / callback records
  • Registry layer — persistence for executor/signer registrations

Schema layout (PostgreSQL, PG-only since v0.1.0)

QTG runs on PostgreSQL only and splits its tables into two logical schemas defined in models.py via __table_args__:
  • public — deployment-global, network-independent identity tables. Exactly three:
    • api_clients, api_client_keys, nonce_registry
    • Alembic head is tracked in public.alembic_version_global.
  • qtg_network — the logical schema used by every other model (~51 tables). At runtime SQLAlchemy rewrites qtg_network to the concrete network bucket via schema_translate_map={"qtg_network": <network>}, and each session runs SET LOCAL search_path for its network class.
The logical network schema can be materialized in either of two physical buckets:
  • mainnet and testnet — one full copy of the qtg_network table set per network class. Each carries its own alembic_version head.
A normal QTG process and its database are single-network: qtg init --network <network> creates public plus exactly the selected mainnet or testnet bucket. At startup, boot_validate_schema_invariants() checks those two schemas and their Alembic heads. The unselected bucket is not required or queried, even though the runtime engine registry can represent both NetworkClass values. See Bootstrap & Configuration for the bootstrap chain.

Table families

Beyond the core movement/registry tables detailed below, the schema includes these families (all in qtg_network unless noted):
  • Identity (public): api_clients, api_client_keys, nonce_registry
  • Operator / policy: allowed_addresses, auto_approve_policies, approval_policy_targets, budget_ledger, execution_stats, template_proposals
  • Audit: audit_events, audit_outbox
  • Agent wallet top-up: agent_authorities (+ agent_authority_signers, agent_authority_allowed_addresses, agent_authority_budget_ledger), agent_wallet_funding_envelopes, agent_wallet_topup_ledger
  • Capital transfers: capital_transfer_requests
  • Balance reservations: balance_reservations, balance_reservation_pools, balance_snapshots
  • Hyperliquid (6): hyperliquid_master_accounts, hyperliquid_agent_wallets, hyperliquid_strategy_bindings, hyperliquid_nonce_leases, hyperliquid_emergency_states, plus related state
  • CCIP (6): ccip_router_registry, ccip_lane_registry, ccip_chain_token_lane, ccip_chain_source_token, ccip_router_preapproval, ccip_send_intent
  • Stargate (Pro): stargate_pools (+ stargate_pool_audit), stargate_chain_registry (+ stargate_chain_registry_audit), stargate_path_baseline (+ stargate_path_baseline_audit), stargate_send_intent
  • Bridge send intents: usdt0_send_intent, stargate_send_intent, ccip_send_intent
  • Runtime / signer ops: evm_nonce_reservation, worker_heartbeats, signer_rotation_events

Entity Relationship Diagram


1. MovementPlanTemplate

Table name: movement_plan_templates Top-level entity of the movement plan. A single template_key may have multiple versions. PlanTemplateStatus enum values: draft, active, archived Check constraint: a CHECK enforces NOT auto_approve_enabled OR (daily_cap_amount IS NOT NULL AND daily_cap_asset IS NOT NULL) — auto-approve-enabled templates must define a daily cap.

Internal UUID policy

Internal persisted UUID PKs are created via the common helper new_internal_uuid(), and the current implementation uses stdlib uuid.uuid7().
  • DB/API types remain plain UUID
  • Purpose: improved insert locality and near-time-ordered UUID generation
  • Current query ordering semantics still use created_at
  • Using id(uuid7) ordering is a follow-up optimization candidate

2. MovementPlanVersion

Table name: movement_plan_versions Version management unit for a template. Stores node/edge construction, approval/completion policy, and a risk-controls snapshot. Unique constraint: (template_id, version) — the same version number cannot be reused within a template.
The transport_family / source_venue / destination_venue / asset columns are denormalized routing metadata stored at version-compile time for fast filtering and reporting; the authoritative source remains the node graph.

GraphShape enum values

completion_policy JSON structure

CompletionAssurance values:
  • provider_completed — provider (exchange/protocol) reports completion
  • destination_observed — on-chain receive at destination detected
  • destination_credited — destination deposit confirmed
  • destination_finalized — destination chain finality confirmed
  • protocol_finalized — protocol-level finality confirmed

approval_policy JSON structure

Stored and covered by the plan-graph hash, but not read by the runtime. Every movement enters PENDING_APPROVAL regardless of its contents.

risk_controls JSON structure

Free-form and carried whole into the compiled-plan hash. Only max_amount is enforced, as a per-node amount cap at dispatch; min_amount is read for route filtering. Any other key is inert.

3. MovementPlanNode

Table name: movement_plan_nodes Definition of a DAG graph node. Belongs to one plan version and is instantiated as MovementRequestNode at execution time. Unique constraint: (plan_version_id, node_key) — node_key cannot be duplicated within a version.

node_kind values

Defined by the NodeKind enum:

action_type examples

executor_selector JSON structure

The matching criteria used to choose an executor. The resolve_bindings() function binds the actual executor based on this value.

signer_selector JSON structure

Criteria for choosing the signer on nodes that require signing.

config (node_config) JSON structure

When the node executes, this is passed to the executor in the node_config field of ExecutionContext. The contents differ by node kind. CEX withdrawal node config example:
CCTP burn node config example:
Observe node config example:

timeout_policy JSON structure

retry_policy JSON structure


4. MovementPlanEdge

Table name: movement_plan_edges DAG graph edge definition. Manages transition conditions and priority between nodes.

EdgeType enum values


5. MovementRequest

Table name: movement_requests Runtime instance of a movement execution request. References template/version and tracks the full lifecycle.

auto_approve_result (policy trace)

auto_approve_result is always populated as the auto-approve policy trace — a machine-readable record of why the request was or was not auto-approved. Non-evaluated paths still seed a trace with a machine_reason (for example manual_required); the actual approval outcome is unchanged by the trace. This makes the auto-approve decision auditable even when the policy engine did not act.

RequestState state transitions

Terminal states: COMPLETED, FAILED, REJECTED, EXPIRED, CANCELLED

intent JSON structure

Carries the movement intent passed by the caller. Structure varies by template.

input_params JSON structure

Concrete parameters required for execution.

callback_config JSON structure

Notes:
  • If url is present, the runtime requires MG_CALLBACK_HMAC_SECRET and a callback host allowlist.
  • If the allowlist or secret is missing, request creation fails closed.

current_frontier meaning

Array of node_keys for nodes currently executable or executing. The frontier is updated as the DAG progresses.
  • Initial creation: node_key of the root node(s)
  • After a node completes: advance to the successor node’s node_key
  • Fully complete: empty array []

ApprovalStatus / ReservationStatus enum values

ApprovalStatus: pending, approved, rejected ReservationStatus: none, held, consumed, released
  • none — before approval
  • held — resource held after approval
  • consumed — used after normal completion
  • released — released after failure/cancel

6. MovementRequestNode

Table name: movement_request_nodes Runtime instance of MovementPlanNode. Tracks individual node execution state, provider references, and binding info. Unique constraint: (request_id, node_key) — node_key cannot be duplicated within a request.

NodeState state transitions

Terminal states: COMPLETED, FAILED, CANCELLED, SKIPPED

executor_binding JSON structure

signer_binding JSON structure

provider_refs JSON structure

Accumulates provider-side reference info returned by the executor. Keys vary by provider/protocol. CEX withdrawal example:
CCTP burn example:
EVM receive observe example:
provider_ref_id is the representative ID chosen by the select_primary_provider_ref() function from provider_refs by priority.

7. MovementArtifact

Table name: movement_artifacts Stores evidence (prepared action, proof, signed payload, etc.) produced during execution.
Note: in the Python model this is mapped as artifact_metadata, but the DB column name is metadata.

artifact_type values

content_format values

Typically one of json, hex, or base64.

Storage strategy

  • content_inline: small data is stored inline directly
  • content_uri: large data references an external store URI (S3, etc.)
  • Both fields are NULLABLE, but at least one must be present

8. MovementEvent

Table name: movement_events Audit-trail table that records every state transition and major event in chronological order.

event_type values

actor_type / actor_id combinations


9. MovementCallbackOutbox

Table name: movement_callback_outbox Outbox-pattern table that manages callbacks to be delivered to external systems.

CallbackOutboxStatus enum values

Retry strategy

  • On failure, next_attempt_at is set to 2 ** min(attempts, 6) seconds of backoff
  • When max_attempts (default 5) is reached, transition to dlq state
  • The callback_dispatcher worker periodically polls records in pending state
Additional implementation notes:
  • MovementCallbackOutbox.id also follows the internal UUID policy and is thus uuid7-based.
  • enqueue_callback() creates the persisted outbox row id, and mirrors that value as callback_id in the payload.

10. ExecutorRegistryEntry

Table name: executor_registry Persists executor registration info. The executor_health worker refreshes it periodically.

11. SignerRegistryEntry

Table name: signer_registry Persists signer registration info.

12. BalanceSnapshot

Table name: balance_snapshots Append-only store of the latest per-venue balance snapshots. Records Gateway unified USDC balance, CEX, and on-chain balances through the same schema. Indexes
  • ix_balance_snapshot_venue_asset_account_fetched(venue_key, asset, account_type, fetched_at)
  • index on fetched_at — for retention cleanup

Lookup semantics

  • The latest lookup unit is (venue_key, asset, account_type).
  • Latest semantics including tie-breaks: ORDER BY fetched_at DESC, id DESC.
  • Both fresh=true API calls and worker fetches write into the same append-only table.

DB Session settings

Sessions are network-class aware. A single base engine owns the connection pool; per-network engines reuse that pool through execution_options(schema_translate_map={"qtg_network": <network>}), so session_for(network_class)() returns a sessionmaker that auto-tags info["network_class"].
  • pool_pre_ping=True — liveness check when taking a connection from the pool
  • expire_on_commit=False — keep loaded attribute values after commit
  • session_for(network_class) — returns the sessionmaker for one network class; an after_begin listener issues SET LOCAL search_path so unqualified qtg_network tables resolve into the right bucket
  • iterate_per_network(...) — runs a coroutine once per network class (used for registry-sync and heartbeat seeding at startup)
  • public-schema models (api_clients, api_client_keys, nonce_registry) use a separate network-independent auth sessionmaker bound to the base engine
Configuration reference: MG_DATABASE_URL environment variable (default: postgresql+asyncpg://qtg:qtg@localhost:5432/qtg_v3). PostgreSQL is required since v0.1.0; the process network class is selected by the required MG_NETWORK_MODE setting.

JSON field conventions summary