Skip to content

v0.1.25.21 — expires_* / finalized_* time-window filters on listReservations

Choose a tag to compare

@amavashev amavashev released this 22 May 12:25
· 254 commits to main since this release
f99c5bc

v0.1.25.21 — expires_* / finalized_* time-window filters on listReservations

Implements cycles-protocol-v0.yaml revision 2026-05-22 and closes #162 via #163. Follow-up to v0.1.25.20 — addresses the operational use case (cleanup sweepers locating reservations expiring or already finalized within a window) that revision intentionally left out by binding from/to to created_at_ms.

Feature: four new query parameters on GET /v1/reservations

Param Bound to Notes
expires_from / expires_to expires_at_ms Required field → applies to every row regardless of status.
finalized_from / finalized_to finalized_at_ms Populated only on COMMITTED / RELEASED. ACTIVE and EXPIRED rows are normatively excluded when this is set.

All ISO 8601 format: date-time, all optional, all inclusive bounds. Each pair binds to its target field regardless of sort_by. The three windows (from/to + expires_* + finalized_*) compose with AND semantics.

Schema addition

ReservationSummary gains an optional finalized_at_ms field so clients filtering with finalized_* can see the timestamp they're filtering on without a follow-up getReservation call. @JsonInclude(NON_NULL) — pre-revision response shapes go out byte-for-byte for rows where the field is absent (i.e., ACTIVE/EXPIRED rows that dominate unfiltered list calls).

Validation

  • Malformed values → 400 INVALID_REQUEST with distinct Invalid {param_name} message identifying which parameter failed.
  • expires_from > expires_to and finalized_from > finalized_to → 400 before any repository call.
  • Blank-string values for any of the six bounds treated as unset (normative per the 2026-05-22 spec carve-out, which also retroactively normatived this behavior for the v0.1.25.20 from/to pair).

Cursor back-compat (the centerpiece)

FilterHasher.hash(...) 10 → 14 args with independent gated emission per pair. Each window pair emits its canonical block only when at least one of its bounds is non-null. This preserves byte-exact back-compat for both prior cursor generations:

Cursor era Canonical form Golden hash for (acme, all-other-fields-empty)
v0.1.25.12 – v0.1.25.18 8 string fields only 2f397ea0e8fb53b7 (locked in v0.1.25.20)
v0.1.25.20 with from=100&to=200 + |fr=100|to=200 ad7204d521cfd133 (newly locked here)
v0.1.25.21 with only expires_* set + |ef=…|et=… (no |fr=|to=) byte-exact under gated emission

A v0.1.25.18 client mid-pagination after a v0.1.25.21 deployment still resolves its cursor, just as v0.1.25.20 promised for the same scenario.

Internal Java signature changes

  • RedisReservationRepository.listReservations(...) 14 → 18 args; listReservationsSorted(...) mirrors.
  • FilterHasher.hash(...) 10 → 14 args.
  • ReservationSummary model gains finalizedAtMs field; toSummary(...) projection updated.
  • Two new predicate helpers: expiresAtInWindow and finalizedAtInWindow. Both applied in legacy SCAN-cursor and sorted paths after the existing scope/status/tenant predicates and createdAtInWindow.
  • finalizedAtInWindow resolves its timestamp via the new resolveFinalizedAtStr helper, shared with buildReservationSummary's projection. Both paths agree on released-wins when both committed_at and released_at are somehow set on a malformed row — fixed a subtle disagreement caught during review where the predicate would have filtered using one timestamp while the projection emitted another.

Wire compatibility

Purely additive at the wire level. Clients that don't send the new params get exactly the v0.1.25.20 response byte-for-byte. The single new response field (finalized_at_ms on ReservationSummary) is optional and NON_NULL-serialized, so v0.1.25.20-shape responses go out byte-for-byte for rows where the field is absent.

Verification

  • 558 protocol-service tests pass (385 data + 173 api), +20 vs v0.1.25.20's 538.
  • New coverage: FilterHasherTest +3 (expires/finalized distinctness, finalized vs from/to, v0.1.25.20 golden lockdown), RedisReservationQueryTest +7 under ExpiresAndFinalizedWindowFilter (legacy expires_from below, legacy expires_to above, finalized excludes ACTIVE, finalized resolves from released_at, all-three AND composition, cursor mismatch on expires window change, malformed-row resolver agreement), ReservationControllerTest +10 under ListReservations (4 malformed-*, 2 reversed-window, expires/finalized propagation with verify(...) locks, all-three combined, blank-as-unset for new windows).
  • JaCoCo 95% bundle gate met.
  • Container scan green against the locally-built v0.1.25.21 image.

Client SDK releases (companion artifacts, landing next)

Will track the same shape as the v0.1.25.20 release chain:

  • runcycles (Python) — passthrough test (permissive **query_params)
  • runcycles (TypeScript) — passthrough test (permissive Record<string, string>)
  • cycles-client-java-spring (Spring Boot starter) — passthrough test (permissive Map<String, String>)
  • runcycles (Rust) — strongly-typed; needs 4 new Option<String> fields on ListReservationsParams plus a new Option<String> field on ReservationSummary for finalized_at_ms