Skip to content

Commit 2a0d1be

Browse files
committed
feat(list_reservations): lock in expires_*/finalized_* window passthrough (0.4.3)
Client-side companion to cycles-protocol-v0.yaml revision 2026-05-22 (runcycles/cycles-protocol#98) and runcycles/cycles-server#163. Closes the Python-client side of issue #162. The existing `list_reservations(**query_params: str)` signature already forwards arbitrary kwargs to the URL query string, so the four new ISO 8601 date-time params (expires_from / expires_to / finalized_from / finalized_to) work over the wire today without a code change. This commit adds sync + async regression tests that pin the contract — future tightening of the kwargs signature cannot drop the new params silently. Unlike `from` (a Python reserved keyword), the new param names are plain kwargs: client.list_reservations( tenant="acme", expires_from="2026-05-22T00:00:00Z", expires_to="2026-05-23T00:00:00Z", finalized_from="2026-05-15T00:00:00Z", finalized_to="2026-05-22T00:00:00Z", ) No protocol or wire-format change; servers older than v0.1.25.21 silently ignore the new params per the additive-parameter guarantee in cycles-protocol-v0.yaml. 393 tests pass at 100% coverage. Bumped to 0.4.3, updated AUDIT.md and CHANGELOG.md.
1 parent dc34406 commit 2a0d1be

4 files changed

Lines changed: 82 additions & 2 deletions

File tree

AUDIT.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Cycles Protocol v0.1.25 — Client (Python) Audit
22

3-
**Date:** 2026-05-21 (v0.4.2 — `from` / `to` ISO-8601 window-filter passthrough on `list_reservations` per `cycles-protocol-v0.yaml` revision 2026-05-21; closes the client side of runcycles/cycles-server#159. No code change — the existing `**query_params` signature already forwards arbitrary kwargs to the URL query string. Added sync + async regression tests that lock the passthrough in (using the `**{"from": ..., "to": ...}` dict-unpack form because `from` is a Python reserved keyword). 391 tests pass at 100% coverage.),
3+
**Date:** 2026-05-22 (v0.4.3 — `expires_from`/`expires_to` and `finalized_from`/`finalized_to` ISO-8601 window-filter passthrough on `list_reservations` per `cycles-protocol-v0.yaml` revision 2026-05-22; closes the Python-client side of runcycles/cycles-server#162. No code change — `**query_params` already forwards arbitrary kwargs. Added sync + async regression tests; unlike `from`/`to` the new param names are plain kwargs (no Python-reserved-word workaround needed). 393 tests pass at 100% coverage.),
4+
2026-05-21 (v0.4.2 — `from` / `to` ISO-8601 window-filter passthrough on `list_reservations` per `cycles-protocol-v0.yaml` revision 2026-05-21; closes the client side of runcycles/cycles-server#159. No code change — the existing `**query_params` signature already forwards arbitrary kwargs to the URL query string. Added sync + async regression tests that lock the passthrough in (using the `**{"from": ..., "to": ...}` dict-unpack form because `from` is a Python reserved keyword). 391 tests pass at 100% coverage.),
45
2026-03-14
56
**Spec:** `cycles-protocol-v0.yaml` (OpenAPI 3.1.0, v0.1.25)
67
**Client:** `runcycles` (Python 3.10+ / httpx / Pydantic v2)

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.3] - 2026-05-22
9+
10+
Wire-passthrough verification for `expires_from`/`expires_to` and `finalized_from`/`finalized_to` query params on `list_reservations`. Implements `cycles-protocol-v0.yaml` revision 2026-05-22 ([runcycles/cycles-protocol#98](https://github.com/runcycles/cycles-protocol/pull/98)) on the client side; runcycles/cycles-server#163 ships the server impl.
11+
12+
### Added
13+
14+
- Sync + async regression tests confirming `list_reservations` forwards the four new ISO-8601 window params to the URL query string byte-exactly. Unlike `from` (a Python keyword), the new param names are plain kwargs:
15+
```python
16+
client.list_reservations(
17+
expires_from="2026-05-22T00:00:00Z",
18+
expires_to="2026-05-23T00:00:00Z",
19+
finalized_from="2026-05-15T00:00:00Z",
20+
finalized_to="2026-05-22T00:00:00Z",
21+
)
22+
```
23+
24+
### Notes
25+
26+
- No protocol or wire-format change. Servers older than v0.1.25.21 will silently ignore the params per the additive-parameter guarantee in `cycles-protocol-v0.yaml`.
27+
- 393 tests pass at 100% coverage (gate ≥95%).
28+
829
## [0.4.2] - 2026-05-21
930

1031
Wire-passthrough verification for the new `from` / `to` query params on `list_reservations`. Implements `cycles-protocol-v0.yaml` revision 2026-05-21 ([runcycles/cycles-protocol#97](https://github.com/runcycles/cycles-protocol/pull/97)) on the client side; runcycles/cycles-server#160 ships the server impl.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "runcycles"
7-
version = "0.4.2"
7+
version = "0.4.3"
88
description = "Python AI agent budget control — enforce LLM cost limits, tool permissions, and multi-tenant policies before agent actions execute."
99
readme = "README.md"
1010
license = "Apache-2.0"

tests/test_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,35 @@ def test_list_reservations_with_from_to_window(self, config: CyclesConfig, httpx
178178

179179
assert response.is_success
180180

181+
def test_list_reservations_with_expires_and_finalized_windows(self, config: CyclesConfig, httpx_mock) -> None: # type: ignore[no-untyped-def]
182+
"""`expires_*`/`finalized_*` ISO-8601 window-filter passthrough (cycles-protocol v0.1.25 revision 2026-05-22).
183+
184+
Unlike `from`, these param names aren't Python reserved keywords, so callers
185+
can use direct kwargs. Test pins all four params on the wire."""
186+
httpx_mock.add_response(
187+
method="GET",
188+
url=(
189+
"http://localhost:7878/v1/reservations?tenant=acme"
190+
"&expires_from=2026-05-22T00%3A00%3A00Z"
191+
"&expires_to=2026-05-23T00%3A00%3A00Z"
192+
"&finalized_from=2026-05-15T00%3A00%3A00Z"
193+
"&finalized_to=2026-05-22T00%3A00%3A00Z"
194+
),
195+
json={"reservations": [], "has_more": False},
196+
status_code=200,
197+
)
198+
199+
with CyclesClient(config) as client:
200+
response = client.list_reservations(
201+
tenant="acme",
202+
expires_from="2026-05-22T00:00:00Z",
203+
expires_to="2026-05-23T00:00:00Z",
204+
finalized_from="2026-05-15T00:00:00Z",
205+
finalized_to="2026-05-22T00:00:00Z",
206+
)
207+
208+
assert response.is_success
209+
181210
def test_get_reservation(self, config: CyclesConfig, httpx_mock) -> None: # type: ignore[no-untyped-def]
182211
httpx_mock.add_response(
183212
method="GET",
@@ -443,6 +472,35 @@ async def test_list_reservations_with_from_to_window(self, config: CyclesConfig,
443472

444473
assert response.is_success
445474

475+
async def test_list_reservations_with_expires_and_finalized_windows(self, config: CyclesConfig, httpx_mock) -> None: # type: ignore[no-untyped-def]
476+
"""Async parity for the `expires_*`/`finalized_*` window-filter passthrough.
477+
478+
See sync sibling for the reserved-keyword note on `from`; the new
479+
v0.1.25.22 params use plain kwargs."""
480+
httpx_mock.add_response(
481+
method="GET",
482+
url=(
483+
"http://localhost:7878/v1/reservations?tenant=acme"
484+
"&expires_from=2026-05-22T00%3A00%3A00Z"
485+
"&expires_to=2026-05-23T00%3A00%3A00Z"
486+
"&finalized_from=2026-05-15T00%3A00%3A00Z"
487+
"&finalized_to=2026-05-22T00%3A00%3A00Z"
488+
),
489+
json={"reservations": [], "has_more": False},
490+
status_code=200,
491+
)
492+
493+
async with AsyncCyclesClient(config) as client:
494+
response = await client.list_reservations(
495+
tenant="acme",
496+
expires_from="2026-05-22T00:00:00Z",
497+
expires_to="2026-05-23T00:00:00Z",
498+
finalized_from="2026-05-15T00:00:00Z",
499+
finalized_to="2026-05-22T00:00:00Z",
500+
)
501+
502+
assert response.is_success
503+
446504
async def test_get_reservation(self, config: CyclesConfig, httpx_mock) -> None: # type: ignore[no-untyped-def]
447505
httpx_mock.add_response(
448506
method="GET",

0 commit comments

Comments
 (0)