feat(jobs): daily hard-delete retention cleanup (F-D10, 30-day default per D-001)
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 5/5
- Tempo estimado
- Mais de uma semana
- Facilidade para iniciantes
- 25/100
Direção de pesquisa
Start with CoreConfig and the soft delete and blob store interfaces in crates/thrust-core/src/lib.rs and crates/thrust-ports/src/lib.rs; then read the worker cron handler, native server timer, and schedules.rs. The issue is blocked by #35 and #43, so those dependencies need to land first. Done means retention deletes only rows past the configured cutoff, removes their blobs and eligible outbox rows, and passes the specified core, adapter, route, and scheduling tests.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Context
Only work items are soft-deleted today. delete_work_item (crates/thrust-core/src/lib.rs:301-344) reads the injected Clock (:309) and sets work_items.deleted_at (crates/thrust-adapters-postgres/migrations/0001_init.sql:40) through Tx::soft_delete_work_item (crates/thrust-ports/src/lib.rs:147-153; Postgres at crates/thrust-adapters-postgres/src/lib.rs:444-455). Nothing ever removes a row: a search of crates/, scripts/ and docs/src/ for hard_delete, purge or retention finds only the R2 binding probe, and the outbox table (0001_init.sql:54-62) only grows. The only scheduled work is the outbox sweeper. The Worker's single cron, * * * * * (crates/thrust-worker/wrangler.toml:20-21), runs it on every trigger; the handler only logs event.cron() and does not branch on it (crates/thrust-worker/src/lib.rs:193-213). The native server runs it on an optional interval (crates/thrust-server/src/main.rs:93-106). ADR-0072's SCHEDULES table in crates/thrust-core/src/schedules.rs (ADR-0072 lines 38-46) does not exist.
Problem
- The plan requires this cleanup. F-D10 is P0 with hard delete at 30 days (
FEATURES.md:83). D-001 records the 30-day default against Plane's 60 (DEVIATIONS.md:9). Q-0043.2 keeps the daily cadence (QUESTIONS.md:181). ADR-0043 notes that nothing else would purge soft-deleted rows (line 93). The review puts the cleanup in M2 (§3, FR-01), and TRACEABILITY asks for a cron test and a retention test (TRACEABILITY.md:54). - Plane at the pin:
hard_deleteruns daily at 00:00 UTC (apps/api/plane/celery.py:61-63) and readsHARD_DELETE_AFTER_DAYS(settings/common.py:420, default 60). It deletes rows withdeleted_at < now - days, first from 18 named models (bgtasks/deletion_task.py:138-180) and then from every model that has adeleted_atfield (:182-191). The comparison is strict, so a row deleted exactly N days ago stays. - ADR-0046 keeps outbox rows for 90 days and says the retention job is not built (lines 32, 71, 120). Under FR-18 the retained outbox is the P0 audit trail, so this purge also sets the P0 audit retention (Q-0048.1).
- The purge runs across tenants, so it cannot take a
Scope. Attachments have no row: they are blobs under<workspace>/<project>/<item>/(crates/thrust-core/src/lib.rs:468-470), so purging an item's row would leave its blobs behind.
Proposal
- Config:
CoreConfig(crates/thrust-core/src/lib.rs:29-47) gainshard_delete_after_days: u32(default 30, D-001) andoutbox_retention_days: u32(default 90), read fromTHRUST_HARD_DELETE_AFTER_DAYSandTHRUST_OUTBOX_RETENTION_DAYS. A negative or unparseable value fails startup, for the reason Plane gives atsettings/common.py:423-436. thrust_core::run_retention(app, &SystemScope)takes theSystemScopefrom #35. Withcutoff = clock.now() - days, it deletes rows withdeleted_at < cutoffin bounded batches, children before parents, from every soft-deletable table (work_itemstoday; later tables join a registry). For each purged item it also deletes the blobs under the item's prefix (BlobStore::listanddelete,crates/thrust-ports/src/lib.rs:200-205). Then it deletes outbox rows that were created more than 90 days ago and have been dispatched (created_at < now - 90 d AND dispatched_at IS NOT NULL); it never purges an undispatched row.- What the purge leaves:
project_sequencesis never touched, so a purged number is never handed out again (ADR-0043 B1). Activity rows are kept, as Plane declaresIssueActivity.issuewithon_delete=DO_NOTHING(apps/api/plane/db/models/issue.py:416). - Scheduling:
schedules.rsholds two entries,outbox-sweep(* * * * *) andhard-delete(0 0 * * *).- The single Worker from #43 adds
"0 0 * * *"totriggers.cronsand dispatches onevent.cron(). - The native server adds a daily timer at 00:00 UTC. It runs as a single process (ADR-0070 round 29), so no leader election is needed (ADR-0072 line 56).
- Each run works through bounded batches inline, within the 15-minute cron wall time (FR-05), instead of enqueuing work as ADR-0072 line 22 says; record that in ADR-0072. A run is idempotent.
- The single Worker from #43 adds
- Test route:
POST /internal/retentiontakes anowoverride. Like/internal/sweep(crates/thrust-api/src/lib.rs:158-160,275), it needs test routes and the admin token, so the contract suite can drive it on all four targets. - F-D10's log cleanups (API, email and webhook logs, versions) join this job once their tables exist.
Acceptance criteria
-
crates/thrust-core/tests/retention.rspasses. It is hermetic: memory adapters, with a settable clock in place ofFixedClock(tests/common/mod.rs:33-39). With 30-day retention, in both tenants, items soft-deleted 31 days and 30 days plus 1 s ago are gone; items deleted exactly 30 days ago and 29 days ago, and live items, remain; a later create gets the next sequence number. Control: two mutants added toscripts/mutants.sh, one comparing with<=and one ignoring the cutoff, each make it fail. - The outbox follows the same rule: a dispatched row created 91 days ago is gone, while a dispatched row created 89 days ago and an undispatched row created 200 days ago remain. Control: a mutant that purges on age alone makes it fail.
- A purged item's prefix lists no blobs, and another item's blobs remain.
-
retention_purges_only_rows_past_the_windowpasses through/internal/retentionon all four targets.pg_adapter.rscovers the purge SQL on real Postgres, with the cutoff bound as a typed parameter (execute_typed). - In
worker_only.rs, next toworker_cron_scheduled_handler_runs_the_sweeper(:110-134), withTHRUST_HARD_DELETE_AFTER_DAYS=0: after/__scheduled?cron=*+*+*+*+*, a deleted item is still listed withscope=with_deleted; after/__scheduled?cron=0+0+*+*+*, it is gone. Control: a handler that ignoresevent.cron()makes it fail. - A test asserts that
wrangler.tomltriggers.cronsandSCHEDULEShold the same cron set. Adding a cron to only one of them makes it fail.
References
- Review: .agents/plans/contrail/reviews/2026-10-19-fable-architecture-review.md (FR-01; §3 M2 row; FR-18 (3))
- FEATURES F-D10 (
FEATURES.md:83); DEVIATIONS D-001 (DEVIATIONS.md:9); TRACEABILITY F-D10 (TRACEABILITY.md:54); Q-0043.2 (QUESTIONS.md:181), Q-0046.2 (:185), Q-0073.2 (:110), Q-0048.1 (:279) - ADR-0043 (lines 79, 89, 93), ADR-0072 (lines 22, 38-46, 56, 60-61, 89), ADR-0046 (lines 32, 71, 120), ADR-0070 (round-29 note), ADR-0045
crates/thrust-core/src/lib.rs:29-47,301-344,468-470;crates/thrust-ports/src/lib.rs:147-153,200-205;crates/thrust-adapters-postgres/src/lib.rs:108-127,444-455;crates/thrust-adapters-postgres/migrations/0001_init.sql:40,54-62crates/thrust-worker/wrangler.toml:20-21;crates/thrust-worker/src/lib.rs:193-213;crates/thrust-server/src/main.rs:93-106;crates/thrust-api/src/lib.rs:158-160,275;crates/thrust-core/tests/common/mod.rs:33-39;scripts/mutants.sh:30-32- Plane oracle (
oracle/v1-pin-20261018):apps/api/plane/bgtasks/deletion_task.py:114-193;apps/api/plane/celery.py:61-63;apps/api/plane/settings/common.py:420,423-448;apps/api/plane/db/models/issue.py:416
Blocked by: #35, #43
Finding: FR-01
- Linguagem predominante
- Rust
- Estrelas
- 0
- Forks
- 0
- Merge médio
- 1h 35min
- PRs com merge (30d)
- 67
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de aRustyDev/thrust
-
area:process prio:p1 type:security
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
Mantenedores costumam responder em até 1 dia
-
area:process bug prio:p1
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 38/100
Mantenedores costumam responder em até 1 dia
-
area:process prio:p1
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 55/100
Mantenedores costumam responder em até 1 dia
-
area:process prio:p1
Dificuldade 4/5 1-2 dias Facilidade para iniciantes 55/100
Mantenedores costumam responder em até 1 dia
-
area:process prio:p1
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 56/100
Mantenedores costumam responder em até 1 dia
Todas as issues de aRustyDev/thrust
Issues semelhantes
-
bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
-
area:frontend kind:bug priority:p3 ready
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 88/100
Mantenedores costumam responder em até 1 dia
-
bug priority: medium ui
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
storytold/vectorcraft#6333 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
bug lexer
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
Mantenedores costumam responder em até 1 dia
-
yara-x fails to build with rsa 0.10.0: unresolved import rsa::Pkcs1v15SignTalvez já em andamento @CfirTsabari assumiu hoje. Aberta
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 87/100
VirusTotal/yara-x#865 ·
Mantenedores costumam responder em até 1 dia