Skip to content

Releases: runcycles/cycles-server

v0.1.25.18 — drop tomcat override (SB 3.5.14 BOM-managed) + Jedis fleet alignment

26 Apr 13:31
abc7d1c

Choose a tag to compare

[benchmark-skip]

Dependency hygiene release. No application-level code or wire-format changes — pom-only patch. Benchmark gate bypassed because a dep-only release exercises only environmental noise on the perf path (precedent: v0.1.25.9, .10, .11).

Changed

  • Spring Boot 3.5.13 → 3.5.14. Patch upgrade picking up upstream security hardening (constant-time DevTools secret comparison, RandomValuePropertySource SecureRandom, consistent SSL hostname verification, ApplicationPidFileWriter/ApplicationTemp symlink fixes).
  • Drop <tomcat.version>10.1.54</tomcat.version> override. SB 3.5.14's BOM now manages Tomcat 10.1.54 directly (verified against spring-boot-dependencies-3.5.14.pom). The explicit pin from v0.1.25.16 (closing CVE-2026-34483 / CVE-2026-34487) is now redundant.
  • Jedis 7.4.1 → 6.2.0. Aligns with cycles-server-events (6.2.0) and cycles-server-admin (6.2.0) on a single Redis-client major across the fleet, simplifying coordinated dependency upgrades. All 152 tests pass on 6.2.0.
  • commons-lang3 3.18.0 override retained — SB 3.5.14's BOM still manages 3.17.0 (CVE-2025-48924 unfixed there). Comment updated to reference SB 3.5.14.

See CHANGELOG.md for the full entry.

Fleet alignment

Matching releases: cycles-server-events v0.1.25.12, cycles-server-admin v0.1.25.41.

v0.1.25.17 — supply-chain CVE fix (commons-lang3 CVE-2025-48924)

22 Apr 13:01
646d09f

Choose a tag to compare

v0.1.25.17 — supply-chain CVE fix (commons-lang3 CVE-2025-48924) [benchmark-skip]

Closes CVE-2025-48924 (Trivy HIGH) on the commons-lang3-3.17.0 jar
that ships transitively in the fat-jar image via swagger-core-jakarta
(OpenAPI UI). Spring Boot 3.5.13's BOM manages commons-lang3 at
3.17.0; the override is removable once Spring Boot ships a managed
version of 3.18.0+.

Fixed (security)

  • Pin commons-lang3.version=3.18.0 in
    cycles-protocol-service/pom.xml.

Notes

  • No code, API, or Lua-script changes. Benchmark gate skipped — the bump
    is in a non-hot-path transitive dep and benchmark deltas would only
    measure environmental noise.
  • All 152 tests pass.
  • Follow-on to v0.1.25.16 (spring-boot-starter-parent 3.5.11 → 3.5.13
    • tomcat.version=10.1.54 pin, closing five HIGH/CRITICAL CVEs in
      spring-security-web and tomcat-embed-core). .16 was a
      pom-version-only bump with no standalone GitHub release; its changes
      ship cumulatively in .17.

Wire format

Unchanged from v0.1.25.15. Clients and dashboard on .15+ remain
fully compatible.

v0.1.25.15 — audit-log retention TTL (rolls up v0.1.25.14 trace_id correlation)

18 Apr 23:37
f5a5773

Choose a tag to compare

Rolled-up release covering both v0.1.25.14 (W3C Trace Context cross-surface correlation) and v0.1.25.15 (audit-log retention TTL fix). Cumulative changelog below; per-version detail in CHANGELOG.md.


v0.1.25.15 — Fixed

  • Runtime-written audit-log entries now respect a configurable retention TTL (default 400 days). Previously, AuditRepository.log() wrote audit:log:{id} keys with no EXPIRE, so runtime-written rows persisted indefinitely until Redis eviction — silently failing to participate in the 400-day retention tier the admin plane applies to authenticated audit rows. Matches cycles-server-admin's audit.retention.authenticated.days=400. Runtime never writes the admin-plane __admin__ / __unauth__ sentinels, so a single tier suffices.

v0.1.25.15 — Added

  • audit.retention.days config (default 400, env AUDIT_RETENTION_DAYS). Set to 0 for indefinite retention (legal hold, archive-store deployments).
  • audit.sweep.cron config (default 0 0 3 * * *, env AUDIT_SWEEP_CRON). Daily @Scheduled sweep prunes stale audit:logs:{tenantId} and audit:logs:_all ZSET pointers whose target audit:log:{id} key has TTL-expired. Self-contained and idempotent — safe to run alongside admin's sweep.

v0.1.25.15 — Internal

  • AuditRepository.LOG_AUDIT_LUA now reads ARGV[4] as an optional TTL in seconds (0 or negative = no EX). Same shape as admin's script, minus the sentinel branching.

v0.1.25.14 — Added

  • W3C Trace Context correlation per cycles-protocol-v0.yaml revision 2026-04-18. Every response now carries an X-Cycles-Trace-Id header. The server accepts a traceparent (W3C version 00) or X-Cycles-Trace-Id header on inbound requests and echoes back the same trace_id; when neither is present it generates a fresh 128-bit id (32 lowercase hex). Malformed headers are silently ignored.
  • trace_id field on ErrorResponse, Event, WebhookDelivery, and AuditLogEntry bodies. Optional for wire back-compat; conformant servers populate it on every payload causally downstream of the request.
  • trace_flags (^[0-9a-f]{2}$) and traceparent_inbound_valid (boolean) on WebhookDelivery per governance-admin spec v0.1.25.28. These preserve the upstream W3C sampling decision so the events sidecar can reconstruct an outbound traceparent with the correct trace-flags byte instead of defaulting to 01.
  • SLF4J MDC now carries traceId alongside requestId for every request.
  • ReservationExpiryService mints a fresh trace_id per sweep batch so reservation.expired events emitted in the same sweep correlate to each other.

v0.1.25.14 — Internal

  • New TraceContextFilter (@Order(0)) runs before RequestIdFilter and sets the cyclesTraceId request attribute for downstream code.
  • EventEmitterService.emit(...) gains a final String traceId parameter (full-arity emitBalanceEvents(...) likewise). Three prior overloads kept as delegating wrappers (traceId = null) for source compatibility.
  • BaseController exposes protected resolveRequestId and resolveTraceId helpers.

Compatibility

  • Wire-additive only — no breaking changes for existing clients. All new fields are optional.
  • No DB migration required.
  • Backward TTL behavior: rows written before v0.1.25.15 stay un-TTL'd until Redis memory pressure evicts them; new writes get the configured TTL.

v0.1.25.13 — hydration cap + enum wire annotations

17 Apr 01:52
c5ffb08

Choose a tag to compare

v0.1.25.13 — hydration cap + enum wire annotations

Two defensive fixes on the v0.1.25.12 sorted-list feature, ported from cycles-server-admin v0.1.25.24. No spec change, no wire-format change, no hot-path performance impact.

Fixed

  • P1 — listReservationsSorted hydration cap. The sorted path no longer hydrates an unbounded reservation population before the in-memory sort. SORTED_HYDRATE_CAP = 2000 mirrors the admin-plane pattern: a labeled break exits the SCAN loop once matching.size() >= cap, a WARN is logged naming the tenant + sort tuple, and the downstream sort/slice/cursor path operates on the capped slice. Page still fills, has_more + next_cursor still populate — the cap is a heap-safety bound, not a correctness bound. Legacy no-sort-params path intentionally uncapped (it streams page-by-page via the SCAN cursor).
  • P2 — Jackson wire annotations on ReservationSortBy + SortDirection. Both enums now carry @JsonValue getWire() + @JsonCreator fromWire(String) matching the admin plane's SortSpec/SortDirection contract. Wire form stays lowercase, parsing stays case-insensitive, null → null. Controller-level validation unchanged: unknown tokens still surface as HTTP 400 INVALID_REQUEST.

Operator guidance

Callers that outgrow the 2000-row cap should narrow filters (status, idempotency_key, scope segments: workspace/app/workflow/agent/toolset). The longer-term path is the deferred per-tenant ZSET index ADR at docs/deferred-optimizations/sorted-list-zset-indices.md, scheduled when a tenant crosses ~10k active reservations.

Tests

  • RedisReservationQueryTest#sortedHydrationStopsAtCap — mocks SCAN page with cap + 10 keys, asserts exactly 5 rows in ascending created_at_ms order and has_more=true, next_cursor != null.
  • EnumsTest (new, cycles-protocol-service-model) — 12 tests covering getWire lowercase emission, fromWire canonical+case-insensitive parsing, null pass-through, IllegalArgumentException on unknown tokens, round-trip identity.
  • Full build green: 358 (data) + 137 (api) = all tests passing; JaCoCo ≥ 95%.

Backward compatibility

Full. Behaviour-visible only for tenants whose sorted-list query previously returned >2000 matching rows — those rows beyond row 2000 in the capped slice are now unreachable without narrowing filters. Same trade-off the admin plane established in v0.1.25.24.

Docker image

ghcr.io/runcycles/cycles-server:0.1.25.13

[benchmark-skip] — no hot-path write changes. Reserve / commit / release / extend / decide / event paths byte-identical to v0.1.25.12. Sorted-list read path adds a bounds check (1 comparison per hydrated row) with no observable latency impact. Full benchmark sweep scheduled when the deferred ZSET optimization lands.

v0.1.25.12 sort_by + sort_dir on GET /v1/reservations

17 Apr 00:00
93f26c8

Choose a tag to compare

What's Changed

  • feat(reservations): sort_by + sort_dir on GET /v1/reservations (v0.1.25.12) by @amavashev in #104

Implements cycles-protocol-v0.yaml revision 2026-04-16. GET /v1/reservations now accepts optional sort_by (reservation_id, tenant, scope_path, status, reserved, created_at_ms, expires_at_ms) and sort_dir (asc, desc — default desc). Invalid enum values return HTTP 400 INVALID_REQUEST. The cursor binds the (sort_by, sort_dir, filters) tuple; mismatched reuse returns 400.

Wire format: backward compatible. Clients omitting both params see byte-identical behaviour to v0.1.25.11 (legacy Redis-SCAN cursor path preserved).

[benchmark-skip]

Sorted path is opt-in and the legacy path is byte-identical. Benchmarks would only measure environmental noise, so the release gate is bypassed per the documented convention.

Full Changelog: v0.1.25.11...v0.1.25.12

v0.1.25.11 concurrent idempotency + counter-accuracy tests

14 Apr 20:12
ba820b7

Choose a tag to compare

What's Changed

  • test(v0.1.25.11): concurrent idempotency + counter-accuracy tests by @amavashev in #99

Closes two gaps flagged in the v0.1.25.10 review:

  • Thundering-herd retry on expired idempotency cache — fires 10 concurrent retries with the same idempotency key after cache expiry, asserts exactly one reservation is created and metric tags split correctly (1 × reason=OK + 9 × reason=IDEMPOTENT_REPLAY).
  • Concurrent custom-counter accuracy — asserts cycles.reservations.reserve counter is accurate under 8-thread × 10-request load with zero lost increments.

Both are regression gates rather than live bug fixes — correctness holds today because Redis Lua execution is single-threaded and Micrometer counters use lock-free AtomicLong internally. Without these tests, a future refactor (idempotency logic moving out of Lua into Java; tag-building aspect with shared state) could silently violate those guarantees.

Wire format unchanged vs v0.1.25.10 — no client changes required.

Full Changelog: v0.1.25.10...v0.1.25.11

v0.1.25.10 custom Micrometer counters + Redis-disconnect test

14 Apr 19:28
53cb43b

Choose a tag to compare

What's Changed

  • feat(metrics): v0.1.25.10 — custom Micrometer counters + Redis-disconnect test by @amavashev in #97

Adds seven domain-level counters under the cycles_* namespace (reserve, commit, release, extend, expired, events, overdraft.incurred) so operators can answer "how many denials in the last 5 minutes by reason and tenant" without reverse-engineering it from HTTP status codes. New RedisDisconnectResilienceIntegrationTest pauses its Testcontainers Redis mid-request to prove the service fails cleanly and recovers.

Dormant bug fixed: ReservationExpiryService.emitExpiredEvent used the wrong Redis key prefix, silently no-op'ing on every expiry since v0.1.25.3. Surfaced immediately by the new cycles.reservations.expired counter test.

Wire format unchanged vs v0.1.25.9 — no request/response body changes.

Full Changelog: v0.1.25.9...v0.1.25.10

v0.1.25.8 — admin-on-behalf-of dual-auth on reservations

13 Apr 19:42
7625235

Choose a tag to compare

Stage 2 of the dashboard ops gap rollout. Admin operators can now read and force-release reservations via the existing endpoints without a tenant API key — closes the runtime-plane side of the gap.

Spec alignment

Implements cycles-protocol revision 2026-04-13 + audit-discoverability clarification. All NORMATIVE requirements verified by contract tests against `cycles-protocol@main`.

Endpoints with new dual-auth

  • `GET /v1/reservations` (listReservations) — admin: `tenant` query param REQUIRED as filter; 400 if missing
  • `GET /v1/reservations/{id}` (getReservation) — admin bypasses tenant ownership; reservation_id pins the owner
  • `POST /v1/reservations/{id}/release` (releaseReservation) — admin force-release + audit-log write

`create` / `commit` / `extend` remain ApiKeyAuth-only by design.

Admin audit-log writing

On admin-driven release, the server writes an audit-log entry with `metadata.actor_type=admin_on_behalf_of` to the store that the governance plane's `GET /v1/admin/audit/logs` reads from — so admin-driven releases surface in the dashboard's existing Audit view without any cross-service plumbing. User-controlled `reason` field is CR/LF-sanitized before recording to prevent log-line forgery.

Tenant-self-service releases do NOT write audit entries (audit remains admin-action-focused, matching the governance plane's existing pattern).

New env var

`ADMIN_API_KEY=` — same value cycles-server-admin uses. Unset → X-Admin-API-Key on allowlisted paths returns 500 with a clear "server misconfiguration" message.

Tests

125/125 pass (was 105; +20 net) — filter admit/reject coverage, controller branching, audit entry verification, CR/LF sanitization regression, fall-through for non-allowlisted admin paths. Contract validation passes against post-merge spec. JaCoCo coverage check passes.

Image

`ghcr.io/runcycles/cycles-server:0.1.25.8` (also `:latest`).

Downstream

Stage 2.3: dashboard PR adding a ReservationsView that surfaces these endpoints. Depends on this image being published.

v0.1.25.7 typed Enums.ReasonCode + flaky EventEmitterServiceTest fix

11 Apr 12:00
0359f7a

Choose a tag to compare

What's Changed

  • refactor: type DecisionResponse / ReservationCreateResponse reason_code as Enums.ReasonCode by @amavashev in #83
  • test: de-flake EventEmitterServiceTest by replacing Thread.sleep with Mockito timeout/after by @amavashev in #82

Closes #81. Companion spec PR: runcycles/cycles-protocol#26.

Two quality improvements bundled, no wire-format change — JSON output is byte-identical to v0.1.25.6 on every response.

Typed Enums.ReasonCode refactor (#83): DecisionResponse.reasonCode and ReservationCreateResponse.reasonCode retyped from free-form String to a typed enum with 6 values (BUDGET_EXCEEDED, BUDGET_FROZEN, BUDGET_CLOSED, BUDGET_NOT_FOUND, OVERDRAFT_LIMIT_EXCEEDED, DEBT_OUTSTANDING). Mirrors the DecisionReasonCode schema added to cycles-protocol-v0.yaml in runcycles/cycles-protocol#26. Jackson serializes enum to name(), so JSON output is byte-identical to v0.1.25.6. Compile-time safety against drift from the spec enum.

De-flaked EventEmitterServiceTest (#82): 13 Thread.sleep(200) + verify() race-condition patterns replaced with Mockito timeout(5000) / after(200).never() verification modes. Test-only, no runtime impact.

No spec bump required. The companion DecisionReasonCode enum in runcycles/cycles-protocol#26 is forward-compatible regardless of merge order.

Full Changelog: v0.1.25.6...v0.1.25.7

v0.1.25.6 distinguish UNIT_MISMATCH from BUDGET_NOT_FOUND on reserve/event/decide

10 Apr 23:36
89d2651

Choose a tag to compare

What's Changed

  • fix: distinguish UNIT_MISMATCH from BUDGET_NOT_FOUND on reserve/event/decide by @amavashev in #80

Closes #79. Addresses runcycles/cycles-client-rust#8.

reserve.lua, event.lua, and the Java-side decide / evaluateDryRun paths now probe alternate units when no budget is found at the requested unit. If a budget exists at the scope in a different unit, returns 400 UNIT_MISMATCH with {scope, requested_unit, expected_units} in details so clients can self-correct. 404 BUDGET_NOT_FOUND remains for the truly-missing case.

No hot-path change — the probe fires only on the empty-budgeted-scopes error path. 291 tests / 0 failures / jacoco coverage checks met.

Companion spec update: runcycles/cycles-protocol#25 (broadens normative UNIT_MISMATCH wording, documents 404 on reserve + event endpoints).

Full Changelog: v0.1.25.5...v0.1.25.6