Skip to content

feat(reservation): surface committed + opt-in metadata on listReservations (v0.1.25.36)#202

Merged
amavashev merged 2 commits into
mainfrom
feat/list-reservations-committed-and-include
Jun 19, 2026
Merged

feat(reservation): surface committed + opt-in metadata on listReservations (v0.1.25.36)#202
amavashev merged 2 commits into
mainfrom
feat/list-reservations-committed-and-include

Conversation

@amavashev

Copy link
Copy Markdown
Collaborator

Summary

Implements #201 (follow-up to #197): surface committed, metadata, and committed_metadata on the GET /v1/reservations list rows. These already appear on the single-row GET /v1/reservations/{id} but were dropped from list responses — the list path already hydrated a full ReservationDetail per row, then toSummary discarded them before serializing.

Implements the hybrid design agreed on the issue, per spec cycles-protocol v0.1.25.8 (runcycles/cycles-protocol#115):

  • committed (the COMMIT charge — a small scalar) → projected unconditionally on list rows, on the same footing as finalized_at_ms (NON_NULL strips it on non-COMMITTED rows).
  • metadata (reserve-time) and committed_metadata (commit-time) → arbitrary-size, possibly-PII maps, so omitted from list rows by default and projected only when the caller opts in via a new include query parameter.

Changes

  • New include query param on listReservations: comma-separated field list (?include=metadata,committed_metadata). Recognized tokens metadata / committed_metadata; unrecognized / empty tokens ignored (no 400). Parsed by a new ReservationInclude enum (Locale.ROOT, case-insensitive).
  • The three fields move from ReservationDetail up to the shared ReservationSummary base; ReservationDetail becomes a marker subclass (single-GET still serializes all three unconditionally).
  • include is projection-only — deliberately not folded into FilterHasher, so changing it mid-pagination never invalidates a cursor (contrast the window filters, which do bind the cursor).
  • listReservations gains an include-aware overload; the legacy 18-arg signature delegates with an empty set. The overload normalizes a null arg and takes a private EnumSet snapshot (defensive copy).
  • Version bump 0.1.25.35 → 0.1.25.36; AUDIT.md updated.

Tests

  • ReservationIncludeTest — parse edge cases (null/blank/whitespace/case/unknown/duplicate/trailing-comma).
  • RedisReservationQueryTest +5 — committed-always; each include combination; legacy-overload default.
  • ReservationControllerTest +3 — param parsed → empty / both / ignored-unknown sets threaded.
  • Full mvn verify green against the v0.1.25.8 spec; JaCoCo 95% gate met.

Review

Codex read-only pass: 2 Low findings, both applied — Locale.ROOT lowercase in the parser, and null/defensive-copy normalization of the include set at the overload entry.

Merge order

Spec-first. Gated on runcycles/cycles-protocol#115 reaching main first — the contract test (OpenApiContractDiffTest) fetches the spec from cycles-protocol@main, so it will only resolve the new fields/param once #115 merges.

…tions (v0.1.25.36)

Implements #201 (follow-up to #197) against
cycles-protocol v0.1.25.8 (runcycles/cycles-protocol#115). The list path
already hydrated a full ReservationDetail per row, but toSummary dropped
committed / metadata / committed_metadata before serializing — the data was
read then thrown away.

Hybrid projection:
  - committed (the COMMIT charge, a small scalar) now projects
    UNCONDITIONALLY on list rows, on the same footing as finalized_at_ms;
    NON_NULL strips it on non-COMMITTED rows.
  - metadata (reserve-time) and committed_metadata (commit-time) are
    arbitrary-size, possibly-PII maps, so they stay OFF list rows by default
    and are projected only when the caller opts in via a new comma-separated
    `include` query parameter (?include=metadata,committed_metadata).
    Unrecognized / empty tokens are ignored without error.

The three fields move from ReservationDetail up to the shared
ReservationSummary base (ReservationDetail becomes a marker subclass; the
single-GET path still serializes all three). A new ReservationInclude enum
parses the param (locale-root, case-insensitive). `include` is projection-only
and is deliberately NOT folded into FilterHasher, so changing it
mid-pagination never invalidates a cursor. listReservations gains an
include-aware overload; the legacy 18-arg signature delegates with an empty
set, which normalizes null and takes a private EnumSet snapshot.

Tests: ReservationIncludeTest (parse edge cases), RedisReservationQueryTest +5
(committed-always + include combinations + legacy-overload default),
ReservationControllerTest +3 (param parsing/threading). Full mvn verify green
against the v0.1.25.8 spec, JaCoCo 95% gate met. AUDIT.md updated.

Spec-first: gated on cycles-protocol#115 reaching main before the contract
test resolves the new fields against the published spec.
…ursor

Adds RedisReservationQueryTest#includeDoesNotBindCursor, the sorted-pagination
case the review flagged as missing: page 1 with no include captures the cursor,
page 2 reuses that cursor while flipping include=committed_metadata. Asserts the
cursor stays valid (no 400, contrast cursorMismatchRejected where sort_by binds)
and that the newly-requested field projects onto page 2. Locks in the
projection-only / not-cursor-bound guarantee against a future refactor that
might fold include into FilterHasher.
@amavashev amavashev merged commit 9665df9 into main Jun 19, 2026
8 checks passed
@amavashev amavashev deleted the feat/list-reservations-committed-and-include branch June 19, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant