Skip to content

Telha — Build Doc (v3.11)

PR/Commit Plan · Conventions · Phase Gates · Risk Register

Companion to: telha-prd-v8.md (the consolidated PRD; §-references below point there) Supersedes: feasibility-review + build plan v1.1 (the review's findings are now merged into PRD v8 §14.1 as the ratified decision log; the historical review is preserved below as Part 1 for audit) Date: 2026-07-02 Status: Approved for build; Phase 0, Phase 1, and Phase 2 built (PR-001..003, PR-010..029 + PR-022b, PR-030..043 done; PR-027/028 live-verified; PR-042 live chat-e2e passed 2026-07-04). Ingestion chain PR-030..037 live-verified end-to-end incl. a real python worker over gRPC; vector chain PR-038/039/040 done; MCP PR-041/042 done; PR-043 field encryption built 2026-07-04 (last Phase-2 PR — spec v0.3 as-built, grpc-contracts v0.4 wire additions). Phase-3 core chain PR-050/051/052 built 2026-07-04: /v1/generate (JSON + SSE) plans evidence, streams cited drafts, verifies every claim, persists auditable traces (GET /v1/trace/:queryId); MCP generate/getTrace activated. PR-053/054/055 built 2026-07-04: the compare engine (POST /v1/compare + gRPC Compare + MCP compareSnapshots + SDK compare() — the LAST stubs anywhere are gone; snapshot-compare spec v0.2 as-built) and the code + email workers (tree-sitter AST projection; .eml/.msg envelope projection with self-healing threading and attachment fan-out; both specs v0.2 as-built), enabled by two additive ingestion seams (submission nodes[].id deterministic upsert + childJobs[].contentB64, ingestion-provenance v0.4 §17). Per-PR **Status:** lines below are authoritative. All flags F1–F10 and OQ-3 ratified. Spec coverage: 30 design specs, all lint-green and all Approved (the four 2026-07-04 feature specs driving the Phase 3b lane, PR-060..070 cleared review 2026-07-04; see v2.5). Phase 3b started: PR-060 clarification core built 2026-07-05 (temporal-clarification v0.7 as-built; ships behind clarify.enabled = false). Sign-offs: confidence-decay F7 math Approved 2026-07-03; memory-planner + claim-verification F7 math Approved 2026-07-04, countersigned by J. Porter; field-encryption + generation-credentials security Approved 2026-07-04 (v0.2 each, conditions incorporated; reviewed by Claude under J. Porter's delegation — countersign/veto open). All F7 and security gates now clear — every PR through Phase 3 is buildable on its dependency order alone. Still outstanding: legal (GDPR erasure — gates PR-081 GA). Phase 3d — Active Memory & Gateway (PR-087..091) added 2026-07-06 from the product-plan gap review: memory-watch / saved-answers-staleness / memory-context specs at v0.2 In Review (OQ review incorporated 2026-07-06; 39 specs lint-green) — the Approved flip is the only gate; 087+089 then parallel, 088 after 087.

How to use this document: Part 1 is the audit trail (why each decision exists). Part 2 is the work: every PR with branch name, ordered commits, and acceptance criteria — open them in dependency order. Part 3 is the ratified decision log and immediate next steps. Part 4 is governance: spec inventory, phase gates, risk register, environment, team lanes. When this doc and a .ai/specs/ file disagree, the spec wins and this doc gets a changelog fix.


Part 1 — Feasibility Review (audit trail)

1.1 Overall verdict

Every task in the PRD is technically achievable with the named stack. The architecture is coherent: RocksDB composite keys for tenant-scoped tritemporal storage, adjacency-list graph indexing, partitioned HNSW, a Rust core binary exposing REST + gRPC + MCP, a TypeScript app layer, and stateless Python workers are all proven patterns with mature libraries. Nothing here requires research-grade invention.

However, the PRD contains several claims that are overstated as written and a few design ambiguities that will bite during implementation if not resolved in the first specs. These are itemized below with the recommended resolution. Each resolution is baked into the PR plan in Part 2.

1.2 Task-by-task assessment

# Task Verdict Notes
1 Monorepo topology ✅ Feasible Cargo workspace + Yarn/Turborepo + Python (uv) coexist fine. CI needs path-filtering to keep builds fast.
2 Storage engine (RocksDB) ✅ Feasible, one claim corrected Composite keys, column families, prefix bloom filters all directly supported by rust-rocksdb. See Flag F1 on the "geometrically impossible" claim.
3 Graph engine ✅ Feasible Adjacency CFs with bounded range scans is the standard KV-graph design (JanusGraph/Dgraph lineage). Sub-ms 1–3 hop on 10M nodes is realistic with warm block cache — treat as a benchmark target, not a guarantee (Flag F6).
4 Tritemporal engine ✅ Feasible Append-only versions + inverted timestamps for latest-first iteration is standard. Atomic cascade via WriteBatch works — but the cascade must include adjacency + secondary index entries in the same batch (Flag F3).
5 Vector index ✅ Feasible, design gap usearch has Rust bindings and supports save/load. But usearch keeps its own vector copies — the vectors CF must be defined as the source of truth with HNSW partitions as rebuildable derived indexes, or you double memory/storage with no recovery story (Flag F4).
6 Query engine ✅ Feasible JSON DSL over serde is straightforward. "Confidence decay" has no formula in the PRD — needs a spec before Phase 1 exit (Flag F7).
7 Schema inference ✅ Feasible Type-lattice merge on write, versioned in schema CF. Well-bounded.
8 API server ✅ Feasible Axum + Tonic in one Tokio runtime on separate listeners is routine. Tonic supports UDS on Unix; fall back to localhost TCP on Windows dev machines.
9 Ingestion pipeline ✅ Feasible, licensing flag Docling (MIT), tree-sitter (MIT), polars (MIT) fine. Firecrawl self-hosted is AGPL; the hosted API is a paid dependency (Flag F5).
10 Memory planner + claim verification ✅ Feasible, ambiguity Requires the Rust core to call external LLM APIs. Feasible, but LLM keys/config now live in the "deep-tech" binary — decide deliberately (Flag F8).
11 MCP server ✅ Feasible rmcp is the official Rust MCP SDK and supports the tool surface described.
12 Open Mercato absorption ✅ Feasible Data-split routing, spec-first convention, and gRPC-metadata tenancy propagation are all implementable. Encryption alignment is sound (see F2 for the operational caveats).
13 SDKs ✅ Feasible The PRD example uses apiKey + url (REST) while Task 12 says the app layer talks gRPC. Resolve: one SDK, two transports (REST for external, gRPC/UDS channel for in-cluster app layer). Baked into PR-027.

1.3 Flags — corrections & decisions required before/while building

F1 — "Cross-tenant leakage geometrically impossible" is overstated. Prefix bloom filters are a performance structure, not a security boundary. Isolation actually depends on every read path constructing keys through tenant-scoped code. If any call site builds a raw key or an unbounded iterator, isolation is gone. Resolution (in PR-011): a single KeyCodec module is the only place keys are ever encoded; raw-byte constructors are pub(crate)-hidden; property tests + fuzz tests assert every generated scan range stays inside the tenant prefix; a CI grep-gate forbids direct db.iterator( calls outside the storage module. The claim then becomes true-by-construction, which is the defensible version. ✅ Ratified: single KeyCodec module, pub(crate)-hidden raw constructors, CI grep-gate on raw iterators.

F2 — Encryption alignment: nonces, rotation, and searchability. Field-encryption-in-app-layer is the right call, but the PRD omitted: (a) nonce uniqueness strategy, (b) key rotation/re-encryption path, (c) the consequence that encrypted fields are invisible to Telha's indexing and vectors — the field-classification policy ("what is PII") must be a spec, not tribal knowledge. ✅ Ratified: the AEAD is XChaCha20-Poly1305 (random 192-bit nonces make collision risk negligible without aggressive rotation schedules); field classification is mandated as a formal .ai/specs/ document; encrypted fields stay opaque to all Telha indexes and vectors by design. Note the deviation from the PRD's literal "AES-GCM" text — the PR-043 spec must record this in its Alternatives Considered + Changelog sections. PR-043 delivers the spec + field-crypto package with rotation support.

F3 — Atomic cascade scope. "Edges tombstoned in same batch_write" is necessary but insufficient. A tombstone must atomically write: node tombstone version, edge tombstone versions, adjacency_out/in entries, and both secondary index entries — otherwise historical snapshots can see ghost adjacency rows even with tombstoned edge versions. ✅ Ratified with one amendment: the stakeholder resolution described adjacency "invalidations" and index "removals" — in an append-only tritemporal store, nothing is ever removed. Deleting adjacency/index rows would blind queries at coordinates before the tombstone, breaking historical snapshots in the opposite direction. The total cascade is one WriteBatch that appends the tombstone versions and their corresponding new adjacency + valid_time_index + tx_time_index entries; readers filter by temporal end-boundary validation, never by row absence. Atomicity and crash-consistency guarantees are unchanged. PR-019's spec commit encodes this as the normative wording, with the ghost-edge invariant tested in both temporal directions.

F4 — Vector source-of-truth. Decide: RocksDB vectors CF is canonical; HNSW time-bucket partitions are derived, persisted via usearch save-files under the data dir, and rebuildable from the CF on corruption or bucket-boundary changes. Bucket size (e.g., monthly) and the "query spans multiple buckets → search N partitions and merge" behavior need a spec. PR-039 implements exactly this. ✅ Ratified: vectors CF is the absolute source of truth; HNSW time-buckets are derived, ephemeral indexes persisted via usearch save-files and automatically rebuilt from the CF on corruption or bucket-boundary changes.

F5 — Firecrawl licensing/cost. The firecrawl Python lib is a client for their API (paid) — self-hosting the Firecrawl service is AGPL-3.0, which may be incompatible with your distribution plans. Decide early: hosted API (cost, data egress), self-host (AGPL isolation via separate service is fine since it's network-isolated), or fallback to Playwright + readability for v1. ✅ Ratified: PR-035 builds around a swappable Fetcher trait — Firecrawl supported, headless Playwright fallback shipped out of the box, which bypasses the AGPL constraint for on-prem enterprise deployments while keeping the cloud API option open.

F6 — Performance numbers are targets, not facts. "Sub-millisecond 1–3 hop traversals on 10M+ nodes" is achievable with warm caches and bounded fan-out but is workload-dependent. PR-029 stands up a Criterion benchmark suite with a 10M-node synthetic dataset so the claim is continuously measured from Phase 1, not asserted. ✅ Ratified: sub-millisecond traversal targets become continuous CI benchmark gates that fail the build on regression.

F7 — Underspecified algorithms. Three pipeline stages have no defined semantics yet: confidence decay (per-hop multiplicative? edge-type-weighted?), token budget policy in evidence assembly, and claim-verification scoring. Each gets a mandatory .ai/specs/ entry as the first commit of its PR — enforced by the spec-first CI gate (PR-002). ✅ Ratified: no code is written for these algorithms until their mathematical semantics are peer-reviewed in a dedicated spec.

F8 — LLM calls from the Rust core. /v1/generate puts LLM orchestration inside Telha Core. This is workable (a LlmProvider trait over Anthropic/OpenAI-compatible HTTP APIs) but means the core binary holds LLM credentials and makes egress calls. Alternative: keep generation in the app layer and have the core expose only evidence-planning. ✅ Ratified: generation stays in the core for v1 to remain adjacent to the memory planner, fully wrapped in the LlmProvider trait (PR-051) so an eventual migration to the app layer is a module swap, not an extraction.

F9 (minor) — Serialization: pick one. rmp-serde / flatbuffers — pick rmp-serde (MessagePack) for v1 everywhere. FlatBuffers' zero-copy wins matter only after profiling shows deserialization as a hotspot; carrying both doubles the codec surface. The StorageEngine value type is Vec<u8> so a later swap is contained. ✅ Ratified: rmp-serde standard for v1; FlatBuffers deferred as premature optimization.

F10 — Timeline realism & the query executor. Phase 1 in 3–4 months is achievable for 2–3 experienced Rust engineers plus 1–2 TS engineers if scope holds. The single biggest schedule risk is the query executor (PR-022) — it touches every subsystem. The PR plan sequences it so storage/graph/temporal land and stabilize first. ✅ Ratified with one scoping amendment. Two parts to the stakeholder resolution:

  • Serde-first AST: agreed — and already how PR-021 is written (serde types + validation, no custom parser). Zero schedule impact; the compiler does the JSON validation.
  • Roaring Bitmaps for AND/OR execution: right instinct, but it must not land inside PR-022, because it would do the opposite of de-risking it. Bitmaps require new infrastructure PR-022 doesn't have: a dense integer↔logicalId mapping, posting-list maintenance on every write path, and — critically — a bitmap answers "which rows match predicate P" but not "which rows matched P at time T". A naive bitmap index silently breaks temporal correctness. The sound design is bitmaps as an accelerator with temporal post-validation: intersect per-(tenant, label, predicate) bitmaps to get a candidate set in microseconds, then end-boundary-validate survivors against the version CFs. That is a self-contained, spec'd, benchmarked addition — now PR-022b, sequenced after PR-029 proves where the scan-based executor is actually slow. PR-022 stays the small, correct, benchmarked baseline that PR-022b must beat on the same suite.

1.4 Conventions assumed by the plan

  • Branching: trunk-based; short-lived branches feat/<pr-id>-<slug>, squash-or-rebase merge, every PR green on CI.
  • Commits: Conventional Commits (feat(storage): …, test(graph): …, spec: …). Each commit below is intended to compile and pass tests independently.
  • Spec-first gate: any PR touching key layouts, query semantics, schemas, or worker pipelines must contain a .ai/specs/{date}-{title}.md commit first; CI enforces (PR-002).
  • Definition of done per PR: spec (if applicable) + code + unit tests + integration test where meaningful + docs/README delta + CI green.
  • IDs: tenantId, organizationId, logicalId are UUIDv7 (16 bytes; v7's time-ordering improves RocksDB write locality vs v4).
  • Timestamps: u64 microseconds since epoch, stored inverted (u64::MAX - ts) where descending iteration is wanted.

Part 2 — PR & Commit Plan

Dependency graph (PRs can proceed in parallel within a lane once their arrows are satisfied):

Phase 0:  PR-001 → PR-002 → PR-003
Phase 1:  PR-010 → PR-011 → PR-012 → PR-013 → PR-014 → PR-015 → PR-016
                                   └→ PR-017 → PR-018 → PR-019
          PR-020 (after 014) · PR-021 → PR-022 (after 016,018,020) → PR-022b (after 029)
          PR-023 (after 010) → PR-024 (after 022) · PR-025 (after 010) → PR-026 (after 022)
          PR-027 (after 024,026) → PR-028 · PR-029 (after 022)
Phase 2:  PR-030 → PR-031 → PR-032 → PR-033 → PR-034/035/036/037 (parallel)
          PR-038 → PR-039 → PR-040 · PR-041 (after 022) → PR-042 · PR-043 (after 028)
Phase 3:  PR-050 → PR-051 → PR-052 · PR-053 · PR-054/055 · PR-056 · PR-057
Phase 3b: PR-060 → PR-061 · PR-062 → PR-063 (after 060+062) → PR-070
          PR-060 → PR-064 · PR-065 → PR-066/067/068/069 (parallel, priority order)
          (3b lanes independent of each other's cluster and of PR-053..057)
Phase 3c: PR-080 → PR-081 (legal gate) · PR-080 → PR-083
          PR-082 · PR-084 → PR-085 · PR-086 (may trail GA)
          (productization; largely parallel; 080 precedes any real-data pilot)

Progress (2026-07-04): Phase 0 + Phase 1 + Phase 2 code-complete. Ingestion chain PR-030..037 done (live roundtrip proven) plus multi-submit streaming; vector chain PR-038/039/040 done; MCP PR-041/042 done (live chat-e2e passed 2026-07-04); PR-043 field encryption built 2026-07-04. Remaining before Phase-2 exit: the §4.2 exit checklist run (mercato chat click-through outstanding). Phase-3 gates all clear: F7 math + security sign-offs complete for memory-planner, claim-verification, and generation-orchestration — the 050→051→052 chain is buildable on dependency order alone.


Phase 0 — Repository Foundation (Week 1–2)

PR-001 · feat/001-monorepo-scaffold — Monorepo topology & toolchain

Status: ✅ Done (pre-existing; reviewed 2026-07-02 — fixed tower-http feature limit-bodylimit and toolchain pin 1.83→1.90).

Goal: The exact directory tree from Task 1, building end-to-end with placeholder code in all three languages.

Commits: 1. chore: initialize repository, root README, .gitignore, .editorconfig, LICENSE — Root docs describe the three-layer architecture and link to .ai/specs/. 2. feat(core): scaffold Rust cargo workspace with core-engine crate/core-engine/Cargo.toml, src/main.rs (hello-world binary), module stubs storage/ graph/ temporal/ vector/ query/ schema/ api/ grpc/ mcp/ planner/ jobs/ each with mod.rs and a doc comment stating its Task number. Pins Rust toolchain via rust-toolchain.toml. 3. feat(app): scaffold app-layer with Yarn workspaces + Turborepo/app-layer/package.json, turbo.json, empty workspaces apps/mercato, packages/{core,enterprise,ai-assistant,ui,queue,events,search} each with package.json + tsconfig.json. apps/mercato is a minimal Next.js app that boots. 4. feat(workers): scaffold Python workspace with uv + shared package layout/workers/pyproject.toml (uv workspace), stub packages docling_worker firecrawl_worker code_worker tabular_worker plus workers_common (shared gRPC/lease code lands here later). 5. feat(sdk): scaffold /sdk/typescript and /sdk/python package skeletons 6. docs(specs): create .ai/specs tree with core-engine/, app-layer/, integration/ and README — README documents the {YYYY-MM-DD}-{title}.md naming rule and the 14-section structure.

Acceptance: cargo build, yarn turbo build, uv sync all succeed from a clean clone.

PR-002 · feat/002-spec-first-harness — Spec-first enforcement (Task 12a)

Status: ✅ Done (pre-existing; reviewed 2026-07-02).

Commits: 1. docs(specs): add 14-section spec template (_TEMPLATE.md) with lint rules documented 2. feat(tooling): add spec linter script (checks filename format, required sections, changelog entry) — Node script under /tooling/spec-lint; exits non-zero on violations. 3. feat(ci): add spec-gate check — PRs labeled 'design-change' (or touching src/storage, src/query, src/schema, workers/, proto/) must include a spec file change 4. docs(specs): add 2026-07-02-monorepo-topology.md retroactively documenting Task 1 decisions — Dogfoods the template; becomes the first real spec.

PR-003 · feat/003-ci-pipelines — CI/CD skeleton

Status: ✅ Done (pre-existing; reviewed 2026-07-02).

Commits: 1. feat(ci): rust pipeline — fmt, clippy (deny warnings), test, doc build; path-filtered to core-engine/ 2. feat(ci): typescript pipeline — eslint, prettier check, typecheck, vitest; turbo-cached 3. feat(ci): python pipeline — ruff, mypy, pytest; path-filtered to workers/ and sdk/python 4. feat(ci): add iterator-gate — grep check forbidding raw_iterator/iterator( usage outside core-engine/src/storage (F1) 5. chore(ci): add release-build job producing telha-core binary artifact (linux x86_64 + aarch64)


Phase 1 — Telha Core Engine + API + SDK (Month 1–4)

PR-010 · feat/010-core-runtime — Binary runtime: config, logging, CLI

Status: ✅ Done (2026-07-02).

Commits: 1. feat(core): add clap CLI (serve, version, check-config subcommands) 2. feat(core): layered config (file + env + flags) — data_dir, rest_addr, grpc_addr/uds_path, log level 3. feat(core): tracing setup — JSON structured logs, per-request span fields (tenant_id, request_id) 4. feat(core): graceful shutdown wiring — tokio signal handling, subsystem shutdown broadcast 5. test(core): smoke test — binary boots with temp config, serves /healthz stub, shuts down clean

PR-011 · feat/011-key-codec — Composite key encoding (Task 2, resolves F1)

Status: ✅ Done (2026-07-02) — proptests + cargo-fuzz target + nightly fuzz workflow in place.

The single most safety-critical PR in the project. Nothing else may construct storage keys.

Commits: 1. spec: .ai/specs/core-engine/2026-07-XX-composite-key-layout.md — Documents every CF's exact byte layout, inverted-timestamp rule, UUIDv7 decision, prefix lengths, endianness (big-endian throughout so byte order == logical order). 2. feat(storage): TenantScope type — (tenant_id, org_id) newtype, 32-byte prefix serialization 3. feat(storage): KeyCodec — encode/decode for node_versions, edge_versions, valid_time_index, tx_time_index keys; constructors private outside storage module 4. feat(storage): ScanRange builder — all range scans derive [prefix, prefix+1) bounds from TenantScope; no public raw-bound API 5. test(storage): property tests (proptest) — roundtrip encode/decode; ordering invariants (later validTime sorts first); every ScanRange lies within its tenant prefix 6. test(storage): fuzz target for key decoding (cargo-fuzz), wired into nightly CI

PR-012 · feat/012-rocksdb-engine — StorageEngine trait + RocksDB backend (Task 2)

Status: ✅ Done (2026-07-02) — traits take typed Key/ScanRange params (ratified deviation from PRD raw-bytes sketch per F1); MemoryEngine reference backend included.

Commits: 1. feat(storage): StorageEngine + StorageSnapshot traits exactly per PRD signature, plus WriteOp enum 2. feat(storage): RocksDbEngine — opens 12 column families (node_versions, edge_versions, adjacency_out, adjacency_in, valid_time_index, tx_time_index, spans, sources, vectors, schema, traces, jobs) 3. feat(storage): per-CF options — 32-byte fixed prefix extractor + prefix bloom filters on tenant-scoped CFs; block cache sizing from config 4. feat(storage): batch_write via rocksdb::WriteBatch with WAL sync policy from config 5. feat(storage): snapshot() over rocksdb snapshots for consistent multi-read 6. test(storage): trait conformance suite (runs against RocksDbEngine; reused later for FoundationDB backend in Phase 4) 7. test(storage): tenant isolation test — write 3 tenants, prove scan_prefix never crosses; assert bloom filter stats via rocksdb perf context

PR-013 · feat/013-domain-models — Version records & serialization (resolves F9)

Status: ✅ Done (2026-07-02) — golden fixtures under core-engine/tests/golden/; regenerate only with UPDATE_GOLDEN=1 after a version-byte bump.

Commits: 1. spec: .ai/specs/core-engine/...-version-record-model.md — NodeVersion/EdgeVersion fields, tritemporal semantics, tombstone representation 2. feat(model): NodeVersion { logical_id, labels, properties: Map<String, Value>, valid_time: Interval, tx_time: Interval, source_record_time, tombstone: bool, provenance } 3. feat(model): EdgeVersion { edge_logical_id, from, to, edge_type, properties, temporal fields, tombstone } 4. feat(model): rmp-serde codecs + version byte prefix for forward-compatible schema evolution of stored values 5. test(model): golden-file serialization tests (byte-exact fixtures committed) to catch accidental format breaks

PR-014 · feat/014-node-write-path — Tritemporal node writes (Task 4, write side)

Status: ✅ Done (2026-07-02) — coarse write mutex for now (shard per logical id if PR-029 shows contention); index emission deferred to PR-016 as planned.

Commits: 1. feat(temporal): TxClock — monotonic transaction-time assignment (hybrid: wall clock + counter tiebreak) 2. feat(temporal): create_node — allocates UUIDv7 logical_id, writes version with open-ended validTime unless supplied 3. feat(temporal): update_node — appends new version; closes prior version's txTimeEnd in same batch (append-only: closure is a new index-visible fact, no in-place mutation of history) 4. feat(temporal): valid-time override support — sourceRecordTime recorded from ingest metadata 5. test(temporal): version chains — N updates produce N+1 immutable versions; concurrent update test proves txTime total order per logical_id

PR-015 · feat/015-node-read-path — Point-in-time reads & history

Status: ✅ Done (2026-07-02) — bitemporal truth table green; winner rule: highest tx_time.start among containing versions, winning tombstone reads as absent.

Commits: 1. feat(temporal): get_current(scope, logical_id) — first row of inverted-time prefix scan 2. feat(temporal): get_as_of(scope, logical_id, valid_t, tx_t) — bitemporal point lookup with end-boundary validation 3. feat(temporal): get_history(scope, logical_id) — full version stream with pagination cursor 4: test(temporal): bitemporal truth table — matrix of (validTime, txTime) queries against a scripted edit history, asserting exactly the PRD semantics (corrections visible only after their txTime, etc.)

PR-016 · feat/016-secondary-indexes — Covering temporal indexes + global scans (Task 2)

Status: ✅ Done (2026-07-02) — includes telha debug index-check + brute-force differential proptest; dedupe resolves entities on temporal containment only (regression-tested).

Commits: 1. spec: ...-global-temporal-scan.md — index maintenance rules, horizon boundary semantics, orphan-entry validation strategy 2. feat(storage): index maintenance — every node/edge version write emits valid_time_index + tx_time_index entries in the same WriteBatch 3. feat(query): global scan operator — bounded range scan from [tenant][org][inverted(T)], forward iteration, validTimeEnd validation via primary CF point-lookup, terminate on horizon/limit 4. feat(query): scan telemetry — rows scanned vs rows returned counters exported for selectivity monitoring 5. test(storage): index consistency checker (debug tool + test) — full-scan cross-verification that every version has exactly its two index entries 6. test(query): "everything valid at T" correctness against brute-force reference implementation on randomized datasets

PR-017 · feat/017-edge-write-path — Edges + adjacency indexes (Task 3)

Status: ✅ Done (2026-07-02) — 5-put write-set + edge-type collision registry in schema CF.

Commits: 1. feat(graph): edge_type hash — stable 8-byte xxh3 of type string; collision registry persisted in schema CF (fail-loud on collision) 2. feat(graph): create_edge — writes edge_versions + adjacency_out + adjacency_in + both temporal indexes in one WriteBatch 3. feat(graph): update_edge versioning mirroring node semantics 4. test(graph): adjacency symmetry property test — every out-entry has a matching in-entry; batch atomicity under injected write failures

PR-018 · feat/018-traversal — Bounded temporal traversal (Task 3)

Status: ✅ Done (2026-07-02); confidence decay implemented 2026-07-03 after F7 sign-off (DecayParams, per-edge-type weights, floor ε, score model "v1").

Commits: 1. spec: ...-traversal-semantics.md — hop bounds, fan-out limits, temporal filtering per hop, cycle handling, determinism of result order 2. feat(graph): expand_one_hop(scope, node, edge_type?, direction, as_of) — bounded adjacency range scan + edge_versions end-boundary validation 3. feat(graph): multi_hop BFS executor — visited-set on (logical_id), per-hop and total fan-out caps, depth 1–3 4. feat(graph): traversal budget guard — hard row-scan ceiling returns partial-with-warning rather than unbounded work 5. test(graph): temporal traversal correctness — snapshot at T must not see edges created after T or tombstoned before T

PR-019 · feat/019-tombstone-cascade — Atomic referential cascade (Task 4, resolves F3)

Status: ✅ Done (2026-07-02) — ghost-edge invariants I1/I2/I3 + crash-at-every-chunk-boundary journal replay green; telha debug cascade-check CLI still to add.

Commits: 1. spec: ...-tombstone-cascade.md — full write-set enumeration: node tombstone version, edge tombstone versions, adjacency entries, all secondary index entries; ghost-edge invariant statement — Normative wording per F3 amendment: the cascade appends tombstone versions + their new adjacency/index entries in one batch; it never deletes historical rows. Readers filter via end-boundary validation, never via row absence. Invariant tested in both temporal directions (post-tombstone snapshots see nothing; pre-tombstone snapshots see everything). 2. feat(temporal): tombstone_node — collects live edges via both adjacency CFs, builds single WriteBatch covering the entire write-set 3. feat(temporal): tombstone_edge standalone operation 4. test(temporal): ghost-edge invariant test — post-tombstone snapshots at any (validT, txT) never traverse into the tombstoned node; historical snapshots before tombstone still do 5. test(temporal): large-cascade test — node with 50k edges tombstones atomically (chunked batches under a single logical operation with a cascade journal for crash recovery; journal replay tested)

PR-020 · feat/020-schema-inference — Zero-schema ingest (Task 7)

Status: ✅ Done (2026-07-02) — lattice laws proptested; label rows keyed xxh3("label/"+name) to avoid edge-registry hash collisions.

Commits: 1. spec: ...-schema-inference.md — type lattice (null < bool/int/float/string/datetime/array/object < any), merge rules, versioning triggers 2. feat(schema): per-(tenant, label) property type observation on every write; lattice merge; write new schema version only on change 3. feat(schema): schema_as_of(scope, T) introspection reading versioned schema CF 4. test(schema): conflicting-type sequences produce documented lattice results; schema time-travel matches write history

PR-021 · feat/021-query-dsl — JSON query language parser (Task 6, front half)

Status: ✅ Done (2026-07-02) — 62-case fixture corpus at core-engine/tests/fixtures/query_corpus.json, reused by the SDKs.

Commits: 1. spec: ...-query-language.md — full grammar: find/where ($eq,$ne,$gt,$gte,$lt,$lte,$in,$exists,$and,$or), atValidTime/atTxTime, expand (edge types, direction, depth), vector clause (deferred flag), limit/cursor, trace flag 2. feat(query): serde AST types + validation (unknown operators rejected, depth caps, cursor opacity) 3. feat(query): logical plan builder — AST → plan nodes (TemporalResolve, Scan, Filter, Expand, Paginate) 4. test(query): parser fixture suite — valid/invalid query corpus with exact error messages (SDKs will reuse these fixtures)

PR-022 · feat/022-query-executor — Execution pipeline (Task 6, back half; resolves F7 for decay)

Status: ✅ Done (2026-07-02); both carve-outs since resolved on 2026-07-03 — decay implemented after F7 sign-off, vector stage being activated in PR-040. HMAC-signed cursors; e2e suite in tests/query_e2e.rs.

Commits: 1. spec: ...-confidence-decay.md — per-hop multiplicative decay with per-edge-type weights (default 0.7), floor cutoff, score propagation into results 2. feat(query): executor — TemporalResolve → (label-scoped prefix scan | global index scan chosen by planner) → predicate filter 3. feat(query): Expand operator wiring traversal engine into pipeline, attaching decay scores 4. feat(query): pagination — stable opaque cursors (last-key based), limit enforcement across expansion 5. feat(query): trace output — per-stage row counts, keys touched, timings; stored to traces CF when trace:true 6. feat(query): vector stage placeholder returning explicit UNIMPLEMENTED (activated in PR-040) 7. test(query): end-to-end scenario suite — contract-history dataset exercising temporal + graph + filter combinations against expected results

PR-022b · feat/022b-bitmap-acceleration — Roaring Bitmap predicate acceleration (F10 amendment; sequenced after PR-029)

Status: ✅ Done (2026-07-03) — storage/bitmap.rs + query/bitmap_planner.rs + differential test tests/bitmap_diff.rs.

Additive accelerator. PR-022's scan executor remains the correctness baseline; the planner chooses bitmaps only where indexed.

Commits: 1. spec: ...-bitmap-predicate-index.md — dense u32 row-id allocation per (tenant, label), roaring posting lists per (tenant, label, property, value-bucket) in a new bitmap_index CF, write-path maintenance rules, and the temporal-validity rule: bitmaps produce CANDIDATES only; every survivor of AND/OR intersection is end-boundary-validated against version CFs before emission 2. feat(storage): bitmap_index CF + row-id allocator (monotonic per tenant/label, persisted watermark) 3. feat(query): posting-list maintenance hooked into node write path (same WriteBatch); equality + range-bucket predicates v1 4. feat(query): planner integration — cost rule choosing bitmap-intersection + validation vs prefix scan; croaring-rs intersections for nested $and/$or trees 5. feat(query): consistency checker + 'telha index rebuild-bitmaps' admin subcommand (bitmaps are derived; rebuildable from version CFs, same doctrine as F4) 6. test(query): differential test — bitmap path vs scan path must return identical results on randomized temporal datasets (the non-negotiable gate) 7. test(bench): PR-029 suite extended with deep AND/OR queries; PR-022b must beat the scan baseline on them without regressing point/traversal benchmarks

PR-023 · feat/023-rest-skeleton — Axum server, auth, tenancy (Task 8, resolves tenancy plumbing)

Status: ✅ Done (2026-07-02) — API keys as sha256 hashes in {data_dir}/api_keys.json via telha api-key create; scope-spoof test proves scope never comes from user input.

Commits: 1. feat(api): axum router, /healthz, /readyz, request-id middleware, tower timeouts + body limits 2. feat(api): API-key auth middleware — keyed lookup → TenantScope; keys managed via CLI subcommand for v1 3. feat(api): TenantScope extractor — every handler receives scope from auth context; no handler parses tenant from user input 4. feat(api): uniform error envelope {code, message, request_id} + problem-details mapping from engine errors 5. test(api): authz matrix — wrong key, missing key, cross-tenant id probing all rejected with correct codes

PR-024 · feat/024-rest-endpoints — Phase-1 REST surface (Task 8)

Status: ✅ Done (2026-07-02) with one recorded deviation: /v1/openapi.json is a hand-rolled skeleton; the utoipa derive pass is still owed.

Commits: 1. feat(api): POST /v1/records — single + batch create with temporal metadata + relationships (delegates to edge write path) 2. feat(api): GET /v1/records/:id and GET /v1/records/:id/history (paginated) 3. feat(api): POST /v1/query — DSL passthrough to executor; trace id returned 4. feat(api): POST /v1/snapshot — point-in-time subgraph export (query sugar with pinned atValidTime/atTxTime) 5. feat(api): GET /v1/schema?at=T — schema introspection 6. feat(api): POST /v1/relationships — create/tombstone edges 7. feat(api): OpenAPI document generated (utoipa) and served at /v1/openapi.json — SDK contract source 8. test(api): HTTP integration suite against embedded engine (tempdir RocksDB) covering every endpoint + error paths

PR-025 · feat/025-proto-contracts — Protobuf definitions + codegen (Task 8)

Status: ✅ Done (2026-07-02) — tonic-build codegen; temporal fields uint64 µs, properties/query as JSON bytes (single grammar source).

Commits: 1. spec: .ai/specs/integration/...-grpc-contracts.md — message field semantics, tenant metadata key ('x-telha-tenant' + signed internal token), deadline conventions 2. feat(proto): /proto/telha/v1/app_layer.proto — AppLayerService (CreateRecords, Query, Snapshot, Compare, BatchWrite) with full message definitions 3. feat(proto): /proto/telha/v1/worker.proto — WorkerService (PollJobs stream, SubmitIngestionResult, Heartbeat) 4. feat(core): tonic-build integration; generated code committed check (buf lint + breaking-change detection in CI)

PR-026 · feat/026-grpc-applayer — AppLayerService implementation + UDS

Status: ✅ Done (2026-07-02) with deferrals: UDS is cfg-gated to unix (Windows dev box is TCP-only); deadline ceiling / cancellation propagation (spec §6) deferred — revisit in PR-031. Signed tenant tokens (HMAC-SHA256, TTL 300s) are the only scope source.

Commits: 1. feat(grpc): tonic server on TCP + Unix Domain Socket (config-selected); shares engine handle with REST 2. feat(grpc): tenant metadata interceptor — validates internal auth token, builds TenantScope (never trusts raw tenant header alone) 3. feat(grpc): CreateRecords/Query/Snapshot/BatchWrite handlers mapping proto ↔ engine types; Compare returns UNIMPLEMENTED until PR-053 4. test(grpc): UDS round-trip integration test; TCP fallback test; deadline/cancellation propagation test

PR-027 · feat/027-typescript-sdk — @telha/sdk (Task 13)

Status: ✅ Done + live-verified (2026-07-02) — all 62 query-corpus cases passed against a live telha.exe; gRPC transport interface pinned here, binding delivered in PR-028 (recorded deviation).

Commits: 1. feat(sdk-ts): package scaffold, dual transport design — RestTransport (fetch, apiKey) + GrpcTransport (@grpc/grpc-js over UDS/TCP for app layer) 2. feat(sdk-ts): records API — create (single/batch), get, history; typed temporal metadata 3. feat(sdk-ts): query builder mirroring the JSON DSL with TypeScript generics for result typing; compare() stub 4. feat(sdk-ts): error taxonomy mapping the REST error envelope + gRPC status codes to typed exceptions; retries with jitter on idempotent calls 5. test(sdk-ts): contract tests running the parser fixture corpus from PR-021 against a live core binary in CI

PR-028 · feat/028-app-layer-integration — Open Mercato wiring + data-split routing (Tasks 12b/12c)

Status: ✅ Done + live-verified (2026-07-03) — integration test green vs live binary (round trip, AMENDS expand, two-tenant isolation, wrong-key auth, PG tenant filter); includes the GrpcTransport binding + cross-language token vector from PR-027. Open: UDS test leg needs a unix host (test ran over TCP); mercato seed-button click-through not yet exercised manually.

Commits: 1. spec: .ai/specs/integration/...-data-split-routing.md — the PostgreSQL vs Telha vs S3 decision table as normative policy, with examples per module 2. feat(app): packages/core — MikroORM base setup, PostgreSQL config, tenant/org entity filters on every entity (Open Mercato convention) 3. feat(app): packages/core/telha module — DI-registered Telha client (GrpcTransport/UDS), tenant context propagation from request session to gRPC metadata 4. feat(app): example module 'contracts' — Order entity in PostgreSQL, CONTRACT_VERSION records in Telha, demonstrating the routing pattern from the PRD verbatim 5. feat(app): apps/mercato — minimal authenticated page listing contract history via Telha query (proves full-stack path) 6. test(app): integration test — app-layer → UDS → core round trip with tenant isolation asserted across two seeded tenants

PR-029 · feat/029-bench-harness — Benchmarks + soak (resolves F6)

Status: ✅ Done (2026-07-02) — criterion suites + nightly workflow with >20% p50 regression gate. First numbers (300-node smoke, dev box, release): point read 10.7µs, 1–3 hop ~27µs, global scan page-100 1.26ms, create_node 18.9µs. Reference-hardware baseline still to be fixed per next steps.

Commits: 1. feat(bench): synthetic dataset generator — configurable node/edge counts, temporal churn, tenant count (targets 10M nodes) 2. feat(bench): criterion suites — point read, 1/2/3-hop traversal, global temporal scan, write throughput; results exported as JSON 3. feat(ci): nightly bench job with regression thresholds (fail on >20% p50 regression); dashboards from stored JSON 4. feat(bench): 24h soak scenario — sustained mixed workload, RSS/compaction monitoring, documented in runbook

Phase 1 exit criteria: all PRs merged; bench p50 1-hop < 1ms warm on reference hardware documented; app-layer demo page live; spec directory covers every layout/semantics decision.


Phase 2 — Vector, Ingestion, App-Layer Integration (Month 4–7)

PR-030 · feat/030-job-queue — Internal job queue with leases (Task 9, storage side)

Status: ✅ Done (2026-07-03) — jobs/mod.rs.

Commits: 1. spec: ...-job-queue-leases.md — job states (pending → leased → completed/failed/dead), lease TTL, retry/backoff policy, poison-job handling, key layout in jobs CF 2. feat(jobs): job model + jobs CF key layout [tenantId][state][priority][createdAt][jobId] enabling ready-job prefix scans; system jobs live under the reserved [0u8;32] nil-tenant namespace (per key-layout spec v0.2), sorting to the CF head for fast system polling via internal SystemScope only 3. feat(jobs): enqueue/lease/extend/complete/fail operations — lease acquisition via atomic state-move in one WriteBatch 4. feat(jobs): reaper task — background tokio interval reclaiming expired leases, incrementing attempt count, dead-lettering after N attempts 5. test(jobs): lease contention test (concurrent pollers never double-lease); reaper recovery test (killed worker's job re-leased after TTL)

PR-031 · feat/031-grpc-workerservice — WorkerService (Task 8/9)

Status: ✅ Done (2026-07-03) — WorkerService (PollJobs long-poll stream, Heartbeat, SubmitIngestionResult with lease fencing) + worker tokens; lifecycle test tests/worker_lifecycle.rs. The PR-026 deadline/cancellation carry-in did NOT land here — still open debt.

Commits: 1. feat(grpc): PollJobs server-streaming — long-poll semantics, per-worker format-capability filter, backpressure via bounded channel 2. feat(grpc): Heartbeat — extends leases for in-flight jobs; worker registry with liveness tracking 3. feat(grpc): SubmitIngestionResult — validates payload, writes nodes/edges/spans/sources transactionally via engine, completes job 4. feat(grpc): worker auth — per-worker tokens, scoped to WorkerService only 5. test(grpc): full lifecycle test with fake worker — poll → heartbeat → submit; crash-mid-job re-lease test

PR-032 · feat/032-ingest-endpoint — Ingestion API + provenance CFs (Task 9)

Status: ✅ Done (2026-07-03) — ingest/mod.rs (BLAKE3 content-addressed blobs, dedup, span ledger); later extended with a multi-submit streaming protocol (seq/partial in submission JSON, lease-scoped continuations, fenced checkpoints — grpc-contracts v0.2 / ingestion-provenance v0.3).

Commits: 1. spec: ...-ingestion-provenance.md — sources CF (document identity, version, content hash), spans CF (token span ledger: source_id, span range, node links), dedup-by-hash rule 2. feat(ingest): POST /v1/ingest — accepts file upload/URL/raw JSON + format hint; stores source record; enqueues typed job; returns job id + status URL 3. feat(ingest): GET /v1/ingest/:jobId — job status polling endpoint 4. feat(ingest): span ledger writes wired into SubmitIngestionResult — every ingested node links to source spans 5. test(ingest): dedup test (same content hash → new version of same source, not duplicate); span linkage integrity test

PR-033 · feat/033-python-foundation — Python SDK + workers_common (Task 13/9)

Status: ✅ Done (2026-07-03) — sdk/python/telha + workers/workers_common (incl. generated gRPC protos + pytest suites).

Commits: 1. feat(sdk-py): telha package — REST client mirroring TS SDK surface (records, query, ingest); typed via pydantic 2. feat(workers): workers_common — gRPC channel management, PollJobs consumer loop, heartbeat thread, structured logging, graceful shutdown, IngestionResult builder helpers 3. feat(workers): worker harness CLI — 'python -m workers_common.run <worker>' with health endpoint for orchestration 4. test(workers): harness test against dockerized core binary (compose file added for local dev: core + postgres + redis)

PR-034 · feat/034-docling-worker — Rich document parsing (Task 9)

Status: ✅ Done (2026-07-03) — projection + failure taxonomy + golden fixtures under workers/docling_worker/tests/fixtures/.

Commits: 1. spec: ...-docling-projection.md — hierarchical span mapping: document → sections → paragraphs as nodes, CONTAINS edges, page/bbox metadata, table extraction handling 2. feat(workers): docling_worker — PDF/DOCX/PPTX parse via docling, hierarchical span + node projection per spec 3. feat(workers): failure taxonomy — corrupt file vs unsupported feature vs OOM → typed job failure reasons 4. test(workers): golden corpus (5 fixture documents) with expected node/edge/span counts; large-doc memory guard test

PR-035 · feat/035-web-worker — Web/SPA crawling (Task 9, resolves F5)

Status: ✅ Done (2026-07-03) — fetcher abstraction + robots/urls + projection; fetcher-equivalence tests in workers/firecrawl_worker/tests/.

Commits: 1. spec: ...-web-ingestion.md — fetcher abstraction decision (Firecrawl hosted vs self-host vs Playwright fallback), robots/crawl-scope policy, recrawl-as-new-source-version rule 2. feat(workers): firecrawl_worker — Fetcher protocol with FirecrawlFetcher + PlaywrightFetcher implementations behind config 3. feat(workers): markdown → span/node projection (headings as hierarchy, links as REFERENCES edges) 4. test(workers): fixture-server crawl test; fetcher-swap test proving both backends produce identical projections

PR-036 · feat/036-tabular-worker — Tabular-to-graph projection (Task 9)

Status: ✅ Done (2026-07-03) — loader/coercion/projection + tests in workers/tabular_worker/.

Commits: 1. spec: ...-tabular-projection.md — rows→nodes, columns→schema labels, cross-reference detection (foreign-key-like columns → edges), type coercion rules via polars 2. feat(workers): tabular_worker — CSV/XLSX via polars, chunked streaming for large files, projection per spec 3. test(workers): projection tests incl. 1M-row streaming memory ceiling; cross-ref edge detection accuracy fixtures

PR-037 · feat/037-json-bypass — Structured JSON direct path (Task 9)

Status: ✅ Done (2026-07-03) — ingest/json_bypass.rs.

Commits: 1. feat(ingest): JSON bypass in core — /v1/ingest with format=json skips the worker fleet entirely; keys serialized directly to nodes (schema inference applies) 2. feat(ingest): nested-object policy — configurable flatten vs child-node-with-edge, per spec addendum 3. test(ingest): bypass round-trip; deep-nesting and array-of-objects cases

PR-038 · feat/038-vector-storage — Temporal-versioned embeddings (Task 5, storage half)

Status: ✅ Done (2026-07-03) — VectorOps + model registry (system rows in schema CF), HTTP embedding provider, in-core EmbedRunner with embed-job fan-out. Recorded deviation: node-level embeddings only (span-level deferred, OQ-1). Security fix: external workers can never lease internal "embed" jobs (grpc-contracts v0.3).

Commits: 1. spec: ...-vector-storage.md — vectors CF as source of truth (F4), key layout per PRD, embeddingModelId registry, dimension validation 2. feat(vector): embedding write path — store f32 slices little-endian; model registry in schema CF; dimension mismatch = hard error 3. feat(vector): embedding provider trait + HTTP implementation (OpenAI-compatible + local endpoint) called from ingestion post-processing job 4. feat(jobs): 'embed' job type — post-ingestion fan-out creating embeddings for new spans/nodes 5. test(vector): versioned embedding history per node; re-embed on content change creates new temporal version

PR-039 · feat/039-hnsw-partitions — Partitioned HNSW (Task 5, index half; resolves F4)

Status: ✅ Done (2026-07-03) — PartitionManager over usearch (fixed-length buckets, seal/manifest/rebuild, LRU budget); recall@10 ≥0.95 vs brute force green; telha vector rebuild CLI. Deviations recorded in hnsw-partitioning spec v0.2 (JSON manifests beside save-files; window-start routing).

Commits: 1. spec: ...-hnsw-partitioning.md — time-bucket size (monthly default, configurable), partition lifecycle (open/sealed), usearch save-file layout under data_dir/vectors/, rebuild-from-CF procedure, multi-bucket query merge 2. feat(vector): PartitionManager — routes each vector to bucket by validTimeStart; maintains usearch index per (tenant, model, bucket) 3. feat(vector): persistence — sealed partitions saved via usearch serialization; startup loads manifests; corrupt/missing partition triggers rebuild from vectors CF 4. feat(vector): search(scope, model, query_vec, temporal_window, k) — selects overlapping buckets, searches each, merges by score with temporal end-boundary validation 5. feat(cli): 'telha vector rebuild' admin subcommand 6. test(vector): recall test vs brute-force on 100k vectors ≥ 0.95@10; rebuild-equivalence test; cross-bucket window merge test

PR-040 · feat/040-vector-query-stage — Vector scoring in query pipeline (Task 6 completion)

Status: ✅ Done (2026-07-03) — vector stage activated (replaces PR-022's UNIMPLEMENTED); query/fusion.rs implements decay-spec §5 fusion with the F7-mandated α fast-paths and sim clamping; text→vector resolves in the async handler layer; e2e in tests/vector_query_e2e.rs. Full gate green 202 tests/17 suites; a post-clippy-fix confirmation test run was still finishing at 22:19.

Commits: 1. feat(query): activate vector clause — {vector: {near: [...] | text: '...', model, k, minScore}} in DSL; planner places scoring pre- or post-graph-expansion per spec rule 2. feat(query): score fusion — vector similarity × confidence decay composite ranking, documented formula in the decay spec (changelog entry) 3. feat(api): text-to-vector convenience — server-side embedding of query text via provider trait 4. test(query): hybrid query e2e — temporal + graph + vector on seeded corpus with expected rankings

PR-041 · feat/041-mcp-server — Integrated MCP server (Task 11)

Status: ✅ Done (2026-07-03) — mcp/{mod,server,shape}.rs + tests/mcp_conformance.rs.

Commits: 1. spec: ...-mcp-tools.md — tool schemas (createRecord, findRecords, compareSnapshots, getHistory, semanticSearch, generate, getSchema, getTrace), auth model, tenant binding per MCP session 2. feat(mcp): rmcp server in core binary (streamable HTTP transport), session auth → TenantScope binding 3. feat(mcp): tools createRecord, findRecords, getHistory, getSchema, semanticSearch — thin adapters over engine APIs with LLM-friendly result shaping (truncation, summarized counts) 4. feat(mcp): compareSnapshots/generate/getTrace registered returning informative 'available in Phase 3' errors until PR-053/051/052 5. feat(mcp): spec ingestion tool — MCP resource exposing .ai/specs/ contents (Task 12a cross-verification) 6. test(mcp): protocol conformance via rmcp test client; tenant isolation across two MCP sessions

PR-042 · feat/042-ai-assistant — App-layer MCP client + chat (Task 12d)

Status: ✅ Done (2026-07-03; live e2e passed 2026-07-04) — packages/ai-assistant (MCP client, chat orchestration, Anthropic provider, deny-by-default RBAC, PG audit) + mercato /chat page; 5 unit tests + typecheck green, and the gated live chat-e2e (scripted find→getHistory + tenant isolation) passed against a live MCP-enabled debug binary. Open: mercato chat click-through with a real model key.

Commits: 1. feat(app): packages/ai-assistant — MCP client connecting to core MCP endpoint, dynamic tool discovery, tool registry surfaced to chat runtime 2. feat(app): chat orchestration — LLM loop (provider-configurable) with tool-call execution against discovered MCP tools, per-user tenant binding 3. feat(ui): chat interface in apps/mercato — streaming responses, tool-call visualization (which tool ran, on what) 4. feat(app): guardrails — tool allowlist per role (RBAC), audit log of tool invocations to PostgreSQL 5. test(app): scripted chat e2e — 'show history of contract X' resolves via getHistory with correct tenant scoping

PR-043 · feat/043-encryption-alignment — Field-level encryption, XChaCha20-Poly1305 (Task 12e, resolves F2)

Status: ✅ Built 2026-07-04 (spec v0.3 as-built record). @telha/enterprise field-crypto: XChaCha20-Poly1305 detached envelopes (magic 0x7E1A ‖ msgpack, AAD = tenant‖label‖logical_id‖field, libsodium CSPRNG nonces), per-tenant DEKs under a KmsProvider KEK (EnvKekProvider v1), bounded/copy-safe/rotate-invalidated DEK cache, ClassificationRegistry (explicit classified: [] required), transport-level telha-module integration (TelhaConnector.transportWrap + EncryptingTransport — the sanctioned path cannot carry plaintext classified fields), BullMQ rotation queue + re-encrypt-forward sweep with zero-reference destruction gate, contracts classification.ts (clauseText classified). Core wire additions recorded in grpc-contracts v0.4: client-supplied create ids (UUIDv7, conflict on reuse), UpdateRecord/PUT record-update surface, {"$bytes"} write convention storing envelopes as raw magic-prefixed Bytes. Tests: 26 enterprise unit (AAD transplant matrix, rotation, cache hygiene, fail-closed KMS, no-side-door, nonce stats — 10M spec run executed), gated live suite (opacity, index exclusion, rotation round-trip, id conflicts), REST/gRPC/SDK suites extended.

Commits: 1. spec: .ai/specs/app-layer/...-field-encryption.md — PII field classification policy per entity/label; XChaCha20-Poly1305 with random 192-bit nonces (ratified F2 decision; PRD's 'AES-GCM' deviation recorded in Alternatives Considered + Changelog); envelope encryption (per-tenant DEK wrapped by KMS-held KEK); rotation procedure; explicit non-searchability consequences 2. feat(app): packages/enterprise/field-crypto — XChaCha20-Poly1305 encrypt/decrypt service (libsodium/@noble), key hierarchy, ciphertext envelope format {v, alg, key_id, nonce24, ct, tag} 3. feat(app): telha module integration — classified fields encrypted before SDK calls; decryption on read paths; plaintext never crosses gRPC for classified fields 4. feat(app): rotation job (BullMQ) — re-encrypt under new DEK by writing new Telha versions (append-only compatible) 5. test(app): storage-opacity test — inspect raw Telha bytes, assert no plaintext; rotation round-trip; nonce-uniqueness statistical test 6. test(integration): verify Telha indexes/vectors contain no classified-field content (metadata-only assertion)

Phase 2 exit criteria: document/web/tabular/JSON ingestion e2e; hybrid vector queries live; AI chat answering via MCP tools; encryption contract enforced by tests.


Phase 3 — Memory Planner, Generation, Full Stack (Month 7–10)

PR-050 · feat/050-memory-planner — Two-phase evidence selection (Task 10, resolves part of F7)

Status: ✅ Built 2026-07-04 (spec → v0.3 as-built). src/planner/: Phase-A recall (structured query + semantic partition search + 1–2-hop expansion, S(n) per the fusion model, BTreeMap-deterministic union) → span explosion with ledger-kind lookup → Phase-B greedy density packing (floor-1 denominator, hard per-source cap with budget_stranded_by_cap, same-(source,version) dedup with containment-always-dedups, total_cmp ties) → EvidencePlan with blake3 plan_hash over raw f64 bit patterns. Script-aware token estimator + TokenAccounting seam (0.9 factor in estimate mode; B ceilinged by ctx/2 when known). [planner] config validated at load; Ingestor::canonical_slice/span_row added as the slice API. Tests: budget-adherence property suite incl. the mandated CJK fixture, determinism under permutation, cap/stranding, expansion decay weights, semantic fusion ranking, tenant isolation (tests/planner_plan.rs + module tests).

Commits: 1. spec: ...-memory-planner.md — two-phase design: Phase A candidate recall (temporal+graph+vector union), Phase B budgeted selection; truth/cache separation semantics; token budget accounting (tokenizer choice, per-span costs) 2. feat(planner): candidate recall — orchestrates query engine + vector search into scored evidence pool with span references 3. feat(planner): budgeted selection — greedy score-per-token packing under budget, diversity constraint (max spans per source), deterministic tie-break 4. feat(planner): truth/cache separation — verified-fact spans flagged distinctly from derived/cached summaries in the assembled plan 5. test(planner): budget adherence property test; selection determinism; recall regression fixtures

PR-051 · feat/051-generation — LLM orchestration + /v1/generate (Task 10, resolves F8 per PRD decision)

Status: ✅ Built 2026-07-04 (spec → v0.3 as-built). src/llm/: LlmProvider trait (streaming-first) + Anthropic Messages and OpenAI-compat impls over a hand-rolled SSE parser (no provider SDKs); credential hygiene per all 9 security conditions (zeroize-on-drop Secret with [REDACTED] Debug/Display, process-wide exact-match scrubbing at the log sink via telemetry::ScrubWriter, provider bodies capped 256 + scrubbed pre-error-chain, unix-0600 secrets file / Windows warning, https-or-loopback egress at config load, operator-only allowlist with 400 on miss, max_output_tokens ceiling, pre-flight reservation + reconcile in costs.rs, retries ≤2 strictly before the stream exists; boot fail-fast on missing credential). Prompt assembly emits prefix-stable [S{n}] blocks (golden bytes pinned tests/golden/prompt_v1.txt). POST /v1/generate JSON + SSE. Deviations recorded in the spec: fixed-length cost windows (24h/30d), per-tenant cap overrides deferred, tokenizer/ctx None v1. tests/generate_e2e.rs drives the real HTTP provider against a local fake: cap-429 pre-flight, retry matrix, credential-redaction-in-error, SSE ordering.

Commits: 1. spec: ...-generation-orchestration.md — LlmProvider trait, credential handling in core (env/file, never logged), prompt assembly template with span citations markers, streaming policy, cost caps per tenant 2. feat(planner): LlmProvider trait + Anthropic and OpenAI-compatible HTTP implementations (reqwest), retry/backoff, token accounting 3. feat(planner): prompt assembly — evidence plan → cited context blocks with span ids; prefix-stable ordering (groundwork for Phase 4 prefix caching) 4. feat(api): POST /v1/generate — plan → generate → return draft + span-id citation map; SSE streaming variant 5. test(planner): provider mock tests; prompt golden files; per-tenant cost cap enforcement

PR-052 · feat/052-claim-verification — Verification pipeline + traces (Task 10, resolves rest of F7)

Status: ✅ Built 2026-07-04 (spec → v0.3 as-built). src/verify/: pinned lexical pipeline over unicode-normalization + unicode-segmentation (NFC → UAX-29 → casefold, plan-local IDF coverage), geometric fusion with β∈{0,1} fast-paths and cos clamped pre-pow, ∀-coverage numeric gate capping whole pairs at 0.2 (date components + English month names normalize; f64-canonical numbers), band validation (±0.1 + supported ≥ partial+0.10), flag/redact policy (redact merges overlapping ranges; trace keeps the original draft), constrained-JSON decomposition with 2 retries → verification: unavailable (never silently unverified). Embedding-space consistency is structural: claims + spans embed at verification time under one model, recorded in trace.models.embed. GenerationTrace rows (kind-discriminated) in the traces CF; GET /v1/trace/:queryId; MCP generate + getTrace activated (tools.json regenerated; conformance updated — compareSnapshots is the last phase gate). Adversarial suite (tests/verify_adversarial.rs): number-swap + topical-fabrication 100% unsupported (spec floor 0.95), paraphrases ≥0.90 supported, entity-swap limitation measured with a canary assertion. Full-pipeline e2e incl. redaction + trace completeness in tests/generate_e2e.rs.

Commits: 1. spec: ...-claim-verification.md — atomic claim decomposition (LLM-assisted), span matching (embedding similarity + lexical overlap hybrid), scoring bands (supported/partial/unsupported), trace record schema 2. feat(planner): claim decomposition stage over generated draft 3. feat(planner): span matcher — per-claim evidence lookup against the plan's spans; hybrid score 4. feat(planner): verdict assembly — annotated response with per-claim scores; unsupported-claim policy (flag vs redact, config) 5. feat(api): traces CF persistence + GET /v1/trace/:queryId returning full assembly record (plan, prompts hash, claims, span links) 6. feat(mcp): activate generate + getTrace tools 7. test(planner): adversarial fixture suite — planted-falsehood drafts must score unsupported; trace completeness test

PR-053 · feat/053-compare-engine — Temporal delta engine (Task 6/8)

Status: ✅ Built 2026-07-04 (snapshot-compare spec v0.2 as-built; grpc-contracts v0.5, mcp-tools v0.3). src/query/compare.rs: id-ordered primary-CF group walk per kind (recorded deviation from the dual-index-merge sketch — the temporal indexes are time-ordered), read-rule winner selection per coordinate, tombstone-vs-validityLapse reasons, property diff with exact Value equality + touch, endpoint-presence checks on edges (load-bearing under §17 self-healing dangling edges), HMAC cursors bound to (scope, request-minus-cursor), compare.max_scan_rows ceiling (2M, partial-with-warning at group boundaries). Surfaces: POST /v1/compare (pointered COMPARE_INVALID), gRPC Compare (replaces the PR-026 UNIMPLEMENTED), MCP compareSnapshots activated (three shaped sections + counts; tools.json regenerated; the phase3_stub helper is gone), TS SDK compare() on both transports (embedded proto regenerated). Tests: tests/compare_e2e.rs (brute-force differential over a ~50-event scripted history × coordinate grid, named q1_vs_q2_risk_assessment_scenario, pagination/ceiling exactness, cursor binding), REST/gRPC/MCP suite extensions. Filters are node-scoped v1 (find = either-side scope, where = C-side only; edges skipped under filters — recorded).

Commits: 1. spec: ...-snapshot-compare.md — delta semantics: added/removed/modified nodes and edges between two (validT, txT) coordinates; property-level diff; pagination of large deltas 2. feat(query): compare executor — dual bounded scans + merge-diff over sorted logical ids, streaming delta output 3. feat(api): POST /v1/compare + gRPC Compare handler (replaces UNIMPLEMENTED from PR-026) 4. feat(mcp): activate compareSnapshots tool 5. test(query): scripted-history delta correctness; Q1-vs-Q2 risk-assessment scenario from the PRD as a named e2e test

PR-054 · feat/054-code-worker — Tree-sitter code ingestion (Task 9)

Status: ✅ Built 2026-07-04 (code-projection spec v0.2 as-built + §15 addendum). workers/code_worker/: manifest (code) → child-job-per-file (code-file) fan-out replaces multi-submit (per-file sources per spec §5; incremental hash-skip = core-side child dedup, ingestion-provenance §17), tree-sitter rs/py/js/ts with pinned grammar versions, deterministic uuid5 ids for FILE/CODE_REF/decls (uuid5(NAMESPACE_URL,"telha:code-worker") namespace), CALLS same-file-only with resolution:"syntactic" (honesty metric enforced by tests), per-file parse errors are stats never job failures. 48 tests (fixture mini-repos per language, determinism, import→CODE_REF fallback), ruff clean.

Commits: 1. spec: ...-code-projection.md — AST→graph mapping: files/modules/functions/classes as nodes, imports/calls as edges, language set v1 (rs, py, js/ts) 2. feat(workers): code_worker — tree-sitter parsing, projection per spec, incremental re-ingest keyed by file content hash 3. test(workers): fixture repos per language with expected graph shapes; import-edge resolution tests

PR-055 · feat/055-email-worker — Email ingestion (Task 9)

Status: ✅ Built 2026-07-04 (email-projection spec v0.2 as-built). workers/email_worker/: single sniffed email format (.msg by OLE magic, else .eml; stdlib email + extract-msg), deterministic uuid5 identity under NAMESPACE_URL — PERSON uses the Entra placeholder convention person-email:{addr} verbatim (PR-062 must adopt the namespace, recorded), MESSAGE email-msg:{message_key}. IN_REPLY_TO/References = self-healing dangling edges to deterministic parent ids (replaces pending-ref properties; heuristic tier deferred — needs graph reads); display names live on SENT_BY/TO/CC edges keeping PERSON byte-stable for upsert-match; quotes are quote-kinded spans excluded from embedding by the property-level mechanism (verified against assemble_embed_text); attachments fan out via childJobs[].contentB64 routed by sniffed type with ATTACHMENT_REF linkage by child source name (child_job_id unknowable at build time — recorded). 42 tests over 12 hand-written .eml fixtures + monkeypatched .msg path (real-OLE fixture gap recorded), ruff clean.

Commits: 1. spec: ...-email-projection.md — headers→edges (SENT_BY, TO, CC, IN_REPLY_TO threading), body→spans, attachments→child ingestion jobs by detected format 2. feat(workers): email_worker — .eml/.msg envelope parsing, threading edge construction, attachment job fan-out via SubmitIngestionResult child-job field (proto addition, buf-checked) 3. test(workers): thread-reconstruction fixtures; attachment fan-out e2e (email → PDF child job → docling)

PR-056 · feat/056-mcp-complete — Full MCP suite + operator UX (Task 12d)

Status: ✅ Built 2026-07-04 (mcp-tools spec v0.4). Core leg: getTrace claim-grouped shaping (shape::generation_trace_body, pure JSON→JSON + slice-API excerpt resolution wired in the server; verdict counts, per-claim evidence with excerpts, planSummary, field caps; query traces keep the generic envelope) — compareSnapshots shaping had already shipped with PR-053. App leg: ai-assistant/src/prompts.ts (role-filtered tool-selection prompt pack with bitemporal brief, operator-object where few-shots, temporal-audit playbooks that degrade per role; default system prompt in runChat with an override seam), ai-assistant/src/trace.ts (pure trace view-model over the pinned getTrace contract), mercato /api/trace/[id] route (session → RBAC gate → tenant-bound McpSession) + trace-viewer.tsx (verdict-colored expandable claim→span provenance) + "view trace" chips in chat (tool_end events now carry traceId), demo admin user carol added (phase-3 tools are admin-only per RBAC doctrine). Tests: 21 offline (ScriptedProvider + FakeMcp covering the four PRD §13.2 canonical scenarios, RBAC discovery filtering, trace view-model incl. unavailable/query-trace cases) + 4 live-gated scenario e2e; tsc clean for ai-assistant + mercato; next build clean. Live browser click-through still rides the existing "mercato click-through" follow-up (needs an MCP-enabled binary + real LLM key).

Commits: 1. feat(mcp): result presentation pass — tabular/diff-shaped tool outputs for compareSnapshots and getTrace, sized for chat consumption 2. feat(app): ai-assistant prompt pack — system prompts teaching the assistant when to use each Telha tool (temporal audit playbooks) 3. feat(ui): trace viewer component — clickable claim→span provenance rendering in the chat UI 4. test(app): the four PRD narrative scenarios ('what changed between Q1 and Q2', etc.) as scripted chat e2e tests

PR-057 · feat/057-eval-harness — Evaluation harness (Task 10/Phase 3)

Status: ✅ Built 2026-07-04 (eval-harness spec v0.2 as-built). Standalone /eval crate (telha-eval, black-box REST driver, NO telha-core dependency, own target dir/build lock): deterministic in-process mock provider speaking both provider protocols (OpenAI-compat SSE chat + embeddings; bag-of-words hash embeddings, sentence-split decomposition with exact byte spans, echo-evidence generation with an ECHO: planted-falsehood channel); dataset v1 (2 corpus docs, 2 qa + 2 adversarial + 4 temporal items — the temporal ones a small scripted-history DSL with $tx: substitution probing /v1/query + /v1/compare); metrics per spec §3 (recall@plan via locally reconstructed json-bypass canonical text, token-coverage precision labels, per-claim falsehood rejection, temporal accuracy, latency p50/p95, tokens/query); run --spawn|--url, gate --report --baseline --floors with exit-code contract and load-time floor ratchet; corpus-hash governance + hash-corpus tool; nightly .github/workflows/eval-nightly.yml with report artifacts as the v1 trend store. 10 unit tests incl. the spec-§12 smoke (doctored reports MUST fail the gate) + a live self-test gated on TELHA_EVAL_BIN. Recorded v1 cuts: structured recall leg only; json-bypass corpora (worker corpora need a live fleet); trend render deferred.

Commits: 1. spec: ...-eval-harness.md — metric definitions: retrieval recall, claim-support precision, latency budgets, cost per query; dataset governance 2. feat(eval): /eval crate + datasets — seeded corpora with labeled QA pairs and expected evidence spans 3. feat(eval): runners — end-to-end generate+verify scoring, JSON reports, CI nightly with trend tracking 4. feat(eval): regression gates — claim-support precision floor blocks release builds

Phase 3 exit criteria: /v1/generate returns verified, traced answers; compare live end-to-end incl. chat; eval harness gating releases.


Phase 3b — Clarification Loop & Source Connectors (added 2026-07-04)

New feature lane from the four 2026-07-04 specs (temporal-clarification, entra-identity-connector, source-connector-framework, chat-delivery-surface). Spec gate CLEARED 2026-07-04 evening: all four specs Approved (cross-spec review by J. Porter; five trust-boundary conditions incorporated — responder assertion, Slack-alias ownership, namespace-scoped connector deletes, nonce lifecycle, hook verify-before-ack + rate limiting). PR-060, PR-062, and PR-065 are startable immediately and in parallel. Numbering slots the clarification cluster at 060–064 and the connector lane at 065–069; the two lanes are independent of each other and of PR-053..057.

PR-060 · feat/060-clarification-core — Clarification model, quorum & answer endpoint

Status:Built 2026-07-05 (spec v0.7 carries the as-built record). src/clarify/ owns the loop: deterministic CLARIFICATION nodes (dedup identity = tenant/source/content-hash/span/kind) + CLARIFIES edge + clarify:temporal jobs (per-job max_attempts = ranking+1, backoff base = escalation window via new per-kind override); core applies the best-guess candidate and links Provenance.clarification in write_nodes; graph-derived authority ranking (AUTHORED_BY/SENT_BY → MENTIONS/TO/CC → OWNED_BY, PERSON depth-1); severity from [clarify.severity] elevations (low never asks); quorum/conflict/steward-override/supersede/post-binding lifecycle per §5/§6 with append-only responses and embedded audit events. Surfaces: GET /v1/clarifications, POST /v1/clarifications/:id/answer (dual auth: API keys with new optional person/role principal binding, or "clarify"-audience bearer tokens which alone may assert responder+rights → else 403 RESPONDER_ASSERTION_FORBIDDEN), §3.9 pendingClarification caveats on /v1/query, WorkerService audience fence (clarifyclarify: kinds; SubmitIngestionResult rejects non-ingest kinds), telha clarify ls|show|answer|review, telha api-key clarify-token. §12 tests owned by this PR green in tests/clarify_e2e.rs + worker_lifecycle::clarify_fence; goldens byte-stable. Feature ships clarify.enabled = false.

Per temporal-clarification §10 PR (1): Provenance clarification field, CLARIFICATION label, severity from tenant policy, dual confidences, response records, quorum policy/state + lifecycle (conflict detection, idempotent re-answer), answer endpoint with the §6 outcome matrix, free-form normalisation gate, steward override, fact-version correction, clarify:temporal job kind + audience fence + capability flag, audit hooks. Spec lands as first commit. Acceptance: the §12 named tests clarify_bitemporal_correction, clarify_quorum_*, clarify_steward_override, clarify_fence, clarify_dedup, clarify_freeform_validation, clarify_pending_answer_caveat, clarify_temporal_reindex_only, clarify_post_binding_conflict, clarify_responder_supersede.

PR-061 · feat/061-clarification-detection — Worker ambiguity detection

Status:Built 2026-07-05 (detection rules landed as §15 addenda: docling-projection v0.3, tabular-projection v0.4, email-projection v0.3; temporal-clarification v0.8 records the as-built). Shared plumbing in workers_common: add_ambiguity builder (mirrors core validation, camelCase wire), clarify_enabled gate, temporal.py deterministic date recognition (ISO + English textual; slash/dash reserved to tabular). Per worker: docling = document-level on the DOCUMENT node (role-dated conflicts w/ precedence lexicon, relative effectiveness phrases anchored to document dates, undated ≥200 bytes; one per doc, priority-ordered); tabular = designated validity column only (explicit options.clarifyDateColumn or exactly-one header-lexicon auto-match), day-first/month-first cell readings with stream-global evidence vote, capped 5/job; email = envelope-date integrity (Date vs earliest transport receipt >48h skew, missing/unparseable Date; body-content detection explicitly deferred). No severity hints emitted in v1 — core policy owns severity. +59 worker tests (237 total green).

Per temporal-clarification §10 PR (2): docling/tabular/email projections emit ambiguities (kind, candidates with span refs + origin labels, both confidences, severity hint) behind the capability gate. Workers never decide quorum policy.

PR-062 · feat/062-entra-connector — Entra/AAD identity-alias connector

Status:Built 2026-07-05 (entra-identity-connector spec v0.5 as-built). workers/entra_connector/ over workers_common + python SDK (SDK gained update_record / create_relationships; create_records passes id/upsert through): Graph delta sync (users/groups/manager, recorded-response fixtures, members@delta membership), PERSON/GROUP upserts under the pinned uuid5 conventions with {address}-only placeholders merged via unconditional ALIAS_OF, guest flag, Retry-After backoff + 410 full resync, item-level fault isolation, CONNECTOR_AUTH_FAILED permanent dead-letter, chain payload {v, usersDelta, groupsDelta, fullSyncCompletedAt, connectionId}. Core side: POST /v1/person-aliases/verified-match (clarify-audience only; evidence re-verified, exact-one-match, idempotent, ALIAS_CONFLICT/ALIAS_AMBIGUOUS/ALIAS_EVIDENCE_INVALID taxonomy, audited). 17 connector tests + core entra_slack_alias_verified_match incl. cross-tenant leg; worker suite 254 green. Unblocks PR-063.

Per entra-identity-connector §10: workers/entra_connector/ over workers_common; sync:entra kind + "connector" audience fence (small core diff); Graph delta sync with recorded-response fixtures; placeholder-person merge via ALIAS_OF; idempotent delta-window replay on crash. Acceptance: entra_delta_sync, entra_alias_binding, entra_placeholder_merge, entra_tenant_scope, entra_throttle_backoff, entra_guest_default.

PR-063 · feat/063-clarify-bot — Chat delivery surface (Teams first, Slack second)

Status:Built 2026-07-05 (chat-delivery-surface v0.5 as-built) — the clarification loop is end-to-end. app-layer/packages/clarify-bot/ (15 modules): ChannelAdapter (persons only — DM-only is structural), Teams (jose BF-JWT verify, Adaptive Cards 1.5) + Slack (raw-body HMAC ±300s, Block Kit) adapters over injectable fetch, node:http hooks with rate-limit → verify → ack → async processing, intent router (discriminated clarify_answer actions only; free text → redirect, never parsed; unknown kind/nonce dropped+audited), nonce persisted-before-send lifecycle, idempotent send, per-tenant credentials (missing = skip, never fallback), digest batching + per-person daily cap, in-lease skip-forward with reason codes, redacted steward escalation, §7 card copy exact, audit sink. Delivery protocol = lease/no-submit: stop heartbeating after delivery; reaper attempts+1 (backoff = escalation window) advances the ranking. Core seams: GET /v1/clarifications/:id (spanExcerpt/aclJson/askedAliases), JobPayload.attempts (grpc-contracts v0.7), TS SDK signServiceToken + cross-language vector. 44 offline tests (all §12 chat_* + clarify_rbac_gate/escalation/caps). COURSE-CORRECTED during build: person-by-id where-filter assumption caught (ids aren't properties) → askedAliases server-side resolution; by-id queries now inexpressible in the bot's client interface.

Per chat-delivery-surface §10: app-layer/packages/clarify-bot/ — ChannelAdapter interface, intent router, Teams then Slack adapters, hooks endpoints with callback verification, identity binding, quorum-aware cards, digest batching, skip reason codes, steward fallback, conflict messaging, audit wiring, FakeAdapter test harness, CLI fallback. Ask-in-chat explicitly OUT of scope (see PR-070). Acceptance: chat_callback_verification, chat_dm_only, chat_identity_binding, chat_idempotent_send, chat_tenant_isolation, chat_card_update, plus clarification-spec clarify_rbac_gate, clarify_escalation, clarify_caps, clarify_attestation.

PR-064 · feat/064-steward-surface — Steward review surface (CLI-first)

Status:Built 2026-07-05 (temporal-clarification v0.8 as-built). telha clarify queue (attention set via clarify::needs_steward_attention: conflicted/expired/dead_lettered + open/attested/partially_confirmed at high/critical, most urgent first); telha clarify show gains delivery history (ClarifyOps::delivery_job joins the clarify:temporal job by payload id — attempt→ranked-candidate mapping + job event trail); telha clarify erase-attribution --person over ClarifyOps::erase_person_attribution (asked[]/responder→nil, message refs dropped, channel "erased", audit details scrubbed, attribution_erased event; confirmation evidence + corrected fact preserved). Recorded limit: current-version rewrite only — physical purge of prior versions + job-payload rankings is PR-081's redaction rewrite (pinned in the test). Tests: clarify_gdpr_tombstone, clarify_delivery_history, clarify_steward_queue, clarify_attestation_then_editor_confirmation. UI can follow without model changes (PR-086).

Per temporal-clarification §10 PR (4): list/inspect/override flows over the clarification states and reason codes. Includes clarify_gdpr_tombstone (attribution erasure preserves corrected intervals — gdpr-erasure spec OQ-5 linkage).

PR-065 · feat/065-connector-core — Source-connector framework core

Status:Built 2026-07-05 (source-connector-framework spec v0.4 as-built). options.sourceMeta envelope (camelCase, required connector field) → deterministic source identity uuidv5(connector:tenant:externalId), root-node source properties, apply-time principal resolution (aad_oid canonical / email placeholder per PR-055 pin; slack/sf deferred to their connectors) + AUTHORED_BY/LAST_MODIFIED_BY/OWNED_BY edges landing BEFORE clarification opening (authority routing proven e2e), acl_json+root_node_id+identity on source versions (additive msgpack). Third token shape: tenant-scoped named connector tokens (audience "connector") → tenant-scoped sync: leasing (third fence), sync-result chain completion over the existing SubmitIngestionResult RPC ({v,sync,nextPayload,delaySecs,stats} → successor with not_before), kind↔audience fence both directions. REST: records/ingest/relationships accept connector bearers, GET /v1/records?externalId=, namespace-scoped DELETE (403 CONNECTOR_NAMESPACE_MISMATCH + security audit), RecordInput.upsert exposing the PR-053 seam. CLI: telha connector run|ls|status, api-key connector-token. Tests: connector_e2e.rs ×5 + worker_lifecycle connector fence/chain.

Per source-connector-framework §10 (core PR): options.source_meta handling, principal resolution + authorship/ownership edges, acl_json on source versions, externalId lookup, sync: audience-fence extension. Deploy core before any connector (additive API surface).

PR-066 · feat/066-sharepoint-connector — SharePoint / OneDrive

Status:Built 2026-07-05 (framework spec v0.5 as-built). workers/sharepoint_connector/: Graph drive delta chains with a per-site cursor dict, format routing by extension (docling/tabular/json; others skip+stat), full sourceMeta envelopes (aad_oid principals, owner facet, ACL from permissions with group refs per OQ-1), deletes via SDK lookup_record + delete_record (namespace-scoped by the connector token when deployed with one), item-level fault isolation, 410 full resync, Retry-After, CONNECTOR_AUTH_FAILED dead-letter, read-only asserted over recorded traffic. SDK additive: ingest_* options passthrough + lookup_record. 24 tests; worker suite 278 green.

PR-067 · feat/067-exchange-connector — Exchange / Outlook

Status:Built 2026-07-05 (framework spec v0.6 as-built). workers/exchange_connector/: Graph messages/delta chains per mailbox allowlist (per-mailbox cursor dict), raw MIME via $valueformat="email" (PR-055 projection parses; attachments fan out projection-side), Prefer: IdType="ImmutableId" so externalId survives folder moves, author = from (sender fallback) as email principal, acl = mailbox-owner principal, oversize post-fetch skip+stat, @removed deletes via lookup+delete, 410 in-run resync, Retry-After, CONNECTOR_AUTH_FAILED dead-letter, read-only asserted over recorded traffic + HTTP layer. OQ-3 landed: email-worker attachment child names now content-hash-derived (attachment:{blake3_hex}) → core child dedup shares one DOCUMENT per distinct attachment bytes across messages/mailboxes (ingestion-provenance v0.5 §18 addendum, email-projection v0.4). 25 connector tests; email worker 57 green.

PR-068 · feat/068-slack-connector — Slack content

Status:Built 2026-07-05 (framework spec v0.8 as-built). workers/slack_connector/: per-channel history cursors, one THREAD → one format="json" ingest (json bypass, label SLACK_THREAD, nested=child → HAS_MESSAGES children), OQ-2 daily rollup ceiling (per-run refinement), authors as email principals only via users.info profile email (placeholder evidence; slack aliases attach only via the clarify-bot verified match), acl omitted v1, NO delete propagation v1 (OQ-4: webhooks later), files from message facets by extension, all-GET read-only (bot token header; no token POST at all). 30 tests; workers suite 365 green. ⚠ A concurrent session produced a competing variant (delete lookback window, different record shape) and overwrote this package mid-build twice — the landed tree is the spec-pinned implementation; reconcile sessions before further connector work.

PR-069 · feat/069-salesforce-connector — Salesforce

Status:Built 2026-07-05 (framework spec v0.7 as-built). workers/salesforce_connector/: per-object updated/deleted window chains ({v, windows: {sobject: {since}}, connectionId}; >30-day-stale or seed → in-place full resync via paged SOQL id enumeration, doubling as backfill), records via format=json (describe-shaped scalar content, date/datetime → µs, system noise held out of content so audit-only touches hash-dedup to no-ops), OwnerId/CreatedById/LastModifiedById → email principals via cached User lookups (no raw sf_user principals; raw ids in extra), ACL none in v1 (recorded limitation), files opt-in via ContentDocument allowlisting (stable id = deleted-feed id; routed docling/tabular by extension), OAuth client-credentials, 429 + REQUEST_LIMIT_EXCEEDED Retry-After, CONNECTOR_AUTH_FAILED dead-letter, namespace deletes via lookup+delete, read-only over recorded traffic. 31 connector tests. Google Drive follows by demand (unnumbered backlog per spec).

PR-070 · feat/070-ask-in-chat — Ask-in-chat over the clarify surface

Status:Built 2026-07-05 (chat-delivery v0.6 as-built). clarify-bot/src/ask.ts: AskHandler seam + AiAskHandler running ai-assistant's runChat (PR-042 engine, MCP session per run) with the asker's role; intent router binds identity FIRST (unmapped → link-your-identity reply + audit, handler never called), role from the existing roles map w/ viewer default, steward→admin ChatRole mapping — tool RBAC = ai-assistant's web-chat allowlists, chat grants nothing beyond web chat; reply = final turn text + "checked:" suffix listing only tools that actually ran, 4000-char clip; audit rows carry no body text. ADDITIVE ChannelAdapter.replyText (Teams message activity / Slack chat.postMessage into the same DM) + adapterSendReply production transport wired in bot.ts (also fixed: PR-063 redirect replies were never delivered in production before this); all PR-063 methods/nonce surfaces untouched. Config ask:{enabled(false), mcpUrl, tokenKeyEnv, anthropicApiKeyEnv?, model?} — deployment-global enablement (recorded deviation: the role map is already global); missing/disabled → exact PR-063 redirect. Slack ack-then-async preserved (reply asserted post-ack); Teams still synchronous per spec §6 (~15s BF window noted for follow-up). Free-text-never-binds re-proven with ask ON. 59 tests (44 PR-063 + 15 ask) + typecheck + build; ai-assistant untouched (21 green). Live click-through vs real MCP + Anthropic pending.

Phase 3b exit criteria: a detected temporal ambiguity flows detection → routed DM card → quorum-checked answer → corrected fact version → clarification-cited evidence in /v1/generate, end-to-end on a live tenant; at least SharePoint + one comms connector (Exchange or Slack) syncing with ACL fidelity; all named clarify_*, chat_*, entra_* tests green.


Phase 3c — Productization (added 2026-07-04; feature-complete ≠ sellable)

Closes the gap between "all features built" and "a product a paying customer can run": durability, compliance, human auth, lifecycle, packaging, docs. Rationale recorded 2026-07-04: the feature PRs contain no backup/restore path at all — for a product sold as an organization's memory, that is the most urgent item in this phase and should precede any pilot holding real data. PRs are largely parallel; stated dependencies only.

PR-080 · feat/080-backup-restore — Consistent backup & verified restore

Status:Built 2026-07-05; spec v0.3 — Approved-with-conditions review (J. Porter) and BOTH conditions implemented the same day: (1) mandatory restore consistency gate — restore cross-verifies every tenant's covering indexes and REMOVES the target on failure, no opt-out (in-crate sabotage test: hash-perfect files, one deleted index row → refused); (2) PostgreSQL as the second v1 backup surface — --pg-database-url/backup.pg_database_url_envpg_dump captured + hash-manifested (requested-but-failed capture FAILS the backup); absent → partial: true + loud warnings at create and restore, pg_restore runbook step printed on restore. Also adopted: incremental-backup tripwire (checkpoint >30min or artifact >50GB reopens the design), local-dir confirmed as the v1 contract. StorageEngine::checkpoint (RocksDB native Checkpoint = online hardlink consistency; MemoryEngine test parity), src/backup/ (create/verify/restore + BLAKE3 manifest over EVERY file incl. sealed vector trios + api_keys.json; completeness checked both ways; partial-dir staging + rename), telha backup create|verify|restore CLI (create = exclusive-open cold path; the [backup] runtime ticker is the online path, first tick immediate), retention prunes only after a verified create, restore = verify-first + EMPTY-target-only + erasure-ledger seam (absent → recorded no-op; present → refuse until PR-081 lands replay). 7 e2e tests incl. roundtrip equality, restore-under-writes, tamper detection, vector-file fidelity, retention, seam legs. AS-BUILT vs the sketch below: per-tenant backup scope, S3 target, artifact encryption, and system:backup job-CF scheduling are NOT in v1 (spec OQ-1/OQ-2 + follow-ups; the ticker is a tokio task, not a jobs-CF kind); the restore consistency gate IS in (v0.3).

Commits: 1. spec: .ai/specs/core-engine/2026-07-05-backup-restore.md — consistency model (RocksDB checkpoint = point-in-time truth; vector save-files/manifests captured as-of the checkpoint with rebuild-from-CF fallback for open buckets), backup manifest (blake3 per file, engine version, CF list), whole-instance + per-tenant scopes, restore verification procedure, erasure-ledger replay hook (gdpr-erasure spec), artifact encryption stance 2. feat(backup): 'telha backup create --out <path>' — checkpoint → archive with vectors dir + manifest; safe under concurrent writes 3. feat(backup): 'telha backup restore --from --data-dir' — hash verification, CF + vector-file restore, erasure-ledger replay, mandatory index-check + bitmap/HNSW rebuild gate before serving 4. feat(backup): scheduled system:backup job (jobs CF, retention count) + filesystem target; s3-compatible target behind config 5. test(backup): round-trip full-scan equality; corrupt-artifact refusal; restore-under-writes consistency; restore-then-index-check green on a clean host

Acceptance: documented RPO; a restore on a fresh machine passes the full conformance + consistency suites.

PR-081 · feat/081-gdpr-erasure — Right-to-be-forgotten implementation

Status:CORE leg built 2026-07-06 (spec v0.6 as-built; v0.5 records the OQ-4..7 review). App-layer erasure leg (PG worker, subject-scoped DEK shred, subject index) is the remaining follow-up — core defines + tests its contract. LEGAL SIGN-OFF still gates GA (not the build); engineering re-review was delegated to Claude with the countersign slot open. Core: src/erasure/{mod,mask,blocklist,program,scan,enumerate,freeze,archives}.rs. OQ-4..7 review (J. Porter) arrived BEFORE any code so all four were built in from the start: identifier-strength model (STRONG = absolute-zero scan closure / WEAK = dual-approved residual-review ledger, default no waivers, strong-waiver structurally impossible), KEYED blocklist digests hmac_blake3_v1(tenant_erasure_secret, …) (per-tenant secret in schema CF + mirror file, re-seeded on restore replay; append-only, no v1 release action), OQ-6 log-attestation completion-report block, OQ-7 per-table PG report shape. 17 of 25 surface-register rows core-done (all CFs, blobs+redaction records, spans, traces, jobs incl. content_b64+ranking scrub, api_keys revoke, HNSW rebuild, archive supersession, backup replay); rows 18-24 = app leg (core accepts + ledgers the report). REST /admin/erasure/* (admin audience: create/approve/preview/execute/verify/report/stage-pg/residuals) + telha erase CLI; ingestion refuses erased content 409 INGEST_ERASED_CONTENT; backup restore now REPLAYS (PR-083 interim refuse-rule lifted). Cross-spec addenda landed: ingestion-provenance v0.6, composite-key-layout v0.5, retention-archival v0.4, backup-restore v0.4, temporal-clarification v0.9 (PR-064 gaps closed). Gates: lib 216 + erasure_e2e 13 + openapi 3 + all integration suites green, clippy --all-targets/fmt/spec-lint clean. Recorded limits: api_keys.json revoke needs server reload (report warns); erasure request record is a data_dir file (holds descriptor plaintext, kept out of scanned CFs). DESIGN (design-pass v0.3, retained for reference): in-place same-length masking (0x2A over span-linked + exact identifier-string ranges, codepoint-widened) under the ORIGINAL blob address — span offsets of all other referrers stay valid, streamed chunk boundaries unmoved; blob redaction record at reserved chunk index u64::MAX carries ranges + post_redaction_hash (content-address downgraded to addressing-only for redacted blobs); erased-content BLOCKLIST checked by all three dedup paths (INGEST_ERASED_CONTENT) kills resurrection; shared §18 blobs mask for every referrer by design (new evidence_redacted verify verdict). Normative 25-row surface register (incl. ingest payload content_b64, GenerationTrace draft text, clarify rankings, PR-082 auth sessions, HNSW sidecars, api_keys person bindings), normative PG stage (app-layer BullMQ worker reports to /admin/erasure/:id/stage/pg; Tier-1 DEK crypto-shred first), restore = ledger program_ref → content-free erasure programs, idempotent replay (lost-ledger path = re-execute from PG request records), archives = rewrite-and-supersede, two-pass verification scan (structural + streaming Aho-Corasick over all CFs/sidecars/programs; envelope ciphertext excluded). Rewrite-under-new-hash REJECTED (no blob→source reverse index exists; breaks dedup identity; doesn't solve span offsets). New OQ-4..7 for counsel/ops (waivers, blocklist permanence, 30-day log bound, Art. 17(3) PG retention). Cross-spec addenda (7 specs) enumerated in the design-pass report, to land with the build PR. Approval HELD at engineering review 2026-07-05 (spec → v0.2): blockers are the canonical-blob redaction design (new OQ-3 — the subject's raw words surviving in content-addressed chunks makes erasure incomplete), plus now-normative surface enumeration, PG handling, and restore/ledger interaction. OQ-1 pinned (redact, recommended to counsel; executor supports deletion fallback); OQ-2 resolved via the retention spec's shared executor/ledger. Path: redacted-blob design pass → re-review → legal sign-off → build.

Commits: tenant-scope DeleteRange erasure + zero-residual verification scan; subject-scoped DEK crypto-shred (app layer, field-encryption spec linkage) + ledgered redaction rewrite; HNSW partition purge/rebuild; erasure ledger persisted + consumed by PR-080 restore; clarification attribution tombstoning (clarify_gdpr_tombstone); telha erase CLI + audit trail.

Acceptance: verification scan proves no residual subject bytes across all CFs, vector files, and backups restored post-erasure.

PR-082 · feat/082-sso-auth — Human authentication (OIDC/SSO)

Status:Built 2026-07-06 (sso-auth spec v0.3 as-built). packages/core/src/auth/ (spec-pinned location): hand-rolled OIDC auth-code+PKCE over fetch + jose (Entra oid/overage profile first, generic OIDC discovery second), server-side sessions (telha_sid HttpOnly cookie → SessionRecord w/ authTime seam; Memory + MikroORM stores; login state/nonce/PKCE = one-shot server-side transaction, no replay), deny-by-default mapGroupsToRoles (unmapped = 403, never viewer) feeding the existing ai-assistant RBAC + steward role, aad_oid→PERSON linkage via property query (IDENTITY_NOT_LINKED surfaced by requirePersonId; ambiguous/failed lookup leaves session unlinked), role refresh (refresh-token when granted else stored-groups re-map; refreshed roles CAPPED at held authority — escalation requires fresh login; empty map revokes). mercato: demo cookie auth DELETED (session.ts, roles.ts fixture map, DEMO_USERS, dead env vars) with every consumer migrated (/api/chat, /api/trace, /contracts + seed gating, /chat, /login rewritten). Config TELHA_AUTH_CONFIG per-tenant (issuer/clientId/clientSecretEnv/groupsClaim/groupRoleMap/session ceilings/hosts). Deviations recorded: Entra groups-overage → typed audited GROUPS_OVERAGE refusal (Graph lookup deferred), per-route currentAuth() not edge middleware (PG store unreachable from edge). 47 new tests across 9 suites (core 50 green; ScriptedIdp = real loopback discovery/JWKS/token + PKCE enforcement); mercato typecheck + next build green; ai-assistant/clarify-bot/enterprise suites untouched-green; turbo typecheck 14/14; spec-lint 36/0. Live Entra click-through + Graph overage + back-channel logout/step-up/multi-IdP deferred per spec. (J. Porter): server-side sessions, deny-by-default group→role mapping (unmapped = 403, never viewer — the review's hardest-defended line), one IdP per tenant, aad_oid PERSON linkage with IDENTITY_NOT_LINKED, demo auth deleted with no parallel path. Deferred with reasons: back-channel logout, real step-up (seam kept via auth_time), multi-IdP. Machine auth unchanged.

Commits: spec: .ai/specs/app-layer/2026-07-05-sso-auth.md (OIDC auth-code + PKCE; Entra ID first, generic OIDC provider second; session model; claims→role mapping into the existing ai-assistant RBAC and clarification steward/editor/viewer roles); packages/core auth module; mercato login/logout/session pages; role-mapping config per tenant; tests incl. cross-tenant session isolation and role-downgrade taking effect on next request.

PR-083 · feat/083-retention — Retention policies & archival

Status:Built 2026-07-06 (retention-archival spec v0.3 as-built). src/retention/{mod,executor,archive,ledger}.rs: policies/holds as append-only versioned documents in the schema CF (namespaced label hashes retention/*, audit trail by construction); planner = normative dry-run with pinned report fields; executor = archive slice export (telha-archive-{tenant}-{µs}/ — length-prefixed msgpack frames + BLAKE3 manifest, .partial staging, HARD verify-before-delete, sideload into fresh dirs only, no built-in encryption per shared stance) → hold RE-CHECK from storage immediately before every delete → StorageEngine::delete_range (new trait method; native RocksDB delete_range_cf override). Shared erasure-executor skeleton for PR-081: mechanism-only EntityEraser/EntityRows + ErasureLedger (JSONL, extends gdpr-erasure record shape w/ method + archive_ref; present ledger keeps backup restore refusing until PR-081 replay); execute_erasure_at = erasure-over-holds trigger, erasure_pending() false until 081. Orphan-stream sweep retires the PR-032 debt (provisional versions past orphan_ttl_days w/o live lease → deleted + ledgered, no opt-in). Surface: system:retention ticker gated on [retention] config (enabled=false), telha retention policy|hold|preview|run|archives CLI, REST /admin/retention/* behind a NEW "admin" token audience (telha api-key admin-token; API keys rejected — establishes the operator/admin auth boundary PR-086 will reuse). Gates: cargo test -j1 exit 0 — 359 passed / 26 targets incl. retention_e2e 11 (all named tests: expiry boundary, legal hold + retention_hold_recheck, archive-then-delete + sideload, shared-blob refcount, orphan sweep, whole-entity, preview accuracy, system-job cycle, admin-scope); clippy --all-targets -D warnings + fmt clean; spec-lint 36/0. Deviations in spec v0.3 (preview byte-floor estimate, edge cascade w/ expired node, per-tenant archive index, whole-model partition rebuild). (J. Porter; conditions incorporated: normative dry-run with pinned report fields, legal holds checked at planning AND immediately before delete + test retention_hold_recheck, archive-encryption wording corrected to the shared artifact-encryption stance, verify-before-delete confirmed hard). Clock = tx-time age; whole-entity expiry only; erasure > hold > policy. Owns the archive slice format; carries the shared erasure-executor skeleton if it lands before PR-081 (§10) — PR-081 is now held for a design pass, so PR-083 SHOULD carry it.

Commits: spec: retention-archival.md (per-label/source-kind policy: keep-forever | archive-after | delete-after; legal-hold override wins; interaction with append-only history and GDPR precedence); retention system job (archival export before any delete; DeleteRange for expired scopes); operator-scoped policy API + CLI; tests: expiry boundary, legal hold, archive-then-restore round trip, orphaned-stream sweep.

PR-084 · feat/084-packaging — Distribution & deployment

Status:Built 2026-07-06 — 2 of 5 legs FULLY verified locally, CI legs draft-pending-CI (runbook Appendix B = verification checklist). VERIFIED: (1) Windows zip — tooling/packaging/make-windows-zip.ps1 computes the DLL closure via recursive objdump (exactly 3: libgcc_s_seh-1, libstdc++-6, libwinpthread-1), stages exe+DLLs+README+sample config (validated via telha check-config), and PROVED it: on PATH=System32;Windows the staged exe served, /readyz green, authenticated write+query roundtrip — the exit-127 MSYS2-DLL debt is retired; (2) Docker — Dockerfile had 3 real bugs (dev-box target-dir pin broke COPY --from=build, missing libclang-dev, no non-root/healthcheck) now fixed; docker build + docker run verified live (healthy 5s, /readyz ok, uid 10001, SIGTERM clean exit; compose healthcheck → /readyz). DRAFTED (never executed — no git remote/CI exists): release-build.yml rewritten (had fatal latent bugs: no protoc/clang, wrong artifact path, toolchain mismatch — same fixes applied to rust.yml/rust-bench.yml) with build-linux → smoke-probe → docker → windows-zip jobs on tag v*; deploy/telha.service (hardened) + telha.env.example; docs/deployment-runbook.md (clean-host systemd/Docker/Windows deploys, key inventory, backup schedule + restore drill, retention, upgrade). UDS: the SERVE leg was never built (PR-026 deferred the whole leg, not just tests — zero cfg(unix) in src/grpc); CI job stubbed if: false with the 3 needed pieces listed; recorded as remaining core debt. Static-link documented, zip-with-DLLs chosen (RUSTFLAGS forks fingerprints/config). Real unblocker for all CI legs: git init + remote. RESOLVED 2026-07-07: GitHub origin set to github.com/Analysisly/telha (private), main pushed (20 commits, force-over-init). The 5 push-triggered workflows (rust/python/typescript/docs + release-build) now run on GitHub Actions; release-build.yml artifact legs still need a v* tag to fire. First CI run is the real verification of the DRAFTED legs above (their fixes are committed but had never executed).

Commits: linux release artifacts smoke-tested in CI (existing release job gains a boot-and-probe step); Docker image build + healthcheck in CI (compose already drafted); UDS test leg enabled on the linux CI runner (retires the PR-026/028 debt); Windows zip with bundled DLLs or static link (dev/eval convenience); systemd unit + deployment runbook (config, keys, backup schedule, upgrade procedure).

Acceptance: docker run → /readyz green; a new operator can deploy from the runbook alone on a clean host.

PR-085 · feat/085-docs — API reference & customer docs

Status:Built 2026-07-06 (two lanes). OpenAPI: /v1/openapi.json is now utoipa-derived (5.5.0, pure proc-macro, zero behavior change) from the real handlers/DTOs — 26 paths / 31 operations / 17 schemas / 4 security schemes (apiKey + connector/clarify/admin bearers, per-route as middlewares accept), serde attrs honored (camelCase, deny_unknown_fields → additionalProperties:false), µs timestamps as u64, SSE mode described in /v1/generate's 200; snapshot pinned at core-engine/openapi/openapi.json (UPDATE_OPENAPI=1 regenerates) + router-mirror coverage test (new un-annotated route fails loudly) + envelope/scheme assertions; hand-rolled skeleton DELETED — PR-024's utoipa deviation retired; doc is now OpenAPI 3.1.0 (was claimed 3.0.3). Gates: cargo test -j1 exit 0 (27 targets ok), clippy --all-targets -D warnings + fmt clean. Follow-ups noted: typed response schemas, SDK generator over the snapshot. Docs: docs/{README,quickstart,sdk-typescript,sdk-python,connectors,clarify-bot,operator-handbook}.md — every command/key/endpoint verified against as-built code (CLI probes + code-read); honest-gap notes inline (no npm/PyPI publish, live tenant legs unverified, Slack scopes unpinned). Verification pass surfaced real defects now tracked separately: python SDK compare() still stubbed post-PR-053, clarify-bot default ports wrong (8080/50051 vs 7625/7626), target/ binaries stale (Jul 3, predate PR-060+ CLI families — release rebuild + zip re-cut queued). Seed content exists (2026-07-06): lite user guide at docs/user-guide/ — foundations (README, getting-started, concepts, working-with-data, ai-answers, administration) PLUS four persona guides (end-users: chat + clarify cards + verification badges incl. the four canonical scenarios; analysts: three time questions, evidence discipline, recipes; stewards: queue/conflicts/overrides + skip-reason diagnostics; developers: data-split routing, SDK patterns, MCP-first AI features). Written to as-built surfaces, Telha copy conventions (no em-dashes); PR-085 extends rather than restarts.

Commits: utoipa derive pass → generated OpenAPI replaces the hand-rolled skeleton (SDK contract source becomes generated truth); 10-minute quickstart (run → api-key → ingest → query → generate); TS/Python SDK guides; connector + clarify-bot setup runbooks (Entra app registration, Teams app deployment, Slack install); operator handbook (backup, key rotation, caps, erasure, retention).

PR-086 · feat/086-operator-console — Operator console v1

Status:Built 2026-07-06. mercato /admin section, role-gated deny-by-default (admin full / steward → steward section ONLY; steward answers are steward-authority-only — admins may look, never bind; answer additionally requires a linked PERSON). Steward review UI over GET /v1/clarifications(+/:id, +answer): client-side attention buckets (conflicted/expired/dead-lettered/high-critical, fail-closed on unknown states), span excerpt + candidates + append-only responses timeline + delivery aliases, explicit steward-override checkbox (responder=personId, rights="steward", channel="console"). Retention panel over /admin/retention/* (policies set/remove, holds + lift w/ core warning surfaced, dry-run preview rendering the pinned report fields). Guided tenant-onboarding checklist (copy-paste CLI commands + config snippets + live readyz/whoami/schema/clarifications probes). Usage page implements what today's surfaces allow + four honest "requires core endpoint" cards = the PR-086b core seam: GET /admin/jobs (chain/connector health), /admin/costs (LLM ledger), /admin/telemetry/scans, /admin/api-keys (+ minor: count/aggregation endpoint). Tokens server-side only: per-call signTenantToken (audience "clarify" for clarifications, "admin" for retention — verified admin tokens are tenant-shaped in core, no new signer needed); optional TELHA_API_KEY for probes; browser never sees tokens. 41 offline tests / typecheck / next build green. NOTE: vitest added to mercato package.json but corepack yarn install deferred (parallel sessions hold the yarn surface) — run once from app-layer/ to enable yarn test directly.

Commits: tenant onboarding flow; usage dashboards (LLM cost ledger + scan telemetry + ingestion stats); connector health view; key inventory (extends the PR-043 admin view); steward review UI over the PR-064 states (spec'd to need no model changes).

Phase 3c exit criteria (= GA gate together with Phase 3/3b): backup/restore verified on clean hardware and scheduled by default; erasure legally signed off and verification-scanned; human SSO live; retention enforceable; a customer can deploy from artifacts + docs without the dev team; eval harness (PR-057) gating releases.


Phase 3d — Active Memory & Gateway (added 2026-07-06; product-plan review)

New feature lane from the 2026-07-06 product-improvement review (gap analysis against the "enterprise memory layer" product plan; assessment recorded in the three new specs). The finding: ~70% of the plan's asks already exist — the genuine gaps are change subscriptions, stale saved answers, and a generation-free planner surface. Ordering ratified by J. Porter 2026-07-06: memory-watch first (the only new core primitive — turns Telha from reactive to proactive and unlocks briefs, alerts, and staleness), saved-answers second (the killer differentiator, depends on watch), memory-context third (cheapest, developer-facing). Spec-first gate: all three specs are v0.2 In Review — J. Porter's OQ review (2026-07-06) resolved all 10 OQs and is incorporated; formal Approved sign-off is the remaining gate (PR-002 doctrine). One stance changed at review: watch-fire history is DURABLE in v1 (minimal WatchFire records — fires power staleness/briefs/escalation and must outlive delivery jobs). Two refinements: answer severity as a separate axis from status; render-time permission safety pinned as a hard invariant. PR-089 is independent and startable in parallel with PR-087 once approved; PR-088 requires PR-087; PR-091 spec now drafted (app-layer/2026-07-06-memory-cards.md v0.1 — the human-chat rendering binding 087/088 producers to the existing Block Kit surface; the parallel session already shipped the Ask/Prove/Correct loop cards, so PR-091 adds the two missing loops, Watch + Brief); PR-090 (memory.verify) remains a numbered placeholder pending its own small spec pass.

PR-087 · feat/087-memory-watch — Durable subscriptions over the change feed

Status: ✅ Built (2026-07-07; spec v0.5 Approved+as-built). All 12 §12 named tests green (watch_entity_fires_on_change, watch_no_fire_on_dedup_resync via the real ingest dedup path, watch_debounce_coalesce, watch_checkpoint_replay via rewind+re-sweep idempotence, watch_tenant_isolation, watch_delivery_gate_lost_access via gate_denials auto-suspend, watch_shared_fanout_rbac, watch_query_diff, watch_clarification_lifecycle, watch_webhook_signature_and_egress, watch_fence, watch_fire_durable). SHIPPED: per-target delivery-kind selection (chat→notify:watch, webhook→webhook:watch, internal→job_kind), in-core WebhookRunner (HMAC-blake3-v1 signed body, https-or-loopback egress at registration, dead-letter→suspend), query/clarification/source matching (slice 2b), cross-tenant evaluator ticker, REST /v1/watches* + admin /admin/watches/webhooks, CLI telha watch + telha debug watch-checkpoint, MCP createWatch/listWatches/deleteWatch (tools.json regenerated). As-built deviations recorded in the spec v0.5 changelog. GATE VERIFIED GREEN 2026-07-07 on the new Linux dev server ([[telha-dev-server]] — Debian 12, 12-core, 22GB): full cargo test = 28 suites exit 0 (173s cold / 15s incremental), clippy --all-targets -D warnings + fmt --check clean. The local Windows box HUNG on this exact suite twice today (75min + 30min deadlocks) and never completed — moving to the server is what let it finish, which surfaced one real failure the hang had hidden: the stale mcp_conformance::lists_all_eight_tools assertion (renamed lists_all_tools, extended 8→11 tools for the added watch tools) — now fixed. FOLLOW-UP: the /v1/watches* REST handlers carry no #[utoipa::path] annotations, so they're absent from the OpenAPI contract (openapi_contract still passes — it checks generated-doc-vs-EXPECTED, not the axum router); annotate them to complete the contract. The foundation PR: watches (entity/label/source/query/clarification/answer scopes) evaluated asynchronously and deterministically from the existing tx-axis covering index (checkpointed per-tenant sweeps; enqueue+checkpoint in one WriteBatch = replayable, no write-path hooks), mandatory debounce + digest cadences (weekly digest IS the Weekly Memory Brief), delivery via notify:watch jobs to the clarify-audience bot (rendering = PR-091) / operator-registered HMAC-signed webhooks / internal job kinds (PR-088 seam), dual permission check (create-time read scope + fire-time ACL/role gate with auto-suspend), watch/* schema-CF documents per the retention precedent. Noise filtering inherited from upsert/content-hash dedup (resyncs produce no versions → no events). Acceptance: the §12 named tests watch_entity_fires_on_change, watch_no_fire_on_dedup_resync, watch_debounce_coalesce, watch_checkpoint_replay, watch_tenant_isolation, watch_delivery_gate_lost_access, watch_shared_fanout_rbac, watch_query_diff, watch_clarification_lifecycle, watch_webhook_signature_and_egress, watch_fence, watch_fire_durable.

PR-088 · feat/088-saved-answers — Saved answers & staleness

Status: ✅ Built + gate-green (2026-07-07; spec v0.3 Approved, v0.5 as-built). SHIPPED: SAVED_ANSWER model + save/get/list/delete (deps from trace claims, never re-planned), the full re-check engine (rules i–iv incl. the matcher clear-back via a NEW shared Verifier::score_claim seam that PR-090 reuses), deterministic status fold + separate severity axis, WatchOps::emit answer_stale seam (idempotent), REST /v1/answers*, MCP saveAnswer/listSavedAnswers (tools.json → 14), CLI telha answers, check:answer internal-audience fence. REFRESH + RUNNER now landed (the two deferred pieces): refresh re-runs generation at CURRENT coordinates and links SUPERSEDES (new→old, old→superseded, append-only) — POST /v1/answers/:id/refresh (202) + telha answers refresh, built on a post_generate refactor (prepare_generation/collect_and_trace/stream_generation + regenerate_for_refresh replaying the original trace's recall params); the in-core AnswerRunner leases check:answer jobs (lease_next_any, fenced from every external audience) and re-checks each batch, fed by a safety-net sweep (sweep_all_at — tenant discovery via the SAVED_ANSWER schema-CF label scan; check_at re-stamps checked_at on due-by-age answers so stable answers leave the due set for a full recheck_max_age_days); both spawned in runtime when answers.enabled. 6 engine tests (added refresh_supersedes_and_links, sweep_enqueues_due_and_runner_rechecks). STILL DEFERRED (spec v0.5 changelog): the watch-driven precise check:answer trigger (needs a dependency→answer reverse index; the sweep is the safety net + emit today), and the pending/conflicted + current-on-untouched §12 tests (need a mock embedder / clarify harness). After PR-087. SAVED_ANSWER nodes pinning generation traces (dependency set = the trace's per-claim evidence coordinates + pending clarifications, never re-derived); save auto-creates an internal watch; check:answer re-check engine (tombstone / winner-moved / clarification-bound-or-conflicted rules + matcher-based re-verify clear-back leg) drives a deterministic status fold (current / partially_stale / stale / pending / conflicted / invalidated / superseded); refresh creates-never-mutates via SUPERSEDES edges; worse transitions emit answer_stale through the watch seam. Re-checks never generate. GDPR: draft text joins the PR-081 surface register. Acceptance: answer_stale_on_correction, answer_current_on_untouched_change, answer_pending_and_conflicted, answer_invalidated_on_tombstone, answer_refresh_supersedes, answer_recheck_idempotent, answer_no_generate_on_check, answer_fence_and_isolation.

PR-089 · feat/089-memory-context — Memory context endpoint (planner without generation)

Status: ✅ Built (2026-07-07; spec v0.3 Approved, v0.4 as-built). Gate-green on the dev server (fmt/clippy -D warnings/full suite, 0 failures; 8 §12 tests pass). SHIPPED: POST /v1/memory/context (planner-parity plan, span-grade items with source coords/score/validTime/pendingClarification, openQuestions, honest stats, kind:"context" trace; no LLM, no egress), MCP memoryContext (tools.json → 12 tools), CLI telha memory context, [context]+[answers] config. As-built deviations in the spec v0.4 changelog (per-item validTime resolved via span-ledger backing node; TS/Python SDK methods deferred). POST /v1/memory/context = the PR-050 planner exposed as the Memory Gateway's memory.context verb: one-planner-two-surfaces parity invariant with /v1/generate, no LLM required or consulted (works with zero [llm]/[embedding] config), span-grade items (text + source coords + scores + validity), clarification-caveat openQuestions, honest budget/cap stats, persisted kind:"context" traces, CLI telha memory context, MCP memoryContext, TS/Python SDK methods. No summary field in v1 (recorded deviation — summaries are generation). Acceptance: context_matches_generate_plan, context_no_llm_required, context_budget_adherence, context_open_questions_parity, context_bitemporal, context_trace_roundtrip, context_tenant_isolation_and_shaping, context_empty_is_ok.

PR-090 · feat/090-memory-verify — Standalone claim-verification endpoint

Status: 🟢 CORE built + gate-green 2026-07-07 (this session; pushed server main @9cd3808, built on dev server: cargo check/clippy -D warnings/fmt clean, 20 verify tests pass incl. 4 new api::verify + the 16 existing verify-module tests unbroken by the seam refactor). SHIPPED: [verify] endpoint config keys; richer Verifier::score_claim_evidence seam (per-candidate scores/signals/best span — score_claim now delegates to it, PR-088's seam intact) + public resolved_embed_model/has_embedder; api/verify.rs (POST /v1/memory/verify, run_verify shared for the coming MCP tool) with bounded-cosine candidate retrieval (reuses planner recall, expand off, non-binding budget, truth-only, against.sourceIds post-filter pin) + up-front embedding-required 422 + model-registration 422 + kind:"verification" trace; verify_config on AppState; router wired. Tests: verify_validation_requires_a_claim, verify_embedding_required, verify_disabled_returns_404, verify_too_many_claims. AS-BUILT DEVIATION: candidate retrieval reuses the planner's recall leg (the one place nodes become spans-with-text) rather than a bespoke bounded search — a big non-binding budget + span-cap truncation approximate "top-m cosine, not the budgeted planner". CLI + MCP now landed (2026-07-07, @7311301): telha memory verify "<claim>" [--against-source <id>] [--valid-at <ts>] (Verify variant on MemoryCommand, shared one-shot AppState with context) and MCP memoryVerify tool (MemoryVerifyParams; shaped envelope + traceId/modelUsed; tools.json regenerated → 15 tools; mcp_conformance 13/13 + lib snapshot green). REMAINING FOLLOW-UPS (each a CROSS-CUTTING pass, not verify-specific — deliberately deferred): (1) TS/Python SDK memory.verify — belongs in a coherent SDK-Gateway pass; the SDKs have NO memory.context/generate either, so a lone verify method would be inconsistent. (2) OpenAPI derive annotation — the openapi_contract EXPECTED mirror confirms ALL Gateway/watch/answer routes (context/verify/watches/answers) are uniformly absent from openapi.json (a PR-085-recorded gap); annotating only verify would be inconsistent — do the Gateway surfaces together. (3) Embedder-dependent §12 tests (verify_bands, verify_numeric_swap_capped, verify_pinned_sources, verify_bitemporal, verify_unsupported_reason_*, verify_entity_swap_limitation, verify_trace_and_isolation, verify_shared_seam) need a mock-embedder harness that does not yet exist in the suite (same gap PR-088 hit). Gate at @7311301: verify 21, mcp 6, mcp_conformance 13, openapi_contract 3, clippy -D/fmt clean. Spec v0.2 Approved 2026-07-06 (J. Porter — all 4 OQs resolved; unsupportedReason added). Exposes PR-052's matcher as POST /v1/memory/verify (the Gateway memory.verify verb): CLAIMS-in / no-generation (no decompose → no [llm] needed), but embedding-REQUIRED — the honest difference from memory.context, since verification is cosine·lexical fusion; no embedding model resolves → 422 VERIFY_UNAVAILABLE, never a silent lexical-only verdict. Enforced model consistency (claim + candidate spans one model, PR-052's chain), bounded cosine candidate retrieval (RBAC/temporal-scoped, pinnable to against.sourceIds; NOT the budgeted planner), PR-052 verdict bands + whole-pair numeric gate reused unchanged, kind:"verification" traces, dedicated verify_* telemetry, signals (lexical/cosine/numericGate) returned by default as a verdict's justification, entity-swap NLI limitation carried forward honestly (canary test). Ships a shared matcher-module seam that PR-088's saved-answer re-check (§3.4iv) consumes instead of duplicating. CLI telha memory verify, MCP memoryVerify, TS/Python SDK. Three verdicts kept + unsupportedReason (no_evidence_found | insufficient_support | numeric_or_date_mismatch, present only when unsupported) so agents get the "why not" without a contradicted verdict the matcher can't guarantee. Draft-in decomposition + true contradicted deferred to the NLI phase (OQ-1/OQ-2). After PR-089 (shares DTO conventions + candidate-retrieval seam); independent of 087/088. Acceptance: verify_bands, verify_numeric_swap_capped, verify_no_llm_required, verify_embedding_required, verify_model_consistency, verify_pinned_sources, verify_bitemporal, verify_empty_unsupported, verify_unsupported_reason_{no_evidence,insufficient_support,numeric_mismatch}, verify_entity_swap_limitation, verify_trace_and_isolation, verify_shared_seam.

PR-091 · feat/091-memory-cards — Teams/Slack memory cards & weekly brief

Status: ⬜ Spec v0.1 Draft (app-layer/2026-07-06-memory-cards.md, drafted 2026-07-06 — awaiting review). Written as a COMPANION spec to chat-delivery-surface (not an addendum edit — that spec was being actively revised by the parallel Block Kit session, v0.7 @ 21:04; a companion defers all transport/identity/nonce/RBAC to it and composes its slack_blocks.ts primitives, avoiding the clobber hazard while keeping one-bot doctrine). Renders what 087/088 produce: Memory Change card, Weekly Memory Brief (per-recipient deduped fold over durable WatchFire rows), Saved Answer Updated card ([Refresh answer]✓confirm [View changed claims] [Keep original]), plus a [Watch this] action on the ALREADY-BUILT grounded ask-answer card (v0.7 = Memory Answer + Prove loops; so 3 of the plan's 5 human loops already ship — Watch + Brief land here). Adds notify: to the clarify-bot poll set with a NotifyDeliveryEntity idempotent on (fire_id, recipient), three additive discriminated action kinds (watch_create/answer_refresh/watch_mute, bound-as-acting-person — the RESPONDER_ASSERTION_FORBIDDEN analogue). DELIVERY RULE (v0.2, review 2026-07-06): every card is DM-only; a shared/team watch fans out to its authorised members' DMs (per-person RBAC at fire time), NO channel content in v1 — chat-delivery §3.1's channel-posting ban is preserved, not excepted (channel membership isn't a Telha access boundary; even names/counts leak). Governed cleared-channel posting deferred (memory-watch OQ-5, doorbell/room split). After PR-087 (Memory Change/Brief/[Watch this]) + PR-088 (Saved Answer card). Single-writer: claim in build doc before touching clarify-bot; rebase onto the landed v0.7 builder. Acceptance: card_watch_change_dm, card_shared_fanout_rbac, card_saved_answer_updated, card_saved_answer_render_safety, card_notify_idempotent, card_brief_dedup, card_action_watch_create, card_action_refresh_confirm, card_action_bind_as_person, card_fence.

Phase 3d exit criteria: a fact correction flows change → watch fire → debounced DM card AND a stale saved answer alert end-to-end on a live tenant; an external agent retrieves permission-scoped context via memory.context (MCP + REST) with zero LLM config; the weekly brief posts to a channel with channel-safe content; all named watch_*, answer_*, context_* tests green.


Phase 4 — Production Hardening (ongoing; epics, to be decomposed later)

These are intentionally left as epics — each will get its own PR breakdown when scheduled, per the spec-first rule:

  • EPIC-401 FoundationDB backend — implement StorageEngine against FDB; PR-012's conformance suite is the acceptance gate. Watch items: FDB 100KB value / 10MB txn limits require chunking large nodes and re-scoping cascade batches.
  • EPIC-402 Prefix-cache-aware prompting — builds on PR-051's prefix-stable assembly; provider cache-hit telemetry.
  • EPIC-403 Tenant performance isolation — per-tenant rate limits (tower middleware), compaction tuning, noisy-neighbor bench scenarios added to PR-029 harness.
  • EPIC-404 Platform connectorspartially superseded 2026-07-04 by the source-connector-framework spec and Phase 3b (PR-065..069 cover SharePoint/Exchange/Slack/Salesforce/GDrive); Jira/ServiceNow/GitHub remain epic-scoped and reuse the same framework when scheduled.
  • EPIC-405 Materialized snapshot cache — cache keyed on (scope, T, query-hash) with invalidation on tx-time watermark advance.
  • EPIC-406 Marketplace packaging — Open Mercato module packaging of the telha module + ai-assistant.

Part 3 — Decision log & immediate next steps

Decision log (ratified 2026-07-02)

Flag Decision Landed in
F1 Tenant isolation by construction: exclusive KeyCodec, hidden raw constructors, CI iterator-gate 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 — amended: append-only tombstone entries, never removals; readers filter by end-boundary validation 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 Perf claims become CI benchmark gates PR-029
F7 Decay/budget/verification math peer-reviewed in specs before any code PR-002 gate; PR-022/050/052
F8 Generation in core for v1 behind LlmProvider trait PR-051
F9 rmp-serde standard; FlatBuffers deferred PR-013
F10 Serde-first AST (already planned); Roaring Bitmaps — amended: shipped as PR-022b accelerator with temporal post-validation, after PR-029 baselines PR-021, PR-022b
OQ-3 Reserved [0u8;32] nil-tenant system namespace for system jobs; unforgeable externally (SystemScope internal-only, nil rejected at auth + provisioning) — PR-012 unblocked Key-layout spec v0.2, PR-012, PR-030

Immediate next steps

(Updated 2026-07-03 night — Phase 0/1 complete, Phase 2 complete except PR-043.)

  1. Build PR-043 done 2026-07-04 — Phase 2 is code-complete. Field-encryption spec v0.3 carries the as-built record; grpc-contracts v0.4 records the wire additions (client-supplied ids, UpdateRecord, $bytes).
  2. Start PR-050 (memory planner) done 2026-07-04 — the whole 050→051→052 chain is built and gate-green. PR-053 / PR-054/055 done 2026-07-04 (compare engine + code/email workers; see v2.4). PR-056 / PR-057 done 2026-07-04 — Phase 3 is code-complete (see v2.6). Still worth doing live: /v1/generate click-through vs a real provider + the mercato chat/trace-viewer browser pass (needs an MCP-enabled binary + LLM key); email→PDF→docling fan-out once a docling worker with the parse extra runs; wire eval gate into the release pipeline when a release cadence exists. 2b. Run PR-042's mercato chat click-through against a live MCP-enabled binary (live chat-e2e passed 2026-07-04). 2c. Phase 3b nearly complete: PR-060/061/062/063/064/065/066 all built 2026-07-05 — the clarification loop runs end-to-end (detection → routed DM card → quorum-checked answer → corrected fact version) and SharePoint syncs through the framework. Remaining: PR-067 (Exchange) / PR-068 (Slack) / PR-069 (Salesforce) connectors by demand, PR-070 (ask-in-chat, after 063✅). Exit criteria need a LIVE tenant click-through (Teams credentials + real workspace) before Phase 3b closes.
  3. Obtain F7 math sign-offs for memory-planner and claim-verification done 2026-07-04 (both specs v0.2 Approved with conditions) — PR-050 is now fully unblocked and startable; the generation-credentials security sign-off remains the gate for PR-051.
  4. Run the Phase-2 exit checklist (§4.2): the pieces are individually green (ingestion e2e with provenance, lease/reaper, recall ≥0.95@10, hybrid queries) — MCP canonical chat scenarios and encryption tests remain.
  5. Fix reference benchmark hardware so the F6 gates have a stable baseline (current numbers are from the Windows dev box).
  6. Pay down recorded debt: utoipa OpenAPI derive pass (PR-024) done PR-085, shared WriteGate, telha debug cascade-check CLI, UDS test legs on a unix host, mercato seed-button click-through, idempotency-key dedup, cleanup of orphaned streamed source versions done PR-083, docker image build verification done PR-084.
  7. Phase 3d (added 2026-07-06): all three specs are v0.2 In Review — J. Porter's OQ review is incorporated (10/10 resolved; WatchFire durability stance change, severity axis, render-safety invariant); the formal Approved flip is the ONLY remaining gate. On approval: PR-087 and PR-089 startable in parallel; PR-088 after 087; PR-090/091 spec passes follow. Ordering rationale ratified by J. Porter: watch first (the new primitive; reactive → proactive), saved answers second (differentiator, needs watch), context third (cheapest, developer-facing — deliberately not first because it improves access, not perceived behaviour).

Part 4 — Governance & Operations

4.1 Spec inventory & status

Spec (.ai/specs/…) Gates Status
_TEMPLATE.md + README + linter + CI gate everything Built & tested (PR-002 package; linter green on real specs, all 11 rules verified against broken fixtures)
core-engine/2026-07-02-composite-key-layout.md PR-011, PR-012 v0.2 review-ready (OQ-3 resolved; PR-012 unblocked on approval)
core-engine/2026-07-02-tombstone-cascade.md PR-019 v0.1 review-ready (encodes corrected F3; cascade journal design included)
core-engine/2026-07-02-version-record-model.md PR-013 v0.1 review-ready (rmp-serde + version byte, half-open intervals, provenance struct)
core-engine/2026-07-02-global-temporal-scan.md PR-016 v0.1 review-ready (same-batch maintenance, validation-via-primary, horizon + long-lived-set mitigation)
core-engine/2026-07-02-traversal-semantics.md PR-018 v0.1 review-ready (BFS shortest-path-first, budgets, dual edge+node validation, determinism)
core-engine/2026-07-02-schema-inference.md PR-020 v0.1 review-ready (type lattice with laws property-tested, same-batch schema versions)
core-engine/2026-07-02-query-language.md PR-021 v0.1 review-ready (serde-first grammar, HMAC cursors, shared fixture corpus)
core-engine/2026-07-02-confidence-decay.md PR-022, PR-040 v0.1 drafted — F7 math sign-off REQUIRED before code (multiplicative decay, geometric-mean fusion, defaults w=0.7/ε=0.05/α=0.6)
core-engine/2026-07-02-bitmap-predicate-index.md PR-022b v0.1 drafted — design settled (ever-matched superset bits, mandatory temporal post-validation, differential gate); activation thresholds await PR-029 data by design
integration/2026-07-02-grpc-contracts.md PR-025 v0.1 review-ready (signed tenant tokens, UDS/TCP, buf evolution rules, error mapping)
integration/2026-07-02-data-split-routing.md PR-028 v0.1 review-ready (three-question routing test, ROUTING.md convention, promotion/demotion policy)
core-engine/2026-07-02-job-queue-leases.md PR-030/031 v0.1 review-ready (atomic state-moves, lease generations, reaper, poison handling, nil-namespace system jobs)
core-engine/2026-07-02-ingestion-provenance.md PR-032 v0.4 built (v0.2/0.3: options/stats/childJobs, streaming; v0.4 §17: deterministic node-id upsert + inline child content + child-enqueue dedup, PR-054/055 enablers)
core-engine/2026-07-02-docling-projection.md PR-034/061 v0.3 built (v0.2 PR-034 as-built; v0.3 = §15 temporal ambiguity detection addendum, PR-061)
core-engine/2026-07-02-web-ingestion.md PR-035 v0.1 review-ready (Fetcher protocol, Playwright default, backend-equivalence gate, robots policy)
core-engine/2026-07-02-tabular-projection.md PR-036/061 v0.4 built (v0.2/0.3 PR-036 + streaming as-built; v0.4 = §15 temporal ambiguity detection addendum, PR-061)
core-engine/2026-07-02-vector-storage.md PR-038 v0.1 review-ready (F4 canonical CF, model registry, classified-content exclusion function)
core-engine/2026-07-02-hnsw-partitioning.md PR-039/040 v0.1 review-ready (open/sealed lifecycle, lazy-load + LRU budget, rebuild doctrine, multi-bucket merge)
core-engine/2026-07-02-mcp-tools.md PR-041/042/056 v0.4 Approved + built (v0.2 PR-041/042 as-built; v0.3 compareSnapshots active; v0.4 getTrace claim-grouped shaping)
app-layer/2026-07-02-field-encryption.md PR-043 v0.3 Approved + built (security-approved v0.2 conditions all implemented; v0.3 = as-built record: transport-level integration, $bytes write convention, client-supplied-id + update wire additions in grpc-contracts v0.4)
core-engine/2026-07-02-memory-planner.md PR-050 v0.3 Approved + built (F7-approved v0.2 conditions all implemented; v0.3 = as-built record: same-(source,version) dedup, stranded-budget definition, per-leg S(n) mapping, plan carries span text)
core-engine/2026-07-02-generation-orchestration.md PR-051 v0.3 Approved + built (security-approved v0.2 conditions all implemented; v0.3 = as-built record: sink-level structural redaction, fixed-length cost windows, cap-override + tokenizer deferrals)
core-engine/2026-07-02-claim-verification.md PR-052 v0.3 Approved + built (F7-approved v0.2 conditions all implemented incl. geometric fusion + whole-pair numeric gate; v0.3 = as-built record: ∀-coverage gate, structural embedding consistency, entity-swap canary)
core-engine/2026-07-02-snapshot-compare.md PR-053 v0.2 Approved + built (as-built: id-ordered primary-CF walk, node-scoped filters, touch resolved per OQ-1 stance, cursor/ceiling semantics pinned)
core-engine/2026-07-02-code-projection.md PR-054 v0.2 Approved + built (as-built: manifest→child-per-file fan-out, deterministic decl ids, same-file-only CALLS, grammar pins)
core-engine/2026-07-02-email-projection.md PR-055/061 v0.3 Approved + built (v0.2 PR-055 as-built; v0.3 = §15 envelope-date ambiguity detection addendum, PR-061)
core-engine/2026-07-02-eval-harness.md PR-057 v0.2 Approved + built (standalone /eval crate, deterministic mock provider, ratchet gates, nightly workflow; v1 cuts recorded)
core-engine/2026-07-02-gdpr-erasure.md GA release v0.1 drafted — pre-GA blocking; legal + engineering review required (tenant DeleteRange + verification scan, subject-scoped DEK crypto-shred + ledgered redaction rewrite, backup restore-hook)
integration/2026-07-04-temporal-clarification.md PR-060/061/063/064/070 v0.9 Approved + PR-060/061/063/064 built — loop end-to-end (v0.9 = PR-063 delivery note: escalation = lease-expiry advancement, GET-by-id + askedAliases, delivery-leg tests green)
integration/2026-07-04-entra-identity-connector.md PR-062 v0.5 Approved + built (v0.4 approval conditions; v0.5 = PR-062 as-built: {address}-only placeholders, non-smtp proxies dropped, group @removed no-write limitation, verified-match error taxonomy)
app-layer/2026-07-04-chat-delivery-surface.md PR-063/070 v0.5 Approved + built (v0.5 = PR-063 as-built: lease/no-submit delivery protocol, askedAliases consumption, unmapped-identity no-write refinement, jose ^6, stewardAliases)
integration/2026-07-04-source-connector-framework.md PR-065..069 v0.4 Approved + core built (v0.3 namespace-delete condition; v0.4 = PR-065 as-built: options.sourceMeta w/ required connector field, third token shape, sync-result chain over SubmitIngestionResult, REST upsert flag)
integration/2026-07-06-memory-watch.md PR-087 ✅ built v0.5 Approved + as-built (2026-07-07) — PR-087 SHIPPED and gate-verified green on the Linux dev server (28 suites + clippy + fmt); tx-index change feed w/ checkpointed sweeps, per-target delivery (chat/webhook/internal), WebhookRunner (HMAC-blake3), query/clarification/source matching, REST/CLI/MCP surfaces; NO channel content (shared→DM fan-out); as-built deviations in the v0.5 changelog
core-engine/2026-07-06-saved-answers-staleness.md PR-088 v0.3 Approved 2026-07-06 (J. Porter) (SAVED_ANSWER nodes over pinned traces, four-rule re-check + pure status fold w/ separate severity axis, refresh-via-SUPERSEDES, render-time permission-safety invariant §3.10)
core-engine/2026-07-06-memory-context.md PR-089 v0.3 Approved 2026-07-06 (J. Porter) (one-planner-two-surfaces parity, no-LLM guarantee, span-grade items + openQuestions, kind:"context" traces, dedicated context_* telemetry namespace)
core-engine/2026-07-06-memory-verify.md PR-090 v0.2 Approved 2026-07-06 (J. Porter) (PR-052 matcher as POST /v1/memory/verify; claims-in/no-generation but embedding-REQUIRED, model consistency, bounded cosine retrieval, three verdicts + unsupportedReason, kind:"verification" traces, shared matcher seam for PR-088)
app-layer/2026-07-06-memory-cards.md PR-091 v0.4 Approved 2026-07-06 (J. Porter — OQ-1 write-action auth SECURITY COUNTERSIGNED) (companion to chat-delivery-surface: DM-only cards, shared watches fan out to authorised members, NO channel content; write-actions via internal single-use person-scoped core.card_write_action token, core does final authz; refresh inherits generation gate; cleared-channel deferred)

4.2 Phase gates (exit checklists)

Phase 0 → clean-clone builds pass in all three toolchains; spec linter + gate live in CI; iterator-gate live; retroactive topology spec merged.

Phase 1 → all of PR-010..029 merged; bench p50 1-hop < 1ms warm on reference hardware, recorded; tenant-isolation matrix + nil-scope unreachability green; ghost-edge invariants I1–I3 green incl. journal replay; app-layer demo page reads Telha over UDS with two-tenant isolation asserted; every layout/semantics decision covered by an Approved spec.

Phase 2 → doc/web/tabular/JSON ingestion e2e with span provenance; lease contention + reaper recovery green; HNSW recall ≥0.95@10 and rebuild-equivalence green; hybrid (temporal+graph+vector) queries live; MCP chat answers the four canonical scenarios; encryption opacity + no-classified-content-in-indexes tests green.

Phase 3 → /v1/generate returns verified, traced answers; adversarial planted-falsehood suite green; compare live end-to-end incl. chat; eval harness gating release builds on claim-support precision floor.

4.3 Risk register

# Risk Likelihood Impact Mitigation
R1 Query executor scope creep breaks Phase-1 schedule Med High F10: serde-first AST; bitmaps split to PR-022b post-benchmarks; executor stays scan-based baseline
R2 Silent temporal-correctness bug (worst-class: invisible to current-state tests) Low-Med Critical Append-only doctrine everywhere; I2 byte-identical backward checks; differential bitmap gate; brute-force reference implementations in tests
R3 Tenant leakage via ad-hoc key construction Low Critical F1 by-construction stack: KeyCodec exclusivity, pub(crate), CI iterator-gate, proptest/fuzz, isolation matrix
R4 HNSW memory footprint at many (tenant × model × bucket) partitions Med Med Sealed-partition unload/lazy-load; monthly buckets; derived-index doctrine makes eviction safe; monitor in soak
R5 Firecrawl licensing/cost surprises Closed F5 Fetcher trait + Playwright fallback shipped
R6 LLM credential surface in core binary Low Med F8: trait-contained, env/file only, never logged, per-tenant cost caps; migration path documented
R7 50k-edge cascades stalling WAL / memory spikes Med High Cascade journal + chunking, node-tombstone-last ordering, kill-9 replay tests at every chunk boundary
R8 Bench targets miss on reference hardware Med Med F6: targets are gates from Phase 1, so misses surface in month 1–2, not at launch; tuning levers documented (block cache, bloom config)
R9 Spec process friction → gate label abuse Med Low no-spec-needed emits CI warning (visible), requires justification; review offense per README
R10 GDPR erasure vs append-only unresolved at GA Med High Pre-GA spec required (inventory §4.1); physical layout already erasure-capable (contiguous DeleteRange)

4.4 Development environment

  • Toolchains: Rust pinned via rust-toolchain.toml; Node 22 (Yarn workspaces + Turborepo); Python via uv workspace.
  • Local stack: docker compose up (added in PR-033) → telha-core + PostgreSQL + Redis; workers run via python -m workers_common.run <worker> against the composed core; app layer yarn dev connects over UDS (Linux/macOS socket path in compose volume) or localhost TCP.
  • Pre-commit: rustfmt, clippy, eslint/prettier, ruff, spec-lint --file on staged specs.
  • CI: path-filtered per-language pipelines; spec lint + gate; iterator grep-gate; buf lint/breaking on /proto; nightly fuzz (key decoder) + criterion benches with regression thresholds; release job builds linux x86_64 + aarch64 binaries.
  • Reference hardware for benchmark gates: to be fixed in PR-029 commit 1 and recorded in the bench runbook — all p50 numbers in this doc are relative to it.

4.5 Team lanes (Phase 1)

Lane Owns Critical path
Storage/Temporal (Rust) PR-010→022 Key spec approval → PR-011 → PR-012 unblocks everything
API (Rust) PR-023→026 Can start PR-023/025 immediately after PR-010
App/SDK (TS) PR-027→028 Starts on PR-024/026 contract stability (OpenAPI + proto)
Shared PR-001..003, PR-029 PR-002 package is pre-built; land day 1

4.6 Changelog

  • 2026-07-06 — v3.11: PR-090 memory-verify APPROVED (J. Porter) — Phase 3d is fully spec-complete and every spec is Approved (087/088/089/090/091). All four OQs resolved; the one refinement is unsupportedReason (no_evidence_found | insufficient_support | numeric_or_date_mismatch, present only when verdict = unsupported, fixed precedence) — applications get the "why not" without a contradicted verdict the lexical-semantic-numeric matcher can't safely guarantee (true contradiction deferred to the NLI phase). Also: draft-in decomposition deferred to an explicit {text, decompose:true} [llm] mode; global ceilings + verify_* telemetry (incl. verify_unavailable_count, verify_numeric_gate_capped_count) in place of per-tenant limits; signals returned by default as a verdict's justification. 41 specs lint-green. Phase 3d now has ZERO spec gates remaining — buildable on dependency order alone: PR-087 + PR-089 parallel → PR-088 after 087, PR-090 after 089 → PR-091 after 087/088.

  • 2026-07-06 — v3.10: PR-090 memory-verify spec drafted (core-engine/2026-07-06-memory-verify.md v0.1 Draft) — Phase 3d is now SPEC-COMPLETE. Exposes PR-052's matcher as POST /v1/memory/verify (Gateway memory.verify): claims-in/no-generation, but embedding-REQUIRED (cosine·lexical fusion — the honest difference from memory.context; no model → 422 VERIFY_UNAVAILABLE, no silent lexical-only fallback), enforced model consistency, bounded cosine candidate retrieval (RBAC/temporal-scoped, pinnable to sources — NOT the budgeted planner), PR-052 bands + numeric gate reused, kind:"verification" traces, dedicated verify_* telemetry, signals returned by default as a verdict's justification, entity-swap NLI limitation carried forward honestly. Ships a shared matcher-module seam PR-088's re-check consumes (no duplication). Deferred: draft-in decomposition + a contradicted verdict (OQ-1/OQ-2). Sequenced after PR-089 (shared DTO + retrieval seam), independent of 087/088. 41 specs lint-green. Phase-3d spec set: 087/088/089/091 Approved, 090 Draft awaiting review — that one review is the only spec gate left in the phase.

  • 2026-07-06 — v3.9: Phase 3d specs ALL APPROVED (J. Porter) — the memory-cards OQ-1 write-action security countersign cleared the last gate. memory-watch → v0.4, saved-answers → v0.3, memory-context → v0.3, memory-cards → v0.4, all Approved; §4.1 rows flipped. OQ-1 countersign hardened the write-action token: internal-only audience core.card_write_action, single-use jti (replay-protected via a used-jti store), scope_fingerprint (tamper / exceeds-permitted-action guard), generation_id (ties a refresh to the exact answer the card showed), core performs the final authorization (the RESPONDER_ASSERTION_FORBIDDEN trust shape). No other design change on the flip. Phase 3d is now buildable on dependency order alone: PR-087 (memory-watch) + PR-089 (memory-context) in parallel; PR-088 (saved-answers) after 087; PR-091 (memory-cards) after 087/088. Remaining Phase-3d spec work: PR-090 (memory.verify) still needs its own small spec pass. 40 specs lint-green.

  • 2026-07-06 — v3.8: Phase 3d open-question review complete (Claude, delegated by J. Porter) — memory-cards → v0.3; the four remaining OQs across the specs are now decided or explicitly deferred. OQ-1 (the one gate): card write-actions ([Watch this]/[Refresh answer]/[Stop watching]) execute under a short-lived, single-use, PERSON-SCOPED chat_action token — signed by the tenant key but scoped to {person_id, role, action_kind, target_id, exp≈120s}, core executes deny-by-default under that person+role, the tenant service token never performs the write directly (least privilege over reusing the clarify token or deep-linking; the RESPONDER_ASSERTION_FORBIDDEN generalization). It adds a core token audience, so J. Porter's explicit security countersign gates PR-091 code (recorded, countersign slot open — same handling as the responder-assertion rule). OQ-2: refresh inherits the steward/admin generation gate; non-generation roles get [View changed claims]+deep-link, no dead-end button. OQ-4: mirror Slack payloads + Teams shape test + fix the pre-existing Teams answerCustom bug in PR-091. OQ-5/memory-cards-OQ-3 (cleared-channel): kept deferred as a named post-Phase-3d candidate with two constraints pinned (implicit membership-as-access permanently rejected; any future channel post is doorbell-only). memory-watch/saved-answers/memory-context have NO open OQs. 40 specs lint-green. Net: the four Phase-3d specs are approvable; the only pre-code gate left is the OQ-1 security countersign.

  • 2026-07-06 — v3.7: channel delivery removed from Phase 3d at review (J. Porter): channel membership is NOT a Telha access boundary, so no memory content goes to a chat channel in v1. memory-watch → v0.3, memory-cards → v0.2. The reasoning: a channel's membership is administered in Slack/Teams, outside Telha's deny-by-default/Entra-mapped/audited RBAC — and even entity names and counts are content that leaks to unauthorised present-and-future members, so the v0.1 "channel summary-only exception" was defending the wrong line. New model: a shared/team watch (chat_channelchat_shared {audience}) fans out to the DMs of its individually authorised members (each RBAC-checked at fire time); an unauthorised member gets nothing; there is no channel-post code path, so the leak is structurally impossible, not merely policy-forbidden. Making channel membership an IMPLICIT access grant is rejected outright; an EXPLICIT, audited admin cleared-channel binding (doorbell/room split: channel carries a content-free "activity" signal, facts fetched per-person via the authenticated app) is the only acceptable channel path and is deferred to its own design pass (memory-watch OQ-5, memory-cards OQ-3). Tests watch_channel_summary_onlywatch_shared_fanout_rbac and card_channel_summary_onlycard_shared_fanout_rbac. 40 specs lint-green.

  • 2026-07-06 — v3.6: PR-091 memory-cards spec drafted (app-layer/2026-07-06-memory-cards.md v0.1 Draft) — the human-chat rendering that loops Phase 3d into the two surfaces. Written as a COMPANION to chat-delivery-surface rather than an addendum edit: the parallel Block Kit session bumped that spec to v0.7 at 21:04 the same day (typed slack_blocks.ts builder + validateMessageBlocks + the grounded ask-answer card = the Memory Answer/Prove loops), so a concurrent edit risked the documented clobber hazard; the companion defers ALL transport/identity/nonce/verify/RBAC/idempotent-send to chat-delivery and composes its primitives. Realization that shrank PR-091: 3 of the product plan's 5 human loops (Ask/Prove/Correct) already ship, so PR-091 = the two missing loops (Watch, Brief) + the Saved Answer Updated card + a [Watch this] action on the existing ask card. Owns: three card templates, the notify: consumer in clarify-bot (NotifyDeliveryEntity idempotent on WatchFire fire_id), per-recipient brief dedup, three additive discriminated action kinds (watch_create/answer_refresh/watch_mute, bound-as-acting-person = the RESPONDER_ASSERTION_FORBIDDEN analogue), and THE ONE NEW TRANSPORT RULE — channel summary-only rendering, the explicit bounded exception to chat-delivery §3.1's blanket channel-posting ban that memory-watch's channel watches force. 40 specs lint-green. Also confirmed the two-surface model: agents (MCP/API, Gateway verbs) vs humans (Slack/Teams cards — this + chat-delivery; the web fact/document browser is a LATER track, @telha/ui+@telha/search are empty stubs). §4.1 row + PR-091 status + Phase-3d intro updated.

  • 2026-07-06 — v3.5: Phase 3d OQ review complete (J. Porter) — all 10 OQs resolved across the three specs, all → v0.2 In Review; the Approved flip is the only remaining gate. One stance CHANGED: memory-watch OQ-2 — fire history is durable in v1 (minimal WatchFire rows in watch-fire/*, persisted in the same WriteBatch as delivery job + checkpoint; status lifecycle, fire_keep_per_watch pruning, GET /v1/watches/:id/fires, watch_fire_durable test; rationale: fires power staleness/briefs/escalation/"why was I notified?" — the audit sink alone is a weak product object). Two refinements incorporated: saved-answers severity as a separate axis (numeric-gate materiality escalates presentation, never the status fold) + render-time permission safety as hard invariant §3.10 (answer TEXT never posts to shared channels without audience-safe rendering; per-answer ACLs stay deferred); memory-context gets a dedicated context_* telemetry/quota namespace separate from generate (rejection accounting + per-item score components both routed to a future explain: true mode). Confirmed as drafted: no label predicates (query watches), duplicate fires accepted w/ PR-091 recipient-level dedup, no system watches (ops = PR-086b), refresh human-initiated, aggregate planner stats only, no confidence bands. Cross-spec linkage added: PR-088's check:answer/claims_json carry fire_id. 39 specs lint-green.

  • 2026-07-06 — v3.4: Phase 3d — Active Memory & Gateway added (PR-087..091) from the 2026-07-06 product-improvement review (gap analysis vs the "enterprise memory layer" plan: change subscriptions, stale saved answers, and a generation-free planner surface are the genuine gaps; the plan's other asks are largely built). Three specs drafted, all v0.1 Draft + lint-green (39 specs total): integration/2026-07-06-memory-watch.md (PR-087 — tx-index change feed with checkpointed deterministic sweeps, debounce/digests incl. the Weekly Memory Brief, dual permission check with auto-suspend, notify:watch/webhook/internal delivery, confidence_dropped excluded with rationale), core-engine/2026-07-06-saved-answers-staleness.md (PR-088 — SAVED_ANSWER nodes pinning traces, four-rule re-check + deterministic status fold, refresh-via-SUPERSEDES, re-checks never generate), core-engine/2026-07-06-memory-context.md (PR-089 — one-planner-two-surfaces parity with /v1/generate, no-LLM/no-egress guarantee, span-grade items + clarification openQuestions, no summary field by design). PR-090 (memory.verify surface) and PR-091 (Teams/Slack memory cards + brief rendering; chat-delivery addendum) numbered as placeholders pending their own spec passes. Build order ratified by J. Porter: 087 → 088, with 089 parallel. Spec approval is the only gate (PR-002 doctrine). Next-steps item 7 added; item 6 debt list synced (utoipa/orphan-sweep/docker done via 085/083/084).

  • 2026-07-05 — v3.3: Phase-3c consolidated review complete (J. Porter). backup-restore → v0.2 Approved with conditions (mandatory restore index-check, PG as second backup surface — both REQUIRED follow-ups on as-built PR-080 before pilot data; incremental tripwire; local-dir v1 contract). gdpr-erasure → v0.2, approval HELD: canonical-blob redaction design (new OQ-3) + surface enumeration + PG handling + restore/ledger interaction now normative; OQ-1 pinned for counsel. sso-auth → v0.2 Approved (all OQs resolved as drafted). retention-archival → v0.2 Approved (normative dry-run, double hold check + retention_hold_recheck, encryption wording fix). Cross-spec principles recorded: state completeness (PG is first-class state), ledger-aware restore as a shared recovery contract, no casual bypasses on integrity gates, defer-with-tripwires. PR-082/083 READY TO START; PR-081 held pending design pass + legal.

  • 2026-07-05 — v3.2: PR-067/068/069/070 + PR-080 built (five PRs in parallel: three connector agents + ask-in-chat agent + backup/restore by the main session) — Phase 3b is CODE-COMPLETE and the durability gap is closed. Connectors: Exchange (per-mailbox delta, MIME → email projection, OQ-3 shared attachments landed alongside), Slack (thread docs via json bypass, rollup ceiling, no-delete v1), Salesforce (per-object windows, describe-shaped json records, User-email principals, ContentDocument files) — framework spec v0.6/0.7/0.8 as-builts; workers suite 365 green. PR-070: ask-in-chat live behind ask.enabled (ai-assistant runChat through the clarify-bot router; free-text-never-binds re-proven with ask ON; chat-delivery v0.6). PR-080 (spec-first: backup-restore v0.1 In Review, countersign pending): StorageEngine::checkpoint, BLAKE3-manifested create/verify/restore, empty-target + verify-first + erasure-ledger seam, retention, [backup] ticker + CLI; 7 e2e tests incl. restore-under-writes and tamper detection. OPERATIONAL INCIDENT recorded: a second concurrent session built a competing PR-068 variant and clobbered the package mid-build twice; the landed tree is the spec-pinned implementation (framework v0.8 coordination note) — single-writer discipline per package is now the recorded rule for parallel sessions. Phase-3b exit now needs ONLY the live-tenant click-through; Phase-3c continues with 081 (legal-gated) / 082 / 083 / 084 / 085.

  • 2026-07-05 — v3.1: Phase-3c spec set completed — sso-auth v0.1 and retention-archival v0.1 drafted (PR-082/083 status lines updated; retention spec owns the archive slice format since PR-080 as-built shipped instance scope only, and resolves gdpr-erasure OQ-2 via the shared executor/ledger method field). 36 spec files lint-green. Consolidated Phase-3c review pending across backup-restore (In Review), gdpr-erasure, sso-auth, retention-archival.

  • 2026-07-05 — v3.0: PR-063 + PR-066 built (clarify-bot + SharePoint connector by background agents, core seams + course-correction by the main session) — the clarification loop is END-TO-END and the first content connector syncs. PR-063: app-layer/packages/clarify-bot/ per chat-delivery v0.5 as-built — Teams/Slack adapters (DM-only structural, verified callbacks, §7 card copy), verify-before-ack hooks with rate limiting, discriminated-action intent router (free text never binds), persisted-before-send nonce lifecycle, idempotent send, per-tenant credentials, digests + caps + skip-forward + redacted steward escalation; delivery protocol = lease/no-submit (reaper attempts+1 = ranking advancement). Core seams: GET /v1/clarifications/:id (spanExcerpt/aclJson/askedAliases), JobPayload.attempts (grpc-contracts v0.7), TS SDK signServiceToken + cross-language vector. INTEGRATION BUG CAUGHT in review: the bot assumed logical ids were where-queryable (they are property-filters only; its FakeCore mirrored the assumption and tests passed) — fixed via server-side askedAliases; by-id queries are now inexpressible in the bot's client interface, with a regression test. 44 bot tests (all §12 chat_* + clarify_rbac_gate/escalation/caps). PR-066: workers/sharepoint_connector/ per framework v0.5 as-built (per-site delta cursor dict, extension routing, full sourceMeta + ACL mirror, deletes via lookup_record, fault isolation, read-only gate); SDK ingest options + lookup_record. 24 connector tests; workers 278 green. Specs → chat-delivery v0.5, framework v0.5, temporal-clarification v0.9, grpc-contracts v0.7. Phase-3b remaining: 067/068/069 by demand, PR-070 ask-in-chat, and a LIVE-tenant click-through for the exit criteria.

  • 2026-07-05 — v2.9: PR-062 + PR-065 built (one session; Entra connector + SDK legs by a background agent) — PR-063 fully unblocked. PR-065 (framework core): options.sourceMeta envelope → deterministic connector source identity, root source properties, apply-time principal→PERSON resolution + authority edges (feeding clarification routing, proven e2e), advisory acl_json on source versions, externalId point lookup, third token shape (tenant-scoped named connector tokens) with tenant-scoped sync: leasing + kind↔audience fences both directions, sync-result chain completion over the existing RPC, namespace-scoped connector deletes (403 CONNECTOR_NAMESPACE_MISMATCH + security audit), REST upsert flag exposing the PR-053 seam, telha connector run|ls|status + api-key connector-token. PR-062: workers/entra_connector/ (Graph delta chains w/ recorded fixtures, placeholder ALIAS_OF merge, guest/disable handling, throttle/410 recovery, auth-failure dead-letter) + python SDK update_record/create_relationships + core POST /v1/person-aliases/verified-match (clarify-audience only, evidence re-verified, exact-one-match). Specs → framework v0.4 / entra v0.5 as-built. Phase-3b state: 060/061/062/064/065 ✅ — next: PR-063 (clarify-bot) closes the loop; PR-066+ extend the connector lane.

  • 2026-07-05 — v2.8: PR-061 + PR-064 built — clarification cluster complete except delivery. PR-061 (worker ambiguity detection): detection rules landed spec-first as §15 addenda (docling v0.3 / tabular v0.4 / email v0.3), shared workers_common plumbing (add_ambiguity camelCase wire mirroring core validation, clarify_enabled gate, deterministic stdlib date recognition — no wall clock, relative phrases anchor to document dates), all three projections emit behind the capability gate with no severity hints (core policy owns severity); worker suite 237 green (+59). PR-064 (steward surface, CLI-first): telha clarify queue / delivery history on show (job join by payload clarification_id) / erase-attribution (OQ-5: attribution tombstoned, confirmation evidence + corrected facts preserved; physical prior-version + job-payload purge recorded as PR-081's); new e2e tests incl. clarify_gdpr_tombstone and the clarify_attestation core leg. temporal-clarification → v0.8. Phase-3b state: 060/061/064 ✅; next = 062 (Entra) + 065 (connector core) in parallel, then 063 (clarify-bot) closes the loop end-to-end.

  • 2026-07-05 — v2.7: PR-060 built — Phase 3b clarification core landed. src/clarify/ (temporal-clarification v0.7 as-built): deterministic CLARIFICATION nodes + CLARIFIES edges + clarify:temporal routing jobs with graph-derived authority ranking; best-guess-at-reduced-confidence pending versions via Provenance.clarification (version-record-model v0.2, goldens byte-stable); full §6 answer matrix (quorum, conflict = steward_only, explicit steward override, responder supersede, post-binding conflicts, idempotent replay, free-form normalisation → 422); responder-assertion trust chain (clarify-audience tokens assert responder+rights; API keys gain optional person/role principal binding; CLI = local-operator boundary); §3.9 pendingClarification caveats on /v1/query; WorkerService clarify-audience fence both directions (grpc-contracts v0.6, no proto change); per-job max_attempts + per-kind backoff overrides (job-queue-leases v0.2); [clarify] config (ships disabled). Named §12 tests owned by PR-060 green (tests/clarify_e2e.rs, worker_lifecycle::clarify_fence). Spec inventory rows synced to the 2026-07-04 approvals (they predated this in the table). Next: PR-061/064 unblocked; 062/065 still parallel-startable; 063 after 062.

  • 2026-07-04 — v2.6: PR-056/057 built — Phase 3 is code-complete. PR-056: getTrace claim-grouped shaping in core (mcp-tools v0.4) + app-layer prompt pack / trace viewer / four PRD canonical scenarios as scripted tests (app leg by a parallel agent; demo admin carol added — phase-3 tools are admin-only). PR-057: standalone /eval harness crate with a deterministic mock provider, dataset v1 (qa/adversarial/temporal), spec-§3 metrics, ratchet-enforced release gates, nightly workflow (eval-harness spec v0.2 Approved). Phase-3 exit criteria: generate ✅; compare live e2e ✅ (chat scenarios scripted; browser click-through rides the mercato follow-up); eval harness gating ✅ (wire into release builds when release cadence starts). Dev-box ops note: cargo test links now run -j 1 (two concurrent MinGW ld jobs thrash 16GB RAM — run-tests-j1.cmd).

  • 2026-07-04 — v2.5: Phase 3b spec gate cleared — all four specs Approved (cross-spec review by J. Porter; conditions incorporated by Claude). temporal-clarification → v0.6 (responder-assertion rule + test), entra-identity-connector → v0.4 (Slack-alias ownership + POST /v1/person-aliases/verified-match + test), source-connector-framework → v0.3 (namespace-scoped connector deletes + test), chat-delivery-surface → v0.4 (nonce lifecycle, hook verify-before-ack + rate limiting, trust-chain cross-ref + 2 tests). PR-060/062/065 marked READY TO START (parallel); 33 spec files lint-green.

  • 2026-07-04 — v2.4: PR-053/054/055 built (one session, workers via parallel agents). Compare engine: src/query/compare.rs + POST /v1/compare + gRPC Compare activated + MCP compareSnapshots activated + TS SDK compare() — no stubs remain anywhere in the surface; snapshot-compare → v0.2 as-built (id-ordered primary-CF group walk replaces the dual-index merge sketch, node-scoped filters, OQ-1 touch resolved), grpc-contracts → v0.5, mcp-tools → v0.3, new [compare] config. Workers: code_worker (tree-sitter rs/py/js/ts, manifest→child-per-file, deterministic decl ids, honest syntactic CALLS; spec v0.2) and email_worker (.eml/.msg sniffed format, Entra-compatible PERSON ids, self-healing IN_REPLY_TO, quote spans excluded from embedding, attachment fan-out with inline bytes; spec v0.2). Enabling core seams in ingestion-provenance v0.4 §17: submission nodes[].id upsert (create / match / version / never-resurrect, NodeOps::upsert_node), childJobs[].contentB64, child-enqueue request-dedup (= code incremental skip). workers_common builder gained add_node(id=) / add_child_job(content=). Exchange-connector sequencing note resolved (PR-055 built).

  • 2026-07-04 — v2.3: Phase 3c — Productization added (PR-080..086), closing the feature-complete → sellable gap identified in review: PR-080 backup/restore (URGENT — the plan previously had no data-durability path; precedes any real-data pilot), PR-081 GDPR erasure build (numbers the pre-GA spec; legal gate), PR-082 SSO/OIDC human auth, PR-083 retention/archival (retires orphaned-stream debt), PR-084 packaging/distribution (retires Docker/DLL/UDS debt), PR-085 docs + utoipa pass (retires PR-024 deviation), PR-086 operator console (may trail GA). Phase-3c exit criteria defined as the GA gate alongside Phase 3/3b; dependency graph extended.

  • 2026-07-04 — v2.2: Phase 3b governance sync: the four 2026-07-04 specs added to the §4.1 inventory (temporal-clarification v0.4 In Review, all OQs resolved; chat-delivery-surface v0.2 Draft with the ask/asks-back disambiguation; entra-identity-connector + source-connector-framework v0.1 Draft); Phase 3b header refreshed to current spec versions; next-steps item 2c added (approvals are the only gate — PR-060/062/065 startable in parallel on sign-off).

  • 2026-07-04 — v2.1: Phase 3b added (PR-060..070) from the four new 2026-07-04 feature specs: clarification cluster (060 core w/ quorum + answer matrix, 061 worker detection, 063 clarify-bot Teams/Slack, 064 steward surface, 070 ask-in-chat) and connector lane (062 Entra identity, 065 framework core, 066–069 SharePoint/Exchange/Slack/Salesforce). Dependency graph + Phase-3b exit criteria added; EPIC-404 marked partially superseded; spec count 26 → 30. Spec-first gate noted: temporal-clarification In Review, other three Draft — approval precedes code per PR-002 doctrine.

  • 2026-07-04 — v2.0: Phase-3 core chain built — PR-050 (memory planner), PR-051 (generation), PR-052 (claim verification), in one pass. All three specs → v0.3 (as-built records). New core surface: src/planner/ (two-phase evidence selection, plan_hash determinism), src/llm/ (LlmProvider + Anthropic/OpenAI-compat streaming impls, credential hygiene incl. sink-level structural redaction, prefix-stable citation prompts, pre-flight cost ledger), src/verify/ (pinned lexical pipeline over unicode-normalization/-segmentation — two new deps —, geometric matcher with whole-pair numeric gate, verdict bands + flag/redact policy, GenerationTrace persistence), POST /v1/generate (JSON + SSE chunk/verification/done{traceId}), GET /v1/trace/:queryId, MCP generate/getTrace activated (tools.json regenerated; compareSnapshots remains the last stub, PR-053). Config gains [planner]/[llm]/[verify] with load-time validation incl. https-or-loopback egress. Gate: 286 tests across 20 targets green, clippy -D warnings clean, fmt clean, spec-lint 29/29. Fixed in passing: mcp/server.rs still called the pre-PR-043 1-arg props_from_json (tree didn't compile as found). Recorded deferrals: per-tenant cap/policy overrides (operator surface), provider tokenizer/ctx, calendar cost windows.

  • 2026-07-04 — v1.9: PR-043 built — Phase 2 code-complete. Field-encryption spec → v0.3 (as-built record), grpc-contracts → v0.4 (client-supplied create ids, UpdateRecord RPC + PUT /v1/records/:id, {"$bytes"} write convention; its OQ-1 marked resolved per field-encryption OQ-1). @telha/enterprise field-crypto package (envelopes, DEK/KEK + cache hygiene, classification registry, EncryptingTransport via new TelhaConnector.transportWrap, BullMQ rotation sweep), contracts classification.ts, SDK records.update + RecordInput.id. Header/progress/next-steps/spec-inventory refreshed; remaining Phase-2 exit item is the §4.2 checklist run.

  • 2026-07-04 — v1.8: security reviews completed for field-encryption and generation-orchestration specs (both → v0.2 Approved with conditions; reviewer: Claude, delegated by J. Porter; F7 v0.2 approvals from v1.7 countersigned by J. Porter). Field-encryption: AAD gains logical_id (envelope-transplant gap), DEK-cache hygiene, pre-crypto tenant scoping, fail-closed KMS, no-side-door guard; token signing keys join the key inventory but stay out of KMS. Generation: structural redaction, https-or-loopback egress, operator-only endpoint/model + allowlist, output ceiling, pre-flight cap reservation, no mid-stream retries. All F7 + security gates clear; PR-043 and the 050→051→052 chain are buildable; only the GDPR-erasure legal sign-off (pre-GA) remains.

  • 2026-07-04 — v1.7: F7 math reviews completed for memory-planner and claim-verification specs (both → v0.2 Approved with conditions; reviewer: Claude, delegated by J. Porter). Substantive outcomes: claim-verification fusion changed arithmetic → geometric with clamped cosine (drafted blend failed its own §12 adversarial metrics), lexical term pinned as plan-local IDF coverage, numeric gate moved to whole-score, entity-swap scoped out as an NLI-needing v1 limitation; memory-planner gained density floor, script-aware token estimator (CJK), pinned dedup + determinism rules, hard per-source cap. PR-050 unblocked; PR-051 still needs security sign-off. query-language §11b demoted to a subsection to restore spec-lint (29 specs, 0 failing).

  • 2026-07-03 — v1.6 (night): statuses refreshed after the day's build-out — PR-022b, PR-030..037 (incl. multi-submit streaming addition), PR-038/039/040, PR-041, PR-042 all marked done (Phase 2 complete except PR-043); confidence-decay spec F7-approved, decay + fusion implemented (PR-018/022 carve-outs closed); header/progress/next-steps rewritten for Phase-2 closeout.

  • 2026-07-03 — v1.5: per-PR **Status:** lines added for all 34 PRs; Phase 0 + Phase 1 (PR-001..003, PR-010..029) marked done incl. recorded deviations/deferrals (utoipa pass, UDS/deadline deferrals, decay F7 gate); progress note added to the dependency graph; immediate next steps rewritten for Phase-2 entry.

  • 2026-07-02 — v1.4: full spec coverage — all Phase 2, Phase 3, PR-022b, and pre-GA GDPR erasure specs drafted (16 new; 26 total + template/README), all lint-green; inventory updated with per-spec sign-off requirements; two new cross-spec designs introduced and recorded (subject-scoped DEK derivation for RtbF crypto-shredding; span-kind priors shared between planner and vector embedding).

  • 2026-07-02 — v1.3: complete Phase 1 spec set drafted (version-record-model, global-temporal-scan, traversal-semantics, schema-inference, query-language, confidence-decay, grpc-contracts, data-split-routing) — all lint-green; inventory updated; Phase 2/3 specs remain just-in-time by design.

  • 2026-07-02 — v1.2: restructured as Build Doc companion to PRD v8; added Part 4 (spec inventory, phase gates, risk register, environment, lanes); Part 1 preserved as audit trail.

  • 2026-07-02 — v1.1: F1–F10 ratified (F3, F10 amended for tritemporal correctness); OQ-3 resolved (nil-tenant namespace); PR-022b added; PR-043 → XChaCha20-Poly1305.
  • 2026-07-02 — v1.0: initial feasibility review + 43-PR plan.