Commit Graph

1298 Commits

Author SHA1 Message Date
Joseph Doherty c1e19ea954 refactor(runtime): DeploymentArtifact decodes TagConfig via shared TagConfigIntent (R2-11, 01/C-1) 2026-07-13 10:47:51 -04:00
Joseph Doherty 7bbec77716 refactor(runtime,opcuaserver): device-host normalization re-homed to Commons DeviceConfigIntent (R2-11) 2026-07-13 10:46:30 -04:00
Joseph Doherty a452a20947 refactor(opcuaserver): composer parses TagConfig once per tag via TagConfigIntent (R2-11, 01/P-1)
Deletes the composer's ExtractTagFullName/Alarm/Historize/Array statics and (co-located
to keep the build green, per plan T5) the three OpcUaServer.Tests ExtractTag* suites whose
tables now live in Commons.Tests/TagConfigIntentTests. T10 is the grep-sweep verification.
2026-07-13 10:42:08 -04:00
Joseph Doherty b6633f7562 feat(commons): DeviceConfigIntent — device-host extraction + normalization home (R2-11) 2026-07-13 10:39:54 -04:00
Joseph Doherty cd3f1270ee feat(commons): TagConfigIntent.Parse — single home for the byte-parity TagConfig intents (R2-11, 01/C-1) 2026-07-13 10:39:07 -04:00
Joseph Doherty 46fedda3e4 Merge R2-02 ResilienceConfig hardening (arch-review round 2) [PR #431]
v2-ci / build (push) Successful in 3m53s
v2-ci / unit-tests (push) Failing after 8m15s
Findings 01/S-6 (High — clamp hostile ResilienceConfig, no ValidationException
driver-brick), 01/U-6 (delete dead bulkhead knob), 01/S-8=03/S12 (Write/Ack
non-idempotent + retry->0), 04/C-7 (JsonObject round-trip preserves unknown
keys), 01/S-7=03/S13 (per-Create options-generation kills respawn cache race).
T15/T18 deferred. STATUS.md conflict resolved additively. Build clean.
2026-07-13 10:29:43 -04:00
Joseph Doherty d8c6357be8 fix(r2-02): factory stamps a per-Create options generation — closes the respawn stale-pipeline race (01/S-7) 2026-07-13 10:23:25 -04:00
Joseph Doherty 5daf7155a8 fix(r2-02): options-generation in the pipeline cache key — stale respawn re-cache unreachable (01/S-7, 03/S13) 2026-07-13 10:22:07 -04:00
Joseph Doherty 901d4e6ed3 Merge R2-03 Virtual-Tag failure quality (arch-review round 2) [PR #430]
v2-ci / build (push) Successful in 4m2s
v2-ci / unit-tests (push) Failing after 8m20s
Findings 02/S13 (VT failure/timeout degrades node to Bad quality, no stale-Good
forever), 02/S12 (evaluator retry-once on disposed-race), 02/P7 (compile-cache
clear gated on changed expression set). T11/T12 deferred. STATUS.md conflict
resolved additively. Build clean.
2026-07-13 10:20:43 -04:00
Joseph Doherty 112f88c9fe fix(r2-02): resilience section — parse-failure lockout + truthful raw pane (04/C-7; live-/run deferred) 2026-07-13 10:19:50 -04:00
Joseph Doherty 9e0a05dc6a fix(r2-02): non-lossy ResilienceFormModel round-trip — preserve unknown keys, never overwrite unparseable stored JSON (04/C-7) 2026-07-13 10:18:31 -04:00
Joseph Doherty a916ae6ebe perf(runtime): gate the VT apply-boundary compile-cache clear on a changed expression set (02/P7) 2026-07-13 10:15:47 -04:00
Joseph Doherty 07eec11d31 docs+test(r2-02): bulkhead doc sweep + parsed-knob-must-be-wired guard (01/U-6, OVERALL theme 1) 2026-07-13 10:15:32 -04:00
Joseph Doherty fea6477f45 Merge R2-01 S7 fault-path hardening (arch-review round 2) [PR #429]
v2-ci / build (push) Successful in 3m37s
v2-ci / unit-tests (push) Failing after 6m49s
Findings 05/STAB-14 (High regression — connect-timeout OCE no longer silently
kills S7 subscriptions), STAB-15 (framing/desync + mid-PDU cancel fatal
classification), STAB-8 seam note. All prod changes in S7Driver.cs; T11 live
outage gate deferred. STATUS.md conflict resolved additively. Build clean.
2026-07-13 10:14:59 -04:00
Joseph Doherty ef00d6e5aa fix(host): retry VT evaluation once on ObjectDisposedException from the apply-boundary cache clear (02/S12) 2026-07-13 10:12:59 -04:00
Joseph Doherty 12b3206555 Merge R2-05 AdminUI authorization (arch-review round 2) [PR #428]
v2-ci / build (push) Successful in 3m27s
v2-ci / unit-tests (push) Failing after 8m29s
Finding 04/C-1 (High): explicit named-policy gating on all 38 AdminUI pages via
new AdminUiPolicies (ConfigEditor + AuthenticatedRead) + anti-drift reflection
guard. T15 live pass deferred (docker-dev auto-admin can't observe deny).
STATUS.md conflict resolved additively. Build clean.
2026-07-13 10:12:57 -04:00
Joseph Doherty 524a0b5606 refactor(r2-02): remove bulkhead fields from the resilience form (01/U-6) 2026-07-13 10:10:06 -04:00
Joseph Doherty 56854139c9 refactor(r2-02): delete the dead bulkhead knob — options + parser (01/U-6); stored JSON keys become ignored unknowns 2026-07-13 10:08:27 -04:00
Joseph Doherty 99b424240a docs(r2-02): correct WriteIdempotentAttribute's unwired claim; disable Write/Ack retry authoring cells (S-8) 2026-07-13 10:07:01 -04:00
Joseph Doherty 20ff67bf9a fix(r2-02): non-idempotent write arm — cache the no-retry snapshot (01/P-4) and record tracker in-flight accounting (S-8 residual) 2026-07-13 10:05:52 -04:00
Joseph Doherty 4640ecb19b docs(r2-06): note WriteLiveValues Quality is a documented-dead input (0.2.0 C-002); keep 192 populated (06/C-7) 2026-07-13 10:04:29 -04:00
Joseph Doherty 92b2ac4891 docs(r2-01): mark EnsureConnectedAsync as the R2-09 connect-throttle seam (STAB-8 note, task 10) 2026-07-13 10:04:22 -04:00
Joseph Doherty 496f97da20 fix(r2-02): write dispatch defaults to non-idempotent — no-retry arm authoritative (03/S12 layer 2) 2026-07-13 10:04:09 -04:00
Joseph Doherty 8999c722a0 fix(r2-01): probe failures mark the handle dead under the gate so the next tick reopens (STAB-15b, task 9) 2026-07-13 10:03:36 -04:00
Joseph Doherty eab0b6be12 fix(r2-02): enforce the spec's no-retry invariant for Write/AlarmAcknowledge overrides (01/S-8, 03/S12 layer 1) 2026-07-13 10:01:54 -04:00
Joseph Doherty 33044061c3 fix(r2-01): cancellation observed mid-PDU marks the connection dead before propagating (STAB-15a, task 8) 2026-07-13 10:01:20 -04:00
Joseph Doherty 57c224c65a fix(r2-02): range-validation warnings on the resilience override form (01/S-6 authoring layer) 2026-07-13 10:00:43 -04:00
Joseph Doherty aac3285468 refactor(adminui): replace authorization string literals with AdminUiPolicies constants 2026-07-13 09:58:42 -04:00
Joseph Doherty dab0029add fix(r2-01): classify ISO-on-TCP framing/desync faults as connection-fatal (STAB-15a, task 7) 2026-07-13 09:58:11 -04:00
Joseph Doherty b77bbd13b7 fix(r2-06): warning parity — malformed-endpoint warning in ServerHistorianOptions.Validate() (06/S-11) 2026-07-13 09:57:29 -04:00
Joseph Doherty b5bf4b73ff fix(adminui): gate every page with named policies — ConfigEditor on the mutating surface (R2-05, 04/C-1) 2026-07-13 09:56:08 -04:00
Joseph Doherty e748f929c8 fix(r2-06): wire AddValidatedOptions<ServerHistorianOptions> + alarm-only-mode startup warning in Program.cs (06/S-11) 2026-07-13 09:55:25 -04:00
Joseph Doherty 8d306a1bd2 fix(r2-02): pipeline Build clamps policy values — building a pipeline can never throw Polly ValidationException (01/S-6 belt-and-braces + brick repro) 2026-07-13 09:55:05 -04:00
Joseph Doherty 5369994e57 fix(r2-06): GREEN — ServerHistorianOptionsValidator (consumer-gated fail-fast on bad Endpoint) (06/S-11) 2026-07-13 09:54:37 -04:00
Joseph Doherty 963543a578 fix(r2-01): filter OCE catches on the owning token in read/write wrappers + poll loop (STAB-14 defense-in-depth, task 4) 2026-07-13 09:54:03 -04:00
Joseph Doherty 294d74c1de fix(r2-02): range-clamp ResilienceConfig overrides in the parser (01/S-6) — timeout/retry/breaker clamped with diagnostics, never brick 2026-07-13 09:53:28 -04:00
Joseph Doherty 01253818de fix(runtime): VT script failure/timeout degrades node quality to Bad once per transition (02/S13) 2026-07-13 09:52:56 -04:00
Joseph Doherty 4bb25fc039 fix(r2-01): surface connect-timeout as TimeoutException, not OCE — poll loops no longer die on unreachable-host outages (STAB-14, task 3) 2026-07-13 09:52:42 -04:00
Joseph Doherty 183b72b7cb feat(security): add ConfigEditor + AuthenticatedRead AdminUI policies behind AdminUiPolicies constants 2026-07-13 09:52:13 -04:00
Joseph Doherty 3108abc13b fix(r2-06): GREEN adapter guard — TryCreate + named InvalidOperationException on bad ServerHistorian:Endpoint (06/S-11) 2026-07-13 09:52:07 -04:00
Joseph Doherty c5ca3e8587 feat(runtime): EvaluationResult carries OpcUaQuality; VT bridge publishes it + historizes BadInternalError on Bad (02/S13) 2026-07-13 09:51:15 -04:00
Joseph Doherty 4be13429b6 fix(r2-02): add ResiliencePolicyRanges single-source-of-truth to Core.Abstractions (01/S-6 task 1) 2026-07-13 09:51:05 -04:00
Joseph Doherty 3b5ef439d4 docs(xmldoc): fix genuine doc issues surfaced by /fixdocs (17 fixes)
v2-ci / build (push) Successful in 3m11s
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Failing after 3m51s
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Failing after 1m50s
v2-ci / unit-tests (push) Failing after 8m37s
- CapabilityInvoker: 3 methods that implement IDriverCapabilityInvoker now use
  <inheritdoc /> instead of duplicating the interface docs (MissingInheritDoc).
- TwinCAT NativeRegistration ctor: added the 8 missing <param> tags (MissingParam).
- Stripped the arch-review bookkeeping IDs STAB-1 (IS7Plc, S7Driver) + STAB-2
  (TwinCATDriver x4) from comments, keeping the descriptive text.

Left the other 86 flagged items untouched — they are checker false positives on
legitimate technical content: interface-implemented events / IEqualityComparer /
IDisposable members whose <inheritdoc/> IS correct (10 InheritDocMisused), and domain
terms the heuristic misreads as tracking IDs (IEEE-754, S7-1200/300/400/1500, PLC-5,
D2-260, S7/Melsec memory addresses, OPC UA Part 9, phase-N design-doc filename refs,
UNS level-N descriptions, and prose like 'the #1 bug source' / failover 'pick #3').
2026-07-10 06:23:12 -04:00
Joseph Doherty 8f7b0017ef Merge branch 'fix/archreview-crit4-twincat-replay' 2026-07-09 06:07:33 -04:00
Joseph Doherty dfc02af3f0 Merge branch 'fix/archreview-crit3-s7-reconnect' 2026-07-09 06:07:33 -04:00
Joseph Doherty 45455b7677 Merge branch 'fix/archreview-crit2-vt-timeout' 2026-07-09 06:07:33 -04:00
Joseph Doherty 0ed30483b4 Merge branch 'fix/archreview-crit1-split-brain-resolver' 2026-07-09 06:07:33 -04:00
Joseph Doherty 75403caa1a feat(archreview #13): apply per-instance ResilienceConfig from the deploy artifact
The DriverInstance.ResilienceConfig column was authored in AdminUI, persisted to the
entity, and serialized into the deployment artifact by ConfigComposer — but the runtime
read path dropped it: DriverInstanceSpec didn't carry it and the invoker factory always
passed null, so every driver got tier defaults regardless of its configured overrides
(a silent dead-config gap — #10's residual sub-finding).

Read-path plumbing (write side was already complete):
- DriverInstanceSpec gains ResilienceConfig; DeploymentArtifact.TryReadSpec reads the column.
- IDriverCapabilityInvokerFactory.Create takes resilienceConfigJson; DriverHostActor.SpawnChild
  threads spec.ResilienceConfig; the concrete factory parses it (ParseOrDefaults, layering on the
  tier), logs any parse diagnostic (never throws), and builds the invoker with the merged options.
- Invalidate-on-change: the pipeline cache keys on (instance, host, capability) and ignores options
  on a hit, so Create() now Invalidate()s the instance's cached pipelines first (no-op on first
  spawn) — a respawn with changed options rebuilds them.
- DriverSpawnPlanner treats a ResilienceConfig change as a stop+respawn (the invoker/options are
  bound to the child at spawn); a pure DriverConfig change stays an in-place delta (no reconnect).
- Host DI passes a logger to the factory for the parse diagnostic.

Verification (deterministic): factory Create applies a retryCount:0 override to actual execution
(control test proves tier default retries), invalidates the instance's cache on re-create (scoped —
sibling survives), malformed config logs+falls-back; planner respawns on ResilienceConfig change
(incl null→json) and stays delta on a pure config change; artifact parse carries/omits the column.
Core.Tests 243 (+5), Runtime.Tests 363 (+5), Host builds clean.
2026-07-08 22:48:17 -04:00
Joseph Doherty eaf78aad90 docs(archreview #11): correct Critical 1 premise — SBR was already active on master, not NoDowning
Akka.Cluster.Hosting's WithClustering enables an SBR downing provider by default
(applies SplitBrainResolverOption.Default when ClusterOptions.SplitBrainResolver is
null), which reads the pre-existing akka.conf keep-oldest block. So the cluster was
NOT running NoDowning before Critical 1 and hard-crash failover already worked — the
typed KeepOldestOption is reinforcing/explicit-in-code, not the sole activator.

Corrects the inaccurate 'HOCON inert / NoDowning / never fails over' framing in:
- ServiceCollectionExtensions.BuildClusterOptions XML comment
- akka.conf split-brain-resolver comment
- docs/Redundancy.md Split-brain section
- SplitBrainResolverActivationTests summary + assertion message (+ method rename)

No code revert (the typed option is correct belt-and-suspenders). Cluster.Tests 29/29.
Surfaced by the #9 hard-kill failover negative control.
2026-07-08 22:34:20 -04:00
Joseph Doherty bacea1a44f fix(archreview #10): wire CapabilityInvoker into prod dispatch via a Polly-free seam
Remediates RESILIENCE-DISPATCH-GAP (surfaced by the 07/C-1 analyzer): the Phase 6.1
CapabilityInvoker resilience pipeline (retry / breaker / bulkhead / telemetry) was
constructed only in tests — the production dispatch layer called driver-capability
methods directly, bypassing it entirely.

The filed plan (thread CapabilityInvoker directly into DriverInstanceActor) is
INFEASIBLE: Runtime is deliberately Polly-free (references Core.Abstractions, not
Core — the same boundary IDriverFactory documents). So this introduces a seam,
mirroring IDriverFactory exactly:

- Core.Abstractions: IDriverCapabilityInvoker (+ NullDriverCapabilityInvoker
  pass-through) and IDriverCapabilityInvokerFactory (+ null factory).
- Core: CapabilityInvoker now implements the interface; new
  DriverCapabilityInvokerFactory builds a per-instance invoker over the
  process-singleton pipeline builder + status tracker + tier resolver.
- Runtime: DriverInstanceActor takes an IDriverCapabilityInvoker (default =
  pass-through) and routes all 6 dispatch sites (write, alarm-ack, subscribe,
  unsubscribe, alarm-subscribe, discover) through it; per-host key resolved via
  IPerCallHostResolver for single-ref calls, driver-instance key for bulk calls.
  DriverHostActor builds + injects the real invoker per spawned driver.
- Host: DriverFactoryBootstrap registers the tracker, pipeline builder, and
  concrete factory (needs DriverFactoryRegistry.GetTier). Runtime SCE resolves the
  factory from DI like IDriverFactory; pass-through on nodes without it bound.
- Analyzer: IDriverCapabilityInvoker.ExecuteAsync/ExecuteWriteAsync are now
  recognized wrapper homes (interface-typed invoker calls must suppress OTOPCUA0001
  too). All 6 RESILIENCE-DISPATCH-GAP pragmas removed — the analyzer (an error in
  Runtime) is now the standing regression guard.

Also adds retry / breaker-open / breaker-close LOGGING to the pipeline builder —
the operator-facing observability surface (the Admin /hosts reader, Phase 6.1
Stream E.2/E.3, was never built, so the pipeline otherwise runs silently).

Scope notes:
- Tier-DEFAULT policy only: DriverInstanceSpec (the deploy artifact) does not carry
  the per-instance ResilienceConfig JSON, so overrides aren't applied yet (tracked
  follow-up: plumb ResilienceConfig through the composer/artifact).
- GenericDriverNodeManager's call is NOT wired: that class is test-only scaffolding
  (zero production references — the Server has its own address-space path); its
  pragma is re-annotated accordingly, not left as a "tracked gap".

Verification (unit + analyzer; live behavioral gate still pending — see FOLLOWUP-10):
- Negative control: unwrapping any site fails the Runtime build with OTOPCUA0001.
- New Runtime guard (recording invoker): write routes via ExecuteWriteAsync with the
  IPerCallHostResolver host; subscribe via ExecuteAsync — proves runtime routing.
- New analyzer test: the interface is a valid wrapper home.
- New pipeline-builder test: retry events are logged.
- Full solution builds clean (0 errors); Runtime.Tests 357, Core.Tests 238,
  Analyzers.Tests 32 all green; pass-through default keeps existing dispatch tests
  byte-for-byte unchanged.
2026-07-08 21:33:16 -04:00