ADR-0046: Housekeeping ships enabled by default (opt-out)

ODD Platform ships data housekeeping on by default — it deletes aged rows out of the box, so bounded DB growth is the default posture and an operator must opt out to keep data indefinitely.

Status

Accepted. Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.

Context

The housekeeping subsystem (ADR-0045) deletes aged data: resolved alerts, search facets, and soft-deleted data entities past a TTL. Every other heavyweight subsystem in the platform — GenAI (ADR-0004), Data Collaboration (ADR-0019), Notifications (ADR-0040) — ships disabled by default, because each needs external configuration to do anything useful. Housekeeping is different in kind: it needs no external system, and the thing it prevents (unbounded table growth on an append-heavy platform) is a problem every deployment has. So the platform had to decide which default serves operators better — off (preserve everything, grow unbounded) or on (bound growth, delete aged data).

Decision

Housekeeping ships enabled by default — it is opt-out, not opt-in. The shipped application.yml sets housekeeping.enabled: true verbatim, and HousekeepingJobManager is gated by @ConditionalOnProperty(value = "housekeeping.enabled", havingValue = "true"). A default deployment therefore runs the cleanup cycle and deletes aged data without any configuration; an operator who wants to keep everything must explicitly set housekeeping.enabled: false.

This is a deliberate divergence from the platform's ship-disabled-by-default family. Those features ship off because they are inert without external wiring; housekeeping ships on because bounded database growth is the platform's default operational posture — leaving it off would let the activity, alert, and search-facet tables grow without limit on every untouched deployment. The default encodes "the platform keeps itself bounded unless you tell it not to."

Consequences

  • 📌 A default deployment deletes data. Out of the box, resolved alerts, search facets, and soft-deleted data entities older than their TTL (each 30 days by default) are removed on the cleanup cycle. This is intended, but an operator who needs indefinite retention (for audit or compliance) must set housekeeping.enabled: false — and should know the default is delete, not keep.

  • The TTLs are configurable (housekeeping.ttl.*), so retention windows can be lengthened rather than disabling the subsystem entirely.

  • Because the condition uses havingValue = "true" with no matchIfMissing, the shipped-on default applies through the shipped application.yml: a deployment that replaces that file without re-supplying housekeeping.enabled gets housekeeping off (the absent key fails the condition). The integration-test profile relies on exactly this, setting housekeeping.enabled: false to keep test data. Operators templating their own config should carry the key forward deliberately.

  • A contributor proposing to flip housekeeping to disabled-by-default "for consistency" with the other subsystems is working against this decision — the inconsistency is intentional and reflects that housekeeping is an operational-hygiene concern, not a feature integration.

Evidence

  • odd-platform-api/src/main/resources/application.yml:165-166housekeeping: / enabled: true (the verbatim shipped default); :167-170 — the ttl block (resolved_alerts_days, search_facets_days, data_entity_delete_days, each 30).

  • odd-platform-api/.../housekeeping/HousekeepingJobManager.java:17-18@Component + @ConditionalOnProperty(value = "housekeeping.enabled", havingValue = "true") (no matchIfMissing): the strict gate that, combined with the shipped true, produces opt-out semantics.

  • odd-platform-api/src/test/resources/application-integration-test.yml:8-9housekeeping: / enabled: false: the test profile opts out, exercising the same toggle and confirming it is the real on/off switch.

See also

Last updated