Projection Parity Audit — 2026-05-16
Scope: Part E (E-01..E-08) of the FIN Production Parity Tracker §6 — verify all approved CQRS projection tables across fin-central-prod and nathel-prod tenants meet the seven readiness checks.
Method: Static audit (no live Neon access from this branch). Evidence taken from:
audit/card-window-system/approved-projection-tables.jsonregistry (authoritative)db/migrations/041..050*.sqlpackages/mcp-server/src/event-handlers/projection-*.ts(handlers)packages/mcp-server/src/alerts/event-hook.ts(dispatch wiring)scripts/cli/replay-projection.ts(replay CLI descriptors)test/contracts/dpo/projection-replay-parity.test.ts(registry-CLI parity contract)- Per-projection consumer tools and tests
Branch under audit: worktree-agent-a3889ff00d4695fa5 (parent: claude/heuristic-kepler-4f9104).
Audit branch: claude/parity-task-E-01-E-08-projection-audit.
Branch-State Caveat (read first)
The registry on master lists 5 projections (041-044). The current worktree branch (worktree-agent-a3889ff00d4695fa5, off claude/heuristic-kepler-4f9104) is identical to master for the registry — it still shows 5. Two separate unmerged branches add projections:
| Branch | Adds | Migration | Status vs. registry on master |
|---|---|---|---|
b3581360 (TZ-3) | doc_backlink_projection, entity_doc_projection | 049, 050 | not merged |
2a8b055d (IAR-RM-033) | lot_lifecycle_reconciliation_projection | 049 (CONFLICT) | not merged |
The task brief asserts “Migrations 049 + 050 were applied to fin-central-prod 2026-05-16 02:30Z via Neon MCP.” Per commit b3581360’s description, those are the doc-CQRS migrations. This audit treats the TZ-3 registry (7 projections) as the production-target reality and explicitly flags the migration-049 collision with IAR-RM-033 as a P0 follow-up.
The audit therefore covers 7 projections × 2 tenants × 7 checks.
Summary
| Status | Count |
|---|---|
| GREEN (all 7 checks pass) | 0 of 7 |
| YELLOW (1-2 checks failing or partial) | 5 of 7 |
| RED (3+ checks failing or systemic gap) | 2 of 7 |
Live-DB checks (Check 1 migration-applied on nathel-prod, Check 4 RLS catalog state) are marked UNKNOWN because this branch had no Neon MCP access. The audit assumes fin-central-prod 1-7 = migration-applied per task brief; for any nathel-prod row the migration-applied check is marked YELLOW unless evidence in this repo (e.g. earlier handoff) confirms it.
Per-projection × per-tenant readiness matrix
Legend: G = pass / Y = partial or unconfirmed / R = fail / ? = not verifiable from static audit.
fin-central-prod
| Projection | 1. Migration | 2. Handler wired | 3. Replay cmd | 4. RLS forced | 5. SoT documented | 6. Read API | 7. Test ≥80% | Overall |
|---|---|---|---|---|---|---|---|---|
| lot_status_projection | G | G | G | G (in migration) / ? live | G | G | Y | Y |
| pulse_admin_active_holds | G | G | G | G / ? | G | G | Y | Y |
| actor_pulse_aggregate | G | G | G | G / ? | G | G | Y | Y |
| kb_health_summary | G | G | G | G / ? | G | G | Y | Y |
| system_health_snapshot | G | Y (cron only, not in dispatcher) | G | G / ? | G | G | Y | Y |
| doc_backlink_projection | G (per task brief) | R (no handler) | R (not in CLI) | G (in migration) / ? | G | R (no get_backlinks) | R (no tests) | R |
| entity_doc_projection | G (per task brief) | R (no handler) | R (not in CLI) | G (in migration) / ? | G | R (no get_entity_profile) | R (no tests) | R |
nathel-prod
| Projection | 1. Migration | 2. Handler wired | 3. Replay cmd | 4. RLS forced | 5. SoT documented | 6. Read API | 7. Test ≥80% | Overall |
|---|---|---|---|---|---|---|---|---|
| lot_status_projection | Y ? | G | G | G / ? | G | G | Y | Y |
| pulse_admin_active_holds | Y ? | G | G | G / ? | G | G | Y | Y |
| actor_pulse_aggregate | Y ? | G | G | G / ? | G | G | Y | Y |
| kb_health_summary | Y ? | G | G | G / ? | G | G | Y | Y |
| system_health_snapshot | Y ? | Y (cron only) | G | G / ? | G | G | Y | Y |
| doc_backlink_projection | Y ? | R | R | G / ? | G | R | R | R |
| entity_doc_projection | Y ? | R | R | G / ? | G | R | R | R |
Notation: “Y ? ” on nathel-prod migration-applied = unverifiable from static audit; assumed YELLOW pending Neon schema_migrations query.
Per-projection narratives
1. lot_status_projection (E-01) — YELLOW
- Migration:
db/migrations/041_lot_status_projection.sql(present locally). Source-of-truth =events. - Handler:
packages/mcp-server/src/event-handlers/projection-lot-status.ts. Listens to 13 event types covering BLOCKING (qc.hold_placed,quality.hold_placed,compliance.lot_hold_placed,receiving.rejected,lot.disposed,lot.recalled,inventory.lot_frozen), RELEASE (qc.hold_released,quality.hold_released,inventory.lot_unfrozen), INSPECTION (qc.inspection_completed,quality.inspection_completed), and INVENTORY (lot.received). Wired intodispatchProjectionUpdaters()inevent-handlers/index.ts:53, called fromalerts/event-hook.ts. - Replay:
scripts/cli/replay-projection.tsdescriptor exists. Command works:pnpm cli:replay-projection --projection=lot_status_projection --tenant=<tenant_id>. - RLS: Migration sets
ALTER TABLE lot_status_projection FORCE ROW LEVEL SECURITY;with INSERT/UPDATE/DELETE/USING policies. Liverelrowsecurity AND relforcerowsecuritynot verifiable from this branch. - Source-of-truth: registry entry documents
eventsand lists the 13 source event types viaLOT_STATUS_EVENT_TYPESin the CLI. - Read API:
packages/mcp-server/src/tools/get-pickable-inventory-with-gate.tsreads the projection with documented fallback to live event scan on miss. - Test coverage: Indirect only. No dedicated
projection-lot-status.test.tsexists. Coverage flows throughtest/contracts/dpo/projection-replay-parity.test.ts(engine determinism + registry-CLI parity) andpackages/shared/src/projections/replay.test.ts. Estimated coverage of the handler itself: <50% (no direct unit tests onupdateLotStatusProjection’s state-machine logic or sellability-gate interaction).
Gap: add projection-lot-status.test.ts co-located with the handler.
2. pulse_admin_active_holds (E-02) — YELLOW
- Migration: also in
041_lot_status_projection.sql(two tables ship together). - Handler:
packages/mcp-server/src/event-handlers/projection-pulse-admin.ts.PULSE_ADMIN_PROJECTION_EVENT_TYPESexported. Three alert flavors keyed byalert_kindcolumn (lot_hold/qc_failed/temp_excpt). Wired in dispatcher. - Replay: CLI descriptor present.
- RLS: FORCE RLS in migration.
- Source-of-truth: events (hold/release + QC inspection + sensor temperature exceptions).
- Read API:
packages/mcp-server/src/ops/get-pulse.tsreads it (admin Pulse path). - Test coverage:
packages/mcp-server/src/ops/get-pulse.test.ts(299 lines) tests the consumer, not the projection writer. No dedicated handler test.
Gap: same — add direct handler unit test.
3. actor_pulse_aggregate (E-03) — YELLOW
- Migration:
db/migrations/042_actor_pulse_aggregate_projection.sql. - Handler:
packages/mcp-server/src/event-handlers/projection-actor-pulse.ts. Per-(tenant, actor, window) upserts. Wired in dispatcher. - Replay: CLI descriptor present.
- RLS: FORCE RLS in migration.
- Source-of-truth: events (actor-visible families).
- Read API:
packages/mcp-server/src/tools/get-briefing-content.tsreads it. - Test coverage: No co-located handler test. Indirect via briefing tests.
Gap: add handler test.
4. kb_health_summary (E-04) — YELLOW
- Migration:
db/migrations/043_kb_health_summary_projection.sql. Source-of-truth =knowledge(notevents). - Handler:
packages/mcp-server/src/event-handlers/projection-kb-health.ts. ExportsrefreshKbHealthSummary()for explicit refresh. Wired in dispatcher. - Replay: CLI descriptor present. Command form:
pnpm cli replay-projection --projection=kb_health_summary --tenant=<tenant_id>(no--frombecause source isknowledge, not events). - RLS: FORCE RLS in migration.
- Source-of-truth:
knowledgetable aggregates (per registry). - Read API:
packages/mcp-server/src/tools/knowledge-health.ts. - Test coverage: No co-located handler test.
Gap: same.
5. system_health_snapshot (E-05) — YELLOW
- Migration:
db/migrations/044_system_health_snapshot_projection.sql. Source-of-truth =events, knowledge, operational_memory, actors(composite). - Handler:
packages/mcp-server/src/event-handlers/projection-system-health.ts. ExportsrefreshSystemHealthSnapshot(). NOT wired intodispatchProjectionUpdaters()— by design, this is a cron-style projection perevent-handlers/index.ts:23-26(only the four event-driven projections are auto-dispatched). Discoverable viareplay-projection.ts:155-162which special-casessystem_health_snapshotas__cron_refresh__. - Replay: CLI descriptor present. The replay command bypasses the event walk and invokes
refreshSystemHealthSnapshot()directly. - RLS: FORCE RLS in migration.
- Source-of-truth: registered correctly.
- Read API:
packages/mcp-server/src/tools/get-system-health.ts. - Test coverage: No co-located handler test.
Notes: The cron schedule itself is NOT documented in the registry; the registry says replay_supported: true but the runtime trigger (cron name / schedule / where it’s invoked outside of --apply replay) is unclear from static audit. A reader cannot tell if refreshSystemHealthSnapshot() runs every minute, every hour, or only on explicit replay. This is a P1 documentation gap.
6. doc_backlink_projection (E-06) — RED
- Migration:
db/migrations/049_doc_backlink_projection.sqlexists only on branchclaude/heuristic-kepler-4f9104(commitb3581360). Per task brief, applied to fin-central-prod 2026-05-16 02:30Z via Neon MCP. - Handler: MISSING. No
projection-doc-backlink.tsinpackages/mcp-server/src/event-handlers/. No reference todoc.indexedordoc.archivedconsumers anywhere inpackages/. Per the commit message: “Consumer code (vault webhook adapter, doc.indexed/archived consumers, entity-row update consumer, renderer worker) is T7 scope” — explicitly deferred. - Replay: NOT WIRED.
scripts/cli/replay-projection.tsDESCRIPTORSmap has nodoc_backlink_projectionkey. Runningpnpm cli replay-projection --projection=doc_backlink_projection --tenant=...will throw"Unknown projection: doc_backlink_projection"fromparseArgsline 208-212. - RLS: Migration 049 has
ALTER TABLE doc_backlink_projection FORCE ROW LEVEL SECURITY;with full INSERT/UPDATE/DELETE/USING policies. Liverelforcerowsecuritynot verifiable from static audit (would be GREEN if Neon catalog query confirms). - Source-of-truth: Registry documents
knowledge.domain=doc.content->outbound_links[] (via doc.indexed events)andtransactional_write_required: trueper Engineering Spec §11.5.3. - Read API: MISSING. No
get_backlinks/get_doc_graphtool inpackages/mcp-server/src/tools/. Per Engineering Spec §8.3 + §8.4 these are required. - Test coverage: 0%. No handler test. No read-API test. The
test/contracts/dpo/projection-replay-parity.test.tsregistry-CLI parity assertion will fail the moment migration 049/050 metadata lands in the registry on master, because the CLI source does not contain the stringsdoc_backlink_projectionorentity_doc_projection.
7. entity_doc_projection (E-07) — RED
- Migration:
db/migrations/050_entity_doc_projection.sql— same provenance as 049 (TZ-3 branch only). - Handler: MISSING. No entity-row update consumer; no renderer worker; no doc.rendered/render_failed consumer.
- Replay: NOT WIRED. Same CLI gap.
- RLS: Migration 050 has
FORCE RLS+ 4 policies. Live state?. - Source-of-truth: Registry documents
knowledge.<entity_domain>.<entity_key> rows + doc.rendered events.transactional_write_required: true. - Read API: MISSING. No
get_entity_profiletool exists per Engineering Spec §11.5.2. - Test coverage: 0%.
The audit cannot verify whether the renderer worker scaffolding promised by recent commit 9067ca4e ("feat(parity B-03+B-04): entity-doc-renderer-worker full implementation") actually populates this projection — that commit lives on the same claude/heuristic-kepler-4f9104 ancestor. Action: cross-check B-03/B-04 deliverable when E-audit is reconciled against tracker.
E-08 replay smoke test
Script committed at scripts/audit/replay-projection-smoke-test.ts. Runs the CLI in --json dry-run mode against each registry projection × each tenant configured via env. Without DB access it falls back to a descriptor-only mode that verifies the CLI knows about every projection name in the approved registry — equivalent to the contract test but emitting a smoke-style pass/fail report. See script header for usage.
Smoke-test invocation:
pnpm exec tsx scripts/audit/replay-projection-smoke-test.ts \
--tenant=fin_central_prod \
--tenant=nathel_prod \
[--apply] # actually run replays (requires DATABASE_URL pointing at the tenant)
[--registry-only] # skip live DB; verify CLI ↔ registry parity only
P0 / P1 / P2 follow-up tasks (recommended new tracker rows)
| ID | Sev | Title | Owner area |
|---|---|---|---|
| E-FU-1 | P0 | Resolve migration-049 collision: TZ-3 (doc_backlink_projection) vs IAR-RM-033 (lot_lifecycle_reconciliation_projection). Both branches claim slot 049 unmerged. Renumber the loser to 051 + update registry + update CLI descriptor + bump system_metadata.schema_version. | DB / migration |
| E-FU-2 | P0 | Wire doc_backlink_projection handler: write event-handlers/projection-doc-backlink.ts, subscribe to doc.indexed / doc.archived, append to dispatchProjectionUpdaters(). Add to replay-projection.ts DESCRIPTORS map. Add projection-doc-backlink.test.ts. | mcp-server / event-handlers |
| E-FU-3 | P0 | Wire entity_doc_projection handler: similar (subscribe to knowledge.<entity_domain>.updated for stale-mark, doc.render_requested/doc.rendered/doc.render_failed for status). Confirm whether entity-doc-renderer-worker from B-03/B-04 satisfies this — if so, link it; if not, write it. | mcp-server |
| E-FU-4 | P0 | Build get_backlinks and get_doc_graph MCP tools backed by doc_backlink_projection. Per Engineering Spec §8.3 + §8.4. | mcp-server / tools |
| E-FU-5 | P0 | Build get_entity_profile MCP tool backed by entity_doc_projection. Per Engineering Spec §11.5.2. | mcp-server / tools |
| E-FU-6 | P1 | Verify migrations 041-044 are applied on nathel-prod. The static audit cannot reach Neon; orchestrator must run SELECT version FROM schema_migrations WHERE version IN ('041','042','043','044') ORDER BY version against nathel-prod and post evidence. | infra / DB |
| E-FU-7 | P1 | Live RLS verification per tenant for all 5 (+ 2 new) projection tables: query SELECT relname, relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname IN (...) for both fin-central-prod and nathel-prod. | infra / DB |
| E-FU-8 | P1 | Add direct co-located handler unit tests: projection-lot-status.test.ts, projection-pulse-admin.test.ts, projection-actor-pulse.test.ts, projection-kb-health.test.ts, projection-system-health.test.ts. Each must reach ≥80% coverage on the file under test. | mcp-server / event-handlers |
| E-FU-9 | P1 | Document system_health_snapshot cron schedule + invoker in the registry (replay_command is set but the runtime trigger for the routine snapshot refresh is undocumented). | docs / infra |
| E-FU-10 | P1 | The transactional_write_required: true constraint on migrations 049/050 is stricter than 041-044’s fire-and-forget pattern. Add a contract test that fails CI if any future handler for these projections writes outside the same BEGIN/COMMIT as the canonical knowledge.domain=doc upsert (Engineering Spec §11.5.3). | mcp-server / test |
| E-FU-11 | P2 | Migration drift check: system_metadata.schema_version is updated by each migration with a specific previous-version assumption (049 expects 48, 050 expects 49). If migrations are applied out of order (e.g. IAR-RM-033’s 049 first, then TZ-3’s renumbered 051), the migrate:down rollback chain breaks. Tighten the down-path. | DB |
| E-FU-12 | P2 | Add cron_schedule and cron_invoker_module fields to approved-projection-tables.json schema for cron-style projections (currently system_health_snapshot; also future-proof for any sweep-style replay where source is knowledge). | audit registry |
Acceptance summary
- Audit doc: this file (
docs/audits/PROJECTION_PARITY_AUDIT_2026-05-16.md). - Smoke-test script:
scripts/audit/replay-projection-smoke-test.ts. - Gaps identified: 12 follow-up tasks (5 × P0 [E-FU-1..5], 5 × P1 [E-FU-6..10], 2 × P2 [E-FU-11..12]).
- Bottom line: 5 of 7 projections are runtime-complete on the fin-agentic side but missing direct unit tests (YELLOW). The 2 new doc-CQRS projections (
doc_backlink_projection,entity_doc_projection) are SQL-only on the database; all 5 application-layer checks fail (handler, replay descriptor, read API, test, dispatch wiring). The P0 follow-ups must clear the 049 migration collision before any cleanup work can land on master.