Spec: Data-Split Routing Policy — PostgreSQL vs Telha vs Blob¶
File: .ai/specs/integration/2026-07-02-data-split-routing.md Status: Draft Owners: App-layer lane; reviewers: storage lane Related: PR-028; PRD v8 §13.1; field-encryption spec (classification interacts with routing)
1. Overview¶
Makes the PRD's data-split table a normative, reviewable policy: which data goes to PostgreSQL (MikroORM), which to Telha Core, which to blob storage — with decision tests, worked examples per module, anti-patterns, and how routing is enforced in review.
2. Problem Statement¶
"Use the right engine" decays into inconsistency without a written test: cognitive data leaks into PostgreSQL (losing temporality and provenance), commodity CRUD leaks into Telha (paying tritemporal cost for a user-preferences row), and six months in, nobody can say why a given entity lives where it does. Routing mistakes are expensive to reverse because they change query patterns, not just storage.
3. Proposed Solution¶
A three-question routing test applied to every new entity/record type, documented in the module's code next to its models, checked in PR review:
- Does history itself carry business meaning? (Will anyone ask "what was true on date X" / "what changed between X and Y"?) → yes ⇒ Telha.
- Does it need graph traversal or temporal-semantic search? → yes ⇒ Telha.
- Is it relational CRUD serving app mechanics (joins, uniqueness constraints, transactions with other commodity rows)? → yes ⇒ PostgreSQL. Bytes >64KB or binary ⇒ blob store, with a Telha/PG record holding the reference. Ties (rare) default to PostgreSQL — promotion into Telha later is an ingest; demotion out of Telha loses history and is effectively forbidden.
4. Architecture¶
Open Mercato module command handler
├─ commodity write ──▶ MikroORM em.persistAndFlush ──▶ PostgreSQL (tenant entity filters)
├─ cognitive write ──▶ @telha/sdk (GrpcTransport/UDS) ──▶ Telha Core (32B key prefix)
└─ blob ────────────▶ storage-s3 (pre-signed URLs); reference stored with its owning record
Cross-links: PG rows may hold telha logical_ids; Telha properties may hold PG row ids —
both are soft references (no cross-engine FK), resolved at the application layer.
5. Data Models¶
Normative routing table:
| Data | Target | Why |
|---|---|---|
| Users, accounts, billing, orders, inventory counts, sessions, settings | PostgreSQL | Relational CRUD, constraints, joins; history is audit-log-grade at most |
| Contract/version timelines, risk evidence, audit trails, org-knowledge entities, anything answered "as of T" | Telha | Tritemporal queries, traversal, provenance |
| Embeddings + semantic search | Telha (vectors CF + HNSW) | Temporal-aware similarity |
| PDFs, images, attachments | S3/local blob | Blob semantics; Telha sources records hold hash + reference |
| Encrypted classified fields | Wherever their record routes | Routing is orthogonal to classification; encrypted fields are opaque either way (field-encryption spec) |
Worked example (contracts module, PR-028 commit 4): Order entity → PostgreSQL; CONTRACT_VERSION with AMENDS relationships → Telha; contract PDF → S3 with Telha source record.
6. API Contracts¶
App-layer convention (enforced by DI): modules obtain the Telha client only via the registered telha module (never construct transports ad-hoc), which guarantees tenant-context propagation from the request session into signed gRPC metadata (grpc-contracts spec). Each module declares its routed types in a ROUTING.md next to its entities — the review artifact.
7. UI/UX¶
Not applicable to end users. Developer UX: the example contracts module is the copyable pattern; module generator templates (Open Mercato convention) scaffold ROUTING.md with the three-question test pre-filled.
8. Configuration¶
None at runtime — routing is a design-time decision. Deliberately NOT configurable: no "storage backend" flag per entity; ambient switchability would defeat the policy's reviewability.
9. Alternatives Considered¶
Everything in Telha (rejected: commodity CRUD pays tritemporal write amplification and loses relational constraints/joins for no benefit). Everything in PostgreSQL with temporal tables (rejected: bitemporal SQL at this query shape — traversal + vectors + span provenance — is the exact deep-tech problem Telha exists to solve). Automatic routing by heuristic (rejected: routing errors are expensive and silent; a human-reviewed three-question test is cheap and auditable). Hard cross-engine FKs via 2PC (rejected: distributed transactions across PG+RocksDB for soft references is complexity without a driving invariant).
10. Implementation Approach¶
PR-028: this spec → telha DI module with tenant propagation → contracts example module (both stores + blob reference) → demo page → two-tenant isolation integration test. ROUTING.md template added to module scaffolding.
11. Migration Path¶
Greenfield for Telha-side data. Promotion path (PG → Telha) is a defined ingest: rows become records with sourceRecordTime = original row timestamps, provenance Origin::Ingestion. Demotion is forbidden by policy (history loss); exceptions require a spec.
12. Success Metrics¶
Every module with persistent types carries ROUTING.md (review checklist item; spot-checked in CI by file-presence lint). The contracts example passes its e2e (order in PG, versions in Telha, PDF in blob, cross-links resolve). Zero cognitive-type MikroORM entities added without a routing justification during Phase 2 review (audit at phase gate).
13. Open Questions¶
- OQ-1: Should the file-presence lint parse ROUTING.md for the three answers rather than mere existence? Stance: existence-only in v1; content review stays human.
14. Changelog¶
- 2026-07-02 — v0.1: initial draft for peer review.