Secrets Manager staged-put/CAS write engine (ADR 0001; #89)#91
Merged
Conversation
Records the v1 scoping decided in a design grilling: flat-JSON Sets only, the CAS base is the write's own first read (conflict model B, a conscious narrowing of "record base at load"), and the active version-quota guard is deferred (bounded retries + mandatory pending-label cleanup carry it). Scope is engine + live-verify (#89/#90); the GUI cell-edit affordance is a later phase. The load-bearing atomic UpdateSecretVersionStage CAS is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the v1 Secrets Manager non-stomping write end-to-end behind fakes + replay, shipped read-only (reachable only via a human-gated binary). Resolves ADR 0032 Decision 8's deferral of the SM write; mirrors the SSM `.env` write precedent (ADR 0029, #72). - wire: SecretsApi gains put_secret_value (-> new VersionId; stages under a janitor-pending-<uuid> label so AWSCURRENT is not moved) and update_secret_version_stage (a CasOutcome::{Committed,Mismatch} CAS); get_secret_value now surfaces the read VersionId via ReadSecret. Fakes script reads/puts/stage-commits and record calls. - secret_write: the write_secret engine — flat-JSON serde_json merge (preserve untouched keys verbatim incl. non-string scalars; nested/array/bare-string/ binary -> NotFlat/Unsupported, never an un-flatten guess), ADR 0001 steps 3-6 (stage -> atomic CAS -> mandatory cleanup), conflict model B (base = the write's own first read; edited-key change across a re-read -> stop Conflict; other-key change -> replay-on-fresh + bounded MAX_ATTEMPTS retry), no-op skip, fresh-token-per-distinct-payload. Standalone SecretsManagerWriter (broker + api). - method: SecretsManagerMethod::write dispatches to the engine (Unsupported stub gone). - aws_impl: real put/stage/version-id factored into replay-testable _with fns + CAS-mismatch classification (shell-side, pending live verification). - bin: human-gated live-verify-sm-write (guided sign-in -> Discovery -> edit -> masked outcome -> masked re-read). THREAT-MODEL: the merged blob + Values are Zeroizing and reach only the writer; ClientRequestToken/VersionId are non-secret opaque ids; no Value or SDK text crosses a Failure/Event/log/Debug. v1 stays read-only (no edit UI). Tests: 55 janitor-aws tests cover the 8 AC scenarios + StaticReplayClient replay of the 3 new SDK methods. Coverage holds at 94.8% (gate not weakened). Full workspace test/clippy/fmt green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Closes #89.
Builds the v1 Secrets Manager non-stomping write — the staged
PutSecretValue+ atomicUpdateSecretVersionStagecompare-and-swap ADR 0001 was written for — end-to-end behind fakes + replay, plus a human-gatedlive-verify-sm-writebinary. Everything lands green in CI; the live run itself is the separate follow-up. Resolves ADR 0032 Decision 8's deferral; mirrors the SSM.envwrite precedent (ADR 0029, #72) exactly. Design of record: ADR 0001 + its Amendment (2026-06-25) (already onmain).What's in it (all in
janitor-aws)SecretsApiseam (wire.rs):get_secret_valuesurfaces the readVersionId(viaReadSecret); newput_secret_value(→ newVersionId; stages under ajanitor-pending-<uuid>label soAWSCURRENTis not moved) andupdate_secret_version_stage(→CasOutcome::{Committed,Mismatch}). Fakes for all three.secret_write::write_secretengine + standaloneSecretsManagerWriter(new file):serde_jsonmerge — replace/insert/remove edited top-level keys, preserve every untouched key verbatim incl. non-string scalars; nested/array/bare-string/secret_binary→ maskedNotFlat/Unsupported(never an un-flatten guess).Conflict(never auto-merge); only other keys changed → replay-on-fresh + boundedMAX_ATTEMPTSretry.uuidv4 token per distinct payload.SecretsManagerMethod::write: dispatches to the engine (theUnsupportedstub is gone).aws_impl.rs: real put/stage/version-id, factored into replay-testable_withfns; CAS-mismatch classification (shell-side, flagged pending live verification per ADR 0001).live-verify-sm-writebinary: guided sign-in → Discovery → edit → masked outcome → masked re-read.Safety
The merged blob + Values are
Zeroizingand reach only the writer;ClientRequestToken/VersionIdare non-secret opaque ids; no Value or SDK text crosses aFailure/Event/log/Debug(THREAT-MODEL). v1 stays read-only by default — the engine is reachable only through the binary, no edit UI.Verification
MAX_ATTEMPTS; cleanup strips the orphaned pending label; no-op skip; invalid key fails closed with no I/O) +StaticReplayClientreplay of the 3 new SDK methods.cargo llvm-cov -p janitor-aws= 94.8% (gate not weakened;secret_write.rs97.7%,wire.rs100%).cargo test/clippy/fmtgreen.Out of scope (unchanged)
The live run (ADR 0001's AWSCURRENT/AWSPREVIOUS + label-reclamation + exact CAS-mismatch error-code checks — the shell errs to the safe side until then), the version-quota cadence guard (deferred —
MAX_ATTEMPTSis the only quota defence for now), and the GUI cell-edit + confirm-diff UI (#80 follow-up).🤖 Generated with Claude Code