Skip to content
Centriu
Centriu Axiom

Data Retention Cleanup Fabricated Deletion Automation: "Deleted" Without Deleting Anything

Centriu Axiom lets an organization configure a data-retention policy — a maximum age past which evidence records, session exports and job-event rows should be automatically deleted. The function actually responsible for enforcing that policy operated exclusively on the same in-memory mock array used by the product's own demo/test mode, with no check of any kind for which environment it was actually running in. Triggering "Run Cleanup" against a real, live organization filtered a harmless JavaScript array held in memory, computed a plausible-looking "N rows deleted" count from the difference in that array's length before and after, and reported success — while the organization's real `axiom_evidence`, `axiom_session_exports` and `axiom_job_events` tables in the actual database were never queried, never touched, and continued growing past their own configured retention window indefinitely. Fixed with a real, scoped SQL delete against each of the three mapped tables, filtered by the resource's own timestamp column, reading the genuinely deleted row count directly back from the database rather than computing it from a mock array. `audit_logs` is deliberately excluded from the delete path — an append-only table with no organization-level delete policy, immutable by design — and for that one resource, the reported deleted count is honestly reported as zero rather than fabricated.
Deleted count: fabricated
Real DELETE, real count
Data retention configuration screen
"Deleted," without deleting anything real.

A retention policy that never reaches the database is a promise with nothing behind it

Letting an organization set a maximum retention age for its own data, and then automatically enforcing that limit, is a real, meaningful control — both an operational one (bounding how much historical data accumulates) and, depending on what the data contains, a compliance-relevant one. The entire value of that control depends on the enforcement mechanism actually reaching the real data store holding the organization's real records. A cleanup function that reports a specific, plausible-sounding count of rows deleted looks, from every angle a person checking on it would normally use, exactly like a cleanup function that actually deleted something — the number itself carries no signal about which table, if any, it actually came from.

How the underlying problem shows up before you fix it

A function responsible for a real, customer-configured operational or compliance action (here: deleting data past a retention limit) contains no check for which environment or data mode it is running in, and always executes against the same fixed target regardless.

A reported "N rows deleted," "N rows processed" or similar count is computed from the length of an in-memory array before and after a filter operation, rather than from the actual number of rows a real database delete statement affected.

A cleanup, purge or archival action that reports success and a specific numeric result provides no independent way to verify it actually touched the real data store — the reported number alone cannot distinguish a genuine deletion from a harmless mock-array filter.

An organization's real data in the tables the feature is meant to govern keeps growing indefinitely past its own configured retention window, with the feature meant to enforce that window reporting success on every scheduled or manual run.

A data-retention or purge feature is built with a genuine, principled exception for one specific data category (here: an append-only audit table, immutable by design) — and the correct behavior for that exception is to honestly report zero deleted, not to silently include it in a fabricated total.

How a cleanup that reported success without deleting anything real was closed

Centriu Axiom's retention-policy feature lets an organization configure, per data resource, a maximum age past which rows should be automatically removed — evidence records captured during a session, exports of a completed session, and individual job-event log entries. A dedicated cleanup function reads an organization's configured policies and, for each one, is meant to delete every row of the relevant resource older than the policy's own cutoff date, then report how many rows were actually removed.

Before this fix, that function's entire implementation operated on `_store` — the same in-memory JavaScript object used throughout the product's own mock/demo mode — with a `switch` statement filtering `_store.evidence`, `_store.sessionExports`, `_store.audit` or `_store.jobEvents` directly by comparing each entry's timestamp to the cutoff, and computing `deleted` as the array's length before the filter minus its length after. There was no check anywhere in this function for which environment or data mode the product was actually running in. Triggering "Run Cleanup" for a real, live organization — using the product's genuinely production-configured Supabase-backed path everywhere else — still reached this exact function, which still only ever touched the in-memory mock array. The organization's real `axiom_evidence`, `axiom_session_exports` and `axiom_job_events` tables, holding the organization's actual production data, were never queried and never modified by this operation, at any point, regardless of how the retention policy was configured or how many runs had already reported success.

The practical consequence is a control that appears, from every angle an operator would normally check, to be working exactly as intended: the cleanup action completes without error, reports a specific, plausible-looking count of rows removed (the mock array does contain some seeded rows, so the count is a real number, just not one describing the real database), and the organization's retention policy shows as actively enforced in whatever configuration screen displays that status. Meanwhile, the organization's actual data — the exact records the policy exists to age out — accumulates without limit in the real database, contradicting both the operational intent of the feature and, for any organization treating retention as a data-minimization control, the substance (if not necessarily the letter, depending on how the policy was represented to that organization) of what the feature was supposed to guarantee.

The fix introduces a real environment check and a genuine, scoped SQL delete for the production path. A mapping (`RETENTION_TABLE_MAP`) associates each resource name with its real table and the specific timestamp column used to age it out — evidence maps to `axiom_evidence` on `captured_at`, exports map to `axiom_session_exports` on `exported_at`, job events map to `axiom_job_events` on `created_at`. For each configured policy, the fix issues a real `DELETE ... WHERE <that column> < cutoff` against the mapped table, with `.select('id')` appended specifically so PostgREST returns the actual rows that were deleted — meaning the `deleted` count reported back is now the genuine length of that returned set, not a value computed from an unrelated in-memory array. One resource, `audit_logs`, is deliberately left out of the table mapping entirely: the fix's own account is explicit that this is correct, not an oversight — the audit log is an append-only table with no organization-scoped delete policy in the database, immutable by design, specifically so that a record of what happened cannot itself be quietly erased by the same mechanism that is supposed to be governed by what happened. For that one resource, and for any resource not present in the mapping, the function now reports a truthful zero deleted, rather than continuing to report a plausible-looking but disconnected number.

What is actually built today

Centriu Axiom's retention-policy cleanup issues a real, scoped SQL delete against the organization's actual `axiom_evidence`, `axiom_session_exports` and `axiom_job_events` tables — filtered by each resource's own real timestamp column against the policy's configured cutoff.

The "rows deleted" count reported to the operator is read directly back from the database's own response to the delete statement, not computed from an in-memory array with no relationship to production data.

The append-only `audit_logs` table is deliberately excluded from the automated delete path, consistent with it having no organization-level delete policy in the database — an intentional design choice, not a remaining gap.

For any resource excluded from the real delete mapping (currently `audit_logs`), the function now honestly reports zero rows deleted for that resource, rather than a number derived from an unrelated mock filter.

The retention-policy record itself is still marked with its last-cleanup timestamp on every run, exactly as before — only the actual deletion mechanism underneath that record changed.

A cleanup report with a real-looking number and nothing real behind it (illustrative framing of the actual measured finding)

Before the fix, an organization configuring a 90-day retention limit on its captured evidence, and later triggering a manual cleanup run, would see a specific count — a genuine number, computed from a real filter operation — reported as rows deleted, with no way to tell from that screen alone that the filter had run against an in-memory demo array rather than the organization's actual evidence table. The organization's real evidence older than 90 days remained in the database, unaffected, no matter how many times cleanup reported success. After the fix, the identical action issues a real, scoped delete against the organization's actual table, and the number reported is the number of rows the database itself confirms were removed.

What changes operationally

Centriu Axiom's data-retention cleanup now performs a real, scoped SQL delete against an organization's actual evidence, session-export and job-event tables, with the reported deleted-row count read directly from the database rather than fabricated from an unrelated in-memory array — closing a gap where a configured retention policy could report successful enforcement indefinitely while the organization's real data was never actually purged.

When this is not the right fit

This automation governs Centriu Axiom's own automated retention-cleanup mechanism for evidence, session exports and job events specifically — it does not retroactively purge data that should have been deleted during the period this defect was live (an organization with that specific concern would need its own manual review of records now past their configured cutoff), and it does not extend automated deletion to the append-only audit log, which remains immutable by deliberate design.

A plausible reported count vs. a count read back from the database itself

Computing a "rows deleted" figure from the length of an array before and after a JavaScript filter is fast, simple, and — critically — indistinguishable in its OUTPUT from a real database delete's own affected-row count, which is exactly what let this defect go unnoticed by anyone reading the reported number alone. Reading the count directly from the database's own response to a real delete statement (via `.select('id')` on the delete) costs nothing extra in complexity and ties the reported number to the one thing it actually needs to represent: rows that genuinely no longer exist in the organization's real data.

Related systems

Main system: Centriu Axiom.

What it does NOT do

  • Does not retroactively delete data that should have been removed during the period this defect was live — an organization wanting to close that specific historical gap would need to trigger a fresh cleanup run now, or review records manually for anything now past its configured cutoff.
  • Does not extend automated deletion to the `audit_logs` table — that exclusion is a deliberate, permanent design choice (no organization-level delete policy exists for it in the database, by design), not a limitation this fix left unresolved.
  • Does not change what retention age an organization can configure, or add any new retention-policy option — this fix corrects only whether the configured policy actually reaches the real database when enforced.
  • Does not overlap with the other two fixes shipped in the same commit (the Health/Errors dashboard's own mock-data disconnect, and a usage-quota counter's stale-period read and non-atomic increment) — both are covered on their own companion pages.
  • Does not claim every Axiom data-management feature shared this defect — this fix is scoped specifically to the retention-policy cleanup function; other data-management paths in the product were not found to share this pattern.

Security and governance

Centriu Axiom's automated data-retention cleanup now performs a real, scoped delete against an organization's own evidence, session-export and job-event tables, with the append-only audit log deliberately and permanently excluded from automated deletion. Any personal data governed by a retention policy remains subject to Brazil's LGPD (Law No. 13,709/2018), including its data-minimization principle. Full detail on access control and audit trails lives at /governanca and /iso.

Pricing and contracting

Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Was any real organization's data actually deleted incorrectly?

No — the defect was the opposite direction: real data was never deleted when a retention policy said it should have been. The cleanup operated only on a harmless, unrelated in-memory mock array.

How was the "rows deleted" count computed before the fix?

As the difference in length of an in-memory JavaScript array before and after a timestamp filter was applied to it — a real number, computed correctly for that array, but with no connection to the organization's actual database tables.

Why is `audit_logs` excluded from automated deletion?

By deliberate design — it is an append-only table with no organization-level delete policy in the database, specifically so a record of what happened cannot be erased by the same mechanism it is meant to help oversee. This is not a remaining gap; it is the correct, intentional behavior.

How does the fix confirm a deletion actually happened, rather than just reporting one?

The delete statement itself includes `.select('id')`, which makes the database return the actual rows it removed — the reported count is the length of that real response, not a value computed separately.

Does this affect data an organization has NOT configured a retention policy for?

No — cleanup only acts on resources with a configured policy for that organization; unconfigured resources are unaffected by this fix.

What does Centriu Axiom cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Axiom actually enforces a configured retention policy

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Axiom — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Axiom — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review