Skip to content

Local two-API E2E evidence

The strict proof passed on PR #3765 at tested merge commit 0f3300c15982903df2eff9fa9d70fb9f3a9cb204, which includes main d6f49aaa32efe28ce1b83db2677db67ad9ff9664 and the locally tested fence release fix from PR #3767 at 36b671f9b. It used an isolated local syrf_e2e MongoDB, real RabbitMQ, two distinct API processes on localhost:12738 and localhost:12742, and only the fixed project 00000000-0000-0000-0000-00000000f024 for materialized writes and serving. Both API hosts and project management used the test-only overlay with statistics writes, screening and annotation families, membership families, and SignalR enabled. Both API hosts additionally enabled Pages, ReviewerProgress, and StageOverviewBundle. The browser and direct API client used seeded local administrator, reviewer, and standard E2ETest roles. No personal or staging account was used; no credential, token, or browser storage state is committed.

Reproduce from the PR worktree with the supported isolated Chromium network namespace, which passed its local preflight and avoided unrelated Docker network changes interrupting Vite module loads:

cd e2e && node scripts/test-chromium-netns.cjs
cd ..
E2E_BROWSER_NETNS=1 bash e2e/run-local.sh --feat024-two-api --spec materialized-statistics-two-api
E2E_BROWSER_NETNS=1 bash e2e/run-local.sh --feat024-two-api --spec stage-overview-and-screening-eligibility --skip-build
pnpm --dir e2e exec tsc --noEmit
bash -n e2e/run-local.sh e2e/scripts/ports.sh e2e/scripts/teardown.sh
git diff --check

The first runner invocation completed a full .NET build. Each invocation started its own isolated test data and cleaned up its owned services and containers. The ignored Playwright report and trace directory is e2e/test-results/; the local full-run logs were /tmp/syrf-pr3765-e2e-pages-on-integrated.log and /tmp/syrf-pr3765-eligibility-integrated.log. These machine-local logs can contain local test-session diagnostics and are not committed.

Focused result Observed evidence
Materialized page consumer Both API hosts reported effective Pages, ReviewerProgress, StageOverviewBundle, and SignalR flags. The Stage Overview browser's initial bundle reported readSource: Materialized. The administrator screening parity read reported readSource: Materialized and inParity: true. Stage reviewer progress has no response provenance field. Its materialized consumer was enabled and its supported baselines were built, but this test does not directly establish the source of each reviewer-progress response.
Real broker fanout and visible refresh Immediately before screening, the test recorded the current project watermark after both browser views and the API 2 observer were mounted. A subsequent screening action produced the same newer ProjectStatisticsChanged revision 11 on the Stage Review API 1 browser socket at 1234 ms, Stage Overview API 1 browser socket at 1234 ms, and authenticated API 2 socket at 1231 ms. Stage Review rendered 1 done at 2160 ms and Stage Overview rendered the member's 1 screened count at 2094 ms after the action. Those renders were 18,843 ms and 12,763 ms after their respective navigation, before the first possible 30-second recovery poll. The test requires event timestamps after the action, a revision above the immediate pre-write watermark, and each live render within 20 seconds.
Disconnection and catch-up The API 2 client disconnected before a second committed screening write. Its event count did not grow while disconnected; both mounted browser views rendered count 2. A fresh authenticated API 2 connection subscribed, then an API 2 materialized parity read observed a newer watermark in parity. A third write produced an API 2 event for this project with a revision newer than the recovered watermark. The assertions tolerate duplicate delivery of a revision without treating it as a new write.
Combined-stage eligibility In a separate run against the same integrated code, seeded reviewer and standard roles each screened one study. The combined screening/annotation stage hid the screening action after sufficient decisions, annotation remained editable, another screening request returned 409, and the persisted screening count stayed at two. This spec creates a random project outside the fixed materialized allowlist, so it establishes source-path eligibility and UI behavior, not materialized annotation parity.
Run totals Two-API proof: 4 passed (three account setup cases plus one proof), 0 failed, in 1.3 minutes after startup/build. Eligibility: 4 passed (three account setup cases plus one scenario), 0 failed, in 1.4 minutes using the compiled code. TypeScript, shell syntax, and diff whitespace checks passed.

The earlier Pages-off proof passed on harness commit 5556d5e55, but it established only broker delivery and live source-path refresh. The stricter Pages-on run initially exposed a real search-import fence-release WriteConflict: the source operation committed but left an active ProjectScreening fence, and supported backfills correctly returned 409 FamilyFenced. PR #3767 adds bounded fresh-transaction retry to that release path. The integrated run above used a fresh isolated database and passed without deleting or bypassing a fence.

The reconnect assertion uses a fresh API 2 SignalR client plus an administrator parity read reporting materialized statistics. It does not prove automatic reconnection of an existing Angular client. This is a local two-host result, not deployed two-pod staging proof. Staging broker/routing, permission and rollback acceptance, and soak remain separate work with dedicated real test accounts; this local E2ETest authentication must not be enabled on shared staging.