feat(secrets): cluster replication via SQL Server and Akka.NET (G-7, 0.2.0)

Secrets were per-node SQLite, so a secret written on one node was invisible to
the rest of a cluster. G-7's design resolved the "shared SQL store vs Akka
replicator" fork to build only the former; both are built here so the choice is
a deployment decision (availability vs partition tolerance) rather than a
library limitation.

Two new packages — ZB.MOM.WW.Secrets.Replicator.SqlServer (shared store, plus a
local-store-with-hub mode) and .Replicator.AkkaDotNet (peer-to-peer over
distributed pub/sub). Core gains ISecretsStoreMigrator, one shared
SecretLastWriterWins predicate so no two stores can disagree on a tie, the
transport-agnostic reconciler, and ReplicatingSecretStore — which closes a real
gap: nothing had ever called ISecretReplicator.PublishAsync, so the seam was
inert and local writes would not have propagated at all.

Verified 182 pass / 1 skip / 0 warnings, including 15 live tests against a real
SQL Server 2022 (the SQLite suite ported case-for-case, so any behavioural
divergence between the stores fails) and a 9-test in-process 2-node Akka
cluster over real remoting. A post-build review caught six defects, all fixed
and now covered: both replication modes could not resolve from the container
(no test had built one), an unbounded fetch that broke past SQL Server's
2100-parameter cap, a poison row that aborted the rest of its batch forever,
Enum.Parse on peer input that could restart the actor in a loop, null crypto
blobs crossing the trust boundary, and a silently dropped pull-read failure.

Packed at 0.2.0 and vulnerability-scanned clean; not yet published to the feed.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
Joseph Doherty
2026-07-18 04:08:23 -04:00
parent e46060fada
commit dd0a846b64
55 changed files with 4848 additions and 51 deletions
@@ -1,5 +1,28 @@
# G-7 (Option A) — Shared SQL-Server `ISecretStore` Implementation Plan
> **✅ EXECUTED 2026-07-18 — and the scope was widened on the user's instruction.** This plan covered
> Option A only, with Option B (Akka) deferred. The user directed that **both** be built, packaged as
> `ZB.MOM.WW.Secrets.Replicator.SqlServer` and `ZB.MOM.WW.Secrets.Replicator.AkkaDotNet` — so the
> "Deferred phase-2" section below is **also built**, and the naming here (`SqlServer/` inside the
> core package; `ZB.MOM.WW.Secrets.Akka`) is superseded by two standalone packages.
>
> Other deltas from what was planned, all deliberate:
> - **Two SQL-Server topologies, not one.** Shared-store (as planned) *plus* a local-store-with-hub
> mode, so the package's `Replicator` name is honest and partition tolerance is available without
> Akka.
> - **`SecretLastWriterWins` extracted to Abstractions.** The plan flagged Task 2 high-risk because
> the LWW semantics "MUST match SQLite exactly"; a shared predicate makes that structural instead
> of a review obligation.
> - **`ReplicatingSecretStore` added.** Discovered during the build that nothing ever called
> `ISecretReplicator.PublishAsync` — the seam was inert. A store decorator fixes every write path.
> - **`ISecretReplicationHub` added**, so the hub sweep is testable offline rather than requiring a
> live SQL Server for every convergence case.
> - **Anti-entropy is bidirectional.** Pull-only would strand a write made while a peer was down:
> the peer never learns the name exists, so it can never ask for it.
>
> Verified: 164 pass / 1 skip / 0 warnings, including 15 live tests against a real SQL Server 2022
> and a 9-test in-process 2-node Akka cluster. See [`components/secrets/GAPS.md`](../../components/secrets/GAPS.md) §G-7.
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans (or
> subagent-driven-development) to implement this plan task-by-task.