Estimate: 26m · Depends on: 1.4.4, 1.4.5, 1.4.6
Comprehensive integration tests against a real Postgres covering the structural invariants. Per Yue's standing rule (and feedback_planner_decides_user_approves's no-mocks-on-DB principle), these tests exercise the actual Postgres triggers + RLS + service-layer transactions. They are the safety net Epic 2-7 will lean on every time they touch the work-item path; landing them in 1.4 means later Epics can confidently extend without fear of silently breaking the kind-parent rule.
Test areas:
IllegalParentTypeError. Drive via workItemsService.createWorkItem AND via direct repo writes (proves the trigger fires regardless of path).DepthLimitExceededError.ParentCycleError.createWorkItem calls against the same project; assert the resulting keys are 1..20 (or contiguous from the starting count) with no duplicates and no lost slots beyond rolled-back transactions (none roll back here, so no gaps expected).WITH CHECK: A cannot insert a work item with W2's workspace_id (constraint rejection).moveWorkItem; the resulting positions sort lexically as expected. Edge cases: move-to-start, move-to-end, move-between.title → revision has { title: { from, to } } and nothing else. Update title + assigneeId → both in the diff. No-op patch → no revision written, no transaction opened. Update explanationMd while explanationSource = ai_draft → diff includes both explanationMd AND the auto-transitioned explanationSource: { from: ai_draft, to: user_edited } (the source transition is itself an audit-worthy event).explanationMd = NULL has source user_authored. A subsequent update writing explanationMd + explicit source ai_draft (the path AI-drafting Epic 7 takes) sets source = ai_draft. A subsequent update patching only explanationMd (no explicit source in the patch) auto-transitions source to user_edited — verified by an integration test in 1.4.7. A subsequent update with explicit source ai_draft (a regenerate) resets the badge. Direct PATCH of explanationSource alone (no explanationMd) is allowed (e.g., user manually dismisses the AI-draft badge) — the diff records it.WorkItemLinkCycleError. Deeper cycle: A→B→C→A; rejected on the closing edge. relates_to A↔B does NOT trigger cycle check (intended).SelfLinkError.CrossWorkspaceLinkError at the service layer, and the trigger backstops if the service is bypassed.relates_to: linkWorkItems(A, B, 'relates_to') produces TWO rows (A→B and B→A); unlinkWorkItems on either deletes both.DuplicateLinkError (unique constraint).isReady(A) returns false. Mark B done → still false (C blocks). Mark C done → returns true. Unlink C while B is still open → returns false again.getBlockers(A) returns B even when called under a P1-narrowed project context (link table is workspace-scoped, not project-scoped).What you'll do: Add tests under tests/integration/work-items/. Use the test-fixture helpers from 1.2.7 + 1.3.5 that spin up users + workspaces + projects against the real Postgres. Add workItemFixtures.ts in tests/fixtures for repeatable work-item setups. Tests run against the same docker-compose'd Postgres as 1.2.7 / 1.3.5.
tests/integration/work-items/ cover every area enumerated above; every test names the invariant it protects in its describe-block.Promise.all over 20 createWorkItem calls; resulting keys form a contiguous set.tests/integration/projects/ and tests/integration/workspaces/ — the integration-test pattern, fixture helperstests/fixtures/userFixtures.ts / workspaceFixtures.ts / projectFixtures.tslib/services/workItemsService.ts + repos + errors (the system under test)