Changelog
Release history for the Cycles Protocol and reference implementations.
v0.1.25.x — patch releases (April-July 2026)
Since the initial v0.1.25 Events & Webhooks release, each component has shipped a stream of patch releases. Wire format is stable within 0.1.x; every change below is additive unless explicitly marked breaking.
Current versions
| Component | Version | Release date |
|---|---|---|
| Protocol spec (runtime) | v0.1.25 (document revision v0.1.25.16) | 2026-07-28 |
| Governance spec (admin) | v0.1.25.42 | 2026-07-21 |
cycles-server (runtime) | v0.1.25.59 | 2026-07-28 |
cycles-server-admin | v0.1.25.55 | 2026-07-21 |
cycles-server-events | v0.1.25.25 | 2026-07-18 |
cycles-dashboard | v0.1.25.85 | 2026-07-21 |
Client SDKs and plugins. The language clients and plugin integrations are versioned independently from the protocol / server / dashboard tracks — they track the client API surface rather than the wire spec. Current releases:
| SDK / plugin | Version | Release date | Runtime | Release notes |
|---|---|---|---|---|
cycles-client-typescript | 0.4.2 | 2026-07-28 | Node 20+ (native fetch) | CHANGELOG |
cycles-client-rust | 0.3.2 | 2026-07-28 | Rust 1.88+ MSRV | CHANGELOG |
cycles-client-python | 0.5.2 | 2026-07-29 | Python 3.10+ (httpx, Pydantic v2) | CHANGELOG |
cycles-spring-boot-starter | 0.3.2 | 2026-07-29 | Spring Boot 3.5+, Java 21 | CHANGELOG |
cycles-mcp-server | 0.6.1 | 2026-07-27 | Node 20+, MCP protocol, Claude Desktop .mcpb | CHANGELOG |
cycles-openai-agents | 0.3.0 | 2026-07-27 | OpenAI Agents SDK (Python) | CHANGELOG |
cycles-openclaw-budget-guard | 0.9.0 | 2026-07-27 | OpenClaw plugin (Node) | CHANGELOG |
cycles-claude-plugin | 0.2.0 | 2026-07-22 | Cycles Budget Guard for Claude Code (Node 22+; companion MCP server 0.6.0) | CHANGELOG |
langchain-runcycles | 0.3.1 | 2026-07-27 | LangChain create_agent middleware (Python) — CyclesModelGate (with cost_fn actual-cost extraction, v0.2.0+) + CyclesToolGate (with tool-side cost_fn, v0.3.0+) + CyclesFanOutGate + streaming-path verified (v0.2.1+); settlement HTTP failures honored per settlement_error_policy (v0.2.3+) | CHANGELOG |
cycles-spring-ai-starter | 0.4.0 | 2026-07-27 | Spring Boot 3.5+, Spring AI 1.0+/1.1+, Java 21 — three extension points added in 0.3.0: pluggable SubjectResolver for per-call attribution (multi-tenant agents can route the Cycles subject from an authenticated principal, header, or thread-local); pluggable PromptTokenEstimator with a real BPE impl (JtokkitPromptTokenEstimator via com.knuddels:jtokkit, opt in with cycles.spring-ai.token-estimator-encoding=cl100k_base / o200k_base); cycles.reservation_id on chat-client traces as a high-cardinality KeyValue for trace ↔ reservation correlation. End-to-end integration test added. 98.3% instruction / 95.7% branch test coverage at 0.3.0; 0.3.1 is a documentation-only patch (fixed a 10x cost-per-token example) verified at 100% / 100%. No breaking changes vs 0.2.0. Companion to cycles-spring-boot-starter. | CHANGELOG |
cycles-ap2-python | 0.3.1 | 2026-07-27 | AP2 payment-mandate guard (Python 3.10+) | CHANGELOG |
The four current language SDKs are validated against runtime spec revision 0.1.25.16 and the separately versioned SDK recovery profile 0.3. Older clients remain wire-compatible with additive 0.1.x server changes, but only the current releases above provide the shared durable known-actual recovery guarantee.
Protocol spec suite (v0.1.26)
- SDK recovery profile v0.3 (2026-07-29) — defines and executes the client-side failure choreography outside server conformance: persist known actual usage before the first settlement request; same-key recovery for ambiguous results; exact schema-valid 200/201 settlement success; expired-commit fallback through
/v1/events; restart-safe rate-limit floors, credential rotation, quarantine, collision-resistant filenames, and concurrent replay; observable non-fatal heartbeat failures; and the explicit boundary before actual usage is known. All four official SDK releases in the current-version table bind every claimed scenario to exact native tests in CI. - Runtime spec document revision v0.1.25.16 (2026-07-28) — adds optional server-authoritative
remaining_ttl_msto successful live create/extend responses and makes it the normative heartbeat scheduling input. Same-key create/extend replays recompute the volatile field fresh while replaying every other field verbatim; CyclesEvidence excludes it. The heartbeat guidance defines per-attempt RTT accounting, timeout-derived retry reserves, repeated same-key recovery inside a shrinking safe window, bounded 429 handling, a two-zero-delay stop guard, and an explicitly best-effort fieldless fallback. - Runtime spec document revision v0.1.25.15 (2026-07-13) — adds the normative payment-rail boundary: reserving holds budget authority, committing records economic exposure, and releasing returns unused authority. None of those Cycles operations proves that a card, bank, blockchain, or other payment rail authorized, captured, charged, settled, voided, or refunded funds.
- Runtime spec document revision v0.1.25.14 (2026-07-11) — extends the closed-tenant binding to the persisting
POST /v1/eventssurface. A fresh event for a closed tenant returns409 TENANT_CLOSED; an exact replay of a pre-close keyed event still returns its stored response. - Runtime spec document revision v0.1.25.13 (2026-07-10) — adds
TENANT_CLOSEDto the runtimeErrorCodeenum with a normative closed-tenant binding in §ERROR SEMANTICS: persisting reservation create/commit/release/extend MUST return409 TENANT_CLOSEDon a closed owning tenant (taking precedence over reservation-state errors for non-replay attempts), while freshdry_run=true//v1/decideevaluations MUST return200 decision=DENY reason_code=TENANT_CLOSED(newDecisionReasonCodeknown value); present-but-malformed tenant records fail closed with500 INTERNAL_ERROR. Companion revisions: evidence spec 0.2.1 (declaresTENANT_CLOSEDin the evidence ErrorResponseMirror), protocol-extensions 0.1.27, governance 0.1.25.37 (addsTENANT_CLOSEDto thereservation.deniedevent's documentedreason_codevalues). Implemented bycycles-server0.1.25.47. - The cycles-protocol repo added
CONFORMANCE.md— a formal MUST / SHOULD / MAY statement of what a conformant Cycles implementation has to do. The active v0.1.25 target requires 12 MUST operations: 4 core runtime reservation operations plus 8 cross-plane event / webhook / balance / auth-introspection operations. Four more runtime operations (decide, list/get reservations, direct-debit events) are SHOULD-level and implemented by the reference servers. - The README repositioned the spec suite around upcoming v0.1.26 extensions (runtime base still v0.1.25). Action-kinds, action-quotas, observe mode, DenyDetail, and
ACTION_QUOTA_EXCEEDED/ACTION_KIND_DENIED/ACTION_KIND_NOT_ALLOWEDreason codes are SHOULD-level today and not yet enforced in runcycles' reference servers. They become MUST only whenCONFORMANCE.mdpromotes v0.1.26 to the active target. - Spec-only
trace_idalignment bumps on the extension specs (cycles-action-kindsandcycles-governance-extensionsto v0.1.27) declaretrace_idonErrorResponseandX-Cycles-Trace-Idoncomponents.headersfor OpenAPI tooling consistency. Governance extensions v0.1.28 then aligned the portableupdatePolicy.priorityrestatement with governance base v0.1.25.42 by addingminimum: 0; it did not change runtime behavior. See Correlation and Tracing.
Runtime server (cycles-server)
- v0.1.25.59 (2026-07-28) — emits server-authoritative
remaining_ttl_mson successful live reserve and extend responses. Both create and extend same-key replays recompute it from current Redis time (returning0once inactive); all other replay fields remain verbatim, and the volatile field is excluded from CyclesEvidence. - v0.1.25.58 (2026-07-14) — Successful keyed direct-event replays now increment
cycles_events_total{decision="APPLIED",reason="IDEMPOTENT_REPLAY"}. Mutation-only overdraft telemetry remains exactly-once. - v0.1.25.57 (2026-07-14) — Test-and-documentation release adding deterministic lost-response recovery coverage and frozen rolling-upgrade Redis fixtures. No production behavior or wire change.
- v0.1.25.56 (2026-07-14) — Scheduled expiry, retention, and reservation-index maintenance now use owner-fenced, renewable Redis leases across replicas, with fixed-cardinality run and duration metrics.
- v0.1.25.55 (2026-07-14) — Internal reservation-listing refactor around a typed query boundary and shared hash mapper. No API, query, cursor, or storage behavior change.
- v0.1.25.54 (2026-07-14) — Default
created_at_msreservation sorting can use a completeness-gated, per-tenant Redis index. Backfill, validation, repair, and automatic full-SCAN fallback keep results complete when the optional index is not ready. - v0.1.25.53 (2026-07-14) — Adds 1,000- and 10,000-row population baselines for the default sorted reservation query to nightly and release regression measurement. No production behavior change.
- v0.1.25.52 (2026-07-14) — Keyed events retain their immutable replay body in the event hash, with legacy fast-body fallback and opportunistic backfill. Commit and direct-event over-limit marking share one exact-integer helper.
- v0.1.25.51 (2026-07-13) — Live and dry-run reserve requests now share the endpoint idempotency namespace; switching mode under the same key returns
409 IDEMPOTENCY_MISMATCH. Also hardens hierarchical over-limit marking, event replay recovery, and corrupt-expiry quarantine. - v0.1.25.50 (2026-07-13) — Budget-aware Redis Lua paths retain signed int64 precision as decimal strings, avoiding IEEE-754 ledger drift and response rounding. Lifecycle replay metrics remain visible without double-counting debt.
- v0.1.25.49 (2026-07-12) — Adds the daily event/delivery index retention sweep (
EVENT_RETENTION_SWEEP_CRON) and hardens exactly-once expiry emission, atomic admin-release audit, and immutable lifecycle replay recovery. - v0.1.25.48 (2026-07-11) —
TENANT_CLOSEDguard extended toPOST /v1/events./v1/eventsis a persisting budget debit, so a debit whose owning tenant isCLOSEDis now rejected with409 TENANT_CLOSED, mirroring the reservation guards from 0.1.25.47. The check runs insideevent.lua(same fail-closed whitelist:CLOSED→ 409;ACTIVE/SUSPENDED→ proceed; malformedtenant:<id>record →500 INTERNAL_ERROR, no debit; absent record → no restriction), after the idempotency-replay block and before any mutation — so a pre-close idempotent replay still returns its stored response, but a fresh post-flip event is rejected,TENANT_CLOSEDtaking precedence over the per-scopeBUDGET_*outcomes. Exposure is narrow:/v1/eventshas no admin-key path and the tenant-key auth filter already 401s a durably-closed tenant, so this closes the residual post-flip race./v1/eventsis outside the evidence surface, so itsTENANT_CLOSEDdenial stamps nocycles_evidence. Runtime spec revision v0.1.25.14. See Error Codes — TENANT_CLOSED. - v0.1.25.47 (2026-07-10) — TENANT_CLOSED Rule 2 guard + webhook matcher parity. The four reservation mutations (create/commit/release/extend) now reject with
409 TENANT_CLOSEDwhen the owning tenant'sCLOSEDflip is durable (governance CASCADE SEMANTICS Rule 2 / Mode B invariant (a); runtime spec v0.1.25.13) — checked inside the Lua scripts, atomic with the budget mutations, taking precedence over reservation-state errors for non-replay attempts. Freshdry_run//v1/decideevaluations on a closed tenant return200 decision=DENY reason_code=TENANT_CLOSED; malformed tenant records fail closed with500; deployments without tenant records are unaffected. Mutation-surface 409s emiterrorCyclesEvidence (create/commit/release). In practice tenant-key calls usually still401at the auth filter; the 409 surfaces mainly on admin-on-behalf-of release and the post-flip/pre-revocation race window. Also refreshes the webhookscope_filterdispatch matcher to be byte-identical to the admin plane's spec-conformant matcher: blank event scopes are treated as unscoped, and trailing-/*filters require a non-empty child segment. See Tenant-Close Cascade Semantics and Webhook Scope Filter Syntax. - v0.1.25.46 (2026-07-04) — Public-endpoint rate limiting. The unauthenticated
GET /v1/evidence/*and CyclesEvidence JWKS endpoints are now rate-limited per client IP (default 300 requests/minute,CYCLES_PUBLIC_RATE_LIMIT_REQUESTS_PER_MINUTE; kill switchCYCLES_PUBLIC_RATE_LIMIT_ENABLED). Over-limit requests receive429witherror=LIMIT_EXCEEDED(new runtime ErrorCode, spec v0.1.25.12). - v0.1.25.45 (2026-06-27) — Operational endpoints require the admin key.
/actuator/prometheus,/actuator/info, aggregate/actuator/health, and the API docs / Swagger endpoints now requireX-Admin-API-Key; liveness/readiness probes and the protocol-public CyclesEvidence/JWKS endpoints stay unauthenticated. Also bounds the event-emission executor queue and adds correlation headers on auth-filter errors. - v0.1.25.44 (2026-06-26) — Deployment hardening: production Compose disables tenant labels on Prometheus metrics and public SpringDoc/Swagger;
exec javaPID-1 entrypoint. No API change. - v0.1.25.43 (2026-06-25) —
/actuator/health/readinessgains a Redis PING contributor; Spring Security explicitly stateless / API-key-only. - v0.1.25.42 (2026-06-25) — Release benchmark gating uses a rolling median from history; guarded tenant-auth DEBUG logging. CI/benchmark only.
- v0.1.25.41 (2026-06-24) — CR/LF log-injection flattening across data-plane logs; per-request controller logs moved INFO → DEBUG.
- v0.1.25.40 (2026-06-24) — Structured protocol-exception logs (method, path, route, status, error code,
request_id,trace_id,reservation_id) and request-context fields on controller logs. - v0.1.25.39 (2026-06-24) — Pagination, replay, auth, and production-default hardening. Legacy SCAN pagination for reservations/balances no longer skips rows when a page limit is reached mid-batch; sorted reservations no longer truncate at 2000 hydrated rows. Auth-filter errors now carry
trace_id,POST /v1/eventsidempotent replay returns the stored original response, invalid tenant default overage policies fail closed, API key validation no longer caches full allow/deny decisions, and production Compose pinscycles-serverto 0.1.25.39. - v0.1.25.38 (2026-06-23) — Unconfigured CyclesEvidence no longer queues source records. If
EVIDENCE_SERVER_IDorEVIDENCE_SIGNING_SIGNER_DIDis blank, the runtime emitter returns nocycles_evidenceref and does not push toevidence:pending. Configured deployments still computeevidence_idsynchronously, queue the source record, and return the evidence ref. - v0.1.25.37 (2026-06-22, per release tag — the repo CHANGELOG has a gap for this version) — Reservation evidence projection. The runtime persists computed evidence refs on reservation hashes and exposes them through
GET /v1/reservations/{id}andinclude=evidenceonGET /v1/reservations, enabling dashboard "View evidence" links after the original response is gone. - v0.1.25.17 (2026-04-22) — Pin
commons-lang33.18.0 to close CVE-2025-48924. No wire or behavior change. - v0.1.25.16 (2026-04-19) — Bump Spring Boot 3.5.11 → 3.5.13, pin embedded tomcat 10.1.54 (CVE remediation). Pom-version-only bump — no standalone release was cut; its changes ship cumulatively in v0.1.25.17. No wire or behavior change.
- v0.1.25.15 (2026-04-18) — Runtime audit-log retention TTL. New
audit.retention.daysconfig (default400, envAUDIT_RETENTION_DAYS) applies a TTL toaudit:log:{id}keys — previously these persisted indefinitely until Redis eviction, silently escaping the authenticated-tier retention the admin plane applies. Newaudit.sweep.cron(default0 0 3 * * *, envAUDIT_SWEEP_CRON) prunes stale ZSET pointers. Set to0for indefinite retention. - v0.1.25.14 (2026-04-18) — W3C Trace Context correlation. Every response now carries
X-Cycles-Trace-Id(32-hex lowercase). Inbound precedence:traceparent→X-Cycles-Trace-Id→ server-generate. New optionaltrace_idfield onErrorResponse,Event,WebhookDelivery,AuditLogEntry.WebhookDeliveryalso gainstrace_flagsandtraceparent_inbound_valid. Malformed inbound correlation headers are tolerated (fall through to next rule) — server never rejects on a bad header. SLF4J MDC carriestraceIdalongsiderequestId.ReservationExpiryServicemints a fresh trace_id per sweep batch so siblingreservation.expiredevents correlate. See Correlation and Tracing. - v0.1.25.13 (2026-04-16) —
SORTED_HYDRATE_CAP = 2000guard onGET /v1/reservationssorted path. Capped WARN log; cursor page still fills from the capped slice. Narrow filters to see past the cap. Superseded by v0.1.25.39, which keeps the 2,000-row warning threshold but no longer truncates sorted results. - v0.1.25.12 (2026-04-16) —
GET /v1/reservationsacceptssort_byandsort_dirquery params. Valid keys:reservation_id,tenant,scope_path,status,reserved,created_at_ms,expires_at_ms. Defaultdesc. Opaque sorted cursor binds to the(sort_by, sort_dir, filters)tuple; reusing under a different tuple returns HTTP 400. - v0.1.25.10 (2026-04-14) — Seven domain-level Prometheus counters (
cycles_reservations_reserve_total,..._commit_total,..._release_total,..._extend_total,..._expired_total,cycles_events_total,cycles_overdraft_incurred_total). New flagcycles.metrics.tenant-tag.enabled(defaulttrue) controls per-tenant cardinality. Fixed —reservation.expiredwebhook now actually fires (was no-op since v0.1.25.3 due to wrong Redis key prefix). - v0.1.25.8 (2026-04-13) — Admin-on-behalf-of release.
POST /v1/reservations/{id}/releaseaccepts eitherX-Cycles-API-Key(tenant) orX-Admin-API-Key(admin). Admin-driven releases write audit entries withmetadata.actor_type=admin_on_behalf_of. New propertyadmin.api-keyfor admin key configuration. - v0.1.25.6 (2026-04-10) — Reserve / commit / decide now distinguish
UNIT_MISMATCHfromBUDGET_NOT_FOUNDwhen a scope has a budget under a different unit. - v0.1.25.5 (2026-04-08) — Fixed duplicate emission of
budget.approaching_limit,budget.at_limit,budget.over_limit,debt.incurredon multi-scope operations. - v0.1.25.4 (2026-04-07) — Event payloads now include all fields webhook consumers need for correct dedup/ordering (
reservation_id,scope,unit,actor, timestamps). - v0.1.25.3 (2026-04-03) — Runtime event emission wired:
reservation.reserved,reservation.committed,reservation.released,reservation.expired,reservation.extended,event.applied, plus budget-state transitions (budget.approaching_limit,budget.at_limit,budget.over_limit). - v0.1.25.2 (2026-04-02) —
getBalancesandlistReservationslowercase the stored scope before segment matching. - v0.1.25.1 (2026-04-01) — Initial runtime event emission.
EventEmitterServicewith async, non-blocking emission on a dedicated daemon thread pool. TTL retention —EVENT_TTL_DAYS(90) /DELIVERY_TTL_DAYS(14). Pipelined event save + subscription lookup for near-zero overhead on non-event paths. Fixed —reservation.commit_overagenow emits only whenactual > estimate(was firing on every commit).
Admin server (cycles-server-admin)
- v0.1.25.55 (2026-07-21) — Policy create and update reject negative
prioritywith400 INVALID_REQUEST, matching governance spec revision 0.1.25.42. - v0.1.25.54 (2026-07-18) — Webhook signing-secret encryption fails closed at startup when its key is missing. Plaintext storage now requires the explicit local-development opt-out
WEBHOOK_SECRET_ALLOW_PLAINTEXT=true; existing plaintext remains readable during migration. - v0.1.25.53 (2026-07-16) — Unsupported HTTP methods return the standard JSON
405 Method Not Allowedresponse witherror=INVALID_REQUESTand an RFC-compliantAllowheader. - v0.1.25.52 (2026-07-15) — Correctness and durability release: truthful and tie-safe pagination, bounded exact sorting, collision-safe API-key creation, durable tenant-close cascade/outbox recovery, atomic bulk idempotency, bounded failed-auth tracking, trace-header CORS, strict request-body fields, and 95% line/branch coverage gates.
- v0.1.25.51 (2026-07-11) — SECURITY: admin-plane webhook category boundary + fail-closed dispatch + replay hardening (#209). Completes the tenant-owned-webhook confidentiality guarantee begun in 0.1.25.50. BEHAVIOR CHANGE. Adds the admin-plane half of the boundary (0.1.25.50 covered only the tenant self-service plane), enforced at all three layers plus a storage-hygiene cleanup: (1) write — the admin write paths (
POST /v1/admin/webhooks?tenant_id=X,PATCH /v1/admin/webhooks/{id}) now reject admin-only event types or categories (api_key,policy,webhook,system) on a concrete-tenant subscription with400 INVALID_REQUEST, validating the effective resulting selectors on update so a status-only reactivation can't re-enable a disabled offender (__system__-owned subscriptions are exempt — legitimate system-wide monitoring); (2) dispatch — a fail-closed boundary skips any admin-only event per-event for a concrete-tenant subscription in both live dispatch and replay, regardless of stored-selector state, so the guarantee doesn't depend on cleanup having run; replay is additionally hardened (intersects with the subscription's own selectors, no-op on non-ACTIVE, all-or-narrow selection — a 202 selects every deliverable event in the window or 400s;max_eventsvalidated[1,1000]; best-effortevents_queued); (3) the last-mile re-check ships incycles-server-events0.1.25.23. A one-time, default-on startup reconciler (storage hygiene, not the security mechanism — dispatch already withholds the events) strips admin-only selectors from stored non-DISABLEDconcrete-tenant rows and disables empty-both rows (webhook.category-boundary.reconcile-on-startup). Migration: monitor a specific tenant's admin-only events with a__system__-owned subscription (create via/v1/admin/webhookswith notenant_id); a system-owned row may carry admin selectors, so name the adminevent_typesyou want (create requires a non-emptyevent_types) — optionally pairing a representative admin type withevent_categoriesfor whole-category coverage — then select the tenant client-side on the envelopetenant_id(api_key.*/webhook.*/system.*are null-scoped, soscope_filtercan't narrow them per-tenant; onlypolicy.*is scope-filterable). The/testprobe (system.webhook_test) is a documented exception. Governance INVARIANT 2 (spec revisions v0.1.25.38–.41). See the release notes and Webhook Category Boundary. Pairs withcycles-server-events0.1.25.23 (last-mile enforcement). - v0.1.25.50 (2026-07-10) — SECURITY: tenant-plane webhook
event_categoriesvalidated against the tenant-accessible boundary.POST /v1/webhooksandPATCH /v1/webhooks/{id}validatedevent_types(budget/reservation/tenant only) but neverevent_categories— and delivery matching treats categories as an additive union with types, so a tenant key could subscribe to an admin-only category (api_key,policy,webhook,system) and receive admin event classes for its tenant. Both paths now reject an admin-only category with400 INVALID_REQUEST(governance spec revision v0.1.25.38). The update path also closes a legacy door where clearing bothevent_typesandevent_categoriesproduced a delivery-side match-ALL subscription. Upgrade past 0.1.25.49 and audit existing tenant subscriptions for admin-only categories and empty-both state — see the release notes for the audit one-liner. (Category-only subscriptions — emptyevent_typeswith non-emptyevent_categories— remain valid on update per governance v0.1.25.39.) Residual event-provenance hardening tracked as cycles-server-admin#209. - v0.1.25.49 (2026-07-10) — Spec-conformant webhook
scope_filtermatching + replay filtering. BEHAVIOR CHANGE: the admin matcher (WebhookRepository.matchesScope) moves from literal prefix matching to the spec's exact-match-with-trailing-*semantics — bare-prefix filters must be rewritten as…/*to keep matching child scopes, "base + descendants" coverage now needs two subscriptions, and null/blank-scope events are excluded from every scope-filtered subscription. Webhook replay now applies the same matcher (previously it bypassed scope matching entirely). Converges with the runtime matcher shipped incycles-serverv0.1.25.47 — both planes now match identically and are pinned to the same test table. See the migration notes and Webhook Scope Filter Syntax. - v0.1.25.48 (2026-07-04) — Cascade event payloads now map to a typed
EventDataTenantCascadeclass (the four*_via_tenant_cascadeevent types). Internal validation/registry only — no wire change. - v0.1.25.47 (2026-06-26) — Admin image readiness healthcheck +
exec java $JAVA_OPTSentrypoint; full-stack compose pins refreshed; README/OPERATIONS deployment doc refresh. - v0.1.25.46 (2026-06-26) — Dependency currency: Jedis 7.5.0 → 7.5.2, springdoc-openapi 2.8.16 → 2.8.17. No code change.
- v0.1.25.45 (2026-06-25) — Security hardening. Actuator, OpenAPI docs, and Swagger now require
X-Admin-API-Key;WEBHOOK_SECRET_ENCRYPTION_KEYis required; repeated 401/403 failures are throttled with429 LIMIT_EXCEEDED; webhook test delivery re-validates the SSRF policy; hardened Redis compose defaults. - v0.1.25.44 (2026-06-24) — CR/LF log-injection flattening across exception/auth/repository logs via a shared
LogSanitizer. - v0.1.25.43 (2026-06-24) — Ops logging context review: structured operational identifiers on governance exception-handler logs.
- v0.1.25.42 (2026-06-24) — Dependency-only security release. Reintroduces the Tomcat 10.1.55 override for Apache Tomcat CVEs, carries the previously unreleased Spring Boot 3.5.15 and Jedis 7.5.0 dependency bumps, and scopes the Trivy SARIF gate to the intended HIGH/CRITICAL severity threshold while jackson-databind 2.21.5 is not yet published. No code, spec, or wire-format changes.
- v0.1.25.41 (2026-04-26) — Dependency hygiene aligning the service fleet on the same Spring Boot patch and Redis client major. Spring Boot 3.5.13 → 3.5.14, Jedis 5.2.0 → 6.2.0, and the explicit Tomcat 10.1.54 override was removed because the Spring Boot BOM manages it directly. No code or wire-format changes.
- v0.1.25.40 (2026-04-23) — Same-release hygiene sweep on the v0.1.25.39 webhook lifecycle emits. Single-op actor now populates
keyIdfromauthenticated_key_id, matching the bulk-path parity already in place.changed_fieldsonwebhook.updatedis now a real diff vs the prior snapshot — identity PATCHes emit emptychanged_fieldsand full-identity PATCHes suppress the emit entirely per spec v0.1.25.33 §6281. The"no-req"literal correlation-id fallback is replaced withreq_<uuid>to preserve uniqueness under misconfiguredRequestIdFilter. No wire or spec surface change. - v0.1.25.39 (2026-04-23) — Webhook lifecycle events (spec v0.1.25.33).
POST /v1/admin/webhooks,PATCH /v1/admin/webhooks/{id},DELETE /v1/admin/webhooks/{id}, andPOST /v1/admin/webhooks/bulk-action(PAUSE / RESUME / DELETE) now emitwebhook.created/.updated/.paused/.resumed/.deletedEvents with the newEventDataWebhookLifecyclepayload (subscription_id,tenant_id,previous_status,new_status,changed_fields,disable_reason). Update-endpoint emit type is classified by the status transition:ACTIVE → PAUSEDyieldswebhook.paused,PAUSED → ACTIVEyieldswebhook.resumed, everything else yieldswebhook.updatedwith the touched properties enumerated inchanged_fields. Bulk path stamps every per-row emit withcorrelation_id = webhook_bulk_action:<action>:<request_id>(one correlation_id per invocation, shared across rows) — skipped / failed rows never emit. Single-op correlation_ids:webhook_create:<id>,webhook_update:<id>:<request_id>,webhook_delete:<id>. The dispatcher-emittedwebhook.disabled(auto-disable on failure threshold) is the events-service's responsibility — seecycles-server-eventsv0.1.25.11. Closes the operator-observability blind spot that v0.1.25.38 explicitly deferred. Aligns with spec v0.1.25.33 and v0.1.25.34'sEventCategory.webhookenum addition. See Event Payloads Reference. - v0.1.25.38 (2026-04-22) — Bulk-action event parity (spec v0.1.25.32).
POST /v1/admin/budgets/bulk-actionandPOST /v1/admin/tenants/bulk-actionnow emit per-row Events matching single-op kinds (budget.funded/.debited/.reset/.reset_spent/.debt_repaidfor budgets;tenant.suspended/.reactivated/.closedfor tenants) after each successful mutation. Correlation-id shape:budget_bulk_action:<action>:<request_id>andtenant_bulk_action:<action>:<request_id>— one correlation_id per invocation, shared across rows. Skipped rows (ALREADY_IN_TARGET_STATE) and failed rows emit no Event. Foraction=CLOSEon tenants the existingtenant_close_cascade:<tenant_id>:<request_id>correlation axis (spec v0.1.25.29) is unchanged — operators tracing an invocation query bytenant_bulk_action:close:<req>, operators tracing one specific tenant's close query bytenant_close_cascade:<tenant_id>:<req>. AggregateAuditLogEntryper invocation (spec v0.1.25.26) unchanged.bulkActionWebhookswas explicitly deferred in this release — its lifecycleEventTypevalues did not yet exist — and is closed in v0.1.25.39. See Using Bulk Actions. - v0.1.25.37 (2026-04-21) — Rule 1(c) bounded-convergence.
PATCH /v1/admin/tenants/{id}withstatus=CLOSEDandPOST /v1/admin/tenants/bulk-actionaction=CLOSE are idempotent on already-CLOSED tenants: the tenant-level write is a no-op (ALREADY_IN_TARGET_STATEfor bulk, 200 for single-op) but the cascade is re-run against remaining non-terminal children. Implements the "implementation-defined convergence mechanism" clause of spec v0.1.25.31 §Rule 1(c), letting operators drive straggler children to terminal state by re-issuing CLOSE — no new endpoint, no new ceremony. Dashboard v0.1.25.44's cascade-recovery banner is the UI affordance for this. - v0.1.25.36 (2026-04-20) — Rule 2 terminal-owner mutation guard coverage completed. Every mutation on an object whose owning tenant is CLOSED now returns
409 TENANT_CLOSEDfrom every admin-mutating endpoint, per spec v0.1.25.30. New guard callsites:POST /v1/admin/policies,PATCH /v1/admin/policies/{id},POST /v1/admin/api-keys,PATCH /v1/admin/api-keys/{id},DELETE /v1/admin/api-keys/{id},POST /v1/admin/webhooks,PATCH,DELETE,POST .../test, and per-row inbulkActionWebhooks. See Tenant-Close Cascade Semantics. - v0.1.25.35 (2026-04-20) — Tenant-close cascade + TENANT_CLOSED guard shipped (Mode B). Closing a tenant (
PATCH /v1/admin/tenants/{id}or bulk-action) now cascades:BudgetLedger → CLOSED,ApiKey → REVOKED, openReservation → RELEASED(reasontenant_closed),WebhookSubscription → DISABLED. One audit entry per mutated object under the samecorrelation_idas the originatingtenant.closed. New event kinds:budget.closed_via_tenant_cascade,api_key.revoked_via_tenant_cascade,reservation.released_via_tenant_cascade,webhook.disabled_via_tenant_cascade. Rule 2 guard — 409 TENANT_CLOSED — active on budget/reservation mutation endpoints (full Rule 2 coverage shipped in v0.1.25.36). Spec v0.1.25.29 / .30 / .31 alignment. runcycles' reference server uses Mode B (flip-first-with-guarded-cascade); Mode A (atomic) is also conformant. - v0.1.25.34 (2026-04-20) — Pin
commons-lang33.18.0 to close CVE-2025-48924. No wire or behavior change. - v0.1.25.33 (2026-04-20) — Bump Spring Boot 3.5.11 → 3.5.13, pin embedded tomcat 10.1.54 (CVE remediation). No wire or behavior change.
- v0.1.25.32 (2026-04-18) — Lenient deserialization on cross-plane read schemas.
EventandWebhookDeliverynow set@JsonIgnoreProperties(ignoreUnknown = true)at the class level. Runtime is the authoritative writer of these records; admin only reads them. Previously admin POJOs were strict, so runtime shipping an additive field in a patch would breaklistEvents/listWebhookDeliveriesuntil admin lockstep-updated. Now runtime can ship additive fields without forcing an admin release. Internal only — no wire contract change. - v0.1.25.31 (2026-04-18) — W3C Trace Context cross-surface correlation — server-side implementation of spec v0.1.25.28. New optional
trace_id(32-hex) onErrorResponse,AuditLogEntry,Eventresponse bodies. NewX-Cycles-Trace-Idresponse header on every response (2xx, 4xx, 5xx). Inbound precedence:traceparent→X-Cycles-Trace-Id→ server-generate. New exact-match query params onGET /v1/admin/audit/logsandGET /v1/admin/events:trace_id,request_id.WebhookDeliverypersiststrace_id+trace_flags+traceparent_inbound_validso the events sidecar can construct outboundtraceparentpreserving inbound sampling. Historical entries withouttrace_idcontinue to round-trip through strict Jackson. See Correlation and Tracing. - v0.1.25.30 (2026-04-18) — Bulk-action audit metadata enrichment. Single
AuditLogEntryper bulk-action invocation (bulkActionTenants,bulkActionWebhooks,bulkActionBudgets) now carries the full per-row outcome arrays plus filter echo plus wall-clockduration_ms. New keys:succeeded_ids,failed_rows,skipped_rows,filter. Worst-case audit row size ~40KB at 500-row cap. Fully additive — existing metadata keys unchanged; specAuditLogEntry.metadatais already typedobjectwithadditionalProperties: trueso no info.version bump. Triage now works from audit alone without re-running the op. - v0.1.25.29 (2026-04-18) — Budget bulk-action endpoint.
POST /v1/admin/budgets/bulk-action— AdminKeyAuth only. Five actions:CREDIT,DEBIT,RESET,REPAY_DEBT,RESET_SPENT.filter.tenant_idREQUIRED (cross-tenant budget bulk out of scope — 400 if blank).amountrequired for all 5 actions;spenthonored only onRESET_SPENT. Filter mirrorslistBudgets—scope_prefix,unit,status,over_limit,has_debt,utilization_min/max,search. Same 500-row cap,expected_count, idempotency-key safety gates as tenants/webhooks bulk. Per-row idempotency derived from{bulkKey}:{scope}:{unit}so retrying the failed subset cannot double-apply. Aligned with governance-admin spec v0.1.25.26. - v0.1.25.28 (2026-04-17) — Audit
tenant_idsentinel split. The previous single<unauthenticated>sentinel is replaced by two:__admin__(admin-plane operations not scoped to a tenant, authenticated-tier retention, never sampled) and__unauth__(pre-auth failures, unauthenticated-tier retention, subject to sampling). URL-safe underscored form — no percent-encoding needed. Migration: dashboards and auditor queries using?tenant_id=<unauthenticated>keep matching historical rows but stop matching fresh writes; migrate to__unauth__or__admin__. Historical rows age out on the correct schedule. Aligned with governance-admin spec v0.1.25.25. - v0.1.25.27 (2026-04-17) — Audit log filter DSL upgrade on
GET /v1/admin/audit/logs. New params:error_code(array, maxItems 25, IN-list, NULL-excluding),error_code_exclude(NOT-IN-list, NULL-passing, combinable witherror_code),status_min/status_max(integer range, mutex with exactstatus).operationandresource_typepromoted from scalar to array (comma-separated; single scalar still parses).searchmatch set extended to includeerror_code+operationin addition toresource_id/log_id. Aligned with governance-admin spec v0.1.25.24. - v0.1.25.26 (2026-04-17) — Bulk-action endpoints.
POST /v1/admin/tenants/bulk-action(SUSPEND | REACTIVATE | CLOSE) andPOST /v1/admin/webhooks/bulk-action(PAUSE | RESUME | DELETE). Filter-only body (no id arrays), 500-row hard cap (LIMIT_EXCEEDEDon overflow), optionalexpected_count→ 409COUNT_MISMATCHif the list changed between preview and submit. Response envelope splitssucceeded[]/failed[]/skipped[].idempotency_keyrequired; 15-minute replay window served from the newIdempotencyStoreprimitive. - v0.1.25.25 (2026-04-17) — Free-text
searchquery param on six admin list endpoints (/v1/admin/tenants,.../api-keys,.../budgets,.../webhooks,.../events,.../audit/logs). Case-insensitive substring match, ≤128 characters, AND-combined with every other filter. Pre-.25 servers ignore the unknown param (additive guarantee). - v0.1.25.24 (2026-04-16) —
sort_by+sort_diron the six list endpoints. Per-endpoint whitelists; unknown keys → 400INVALID_REQUEST. Default ordering changes forlistBudgets(utilization DESC) andlistWebhookSubscriptions(consecutive_failures DESC) — callers relying on the prior set-iteration order must pass an endpoint-supportedsort_byandsort_direxplicitly. Total-order cursor with primary-key tie-breaker;SORTED_HYDRATE_CAP = 2000on time-indexed endpoints. - v0.1.25.23 (2026-04-16) —
BudgetLedger.tenant_idexposed on the wire (@JsonInclude(NON_NULL)). Cross-tenant list responses now carry per-row tenant attribution without scope-string parsing. - v0.1.25.22 (2026-04-16) — Cross-tenant list for
GET /v1/admin/api-keysandGET /v1/admin/budgetsunderAdminKeyAuth. Omittenant_idto walk every tenant; composite cursor{tenantId}|{keyId}/{tenantId}|{ledgerId}. Four new budget filters:over_limit,has_debt,utilization_min,utilization_max. Deleted-cursor-tenant handling skips forward instead of stalling. - v0.1.25.20 (2026-04-16) — Audit log now captures failed requests (401/403/400/404/409/500) with
error_code, sanitizedmetadata.error_message,metadata.method,metadata.path. New sentinel tenant<unauthenticated>for pre-auth failures. Tiered TTL with SOC2-compliant defaults:audit.retention.authenticated.days=400,audit.retention.unauthenticated.days=30(both0= indefinite). Optionalaudit.sample.unauthenticatedfor DDoS exposure. Daily index sweepaudit.sweep.cron(default0 0 3 * * *). New countercycles_admin_audit_writes_total{path_class, outcome}— alert onoutcome=errornonzero. Semantic change: dashboards that assumed "audit entry exists ⇒ operation succeeded" must now checkstatus/error_code. - v0.1.25.19 (2026-04-16) —
GET /v1/auth/introspectaccepts bothAdminKeyAuthandApiKeyAuth. Tenant keys returnauth_type=tenantshape withtenant_id, optionalscope_filter, and a per-capability boolean table. Admin-plane capabilities forced tofalseunder tenant auth. - v0.1.25.18 (2026-04-15) —
RESET_SPENTfunding operation onPOST /v1/admin/budgets/fund. Clears (or overrides)spentfor billing-period rollover, distinct fromRESETwhich preservesspent. Optionalspent >= 0override for migration / proration / credit-back. New eventbudget.reset_spent.BudgetFundingResponsegains nullableprevious_spent+new_spent. - v0.1.25.17 (2026-04-14) — Fixed cjson empty-array round-trip bug that dropped records from
GET /v1/admin/api-keys(and defensively onPolicy.caps.tool_allowlist/tool_denylist/Tenant.metadata).revokeApiKeyon already-revoked key now returns 409KEY_REVOKED(was 200). - v0.1.25.16 (2026-04-13) — Dual-auth on six tenant-scoped webhook endpoints (
GET/PATCH/DELETE /v1/webhooks/{id},POST /v1/webhooks/{id}/test,GET /v1/webhooks,GET /v1/webhooks/{id}/deliveries). Admin operators can pause / inspect / force-delete tenant webhooks during incident response.POST /v1/webhooks(create) remains tenant-only. - v0.1.25.15 (2026-04-13) —
ScopeValidatorenforces canonical scope grammar (tenant:<id>first; canonical kind ordertenant → workspace → app → workflow → agent → toolset; wildcards only terminal in policy patterns). - v0.1.25.14 (2026-04-13) — Dual-auth on
POST /v1/admin/budgets,POST /v1/admin/policies,PATCH /v1/admin/policies/{id}. Admin auth requirestenant_idin the body; audit-log recordsmetadata.actor_type=admin_on_behalf_of. - v0.1.25.13 (2026-04-13) — Fixed CORS
allowedMethodsmissingPUT, which blocked browser dashboards from callingPUT /v1/admin/config/webhook-security. - v0.1.25.12 (2026-04-12) — Targeted 404/409 error responses on admin endpoints; documented 400 across every admin operation; spec-compliance hardening pass.
- v0.1.25.11 (2026-04-12) — Contract testing default ON.
*ControllerTestvalidates every 2xx/4xx/5xx JSON body against the pinned spec at build time. Offline builds:CONTRACT_VALIDATION_ENABLED=false. - v0.1.25.10 (2026-04-12) — Typed
Permissionenum +Capabilitiesclass; spec-compliance hardening.
Events service (cycles-server-events)
- v0.1.25.25 (2026-07-18) — Delivery enforces an always-on SSRF baseline for local, private, metadata, CGNAT, and IPv6 unique-local destinations. Missing secret-encryption keys fail startup; local private-network delivery and plaintext storage require explicit development opt-outs.
- v0.1.25.24 (2026-07-15) — Multi-replica reliability and security release: age-gated owner-fenced delivery/evidence recovery, durable dispatcher outbox and quarantine, atomic failure/auto-disable transitions, strict evidence validation and JCS canonicalization, Redis ACL/TLS/timeouts, and cross-replica ordering and maintenance leases.
- v0.1.25.23 (2026-07-11) — SECURITY: last-mile webhook ownership boundary (#209). The delivery worker now re-evaluates governance WEBHOOK SUBSCRIPTION INVARIANT 2 against the freshly-reloaded event and subscription immediately before every outbound POST — initial delivery, retries, and recovered/orphaned redeliveries all funnel through the one send path. A concrete-tenant subscription (
tenant_idpresent and!= "__system__") is never delivered an admin-only event; the decision is per-event (fail-closed raw-string allowlist, so a future admin event type an old worker's enum hasn't learned is still blocked), so a mixed subscription still receives its tenant-accessible events. This catches deliveries queued before this version deployed and every retry — paths that never re-pass the admin-plane enqueue gate. A blocked delivery is dropped as terminal (FAILED,ownership boundary (#209), no retry, no failure-budget impact) and counted oncycles_webhook_delivery_boundary_skipped_total. Rolling-deploy note: per-worker — airtight only once all delivery workers are on 0.1.25.23 (a lingering ≤0.1.25.22 worker can still send a violating queued delivery). Pairs withcycles-server-admin0.1.25.51. See the release notes. - v0.1.25.22 (2026-07-04) — Delivery-time SSRF guard. The dispatcher re-validates each subscription URL against the current
config:webhook-security(scheme /allow_http, resolved IP vsblocked_cidr_ranges,allowed_url_patterns) immediately before every outbound POST. A blocked delivery fails permanently (ssrf_blocked, no retry) and does not count against the subscription's failure budget. - v0.1.25.21 (2026-07-03) —
system.webhook_delivery_failedmeta-alert emitted on retry exhaustion; the four*_via_tenant_cascadeevent types added to the vocabulary; unknowncategory/actor.typevalues no longer poison deliveries. - v0.1.25.20 (2026-06-26) — Webhook processing recovery made multi-replica safe (timestamped claims, idle-based requeue); delivery-state write failures fail closed;
exec javaentrypoint. - v0.1.25.19 (2026-06-26) — Jedis 7.5.0 → 7.5.2. No code change.
- v0.1.25.18 (2026-06-25) — Reliable claim/ack queue (BRPOP → BLMOVE). Delivery IDs are now claimed via
BLMOVE dispatch:pending → dispatch:processingand acked withLREM; startup recovery requeues orphaned entries. Closes the crash-loss window of the previous destructiveBRPOPconsumption. Also fail-closed secret decrypt and a Redis PING health indicator. - v0.1.25.17 (2026-06-24) — Effective trace-id on transport-failure logs; CR/LF log-injection flattening.
- v0.1.25.16 (2026-06-24) — Ops logging context review: stable operational identifiers on delivery/retry/dispatch/evidence logs; transport failures log the target host rather than the raw URL.
- v0.1.25.15 (2026-06-23) — Unconfigured CyclesEvidence is a supported disabled mode. A blank
EVIDENCE_SERVER_IDprevents the evidence signer worker, envelope builder, and local signing key from being created. The worker no longer claims source records or dead-letters them solely becauseserver_idis absent. Retention cleanup also skips non-ZSET Redis keys matched by broadevents:*scans, avoidingWRONGTYPEstartup noise. - v0.1.25.14 (2026-06-15) — CyclesEvidence signing tier. The events service consumes
evidence:pending, buildscycles-evidence/v0.1envelopes, cross-checks the producer-stampedevidence_id, Ed25519-signs with the private key held only in this service, and stores the envelope content-addressed for the runtime server to serve. - v0.1.25.11 (2026-04-23) — Dispatcher-side webhook lifecycle emit (spec v0.1.25.33). When
DeliveryHandler.incrementConsecutiveFailurescrossesdisable_after_failures, the dispatcher now writes awebhook.disabledEvent directly to the shared Redis store alongside the existingDISABLEDstatus flip andcycles_webhook_subscription_auto_disabled_totalmetric.EventType.WEBHOOK_DISABLEDandEventCategory.WEBHOOKenum values added (additive, no wire break).correlation_id = webhook_auto_disable:<subscription_id>:<delivery_id>; payload conforms toEventDataWebhookLifecyclewithdisable_reason="consecutive_failures_exceeded_threshold",actor.type=system,source=cycles-events;trace_idcopied from the triggering Delivery when present. Emit is best-effort — a Redis write failure logs at WARN but does not revert the status flip. The operator-initiated webhook lifecycle emits (webhook.created/updated/paused/resumed/deleted) remain the responsibility ofcycles-server-adminv0.1.25.39; this patch closes only the auto-disable path the spec names as the dispatcher's exclusive emission point. - v0.1.25.10 (2026-04-20) — Bump Spring Boot 3.5.11 → 3.5.13, pin embedded tomcat 10.1.54 (CVE remediation). No wire or behavior change.
- v0.1.25.9 (2026-04-18) — Management port split.
health,info, andprometheusactuator endpoints moved from application port7980to a dedicated management port (default9980, envMANAGEMENT_PORT). Migration: Prometheus scrape configs must update target port from:7980→:9980; kubelet probes and DockerHEALTHCHECKsame. Published Docker imageHEALTHCHECKalready updated. No wire-format change for dispatch. The reference events service is an outbound worker, so do not publish7980for webhook delivery; keep9980internal-only. - v0.1.25.8 (2026-04-18) — Cross-surface correlation on
WebhookDelivery. Three new OPTIONAL fields:trace_id(captured at dispatch time from originating event),trace_flags(W3C trace-flags byte for outboundtraceparent),traceparent_inbound_valid(whether upstream sent valid W3C traceparent). Aligns with governance-admin spec v0.1.25.28. Dispatcher honorstrace_flagswhentraceparent_inbound_valid=true, otherwise defaults to01(sampled). Proactivetrace_idstamping onDeliveryas rolling-upgrade safety net for pre-.31 admin servers. - v0.1.25.7 (2026-04-18) — trace_id and W3C Trace Context headers on every outbound webhook delivery. New outbound headers:
X-Cycles-Trace-Id(always present),traceparent: 00-<trace_id>-<16-hex-span>-<flags>(fresh span-id per delivery, never reused from inbound),X-Request-Id(when event carriesrequest_id). NewEvent.trace_idfield, optional. Non-fataltrace_id_shapevalidation rule — malformedtrace_idincrementscycles_webhook_events_payload_invalid_total{rule="trace_id_shape"}and the dispatcher falls back to minting a fresh id so outbound header stays well-formed. Aligns with governance-admin spec v0.1.25.27. - v0.1.25.6 (2026-04-16) —
BUDGET_RESET_SPENTadded to theEventTypevocabulary. Eight new Prometheus metrics undercycles_webhook_*:delivery_attempts_total,delivery_success_total,delivery_failed_total,delivery_retried_total,delivery_stale_total,subscription_auto_disabled_total,events_payload_invalid_total, pluscycles_webhook_delivery_latency_secondstimer.cycles.metrics.tenant-tag.enabledflag mirrors the runtime. Non-fatalEventPayloadValidatoron every ingested event (WARN + counter on violation; never drops). - v0.1.25.5 (2026-04-08) — Force HTTP/1.1 on outbound webhook deliveries. Fixes silent body-drop against HTTP/2 reverse proxies that upgrade to h2c.
- v0.1.25.4 (2026-04-07) —
SubscriptionRepository.updateDeliveryStateswitched to partial merge (previously overwrote admin-side PATCH writes under contention). - v0.1.25.3 (2026-04-03) —
micrometer-registry-prometheusdependency added (/actuator/prometheuswas 404 without it). TypedDeliveryStatus/WebhookStatusenums. - v0.1.25.1 (2026-04-01) — Initial release. Redis-driven dispatcher consuming
dispatch:pendingvia BRPOP. HMAC-SHA256 signing, exponential-backoff retry, auto-disable after consecutive failures. AES-256-GCM encryption of signing secrets at rest (WEBHOOK_SECRET_ENCRYPTION_KEY, 32-byte key). TTL retention (90d events / 14d deliveries). Configurabledispatch.http.connect-timeout-seconds+dispatch.http.timeout-seconds.
Dashboard (cycles-dashboard)
- v0.1.25.85 (2026-07-21) — Policy create/edit rejects negative priority replacements, while preserving visible legacy values during unrelated edits. Aligns with governance spec 0.1.25.42 and admin 0.1.25.55.
- v0.1.25.84 (2026-07-20) — Extracts policy editing into focused owners and hardens mutation ownership, validation, stale-read invalidation, clears, closed-tenant behavior, and preservation of unknown server values.
- v0.1.25.83 (2026-07-20) — Hardens API-key and tenant-lifecycle mutation ownership and updates the shipped fleet to admin 0.1.25.54/events 0.1.25.25 with fail-closed production secret encryption.
- v0.1.25.82 (2026-07-20) — Makes webhook-detail statistics explicitly loaded-page scoped and corrects attempts severity, response-time filtering/pluralization, accessibility, and failure-threshold health state.
- v0.1.25.81 (2026-07-20) — Hardens webhook editing against duplicate/stale writes and makes supported clears, numeric validation, selector healing, and sibling-operation ownership match the admin API.
- v0.1.25.80 (2026-07-20) — Hardens webhook status, delete, secret rotation, endpoint-test, and replay workflows against duplicate actions, stale polling, and post-commit navigation or refresh failures.
- v0.1.25.79 (2026-07-19) — Extracts webhook subscription/delivery acquisition into a focused owner and makes polling, pagination, cancellation, and stale-publication handling safe for retained delivery-history pages.
- v0.1.25.78 (2026-07-19) — Extracts tenant suspend/reactivate/close, cascade recovery, and Emergency Freeze protocols and hardens duplicate-action, polling, cancellation, and refresh ownership.
- v0.1.25.77 (2026-07-19) — Extracts tenant/webhook filter-wide bulk protocols and ensures preview, cursor traversal, expected counts, and final mutation use one immutable filter snapshot.
- v0.1.25.76 (2026-07-18) — Makes Tenant Detail child acquisition bounded and abortable, with explicit tab, polling, post-mutation refresh, and row-batch cancellation ownership.
- v0.1.25.75 (2026-07-17) — Extracts budget acquisition into a focused owner and ensures load-more and export reuse the exact applied filter/sort tuple that produced the visible cursor.
- v0.1.25.74 (2026-07-17) — Extracts budget filter-wide mutations and binds preview, exact-count gating, confirmation, and submission to one immutable filter tuple.
- v0.1.25.73 (2026-07-17) — Extracts Overview's two-phase polling and bounded cursor walks into a focused, directly tested acquisition protocol without changing API requests or cadence.
- v0.1.25.72 (2026-07-16) — Treats a successful single-budget funding request as committed even if its follow-up refresh fails, preventing duplicate mutations under a new idempotency key and separating stale-display warnings.
- v0.1.25.71 (2026-07-16) — Extracts budget detail/funding presentation and typed CREDIT, DEBIT, RESET, RESET_SPENT, and REPAY_DEBT request construction while preserving wire behavior.
- v0.1.25.70 (2026-07-16) — Corrects audit/event list acquisition, query synchronization, loaded-page state, and export ownership.
- v0.1.25.69 (2026-07-16) — Enforces lazy-chunk SRI under the production CSP and preserves the operator's admin key across transient network, malformed-response, and upstream 5xx failures.
- v0.1.25.68 (2026-07-15) — Removes no-op editing of immutable API-key expiry and tenant reservation-expiry policy, corrects bulk RESET/RESET_SPENT semantics and zero handling, and hardens large-fleet counts plus list refresh/pagination ownership.
- v0.1.25.66 (2026-07-04) — Bundled server stack bumped to
cycles-server0.1.25.46 andcycles-server-events0.1.25.22 (compose + README). Operator notes: events .22 re-validates webhook URLs against the webhook-security config at delivery time — dev stacks delivering tohttp://or private-network targets must adjust Settings → Webhook security or deliveries permanently fail withssrf_blocked; server .46 adds default-on 429 rate limiting on the public evidence/JWKS endpoints. No dashboard code change. - v0.1.25.65 (2026-07-03) — The four
*_via_tenant_cascadeevent kinds (spec v0.1.25.35) added toEVENT_TYPES, so they appear in the Events type-filter typeahead and can be selected in webhook subscription event-type checkboxes. CI coverage gate raised to the repo's ≥95% line threshold. - v0.1.25.64 (2026-06-26) — Bundled fleet bump (
cycles-server0.1.25.44, admin 0.1.25.47, events 0.1.25.20); nginx container healthcheck; backend healthchecks moved to/actuator/health/readiness; hardened production compose defaults (required Redis password, required webhook-secret encryption, SpringDoc off, tenant metrics off). - v0.1.25.63 (2026-06-22) — Reservation → evidence links. The reservation detail dialog gains one-click View reserve / commit / release evidence links to the Evidence viewer, and the list
includetoggle requests theevidenceprojection. Requirescycles-serverv0.1.25.37+; older servers omit the field and no links render. - v0.1.25.62 (2026-06-22) — Reservations and Evidence gap closure. Reservations view gains
committed/finalized_at_mscolumns, a read-only detail dialog with reserve-time and commit-time metadata (include=metadata,committed_metadata), and advanced filters (created/expires/finalized time ranges, Subject filters). Create/Edit Policy exposescaps,rate_limits,reservation_ttl_override, and the effective window; Create/Edit Webhook exposesthresholdsandretry_policy; API-key edit can adjustexpires_at; tenant create/edit exposesmax_reservation_extensionsandreservation_expiry_policy. New Evidence page retrievesGET /v1/evidence/{id}and resolves the signer key against the published JWK Set. Runtime-plane compose baseline moved tocycles-server0.1.25.36. - v0.1.25.61 (2026-05-31) — Configurable reverse-proxy upstreams. The bundled nginx proxy's two upstream targets become environment variables (
ADMIN_UPSTREAM/RUNTIME_UPSTREAM), so the proxy that routes/v1/reservations*,/v1/evidence*, and/v1/.well-known/cycles-jwks.jsonto the runtime plane can be retargeted at deploy time without an image rebuild. Deployment-only change. - v0.1.25.59 (2026-04-23) — Spec alignment v0.1.25.31 → v0.1.25.34. Adds the six
webhook.created/.updated/.paused/.resumed/.disabled/.deletedvalues to the Events view type datalist pluswebhookto the category dropdown, so operators can filter on the new lifecycle events emitted by admin v0.1.25.39 and events v0.1.25.11. Payload renderer stays untyped (Record<string, unknown>) — matches the existing pattern forEventDataTenantLifecycle. Compose pins bumped to admin v0.1.25.39 + events v0.1.25.11 so new events actually fire end-to-end. Operator bug folded in: Overview budget-utilization donut and the at-cap attention card now exclude spec-terminal CLOSED budgets (spec v0.1.25.29) from both the bucketing and the total, so a CLOSED-at-120% budget no longer inflates "Over cap" and CLOSED budgets no longer inflate "Healthy". FROZEN stays included — it's non-terminal. - v0.1.25.58 (2026-04-23) — Mobile-responsive sweep. Ten fixes across layout shell, tables, menus, dialogs: Escape closes the drawer with focus-return to hamburger, hamburger sized to 44×44 with
aria-expanded/aria-controls, root usesh-dvh(mobile Safari URL-bar),PageHeaderreflows to column on narrow viewports, table minimum widths tightened,RowActionsMenuclamps to viewport horizontally,FormDialog+ConfirmActionfooters flex-wrap,LoginViewandNotFoundViewfit 320w withmin-h-dvh. Virtualized-table card-mode on phones andCommandPalettesoft-keyboard handling deferred to a future release. - v0.1.25.57 (2026-04-23) — Correctness + debuggability sweep. Replay body properly typed with pre-flight validation of
max_events; tenant-list failure surfaces in the top banner instead of inline filter text;auth.restore()is single-flight so concurrent cold-load callers coalesce; timeout error messages include method + URL path; JSON-parse failures on error bodies logconsole.warn;ReservationsViewreads?tenant_id=and mirrors the filter to URL; bulk-action durations useIntl.NumberFormatfor locale-aware formatting. - v0.1.25.56 (2026-04-23) — P2 accessibility + form-UX closeout.
TenantsViewcreate form validates live withFormDialog.submitDisabled;BaseChartauto-renders ansr-onlydata table from pie-shaped options so screen readers get every donut;.chip:focus-visiblering added.RowActionsMenukeyboard nav (ArrowUp/Down/Home/End/Escape/Tab) was already correct — added a regression-lock test. - v0.1.25.55 (2026-04-23) — L-tier polish + coverage backfill. Nine hardcoded polling-interval literals across views collapsed to shared constants;
.form-labelgainsfont-medium;RefreshButtondark hover states wired. Coverage additions onuseChartTheme,useListExportboundaries, andusePollingstale-after-unmount. - v0.1.25.54 (2026-04-23) — Full-app UX & safety sweep (P0 / P1). Catch-all 404 route with
NotFoundView; detail views distinguish pending fetch vs 404;useListExportthreadsAbortSignalthroughfetchPageand drops late pages; named-route discipline across Sidebar / TenantDetail / WebhookDetail / BudgetsView; per-routedocument.titleviameta.title+afterEach;LoadingSkeletonon cold load for list views; logout confirmation viaConfirmAction;formatDateTime/formatTimeinclude short timezone marker;usePolling.lastSuccessAt+PageHeaderfreshness pill; sharedInlineErrorBannerwith dismiss × replaces nine inline banners;BudgetsViewroute.query.filterwatcher re-runsloadListon change; "all tenants" scope banner onBudgetsViewnon-cross-tenant-filter case. - v0.1.25.53 (2026-04-22) — Counter-strip / donut / drill-down reconciliation. Five instances of the same class of bug where two surfaces showing the same concept read from different data sources or applied different filters. Fixes: webhooks-active drill-down now pushes
status=server-side (was client-side filtering one page — 62 vs 12); webhook fleet-health donut slices are status-pure{active, paused, disabled}sourced fromoverview.webhook_counts(not failure-biased — fixes 5 vs 6 paused); utilization-donut sampling bumplimit=500 → 2000; events drill-downs from Overview carryfrom/tomatching the tile window; Expiring Keys "View all" carries?expiring_within_7d=1so the drill-down shows the same set as the card. - v0.1.25.52 (2026-04-22) — Webhook fleet-health donut relocated to Overview. Moved from
WebhooksViewto the Overview 4-up chart grid (after budget utilization, before events-by-category) so operators get fleet-glance health on the landing page without it pushing the webhooks table below the fold. Same data source (listWebhooksalready fetched for the failing-webhooks card), same drill-down contract. Overview chart grid widenedlg:grid-cols-3→lg:grid-cols-4. - v0.1.25.51 (2026-04-22) — Webhook visualizations — fleet-health donut + per-subscription stat row.
WebhookDetailViewgains a four-up stat row between the subscription card and the Delivery History table: last-success chip with PagerDuty-style traffic-light semantics (green <1h, amber 1h–24h, red ≥24h or no success), delivery-outcome donut (click sets the history-table status filter in place), attempts-per-delivery histogram bucketed 0/1/2/3/4/5+, response-time p50/p95/max computed via NIST nearest-rank. Fleet-health donut overwebhooks(client-side reduce from the 60s poll, no new request) with click-to-drill via?status=...or?failing=1. - v0.1.25.50 (2026-04-22) — Budget fleet-utilization reframed. Overview chart now partitions budgets by actual
spent / allocatedacross Healthy (<0.9), Near cap (0.9 ≤ util < 1.0), Over cap (≥1.0) and drills to/budgets?utilization_min=…&utilization_max=…— replaces the v0.1.25.48 stacked bar that was keyed offbudget_counts.over_limit/budget_counts.with_debt(a financial overdraft signal, not a utilization signal).BudgetsViewhydratesfilterUtilMin/filterUtilMaxfromroute.queryon mount so deep-links from the Overview drill-down actually filter. Chart type changed to donut so the three Overview charts share one shape. - v0.1.25.49 (2026-04-22) — Chart drill-down (slice-click → filtered list view) + color-palette fix. Every slice / segment on the three Overview charts is clickable and navigates to the corresponding filtered list view via
router.push(budgetsstatus=/filter=over_limit/filter=has_debt, eventscategory=). Ten-hue qualitative palette added touseChartThemewith ahashCategory(name) % 10fallback so previously-collided neutral-grey categories (tenant,api_key,audit) now get distinct hues —policykeeps danger-red andreservationkeeps success-green because operators already associate those semantics. - v0.1.25.48 (2026-04-22) — Overview charts expanded from 1 to 3. Added budget-utilization stacked bar + events-by-category donut beside the existing budget-status donut, laid out as a 3-up grid beneath the counter strip. Same
/v1/admin/overviewpayload — no new fetches. Each chart has its ownv-ifempty-state guard plus a wrappinghasAnyChartso the row hides entirely on a fresh environment. - v0.1.25.47 (2026-04-22) — Charting layer trial slice (budget-status donut). Introduces ECharts +
vue-echartsas a tree-shaken lazy-loaded chunk (~142 KB gz,CanvasRendereronly), new sharedBaseChart.vuewrapper, newuseChartThemecomposable (reactive palette re-deriving on dark-mode flip), plus one live chart — the budget-status donut on Overview.OverviewViewinitial chunk stays at ~6.4 KB gz viadefineAsyncComponent. - v0.1.25.46 (2026-04-21) — Hide terminal-state rows by default across every list view. Tenants, Budgets, Webhooks, API Keys, and TenantDetail sub-lists now hide CLOSED / DISABLED / REVOKED / EXPIRED rows on mount and surface a "Show closed (N)" / "Show disabled (N)" / "Show revoked (N)" toggle mirroring the hidden count. Toggle state mirrors to
?include_terminal=1on top-level views; auto-engages when anexplicit_statusmatching a terminal value is chosen. Matches the GitHub / Linear / Gmail convention — new shareduseTerminalAwareListcomposable, single source of truth for what counts as terminal per entity kind. - v0.1.25.45 (2026-04-21) — Closed-tenant children excluded from Overview attention cards + Tenants filter persists across drill-in + clean-close banner flash. Overview fetches
listTenants({status:'CLOSED'})alongside the existing attention-card sources and filters Budgets-at-cap / Frozen-budgets / Debt-budgets / Expiring-API-keys / Failing-webhooks to exclude closed-tenant children (transient Mode B convergence window, spec v0.1.25.31).TenantsViewfilter state (parent, status) survives drill-in via arouter.replace({ query })watcher; back-navigation usesrouter.back()so the detail URL stays clean.executeTenantActionnow four-way refetches on CLOSE so the cascade-recovery banner doesn't flash on a clean close. - v0.1.25.44 (2026-04-20) — Cascade-recovery banner (consumes spec v0.1.25.31 Rule 1(c)).
TenantDetailViewrenders an amber banner below the CLOSED-tenant tombstone whencascadeIsIncomplete(children)— enumerates pending counts per axis (budgets, webhook subscriptions, API keys) and exposes a "Re-run cascade" button that re-PATCHes{status: CLOSED}. Idempotent at the tenant level (no-op per Rule 1) but drives remaining non-terminal children to terminal states via the admin v0.1.25.37 bounded-convergence path. Serves historical tenants closed pre-admin-.35(cascade never ran) and partial-cascade-failure recovery without requiring operators to curl the admin API by hand. - v0.1.25.43 (2026-04-20) — Closed-tenant tombstone + cascade preview UI. Consumes admin v0.1.25.36 cascade implementation. New TenantDetailView amber banner when
tenant.status === 'CLOSED'("Tenant closed — all owned objects are read-only."). CLOSE confirm-dialog now previews what the cascade will terminate (budgets, webhook subscriptions, API keys, open reservations with counts).TENANT_CLOSED409 humanizer ("Tenant is closed — this object is read-only.") on race conditions. Audit + event-timeline rows render a small amber "tenant cascade" chip when the event kind carries_via_tenant_cascade, letting operators distinguish cascade-triggered state changes from user-driven ones when correlating bycorrelation_id. Admin image pin 0.1.25.32 → 0.1.25.36 (cascade requires admin .36). New sharedisTerminalTenant()predicate insrc/utils/tenantStatus.ts. Spec pointer v0.1.25.29 → v0.1.25.31. - v0.1.25.42 (2026-04-19) — Security: base-image bumps (
nginx:1.27-alpine→nginx:1.29-alpine,node:20.19→20.20) resolving 57 Alpine-layer CVEs flagged by Trivy. - v0.1.25.41 (2026-04-19) — Dependabot-bundled dependency bumps including
vue-router 4.6.4 → 5.0.4(major, no breaking changes for this app). TypeScript typecheck clean; 742 tests green. - v0.1.25.40 (2026-04-19) — Shared icon library at
src/components/icons/— 24 reusable SVG components (CopyJsonIcon, CopyIcon, KebabIcon, etc.). Stroke-width unified to1.5; four icons upgraded to Heroicons v2 geometry. Copy JSON moved from dedicated rows/columns to overlay icons and kebab menus — WebhookDetailView delivery column shrinks 88px → 40px; panels lose ~35–50px footer rows. - v0.1.25.39 (2026-04-18) — Cross-surface trace / request correlation chip on Events, Audit, and WebhookDeliveries rows. Click
trace_idon Audit → EventsView filtered to the same trace; clicktrace_idon Events → AuditView filtered to originating entry. Requirescycles-server-adminv0.1.25.31+. Webhook delivery history fixes: interface field mapping (http_status→response_status,delivered_at→completed_at), StatusBadge delivery-status colors, Error column on FAILED rows, CSV export enrichment. - v0.1.25.38 (2026-04-18) — Structured bulk-action audit detail in AuditView expanded row. Renders
succeeded_ids/failed_rows/skipped_rows/filterecho /duration_msas a first-class layout (not raw JSON). Requirescycles-server-adminv0.1.25.30+. - v0.1.25.37 (2026-04-18) — Per-row Copy JSON affordance across EventsView, AuditView, EventTimeline, WebhookDeliveries. Row-select bulk failures open
BulkActionResultDialog. EventTimelinecorrelation_idclick → EventsView filtered. - v0.1.25.36 (2026-04-18) — BudgetsView row-select + bulk Freeze / Unfreeze. Row-select checkboxes + floating bulk toolbar mirror the TenantsView pattern.
- v0.1.25.35 (2026-04-18) — Budget bulk-action UI. Filter-apply toolbar on BudgetsView for
CREDIT,DEBIT,RESET,RESET_SPENT,REPAY_DEBT. Requirescycles-server-adminv0.1.25.29+. - v0.1.25.34 (2026-04-18) —
BulkActionResultDialogcomponent — per-row outcome triage for bulk operations. NewerrorCodeMessages.tsutility — single source of truth for operator-facing error code prose. - v0.1.25.33 (2026-04-18) — AuditView filter DSL completeness against governance-admin v0.1.25.24:
error_code_exclude,operationIN-list,resource_typetypeahead datalist,status_min/status_maxrange. Deep-link support via query params. - v0.1.25.28 (2026-04-17) — Bulk-action UI on TenantsView + WebhooksView (filter-apply path in addition to row-select). Image tag
ghcr.io/runcycles/cycles-dashboard:0.1.25.28. Spec alignment bumped to v0.1.25.23. CI gate runs 6 new e2e bulk-action probes (empty filter → 400, invalid action → 400, zero-match → 200, idempotency replay, webhook mirror) against the published image on every release. - v0.1.25.27 — Free-text
searchwired into six admin list views (Tenants, Budgets, ApiKeys, Audit, Webhooks, Events). Debounced page-1 refetch honors cursor-tuple invalidation. Client-side fallback on pre-.25 servers. RESET_SPENT funding operation available from BudgetDetail → Fund (requires admin v0.1.25.18+). - v0.1.25.26 — V4 server-side sort across six admin views +
ReservationsView(runtime plane). TenantsView "+N more" and inline child-links thread?parent=<src>so the back arrow returns to the source parent. Scale hardening: row virtualization via@tanstack/vue-virtualacross seven list views; pagination + N+1 mitigation; cancel-button on long exports; dark-mode and a11y (WCAG AA) passes. - v0.1.25.22 — ApiKeysView + BudgetsView consume the cross-tenant
/v1/admin/api-keysand/v1/admin/budgetsendpoints.BudgetLedger.tenant_idrendered as a first-class column. - New routes introduced across the window:
/api-keys(first-class cross-tenant list),/reservations(runtime-plane force-release),/tenants/:id(detail with parent/children breadcrumb),/webhooks/:id(single subscription detail). - Global command palette:
Cmd/Ctrl+Kor/opens a tenant search (3-page prefetch, 60s cache, substring filter, "Load more" for scale).
v0.1.25 — April 2026 (Initial release)
Pillar 4: Events & Webhooks (Observability Plane)
New event-driven observability system spanning all three services.
Protocol spec (non-breaking, guidance only):
- Added WEBHOOK EVENT GUIDANCE section documenting 41 event types, payload schema, delivery protocol, and X-Cycles-Signature HMAC verification
- No new API endpoints — guidance is informational, not normative
Admin server (20 new endpoints):
- 12 admin webhook/event endpoints at
/v1/admin/webhooksand/v1/admin/events - 8 tenant self-service endpoints at
/v1/webhooksand/v1/events(requireswebhooks:read/write,events:read) GET/PUT /v1/admin/config/webhook-security— SSRF protection with blocked CIDR ranges- 41 event types across 6 categories: budget (16), reservation (5), tenant (6), api_key (6), policy (3), system (5)
- Event emission wired into all existing controllers
Runtime server (event emission):
reservation.deniedon DENY decision (reserve and decide endpoints)reservation.commit_overageon commit with actual > estimated
Events delivery service (cycles-server-events, internal app port 7980):
- Async webhook delivery via BLMOVE claim/ack from the shared Redis dispatch queue (BRPOP before v0.1.25.18)
- HMAC-SHA256 payload signing (
X-Cycles-Signature: sha256=<hex>) - Exponential backoff retry, auto-disable after consecutive failures
- Stale delivery protection (>24h deliveries auto-fail on pickup)
Security:
- AES-256-GCM encryption for signing secrets at rest (
WEBHOOK_SECRET_ENCRYPTION_KEYenv var) - Webhook URL SSRF protection: private IPs blocked by default, HTTPS required in production
Data retention:
- Event keys: 90-day Redis TTL (configurable via
EVENT_TTL_DAYS) - Delivery keys: 14-day Redis TTL (configurable via
DELIVERY_TTL_DAYS) - ZSET index cleanup: hourly via RetentionCleanupService
Testing: 530 tests across 3 services (events: 114, admin: 319, server: 97), all 95%+ coverage. Full-stack E2E test (23 assertions) verified across all services via Docker.
v0.1.24 — March 2026
Migration required — default overage policy changed
The default commit_overage_policy changed from REJECT to ALLOW_IF_AVAILABLE. If you relied on REJECT as the implicit default, reservations and commits that previously failed will now succeed and may allow overspend. To preserve the previous behavior, explicitly set overagePolicy = "REJECT" on your decorators/annotations, or update tenant defaults via PATCH /v1/admin/tenants/{id} with "default_commit_overage_policy": "REJECT".
Protocol (breaking):
- Default overage policy changed from
REJECTtoALLOW_IF_AVAILABLE ALLOW_IF_AVAILABLEcommits now always succeed: when remaining budget can't cover the full overage delta, the charge is capped to estimate + available remaining andis_over_limitis set to block future reservationsis_over_limitextended to also cover cappedALLOW_IF_AVAILABLEcommitsCommitResponse.chargedmay now be less thanactualwhen overage is cappedEventCreateResponsenow includes optionalchargedfield (present whenALLOW_IF_AVAILABLEcaps the charge to remaining budget)- Three new error codes:
BUDGET_FROZEN(409),BUDGET_CLOSED(409),MAX_EXTENSIONS_EXCEEDED(409) — error code count increased from 12 to 15
Server:
- Updated commit Lua script with capped-delta logic for
ALLOW_IF_AVAILABLE - Updated default fallback in reservation and commit paths from
REJECTtoALLOW_IF_AVAILABLE
Admin Server:
- Default tenant
default_commit_overage_policychanged fromREJECTtoALLOW_IF_AVAILABLE - New endpoint:
PATCH /v1/admin/budgets?scope=&unit=— updateoverdraft_limit,commit_overage_policy, andmetadataon existing budgets with atomicis_over_limitrecalculation - Budget fund and patch endpoints now use query parameters (
?scope=...&unit=...) instead of path variables for consistency with the balances API - New endpoint:
PATCH /v1/admin/policies/{policy_id}— update all mutable policy fields (name, description, priority, caps, overage policy, TTL override, rate limits, effective dates, status). Note: policy runtime enforcement is deferred to a future version; policies are stored but not yet evaluated by the protocol server - Tenant update extended with
default_reservation_ttl_ms,max_reservation_ttl_ms, andmax_reservation_extensions— reservation TTL is now configurable per-tenant - Budget metadata support on create and update
v0.1.23 — March 2026
Protocol:
- Complete OpenAPI 3.1.0 specification
- 9 protocol endpoints: decide, reserve, list, get, commit, release, extend, balances, events
- 4 unit types: USD_MICROCENTS, TOKENS, CREDITS, RISK_POINTS
- 3 overage policies: REJECT, ALLOW_IF_AVAILABLE, ALLOW_WITH_OVERDRAFT
- Subject hierarchy: tenant, workspace, app, workflow, agent, toolset
- Dry-run mode for shadow evaluation
- Reservation TTL with grace period and extend
- Idempotent operations with per-endpoint scoping
- 12 error codes with structured error responses
- Caps and three-way decision model (ALLOW, ALLOW_WITH_CAPS, DENY)
- Debt and overdraft model
Server:
- Spring Boot 3.5 + Java 21 runtime
- Redis 7+ with Lua scripts for atomic operations
- Docker images on GHCR
- Health check endpoint (Spring Boot Actuator)
- Request ID generation and tracking
Admin Server:
- Tenant lifecycle management (ACTIVE, SUSPENDED, CLOSED)
- API key management with granular permissions
- Budget ledger CRUD with funding operations (CREDIT, DEBIT, RESET, REPAY_DEBT)
- Policy management with scope patterns
- Audit logging
- Cursor-based pagination
Client SDKs:
- Python:
runcycles— decorator, programmatic, and async APIs - TypeScript:
runcycles— withCycles HOF, reserveForStream, programmatic APIs - Java/Spring:
cycles-client-java-spring— @Cycles annotation with SpEL expressions
Integrations:
- OpenAI, Anthropic, LangChain (Python)
- OpenAI, Anthropic, LangChain.js, Vercel AI SDK, AWS Bedrock, Google Gemini, Express (TypeScript)
- Spring AI (Java)
- OpenClaw agent framework (TypeScript)
Version compatibility
| SDK / Component | Version | Compatible server |
|---|---|---|
runcycles (Python) | 0.5.2 | v0.1.23+, v0.1.24+, v0.1.25+; server-authoritative heartbeat on runtime 0.1.25.59+ |
runcycles (TypeScript) | 0.4.2 | v0.1.23+, v0.1.24+, v0.1.25+; server-authoritative heartbeat on runtime 0.1.25.59+ |
runcycles (Rust) | 0.3.2 | v0.1.23+, v0.1.24+, v0.1.25+; server-authoritative heartbeat on runtime 0.1.25.59+ |
cycles-client-java-spring | 0.3.2 | v0.1.23+, v0.1.24+, v0.1.25+; server-authoritative heartbeat on runtime 0.1.25.59+ |
@runcycles/mcp-server | 0.6.1 | v0.1.23+, v0.1.24+, v0.1.25+ |
@runcycles/openclaw-budget-guard | 0.9.0 | v0.1.23+, v0.1.24+, v0.1.25+ |
| Cycles Budget Guard for Claude Code | 0.2.0 | v0.1.23+, v0.1.24+, v0.1.25+; companion MCP server 0.6.0 |
| Cycles Server (runtime) | v0.1.25.59 | Protocol v0.1.25 (runtime revision v0.1.25.16) plus CyclesEvidence v0.2.2 signer-authority layer |
| Cycles Admin Server | v0.1.25.55 | Governance spec v0.1.25.42 |
| Cycles Events Service | v0.1.25.25 | Shared Redis dispatch queue plus CyclesEvidence signing queue |
| Cycles Dashboard | v0.1.25.85 | Admin v0.1.25.42 for current governance views; runtime v0.1.25.37+ for reservation evidence links; events v0.1.25.14+ for signed evidence |
The four language-client versions in the compatibility table are backward-compatible with server v0.1.23. Integration packages can require a newer client or server feature; follow each package's release notes. New v0.1.24 features (budget patch, policy patch, capped ALLOW_IF_AVAILABLE commits) require server v0.1.24+. New v0.1.25 features (event emission, webhook delivery, events service, policy_id / deny_detail on reservation.denied) require server v0.1.25.
Minimum versions for specific features
| Feature | Minimum component |
|---|---|
Server-authoritative remaining_ttl_ms heartbeat scheduling | cycles-server v0.1.25.59 (runtime spec revision v0.1.25.16); Python 0.5.2, TypeScript 0.4.2, Rust 0.3.2, or Spring Boot starter 0.3.2 |
| Durable known-actual settlement recovery profile 0.3 | Python 0.5.2, TypeScript 0.4.2, Rust 0.3.2, or Spring Boot starter 0.3.2 |
| Dashboard Evidence viewer and reservation "View evidence" links | cycles-dashboard v0.1.25.63+; cycles-server v0.1.25.37+ for include=evidence; cycles-server-events v0.1.25.14+ to sign envelopes |
| Unconfigured CyclesEvidence disabled mode (no queueing or dead-lettering when identity is blank) | cycles-server v0.1.25.38, cycles-server-events v0.1.25.15 |
Tenant-close cascade + TENANT_CLOSED (409) on the admin plane + 4 _via_tenant_cascade event kinds | cycles-server-admin v0.1.25.35 (initial Mode B cascade) / v0.1.25.36 (full Rule 2 guard coverage); cycles-dashboard v0.1.25.43 (tombstone + cascade preview UI); governance-admin spec v0.1.25.29 / .30 / .31 |
TENANT_CLOSED (409) runtime guard on reservation create/commit/release/extend + reason_code=TENANT_CLOSED on fresh dry-run / /v1/decide DENYs | cycles-server v0.1.25.47 (runtime spec v0.1.25.13) |
W3C Trace Context (trace_id on responses + audit/events filter) | cycles-server v0.1.25.14, cycles-server-admin v0.1.25.31, cycles-server-events v0.1.25.7, cycles-dashboard v0.1.25.39 |
Runtime audit-log retention TTL (AUDIT_RETENTION_DAYS) | cycles-server v0.1.25.15 |
| Events service management port split (9980) | cycles-server-events v0.1.25.9 |
Bulk-action audit metadata enrichment (succeeded_ids, failed_rows, filter, duration_ms) | cycles-server-admin v0.1.25.30 |
Budget bulk-action endpoint (POST /v1/admin/budgets/bulk-action) | cycles-server-admin v0.1.25.29, cycles-dashboard v0.1.25.35 |
Audit tenant sentinel split (__admin__ / __unauth__) | cycles-server-admin v0.1.25.28 |
Audit log filter DSL (error_code_exclude, status_min/max, array operation/resource_type) | cycles-server-admin v0.1.25.27, cycles-dashboard v0.1.25.33 |
Sorted pagination on GET /v1/reservations (sort_by, sort_dir) | cycles-server v0.1.25.12 |
Admin-on-behalf-of release (X-Admin-API-Key on /v1/reservations/{id}/release) | cycles-server v0.1.25.8 |
| Bulk-action endpoints on tenants + webhooks | cycles-server-admin v0.1.25.26 |
Free-text search on admin list endpoints | cycles-server-admin v0.1.25.25 |
| Server-side sort on admin list endpoints | cycles-server-admin v0.1.25.24 |
| Cross-tenant list for API keys + budgets | cycles-server-admin v0.1.25.22 |
| Failed-request audit capture + tiered TTL | cycles-server-admin v0.1.25.20 |
RESET_SPENT funding operation | cycles-server-admin v0.1.25.18 |
| Dual-auth on tenant webhook endpoints | cycles-server-admin v0.1.25.16 |
| Webhook delivery via HTTP/1.1 (h2c fix) | cycles-server-events v0.1.25.5 |
For detailed API changes, see the Cycles Protocol specification.