Skip to content

feat(AF-499): Data Lifecycle Manager — retention policies, right-to-erasure, proxy-enforced pseudonymization & compliance#518

Merged
babltiga merged 16 commits into
mainfrom
feature/AF-499-data-lifecycle-manager
Jun 30, 2026
Merged

feat(AF-499): Data Lifecycle Manager — retention policies, right-to-erasure, proxy-enforced pseudonymization & compliance#518
babltiga merged 16 commits into
mainfrom
feature/AF-499-data-lifecycle-manager

Conversation

@babltiga

@babltiga babltiga commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the Data Lifecycle Manager epic (#499) end-to-end: a new lifecycle Spring Modulith module plus targeted extensions to proxy, workflow/ai, audit, compliance, notifications, and the engine plugins — with full frontend, e2e, docs, website, and 7-locale i18n.

Built as verified, compiling, individually-green milestones (see commits). Opened as draft during construction; now complete.

What's implemented

  • Retention policies — per-datasource CRUD (/api/v1/lifecycle/policies, ADMIN, validated), targeting table/column-set/classification-tag with a window + action (HARD_DELETE/SOFT_DELETE/PSEUDONYMIZE); dry-run preview (impact without executing); clustered-safe RetentionPolicyScanJob.
  • Right-to-erasuredeletion_requests state machine PENDING_SCOPE_AI → PENDING_REVIEW → APPROVED → EXECUTED (+ REJECTED/FAILED/CANCELLED); self-service submit + admin review (submitter can never self-approve); async scope detection (AI plug-in point).
  • Proxy enforcement — read-time pseudonymization (LifecycleTransformColumnMaskDirective, per-org AES-256-GCM salt with rotation); soft-delete read filter (new IS_NULL operator) + DELETE → UPDATE rewrite in RowSecurityRewriter (fail-closed). Engine appliers (mongo/couchbase/es/dynamo) handle IS_NULL natively.
  • Approved-erasure executionErasureExecutionJob runs governed, parameter-bound deletes per scope table through the proxy, reusing the soft-delete rewrite; writes lifecycle_runs + tamper-evident DATA_ERASURE_COMPLETED proof-of-deletion audit; EXECUTED/FAILED.
  • ComplianceRETENTION_ADHERENCE report (deletion history) over lifecycle_runs, signed PDF/CSV export.
  • NotificationsERASURE_APPROVED fan-out to the submitter across all chat channels + in-app.
  • Frontend/admin/lifecycle/policies (CRUD + preview), /lifecycle/erasure (submit), /admin/lifecycle/erasure (review queue, optimistic approve/reject); types, api, enum labels, routing, sidebar, 7-locale i18n.
  • Docs/website/i18n0309, README, marketing homepage card + operator-docs section + content-source map; backend + frontend locale parity.

Acceptance criteria

  • Retention policies CRUD + preview (validated, ADMIN, org-scoped)
  • RetentionPolicyScanJob clustered-safe + idempotent + per-row tolerant
  • Right-to-erasure workflow + scope detection + no self-approval + soft-delete fallback
  • Proxy enforcement: soft-delete filter + DELETE→UPDATE + read-time pseudonymization (fail-closed, no string-concat)
  • Configurable transforms (SHA256_SALTED + encrypted salt + rotation, FORMAT_PRESERVING, TOKENIZATION); aggregates preserved
  • Dry-run preview reports impact without executing
  • Proof-of-deletion audit_log rows + completion notifications
  • Retention-adherence compliance report (signed PDF/CSV)
  • Docs (0309), README, website, i18n updated; locale + validation parity green
  • ApplicationModulesTest + ApiPackageDependencyTest pass; coverage gates green

Verification

Backend: lifecycle suite + ApplicationModulesTest + ApiPackageDependencyTest + MessagesParityTest green; engine appliers compile + pass. Frontend: lint + typecheck + test:coverage (974 tests, all thresholds met) + build green; locale parity green. E2E: lifecycle-manager.spec.ts (policy preview + erasure approve via UI).

Known follow-ups (noted in docs)

  • AI-assisted per-table subject-column detection (the erasure executor currently derives the linking column from subject_type).
  • Automatic referential-integrity → soft-delete fallback on hard-delete failure.

Closes #499.

babltiga added 4 commits June 29, 2026 15:53
…ation

Add the lifecycle Spring Modulith module foundation: V103 schema
(retention_policies, deletion_requests, decisions, lifecycle_runs,
lifecycle_salt + enums), retention-policy CRUD + dry-run preview via the
proxy QueryDryRunService, audit actions/resource types, and 7-locale i18n.
Module boundaries, api-package purity, and i18n parity verified.
Add lifecycle_runs entity/repo, RetentionPolicyScanService (idempotent
per-policy staging via dry-run eligibility), the clustered-safe
RetentionPolicyScanJob (@scheduled + @SchedulerLock), and the
LifecycleScanCompletedEvent. Docs (05/09) + CLAUDE.md env table updated.
Add deletion_requests + decisions persistence, the erasure state machine
(PENDING_SCOPE_AI -> PENDING_REVIEW -> APPROVED/REJECTED/CANCELLED),
self-service submit/list/cancel + admin review (approve/reject, no
self-approval) endpoints, the async ErasureScopeAnalyzer (deterministic
scope from retention policies; AI plug-in point), events, audit rows, and
unit tests. API spec updated.
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Backend Test Results

4 340 tests  +86   4 340 ✅ +86   13m 5s ⏱️ -42s
  577 suites +14       0 💤 ± 0 
  577 files   +14       0 ❌ ± 0 

Results for commit 7deb4c4. ± Comparison against base commit 81ef875.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Backend Code Coverage

Overall Project 93.1% -0.42% 🍏
Files changed 89.1% 🍏

File Coverage
ErasureSelfApprovalException.java 100% 🍏
LifecycleRunKind.java 100% 🍏
LifecycleRunView.java 100% 🍏
RetentionPolicyNotFoundException.java 100% 🍏
UpdateRetentionPolicyCommand.java 100% 🍏
ErasureDecision.java 100% 🍏
LifecycleAction.java 100% 🍏
ErasureStatus.java 100% 🍏
LifecycleSubjectType.java 100% 🍏
DeletionRequestInvalidStateException.java 100% 🍏
DeletionRequestNotFoundException.java 100% 🍏
CreateRetentionPolicyCommand.java 100% 🍏
ErasureRequestService.java 100% 🍏
RetentionPolicyView.java 100% 🍏
LifecycleTransform.java 100% 🍏
LifecycleException.java 100% 🍏
InvalidRetentionPolicyException.java 100% 🍏
ErasureRequestView.java 100% 🍏
LifecycleRunStatus.java 100% 🍏
ErasureReviewService.java 100% 🍏
ErasureRequestResponse.java 100% 🍏
ErasureRequestPageResponse.java 100% 🍏
UpdateRetentionPolicyRequest.java 100% 🍏
RetentionPolicyPageResponse.java 100% 🍏
LifecyclePreviewResponse.java 100% 🍏
CreateRetentionPolicyRequest.java 100% 🍏
RetentionPolicyResponse.java 100% 🍏
SubmitErasureRequestBody.java 100% 🍏
AdminErasureRequestController.java 100% 🍏
ErasureDecisionRequest.java 100% 🍏
NotificationEventType.java 100% 🍏
RetentionAdherenceReportRow.java 100% 🍏
ComplianceReportType.java 100% 🍏
DeletionRequestEntity.java 100% 🍏
RetentionPolicyEntity.java 100% 🍏
DefaultErasureReviewService.java 100% 🍏
RetentionPolicyScanService.java 100% 🍏
ErasureRequestViewMapper.java 100% 🍏
DefaultLifecycleRunLookupService.java 100% 🍏
RetentionWindow.java 100% 🍏
RetentionPolicyScanJob.java 100% 🍏
LifecycleConfiguration.java 100% 🍏
LifecycleProperties.java 100% 🍏
RowSecurityOperator.java 100% 🍏
SoftDeleteDirective.java 100% 🍏
ErasureRequestRejectedEvent.java 100% 🍏
LifecycleScanCompletedEvent.java 100% 🍏
ErasureRequestSubmittedEvent.java 100% 🍏
ErasureScopeAnalyzedEvent.java 100% 🍏
ErasureRequestApprovedEvent.java 100% 🍏
AuditAction.java 100% 🍏
AuditResourceType.java 100% 🍏
DefaultRetentionPolicyService.java 99% -1% 🍏
RetentionPolicyController.java 98.45% -1.55% 🍏
DefaultLifecycleDirectiveResolutionService.java 98.29% -1.71% 🍏
ComplianceReport.java 97.47% -2.53% 🍏
DefaultComplianceReportService.java 96.82% -1.59% 🍏
RetentionPolicyViewMapper.java 96.55% -3.45% 🍏
LifecycleSaltService.java 96.08% -3.92% 🍏
ErasureRequestController.java 96.04% -3.96% 🍏
LifecycleExceptionHandler.java 95.4% -4.6% 🍏
ErasureExecutionService.java 95.25% -4.75% 🍏
LifecyclePreviewCalculator.java 95% -5% 🍏
DefaultQueryLifecycleService.java 93.58% 🍏
ColumnMasker.java 93.3% -1.03% 🍏
DefaultErasureRequestService.java 93.25% -6.75% 🍏
DefaultQueryExecutor.java 93.22% 🍏
QueryExecutionRequest.java 92.82% -0.96% 🍏
LifecyclePageAdapter.java 92.73% -7.27% 🍏
ErasureScopeAnalyzer.java 92.06% -7.94% 🍏
LifecyclePreviewResult.java 90.91% -9.09% 🍏
DefaultQueryDryRunService.java 90.88% 🍏
RowSecurityRewriter.java 89.96% -1.69% 🍏
SpringPageableAdapter.java 85% -15% 🍏
ComplianceReportController.java 84.14% -10.34% 🍏
MsTeamsPayloadFactory.java 83.02% -0.34% 🍏
SlackBlockKitFactory.java 82.17% -0.43% 🍏
TelegramMessageFactory.java 81.94% -0.45% 🍏
NotificationDispatcher.java 80.91% -2.95% 🍏
DiscordPayloadFactory.java 80.61% -0.47% 🍏
CompliancePdfWriter.java 80.34% -9.43% 🍏
ComplianceReportResponse.java 78.59% -21.41% 🍏
EmailNotificationStrategy.java 77.85% -0.17% 🍏
NotificationListener.java 74.71% -4.6% 🍏
NotificationContextBuilder.java 70.6% -0.09% 🍏
ComplianceCsvWriter.java 63.12% -32.1% 🍏
LifecycleAuditWriter.java 53.19% -46.81% 🍏
ErasureExecutionJob.java 30% -70% 🍏
LifecycleRunEntity.java 0% 🍏

babltiga added 5 commits June 29, 2026 17:37
Add Testcontainers integration tests for the retention-policy controller
(CRUD + preview + validation/404/403/401) and the erasure controllers
(submit -> async scope -> approve/reject, self-approval 403, cancel, 404),
exercising the controllers, DTOs, entities, repos, mappers, exception
handler, page adapters, and audit writer end to end; plus a
LifecyclePreviewCalculator unit test. Drop the surrounding @transactional
on preview() so the proxy dry-run's datasource-lookup failure degrades to a
best-effort estimate instead of poisoning the transaction.
Enabled PSEUDONYMIZE retention policies now contribute post-fetch
ColumnMaskDirectives (LifecycleDirectiveResolutionService) merged into the
query execution path by DefaultQueryLifecycleService, applied by the shared
ColumnMasker. LifecycleTransform maps onto the masker; SHA256_SALTED/
TOKENIZATION use a per-org salt (LifecycleSaltService, AES-256-GCM in
lifecycle_salt, rotatable) via a new backward-compatible salt param on the
HASH strategy. Docs 05/07 updated.
… rewrite

Add SoftDeleteDirective + IS_NULL RowSecurityOperator. RowSecurityRewriter
now rewrites a plain DELETE on a soft-delete target into UPDATE SET
marker=CURRENT_TIMESTAMP (fail-closed on DELETE...USING/joins), and an
IS_NULL read filter hides soft-deleted rows from SELECT/UPDATE/DELETE.
LifecycleDirectiveResolutionService resolves both from enabled SOFT_DELETE
policies; DefaultQueryLifecycleService merges them onto the execution
request (new softDeleteDirectives field, backward-compatible ctor). Docs 05
updated.
…liers

The new IS_NULL operator broke the exhaustive operator switches in the
mongodb/couchbase/elasticsearch/dynamodb row-security appliers. Translate it
to each engine's natural is-null/absent filter (Mongo {field:null}, N1QL IS
NULL, ES must_not exists, PartiQL IS MISSING) before the empty-values
deny-all guard, so a soft-delete read filter hides only deleted rows. Add
EsJson.exists + per-engine applier tests. Cassandra/Neo4j already fail-closed
via their default case.
Add the /admin/lifecycle/policies page (list + create modal + delete + dry-run
preview), api/lifecycle.ts, lifecycle types + enum-label helpers, route +
sidebar entry, and 7-locale i18n (parity green). Docs 06 + website feature
card updated.
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Frontend Test Results

975 tests  +3   975 ✅ +3   2m 22s ⏱️ -7s
133 suites +1     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 7deb4c4. ± Comparison against base commit 81ef875.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Frontend Coverage (frontend)

Status Category Percentage Covered / Total
🔵 Lines 94.12% (🎯 90%) 1731 / 1839
🔵 Statements 92% (🎯 90%) 1932 / 2100
🔵 Functions 92.4% (🎯 90%) 487 / 527
🔵 Branches 82.04% (🎯 80%) 1042 / 1270
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
frontend/src/utils/enumLabels.ts 97.45% 100% 93.18% 97.43% 180, 188, 269
frontend/src/utils/erasureStatus.ts 100% 100% 100% 100%
Generated in workflow #638 for commit 7deb4c4 by the Vitest Coverage Report Action

babltiga added 6 commits June 29, 2026 18:57
Add the self-service /lifecycle/erasure submit page (form + my-requests list
with cancel) and the admin /admin/lifecycle/erasure review queue (optimistic
approve/reject + scope-snapshot expander), erasureStatus tag-colour util +
test, routes + sidebar entries, 7-locale i18n (parity green), and an e2e spec
(seed via API, drive preview + approve via UI) with helpers. Docs 06 updated.
Add ComplianceReportType.RETENTION_ADHERENCE + RetentionAdherenceReportRow,
a lifecycle.api LifecycleRunLookupService (compliance -> lifecycle.api,
acyclic) exposing lifecycle_runs over a period, and the report builder +
CSV/PDF writers + response mapping + GET /admin/compliance/reports/
retention-adherence (signed PDF/CSV export via the existing path). Backward-
compatible ComplianceReport ctor; tests for the report path + lookup. Docs
04/05 updated.
Add tests for lifecycleActionLabel / lifecycleTransformLabel /
lifecycleSubjectTypeLabel / erasureStatusLabel, raising enumLabels.ts
function coverage (the CI per-file report flagged the new helpers as
uncovered).
Wire an ERASURE_APPROVED NotificationEventType through the full fan-out:
NotificationListener consumes ErasureRequestApprovedEvent (now carrying
requestedBy) and dispatches via buildLifecycleErasure (submitter recipient) to
the chat factories (Slack/Discord/Teams/Telegram) + in-app; no email template
(fallback subject); PagerDuty not-applicable. notifications -> lifecycle.events
(acyclic). Context-builder tests + docs 05/08.
Add ErasureExecutionService + clustered-safe ErasureExecutionJob: per scope
table, run a governed parameter-bound DELETE FROM <table> through the proxy
(subject predicate as a bound RowSecurityDirective; table validated as a
simple identifier), reusing the SOFT_DELETE rewrite. Writes a lifecycle_runs
row + tamper-evident DATA_ERASURE_COMPLETED proof-of-deletion audit, then
transitions EXECUTED/FAILED (per-table failures isolated). New env var +
scheduled-job docs. Unit tests cover happy path, failure, empty scope, and
unsafe-identifier rejection.
Add the Data Lifecycle Manager to the README feature list, a 'Data lifecycle
& right-to-erasure' feature card on the marketing homepage, a #cfg-lifecycle
Configuration subsection (retention policies, erasure, env vars) on the
operator docs page, and the content-source map row.
@babltiga
babltiga marked this pull request as ready for review June 29, 2026 15:56
@babltiga babltiga changed the title feat(AF-499): Data Lifecycle Manager — module + retention policies (phase 1) feat(AF-499): Data Lifecycle Manager — retention policies, right-to-erasure, proxy-enforced pseudonymization & compliance Jun 29, 2026
… engines

The IS_NULL row-security handling changed the engine appliers, so their
reproducible shaded JARs drifted from the connector.json SHA-256 pins. Bump
each plugin version (mongodb 1.0.5, couchbase 1.0.4, elasticsearch 1.0.3,
dynamodb 1.0.2) and re-pin fileName/url/sha256. check-engine-pins.mjs green.
@babltiga
babltiga merged commit 424b2de into main Jun 30, 2026
28 checks passed
@babltiga
babltiga deleted the feature/AF-499-data-lifecycle-manager branch June 30, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AF-499: Data Lifecycle Manager — retention policies, right-to-erasure workflow, proxy-enforced pseudonymization & compliance reporting

1 participant