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:
@@ -1,5 +1,12 @@
|
||||
# G-7 — Clustered secret replication: design & fork resolution
|
||||
|
||||
> **✅ SUPERSEDED BY THE BUILD, 2026-07-18.** This document resolved the fork to Option A and
|
||||
> deferred Option B. On the user's instruction **both were built**, as
|
||||
> `ZB.MOM.WW.Secrets.Replicator.SqlServer` (shared-store *and* hub modes) and
|
||||
> `ZB.MOM.WW.Secrets.Replicator.AkkaDotNet`. The fork analysis below is still the right way to
|
||||
> *choose* between them — it is now a deployment decision, not a build one. The "Decision" section's
|
||||
> YAGNI reasoning for deferring Option B no longer applies.
|
||||
|
||||
> **Status:** design-only (G-7 in [`components/secrets/GAPS.md`](../../components/secrets/GAPS.md)).
|
||||
> Companion executable plan: [`2026-07-17-secrets-g7-sqlserver-store.md`](2026-07-17-secrets-g7-sqlserver-store.md).
|
||||
> Prerequisite G-2…G-6 have landed for both clustered apps (ScadaBridge, OtOpcUa); G-8 (KEK
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-07-17-secrets-g7-sqlserver-store.md",
|
||||
"status": "executed 2026-07-18 with widened scope (both options built as .Replicator.* packages)",
|
||||
"tasks": [
|
||||
{"id": 0, "subject": "Task 1: SQL-Server schema + migrator", "status": "pending"},
|
||||
{"id": 1, "subject": "Task 2: SqlServerSecretStore : ISecretStore", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 2, "subject": "Task 3: DI store-selection wiring", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 4: Docs + adoption notes", "status": "pending", "blockedBy": [1]}
|
||||
{
|
||||
"id": 0,
|
||||
"subject": "Task 1: SQL-Server schema + migrator",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"subject": "Task 2: SqlServerSecretStore : ISecretStore",
|
||||
"status": "completed",
|
||||
"blockedBy": [
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"subject": "Task 3: DI store-selection wiring",
|
||||
"status": "completed",
|
||||
"blockedBy": [
|
||||
1
|
||||
],
|
||||
"note": "Delivered as AddZbSecretsSqlServerStore / AddZbSecretsSqlServerReplication in a standalone package rather than a SecretsOptions.Store enum in core."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"subject": "Task 4: Docs + adoption notes",
|
||||
"status": "completed",
|
||||
"blockedBy": [
|
||||
1
|
||||
],
|
||||
"note": "docs/operations/clustered-secrets.md covers all three topologies."
|
||||
}
|
||||
],
|
||||
"deferredPhase2": "Option B — ZB.MOM.WW.Secrets.Akka replicator (build only on a stated partition-tolerance requirement); see plan doc.",
|
||||
"lastUpdated": "2026-07-17"
|
||||
"deferredPhase2": "BUILT 2026-07-18 as ZB.MOM.WW.Secrets.Replicator.AkkaDotNet (no longer deferred).",
|
||||
"lastUpdated": "2026-07-18"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user