I dismissed this finding from the code review as a false positive, reasoning that Akka's ActorBase caches Self in a field and that three passing anti-entropy tests traverse the path. Both premises were wrong. Self resolves through Context, which is [ThreadStatic], and throws NotSupportedException once a continuation resumes on a thread-pool thread. The tests passed because a local SQLite store usually completes await SYNCHRONOUSLY, so the continuation stayed on the mailbox thread and the context was still intact. Correctness therefore depended on store latency and thread-pool timing: green here, broken under a slower or contended store, with the only symptom a per-peer warning every announce interval while nodes silently stopped converging. The live-broadcast fast path masked it further — only the anti-entropy repair path was dead. Captures self on the actor thread and passes it in. Adds GenuinelyAsyncSecretStore to force the async path, a regression test that fails on the unfixed code (20s timeout) and passes in 3s after, and ActorContextAfterAwaitTests pinning the underlying Akka behaviour so the wrong assumption cannot be made again. Audited every remaining Self/Sender access in the actor. 170 pass offline / 184 with the live SQL suite / 1 skip / 0 warnings. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
Secrets (encrypted secret store + ${secret:} resolution)
Normalizes how the family stores and consumes secrets — SQL/login passwords, API-key HMAC
peppers, LDAP bind passwords, connection strings, TLS material — which are handled ad-hoc
and inconsistently across the three apps today (Data-Protection-encrypted connection
strings in ScadaBridge; peppers/passwords in environment variables; LDAP passwords in
appsettings).
The goal is the shared ZB.MOM.WW.Secrets library: AES-256-GCM envelope encryption at
rest, a pluggable master-key provider and store, an audited ISecretResolver +
${secret:name} config expander for app runtime, and a Blazor /admin/secrets management
UI. The library is built, published (0.1.2), and live-proven via its reference consumer;
per-app adoption is the tracked follow-on.
- Target:
spec/SPEC.md - Realized API:
shared-contract/ZB.MOM.WW.Secrets.md - Implementation:
../../ZB.MOM.WW.Secrets/· designdocs/plans/2026-07-15-secrets-manager-design.md - Adoption backlog:
GAPS.md
Status
| State | |
|---|---|
| Library | Built + published — ZB.MOM.WW.Secrets{,.Abstractions,.Ui} 0.1.2 on the dohertj2-gitea feed; .Cli in-repo (not packed); .Akka replicator deferred (design only) |
| Reference consumer | HistorianGateway — adopted + live-proven (2026-07-16): historian password sourced via ${secret:}, authenticated read against the real wonder historian |
| Three sister apps | Not yet adopted — see per-app current-state + GAPS |
Per-project current state
| Project | Today (baseline) | Doc |
|---|---|---|
| OtOpcUa | (code-verified baseline) | current-state/otopcua/CURRENT-STATE.md |
| MxAccessGateway | (code-verified baseline) | current-state/mxaccessgw/CURRENT-STATE.md |
| ScadaBridge | (code-verified baseline) | current-state/scadabridge/CURRENT-STATE.md |
Not applicable as a fourth adopter row but the exemplar: HistorianGateway already consumes the lib — its wiring is the template the three apps follow (see the shared-contract "Consumer wiring" section).