Commit Graph

930 Commits

Author SHA1 Message Date
Joseph Doherty 3c908f1df0 review(AdminUI): fix null-TagConfig crash, CTS leak, unencoded historian tag
Review at HEAD 7286d320. AdminUI-002: IsValidJson null/blank -> friendly error (was
ArgumentNullException). AdminUI-003: DriverStatusPanel Reconnect/Restart dispose CTS (build-
verified, live /run deferred). AdminUI-005: HistorianWonderware picker URL-encodes tag name.
AdminUI-008: Format round-trip test. 001 (script-page authz) + 004 (hub [Authorize]) left
Open as cross-cutting w/ Host/Security.
2026-06-19 10:52:23 -04:00
Joseph Doherty 1aa7905676 review(ControlPlane): fix premature deploy-seal from unexpected-node ack
Review at HEAD 7286d320. ControlPlane-001 (Medium): ConfigPublishCoordinator.HandleAck
now discards acks from nodes not in _expectedAcks (prevented premature SealDeployment) +
regression test. -002 (flipped-node log count), -003 (redundant mapper arms) tidied.
2026-06-19 10:52:22 -04:00
Joseph Doherty 3512089c90 review(Runtime): record findings + fix artifact-decode type tolerance
Review at HEAD 7286d320. Runtime-002/006 (Medium): DeploymentArtifact lenient-parse
now degrades wrong-typed JSON fields to defaults/skipped-row instead of throwing (fails
the deploy) + regression tests; byte-parity with AddressSpaceComposer preserved. Runtime-001
(UNS rename) deferred cross-module (needs AddressSpacePlan rename signal + EnsureFolder
rename). 003/004/005 Won't-Fix.
2026-06-19 10:52:22 -04:00
Joseph Doherty d23e585cdb review(Security): fix login open-redirect (High) + stale LDAP doc
Code review at HEAD 7286d320. Security-001 (High): guard returnUrl with a local-URL
check before redirect (open-redirect/phishing vector) + regression test. Security-002:
update stale LdapOptions dev-LDAP doc reference.
2026-06-19 10:22:59 -04:00
Joseph Doherty b1946194d6 review(Cluster): record findings + fix snapshot consistency, dispose, stale docs
Code review at HEAD 7286d320. Cluster-001 (SeedFromCurrentState reads from one
snapshot), Cluster-003 (HoconLoader double-dispose), Cluster-004 (stale akka.conf
header), Cluster-005 (ServiceLevelCalculator tests added to Cluster.Tests). Cluster-002
deferred (no production caller).
2026-06-19 10:22:59 -04:00
Joseph Doherty 6dc74289ce review(Commons): record findings + add deferred-sink/equip-nodeid tests, fix stale Phase7 doc
Code review at HEAD 7286d320. Commons-001 (stale Phase7 telemetry doc) fixed;
Commons-003/004 close test-coverage gaps (DeferredAddressSpaceSink/ServiceLevelPublisher
forwarding seam + EquipmentNodeIds whitespace branch). Commons-002 (CorrelationId
typing) deferred as cross-cutting.
2026-06-19 10:22:59 -04:00
Joseph Doherty a0dccbf8ee test(drivers): tighten driver-page converter guard + clarify S7 numeric case
Code-review follow-ups: the page-coverage test now asserts every *DriverPage
declares a _jsonOpts serializer (so a new page that serialises config a
different way fails the guard, not just converter removal); clarify that 40 ==
(int)S7CpuType.S71500 in the numeric-throws test.
2026-06-19 05:06:43 -04:00
Joseph Doherty 4b14feb373 fix(drivers): serialize driver-config enums as strings in AdminUI pages + probes
AdminUI driver-instance pages serialized enum config fields (S7 CpuType,
Modbus DataType/Region, AbCip PlcFamily, ...) as JSON *numbers* because each
page's _jsonOpts lacked a JsonStringEnumConverter. The driver factories,
however, deserialize into string-typed DTOs (+ lenient ParseEnum) and throw
when binding a JSON number to a string? — so an AdminUI-authored config
containing any enum field produced a blob the driver could not parse,
faulting the driver on deploy. Proven end-to-end for S7 and Modbus; latent
for AbCip/AbLegacy/TwinCAT/FOCAS/Galaxy/Historian. Only OpcUaClient was safe
(its factory + probe already carried the converter).

Add JsonStringEnumConverter to all 9 driver-instance pages' _jsonOpts and the
8 missing driver probes' _opts (factories unchanged — already string-via-
ParseEnum; strictly more permissive, also lets pages load hand-seeded
string-enum configs back into the form).

Also fix DriverProbeHandshakeE2eTests.AbCip_Green_AgainstSim to probe a real
sim tag (TestDINT) — the no-tags @raw_cpu_type fallback is rejected by the
ab_server sim with ErrorBadParam (a real ControlLogix returns ErrorNotFound,
which the probe treats as reachable; hardware-gated follow-up).

Tests: reflection guard over all driver pages' _jsonOpts (AdminUI.Tests);
factory round-trip + numeric-form-throws guards for S7 and Modbus.

Found by running the never-before-run FB-9/FB-10 live verifies.
2026-06-19 04:52:47 -04:00
Joseph Doherty 7a8ae9600b refactor(opcua): FB-7 review nits — model-event SourceNode=Server, dataType guard, shape tests
Code-review follow-ups on the FB-7 surgical shape-write commit:
- GeneralModelChangeEvent now sets SourceNode=Server + SourceName (Part 3 §8.7.4)
  so clients filtering events by SourceNode match it (report still uses source:null).
- UpdateTagAttributes adds an explicit dataType null/empty guard (widened surface).
- Tighten the ArrayLengthDiffers doc comment.
- Add array→scalar transition test + null-arrayLength zero-default test (coverage
  symmetry). 275/275 OpcUaServer.Tests green.
2026-06-19 03:30:29 -04:00
Joseph Doherty fb094fa566 feat(opcua): FB-7 surgical DataType/array-shape in-place tag writes
Widen the F10b surgical address-space path so a changed equipment tag whose
only differences are DataType / IsArray / ArrayLength (on top of the existing
Writable / Historizing) is applied IN PLACE on the live node instead of forcing
a full RebuildAddressSpace that drops every client's subscriptions server-wide.

ISurgicalAddressSpaceSink.UpdateTagAttributes gains (dataType, isArray,
arrayLength); the DeferredAddressSpaceSink wrapper forwards all six args (the
prod-inertness seam). OtOpcUaNodeManager swaps DataType + ValueRank +
ArrayDimensions in place, and on a real shape change (a) resets the node to
BadWaitingForInitialData so no stale wrong-typed value is exposed (closes the
prior brief-value-type-mismatch objection) and (b) raises a Part 3
GeneralModelChangeEvent (verb=DataTypeChanged) so model-aware clients re-read
the definition. A Writable/Historizing-only change leaves the shape untouched
(no reset, no model event) — original behaviour preserved byte-for-byte.

AddressSpaceApplier.TagDeltaIsSurgicalEligible adds the three shape fields to
its whitelist; FullName/Name/DriverInstanceId/alarm differences still rebuild.

Tests: new NodeManagerSurgicalShapeUpdateTests boots a real server to prove the
in-place swap + value reset + the no-reset backward-compat path + the model-event
builder; AddressSpaceApplierTests invert the two former DataType/IsArray-rebuild
cases to surgical and assert the shape args land; DeferredAddressSpaceSinkTests
assert the shape args forward. 273/273 OpcUaServer.Tests green; full solution builds.
2026-06-19 03:21:03 -04:00
Joseph Doherty 85afb678cd docs(test): mark the audit builder-vs-wiring coverage boundary explicit
Code-review nit: Item C asserts BuildWriteFailureAuditEvent in isolation; the
single in-lock call-site wiring is covered by inspection + the production-proven
path (bb59fd4e). Documented as a deliberate boundary with a promote-if-second-
call-site note.
2026-06-19 02:22:41 -04:00
Joseph Doherty d8254630bb refactor(adminui): trim inline-script label + null-case test + helper-proxy note
Code-review nits: trim the seed name so the in-session dropdown label matches
the server-trimmed persisted name; add a null-selectedId test for
ResolveScriptLabel; and note in CreateNewScriptAsync that the ordering
invariant is proxied by the pure helper (AdminUI has no bUnit).
2026-06-19 02:16:59 -04:00
Joseph Doherty 36eb14e88d feat(opcua): emit Bad blip + AuditWriteUpdateEvent + sync fail-fast on failed device write 2026-06-19 02:14:58 -04:00
Joseph Doherty e047af0553 refactor(adminui): harden SetFullName (reject blank ref) + cover alarm-flag re-pick
Code-review nits: SetFullName now throws on a blank reference (was silently
persisting FullName:null → silent deploy-time bind failure), and a new test
covers the alarm-typed re-pick combo (SeedDefaultAlarm over an already-edited
alarm leaves it intact).
2026-06-19 02:08:43 -04:00
Joseph Doherty da57c307a7 fix(adminui): refresh script dropdown label after inline create
After inline "New script" creates an SC-… id, the entry is now added
to _scripts BEFORE _form.ScriptId is set so the <InputSelect> has a
matching <option> on first render and the displayed label is correct.
Extracts VirtualTagModalHelpers.ResolveScriptLabel as a testable pure
helper (5 new unit tests in VirtualTagScriptDropdownTests).
2026-06-19 02:06:51 -04:00
Joseph Doherty 2dd723e195 fix(adminui): preserve edited alarm fields on Galaxy address re-pick 2026-06-19 02:02:06 -04:00
Joseph Doherty f81fa76809 test(cli): assert Enable/Disable object node + add suffix-idempotency test
Closes the code-review gap: the Enable/Disable success tests now assert the
derived '.Condition' object node (the CallMethodObjectIds capture was added
but unused), and a new test proves an already-suffixed condition node isn't
double-suffixed (mirrors AcknowledgeAlarmAsync_LeavesConditionSuffixAlone).
2026-06-19 01:57:53 -04:00
Joseph Doherty eede0c926b test(opcuaclient): add AcknowledgeAsync stale-session race test + trim fallback delay
Closes the code-review coverage gap: AcknowledgeAsync now has its own
swap-across-the-gate regression test (CallAsync lands on the post-gate
session), the subscribe-path coverage gap is documented, and the bounded
fallback drops from 2s to 250ms (the buggy-path signal is a synchronous
pre-await read, so it always wins well inside the bound).
2026-06-19 01:56:56 -04:00
Joseph Doherty 88d5ba45bf feat(cli): add enable/disable condition commands (H4 client path) 2026-06-19 01:48:21 -04:00
Joseph Doherty eb328e5761 fix(opcuaclient): re-resolve session inside _gate in history/read paths (stale-session race) 2026-06-19 01:41:19 -04:00
Joseph Doherty 3eb370d4ea refactor(adminui): explicit discard on fire-and-forget audit + Untrust not-found test (review)
v2-ci / build (push) Failing after 49s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
2026-06-19 00:45:10 -04:00
Joseph Doherty 3ee0099fae refactor(adminui): omit error key on success cert-audit rows + assert OccurredAtUtc (review) 2026-06-19 00:38:41 -04:00
Joseph Doherty 23d45efcfd feat(adminui): audit cert Trust/Untrust/Delete to ConfigAuditLog 2026-06-19 00:36:54 -04:00
Joseph Doherty de6ce147fc fix(runtime): drop OpcUaProbeResult in redundancy-topic subscribers (no dead-letter) 2026-06-19 00:32:03 -04:00
Joseph Doherty 084d73ea2b feat(adminui): IAuditWriter adapter + cert-action audit-event factory 2026-06-19 00:31:37 -04:00
Joseph Doherty 40e8a23e7c refactor(opcuaserver): rename Phase7* address-space pipeline to AddressSpace*
v2-ci / build (push) Failing after 37s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
The OPC UA address-space build pipeline was named after a v2-roadmap
milestone number rather than its domain. Rename the family to describe
what it does (build/diff/apply the OPC UA address space):

  Phase7Composer          -> AddressSpaceComposer
  Phase7CompositionResult -> AddressSpaceComposition
  Phase7Planner           -> AddressSpacePlanner
  Phase7Plan              -> AddressSpacePlan
  Phase7Applier           -> AddressSpaceApplier
  Phase7ApplyOutcome      -> AddressSpaceApplyOutcome

The 9 Phase7*Tests suites follow suit; Phase7ScriptingEntitiesTests ->
ScriptingEntitiesTests (it tests the scripting migration, not the
pipeline). Log-message prefixes move to the new class names.

Pure mechanical rename, no behavioral change. EF migration classes/IDs
(AddPhase7ScriptingTables, ExtendComputeGenerationDiffWithPhase7) are
immutable and left untouched, as are historical design docs.

Build clean; OpcUaServer 261/261, Runtime 272/272, ScriptingEntities
12/12 green.
2026-06-18 19:16:28 -04:00
Joseph Doherty 22b0611fb9 fix(opcua): forward ISurgicalAddressSpaceSink through DeferredAddressSpaceSink (F10b surgical path was inert in prod) 2026-06-18 13:57:53 -04:00
Joseph Doherty b472bba384 test(opcua): pin F10b surgical guard — alarm-bearing tag rebuilds + multi-delta surgical success 2026-06-18 13:44:26 -04:00
Joseph Doherty f6618144a4 perf(opcua): surgical in-place tag-attribute writes (Writable/Historizing) avoid rebuild (F10b) 2026-06-18 13:37:56 -04:00
Joseph Doherty b05c281132 test(opcua): pin F10b boundary — vtag EquipmentId-change + multi-vtag-mixed still rebuild 2026-06-18 13:11:53 -04:00
Joseph Doherty fac4418708 docs(client): document Galaxy fallback operator-comment limitation + pin null behavior
Closes backlog #12: Galaxy's AckMsg attribute is WRITE-ONLY and the OPC UA
event SelectClause carries no comment field, making OperatorComment
unrecoverable on the sub-attribute fallback path. Documents the two concrete
reasons in-code and tightens the AlarmEventArgs XML doc; adds pinning test
OnAlarmEvent_GalaxyFallback_LeavesOperatorCommentNull.
2026-06-18 13:06:37 -04:00
Joseph Doherty 6c6a2c4203 perf(opcua): skip address-space rebuild for vtag expression/deps/historize-only edits (F10b) 2026-06-18 13:03:50 -04:00
Joseph Doherty 4022457000 test(focas): integration test for cnc_getfigure position scaling via focas-mock 2026-06-18 12:44:35 -04:00
Joseph Doherty c8ab8fc348 test(abcip): Emulate-tier nested-UDT live-gate smoke + docs (backlog #6)
Add AbCipEmulateNestedUdtTests (skip-gated, AB_SERVER_PROFILE=emulate) to close
the live-gate gap for nested-struct UDT discovery via CIP Template Object (class 0x6C)
threaded by commits 3d8ce4e8/d203f31c. Compiles + skips cleanly against ab_server
(no CIP Template Object service). Update docs/drivers/AbCip.md nested-struct section
to record the shipped decode path, the Emulate-only live-gate, and offline unit coverage.
2026-06-18 12:40:04 -04:00
Joseph Doherty 70aad3ef48 test(historian): cover PerEventStatus length-mismatch fallback + Retry status mapping 2026-06-18 12:37:31 -04:00
Joseph Doherty e5f568d01f docs(focas): retire stale 'wire backend returns no figures' comments after cnc_getfigure shipped 2026-06-18 12:32:55 -04:00
Joseph Doherty feddc2b80e feat(historian): emit PermanentFail for poison alarm events via additive PerEventStatus sidecar IPC field 2026-06-18 12:30:14 -04:00
Joseph Doherty f320f323ae feat(focas): add cnc_getfigure wire command + focas-mock handler 2026-06-18 12:23:14 -04:00
Joseph Doherty cb062fce90 feat(adminui): driver-snapshot GetAll() + pure Hosts driver-view builder (#8) 2026-06-18 11:40:37 -04:00
Joseph Doherty 3d8ce4e85f feat(abcip): thread nested-struct template id so nested UDT members are addressable (#6) 2026-06-18 11:33:41 -04:00
Joseph Doherty 0414a04892 test(adminui): review fixes — locked-snapshot assert, stable fake health, InitializeCount property 2026-06-18 09:18:37 -04:00
Joseph Doherty f87ad5ae8f test(adminui): E2E deployed-driver Healthy->Reconnecting->Healthy transition on Reconnect 2026-06-18 09:12:15 -04:00
Joseph Doherty ffb725e4c1 test(harness): production-fidelity DI (AddOtOpcUaRuntime) + opt-in fake driver factory 2026-06-18 08:55:35 -04:00
Joseph Doherty 7a3d2712c0 test(opcuaclient): event-history smoke + docs(historian): driver event passthrough
v2-ci / build (push) Failing after 43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
2026-06-18 06:12:51 -04:00
Joseph Doherty 045f9ca2e8 feat(opcuaclient): implement IHistoryProvider.ReadEventsAsync passthrough 2026-06-18 06:07:32 -04:00
Joseph Doherty d48674ba31 fix(opcuaclient): review — UTC-kind the missing-time sentinel + test hardening
Code-review I2: CoerceDateTime's missing-field sentinel was DateTime.MinValue
(Kind=Unspecified) — a downstream .ToUniversalTime() could shift it; now UTC-kinded.
M4: assert BrowsePath namespace==0 + the sentinel's UTC Kind.
2026-06-18 06:05:58 -04:00
Joseph Doherty e859963853 feat(opcuaclient): add BuildBaseEventFilter + MapHistoryEvents pure cores 2026-06-18 06:02:11 -04:00
Joseph Doherty b47fc10ec0 feat(adminui): CertificateStoreManager — by-thumbprint trust/untrust/delete 2026-06-18 05:04:36 -04:00
Joseph Doherty 15922d8483 test(galaxy): live-gw smoke — writer borrows subscription handle, skips AddItem
Subscribe a real tag, register its gateway item handle, write via the
registry-wired writer: asserts the borrowed-handle write commits Good with
AddItemCallCount==0 (control with no source: ==1). Proves the subscription
handle is usable for a committing no-login supervisory write. Skip-gated on
MXGW_ENDPOINT + GALAXY_MXGW_API_KEY; verified live vs 10.100.0.48:5120 (3/3).
2026-06-18 04:34:11 -04:00
Joseph Doherty e9da9c29d2 fix(galaxy): authoritative handle resolution + review cleanups
Make SubscriptionRegistry.TryResolveItemHandle confirm a live subscription
genuinely binds fullRef->handle (via the reverse index) rather than trusting
the forward-map hint + a bare liveness check. Fixes the cross-ref-same-handle
hazard (wrong-tag borrow) while preserving the legitimate
multiple-subscriptions-per-tag borrow. Adds cross-ref + same-ref-multi-sub
tests; drops a duplicate SubscriptionEntry <summary>; documents the writer's
supervisory-advise reconnect lifecycle.
2026-06-18 04:29:45 -04:00