diff --git a/test/s3/lifecycle/s3_lifecycle_expiration_date_test.go b/test/s3/lifecycle/s3_lifecycle_expiration_date_test.go index 199a0bef4..a60efeb82 100644 --- a/test/s3/lifecycle/s3_lifecycle_expiration_date_test.go +++ b/test/s3/lifecycle/s3_lifecycle_expiration_date_test.go @@ -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() diff --git a/weed/s3api/s3lifecycle/bootstrap/walker.go b/weed/s3api/s3lifecycle/bootstrap/walker.go index e9ee1a547..adedb4fcd 100644 --- a/weed/s3api/s3lifecycle/bootstrap/walker.go +++ b/weed/s3api/s3lifecycle/bootstrap/walker.go @@ -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,