feat(iios): retention_policy_snapshot + sweep (archive→redact, hold-aware) (P9)

Adds IiosRetentionPolicySnapshot (per-interaction frozen lifecycle timestamps)
+ IiosInteraction.dataClass, and a RetentionService whose sweep captures
snapshots then archives (status change) and, past delete_after, redacts content
in place (reusing the Slice-8 tombstone) — never a hard delete. An active
compliance hold blocks both; every action is audited and tenant-scopeable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 12:50:49 +05:30
parent a5cd8f0ec4
commit 035315ef73
7 changed files with 310 additions and 1 deletions
@@ -435,6 +435,7 @@ model IiosInteraction {
occurredAt DateTime @default(now())
receivedAt DateTime @default(now())
status IiosDeliveryState @default(RECEIVED)
dataClass String @default("internal") // envelope data class; drives retention window (P9)
policyDecisionRef String?
consentReceiptRef String?
crreBundleRef String?
@@ -493,6 +494,27 @@ model IiosProcessedEvent {
@@id([consumerName, eventId])
}
/// Per-resource retention snapshot (P9). Absolute lifecycle timestamps frozen from the
/// resource's age + policy window; read by the retention sweep. delete = redact-in-place.
model IiosRetentionPolicySnapshot {
id String @id @default(cuid())
policyKey String
scopeSnapshotId String
targetType String // 'interaction'
targetId String
dataClass String
retainUntil DateTime?
archiveAfter DateTime
deleteAfter DateTime
sourceVersion String @default("v1")
status String @default("ACTIVE") // ACTIVE | ARCHIVED | REDACTED
createdAt DateTime @default(now())
@@unique([targetType, targetId])
@@index([status, deleteAfter])
@@index([scopeSnapshotId, status])
}
/// Legal/compliance hold (P9 DSR). An ACTIVE, unexpired hold over a target BLOCKS
/// erasure/retention deletion until an authorised release. Never auto-released.
model IiosComplianceHold {