Pre-existing, noticed while implementing #10347. Not touched by that diff, which keeps the Archiver's existing posture rather than inventing half a feature.
What is missing
LifecycleService.reapObject resolves every declared window through effectiveWindowMs(...) before reaping, so ADR-0057 P4 governance applies:
archiveObject does none of this. It reads archive.after — and, since #10347, ttl.expireAfter — straight from the declaration via parseLifecycleDuration. So for any object declaring archive:
- an operator's window override is silently ignored;
- a retention floor registered against that object is never evaluated, so a violation is neither reported nor logged;
- per-tenant windows do not apply.
The consequence is not a data-loss risk in the override direction (the declaration is the safer, longer window in the case that matters), but it is a silent divergence between what the lifecycle settings namespace claims to control and what it actually controls, and it is invisible: report.floorViolations stays empty for archive-declaring objects however the floors are registered.
Why #10347 did not close it
Before that card, ttl was inert on archive-declaring objects, so its expireAfter override was inert too. After it, the declared ttl executes in the Archiver while the override still does not reach it — a ttl-only object and a ttl + archive object now answer an operator override differently. That inconsistency is what surfaced this, but the gap itself predates it and covers archive.after too.
Notes for whoever takes it
- Wiring only the global override and not the per-tenant ones would be a half-feature; the per-tenant leg needs the Archiver's batch loop to run per tenant window the way
reap() does, which is the real work here. - The floor check is the cheaper half and arguably the more valuable one: it is the leg whose absence is silent rather than merely conservative.
- Objects affected today:
sys_audit_log, sys_metadata_audit — the only two declaring lifecycle.archive.
Pre-existing, noticed while implementing #10347. Not touched by that diff, which keeps the Archiver's existing posture rather than inventing half a feature.
What is missing
LifecycleService.reapObjectresolves every declared window througheffectiveWindowMs(...)before reaping, so ADR-0057 P4 governance applies:maxAge/expireAfter) beats the declaration;LifecycleRetentionFloor(finding(objectql): lifecycle 的 settings 覆盖窗口没有下限校验 —— 运维把 maxAge 调到某个消费方的假设之下时,后果是静默的(以 sys_job_queue 去重窗为例) #5195) is rejected, logged aterror, and recorded inreport.floorViolations;reap()additionally resolves per-tenant windows and issues one candidate read per overriding tenant.archiveObjectdoes none of this. It readsarchive.after— and, since #10347,ttl.expireAfter— straight from the declaration viaparseLifecycleDuration. So for any object declaringarchive:The consequence is not a data-loss risk in the override direction (the declaration is the safer, longer window in the case that matters), but it is a silent divergence between what the
lifecyclesettings namespace claims to control and what it actually controls, and it is invisible:report.floorViolationsstays empty for archive-declaring objects however the floors are registered.Why #10347 did not close it
Before that card,
ttlwas inert on archive-declaring objects, so itsexpireAfteroverride was inert too. After it, the declaredttlexecutes in the Archiver while the override still does not reach it — attl-only object and attl+archiveobject now answer an operator override differently. That inconsistency is what surfaced this, but the gap itself predates it and coversarchive.aftertoo.Notes for whoever takes it
reap()does, which is the real work here.sys_audit_log,sys_metadata_audit— the only two declaringlifecycle.archive.