Per the Q4 mollifier-cancel design — first mutation endpoint. engine.createCancelledRun: new run-engine method that writes a CANCELED TaskRun row directly from a buffer snapshot. Skips queue insertion, waitpoint creation, and concurrency reservation (run never executes). Emits runCancelled so the existing handler writes the TaskEvent cancellation row. P2002 from double-pop is caught and returns the existing row without re-emitting. Drainer bifurcation: mollifierDrainerHandler routes to createCancelledRun when snapshot.cancelledAt is set. Cancel-wins- over-trigger — customer intent is terminal. Cancel route: wraps the call in mutateWithFallback. PG-row hits go through the existing CancelTaskRunService. Buffered-QUEUED hits land a mark_cancelled patch on the snapshot via mutateSnapshot. busy snapshots wait for drainer resolution then call the PG service against the resulting row. 404 / 503 surface for genuine missing or drainer-hung cases. Known follow-up: the Q3 wait-and-bounce for cancel-of-buffered-FAILED relies on the drainer eventually writing a SYSTEM_FAILURE PG row on terminal materialisation failure. That drainer-side write isn't implemented yet (the failed-drain path today only marks the buffer entry hash FAILED). Cancel-of-state-3 will currently 503 after 2s instead of returning the SYSTEM_FAILURE row. Acceptable rare-race behaviour; flagged for a follow-up alongside the drainer sweeper work.
Webapp tests
Three suites live in this directory.
Unit tests — *.test.ts
Run with pnpm test from apps/webapp. Default vitest pickup. No
container setup. Run on every PR via unit-tests-webapp.yml.
Smoke e2e — *.e2e.test.ts
End-to-end auth baseline that proves the route auth plumbing is wired up.
Each file spins up its own webapp + Postgres + Redis container in
beforeAll (~30s startup). Vitest config: vitest.e2e.config.ts. Run on
every PR via e2e-webapp.yml.
cd apps/webapp
pnpm exec vitest --config vitest.e2e.config.ts
Comprehensive auth e2e — *.e2e.full.test.ts
The full RBAC auth matrix — every route family with explicit pass/fail scenarios. See TRI-8731 for the parent ticket and TRI-8732 onwards for each family's coverage spec.
Architecture: one container reused across the whole suite via
vitest.e2e.full.config.ts's globalSetup. Test files share the server
through getTestServer() from helpers/sharedTestServer.ts. Each test
seeds its own resources so order doesn't matter.
Layout:
| File | Top-level describe | Family subtasks |
|---|---|---|
auth-api.e2e.full.test.ts |
API |
TRI-8733 trigger, TRI-8734 run resource, TRI-8735 run mutations, TRI-8736 run lists, TRI-8737 batches, TRI-8738 prompts, TRI-8739 deployments + query, TRI-8740 waitpoints + input streams, TRI-8741 PAT |
auth-dashboard.e2e.full.test.ts |
Dashboard |
TRI-8742 admin pages |
auth-cross-cutting.e2e.full.test.ts |
Cross-cutting |
TRI-8743 deleted projects / revoked keys / expired JWTs / env mismatch / force-fallback toggle |
Adding a new family: pick the relevant file, add a nested describe
block. Inside, seed your own fixtures via the helpers and hit the shared
server.
describe("Trigger task", () => {
const server = getTestServer();
it("missing Authorization → 401", async () => {
const res = await server.webapp.fetch("/api/v1/tasks/x/trigger", { method: "POST", body: "{}" });
expect(res.status).toBe(401);
});
});
CI: e2e-webapp-auth-full.yml. Triggers on workflow_dispatch,
nightly schedule, and PRs touching auth-relevant paths (route builders,
rbac.server.ts, apiAuth.server.ts, apiroutes, the suite itself).
Run locally:
cd apps/webapp
pnpm exec vitest --config vitest.e2e.full.config.ts