feat(durable): add zeph durable rotate-key for AEAD payload key rotation#6452
Merged
Conversation
Issue #6447 originally described an operational-tooling gap for a high-water-mark key-rotation primitive credited to #6360. That primitive does not exist in the codebase and #6360 is an unimplemented, open research issue, so this PR rescopes #6447 to the real analogous gap: XChaCha20Poly1305Cipher::with_previous existed but was never wired to an operator-facing surface. Adds zeph durable rotate-key to open and close a rotation window for the durable execution layer's AEAD payload key (ZEPH_DURABLE_KEY). [durable] gains key_id (operator-controlled current-key selector) and previous_key_id (rotation-window state), both config-driven so rotation no longer requires a recompile. load_durable_cipher, the single chokepoint shared by the agent write path, scheduler-daemon write path, and CLI/--reveal read path, builds the cipher from both fields and hard-errors if previous_key_id is set but the vault secret is missing. Design went through two adversarial critique rounds before implementation: the first caught that vault-first write ordering (originally proposed) leaves a crash window that silently mis-decrypts every pre-rotation payload, and that a second rotation while a window is open would silently orphan the first previous key's blobs given the cipher's single previous-key slot. The shipped design writes config-first then vault, refuses a second rotation while one is open, enforces a shared-DB acknowledgment gate (rotating also re-derives the control-entry HMAC key, which has no rotation window of its own), and defaults --drop-previous to a blob-scan verification before discarding the previous key. A post-implementation critique and independent tester pass both found the same coverage gap (the fail-closed branch was untested at the layer every normal process bootstrap actually calls through, not just the CLI's own guard) and it was closed with a follow-up round of tests before this went to review. zeph --init's existing key-replacement wizard step is unchanged in behavior but now labels itself as a destructive reset with no rotation window, pointing operators to rotate-key for the safe alternative. HMAC rotation windows on shared databases and a TUI passive rotation indicator are explicitly out of scope and filed as follow-ups (#6450, #6451). Closes #6447
Merged
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
with_hwm_key/with_previous_hwm_key/HWM_KEY_EPOCH/DurableError::HighWaterMarkIntegrity— zero matches) and research(security): persisted transcript/history integrity — no tamper detection before durable replay #6360 is an unimplemented, openresearch(security)issue with no closing PRs. This PR rescopes wire durable high-water-mark key rotation (with_previous_hwm_key) into --init/a CLI command #6447 to the real analogous gap it also described:XChaCha20Poly1305Cipher::with_previousexisted but was never operationally wired.zeph durable rotate-keyto open/close a rotation window for the durable execution layer's AEAD payload key (ZEPH_DURABLE_KEY).[durable]gains config-drivenkey_id/previous_key_id(no more recompile to rotate).load_durable_cipher— the single chokepoint shared by the agent write path, scheduler-daemon write path, and CLI/--revealread path — now builds the cipher from both fields and hard-errors ifprevious_key_idis set but the vault secret is missing.--ack-shared-db-drain), and a default-on blob-scan before--drop-previousdiscards the previous key (--forceto skip, scoped only to that path).zeph run/resumeactually calls through, not just the CLI's own guard) — closed with a follow-up round of tests before review.zeph --init's existing key-replacement wizard step is unchanged in behavior but now labels itself a destructive reset with no rotation window, pointing operators torotate-keyfor the safe alternative.Closes #6447
Test plan
cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins(14418 passed, 35 skipped)RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"gitleaks protect --staged --no-banner --redactsrc/commands/durable.rs(guard sequence, fail-closed branch at the bootstrap layer, round-trip through the real CLI+backend, R5 error-mapping end-to-end) + unit/doc-test coverage acrosszeph-config,zeph-core,zeph-durable.local/testing/playbooks/durable-key-rotation.mdand.local/testing/coverage-status.mdupdated