1.4.6 Revision history: WorkItemRevision table + repository + service integration
Estimate: 18m · Depends on: 1.4.4
Land the WorkItemRevision table + repository, and wire the service layer's create / update / archive methods to emit revision rows atomically (inside the same transaction as the underlying mutation). The audit trail is what Epic 5's activity feed consumes, what Epic 6's reporting reads, and what Epic 7's "what changed since last planning pass" diffing requires. Shipping it in 1.4 (vs. deferring to Epic 5) means no migration that retro-fills history — every write from the moment this Subtask lands is auditable.
Schema: WorkItemRevision { id String @id @default(cuid()), workItemId String, changedById String, changedAt DateTime @default(now()), changeKind String /* 'created' | 'updated' | 'archived' */, diff Json }. Relations: workItem WorkItem @relation(onDelete: Cascade) + changedBy User @relation(onDelete: Restrict) (we want to keep history even if the user is deleted — but enforce that the writer existed at the time; user-soft-delete is the workaround if a user really needs to be removed). Indexes: @@index([workItemId, changedAt]) for the activity-feed query; @@index([workItemId]) for cascade.
Diff shape: JSON object { field: { from, to } }. For created, from is null and to is the initial value. For archived, { archivedAt: { from: null, to: <timestamp> } }. For updated, only changed fields appear.
What you'll do: Add WorkItemRevision to prisma/schema.prisma; generate the migration add_work_item_revisions. Add lib/repositories/workItemRevisionRepository.ts with create(data, tx), listByWorkItem(workItemId, paginate). Update workItemsService to call the revision repo inside its transactions (createWorkItem → 'created'; updateWorkItem with diff → 'updated'; archiveWorkItem → 'archived'; moveWorkItem and assignWorkItem are forms of updateWorkItem so their revisions flow through the same path). Add RLS policy on work_item_revision if 1.4.5 didn't already (it should have — coordinate).
Acceptance criteria
WorkItemRevisioninprisma/schema.prismawith the verbatim fields + relations + indexes above. Migrationadd_work_item_revisionsapplies cleanly.workItemRevisionRepositoryexportscreate(data, tx)(required-tx) andlistByWorkItem(workItemId, { take, cursor }).workItemsService.createWorkItemwrites a 'created' revision in the same transaction as the work-item insert.workItemsService.updateWorkItemwrites an 'updated' revision with the field diff. Diff only includes changed fields; identical field assignments are omitted.workItemsService.archiveWorkItemwrites an 'archived' revision.- Atomicity: an injected failure in the revision write rolls back the work-item write too. Test asserts this by mocking the revision repo to throw — both rows absent after.
- RLS verified: revisions for cross-workspace work items are unreadable (the policy joins to the parent work_item).
- Service-layer tests cover: revision-on-create has changeKind='created' + the full initial state; revision-on-update has only the changed fields; revision-on-archive has changeKind='archived'.
- All quality gates green; existing suite stays green.
Context refs
motir-core/CLAUDE.md— 4-layer rule (auto-loaded)lib/services/workItemsService.ts(from 1.4.4) — the existing transactional skeleton this Subtask extendslib/repositories/workItemRepository.ts(from 1.4.2) — the repository pattern to mirrorprisma/migrations/.../work_item_rls/migration.sql(from 1.4.5) — confirm the revisions RLS policy is in scope of that migration or add here- This Story page — revision schema spec + diff shape