From a510f29d1d0333b96801ec24f662177bca6ffe1d Mon Sep 17 00:00:00 2001 From: Albert Mavashev Date: Thu, 21 May 2026 10:06:20 -0400 Subject: [PATCH 1/2] feat(listReservations): add from/to created-time range filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes runcycles/cycles-server#159. Adds two optional query parameters to GET /v1/reservations: * from: ISO 8601 date-time. Inclusive lower bound on created_at_ms. * to: ISO 8601 date-time. Inclusive upper bound on created_at_ms. Either may be supplied alone (open interval) or together (closed window). The filter always binds to created_at_ms regardless of sort_by, so sort-by-expires_at_ms + time-window-by-created_at_ms is well-defined. Naming matches the family-wide convention already in use on listAuditLogs, listEvents, listWebhookDeliveries, listTenantEvents, and listTenantWebhookDeliveries (all `from`/`to` + format: date-time). Bespoke names (`created_after`/`created_before`) or Unix-epoch wire types would split the convention for clients and codegen. Validation: * `from > to` MUST return 400 INVALID_REQUEST. * Either alone is valid; absent means "no bound on that side." * Malformed date-time values MUST return 400. Additive-parameter guarantee: servers that don't recognize the parameters MUST ignore without error and return the unfiltered set. Backward compatible — purely additive, no request or response schema changes, both ApiKeyAuth and AdminKeyAuth callers see them. Spec changes: - cycles-protocol-v0.yaml: two new query params on listReservations plus a new TIME-RANGE FILTERS (NORMATIVE, ADDITIVE) prose block in the operation description. - changelogs/cycles-protocol-v0.md: new v0.1.25 — 2026-05-21 entry. - CONFORMANCE.md: listReservations SHOULD bullet updated to mention the new time-window query path. info.version stays at 0.1.25 per the existing revision convention (multiple dated v0.1.25 entries already coexist in the changelog). Verification: - npx spectral lint cycles-protocol-v0.yaml --fail-severity=error → 0 errors. 20 pre-existing warnings on top-level schema descriptions, unchanged from main. - python scripts/validate_changelogs.py → all 5 specs OK. Opening as DRAFT pending issue author confirmation on the `from`/`to` naming and five open questions raised in runcycles/cycles-server#159 (comment 4508958838): inclusivity, from>to=400, open intervals, sort_by binding, rationale nit on the expires_at use case. --- CONFORMANCE.md | 2 +- changelogs/cycles-protocol-v0.md | 52 ++++++++++++++++++++++++++++++++ cycles-protocol-v0.yaml | 51 +++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 1f14fb3..24ad123 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -96,7 +96,7 @@ A conformant implementation SHOULD: - Emit events for budget-state changes (reservation.*, budget.*, quota.*) matching the `EventType` enum. Implementations MAY sample or filter which events they emit, but emitted events MUST follow the schema. - Propagate `X-Cycles-Trace-Id` and W3C `traceparent` headers per `cycles-protocol-v0.yaml` §CORRELATION AND TRACING. Trace correlation is central to multi-service debugging. - Implement `POST /v1/decide` — marked OPTIONAL in the v0 spec, but agent frameworks need soft-landing signals for graceful degradation. -- Implement `GET /v1/reservations` (**listReservations**) — marked OPTIONAL in v0; useful for reservation recovery (re-discover a lost `reservation_id` via `idempotency_key`) and for identifying stuck `ACTIVE` reservations. +- Implement `GET /v1/reservations` (**listReservations**) — marked OPTIONAL in v0; useful for reservation recovery (re-discover a lost `reservation_id` via `idempotency_key`), for identifying stuck `ACTIVE` reservations, and for time-window queries via the additive `from`/`to` parameters (v0.1.25, revision 2026-05-21). - Implement `GET /v1/reservations/{reservation_id}` (**getReservation**) — marked "optional, for debugging" in v0; valuable for support / monitoring of long-running reservations. - Implement `POST /v1/events` (**createEvent**) — marked OPTIONAL in v0; the post-only accounting path for cases where pre-estimation is unavailable (bills-later providers, receipt ingestion). diff --git a/changelogs/cycles-protocol-v0.md b/changelogs/cycles-protocol-v0.md index cc4c5df..8f5304b 100644 --- a/changelogs/cycles-protocol-v0.md +++ b/changelogs/cycles-protocol-v0.md @@ -6,6 +6,58 @@ New entries are added directly to this file. See `scripts/validate_changelogs.py --- +## v0.1.25 — 2026-05-21 + +_(revision 2026-05-21 — `from`/`to` created-time range filters on listReservations)_ + +- Adds two optional query parameters to `listReservations` + (`GET /v1/reservations`): + * `from`: ISO 8601 date-time. Inclusive lower bound on + reservation `created_at_ms`. May be supplied alone (no + upper bound) or paired with `to`. + * `to`: ISO 8601 date-time. Inclusive upper bound on + reservation `created_at_ms`. May be supplied alone (no + lower bound) or paired with `from`. +- Closes a real client-side cost: today, fetching "last 24h + of reservations" requires sort-by-`created_at_ms` + a + page-size escalation loop until the oldest item falls + outside the window. For high-volume agent clusters this + scans far more rows than the caller actually needs. With + `from`/`to`, the server boundaries the scan to the + requested window and pagination over that window remains + cursor-stable. +- Both parameters bind to `created_at_ms` regardless of + `sort_by`. A client sorting by `expires_at_ms` while + filtering by `from`/`to` gets the expected behavior: + results in the requested window, ordered by expiry. This + keeps the contract predictable across sort keys (no + per-key filter semantics to memorize). +- Validation: + * Servers MUST reject `from > to` with HTTP 400 + INVALID_REQUEST. + * Either parameter alone is valid; absent parameter + means "no bound on that side." + * Malformed date-time values MUST be rejected with HTTP + 400 INVALID_REQUEST (consistent with other ISO 8601 + query parameters in the spec family). +- Additive-parameter guarantee: servers that don't recognize + `from`/`to` MUST ignore without error and return the + unfiltered set. Older clients that never send them get the + pre-revision wire behavior byte-for-byte. +- Naming and wire-type rationale: matches the family-wide + `from`/`to` + `format: date-time` convention already in + use on `listAuditLogs`, `listEvents`, + `listWebhookDeliveries`, `listTenantEvents`, and + `listTenantWebhookDeliveries` in the governance-admin + spec. Bespoke `created_after`/`created_before` names or + Unix-epoch wire types would split the convention for + clients and codegen. +- Backward compatible: purely additive. No request or + response schema changes. Both ApiKeyAuth and AdminKeyAuth + callers see the new parameters. + +--- + ## v0.1.25 — 2026-04-18 _(revision 2026-04-18 — trace_id cross-surface correlation, W3C Trace Context-compatible)_ diff --git a/cycles-protocol-v0.yaml b/cycles-protocol-v0.yaml index 34b1409..d8cbea7 100644 --- a/cycles-protocol-v0.yaml +++ b/cycles-protocol-v0.yaml @@ -1470,6 +1470,20 @@ paths: - Query parameters tenant/workspace/app/workflow/agent/toolset filter on the canonical Subject fields. - Filtering on Subject.dimensions is out of scope for v0 unless explicitly implemented by the server. + TIME-RANGE FILTERS (NORMATIVE, ADDITIVE): + - Query parameters `from` and `to` (ISO 8601 date-time strings) + bound the `created_at_ms` of returned reservations, inclusive + on both ends. + - The filter is fixed to `created_at_ms` regardless of `sort_by`; + sorting by `expires_at_ms` while filtering by `from`/`to` is + valid and well-defined. + - Either may be supplied alone (open interval) or together + (closed window). `from > to` MUST return 400 INVALID_REQUEST. + - Both are additive parameters: servers that don't recognize + them MUST ignore without error and return the unfiltered set. + This matches the family-wide `from`/`to` convention on + `listAuditLogs`, `listEvents`, and `listWebhookDeliveries`. + TENANCY (NORMATIVE): - Under ApiKeyAuth: the server MUST scope results to the effective tenant derived from auth. If the tenant query parameter is @@ -1524,6 +1538,43 @@ paths: in: query required: false schema: {type: string} + - name: from + in: query + required: false + description: >- + Inclusive lower bound on reservation creation time. ISO 8601 + date-time. When set, the server MUST return only reservations + whose `created_at_ms` is greater than or equal to this + timestamp. The filter ALWAYS binds to `created_at_ms`, + independent of `sort_by`. May be supplied alone (no upper + bound) or paired with `to`. Servers MUST reject `from > to` + with HTTP 400 INVALID_REQUEST. + + Additive parameter — servers that don't recognize it MUST + ignore without error (additive-parameter guarantee). Matches + the `from` / `to` convention on `listAuditLogs`, + `listEvents`, and `listWebhookDeliveries` in the governance + spec family. + schema: + type: string + format: date-time + - name: to + in: query + required: false + description: >- + Inclusive upper bound on reservation creation time. ISO 8601 + date-time. When set, the server MUST return only reservations + whose `created_at_ms` is less than or equal to this + timestamp. The filter ALWAYS binds to `created_at_ms`, + independent of `sort_by`. May be supplied alone (no lower + bound) or paired with `from`. Servers MUST reject `from > to` + with HTTP 400 INVALID_REQUEST. + + Additive parameter — servers that don't recognize it MUST + ignore without error. + schema: + type: string + format: date-time - name: sort_by in: query required: false From a5e3f447160a01893dbc50ccd9a81e0d3aa6f8d7 Mon Sep 17 00:00:00 2001 From: Albert Mavashev Date: Thu, 21 May 2026 10:08:48 -0400 Subject: [PATCH 2/2] chore(merged): regenerate cycles-openapi-protocol-merged.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to a510f29 — the merge-check CI step (`make merge-check` / `python scripts/merge_specs.py` + git-diff guard on `merged/`) fails when a source spec is modified without rerunning the merge script. This commit picks up the `from`/`to` query params on listReservations and the TIME-RANGE FILTERS prose block in the merged protocol artifact. No semantic changes beyond what's already in cycles-protocol-v0.yaml — pure mechanical regeneration via scripts/merge_specs.py. Verified: - `python scripts/merge_specs.py` writes both merged artifacts; only cycles-openapi-protocol-merged.yaml changed (admin merge already excludes the runtime base). - `git diff merged/` shows the same TIME-RANGE FILTERS block and two new query params, just propagated through the merge. - `npx spectral lint merged/cycles-openapi-protocol-merged.yaml --fail-severity=error` → 0 errors (pre-existing warnings only). --- merged/cycles-openapi-protocol-merged.yaml | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/merged/cycles-openapi-protocol-merged.yaml b/merged/cycles-openapi-protocol-merged.yaml index 8f97f90..0f2101c 100644 --- a/merged/cycles-openapi-protocol-merged.yaml +++ b/merged/cycles-openapi-protocol-merged.yaml @@ -2186,6 +2186,20 @@ paths: - Query parameters tenant/workspace/app/workflow/agent/toolset filter on the canonical Subject fields. - Filtering on Subject.dimensions is out of scope for v0 unless explicitly implemented by the server. + TIME-RANGE FILTERS (NORMATIVE, ADDITIVE): + - Query parameters `from` and `to` (ISO 8601 date-time strings) + bound the `created_at_ms` of returned reservations, inclusive + on both ends. + - The filter is fixed to `created_at_ms` regardless of `sort_by`; + sorting by `expires_at_ms` while filtering by `from`/`to` is + valid and well-defined. + - Either may be supplied alone (open interval) or together + (closed window). `from > to` MUST return 400 INVALID_REQUEST. + - Both are additive parameters: servers that don't recognize + them MUST ignore without error and return the unfiltered set. + This matches the family-wide `from`/`to` convention on + `listAuditLogs`, `listEvents`, and `listWebhookDeliveries`. + TENANCY (NORMATIVE): - Under ApiKeyAuth: the server MUST scope results to the effective tenant derived from auth. If the tenant query parameter is @@ -2246,6 +2260,24 @@ paths: required: false schema: type: string + - name: from + in: query + required: false + description: |- + Inclusive lower bound on reservation creation time. ISO 8601 date-time. When set, the server MUST return only reservations whose `created_at_ms` is greater than or equal to this timestamp. The filter ALWAYS binds to `created_at_ms`, independent of `sort_by`. May be supplied alone (no upper bound) or paired with `to`. Servers MUST reject `from > to` with HTTP 400 INVALID_REQUEST. + Additive parameter — servers that don't recognize it MUST ignore without error (additive-parameter guarantee). Matches the `from` / `to` convention on `listAuditLogs`, `listEvents`, and `listWebhookDeliveries` in the governance spec family. + schema: + type: string + format: date-time + - name: to + in: query + required: false + description: |- + Inclusive upper bound on reservation creation time. ISO 8601 date-time. When set, the server MUST return only reservations whose `created_at_ms` is less than or equal to this timestamp. The filter ALWAYS binds to `created_at_ms`, independent of `sort_by`. May be supplied alone (no lower bound) or paired with `from`. Servers MUST reject `from > to` with HTTP 400 INVALID_REQUEST. + Additive parameter — servers that don't recognize it MUST ignore without error. + schema: + type: string + format: date-time - name: sort_by in: query required: false