Resolve all 33 medium-severity architecture review findings
Second slice of the 2026-09-09 whole-system review remediation
(docs/plans/2026-09-09-arch-review.md §10), on top of the High slice 146a1323.
Runtime/devices: SR-2 alarm on-trigger integration scope; SR-3 correlated NativeAlarmActor
subscribe retry; SR-4 immutable audited DB target; SR-5 ordered InstanceActor stop/flush;
SR-12 rehydration fence; SR-13 S&F gate recheck per delivery; DC-4 no OPC UA security
downgrade; DC-5 alarm snapshot for every new subscriber; ORCH-1 sync-dispose joins the pump.
Management/UI: MG-2 site scope on audit tree + Flow Debugger Replay; MG-3 stale-probe memo
removed; MG-4 server-owned bulk-deploy deadline; UI-5 sandbox runs in a killable child
process (new CentralUI.SandboxWorker project); UI-14 bounded sandbox output/IPC.
Edge/audit: EI-1 relay audit ownership; EI-2 SkipBodyCapture suppresses error text; EI-3/EI-11
error-field redaction + caps; EI-4 single relay deadline, pre-execution-only retry; EI-5
authenticated relay provenance; EI-10 body cap before audit read.
Persistence/contracts: DL-1 global Instance.UniqueName unique index + conflict translation;
DL-2 DataConnection protocol config encrypted at rest (migration
20260909203046_EnforceGlobalInstanceNamesAndProtectProtocolConfiguration — coordinated
central upgrade, old binaries cannot read protected rows); DL-3 bounded Replay paging;
DL-6 cross-site Overwrite of a deployed instance rejected; XC-4 proto descriptor contract test.
Config/hygiene: XC-1 central listener isolation by local port; XC-2/XC-3 inert SMTP + cron
keys removed from rig settings; CM-2 env2 bootstrap guard enabled; DC-6 deploy site exit code;
DC-7 notifier retries only explicit standby rejection; XC-5 ClusterClient purged from docs.
Gates recorded in the report: build 0/0, 9 397 passed / 0 failed, rig redeployed on all
eight nodes with the migration applied. 46 Low findings remain open.
This commit is contained in:
@@ -435,7 +435,7 @@ The `AuditLog` table is append-only and grows by every script-trust-boundary eve
|
||||
- Connection strings are provided via the Host's `DatabaseConfiguration` options (bound from `appsettings.json`).
|
||||
- EF Core manages connection pooling via the underlying ADO.NET SQL Server provider.
|
||||
- The DbContext is registered as a **scoped** service in the DI container, ensuring each request/operation gets its own instance.
|
||||
- **`DbContext` pooling (`AddDbContextPool`) is deliberately NOT used (arch-review WP2.2 — verified, not overlooked).** Pooling requires a context with a single public constructor taking only `DbContextOptions<TContext>`; `ScadaBridgeDbContext` has a second, runtime constructor taking `IDataProtectionProvider`, because the encrypting value converter for secret-bearing columns is built during `OnModelCreating` from that provider — and the model itself *differs* between the two constructors (no provider ⇒ no encrypting converter), so a pooled activator could silently produce a context that reads secret columns as ciphertext. Adopting pooling would mean moving the protector out of the constructor into a `DbContextOptions` extension — a change to the secrets-at-rest path, not a performance refactor — so it is deferred rather than folded into this pass.
|
||||
- **`DbContext` pooling (`AddDbContextPool`) is deliberately NOT used (arch-review WP2.2 — verified, not overlooked).** Pooling requires a context with a single public constructor taking only `DbContextOptions<TContext>`; `ScadaBridgeDbContext` has a second, runtime constructor taking `IDataProtectionProvider`, because the encrypting value converter for secret-bearing columns is built during `OnModelCreating` from that provider — and the model itself *differs* between the two constructors (no provider ⇒ schema-only protector that rejects secret access), so a pooled activator could silently produce a context that reads secret columns as ciphertext. Adopting pooling would mean moving the protector out of the constructor into a `DbContextOptions` extension — a change to the secrets-at-rest path, not a performance refactor — so it is deferred rather than folded into this pass.
|
||||
- No connection management for the Machine Data Database — that is handled separately by consumers (Inbound API scripts, external system gateway).
|
||||
|
||||
---
|
||||
@@ -464,3 +464,35 @@ The `AuditLog` table is append-only and grows by every script-trust-boundary eve
|
||||
### Configuration command transactions (2026-09-09)
|
||||
|
||||
`ConfigurationCommandTransaction` creates a fresh dependency scope and EF context for each execution-strategy attempt. Entity changes and staged audit entries share one transaction, including intermediate saves needed for generated keys. A failure before commit rolls back both. An uncertain commit is not replayed, preventing duplicate creates; the caller must reconcile its result. Postcommit cache actions execute while their dependency scope remains alive; failures are logged without replaying the committed operation.
|
||||
|
||||
### Bounded trace retrieval
|
||||
|
||||
Execution-chain readers use a parameterized permitted-site JSON set throughout the up/down SQL walk and metadata projection. Replay caps chain identifiers at2048, reads at most501ordered candidates sequentially, and bounds DetailsJson in SQL before materialization. Whole events exceeding the remaining UTF-8 budget are omitted and `TraceEventBatch.Truncated` is set; `RootExecutionId` survives independently when an oversized first row cannot be included.
|
||||
|
||||
|
||||
### Global instance identity and protocol credential upgrade
|
||||
|
||||
Instance UniqueName is globally unique in SQL Server, matching name-based routing and lookup.
|
||||
The migration checks for existing duplicates before creating the unique index and fails with
|
||||
an explicit inventory/remediation message. It does not select a winning site or rename deployed
|
||||
instances. Concurrent creation/import conflicts become InstanceNameConflictException and a
|
||||
curated domain failure. SiteId retains a separate lookup index.
|
||||
|
||||
DataConnection.PrimaryConfiguration and BackupConfiguration are protected in full with a
|
||||
purpose-scoped Data Protection converter, using a versioned dp:protocol:v1: prefix. Both SQL
|
||||
columns are nvarchar(max) to accommodate ciphertext expansion. Schema-only contexts reject
|
||||
non-null protocol configuration writes without a real key ring.
|
||||
|
||||
After schema migration/validation and before repositories are used, DataConnectionSecretUpgrade
|
||||
reads legacy rows in batches of 256 and protects both columns with an atomic, binary-comparison
|
||||
compare-and-swap update. Concurrent central startup is safe; reruns validate existing ciphertext
|
||||
without double encryption. No plaintext fallback exists on normal entity reads. Protection failure
|
||||
prevents startup rather than silently using plaintext.
|
||||
|
||||
Upgrade central nodes together: old binaries cannot read the new protected values. SQL migration
|
||||
alone does not encrypt data; the first updated host performs the data upgrade with the shared
|
||||
persisted key ring. Keep a coordinated pre-upgrade database/key-ring backup. Downgrade is rejected
|
||||
while protected protocol rows exist. The existing same-database key-ring storage remains
|
||||
defence in depth; this change does not introduce external key management.
|
||||
|
||||
Trace replay reads keyset pages of eight rows, so EF retry buffering cannot materialize a whole trace before the consumer applies its byte budget. SQL omits an oversized DetailsJson value before transfer; all other projected strings have schema length limits. Canonical serialized UTF-8 size, including escaping and metadata, determines the final 1 MiB limit.
|
||||
|
||||
Reference in New Issue
Block a user