Skip to content

Releases: runcycles/cycles-server

v0.1.25.48 — TENANT_CLOSED guard on POST /v1/events

Choose a tag to compare

@amavashev amavashev released this 11 Jul 16:17

Extends the tenant-close terminal-owner guard (TENANT_CLOSED) to the runtime event-emission endpoint.

Added

  • 409 TENANT_CLOSED on POST /v1/events when the owning tenant's status=CLOSED flip is durable. POST /v1/events is a persisting budget debit (post-only accounting — it directly mutates remaining/spent on every budgeted derived scope and may accrue overdraft debt under ALLOW_WITH_OVERDRAFT), so it is on the same runtime-plane persisting surface as the reservation mutations and is now guarded identically. The check runs inside event.lua (byte-identical to the reserve guard): after idempotency-replay handling and before any debit, so a pre-close same-key replay still returns its original stored response while a fresh event on a CLOSED tenant is rejected. Fail-closed on a malformed/undeterminable tenant record (500 INTERNAL_ERROR, no debit); no restriction when no tenant record exists (runtime-only deployments). Runtime spec revision: v0.1.25.14 (runcycles/cycles-protocol#128) adds POST /v1/events to the closed-tenant binding.
    • Exposure closed: narrower than the reservation surface — /v1/events is not on the admin-key allowlist, and tenant keys on a durably-closed tenant are already 401'd at the auth filter. The guard closes the remaining auth-check→script race window (Mode B invariant (a)).
    • No error-evidence: /v1/events is outside the evidence-endpoint set (an accounting endpoint, not a decision endpoint) — consistent with its other denial outcomes, which also emit none.

Compatibility

  • Additive. TENANT_CLOSED is a pre-existing ErrorCode; /v1/events already declared a 409. No wire, Redis, event, or evidence schema change; the existing handleScriptError mapping already routes TENANT_CLOSED to 409, so no Java change was required. Deployments without a governance plane are unaffected.
  • Release prep: prod + full-stack compose self-pin bumped 0.1.25.46 → 0.1.25.48 (the bundled stack had not been bumped since .46 and predated the tenant-close guards; upgrade recommended).

1,066 tests green including integration + contract validation against runtime spec v0.1.25.14; JaCoCo ≥95% on all modules; external review (codex) SHIP. Full details: CHANGELOG.md [0.1.25.48], AUDIT.md 2026-07-11 entries, PR #232.

v0.1.25.47 — TENANT_CLOSED Rule 2 guard + webhook matcher parity

Choose a tag to compare

@amavashev amavashev released this 10 Jul 18:55
eb6db8b

Governance conformance release: the runtime half of the tenant-close cascade (Rule 2 terminal-owner guard) plus webhook scope-filter matcher parity with the admin plane.

Added

  • 409 TENANT_CLOSED on the four reservation mutations (create, commit, release, extend) once the owning tenant's status=CLOSED flip is durable in the shared Redis. Implemented inside the reserve/commit/release/extend Lua scripts — atomic with the budget mutations and immune to the tenant-config cache TTL. Implements governance CASCADE SEMANTICS Rule 2 / Mode B invariant (a); runtime spec revision v0.1.25.13 (runcycles/cycles-protocol#125). Closes the two paths the auth layer could not: admin-on-behalf-of release on a closed tenant (previously succeeded; now 409) and the auth-check→script race window. Tenant-key requests on closed tenants keep their existing per-request 401.
  • Truthful non-persisting evaluations: a fresh dry_run create or POST /v1/decide on a CLOSED tenant returns 200 decision=DENY with reason_code=TENANT_CLOSED (new DecisionReasonCode value) instead of what could previously be a signed ALLOW attestation for a request whose live execution must fail. Cached pre-close replays keep their original payload.
  • Fail-closed governance reads: a present-but-malformed tenant record (undecodable JSON, non-object, missing status, or a status outside the closed ACTIVE|SUSPENDED|CLOSED enum) fails the request 500 INTERNAL_ERROR with no mutation. Absent tenant record (runtime-only deployments) ⇒ no guard, unchanged behavior.
  • Signed denial receipts: mutation-surface 409 TENANT_CLOSED (persisting create, commit, release) emits an error CyclesEvidence envelope and stamps cycles_evidence, like the other budget/lifecycle denial codes (declared in evidence ErrorResponseMirror, cycles-evidence-v0.2.yaml 0.2.1). TENANT_CLOSED takes precedence over RESERVATION_FINALIZED/RESERVATION_EXPIRED for non-replay attempts; same-key idempotent replays return their original response.

Fixed

  • Webhook scope_filter matching is now byte-identical to the admin plane's spec-conformant matcher (cycles-server-admin #206): a blank/whitespace event scope is treated as unscoped (excluded from every scope-filtered subscription — a bare * filter no longer matches a blank "" scope), and a trailing-/* filter requires a non-empty child segment (tenant:a/* no longer matches the degenerate tenant:a/). Both planes' matchers now pin the same test table.

Compatibility

  • Additive ErrorCode and DecisionReasonCode value (TENANT_CLOSED), per runtime spec v0.1.25.13. Deployments without a governance plane see no behavior change; closed tenants previously surfaced only as auth-layer 401s or budget-state errors.
  • Matcher change affects runtime webhook dispatch only for blank-scope events and empty-child trailing-wildcard filters.
  • No HTTP wire-shape, Redis schema, or event-wire change. The Lua mutation scripts gain one in-script GET tenant:<id> + decode per call — no additional network round trip.

1,058 tests green including integration + contract validation against spec v0.1.25.13; JaCoCo ≥95% line coverage on all modules; six external review rounds. Full details: CHANGELOG.md [0.1.25.47], AUDIT.md 2026-07-10 entries, PR #231.

v0.1.25.46 — 429 rate limiting on public evidence/JWKS endpoints

Choose a tag to compare

@amavashev amavashev released this 04 Jul 13:03
2236f54

Security release: 429 rate limiting on the public endpoints (#224, hardened by #225).

Added

  • 429 throttling on GET /v1/evidence/* and the CyclesEvidence JWKS — the spec's SHOULD-level rate limiting on the only anonymous surface. Fixed 60s window per client IP, per instance; over-limit requests get 429 with error=LIMIT_EXCEEDED (new ErrorCode, spec v0.1.25.12), Retry-After, X-RateLimit-Reset, X-RateLimit-Remaining: 0, and the standard correlation headers. Authenticated /v1 endpoints are not throttled (abuse there is key-attributable).
  • Config: CYCLES_PUBLIC_RATE_LIMIT_ENABLED (default true), CYCLES_PUBLIC_RATE_LIMIT_REQUESTS_PER_MINUTE (default 300).
  • Client map is hard-bounded (#225): stale-window entries evicted first; a unique-key flood within one window resets the map entirely (heap protection — per-IP limiting cannot constrain an address-rotating attacker).

Compatibility
New behavior only for anonymous callers exceeding 300 req/min/IP on the two public endpoints. The limiter is per instance and keyed on the socket peer — behind a connection-terminating ingress, prefer ingress limiting and/or raise the limit. No wire, Redis, Lua, event, or evidence schema change; Enums.ErrorCode gains LIMIT_EXCEEDED (additive).

501 tests green incl. integration + contract validation against spec v0.1.25.12; spec coverage 11/11; JaCoCo ≥95%. Full details: CHANGELOG.md [0.1.25.46], AUDIT.md 2026-07-04 entries.

[benchmark-skip] — the new filter gates only the public evidence/JWKS endpoints, which the benchmark suite does not exercise; on benchmarked paths it adds a single string prefix comparison per request, indistinguishable from shared-runner noise.

v0.1.25.45 Harden runtime production readiness

Choose a tag to compare

@amavashev amavashev released this 27 Jun 11:56
0242306

What's Changed

Gates operational endpoints (/actuator/prometheus, /actuator/info, aggregate /actuator/health, API docs, Swagger) behind the configured admin key, while liveness/readiness probes and the protocol-public CyclesEvidence/JWKS endpoints stay open. Bounds async event emission with a saturation-logged queue (no request-path blocking), normalizes filter ordering + MDC handling, hardens auth-filter error responses, and removes the tracked dump.rdb snapshot (now gitignored).

[benchmark-skip]

Security/infra hardening — no wire-format or hot-path changes.

Full Changelog: v0.1.25.44...v0.1.25.45

v0.1.25.44 — production compose hardening: Prometheus tenant-label off, SpringDoc off, exec PID-1, fleet pins

Choose a tag to compare

@amavashev amavashev released this 26 Jun 17:50
53333a4

[benchmark-skip] — Deployment-config-only release (production Compose hardening, Dockerfile, docs, pom dependency pins). Zero Java, Lua, or hot-path change since 0.1.25.43, so the reserve/commit/release/event latencies the benchmark measures cannot have regressed. The release gate flaked on shared-runner noise (all metrics uniformly +32–44%, with the rolling median itself inflated by today's slower runners). Bypassed per the workflow's documented policy for infra-only releases.

[0.1.25.44] — 2026-06-26

Fixed

  • Production Compose now disables tenant labels on custom Prometheus domain
    metrics via CYCLES_METRICS_TENANT_TAG_ENABLED=false, avoiding tenant-id
    disclosure and high-cardinality series when /actuator/prometheus is exposed
    to a shared scrape path.
  • Production Compose now disables public SpringDoc API docs and Swagger UI by
    default. Local/developer defaults remain unchanged.
  • Production Compose no longer sets DASHBOARD_CORS_ORIGIN on the runtime
    server, because the runtime service does not consume that variable. The
    admin service still owns dashboard CORS in the full-stack deployment.
  • Full-stack production Compose now references current sibling service images:
    cycles-server-admin:0.1.25.47 and cycles-server-events:0.1.25.20.
  • Full-stack production Compose now probes admin/events readiness endpoints
    instead of aggregate health, matching the sibling services' Redis-aware
    readiness split. The events worker is published on its management port
    9980, not the internal worker port 7980.
  • Full-stack production Compose now requires WEBHOOK_SECRET_ENCRYPTION_KEY
    for the admin/events deployment path and sets
    WEBHOOK_SECRET_ENCRYPTION_REQUIRED=true for admin.
  • Production Compose examples now point at the cycles-server:0.1.25.44 image
    tag.
  • The runtime image entrypoint now uses exec java ... so the JVM is PID 1 and
    receives container SIGTERM directly for Spring Boot graceful shutdown.

Compatibility

  • Deployment hardening only. No HTTP API schema, Redis data model, Lua, event,
    evidence, or protocol behavior change. Custom Prometheus metrics emitted from
    the production Compose deployment no longer include the tenant label unless
    explicitly re-enabled. Full-stack production deployments now fail fast if the
    webhook secret encryption key is missing.

v0.1.25.43 — production readiness: Redis health contributor, requestId MDC, stateless security hardening

Choose a tag to compare

@amavashev amavashev released this 25 Jun 16:28
e416c0e

[benchmark-skip] — Infra/ops-only release (Redis health contributor, requestId MDC, stateless Spring Security config). No hot-path, Lua, or wire change. The release benchmark gate flaked on reserve_p50 only (runner noise: bounced 3.9–4.4 ms vs ~3.5 ms normal, <1% over threshold on a single metric while all others passed); bypassed per the workflow's documented policy for infra-only releases that the benchmark cannot meaningfully measure.

[0.1.25.43] — 2026-06-25

Fixed

  • /actuator/health/readiness now includes a Redis PING health contributor.
    Container, Dockerfile, and release-smoke healthchecks now use readiness and go
    unhealthy when the ledger dependency is unreachable instead of reporting a
    live HTTP process as healthy while API operations return Redis-backed 5xx
    errors. /actuator/health/liveness remains process-only.
  • RequestIdFilter now places requestId in MDC for the lifetime of each
    request, matching the operations runbook and making structured logs
    consistently joinable by both requestId and traceId.
  • Spring Security is now explicitly stateless/API-key-only: HTTP Basic, form
    login, logout, and Spring Boot's generated default user autoconfiguration are
    disabled.
  • Redis connection failures during API-key validation now log one concise
    redis_unavailable warning instead of a full stack trace per affected
    request; unexpected validation exceptions still keep stack traces.
  • Production Compose examples now point at the 0.1.25.43 image tag.

Compatibility

  • Operational readiness change only. No HTTP API schema, Redis data model, Lua,
    event, evidence, or protocol behavior change. The unauthenticated readiness
    endpoint can now return DOWN/503 when Redis is unavailable.

v0.1.25.42 — operational logging context + log sanitization + release-gate rolling baseline (rolls up .40/.41)

Choose a tag to compare

@amavashev amavashev released this 25 Jun 10:38
fe8d9a8

[0.1.25.42] — 2026-06-25

Fixed

  • Release benchmark gating now compares the candidate median against the
    rolling median from benchmark-data/benchmarks/history.jsonl when history is
    available, while keeping the strict 25% threshold. This avoids blocking a
    release on one unusually fast prior release sample; baseline.json remains
    updated after successful releases for reference/bootstrap.
  • Guarded tenant-authorization DEBUG logging so request-time sanitizer work is
    skipped when DEBUG is off.

Compatibility

  • CI/workflow and benchmark script change only. No HTTP request/response,
    Redis, Lua, event, evidence, or spec change.

[0.1.25.41] — 2026-06-24

Fixed

  • Flattened CR/LF characters in dynamic operator-log fields added by the
    logging-context review so request/config/exception values cannot inject
    misleading log lines.
  • Removed API-key prefix/masked-token material from debug logs; auth logging now
    reports only key presence/length plus sanitized tenant/key/reason context on
    failures.
  • Sanitized JWKS retired-key parsing warnings and auth rejection logs while
    preserving method, path, request id, trace id, and error context.
  • Extended the same CR/LF flattening to data-plane repository/service failure
    logs (reservation, audit, event-emitter, evidence, expiry) via a shared
    LogSanitizer utility, so request-derived strings logged below the
    controller layer cannot inject log lines either.
  • Sanitized the remaining exception-handler path/route/reservation-id fields
    and tenant-authorization DEBUG values; added a regression assertion for
    handled protocol-exception log flattening.
  • Per-request controller request logs are emitted at DEBUG, not INFO.
    This removes default production success-path request log volume and keeps
    sanitize/attribute lookups behind isDebugEnabled; exception and
    side-effect-failure logs remain at INFO/WARN. Enable DEBUG for that
    logger to restore per-request lines. A later release-CI follow-up found the
    benchmark gate was also over-sensitive to a single fast baseline sample; see
    0.1.25.42.

Compatibility

  • No HTTP request/response, Redis, Lua, event, evidence, or spec change. Default
    log volume drops (per-request request logs move from INFO to DEBUG).

[0.1.25.40] — 2026-06-24

Fixed

  • Replaced the class-only Landed in cycles exception handler log with
    structured protocol-exception logs carrying method, path, matched route,
    status, error code, request_id, trace_id, and reservation_id.
  • Added the same operational context to validation, malformed-body, and
    unexpected exception handler logs so 4xx/5xx responses can be joined to
    application logs.
  • Added request-context fields to controller request logs for reservations,
    balances, decisions, events, and evidence retrieval.
  • Made formerly silent non-blocking controller side-effect failures visible at
    WARN without changing response behavior.
  • Tightened auth and async event/evidence/audit logs to include safe identifiers
    such as tenant, resource, event, request, and trace context while avoiding
    full validation DTOs, request DTOs, API keys, and raw idempotency keys.

Validation

  • mvn -B -pl cycles-protocol-service-api -am "-Dtest=GlobalExceptionHandlerTest,ApiKeyAuthenticationFilterTest,AdminApiKeyAuthenticationFilterTest" "-Dsurefire.failIfNoSpecifiedTests=false" test
  • mvn -B -pl cycles-protocol-service-api -am "-Dtest=ReservationControllerTest,DecisionControllerTest,EventControllerTest,BalanceControllerTest,EvidenceControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false" "-Dcontract.validation.enabled=false" test
  • mvn -B -pl cycles-protocol-service-data -am "-Dtest=ApiKeyRepositoryTest,ApiKeyValidationServiceTest,AuditRepositoryTest,EventEmitterRepositoryTest,EventEmitterServiceTest,ReservationExpiryServiceTest,EvidenceEmitterTest" "-Dsurefire.failIfNoSpecifiedTests=false" test

v0.1.25.39 — protocol review fixes (SCAN pagination, idempotent replay, auth cache freshness, prod Redis hardening)

Choose a tag to compare

@amavashev amavashev released this 24 Jun 11:59
f50fe80

Fixed

  • Legacy SCAN pagination for GET /v1/reservations and GET /v1/balances
    now uses opaque intra-batch cursors when a page limit is reached mid-batch,
    preventing deterministic skipped rows on follow-up pages for an unchanged
    SCAN batch. As with Redis SCAN generally, pagination remains best-effort
    under concurrent writes or Redis rehashing.
  • Sorted GET /v1/reservations no longer truncates matches at 2000 hydrated
    rows. Large result sets still warn so operators can narrow filters or add
    indexed listing support later.
  • Auth-filter error bodies now include trace_id and set
    X-Cycles-Trace-Id when the trace filter has not already done so.
  • POST /v1/events idempotent replay now returns the stored original response
    payload and skips duplicate event metrics/balance event emission.
  • Invalid tenant default_commit_overage_policy values now fail closed with
    INVALID_REQUEST; Lua commit/event paths also reject unknown overage
    policies defensively.
  • API key validation no longer caches full allow/deny decisions. It still
    caches BCrypt verification results briefly, including repeated wrong-secret
    checks keyed by the current stored hash, while status, expiry, tenant, and
    tenant-status checks are read on every request.
  • Production Compose files require REDIS_PASSWORD, authenticate Redis health
    checks, stop publishing Redis on the host port, and pin cycles-server to
    0.1.25.39.

Documentation

  • Clarified idempotency status codes: header/body key mismatch is
    400 INVALID_REQUEST; same-key/different-payload replay is
    409 IDEMPOTENCY_MISMATCH.

v0.1.25.38 — disable evidence emission when unconfigured

Choose a tag to compare

@amavashev amavashev released this 23 Jun 20:33
3cc44a7

Disable CyclesEvidence emission when the public identity is unconfigured (#207). Pairs with cycles-server-events v0.1.25.15.

Fixed

  • When EVIDENCE_SERVER_ID or EVIDENCE_SIGNING_SIGNER_DID is blank, EvidenceEmitter.emit(...) now returns null before building a source record or pushing to evidence:pending — an intentionally non-evidence deployment no longer accumulates work for a signer that should be off.
  • Configured deployments are unchanged: null-strip payload, synchronous evidence_id compute, record stamp, queue push, cycles_evidence ref returned.

Additive / no wire change. mvn -B verify green.

v0.1.25.37 — link reservations to their evidence (include=evidence)

Choose a tag to compare

@amavashev amavashev released this 22 Jun 16:41
07292da

Link reservations to their CyclesEvidence envelope(s) — implements cycles-protocol v0.1.25.9 (#117).

The cycles_evidence ref previously rode only on the live reserve/commit/release response, so a reservation fetched later had no path back to its signed envelope. The server now persists each computed evidence ref onto the reservation and surfaces it via a new opt-in evidence projection.

Changes

  • evidence projection on ReservationSummary / ReservationDetail: a ReservationEvidence map keyed by artifact type (reserve / commit / release) of CyclesEvidenceRefs.
  • include=evidence token on GET /v1/reservations (omitted from list rows by default, projection-only — not folded into the cursor/filter hash). Always present on the single-row getReservation when recorded.
  • persistEvidenceRef does a fail-open follow-up HSET of <artifact>_evidence_id + _evidence_url onto the reservation hash right after the ref is stamped (preserves the Lua-set terminal TTL).
  • Degrades gracefully: null when evidence emission is disabled or for pre-feature reservations.

Additive / non-breaking — clients that don't request include=evidence get byte-identical responses. Full mvn verify green across model/data/api; JaCoCo 95% gate met.

Downstream: cycles-dashboard renders one-click "View evidence" links per reservation (runcycles/cycles-dashboard#212).