v0.4.1-beta
·
286 commits
to master
since this release
Spooky 0.4.1-beta — Staged Activation & Rollback
Patch release covering all changes since 0.4.0-beta.
Added
- Staged runtime activation —
POST /admin/runtime/validate,/preview, and/activatesplit config changes into plan-then-commit. Validate and preview read, validate, compatibility-gate, and produce a per-domain diff without touching the running runtime; activate commits it as an explicit transaction returning the diff, structured rejections, and a history entry. - Rollback by generation id —
POST /admin/runtime/rollbackrestores a previously retained runtime generation. Rollback moves forward to a new generation number while restoring the target's content and its original config source. - Runtime generation history —
GET /admin/runtime/historyand/history/{generation}expose the operation log alongside retained-generation records (status,rollback_candidate,has_bundle, and anoteexplaining failed staged prepares), so operators can discover which generations are valid rollback targets. - Alternate config source activation — activation accepts a
config_pathin the request body, enabling canary workflows that switch between config files without a restart. - Optimistic concurrency —
expected_generationon activate andexpected_active_generationon rollback reject with409when the runtime has moved underneath the caller. - Runtime activation observability, and canonical rejection reasons normalized across operator surfaces.
Fixed
- Activating an alternate
config_pathnow makes that file the active runtime source. Previously the runtime kept advertising the startup path, so a later reload-without-body silently re-read the wrong file and the control-plane view misreported the active config source. - Generation history records only the operation actually requested. Previously every staged call wrote both a
validateand a syntheticpreviewentry, so a single activation consumed three history slots and the audit trail claimed preview activity that never occurred. - Retained-generation state is now surfaced by the history API. Failed staged prepares were tracked internally but never exposed, leaving operators unable to see why a candidate never committed.
Changed
- Operator conflicts are now classified instead of collapsing into
500. Stale-generation and restart-required conflicts return409; an unknown rollback target returns404; an invalid config returns400. Genuine runtime faults — resource preparation failure, or a failed runtime swap — still return500. POST /admin/runtime/reloadnow defaults to the currently active runtime config source rather than always re-reading the startup path. It remains supported as a shortcut over the same staged pipeline as/activate.- Config defaults co-located with the types they belong to, and serde default patterns normalized.
- Test suite restructured by behavioral domain, with shared integration harnesses for the request path, bootstrap/QUIC parity, runtime swap, and backend lifecycle.
- Documentation updated for the staged control-plane surface: control API reference, API overview, production readiness, migration guide, and roadmap.
Upgrade Notes
500 Internal Server Error now return 409 Conflict or 404 Not Found. Tooling that treats any non-2xx from /admin/runtime/reload as an infrastructure failure should be updated to distinguish retryable operator conflicts from genuine faults.
Note that validate and preview return 200 even when a candidate is rejected — the status code reflects the planning request, not the verdict. Read candidate_status and rejected_changes.
Known Limitations
- No dynamic backend discovery (static config only)
- Listener bind address and protocol changes still require a restart
- Retained generations are capped at a fixed bound; rollback reaches recent generations, not arbitrarily old ones
- Retention is not yet configurable, and there is no automatic rollback on post-activation health regression
- Pre-GA — extended soak testing recommended before broad production rollout