Skip to content

Telha: Unified AI-Native Application Engine

Product Requirements Document — v8 (Consolidated)

Supersedes: v7 (GraphKV_Stripped_PRD_task-02f.md) Status: Approved for build — all design flags resolved and ratified 2026-07-02 Companion documents: - telha-build-doc.md — PR/commit-level build plan, conventions, phase gates, risk register - .ai/specs/core-engine/2026-07-02-composite-key-layout.md (v0.2, review-ready) - .ai/specs/core-engine/2026-07-02-tombstone-cascade.md (v0.1, review-ready) - PR-002 harness package (spec template, linter, CI gate — built & tested)

v8 changelog vs v7: incorporates ten ratified design resolutions (F1–F10) and one resolved open question (OQ-3); corrects two claims for tritemporal soundness (tenant-isolation wording, cascade semantics); specifies previously-implicit designs (key byte layouts, cascade journal, HNSW partition lifecycle, nil-tenant namespace, encryption cipher and envelope, bitmap accelerator scoping); replaces AES-GCM with XChaCha20-Poly1305; adds the Roaring Bitmap accelerator as a discrete post-baseline deliverable (PR-022b).


1. Executive Summary

A unified monorepo combining a single-process Rust tritemporal graph + vector engine (Telha Core) with an enterprise full-stack TypeScript application layer (Open Mercato). One binary for deep-tech storage, one framework for application logic — connected via local gRPC/UDS. A spec-first AI harness governs all changes: every design decision lives in .ai/specs/ before code, enforced by CI, and is readable by the system's own MCP server for self-verification.

The product promise: trustworthy time travel over organizational knowledge. Every fact carries three time axes, every answer carries provenance down to source spans, every generated claim is verified against evidence, and tenant isolation holds by construction at the physical storage layer.

2. Goals and Non-Goals

Goals (v1): - Tritemporal (validTime / sourceRecordTime / transactionTime) property graph with immutable, append-only versioning and correct historical snapshots in both temporal directions. - Tenant isolation true-by-construction at the key-byte level, not merely policy-enforced. - Temporal-aware semantic search (partitioned HNSW over versioned embeddings). - Multi-format ingestion (rich docs, tabular, web, JSON, code, email) with span-level provenance. - Verified generation: memory planning → LLM generation → claim-level verification → full memory trace. - Non-technical operator access to all of the above via MCP tools in a chat interface. - Continuous, CI-gated verification of every performance and correctness claim.

Non-goals (v1): - Distributed storage (FoundationDB backend is Phase 4, enabled by the StorageEngine trait). - Hard-delete semantics beyond GDPR erasure (which requires its own spec before GA — see Open Questions register, §14). - Rewriting Open Mercato in Rust; it is absorbed as-is and connected at the data boundary. - FlatBuffers serialization (deferred; rmp-serde is the v1 standard — decision F9).

3. System Architecture

3.1 Monorepo Topology

/telha-workspace
  ├── /core-engine                <-- Single-process Rust binary (Telha Core)
  │     ├── /src/storage          <-- RocksDB, KeyCodec (sole key authority), composite key mappings
  │     ├── /src/graph            <-- Graph engine, adjacency indexing, bounded traversal
  │     ├── /src/temporal         <-- Tritemporal engine, snapshot logic, tombstone cascade
  │     ├── /src/vector           <-- Partitioned HNSW, temporal-versioned embeddings
  │     ├── /src/query            <-- Serde-first JSON query AST + scan executor (+ bitmap accelerator, PR-022b)
  │     ├── /src/schema           <-- Schema inference engine (type lattice, versioned)
  │     ├── /src/api              <-- Axum REST server
  │     ├── /src/grpc             <-- Tonic gRPC server (WorkerService, AppLayerService), UDS
  │     ├── /src/mcp              <-- Integrated MCP server (rmcp)
  │     ├── /src/planner          <-- Memory planner + LlmProvider + claim verification
  │     ├── /src/jobs             <-- Job queue with leases, reaper, cascade journal
  │     └── Cargo.toml
  ├── /app-layer                  <-- Open Mercato full-stack framework
  │     ├── /apps/mercato         <-- Next.js web application (incl. chat + trace viewer)
  │     ├── /packages/core        <-- MikroORM modules, CRUD, events, telha module (gRPC/UDS client)
  │     ├── /packages/enterprise  <-- Multi-tenancy, field-crypto (XChaCha20-Poly1305), compliance
  │     ├── /packages/ai-assistant <-- AI assistant with MCP client, tool RBAC, audit log
  │     ├── /packages/ui          <-- Design system + frontend components
  │     ├── /packages/queue       <-- BullMQ job processing (incl. key-rotation job)
  │     ├── /packages/events      <-- SSE + DOM Event Bridge
  │     ├── /packages/search      <-- Search indexing
  │     └── package.json          <-- Yarn workspaces, Turborepo
  ├── /workers                    <-- Python ingestion workers (stateless gRPC clients)
  │     ├── /workers_common       <-- shared gRPC/lease/heartbeat/shutdown harness
  │     ├── /docling_worker       <-- Rich document parsing
  │     ├── /firecrawl_worker     <-- Web/SPA crawling (swappable Fetcher: Firecrawl | Playwright)
  │     ├── /code_worker          <-- Tree-sitter AST parsing
  │     ├── /tabular_worker       <-- Tabular-to-graph projection
  │     └── /email_worker         <-- Envelope parsing + attachment fan-out (Phase 3)
  ├── /sdk
  │     ├── /typescript           <-- @telha/sdk (dual transport: REST external, gRPC/UDS internal)
  │     └── /python               <-- telha (REST, pydantic-typed)
  ├── /proto                      <-- Protobuf contracts (buf-linted, breaking-change-gated)
  ├── /tooling
  │     └── /spec-lint            <-- spec-first linter (RULE-01..11)
  └── .ai/specs/                  <-- Unified spec-first design manuals (MCP-readable)
        ├── _TEMPLATE.md          <-- mandatory 14-section structure
        ├── /core-engine
        ├── /app-layer
        └── /integration

AI coding agents read full full-stack context from one repository; the spec directory is the design memory, exposed as an MCP resource so the running system can cross-verify its behavior against its own manuals.

3.2 Baseline Production Safety Pipeline

     [ Inbound Agent / Long-Context API Request ]
                          |
                          v
         Open Mercato Application Layer
    (Auth, RBAC, Field Encryption, Tenant Scoping, Billing)
                          |
                  (Local gRPC / UDS)
                          |
                          v
               Tritemporal Filter Layer
    (Bounded range scan + end-boundary validation,
     tenant-scoped prefix isolation via KeyCodec)
                          |
                          v
               Graph Relevance Engine
    (Multi-hop confidence decay, bounded traversals)
                          |
                          v
               Vector Semantic Scoring
    (Partitioned HNSW against valid time-partitions)
                          |
                          v
               Evidence Assembly + Budget
    (Truth/cache separation, token budget packing)
                          |
                          v
               Cost / Degraded Optimization
    (Prefix-cache-aware prompt, span trade-offs)
                          |
                          v
               [ Hot Model Generation via LlmProvider ]
                          |
                          v
               Claim Verification Pipeline
    (Atomic claims -> source-span matching -> scoring)
                          |
                          v
     [ Verified Response + Memory Trace (persisted, queryable) ]

4. Telha Core — Storage Engine (Task 2)

4.1 Primitive encodings (normative — see composite-key-layout spec)

Primitive Encoding Rationale
tenantId, organizationId, logicalId UUIDv7, 16 raw bytes v7 time-ordering improves RocksDB write locality
Timestamps u64 microseconds since epoch, big-endian Byte order == numeric order
Inverted timestamps u64::MAX − ts, big-endian Descending sort under ascending comparator → latest-first iteration
edge_type_hash xxh3_64 of UTF-8 type string, 8 bytes BE Fixed width; collision registry in schema CF, fail-loud

Every key begins with the 32-byte tenant prefix [tenantId (16)][organizationId (16)].

Reserved system namespace (ratified OQ-3): the all-zero prefix [0u8; 32] is the system/nil-tenant scope (initially: system jobs). It preserves fixed-width guarantees, sorts to the head of each CF for fast system polling, and requires zero parsing exceptions. It is unforgeable externally: TenantScope::new() rejects nil components, tenant provisioning independently rejects nil UUIDs, no auth path can resolve any credential to the nil scope, and only the internal pub(crate) SystemScope constructor can emit it.

4.2 Key layouts per column family (fixed-width, normative)

node_versions     [32B scope][logicalId 16][inv(validTimeStart) 8][inv(txTimeStart) 8]                       = 64B
edge_versions     [32B scope][edgeLogicalId 16][inv(validTimeStart) 8][inv(txTimeStart) 8]                   = 64B
adjacency_out     [32B scope][fromId 16][edgeTypeHash 8][inv(validTimeStart) 8][inv(txTimeStart) 8][toId 16] = 88B
adjacency_in      [32B scope][toId 16][edgeTypeHash 8][inv(validTimeStart) 8][inv(txTimeStart) 8][fromId 16] = 88B
valid_time_index  [32B scope][inv(validTimeStart) 8][inv(txTimeStart) 8][logicalId 16][kind 1]               = 65B
tx_time_index     [32B scope][inv(txTimeStart) 8][inv(validTimeStart) 8][logicalId 16][kind 1]               = 65B
vectors           [32B scope][nodeLogicalId 16][embeddingModelId 8][inv(validTimeStart) 8][inv(txTimeStart) 8] = 72B
schema            [32B scope][labelHash 8][inv(txTimeStart) 8]                                               = 48B
sources           [32B scope][sourceId 16][inv(txTimeStart) 8]                                               = 56B
spans             [32B scope][sourceId 16][spanStart 8][spanEnd 8]                                           = 64B
traces            [32B scope][queryId 16]                                                                    = 48B
jobs              [32B scope][state 1][priority 1][createdAt 8][jobId 16]   (createdAt NOT inverted: FIFO)   = 58B
bitmap_index      (PR-022b, derived) posting lists per (scope, label, property, value-bucket)
  • Values: MessagePack (rmp-serde) with a leading version byte for forward-compatible evolution.
  • Secondary-index values are the primary-CF key (covering indexes) — validation is a point lookup, never a scan.
  • The kind byte (node 0x00 / edge 0x01) lets one temporal index serve both version CFs.
  • All keys fixed-width; any future variable-length component requires spec amendment first.

4.3 Tenant isolation by construction (ratified F1)

Prefix bloom filters are a performance structure, not a security boundary. Isolation is therefore enforced structurally:

  1. A single KeyCodec module is the exclusive location for key encoding; raw-byte constructors are pub(crate)-hidden within the storage module.
  2. ScanRange is the only way to obtain iterator bounds; every constructor derives [prefix, successor-within-prefix) from a TenantScope — no public raw-bound API exists.
  3. CI grep-gate rejects any iterator(/raw_iterator usage outside core-engine/src/storage.
  4. Property tests assert every generated scan range lies inside its tenant prefix (including the all-0xFF successor carry case); a fuzz target hardens the decoder.
  5. Integration test: multi-tenant dataset, zero cross-prefix rows across the full access-pattern matrix; nil-scope unreachability from every external path (REST, gRPC, MCP).

RocksDB per-CF configuration: prefix_extractor = fixed(32), memtable prefix bloom ratio 0.1, whole-key blooms on point-lookup CFs (versions, vectors) and off on scan-heavy CFs (adjacency, temporal indexes).

4.4 Storage trait for future distribution

trait StorageEngine: Send + Sync {
    fn get(&self, cf: &str, key: &[u8]) -> Result<Option<Vec<u8>>>;
    fn put(&self, cf: &str, key: &[u8], value: &[u8]) -> Result<()>;
    fn delete(&self, cf: &str, key: &[u8]) -> Result<()>;
    fn scan_prefix(&self, cf: &str, prefix: &[u8]) -> Result<Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)>>>;
    fn batch_write(&self, ops: Vec<WriteOp>) -> Result<()>;
    fn snapshot(&self) -> Result<Box<dyn StorageSnapshot>>;
}

A trait-conformance test suite (PR-012) is the acceptance gate for the Phase 4 FoundationDB backend (which will additionally require chunking for FDB's 100KB value / 10MB transaction limits).

4.5 Temporal query strategies

  • Entity-scoped: prefix scan on [scope][logicalId] — latest-first by construction.
  • Global cross-cutting ("everything valid at T within tenant"): bounded range scan on valid_time_index from [scope][inv(T)], iterating forward, validating validTimeEnd via covering-index point-lookup, terminating at horizon boundary or result limit. Scan telemetry (rows scanned vs returned) is exported for selectivity monitoring.

5. Graph Engine (Task 3)

Adjacency key layouts per §4.2. Traversal semantics (normative, PR-018 spec):

  • expand_one_hop(scope, node, edge_type?, direction, as_of) — bounded adjacency range scan + edge_versions end-boundary validation.
  • Multi-hop BFS, depth 1–3, visited-set on logicalId, per-hop and total fan-out caps.
  • Traversal budget guard: a hard row-scan ceiling returns partial-with-warning rather than unbounded work.
  • Temporal correctness: a snapshot at T never sees edges created after T or tombstoned before T (tested in both directions).

Performance target (ratified F6): sub-millisecond 1–3 hop traversals on 10M+ nodes is a continuously verified benchmark gate (Criterion suite, synthetic 10M-node dataset, nightly CI with >20% p50 regression failure), not an assertion.


6. Tritemporal Engine (Task 4)

Three time axes: validTime (when true in the world), sourceRecordTime (when the source recorded it), transactionTime (when Telha learned it). Immutable append-only versioning; transaction time is assigned by a monotonic hybrid clock with per-logicalId total order.

6.1 Tombstone referential cascade (ratified F3, corrected — normative)

The cascade is strictly append-only. tombstone_node produces one WriteBatch containing only puts:

  1. the node tombstone version (validTime capped, tombstone: true),
  2. an edge tombstone version for every live edge (discovered via both adjacency CFs against a consistent snapshot),
  3. the new adjacency_out/adjacency_in entries for each edge tombstone version,
  4. valid_time_index + tx_time_index entries for every new version above.

Nothing is ever deleted. Deleting adjacency or index rows would blind queries at coordinates before the tombstone — destroying backward history, the failure class most dangerous to a time-travel product because it is invisible to current-state tests. Readers determine liveness exclusively via end-boundary validation, never via row absence.

Ghost-edge invariants (CI-gated): - I1 (forward): at any coordinate at/after the cascade, no query or traversal returns the node or cascaded edges. - I2 (backward): at any coordinate strictly before the cascade, results are byte-identical to recorded pre-cascade query results. - I3 (atomicity): no observable coordinate exists where the node is tombstoned but any of its edges is live.

Large cascades (cascade journal): when the write-set exceeds max_batch_ops (default 100,000 ops), a journal entry is written (WAL-synced) to the jobs CF, batches apply in chunks with the node tombstone in the final chunk — so a crash at any chunk leaves the node visibly alive with no invariant broken, and the reaper deterministically resumes the journal on restart. Chunk replay is idempotent (byte-identical puts). Cascade batches always use sync=true WAL regardless of global policy: a user-visible destruction event must survive power loss the instant it is acknowledged.

Edge tombstone versions carry cascade_of: <node-tombstone-id> provenance metadata for audit.


7. Vector Index — Temporal-Versioned + Partitioned HNSW (Task 5)

7.1 Source of truth doctrine (ratified F4)

The RocksDB vectors CF is the absolute source of truth (key layout §4.2; values are raw little-endian f32 slices; dimension validated against a model registry in the schema CF — mismatch is a hard error).

HNSW indexes are derived, ephemeral structures: one usearch index per (tenant, embeddingModel, time-bucket), persisted as usearch save-files under data_dir/vectors/, loaded from manifests at startup. Corruption, a missing file, or a bucket-boundary reconfiguration triggers automatic rebuild from the CF. An admin subcommand (telha vector rebuild) forces it.

7.2 Partitioning & query semantics

  • Bucketing by validTimeStart (monthly default, configurable); partitions are open (accepting writes) or sealed (persisted).
  • Every vector in a queried partition is temporally valid by construction of the bucket; queries whose temporal window spans multiple buckets search each overlapping partition and merge by score, with temporal end-boundary validation on survivors.
  • Recall gate: ≥ 0.95@10 vs brute force on a 100k-vector corpus; rebuild-equivalence test guarantees derived-index doctrine holds.

7.3 Embedding production

EmbeddingProvider trait (HTTP: OpenAI-compatible + local endpoint). Post-ingestion embed jobs fan out for new spans/nodes; content changes produce new temporal vector versions, never overwrites. Classified (encrypted) fields are never embedded (§12).


8. Query Engine (Task 6)

8.1 Language (Serde-first AST — ratified F10 part 1)

JSON, parsed directly into serde types — no custom parser; the compiler owns validation. Surface: find (label) · where ($eq $ne $gt $gte $lt $lte $in $exists $and $or) · atValidTime / atTxTime · expand (edge types, direction, depth ≤ 3) · vector ({near | text, model, k, minScore}) · limit / opaque cursor · trace flag. Unknown operators are rejected with exact error messages; the parser fixture corpus is shared with the SDKs as contract tests.

8.2 Execution pipeline

temporal resolution → planner chooses label-scoped prefix scan | global temporal index scan → predicate filter → graph expansion with confidence decay (per-hop multiplicative, per-edge-type weights, default 0.7, floor cutoff — full math peer-reviewed in its spec before code, per F7) → vector scoring & score fusion (similarity × decay, formula spec'd) → pagination (stable last-key cursors) → trace output (per-stage row counts, keys touched, timings; persisted to traces CF when requested).

8.3 Roaring Bitmap accelerator (ratified F10 part 2 — discrete deliverable PR-022b)

Bitmaps answer "which rows match P," not "which rows matched P at time T" — so they are never a correctness path. Design: dense u32 row-ids per (tenant, label); roaring posting lists per (tenant, label, property, value-bucket) in a bitmap_index CF maintained in the same WriteBatch as writes; nested \(and/\)or trees resolve via croaring intersections to a candidate set, every survivor of which undergoes mandatory temporal end-boundary validation before emission. Planner cost rules choose bitmap-vs-scan. Bitmaps are derived state (rebuildable, F4 doctrine). The non-negotiable gate: a differential test proving bitmap-path results identical to scan-path results on randomized temporal datasets. Sequenced after the benchmark harness identifies real predicate-heavy hotspots; the scan executor remains the correctness baseline it must beat.


9. Schema Inference (Task 7)

Zero-schema ingest: any JSON accepted. Per-(tenant, label) property types observed on every write and merged through a type lattice (null < bool/int/float/string/datetime/array/object < any); a new schema version is written only on change. schema_as_of(scope, T) provides introspection at any time T. Conflicting-type sequences produce documented lattice results (fixture-tested).


10. API Server (Task 8)

10.1 REST (Axum) — external clients & SDKs

POST   /v1/records              — create record(s) with temporal metadata + relationships
GET    /v1/records/:id          — current version
GET    /v1/records/:id/history  — all versions (paginated)
DELETE /v1/records/:id?validEnd=T — temporal tombstone (append-only cascade; no hard delete exists)
POST   /v1/query                — temporal + graph + vector DSL
POST   /v1/snapshot             — graph at a point in time
POST   /v1/compare              — temporal snapshot delta (Phase 3)
POST   /v1/ingest               — async ingestion (multipart/URL/JSON + format hint) → jobId
GET    /v1/ingest/:jobId        — job status
POST   /v1/generate             — memory planning + generation + verification (Phase 3; SSE variant)
POST   /v1/relationships        — edge create/tombstone
GET    /v1/schema?at=T          — schema introspection
GET    /v1/trace/:queryId       — memory trace
GET    /v1/openapi.json         — generated OpenAPI (SDK contract source)

Auth: API-key middleware → TenantScope; handlers receive scope from auth context only — no handler ever parses tenancy from user input. Uniform error envelope {code, message, request_id}. Tower timeouts + body limits; request-id middleware; /healthz, /readyz.

10.2 gRPC (Tonic) — internal (app layer + workers)

service AppLayerService {
    rpc CreateRecords(CreateRecordsRequest) returns (CreateRecordsResponse);
    rpc Query(QueryRequest) returns (QueryResponse);
    rpc Snapshot(SnapshotRequest) returns (SnapshotResponse);
    rpc Compare(CompareRequest) returns (CompareResponse);
    rpc BatchWrite(BatchWriteRequest) returns (BatchWriteResponse);
}
service WorkerService {
    rpc PollJobs(PollJobsRequest) returns (stream JobPayload);
    rpc SubmitIngestionResult(IngestionResult) returns (SubmitResponse);
    rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
}

Transport: Unix Domain Socket on Linux (config-selected; localhost TCP fallback for non-UDS dev platforms) — zero network overhead, clean process boundary. Tenancy: gRPC metadata carries tenant identifiers plus a signed internal token validated by an interceptor; a raw tenant header alone is never trusted. Deadlines/cancellation propagate. Contracts live in /proto, buf-linted with breaking-change detection in CI. Worker auth: per-worker tokens scoped to WorkerService only.


11. Multi-Format Ingestion Pipeline (Task 9)

Six format-aware strategies converging into SubmitIngestionResult:

Format Parser Strategy
Rich docs (PDF, DOCX, PPTX) IBM Docling Layout AI → hierarchical spans; document→sections→paragraphs nodes + CONTAINS edges; failure taxonomy (corrupt / unsupported / OOM)
Tabular (CSV, XLSX) Polars Rows→nodes, columns→schema, FK-like cross-refs→edges; chunked streaming (1M-row memory ceiling tested)
Web/SPA Swappable Fetcher trait (ratified F5): Firecrawl or built-in Playwright fallback Headless render → Markdown; headings→hierarchy, links→REFERENCES edges. Playwright fallback ships out of the box, bypassing the AGPL constraint for on-prem deployments while keeping the cloud API option open. Both backends must produce identical projections (tested).
Structured JSON Direct bypass in core Skips worker fleet entirely; schema inference applies; nested-object policy (flatten vs child-node) configurable
Code (.rs .py .js/.ts) Tree-sitter Files/modules/functions/classes→nodes; imports/calls→edges; incremental re-ingest by content hash (Phase 3)
Email (.eml .msg) Envelope parser Headers→edges (SENT_BY/TO/CC/IN_REPLY_TO threading); body→spans; attachments→child jobs by detected format (Phase 3)

Provenance: sources CF records document identity, version, and content hash (same-hash re-ingest = new version of same source, never a duplicate); spans CF is the token span ledger — every ingested node links to the exact source spans it came from. This linkage is what makes Phase-3 claim verification possible.

Job queue: states pending → leased → completed/failed/dead; lease acquisition is an atomic state-move in one WriteBatch; a background reaper reclaims expired leases, increments attempts, and dead-letters after N failures; concurrent pollers can never double-lease (tested). Workers are stateless gRPC clients built on the shared workers_common harness (poll loop, heartbeat thread, graceful shutdown). System-level jobs live in the nil-tenant namespace (§4.1).


12. Security Model

12.1 Double-walled tenancy (Task 12b)

  1. Application layer: RBAC + tenant scoping on all API calls; MikroORM entity filters on every PostgreSQL entity; per-user tenant binding propagated to gRPC metadata (signed).
  2. Storage layer: 32-byte tenant prefix in every key; isolation true-by-construction per §4.3.

12.2 Field-level encryption (Task 12e — ratified F2)

Cipher: XChaCha20-Poly1305 with random 192-bit nonces (nonce-collision risk negligible without aggressive rotation; deviation from v7's "AES-GCM" recorded in the spec's Alternatives Considered). Envelope encryption: per-tenant DEKs wrapped by a KMS-held KEK; ciphertext envelope {v, alg, key_id, nonce24, ct, tag}. Rotation: BullMQ job re-encrypts under a new DEK by writing new Telha versions (append-only compatible).

Open Mercato Application Layer
  -> classifies fields per the formal PII-classification spec (never tribal knowledge)
  -> encrypts classified fields before they cross the wire
  -> passes encrypted payloads to Telha Core via gRPC

Telha Core
  -> never decrypts — stores opaque encrypted bytes in node properties
  -> indexing/search operates on non-encrypted metadata fields only
  -> semantic vectors generated from non-classified content only (enforced by test)

Telha is never a decryption surface; keys live exclusively in the app layer's KMS. Consequence, accepted by design: classified fields are invisible to indexes and vector search. Tests inspect raw Telha bytes to assert no plaintext, and assert indexes/vectors contain no classified-field content.

12.3 System namespace hardening

The nil-tenant namespace (§4.1) sorts to the head of every CF and carries system jobs — precisely where an attacker would want to write. It is unforgeable from every external path; a dedicated unreachability test covers REST, gRPC, and MCP.


13. Application Layer, MCP, and Generation

13.1 Data-split routing (Task 12c — normative policy)

Data Type Storage Target Access Pattern
Commodity SaaS data (users, accounts, billing, orders, inventory) PostgreSQL via MikroORM Standard CRUD, relational joins
Long-context cognitive memory (evolving timelines, contract history, audit trails, risk evidence) Telha Core via gRPC/UDS Tritemporal queries, traversal, semantic search
Embeddings + vector search Telha Core (partitioned HNSW) Temporal-aware similarity
File blobs (PDFs, images, attachments) S3/local via storage-s3 Blob storage, pre-signed URLs
// Inside an Open Mercato module command handler:
await em.persistAndFlush(order);                    // commodity → PostgreSQL

await telha.records.create({                        // cognitive → Telha
  label: 'CONTRACT_VERSION',
  data: { clauseText, amendments, riskScore },
  validTime: { start: effectiveDate, end: expiryDate },
  relationships: [{ type: 'AMENDS', target: previousVersionId }]
});

13.2 Integrated MCP server (Tasks 11, 12d)

Built into the Telha Core binary via rmcp (streamable HTTP transport); each MCP session authenticates and binds to a TenantScope. Tools: createRecord, findRecords, compareSnapshots, getHistory, semanticSearch, generate, getSchema, getTrace — plus a spec resource exposing .ai/specs/ so the system's design manuals are machine-readable for cross-verification (Task 12a). Results are LLM-shaped (truncation, summarized counts; tabular/diff shapes for compare and trace).

The app layer's ai-assistant package discovers these tools dynamically over MCP and executes them from a chat interface with per-role tool allowlists (RBAC) and a PostgreSQL audit log of every invocation. The chat UI includes a trace viewer rendering clickable claim→span provenance. Canonical operator scenarios (each a scripted e2e test): "What changed in our risk assessment between Q1 and Q2?" · "Show me all versions of contract #1234" · "Find all evidence related to supply chain delays" · "Summarize the current state of Project Alpha with full provenance."

13.3 Memory planner, generation, claim verification (Task 10)

  • Two-phase planning: Phase A candidate recall (temporal + graph + vector union into a scored evidence pool with span references); Phase B budgeted selection (greedy score-per-token packing under a token budget, per-source diversity constraint, deterministic tie-break). Truth/cache separation: verified-fact spans flagged distinctly from derived summaries.
  • Generation (ratified F8): stays in the core for v1, adjacent to the planner, wrapped entirely in an LlmProvider trait (Anthropic + OpenAI-compatible HTTP implementations; credentials via env/file, never logged; per-tenant cost caps; retry/backoff; SSE streaming). Prompt assembly is prefix-stable — deterministic evidence ordering — as groundwork for Phase-4 prefix-cache-aware construction. A future migration of generation to the app layer is a module swap behind the trait, not an extraction.
  • Claim verification: generated drafts decompose into atomic claims; each claim is matched to plan spans by hybrid embedding-similarity + lexical-overlap scoring into bands (supported / partial / unsupported); unsupported-claim policy (flag vs redact) is configurable. Adversarial gate: planted falsehoods must score unsupported.
  • Memory trace: the full assembly record (plan, prompt hash, claims, span links, per-stage stats) persists to the traces CF, retrievable via /v1/trace/:queryId, the getTrace MCP tool, and the chat trace viewer.
  • All three algorithm families (decay, budgeting, verification scoring) have their mathematics peer-reviewed in dedicated specs before any code, enforced by the spec-first CI gate (ratified F7).

13.4 SDKs (Task 13)

One TypeScript SDK, two transports: RestTransport (fetch + apiKey) for external clients, GrpcTransport (@grpc/grpc-js over UDS/TCP) for the in-cluster app layer — resolving v7's apiKey-vs-gRPC ambiguity. Typed query builder mirroring the DSL; error taxonomy mapping the REST envelope + gRPC status codes; retries with jitter on idempotent calls; contract-tested against the live core using the shared parser fixture corpus.

const telha = new Telha({ transport: 'uds', path: '/run/telha.sock' });  // app layer
// const telha = new Telha({ apiKey: '...', url: 'https://...' });       // external

await telha.records.create({ label: 'RISK', data: { severity: 8 }, validTime: { start: '2025-05-01' }});
const risks = await telha.query({ find: 'RISK', where: { severity: { $gte: 7 } }, atValidTime: '2025-05-01', expand: ['OWNED_BY'] });
const delta = await telha.compare({ baseline: { validTime: '2025-05-01' }, comparison: { validTime: '2025-07-01' }, find: 'RISK' });

Python SDK mirrors the REST surface with pydantic typing.


14. Registers

14.1 Ratified decision log (2026-07-02)

ID Decision Lands in
F1 Tenant isolation by construction: exclusive KeyCodec, hidden raw constructors, CI iterator-gate, property/fuzz tests PR-011, PR-003
F2 XChaCha20-Poly1305 (192-bit nonces) replaces AES-GCM; PII classification is a formal spec PR-043
F3 Total atomic cascade, append-only (never deletes); ghost-edge invariants I1–I3; cascade journal for oversized write-sets PR-019
F4 vectors CF canonical; HNSW buckets derived + auto-rebuilt PR-039
F5 Swappable Fetcher trait; Playwright fallback shipped; Firecrawl optional PR-035
F6 All performance claims are CI benchmark gates PR-029
F7 Decay/budget/verification math peer-reviewed in specs before code PR-002 gate
F8 Generation in core for v1 behind LlmProvider trait PR-051
F9 rmp-serde standard; FlatBuffers deferred PR-013
F10 Serde-first AST; Roaring Bitmaps as separate accelerator PR with mandatory temporal post-validation PR-021/022/022b
OQ-3 Reserved [0u8;32] nil-tenant system namespace, unforgeable externally key spec v0.2, PR-012/030

14.2 Open questions register (owner: spec review)

ID Question Blocks Stance
KEY-OQ1 Widen edge_type_hash to 16B if collision registry ever fires? Nothing 8B xxh3 + fail-loud registry; revisit on first collision
KEY-OQ2 / CAS-OQ3 GDPR physical erasure vs append-only immutability GA (not build) Contiguous prefixes deliberately preserve trivial DeleteRange; a dedicated erasure spec (procedure, journals, backups, HNSW files) required before GA
CAS-OQ1 Retroactive tombstone (valid_end in the past)? Nothing Mechanically free (just another valid-time fact); confirm product intent
CAS-OQ2 Un-tombstone / resurrection semantics Nothing Forbidden at API level in v1; defer
GEN-OQ1 Long-term home of generation (core vs app layer) Nothing (trait-contained) Core for v1 per F8; revisit post-Phase-3

14.3 Dependency summary

Telha Core (Rust): rust-rocksdb · axum + tower · tonic + prost · rmp-serde · usearch · croaring-rs (PR-022b) · xxhash-rust · uuid (v7) · tokio · rmcp · reqwest (LlmProvider/EmbeddingProvider) · tracing · clap · proptest + cargo-fuzz + criterion (dev). App layer (TypeScript): Next.js · MikroORM + PostgreSQL · BullMQ + Redis · Turborepo + Yarn workspaces · @telha/sdk · libsodium/@noble (XChaCha20-Poly1305). Python workers: docling · firecrawl-py or playwright (Fetcher-selected) · tree-sitter · polars · grpcio · pydantic. Tooling: buf (proto lint/breaking) · spec-lint (Node, zero-dep) · uv (Python workspace).

14.4 Why this approach

Decision Rationale
Unified monorepo (Rust + TS + Python) AI agents get full-stack context; no black-box fragmentation
Spec-first harness, CI-enforced, MCP-readable Prevents sloppy design; system cross-verifies against its own manuals; decisions never relitigated silently
Tenant-scoped physical keys, isolation by construction The guarantee is structural and tested, not aspirational
Append-only everywhere (versions, cascades, specs) Historical truth is the product; nothing that would falsify history is expressible
Data-split routing (PostgreSQL + Telha + S3) Each engine does what it is built for
Local gRPC/UDS between layers Zero network overhead, clean process boundary, independent scaling
Encryption in app layer only (XChaCha20-Poly1305) Telha never holds keys; minimal attack surface
Derived-index doctrine (HNSW, bitmaps) Anything rebuildable can never corrupt the truth
Claims → benchmarks, algorithms → specs Every promise in this document is either CI-gated or peer-reviewed math
Open Mercato absorbed as-is Don't translate web boilerplate; connect at the data boundary

15. Build Phases (summary — full PR/commit detail in telha-build-doc.md)

  • Phase 0 (wk 1–2): monorepo scaffold, spec-first harness (built & tested), CI pipelines + iterator-gate. → PR-001..003
  • Phase 1 (mo 1–4): KeyCodec, RocksDB engine, tritemporal read/write, secondary indexes, adjacency + traversal, append-only cascade, schema inference, query DSL + scan executor, REST + gRPC/UDS, TS SDK, app-layer wiring, benchmark harness. → PR-010..029 (+PR-022b when benchmarks justify)
  • Phase 2 (mo 4–7): job queue + leases + reaper, WorkerService, ingestion + provenance, Python SDK + workers (docling/web/tabular/JSON), vectors + partitioned HNSW, hybrid queries, MCP server, ai-assistant chat, field encryption. → PR-030..043
  • Phase 3 (mo 7–10): memory planner, LlmProvider generation, claim verification + traces, compare/delta engine, code + email workers, full MCP suite + trace viewer, evaluation harness with release gates. → PR-050..057
  • Phase 4 (ongoing): FoundationDB backend, prefix-cache-aware prompting, tenant performance isolation, platform connectors, materialized snapshot cache, marketplace packaging. → EPIC-401..406