UmbraTrans Datastore Specification
Version: 1.0
Status: Active
Last Updated: 2026-10-31
This document defines the complete datastore contract that every UmbraTrans utility (web API, workers, CLI tools, importers) must understand. It covers PostgreSQL schema, NFS directory layout, and the invariants each component must respect.
1. PostgreSQL Schema (Authoritative Store)
1.1 Core Identity Tables (Phase 1)
| Table |
Purpose |
Key Columns |
public.users |
Canonical accounts |
id (uuid, PK), email (citext, unique, not null), username (citext, unique, nullable), display_name, role, status, organization_id, created_at |
public.user_credentials |
Auth credentials (one per user) |
id (uuid, PK), user_id (uuid, unique, FK), credential_type (local_password/oidc), password_hash (argon2id), must_change_password, failed_login_count, locked_until |
public.sessions |
Server-side sessions |
id (uuid, PK), user_id (FK), token_hash, created_at, last_seen_at, idle_deadline_at, absolute_deadline_at, revoked_at |
public.organizations |
Tenants |
id (uuid, PK), name, slug (citext, unique), status (active/suspended/archived), created_at |
public.user_roles |
Role assignments |
user_id, role_id, scope (organization/project), organization_id, project_id, status |
public.audit_events |
Immutable audit log |
id (bigserial, PK), occurred_at, action, outcome, actor_user_id, target_user_id, organization_id, detail (jsonb) |
1.2 Project & Work Tables (Phase 2+)
| Table |
Purpose |
Key Columns |
tenant_<org>.projects |
Projects (per-tenant schema) |
id (uuid, PK), name, slug, status, created_by, created_at |
public.jobs |
Work queue |
id (uuid, PK), organization_id, project_id, lang, kind, status, priority, requires (jsonb), lease_owner, lease_expires_at, attempt, max_attempts, payload, result, error, created_at, started_at, finished_at |
public.job_events |
Job history |
id (bigserial, PK), job_id, node_id, event, detail (jsonb), at |
1.3 Compute & Node Tables
| Table |
Purpose |
Key Columns |
public.compute_nodes |
Worker nodes |
id (uuid, PK), organization_id, name, is_controller, trust_domain, status (provisioning/online/draining/offline/retired), last_heartbeat_at |
public.compute_devices |
GPUs/CPUs per node |
id (uuid, PK), compute_node_id, kind (cpu/gpu_discrete/...), label, vram_total_mb, vram_free_mb, throughput_factor |
public.model_registry |
Known models |
id (uuid, PK), slug (unique), kind, vram_min_mb, vram_preferred_mb, requires_accelerators |
public.node_models |
Model residency |
compute_node_id, model_id, device_id, state (loaded/warm/available/loading/failed), vram_held_mb |
public.node_heartbeats |
Node health |
compute_node_id (PK), reported_at, cpu_busy_pct, ram_free_mb, vram_free_mb, active_jobs, resident_models (jsonb) |
1.4 Artifact & Provenance Tables
| Table |
Purpose |
Key Columns |
public.artifacts |
Content-addressed artifacts |
id (uuid, PK), project_id, organization_id, lang, kind, status (intermediate/final/superseded), content_hash (sha256), byte_size, media_type, storage_key, storage_uri, storage_node_id, integrity (unverified/verified), verified_at, derivation (jsonb), parent_artifact_id, created_by_job_id, created_by_user_id, created_at |
public.job_artifacts |
Provenance links |
job_id, artifact_id, role (consumed/produced), created_at (PK: job_id, artifact_id, role) |
public.project_source_refs |
Audio import ledger |
project_id, organization_id, system, node_id, external_id, source_title, languages (jsonb), content_hash, imported_at |
1.5 Publisher & Byline Tables
| Table |
Purpose |
Key Columns |
public.publishers |
Imprints/publishers |
id (uuid, PK), parent_id, name, slug (unique), owner_organization_id, kind (imprint/publisher) |
public.project_publishers |
Project→Publisher mapping |
project_id, publisher_id, lang (PK: project_id, publisher_id, lang) |
public.authors |
Bylines (authors, translators, narrators) |
id (uuid, PK), name, display_name, slug (unique), user_id, owner_organization_id |
public.project_authors |
Project→Author mapping |
project_id, author_id, credit_role, lang, position (PK: project_id, author_id, credit_role, lang) |
2. Tenancy Model
2.1 Schema-per-Tenant Isolation
- Platform tables (
users, organizations, jobs, artifacts, compute_nodes, etc.) live in public schema
- Tenant tables (
projects, chapters, utterances, etc.) live in tenant_<org_id> schema
- Isolation mechanism:
SET LOCAL search_path = tenant_<org_id>, public on every transaction
- RLS policies enforce organization membership on all platform tables
2.2 Connection Protocol
Every database operation MUST:
2.3 Helper Functions (Database-Side)
3. NFS Directory Layout (Shared Storage)
3.1 Root Structure
3.2 Path Conventions
| Data Type |
NFS Path |
PostgreSQL Reference |
| Production source |
/mnt/umbra-data/productions/<project>-<lang>/source/book.json |
project_source_refs.book_directory |
| Translation runs |
/mnt/umbra-data/productions/<project>-<lang>/runs/ |
artifacts.storage_uri (file://) |
| Audio renders |
/mnt/umbra-data/audio/<project_id>/<lang>/ |
artifacts.storage_uri (file://) |
| Cover images |
/mnt/umbra-data/covers/<asset_id>/ |
format_metadata.cover_front_asset_id |
| Artifact bytes |
/mnt/umbra-data/artifacts/<org>/<project>/<hash>/data |
artifacts.storage_key |
3.3 Access Patterns
| Component |
Productions |
Artifacts |
Covers |
Audio |
| Web API |
Read |
Read |
Read/Write |
Read |
| Translation Worker |
Read/Write |
Read |
Read |
Read |
| Audio Worker |
Read |
Read/Write |
Read |
Read/Write |
| Import Job |
Read |
Write |
Write |
Write |
4. Invariants & Contracts
4.1 Content Addressing (Artifacts)
- Identity = SHA256 hash of bytes (
content_hash)
- Storage key ≠ Identity —
storage_key is location, content_hash is identity
- Deduplication: Same hash = same artifact (within project scope)
- Immutability:
artifacts table has BEFORE UPDATE trigger preventing content_hash/storage_key changes
- Supersession: New version = new row with
parent_artifact_id pointing to previous
4.2 Job Queue Semantics
- Lease expiration returns job to
queued (handled by controller or job timeout)
- Idempotency key prevents duplicate submission
- Parent job links for decomposition (one job → many sub-jobs)
4.3 Audio Import Idempotency
4.4 Worker Job Contract
Each worker mode polls for specific job kinds:
| Worker Mode |
Job Kinds |
Required Resources |
translation |
translate, analyze, critic, backtranslate |
GPU (LLM), CPU |
audio |
tts, asr, assemble, encode, master |
GPU (TTS), CPU, Audio storage |
5. Component Responsibilities
5.1 Web API (umbratrans-web)
- Reads: Users, orgs, projects, jobs, artifacts, format metadata
- Writes: Users, orgs, projects, format metadata, cover uploads, job queue (enqueue)
- NFS: Reads productions for status, reads/writes covers, reads artifacts for download
- DB: Uses
PostgresAccountRepository + direct queries for project status
5.2 Translation Worker (umbratrans-worker translation)
- Polls:
jobs WHERE kind IN ('translate','analyze','critic','backtranslate')
- Reads: Source text from productions NFS, project metadata from DB
- Writes: Translation artifacts to NFS + DB, job results to DB
- External: Calls Ollama/LLM API for translation
5.3 Audio Worker (umbratrans-worker audio)
- Polls:
jobs WHERE kind LIKE 'audio%'
- Reads: Translation artifacts from NFS, speaker profiles from DB
- Writes: Audio renders to NFS (
/audio/) + artifact rows to DB
- External: Calls TTS/ASR models (local or remote)
5.4 Import CLI (umbratrans-import-productions)
- Reads: Productions from NFS (
/productions/)
- Writes: Audio import tables (
project_source_refs, translation_chapters, translation_speakers, translation_utterances, artifacts, translation_imports)
- NFS: Reads bundle, writes artifacts to
/artifacts/
- Idempotent: Re-runnable, skips unchanged content
5.5 Migration CLI (umbratrans-migrate)
- Applies: All
.sql files in umbratrans_web/migrations/ (except rollback)
- Order: Lexicographic by filename (0001, 0002, ...)
- Transaction: Each migration in its own transaction
- Idempotent: Uses
IF NOT EXISTS, ON CONFLICT, guarded ALTER TABLE
6. Environment Variables
6.1 Required for All Components
| Variable |
Description |
Example |
UMBRA_ENV |
Environment: development/test/staging/production |
production |
UMBRATRANS_DATABASE_DSN |
PostgreSQL connection string |
postgresql://user:pass@host:5432/db?sslmode=require |
UMBRATRANS_PRODUCTIONS_ROOT |
NFS productions mount |
/mnt/productions |
UMBRATRANS_TOKEN_PEPPER |
32+ byte secret for token signing |
base64-encoded |
6.2 Web API Specific
| Variable |
Description |
UMBRATRANS_REPOSITORY |
postgres (production) / memory / json (dev) |
UMBRATRANS_ALLOWED_HOSTS |
Comma-separated host allowlist |
UMBRATRANS_VERIFICATION_BASE_URL |
Absolute URL for email links |
UMBRATRANS_COOKIE_SECURE |
true in production |
UMBRATRANS_REQUIRE_CSRF |
true in production |
UMBRATRANS_SMTP_* |
SMTP configuration |
UMBRATRANS_STATIC_ROOT |
Static files path (/app/web) |
6.3 Worker Specific
| Variable |
Description |
OLLAMA_BASE_URL |
LLM API endpoint (http://ollama:11434/v1) |
UMBRATRANS_WORKER_POLL_INTERVAL |
Queue poll seconds (default: 5) |
7. Kubernetes Deployment Contract
7.1 Required Resources
| Component |
CPU |
Memory |
GPU |
NFS Mounts |
| Web API |
250m-1000m |
256-512Mi |
No |
productions (ro), artifacts (rw) |
| Translation Worker |
2000m-4000m |
2-4Gi |
1x GPU |
productions (rw), artifacts (rw) |
| Audio Worker |
2000m-4000m |
4-8Gi |
1x GPU |
productions (ro), artifacts (rw), audio (rw) |
7.2 Service Discovery
| Service |
DNS |
Port |
Purpose |
umbratrans-web |
umbratrans-web.umbratrans.svc |
80 |
Web API + UI |
umbratrans-db-rw |
umbratrans-db-rw.umbratrans.svc |
5432 |
Primary DB (writes) |
umbratrans-db-ro |
umbratrans-db-ro.umbratrans.svc |
5432 |
Replica DB (reads) |
umbratrans-db-pgbouncer |
umbratrans-db-pgbouncer.umbratrans.svc |
6432 |
Pooled connections |
ollama |
ollama.umbratrans.svc |
11434 |
LLM API |
7.3 Health Checks
Health endpoint returns:
8. Backup & Recovery
8.1 PostgreSQL (CNPG)
- Continuous: WAL archiving to S3
- Scheduled: Daily base backup at 02:00 UTC (retention: 30 days)
- Point-in-time recovery: Supported via
barman-cloud-wal-archive
- Test: Monthly restore to staging
8.2 NFS Data
- Productions: Rsync to offsite daily
- Artifacts: Content-addressed, reproducible from source
- Covers: Reproducible from source projects
8.3 Recovery Procedures
| Scenario |
Procedure |
| Web pod crash |
Kubernetes restarts (liveness probe) |
| Worker pod crash |
Kubernetes restarts, job lease expires, re-queued |
| DB primary failover |
CNPG promotes replica (30-60s), connections retry |
| NFS server down |
Workers pause (NFS hard mount), resume on recovery |
| Data corruption |
Restore PG from backup, re-import productions |
9. Security Boundaries
| Boundary |
Enforcement |
| Tenant isolation |
search_path + RLS policies on all platform tables |
| API authentication |
Argon2id password hash, Secure/HttpOnly/SameSite cookies, CSRF tokens |
| Session management |
Server-side sessions with token hash only, idle/absolute timeouts |
| Worker auth |
Node credentials (argon2id key hash), scoped to queue operations |
| NFS access |
Kubernetes fsGroup=1000, NFS no_root_squash, directory mode 0700 |
| Database credentials |
Stored in Kubernetes Secrets, never in images or configmaps |
| Token pepper |
32+ bytes, in Secret, used for session/verification token HMAC |
10. Version Compatibility
| Component |
Minimum Schema Version |
Compatible With |
| Web API 0.2.x |
Migrations 0001-0010 |
Workers 0.2.x, Import 0.2.x |
| Workers 0.2.x |
Migrations 0001-0010 |
Web API 0.2.x |
| Import CLI 0.2.x |
Migrations 0001-0010 |
Web API 0.2.x |
Breaking changes require: New migration file, version bump, coordinated deploy.
11. Quick Reference: Key SQL Queries
12. Appendix: Migration Order
Apply with: kubectl apply -k k8s/overlays/prod (includes migration init container)
This specification is the source of truth for all datastore interactions. Any component that reads or writes data must conform to these contracts.