claude-code
claude-code copied to clipboard
Migrate all entities to versioned tuple-3 pattern with timestamp + deleted
Summary
Migrate all database entities to use the versioned tuple-3 pattern with simplified timestamp columns. This ensures true data immutability - the application can only INSERT, never UPDATE or DELETE.
Current State
We have a mix of:
-
Versioned entities (tuple-3):
(organizationId, id, version)withcreatedAt,updatedAt,deletedAt,isLatest,supersededAt -
Non-versioned entities: Simple
idPK withcreatedAt,updatedAt,deletedAt
Proposed Pattern
For ALL entities, use tuple-3 versioning:
model Entity {
organizationId String
id String @default(cuid())
version Int @default(1)
// Version tracking (simplified)
isLatest Boolean @default(true)
timestamp DateTime @default(now()) // Replaces createdAt/updatedAt/supersededAt
deleted Boolean @default(false) // Replaces deletedAt
// Entity data...
@@id([organizationId, id, version])
@@index([organizationId, id, isLatest])
}
How timestamps work:
-
Creation time:
timestampon version 1 -
Last update time:
timestampon latest version -
Deletion time:
timestampon the version wheredeleted=true
Benefits:
- True immutability: Every change = new row, no UPDATEs needed
-
Simpler schema: 2 columns (
timestamp,deleted) instead of 4 (createdAt,updatedAt,deletedAt,supersededAt) - Consistent pattern: Same approach for all entities
- Full audit trail: Every version preserved with who/when/why
- Point-in-time queries: Can reconstruct state at any moment
- DB-level protection: App role can be restricted to INSERT only
Entities to Migrate
Currently Versioned (update column pattern):
- [x] Organization → add tuple-3 PK
- [x] User ✓
- [x] CareWorker ✓
- [x] Client ✓
- [x] CarePlan ✓
- [x] RiskAssessment ✓
- [x] Visit ✓
- [x] VisitNote ✓
- [x] TrainingModule ✓
- [x] Policy → needs tuple-3
- [x] PolicyQuiz ✓
- [x] PolicyQuizQuestion ✓
- [x] PolicyQuizAttempt ✓
- [x] PolicyQuizAnswer ✓
Need Full Migration to Versioned:
- [ ] JobRole
- [ ] TrainingRoleRequirement
- [ ] CareWorkerAvailability
- [ ] ClientFunding
- [ ] FundingSource
- [ ] CarePlanDomain
- [ ] TaskTemplate
- [ ] VisitType
- [ ] VisitTypeTaskTemplate
- [ ] VisitTask
- [ ] TrainingCompletion
- [ ] ModuleSection
- [ ] ModuleContent
- [ ] QuizQuestion
- [ ] QuizAttempt
- [ ] QuizAnswer
- [ ] ModuleProgress
- [ ] TrainingBookmark
- [ ] TrainingContentHash
- [ ] PolicyVersion
- [ ] PolicyAcknowledgement
- [ ] OnboardingRequirement
- [ ] Document
- [ ] Contract
- [ ] HolidayRequest
- [ ] HolidayBalance
- [ ] Alert
- [ ] ComplianceOverride
- [ ] DataConsent
- [ ] DataRequest
- [ ] DataRetentionPolicy
- [ ] VisitTypePrice
- [ ] RateMultiplier
- [ ] BankHoliday
- [ ] ClientPriceOverride
- [ ] CareWorkerPayRate
- [ ] CareWorkerVisitTypeRate
- [ ] OvertimeRule
- [ ] ExpenseRate
- [ ] Invoice
- [ ] InvoiceLineItem
- [ ] Payment
- [ ] CreditNote
Possibly Exempt (truly ephemeral):
- [ ] PushToken - device tokens, replaced frequently
- [ ] Notification - transient, could expire
- [ ] NotificationPreferences - user settings, low audit value
Migration Strategy
-
Phase 1: Update existing versioned entities to use
timestamp+deleted - Phase 2: Migrate high-value entities (Invoice, Payment, Document, Contract)
- Phase 3: Migrate configuration entities (VisitType, FundingSource, etc.)
- Phase 4: Migrate operational entities (VisitTask, TrainingCompletion, etc.)
- Phase 5: Decide on ephemeral entities
Breaking Changes
- All queries need
WHERE isLatest = true AND deleted = false - Relations become more complex (reference by id, query with isLatest)
- Prisma relations may need to be removed in favor of manual queries
- API endpoints need to use versioned update pattern
Related
- #116 - Database immutability (this supersedes that approach)
- #133 - Database constraints
Labels
enhancement, database, architecture