DataPoint snapshots accumulated in /srv/krill/data/records/<node-id>/
indefinitely. A Pi running for a year carried a year of hour-bucket files
per DataPoint, even when the UI was only ever showing “Last hour” — and
range queries paid for that disk footprint on every read.
The SDK already declared DataPointMetaData.maxAge: Long = 0 with the
comment “Maximum age (epoch millis) of stored snapshots — 0 means keep
forever”, but no read path or background job ever consumed it.
Wire maxAge end-to-end in this repo, without touching the SDK:
ServerDataStore.range / last / reverseSequence:
computes cutoff = currentTimeMillis() - meta.maxAge once per call and
filters snapshots with timestamp >= cutoff. maxAge == 0 short-circuits
to Long.MIN_VALUE, preserving today’s “keep forever” behaviour.ServerTask (DataPointTtlCleanupTask) registered
via ServerBoss.addTask in Lifecycle.kt. Whole-file deletes only — a
bucket file is removed when its end timestamp (bucketStart + 3_600_000)
is already past the cutoff. Files that straddle the cutoff are left intact
and the read filter handles the boundary.ComputeTimeRange chip set
(Forever / Minute / Hour / Day / Week / Month / Year) via a small
RetentionPicker that writes the millisecond equivalent into meta.maxAge.meta.maxAge > 0.maxAge field had the right
semantics; renaming it to dataTTL would have meant a cross-repo edit,
serializer migration, and coordinated deploy for a purely cosmetic gain.
If the user-facing label needs to say “TTL” or “retention”, that goes in
the UI string — never in the SDK type.post(). Whole-file deletes are race-free because a finished hour
bucket is immutable forever — and once now > bucketStart + 1h + maxAge,
no live writer can possibly touch it.MONTH = 30d, YEAR = 365d — by design. Same approximation
ComputeTimeRange.getTime() already uses everywhere else, so the chip-set
in the editor and the time-axis on graphs stay consistent. Calendar-aware
retention is intentionally out of scope.ServerDataStoreTest + DataPointTtlCleanupTaskTest
use @TempDir and a hardcoded clock/dataRoot constructor parameter
to exercise the on-disk implementation without /srv/krill. The full
editor round-trip test was scoped down to RetentionWindowTest — a
full editor stand-up needs DI/Koin scaffolding the project doesn’t
currently have for DataPoint.