Data lifecycle and deletion
Deletion is a lifecycle, not a single DELETE. The platform distinguishes immediate read suppression from physical purge, and it distinguishes an organization suspension from a deletion job. Confusing those states is how deleted data reappears through a cache or projection.
Memory lifecycle
The target memory flow is:
authenticated source
→ validate/scope/size
→ PII and safety checks
→ candidate observation
→ exact idempotency/hash dedup
→ near-duplicate/temporal/conflict analysis
→ active | quarantine | supersede | expire | delete
Every observation is intended to carry tenant/resource scope, actor/source, content hash, observed/processed/valid times, source reference, status, retention or legal hold, model/embedding/preprocessing identities, and links to duplicates, conflicts, or supersession.
The durable substrate exists locally, but the full append-only provenance ledger and accepted public lifecycle contract do not. GAP-007 remains open.
Forget
POST /v1/memories/{memory_id}/forget is a provisional revision-fenced tombstone:
- The caller must supply the expected lifecycle revision.
- The row is marked tombstoned with a new revision.
- Reads, vector search, full-text search, hot cache, feedback, dedup, and conflict candidate loads filter on
deleted_at IS NULLandtombstoned = FALSE. - Cache eviction happens after the authoritative tombstone commits.
- A stale revision is rejected rather than overwriting a newer lifecycle state.
This is not yet an accepted public deletion contract. Full-store fan-out, projection fencing, restore behavior, and independent verification remain open.
Organization lifecycle
| Operation | Route | Effect |
|---|---|---|
| Suspend | POST /v1/organizations/{org_id}/suspend | Marks the organization suspended and publishes invalidation; owner-level permission required |
| Request deletion | DELETE /v1/organizations/{org_id} | Returns 202 with a deletion job; enqueue failure is fail-closed |
| Poll job | GET /v1/organizations/{org_id}/deletion-jobs/{job_id} | Returns job status and store receipts |
Deletion is executed by a worker saga with a short soft time limit and a longer hard limit. The job claims work, checks legal holds under an advisory lock, resolves archived object URIs before the PostgreSQL purge, purges canonical rows, publishes model-policy invalidation, runs optional store stages, and finalizes only when receipts satisfy the deployment profile.
Store receipts
The saga records per-store status rather than claiming one global success:
| Receipt state | Meaning |
|---|---|
verified | The deployed store stage completed and its digest/count checks passed |
failed | The stage failed or a deployed store was unreachable |
not_applicable | The store is not deployed for this profile |
A deployed store is not finalized on not_applicable. An undeployed store may be not_applicable; it is never counted as verified. ClickHouse, Redis, and object-store stages are optional only when their runtime configuration says they are not deployed.
The PostgreSQL stage removes evidence/outbox rows, policy and credential rows, rate-limit overrides, TOTP secrets, operator ledger rows, memory rows, sessions, directives, tokens, agents, invites, and users, then marks the organization cancelled. Memory deletion emits a purged lifecycle outbox event. Active legal holds are never deleted mid-saga.
ibex_billing tables are intentionally protected from the organization-deletion cascade: rate cards, budget periods, and enforcement decisions can outlive operational tenant data. Do not describe organization deletion as erasing every billing record. See Billing and budgets.Legal holds
Legal holds are set and cleared through the management API:
- Listing requires a tenant-scoped operator token.
- Setting or clearing requires
LegalHoldManageand a session subject. - An active hold blocks physical purge but does not restore retrieval eligibility for an already tombstoned row.
- Deletion checks the hold under the same advisory lock used by hold creation, so a hold cannot land between the check and the purge stage.
- A blocked job ends as
hold_blocked, not as a successful deletion.
Retention
The accepted evidence-contract baseline is 30 days after occurred_at, with legal or incident holds overriding deletion. This is a contract default; runtime retention enforcement and deletion receipts are not proven across every store.
Retention must not be inferred from a ClickHouse TTL, object-store lifecycle rule, or projection completeness. Those are implementation details, not authority.
What is proven locally
- Revision-fenced memory tombstones and read filters.
- Post-response cache invalidation after the tombstone commit.
- A receipted PostgreSQL-first organization deletion saga.
- Legal-hold blocking and job status reporting.
- Optional ClickHouse, Redis, and object-store stages when configured.
What is not proven
- A complete per-store deletion certificate with restore verification.
- Atomic fencing between deletion and every in-flight replay or projection write.
- Full object-store and backup-copy purge.
- Hosted or HA deletion recovery.
- Legal-hold release followed by a verified purge drill.
- A public, accepted memory lifecycle including checkpoints and provenance.
Operator checklist
- Determine whether the request is suspension, forget, or organization deletion.
- Check for active legal holds before promising a purge.
- Record the deletion job ID and poll receipts; do not infer completion from the
202. - Verify each deployed store reports
verified, not merelynot_applicable. - Confirm tombstoned rows stay unreadable while caches and projections rebuild.
- Preserve IDs and outcomes; do not log memory content or secrets.
Related
- Search and lifecycle — provisional forget contract
- Evidence and relay — outbox and replay fencing
- Failure modes and recovery — restore classes
- Management API — deletion and legal-hold routes
- Tenant isolation — cross-tenant deletion boundaries

