|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to `cycles-server` are recorded here. Format follows [Keep a |
| 4 | +Changelog](https://keepachangelog.com/en/1.1.0/); versions use |
| 5 | +[Semantic-ish Versioning](https://semver.org/) with a fourth "patch-of-patch" |
| 6 | +segment for same-day follow-ups. |
| 7 | + |
| 8 | +This file is for **downstream consumers** — people pulling the Docker image or |
| 9 | +JAR. For internal engineering history (root cause analyses, rejected |
| 10 | +alternatives, test-strategy decisions) see [`AUDIT.md`](AUDIT.md). |
| 11 | + |
| 12 | +Wire format is considered stable within a minor version (`0.1.x`). Breaking |
| 13 | +changes to request/response bodies or Lua-script semantics would require a |
| 14 | +minor bump. "Internal signature changes" (e.g. Java method parameters) are |
| 15 | +called out but are not breaking to API clients. |
| 16 | + |
| 17 | +## [0.1.25.10] — 2026-04-14 |
| 18 | + |
| 19 | +### Added |
| 20 | + |
| 21 | +- Seven domain-level Prometheus counters under the `cycles_*` namespace. See |
| 22 | + [`OPERATIONS.md`](OPERATIONS.md) for the full list, tag semantics, and |
| 23 | + alerting recipes. Short summary: |
| 24 | + - `cycles_reservations_reserve_total` |
| 25 | + - `cycles_reservations_commit_total` |
| 26 | + - `cycles_reservations_release_total` |
| 27 | + - `cycles_reservations_extend_total` |
| 28 | + - `cycles_reservations_expired_total` |
| 29 | + - `cycles_events_total` |
| 30 | + - `cycles_overdraft_incurred_total` |
| 31 | +- Configuration flag `cycles.metrics.tenant-tag.enabled` (default `true`) — |
| 32 | + set to `false` in deployments with many thousands of tenants to keep |
| 33 | + Prometheus cardinality bounded. |
| 34 | +- `RedisDisconnectResilienceIntegrationTest` — exercises the paused-Redis |
| 35 | + failure mode end-to-end and guards against silent-failure regressions. |
| 36 | + |
| 37 | +### Fixed |
| 38 | + |
| 39 | +- `ReservationExpiryService` was silently no-op'ing event emission on every |
| 40 | + expiry since v0.1.25.3 because it looked up the wrong Redis key prefix |
| 41 | + (`reservation:<id>` instead of `reservation:res_<id>`). The |
| 42 | + `reservation.expired` webhook now actually fires. If your downstream |
| 43 | + webhook consumer assumed expiries would never emit an event, update it |
| 44 | + before upgrading. |
| 45 | + |
| 46 | +### Wire format |
| 47 | + |
| 48 | +Unchanged. Upgrading from v0.1.25.9 requires no client changes. |
| 49 | + |
| 50 | +### Notes for upgraders |
| 51 | + |
| 52 | +- New counters appear on your next Prometheus scrape. No config change |
| 53 | + needed to emit them; they are on by default. |
| 54 | +- The `reservation.expired` webhook fix will start delivering events you |
| 55 | + weren't receiving before. Confirm your webhook endpoint handles them. |
| 56 | + |
| 57 | +## [0.1.25.9] — 2026-04-14 |
| 58 | + |
| 59 | +### Added |
| 60 | + |
| 61 | +- Seven new test classes landing the second-wave coverage plan |
| 62 | + (overdraft property tests, `expire.lua` direct conformance, admin-release |
| 63 | + vs agent-commit race, multi-scope attribution under contention, |
| 64 | + idempotency-cache expiry, clock-skew resilience, audit-log completeness). |
| 65 | + See [`AUDIT.md`](AUDIT.md) for the full strategy. |
| 66 | + |
| 67 | +### Wire format |
| 68 | + |
| 69 | +Unchanged. Test-only release. |
| 70 | + |
| 71 | +## [0.1.25.8] — 2026-04-13 |
| 72 | + |
| 73 | +### Added |
| 74 | + |
| 75 | +- **Admin-on-behalf-of release** (spec revision 2026-04-13). Dual-auth |
| 76 | + endpoint: `POST /v1/reservations/{id}/release` now accepts either a |
| 77 | + tenant `X-Cycles-API-Key` or a server-configured `X-Admin-API-Key` via |
| 78 | + `AdminApiKeyAuthenticationFilter`. Admin-driven releases write an audit |
| 79 | + entry with `metadata.actor_type=admin_on_behalf_of` to the shared |
| 80 | + `audit:log:*` Redis store, surfacing in the governance dashboard. |
| 81 | +- `admin.api-key` application property for configuring the admin key. |
| 82 | + |
| 83 | +### Security |
| 84 | + |
| 85 | +- Admin key comparison uses `MessageDigest.isEqual` (constant-time on |
| 86 | + equal-length inputs). Deployments should rotate to fixed-length keys. |
| 87 | +- CR/LF injection guarded in audit-log `reason` field. |
| 88 | + |
| 89 | +## [0.1.25.7] — 2026-04-11 |
| 90 | + |
| 91 | +### Added |
| 92 | + |
| 93 | +- `Enums.ReasonCode` as a typed enum (previously stringly-typed on the |
| 94 | + wire). Drop-in Jackson round-trip-compatible with existing clients. |
| 95 | + |
| 96 | +### Fixed |
| 97 | + |
| 98 | +- Flaky `EventEmitterServiceTest` on CI. Replaced `Thread.sleep(200)` + |
| 99 | + `verify()` racing with `Mockito.timeout()` / `after()`. |
| 100 | + |
| 101 | +## [0.1.25.6] — 2026-04-10 |
| 102 | + |
| 103 | +### Changed |
| 104 | + |
| 105 | +- Reserve/event/decide now distinguish `UNIT_MISMATCH` from |
| 106 | + `BUDGET_NOT_FOUND`. Previously a wrong-unit request surfaced as "budget |
| 107 | + not found" which was misleading when the scope had a budget under a |
| 108 | + different unit. The scripts now probe alternate units and emit |
| 109 | + `UNIT_MISMATCH` with the set of configured units. |
| 110 | + |
| 111 | +## [0.1.25.5] — 2026-04-08 |
| 112 | + |
| 113 | +### Fixed |
| 114 | + |
| 115 | +- Duplicate emission of budget-state transition events |
| 116 | + (`budget.approaching_limit`, `budget.at_limit`, |
| 117 | + `budget.over_limit`, `debt.incurred`) on multi-scope operations. Closes |
| 118 | + `cycles-server-events#15`. |
| 119 | + |
| 120 | +## [0.1.25.4] — 2026-04-07 |
| 121 | + |
| 122 | +### Changed |
| 123 | + |
| 124 | +- Event-data payloads now include all fields the webhook consumers |
| 125 | + require for correct dedup/ordering (`reservation_id`, `scope`, `unit`, |
| 126 | + `actor`, timestamps). Previously some events arrived with missing |
| 127 | + fields that forced webhook consumers to re-query the server. |
| 128 | + |
| 129 | +## [0.1.25.3] — 2026-04-03 |
| 130 | + |
| 131 | +### Added |
| 132 | + |
| 133 | +- Runtime event emission: `reservation.reserved`, `reservation.committed`, |
| 134 | + `reservation.released`, `reservation.expired`, `reservation.extended`, |
| 135 | + `event.applied`, and the budget-state transitions. Events land on a |
| 136 | + Redis stream consumed by `cycles-server-events` for webhook fan-out. |
| 137 | +- `PROTOCOL_VERSION` constant correctly set to `v0.1.25`. |
| 138 | + |
| 139 | +## [0.1.25.2] — 2026-04-02 |
| 140 | + |
| 141 | +### Fixed |
| 142 | + |
| 143 | +- `getBalances` and `listReservations` now lowercase the stored scope |
| 144 | + before segment matching, so operator-curated budgets with mixed-case |
| 145 | + scope paths are findable. Writes were already lowercase; this was a |
| 146 | + read-side defensive fix. Closes |
| 147 | + `cycles-openclaw-budget-guard#70`, `cycles-server-admin#54`. |
| 148 | + |
| 149 | +## [0.1.25.1] — 2026-04-01 |
| 150 | + |
| 151 | +### Added |
| 152 | + |
| 153 | +- Webhook event emission from the runtime server. Reserve/commit/release/ |
| 154 | + extend and decide now emit events to the shared Redis dispatch queue |
| 155 | + for `cycles-server-events` to fan out to configured subscribers. |
| 156 | + Events include `reservation.denied` (on DENY decisions) and |
| 157 | + `reservation.commit_overage` (when committed `actual > estimate`). |
| 158 | +- `EventEmitterService` with async, non-blocking emission on a |
| 159 | + dedicated daemon thread pool (`CompletableFuture.runAsync`). The |
| 160 | + request thread never waits on event writes. |
| 161 | +- TTL retention: event keys expire after 90 days, delivery keys after |
| 162 | + 14 days. Configurable via `EVENT_TTL_DAYS` / `DELIVERY_TTL_DAYS`. |
| 163 | + |
| 164 | +### Performance |
| 165 | + |
| 166 | +- Event save (SET + EXPIRE + 2× ZADD) and subscription lookup |
| 167 | + (2× SMEMBERS) batched into one Redis pipeline round-trip (was 6 |
| 168 | + sequential). Near-zero overhead on non-event paths; commit p50 |
| 169 | + recovered from 13.4ms to 5.6ms after the fix below. |
| 170 | + |
| 171 | +### Fixed |
| 172 | + |
| 173 | +- Commit overage event was firing on every commit, not just true |
| 174 | + overages. Now emits only when `actual > estimateAmount`. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## Archive |
| 179 | + |
| 180 | +v0.1.x and earlier versions predating this changelog: see `AUDIT.md`. |
| 181 | + |
| 182 | +[0.1.25.10]: https://github.com/runcycles/cycles-server/compare/v0.1.25.9...v0.1.25.10 |
| 183 | +[0.1.25.9]: https://github.com/runcycles/cycles-server/compare/v0.1.25.8...v0.1.25.9 |
| 184 | +[0.1.25.8]: https://github.com/runcycles/cycles-server/compare/v0.1.25.7...v0.1.25.8 |
| 185 | +[0.1.25.7]: https://github.com/runcycles/cycles-server/compare/v0.1.25.6...v0.1.25.7 |
| 186 | +[0.1.25.6]: https://github.com/runcycles/cycles-server/compare/v0.1.25.5...v0.1.25.6 |
| 187 | +[0.1.25.5]: https://github.com/runcycles/cycles-server/compare/v0.1.25.4...v0.1.25.5 |
| 188 | +[0.1.25.4]: https://github.com/runcycles/cycles-server/compare/v0.1.25.3...v0.1.25.4 |
| 189 | +[0.1.25.3]: https://github.com/runcycles/cycles-server/compare/v0.1.25.2...v0.1.25.3 |
| 190 | +[0.1.25.2]: https://github.com/runcycles/cycles-server/compare/v0.1.25.1...v0.1.25.2 |
| 191 | +[0.1.25.1]: https://github.com/runcycles/cycles-server/releases/tag/v0.1.25.1 |
0 commit comments