feat: enforce scheduled content retention by partition drop - #1481
Open
pjb157 wants to merge 43 commits into
Open
feat: enforce scheduled content retention by partition drop#1481pjb157 wants to merge 43 commits into
pjb157 wants to merge 43 commits into
Conversation
Deploying control-layer with
|
| Latest commit: |
03a0d3f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b4faad77.control-layer.pages.dev |
| Branch Preview URL: | https://peter-retention-lifecycle.control-layer.pages.dev |
pjb157
force-pushed
the
peter/privacy-aware-request-logging
branch
from
August 13, 2026 15:10
1214843 to
77c7cd3
Compare
pjb157
force-pushed
the
peter/retention-lifecycle
branch
2 times, most recently
from
August 14, 2026 08:52
2a954a9 to
68db145
Compare
pjb157
changed the base branch from
peter/privacy-aware-request-logging
to
main
August 14, 2026 08:52
pjb157
force-pushed
the
peter/retention-lifecycle
branch
from
August 14, 2026 09:17
68db145 to
70f57d0
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds policy-driven, disabled-by-default content retention to the fusillade daemon and Postgres storage backend, exposing a configurable sweep worker that expires files, ages out terminal batches, and deletes/redacts eligible batchless requests while emitting aggregate metrics.
Changes:
- Introduces retention policy/cutoff/outcome types in
fusillade-coreand re-exports them throughfusilladeandfusillade-arsenal. - Adds a retention sweep worker to the daemon with startup validation and bounded per-tick chunking.
- Implements Postgres retention sweep logic plus a migration for retention-related access-path indexes and corresponding operator documentation/config validation.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| fusillade/src/manager/mod.rs | Re-exports retention sweep types from fusillade-core. |
| fusillade/src/lib.rs | Publicly re-exports retention sweep types from the manager module. |
| fusillade/src/daemon/transitions.rs | Updates test storage stub to satisfy new DaemonStorage retention API. |
| fusillade/src/daemon/mod.rs | Adds retention startup validation, shutdown-aware helper, and retention sweep background task with metrics. |
| fusillade/src/daemon/config.rs | Adds retention policy + sweep interval fields to daemon config and round-trip tests. |
| fusillade/README.md | Documents automated content retention behavior and operational rollout steps. |
| fusillade-core/src/manager.rs | Defines RetentionSweepPolicy, immutable cutoffs, sweep outcome, and extends DaemonStorage. |
| fusillade-core/src/lib.rs | Re-exports retention sweep types from manager. |
| fusillade-core/src/daemon_record/transitions.rs | Updates test storage stub to satisfy new retention API. |
| fusillade-arsenal/src/postgres.rs | Implements Postgres retention sweeping plus lock-ordering changes and extensive regression tests. |
| fusillade-arsenal/src/lib.rs | Re-exports retention sweep types from fusillade-core. |
| fusillade-arsenal/migrations/20260813000000_add_retention_sweep_indexes.up.sql | Adds (non-concurrent) creation of candidate retention indexes with precreate guidance in comments. |
| fusillade-arsenal/migrations/20260813000000_add_retention_sweep_indexes.down.sql | Drops the new retention sweep indexes. |
| dwctl/src/config.rs | Wires retention config into dwctl, adds validation, and tests for config invariants. |
| .github/fixtures/fusillade-migration-sha384.txt | Updates migration checksum fixture to include the new migration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
8442
to
8444
| let lease_acquired: bool = sqlx::query_scalar( | ||
| "SELECT pg_try_advisory_xact_lock(hashtextextended('fusillade.retention.sweep', 0))", | ||
| ) |
Comment on lines
+8544
to
+8549
| SELECT 1 FROM requests r | ||
| WHERE r.batch_id = batches.id | ||
| AND r.state = 'canceled' | ||
| AND r.claimed_at IS NOT NULL | ||
| AND r.canceled_at > NOW() - make_interval(secs => $3) | ||
| ) |
pjb157
force-pushed
the
peter/retention-lifecycle
branch
from
August 14, 2026 09:30
70f57d0 to
38678f8
Compare
Contributor
Author
|
Re-review follow-up is available in
Verification:
|
pjb157
force-pushed
the
peter/retention-lifecycle
branch
from
August 16, 2026 10:43
8f4aa4d to
3a432e9
Compare
pjb157
force-pushed
the
peter/retention-lifecycle
branch
from
August 18, 2026 09:22
26687f4 to
57720e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the data retention policy for every class of customer content the API stores. Scheduled deletion is partition drop only — content lives in time-keyed partitions and expires by irreversibly dropping whole relations through one shared, journaled, crash-safe engine. There is no recurring row-by-row content deletion anywhere after this PR, and every destructive control ships disabled.
How each data class is deleted
retained_response_objectspartitions, keyed by deletion datebatch_requests_archivepartitionsbatches.retention_expired_atin the same transactionrequest_templates_g2partitions; new writes cut over behind a flag, the legacy heap freezes in place untouchedretention_expired_at, row survives) once every referencing batch's content is gone; batches are stamped at partition drop. Account-lifetime retention, exactly as the policy statesOne retirement engine, three families
Every partition drop runs through a single state machine (
partition_retirement.rs): journal the exact partition identity (schema + OID, parent OID, child name + OID, bounds — with a CHECK constraint that makes tampered identities unrepresentable), fence the bucketretiringin the same commit so reads fail closed,DETACH PARTITION CONCURRENTLY,FINALIZErecovery after crashes, drop only the journaled relation, and complete atomically. Families differ only in a declarative spec: names, bounds width, eligibility SQL, and an optional metadata stamp. Lock/statement timeouts are retryable no-ops on a durable journal; renamed, re-bounded, or replaced relations are refused; an unfinished journal remains recoverable even after its flag or period is withdrawn.Destructive DDL runs only on an explicitly installed single-session maintenance pool (max 1 connection), attested at startup against the primary, with server-side lock and statement timeouts.
Reads and writers
Point, list, and count reads are byte-identical before and after content moves; everything in a
retiring/retiredbucket answers not-found before any physical DDL. Template reads are generation-transparent through one view (the claim path resolves generation-2 ids through a route oracle that prunes to a single weekly partition). Late writers to moved or dropped response identities are blocked by durable content-free fences; claim, pending, and mutation paths remain live-only.Safety defaults and flags (all off)
batchless_archive_sweep_enabled,batchless_archive_backfill_enabled,retained_response_retirement_enabled,batch_archive_retirement_enabled+batch_archive_retention_days,template_generation_writes_enabled,template_retirement_enabled+template_retention_days. Retention periods have no defaults — enabling any retirement without an explicit positive period fails startup validation, as does enabling retirement without the maintenance endpoint on a dedicated database, or template retirement without the write cutover.Observability and evidence
Aggregate-only metrics with fixed labels for every phase (movement counts/bytes, partition runway and readiness, per-family retirement and retry counters, route/fence cleanup counters, file-content expiry). The retirement journal and bucket tombstones are permanent, dated, content-free records of every deletion — audit evidence by construction. No request identifiers, owners, models, or payloads appear in any log, metric, or error (enforced by the repo's no-payload-logging guard). A read-only preflight script verifies index readiness and exact partition attachment before any enablement.
Testing
Rollout (each step independently reversible until noted)
🤖 Generated with Claude Code