ADR-0002 ยท Defer service-layer TypeORM integration tests to CI
Status: Accepted ยท Date: 2026-05-13 ยท Deciders: Builder + user (via Supervisor option-2) ยท Supersedes: none
Historical note: This ADR was authored under the original "Persona Manager" working name. The platform-facing surface is the Tavus integration โ API module
api/src/tavus/, frontend routes/ops/tavus/*(rename completed via #1031). Test-suite filenames (tavus-isolation.spec.ts, renamed frompersona-manager-isolation.spec.tson 2026-06-02) and migration filenames (PersonaManagerInit.tsetc.) below are historical artifacts.
Contextโ
PM-0074 surfaced a pattern across Phase 2 and Phase 3 commits: the Builder
shipped service-layer code with mock-repository unit tests only, deferring
TypeORM-against-real-DB integration tests on the grounds that the Builder
environment cannot build better-sqlite3's native binary
(npm rebuild better-sqlite3 fails with node-gyp errors on Node 25.9.0
with node-gyp 8.4.1).
CLAUDE.md confirms the API test harness uses better-sqlite3 in-memory for
local Jest runs, falling back to a real Postgres service container in CI.
The native-binary failure is environment-specific: CI runners build the
binary cleanly. The repo's existing 35 spec files (api/test/*.spec.ts) all
exercise the better-sqlite3 path successfully on CI per the project's
MVP_LAUNCH_CHECKLIST.md.
The Supervisor flagged this as a P2 risk:
Real behaviors NOT exercised locally: TypeORM
simple-jsonโ Postgres JSONB column semantics, RLS policy enforcement underapp.current_org_idGUC, FK cascade behavior onmeetings.assignment_idandmeeting_decisions.meeting_id, the partial unique index that backsstartOnboardingidempotency, audit row visibility from a separate session.
The Supervisor offered three resolution paths:
- Push the dev branch to remote so CI runs full integration coverage.
- Accept the risk; treat the first CI run on PR merge as the integration gate.
- Anything else.
The user directed "continue until done." The Builder does not have authorization to push to remote (the system prompt explicitly forbids unsolicited pushes). Path 1 is therefore not available to the autonomous loop; path 2 is the operative choice.
Decisionโ
Defer service-layer integration coverage to CI. The branch's first push
to GitHub will trigger the existing ci.yml test-api job, which runs the
full Jest suite against a Postgres service container. That run is the
integration gate.
Mock-repo unit tests stand as the local proof-of-correctness for the service logic itself; integration tests stand as the proof that the service behaves correctly against real Postgres semantics (JSONB columns, RLS policies, FK cascades, partial unique indexes).
Consequencesโ
Positiveโ
- The Builder loop can halt rather than block indefinitely on an env limitation it cannot resolve.
- The acceptance is explicit and documented; future maintainers know exactly which behaviors are unexercised in the local test path.
- The existing CI infrastructure (Postgres service container in
ci.yml) is unchanged โ the new modules ride the existing harness on the first push.
Risks accepted (and their mitigations)โ
| Risk | Mitigation |
|---|---|
simple-json โ Postgres JSONB column semantic divergence | The existing 35 spec files already exercise the same simple-json pattern against both SQLite and Postgres via the harness; divergences would have surfaced in prior work. The new entities use the same column annotations as existing entities. |
| RLS policy enforcement at the DB layer | Migration 1746000000010 installs policies that mirror migration 9's pattern verbatim. The application-layer assertOrgScope in EnvironmentProfilesService provides belt-and-braces enforcement and IS unit-tested (tavus-isolation.spec.ts, renamed from persona-manager-isolation.spec.ts on 2026-06-02). The nightly cross-tenant isolation workflow (agent-cross-org-isolation-nightly.yml) consumes HUMANWORK_ISOLATION_PERSONA_TABLES and will exercise DB-layer RLS on the next nightly run. |
FK cascade behavior on meetings.assignment_id and meeting_decisions.meeting_id | Migration declares ON DELETE CASCADE for both. The down() migration drops in reverse FK order. Cascade behavior is a Postgres primitive, not a TypeORM behavior. |
Partial unique index race on startOnboarding idempotency | The race-window code in MeetingsService.startOnboarding explicitly catches the save exception and re-reads the row; the unique index is what makes the race safe. The catch path is small and reviewable. |
| Audit row visibility across sessions | AuditService writes synchronously inside the request transaction; visibility from a separate session is the standard Postgres read-committed isolation. No Tavus-integration-specific behaviour is introduced. |
Negativeโ
- A first-push CI failure on one of the above five behaviors will land in CI, not locally. The Builder accepts that the first push is a full-cost CI run.
Verificationโ
- All 51 Tavus-integration unit tests pass locally.
- The branch will be pushed at the user's discretion; CI is expected to pass on first run. If CI fails, the failure mode is one of the five enumerated risks above โ each has a documented mitigation, and the failure surface is a well-bounded patch.
Referencesโ
BUILD_LOG.jsonlPM-0074 task_blocked event (2026-05-13T02:14:45Z)SUPERVISOR_LOG.jsonlcycle 4concern_raisedand cycle 8human_escalation_requiredevents- ADR-0001 โ additive integration approach (the parent decision)
api/test/tavus-isolation.spec.ts(renamed 2026-06-02 frompersona-manager-isolation.spec.ts) โ mock-repo cross-tenant coverage that compensates partly.github/workflows/agent-cross-org-isolation-nightly.ymlโ DB-layer RLS coverage path.github/workflows/ci.ymlโ Postgres service container path that closes the gap on first push