fix(s3/lifecycle): wire ExpirationDate dispatch through bootstrap walker

The walker explicitly skipped ModeScanAtDate actions on the comment
"SCAN_AT_DATE runs its own date-triggered bootstrap" — but no such
bootstrap exists in the scheduler or shell layer. The result: rules
with Expiration{Date: ...} compiled correctly, populated the
snapshot's dateActions map, and were never dispatched.
ExpirationDate is silently a no-op in production.

EvaluateAction already handles ActionKindExpirationDate correctly
(rejects when now.Before(rule.ExpirationDate), otherwise emits
ActionDeleteObject). The walker just needed to fall through instead
of skipping. Pre-date walks become no-ops via EvaluateAction's date
check; post-date walks expire eligible objects.

Un-skip TestLifecycleExpirationDateInThePast — it now exercises the
fixed path end-to-end.
This commit is contained in:
Chris Lu
2026-05-09 23:30:47 -07:00
parent 60bee61189
commit e785f59d6f
2 changed files with 10 additions and 13 deletions

View File

@@ -20,15 +20,6 @@ import (
// separate compile + dispatch branch (engine.decideMode case
// ActionKindExpirationDate) that wouldn't be exercised otherwise.
func TestLifecycleExpirationDateInThePast(t *testing.T) {
// SCAN_AT_DATE is a documented mode in engine.decideMode but the
// dispatcher path that fires it isn't wired to the run-shard shell
// command yet. The bootstrap walker explicitly skips actions in
// ModeScanAtDate (walker.go:141 — "SCAN_AT_DATE runs its own
// date-triggered bootstrap"), but there is no such bootstrap in the
// scheduler or shell layer. Until that lands, this test would
// always time out. Keeping the test in source so it activates the
// moment the date-triggered scan path is wired.
t.Skip("ScanAtDate dispatch path not yet wired to run-shard; activate when the date-bootstrap lands")
c := s3Client(t)
fc, fcClose := filerClient(t)
defer fcClose()

View File

@@ -135,10 +135,16 @@ func walkEntry(ctx context.Context, snap *engine.Snapshot, bucket string, entry
if action == nil {
continue
}
// SCAN_AT_DATE runs its own date-triggered bootstrap. DISABLED can
// be flipped at runtime independent of XML Status, so skip it even
// though EvaluateAction would also reject.
if action.Mode == engine.ModeScanAtDate || action.Mode == engine.ModeDisabled {
// DISABLED can be flipped at runtime independent of XML Status,
// so skip it even though EvaluateAction would also reject.
// SCAN_AT_DATE actions are processed here too — the date check
// in EvaluateAction (now.Before(rule.ExpirationDate)) gates the
// dispatch, so pre-date walks are no-ops and post-date walks
// expire eligible objects. The earlier "scan-at-date runs its
// own bootstrap" plan was never wired; until that lands, the
// regular bootstrap walk is the only path that fires
// ExpirationDate rules.
if action.Mode == engine.ModeDisabled {
continue
}
// (kind, info) shape gate: ABORT_MPU only on MPU init records,