MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-49

1.4.6 Revision history: WorkItemRevision table + repository + service integration

Done
Description

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

  • WorkItemRevision in prisma/schema.prisma with the verbatim fields + relations + indexes above. Migration add_work_item_revisions applies cleanly.
  • workItemRevisionRepository exports create(data, tx) (required-tx) and listByWorkItem(workItemId, { take, cursor }).
  • workItemsService.createWorkItem writes a 'created' revision in the same transaction as the work-item insert.
  • workItemsService.updateWorkItem writes an 'updated' revision with the field diff. Diff only includes changed fields; identical field assignments are omitted.
  • workItemsService.archiveWorkItem writes 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 extends
  • lib/repositories/workItemRepository.ts (from 1.4.2) — the repository pattern to mirror
  • prisma/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