You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AUDIT.md
+27-1Lines changed: 27 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,7 @@
1
1
# Cycles Protocol v0.1.25 — Server Implementation Audit
2
2
3
-
**Date:** 2026-04-18 (v0.1.25.14 — trace_id (W3C Trace Context) cross-surface correlation per cycles-protocol revision 2026-04-18; new `TraceContextFilter` extracts `traceparent` or `X-Cycles-Trace-Id` from inbound requests or generates a fresh 128-bit id, echoes `X-Cycles-Trace-Id` on every response, populates `trace_id` on `ErrorResponse` / `Event` / `WebhookDelivery` / `AuditLogEntry`),
3
+
**Date:** 2026-04-18 (v0.1.25.15 — runtime audit-log retention TTL fix; `AuditRepository` now writes `audit:log:{id}` keys with `EX ttl` via the same Lua shape admin uses, configurable via `audit.retention.days` (default 400d), daily `@Scheduled` sweep prunes stale ZSET index pointers; closes a gap where runtime-written rows persisted indefinitely and did not participate in admin's authenticated-tier retention),
4
+
2026-04-18 (v0.1.25.14 — trace_id (W3C Trace Context) cross-surface correlation per cycles-protocol revision 2026-04-18; new `TraceContextFilter` extracts `traceparent` or `X-Cycles-Trace-Id` from inbound requests or generates a fresh 128-bit id, echoes `X-Cycles-Trace-Id` on every response, populates `trace_id` on `ErrorResponse` / `Event` / `WebhookDelivery` / `AuditLogEntry`),
4
5
2026-04-16 (v0.1.25.13 — hydration cap + enum wire annotations on the sorted `GET /v1/reservations` path; `SORTED_HYDRATE_CAP=2000` guard on the in-memory sort hydration with WARN-on-cap, matches admin plane's v0.1.25.24 pattern; `@JsonValue`/`@JsonCreator fromWire` on `ReservationSortBy` + `SortDirection` to mirror admin's `SortSpec`/`SortDirection` contract),
5
6
2026-04-16 (v0.1.25.12 — `sort_by` + `sort_dir` on `GET /v1/reservations` per cycles-protocol spec revision 2026-04-16; 7-value sort enum, opaque cursor binds `(sort_by, sort_dir, filters)` tuple, legacy SCAN-cursor path preserved when both params omitted),
6
7
2026-04-14 (automated performance regression detection — nightly trend + release gate, no version bump),
Closes a gap surfaced by the post-v0.1.25.14 alignment audit: runtime's `AuditRepository.log()` was writing `audit:log:{id}` string keys with no `EX`, so runtime-written audit rows persisted indefinitely until Redis memory-eviction kicked in. This broke the 400-day retention story the admin plane tells operators — admin's `AuditRepository` already applies tiered TTL (400d authenticated / 30d unauthenticated) via a conditional Lua `SET … EX ttl`, but runtime-written rows were silently non-compliant with that contract. The audit dashboard reads from the shared index, so stale admin ZSETs would also accumulate pointers to long-expired runtime rows without a compensating sweep.
27
+
28
+
**Root cause:** the original v0.1.25.8 introduction of runtime-side audit writes copied admin's Lua shape from *before* admin added per-entry TTL in its v0.1.25.20. Admin's TTL work never back-propagated to runtime — not a regression, just an unnoticed drift.
29
+
30
+
**Scope decision — runtime-side fix over admin-side reconciliation:** the writer should own retention, not a downstream sweeper. Admin-side reconciliation would have required a reaper polling for TTL-less keys — heavier, fights the symptom, couples admin's cleanup cadence to runtime's write rate. Runtime-side adds ~100 LOC (Lua arg + config + sweeper mirror) with zero API surface change.
31
+
32
+
**Scope decision — single tier instead of admin's two tiers:** runtime only writes real-tenant audit rows. The `__admin__` (platform-plane) and `__unauth__` (pre-auth failure) sentinels are admin-plane concerns — runtime authenticates every request before the audit write, and runtime-plane operations like reservation release are always tenant-attributed. So one `audit.retention.days` knob (default 400 to match admin's authenticated tier) is sufficient. If a future runtime endpoint ever needs the 30-day short-tier behavior, this config can be extended without wire change.
33
+
34
+
**Lua change:**`LOG_AUDIT_LUA` now reads `ARGV[4]` as an optional TTL in seconds; conditional branch matches admin's script byte-for-byte (minus the sentinel logic). Atomic guarantee preserved — SET + 2×ZADD still run in one call, so the TTL addition cannot introduce orphaned index pointers on a mid-write crash.
35
+
36
+
**Sweeper:** mirrors admin's `sweepStaleIndexEntries()` — daily @Scheduled cron (default `0 0 3 * * *`), `ZREMRANGEBYSCORE` on `audit:logs:_all` plus SCAN over per-tenant indexes. Runtime deploys a sweeper of its own (rather than depending on admin's sweep hitting the shared Redis) so a runtime-only topology stays self-healing. Two sweepers scanning the same index is idempotent — `ZREMRANGEBYSCORE` on already-swept ranges is a no-op.
37
+
38
+
**Config:**
39
+
40
+
-`audit.retention.days` (env `AUDIT_RETENTION_DAYS`) — default `400`. `0` = indefinite retention; the sweeper is skipped explicitly in that mode (matching admin's `authenticatedRetentionDays=0` behavior).
**Backward compatibility:** new writes get TTL; old runtime-written keys stay un-TTL'd until Redis memory pressure evicts them. No API change, no wire change, no admin-side reconciliation needed — admin's reader doesn't care whether the target key has a TTL, it only cares that a value is present or not (and already null-body-tolerant for TTL-expired pointers).
44
+
45
+
**Tests:** 7-case `AuditRepositoryTest` covering: (1) TTL passed as ARGV[4] = 400×86400, (2) `retentionDays=0` passes `"0"`, (3) `logId` + timestamp set on the entry, (4) Redis failure is non-fatal, (5) sweeper removes global and per-tenant pointers, (6) sweeper is a no-op when retention is 0, (7) sweep Redis failure is non-fatal. All green; full data module still 365 / 365; full api module still 152 / 152.
Implements the `CORRELATION AND TRACING` normative section added to `cycles-protocol-v0.yaml` in spec revision 2026-04-18 (commit `8d65959`). Introduces a third correlation identifier — `trace_id` — that is W3C Trace Context-compatible (OpenTelemetry-native) and links every HTTP request to its `ErrorResponse`, audit-log entry, emitted events, and outbound webhook deliveries under one logical-operation grain.
Copy file name to clipboardExpand all lines: OPERATIONS.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -414,6 +414,8 @@ don't fit.
414
414
| `cycles.expiry.initial-delay-ms` | `5000` | Mostly a test knob. Leave. |
415
415
| `cycles.tenant-config.cache-ttl-ms` | `60000` | Lower if admin tenant config changes need to take effect faster than 60s. |
416
416
| `admin.api-key` | (empty) | Set to a fixed-length secret to enable the admin-on-behalf-of endpoint (v0.1.25.8+). Leave empty to disable. |
417
+
| `audit.retention.days` | `400` | Retention for runtime-written audit rows (v0.1.25.15+). Default matches admin's `audit.retention.authenticated.days` — SOC2 Type II 12-month lookback + 1-month auditor-lag buffer. Set `0` for indefinite retention (legal hold, archive-store deployments). |
418
+
| `audit.sweep.cron` | `0 0 3 * * *` | Daily cron for pruning stale ZSET index pointers (v0.1.25.15+). Lower cadence if audit write volume is very high; leave as-is otherwise. Skipped when `audit.retention.days=0`. |
417
419
| `management.endpoints.web.exposure.include` | `health,info,prometheus` | Add more actuator endpoints if you need them, but `prometheus` is the one ops cares about. |
Copy file name to clipboardExpand all lines: cycles-protocol-service/cycles-protocol-service-data/src/main/java/io/runcycles/protocol/data/repository/AuditRepository.java
0 commit comments