Skip to content

Releases: runcycles/cycles-client-rust

v0.2.7 - Commit-retry wiring fix, plus TENANT_CLOSED + LIMIT_EXCEEDED error-code support.

Choose a tag to compare

@github-actions github-actions released this 17 Jul 17:25
3dd35e5

Commit-retry wiring fix, plus TENANT_CLOSED + LIMIT_EXCEEDED error-code support.

Fixed

  • Commit retry engine wired into guard.commit() (#62). CommitRetryEngine existed since 0.2.0 with complete backoff logic but was #[allow(dead_code)] and never invoked, so the documented retry_* config knobs (builder methods, CYCLES_RETRY_* env vars) were silent no-ops and a transient commit failure permanently leaked the reservation until server-side TTL expiry, never recording the actual spend. guard.commit() now retries retryable failures (transport errors, 5xx, and error codes the protocol classifies transient per Error::is_retryable) inline with exponential backoff:

    • the heartbeat keeps extending the reservation TTL until the commit outcome is final, so retries cannot lose the reservation to expiry;
    • the returned Ok/Err is definitive — no commit activity survives the call, so callers may safely compensate on Err;
    • no detached background task exists, so nothing is silently lost on runtime shutdown;
    • retries reuse the original CommitRequest (same idempotency key), so a commit that already landed server-side cannot double-charge.

    Under a persistent outage commit() blocks for the full backoff schedule; tune the retry_* knobs or set retry_enabled(false) for fail-fast single-attempt commits.

  • Cargo.lock: quinn-proto 0.11.14 → 0.11.16 for RUSTSEC-2026-0185 (remote memory exhaustion via unbounded out-of-order stream reassembly) and anyhow 1.0.102 → 1.0.103 for RUSTSEC-2026-0190 (unsoundness in Error::downcast_mut()); both transitive dependencies, flagged by the cargo audit --deny warnings CI gate.

TENANT_CLOSED + LIMIT_EXCEEDED error-code support. TENANT_CLOSED implements the runtime spec v0.1.25.13 revision of cycles-protocol-v0.yaml (runcycles/cycles-protocol#125): servers return HTTP 409 error=TENANT_CLOSED on reservation create/commit/release/extend when the owning tenant is CLOSED (mirrors governance spec Rule 2). LIMIT_EXCEEDED closes the same class of gap for the runtime spec v0.1.25.12 revision (2026-07-04): HTTP 429 rate-limit responses (public evidence/JWKS endpoints) carry error=LIMIT_EXCEEDED plus Retry-After / X-RateLimit-Reset headers.

Added

  • ErrorCode::TenantClosed variant (serde string mapping "TENANT_CLOSED"). ErrorCode is #[non_exhaustive] with a #[serde(other)] Unknown arm, so this is source- and wire-compatible.
  • Error::is_tenant_closed() helper, mirroring Error::is_budget_exceeded(): matches Error::Api { code: Some(ErrorCode::TenantClosed), .. }.
  • Regression tests: serde roundtrip for the new variant (src/models/enums.rs), Error helper behavior (tests/error_test.rs), and a wiremock test pinning that a 409 TENANT_CLOSED body surfaces as Error::Api with the typed code — not the BudgetExceeded convenience variant — and is non-retryable (tests/client_test.rs).
  • CyclesClientBuilder::retry_initial_delay(), ::retry_multiplier(), ::retry_max_delay() — the retry knobs that previously existed only as config fields / env vars now have builder setters (#62).
  • ErrorCode::LimitExceeded variant (serde string "LIMIT_EXCEEDED"), added in spec declaration order (after MaxExtensionsExceeded; TenantClosed relocated after it so the enum mirrors the spec exactly). Classified retryable by ErrorCode::is_retryable() — 429 is transient and the spec instructs retry after the indicated delay; Error::is_retryable() picks this up via the code-based arm (the status-based arm only covers ≥500). This preserves the prior #[serde(other)] Unknown → retryable fallback behavior, now typed instead of accidental. Enum-only by design, matching the BudgetFrozen/BudgetClosed pattern: not a reservation-lifecycle denial, so no Error helper or 409 classification change. Serde roundtrip + Error retryability + wiremock 429 regression tests added.

Notes

  • Purely additive; no wire-format change. Before this release, a server returning TENANT_CLOSED deserialized to ErrorCode::Unknown via the #[serde(other)] forward-compat arm — deserialization never failed, but ErrorCode::Unknown.is_retryable() is true, so Error::is_retryable() reported a 409 TENANT_CLOSED as retryable. With this release the code is typed and correctly non-retryable.
  • The 409 classification in client.rs (BUDGET_EXCEEDED / OVERDRAFT_LIMIT_EXCEEDED / DEBT_OUTSTANDING → Error::BudgetExceeded) is intentionally unchanged: TENANT_CLOSED is a tenant-state error, not a budget-family error, and surfaces as Error::Api.

v0.2.4 - meta data update

Choose a tag to compare

@github-actions github-actions released this 08 May 12:37
e8a9f4a

Changed

  • Crates.io description and keywords broadened to cover the three pillars of
    Cycles' runtime authority: spend, risky tool actions, and audit gaps. Prior
    framing ("budget-management protocol — deterministic spend control") only
    surfaced the spend dimension and missed search-intent traffic for action
    control and audit-trail use cases.
  • Keyword set updated from ["cycles", "budget", "llm", "ai-agents", "cost-control"] to ["ai-agents", "llm", "budget", "governance", "audit-log"]. Same five-keyword cap, broader coverage.
  • README.md opening reorganized around the three pillars (spend / risky
    actions / audit gaps), each with a one-line concrete affordance, instead
    of leading with budget enforcement only.

No behavioral changes. API surface, wire protocol, and conformance audit
results are identical to 0.2.3.

v0.2.3 — 404 unit-mismatch diagnostic

Choose a tag to compare

@amavashev amavashev released this 11 Apr 13:31
f468763

Fixed

  • Misleading 404 on reserve / decide / event when the request unit does not match the stored budget's unit (#8). The server indexes budgets by the composite (scope, unit) key, so reserving in the wrong unit surfaces as "Budget not found for provided scope: …" even when the scope itself has an ACTIVE budget. create_reservation, create_reservation_with_metadata, decide, and create_event now enrich such 404s in-flight with the unit that was sent, so the mismatch is self-diagnosing. All other Error::Api fields (status, code, request_id, retry_after, details) are preserved unchanged — no behavioral change for other errors.

    Example enriched error:

    Api { status: 404, code: Some(NotFound),
          message: "Budget not found for provided scope: tenant:rider
                    (request was sent with unit=TOKENS; verify an ACTIVE
                     budget exists at this scope AND unit — the server
                     indexes budgets by (scope, unit), so a mismatched
                     unit surfaces as a 404 NOT_FOUND)",
          request_id: Some("..."), ... }
    

Docs

  • Amount, WithCyclesConfig::new, the with_cycles_usage example, and the README Quick Start all note the (scope, unit) budget indexing invariant (spec line 667) so new users don't hit this.

Tests

  • 5 new unit tests (client::tests::enrich_budget_not_found_*) and 4 new wiremock integration tests (create_reservation_404_*, decide_404_*, create_event_404_*, plus a non-matching 404 pass-through control). Coverage 95.55% (472/494 lines), above the 95% project threshold.

Server-side follow-up

The underlying misleading 404 is a server diagnostic gap, tracked as runcycles/cycles-server#79. Once the server fix (v0.1.25.6) is deployed, wrong-unit requests return structured 400 UNIT_MISMATCH with details.{scope, requested_unit, expected_units} directly — the client enrichment in this release remains forward-compatible and will simply stop firing on the wrong-unit case (still fires for truly-missing 404s). Retirement of the workaround is tracked in #10 and is non-urgent.

Install

[dependencies]
runcycles = "0.2.3"

Full diff: v0.2.2...v0.2.3 · PR: #9 · crates.io: runcycles 0.2.3 · docs.rs: runcycles 0.2.3

v0.2.2 API helpers, ergonomics

Choose a tag to compare

@amavashev amavashev released this 02 Apr 20:29
4ffc0df

What's Changed

  • fix: API ergonomics from dev feedback (0.2.2) by @amavashev in #5

Full Changelog: v0.2.1...v0.2.2

v0.2.1 doc update

Choose a tag to compare

@amavashev amavashev released this 31 Mar 12:11
50d281f

Updated README, version

Full Changelog: v0.2.0...v0.2.1

v0.2.0 - Initial public release

Choose a tag to compare

@amavashev amavashev released this 31 Mar 11:45
b600265

What's Changed

  • Add complete Rust client for Cycles budget authority protocol by @amavashev in #1
  • fix(ci): bump MSRV from 1.75 to 1.88 by @amavashev in #2
  • fix(ci): pass rust-versions explicitly to avoid MSRV mismatch by @amavashev in #3
  • chore: exclude internal files from crates.io package by @amavashev in #4

New Contributors

Full Changelog: https://github.com/runcycles/cycles-client-rust/commits/v0.2.0