Skip to main content

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 from persona-manager-isolation.spec.ts on 2026-06-02) and migration filenames (PersonaManagerInit.ts etc.) 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 under app.current_org_id GUC, FK cascade behavior on meetings.assignment_id and meeting_decisions.meeting_id, the partial unique index that backs startOnboarding idempotency, audit row visibility from a separate session.

The Supervisor offered three resolution paths:

  1. Push the dev branch to remote so CI runs full integration coverage.
  2. Accept the risk; treat the first CI run on PR merge as the integration gate.
  3. 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)โ€‹

RiskMitigation
simple-json โ†” Postgres JSONB column semantic divergenceThe 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 layerMigration 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_idMigration 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 idempotencyThe 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 sessionsAuditService 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.jsonl PM-0074 task_blocked event (2026-05-13T02:14:45Z)
  • SUPERVISOR_LOG.jsonl cycle 4 concern_raised and cycle 8 human_escalation_required events
  • ADR-0001 โ€” additive integration approach (the parent decision)
  • api/test/tavus-isolation.spec.ts (renamed 2026-06-02 from persona-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