Edictum
Edictum ConsoleReference

Database

PostgreSQL data model with 16 tables, monthly event partitioning, Alembic migrations, and tenant isolation on every query.

AI Assistance

Right page if: you need to understand the PostgreSQL data model (16 tables), debug a query, write a migration, or plan a backup strategy for Edictum Console. Wrong page if: you need the API endpoints that read/write this data -- see https://docs.edictum.ai/docs/console/reference/api. Gotcha: the events table is range-partitioned by month. Purging drops entire partitions, not individual rows. If you lose EDICTUM_SIGNING_KEY_SECRET, all encrypted private keys and notification secrets become unrecoverable.

Edictum Console uses PostgreSQL for persistent storage with SQLAlchemy 2.0 async ORM and Alembic for schema migrations.

Tables

Core

TablePurposeKey Columns
tenantsCustomer organizations. Single tenant is the default UX, but the data model supports multiple.id, name, external_auth_id, created_at
usersLocal user accounts. Email/bcrypt password.id, tenant_id, email, password_hash, is_admin
api_keysbcrypt-hashed API keys with environment scope. Full key shown only at creation.id, tenant_id, key_prefix, key_hash, env, label, revoked_at
signing_keysEd25519 keypairs. Private key encrypted at rest with NaCl SecretBox. One active key per tenant.id, tenant_id, public_key (bytes), private_key_encrypted (bytes), active

Contracts and Bundles

TablePurposeKey Columns
contractsVersioned individual contracts in the library. Each update creates a new version row.id, tenant_id, contract_id (stable ID), version, type, name, definition (JSON), tags (JSON), is_latest, created_by
bundle_compositionsComposition recipes -- ordered lists of contracts with mode overrides.id, tenant_id, name (unique per tenant), description, defaults_mode, update_strategy, tools_config (JSON), observability (JSON)
bundle_composition_itemsContract membership within a composition. Position determines order.id, tenant_id, composition_id, contract_id, position, mode_override, enabled
bundlesVersioned contract bundles (compiled YAML + Ed25519 signature).id, tenant_id, name, version, revision_hash, yaml_bytes, signature (bytes), composition_id, composition_snapshot (JSON), uploaded_by, source_hub_slug (nullable), source_hub_revision (nullable)
deploymentsBundle deployment records -- which version is active for each environment.id, tenant_id, env, bundle_name, bundle_version, deployed_by, created_at

Unique constraints:

  • bundles: (tenant_id, name, version)
  • contracts: (tenant_id, contract_id, version)
  • bundle_compositions: (tenant_id, name)
  • bundle_composition_items: (composition_id, contract_id)

Events and Approvals

TablePurposeKey Columns
eventsAudit events from agent tool calls. Partitioned by month.id, tenant_id, call_id, agent_id, tool_name, verdict, mode, env, timestamp, payload (JSON)
approvalsHITL approval requests with state machine (pending -> approved/denied/timeout).id, tenant_id, agent_id, tool_name, tool_args (JSON), message, status, env, timeout_seconds, timeout_effect, decision_source, contract_name, decided_by, decided_at, decision_reason, decided_via

Events deduplication: unique constraint on (tenant_id, call_id, created_at). Duplicate call_id values within the same tenant are silently ignored on ingest.

Notifications

TablePurposeKey Columns
notification_channelsChannel configurations with encrypted secrets.id, tenant_id, name, channel_type, config (JSON, nullable -- pre-migration rows), config_encrypted (bytes), enabled, filters (JSON), last_test_at, last_test_ok

Channel types: telegram, slack, slack_app, discord, webhook, email.

Agents and Assignment

TablePurposeKey Columns
agent_registrationsPersistent agent identities. Auto-created on first SSE connection.id, tenant_id, agent_id (unique per tenant), display_name, tags (JSON), bundle_name, last_seen_at
assignment_rulesPattern-based bundle assignment rules, priority-ordered.id, tenant_id, priority (unique per tenant), pattern, tag_match (JSON), bundle_name, env

AI

TablePurposeKey Columns
tenant_ai_configsPer-tenant AI provider configuration (Anthropic, OpenAI, OpenRouter, Ollama).id, tenant_id, provider, api_key_encrypted (bytes), model, base_url
ai_usage_logsToken usage and cost tracking per AI request.id, tenant_id, provider, model, input_tokens, output_tokens, total_tokens, duration_ms, estimated_cost_usd, request_type

Events Partitioning

The events table is PostgreSQL range-partitioned by created_at (monthly partitions).

events (parent table)
+- events_2026_01
+- events_2026_02
+- events_2026_03
+- ...

Partition management: A background worker runs every 24 hours and ensures partitions exist for the next 3 months. Partitions are created with:

CREATE TABLE events_YYYY_MM PARTITION OF events
  FOR VALUES FROM ('YYYY-MM-01') TO ('YYYY-MM+1-01');

Purging: The settings danger zone allows purging events older than 30, 60, or 90 days. This drops entire monthly partitions for efficiency rather than row-by-row deletion.


Migrations

Alembic manages schema changes. Migrations run automatically on server startup.

RevisionDescription
001Initial schema: tenants, users, api_keys, signing_keys, bundles, deployments, events, approvals, notification_channels
002Add env column to events, add timestamp index
003Add composable contracts: contracts, bundle_compositions, bundle_composition_items tables
004Add agent management: agent_registrations, assignment_rules tables
005Add AI assistant: tenant_ai_configs, ai_usage_logs tables
006Encrypt notification channel config: add config_encrypted column

Running Migrations Manually

Migrations run on startup, but you can also run them manually:

# Inside the container
alembic upgrade head

# Check current revision
alembic current

Tenant Isolation

Every table with user data has a tenant_id column. Every database query filters by tenant_id from the authenticated context. There are no admin-sees-all queries. This is enforced at the service layer -- routes extract tenant_id from the auth context and pass it to every service call.

The tenant_id is resolved from:

  • API key auth: tenant_id stored on the API key row
  • Dashboard auth: tenant_id stored in the Redis session

Backup Recommendations

  1. PostgreSQL: Use pg_dump or continuous archiving (WAL). The events table is the largest -- consider backing up partitions individually.
  2. Redis: Sessions are ephemeral (TTL-based). Loss of Redis data means users must re-login. No critical data is Redis-only.
  3. Encryption keys: Back up EDICTUM_SIGNING_KEY_SECRET. Without it, encrypted private keys and notification secrets cannot be decrypted.

Last updated on

On this page