Skip to content

Secrets Manager staged-put/CAS write engine (ADR 0001; #89)#91

Merged
Kyle-Falconer merged 2 commits into
mainfrom
issue-89-sm-write
Jun 25, 2026
Merged

Secrets Manager staged-put/CAS write engine (ADR 0001; #89)#91
Kyle-Falconer merged 2 commits into
mainfrom
issue-89-sm-write

Conversation

@Kyle-Falconer

Copy link
Copy Markdown
Member

Closes #89.

Builds the v1 Secrets Manager non-stomping write — the staged PutSecretValue + atomic UpdateSecretVersionStage compare-and-swap ADR 0001 was written for — end-to-end behind fakes + replay, plus a human-gated live-verify-sm-write binary. Everything lands green in CI; the live run itself is the separate follow-up. Resolves ADR 0032 Decision 8's deferral; mirrors the SSM .env write precedent (ADR 0029, #72) exactly. Design of record: ADR 0001 + its Amendment (2026-06-25) (already on main).

What's in it (all in janitor-aws)

  • SecretsApi seam (wire.rs): get_secret_value surfaces the read VersionId (via ReadSecret); new put_secret_value (→ new VersionId; stages under a janitor-pending-<uuid> label so AWSCURRENT is not moved) and update_secret_version_stage (→ CasOutcome::{Committed,Mismatch}). Fakes for all three.
  • secret_write::write_secret engine + standalone SecretsManagerWriter (new file):
    • Flat-JSON only serde_json merge — replace/insert/remove edited top-level keys, preserve every untouched key verbatim incl. non-string scalars; nested/array/bare-string/secret_binary → masked NotFlat/Unsupported (never an un-flatten guess).
    • ADR 0001 steps 3–6: stage → atomic CAS → mandatory pending-label cleanup.
    • Conflict model B: base = the write's own first read; a key we edited changing across a re-read → stop with Conflict (never auto-merge); only other keys changed → replay-on-fresh + bounded MAX_ATTEMPTS retry.
    • No-op skip (don't manufacture a version); fresh uuid v4 token per distinct payload.
  • SecretsManagerMethod::write: dispatches to the engine (the Unsupported stub is gone).
  • aws_impl.rs: real put/stage/version-id, factored into replay-testable _with fns; CAS-mismatch classification (shell-side, flagged pending live verification per ADR 0001).
  • live-verify-sm-write binary: guided sign-in → Discovery → edit → masked outcome → masked re-read.

Safety

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 (THREAT-MODEL). v1 stays read-only by default — the engine is reachable only through the binary, no edit UI.

Verification

  • 55 janitor-aws tests cover the 8 acceptance scenarios (Applied happy path; non-flat→Unsupported ×4; same-key→Conflict; other-key→replay+retry+Applied with teammate keys preserved; persistent→Conflict after MAX_ATTEMPTS; cleanup strips the orphaned pending label; no-op skip; invalid key fails closed with no I/O) + StaticReplayClient replay of the 3 new SDK methods.
  • cargo llvm-cov -p janitor-aws = 94.8% (gate not weakened; secret_write.rs 97.7%, wire.rs 100%).
  • Full workspace cargo test / clippy / fmt green.

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_ATTEMPTS is the only quota defence for now), and the GUI cell-edit + confirm-diff UI (#80 follow-up).

🤖 Generated with Claude Code

Kyle-Falconer and others added 2 commits June 25, 2026 13:54
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>
@Kyle-Falconer Kyle-Falconer merged commit 12c3854 into main Jun 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build the Secrets Manager staged-put/CAS write engine (ADR 0001) + live-verify-sm-write binary

1 participant