Commit Graph

3859 Commits

Author SHA1 Message Date
Joseph Doherty 0be21be393 Merge b647-mtc-dataset: MTConnect DATA_SET/TABLE as JSON strings; EVENT enumerations parked (#650)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:21:34 -04:00
Joseph Doherty 9631a40f1f fix(mtconnect): resetTriggered only resets data sets; empty set is Good {}; re-baseline test (#650 review)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:20:23 -04:00
Joseph Doherty 4feea47c43 Merge b647-focas-fixture: OtOpcUa-owned FOCAS fixture (#651) + FOCAS backlog docs (#633)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:16:41 -04:00
Joseph Doherty da43278e8e Merge b647-opcua-nskind: retire OpcUaClient TargetNamespaceKind/UnsMappingTable (#648)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:16:41 -04:00
Joseph Doherty f8f8eb9fb8 feat(mtconnect): serve DATA_SET/TABLE as compact JSON strings; park EVENT enumerations (#650)
Parser captures Entry/Cell content; the observation index merges /sample deltas
into the /current baseline (resetTriggered replaces, removed deletes, UNAVAILABLE
drops). Fixture gains a TABLE item and periodic DATA_SET/TABLE churn.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:15:32 -04:00
Joseph Doherty dd05baedd6 refactor(opcuaclient): remove vestigial TargetNamespaceKind + UnsMappingTable (#648)
Nothing read the mapping table beyond the startup validator; v3 UNS projection is
explicit via UnsTagReference. Legacy keys in stored DriverConfig are ignored (pinned).
Reverts the docker-dev opc-plc seed workaround from fdee129f.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:14:37 -04:00
Joseph Doherty 7dd64b8ccd feat(focas): OtOpcUa-owned otopcua-focas-fixture stack (#651) + FOCAS backlog docs (#633)
Fixture renamed off ignitionoee's otopcua-focas-sim, port 8193 published, project label,
default endpoint 10.100.0.35:8193; infra/dev-environment docs. FOCAS.md: MACRO:3901 parts
count, PMC override + cycle-time recipes, won't-do/blocked backlog.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 19:12:13 -04:00
Joseph Doherty 8801631739 Merge b640-601: honest deploy acks — Applied only after the address-space outcome, degraded flag + reason (#601)
v2-ci / build (push) Successful in 4m42s
v2-ci / unit-tests (push) Failing after 20m19s
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

# Conflicts:
#	src/Server/ZB.MOM.WW.OtOpcUa.Runtime/OpcUa/OpcUaPublishActor.cs
2026-10-08 18:00:23 -04:00
Joseph Doherty 0a821417ea fix(deploy): derive the apply-outcome timeout from central's apply deadline (105 s) and name a timeout reason distinctly (#601 review)
60 s labelled a slow-but-clean large rebuild degraded. The deadline moves to a
shared Commons DeployTimings constant that ConfigPublishCoordinator reads, and
the node's timeout is deadline - 15 s margin, so the held ack still lands in
time. A timed-out ack's reason now says the outcome was not reported in time,
distinct from a real sink failure.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:54:09 -04:00
Joseph Doherty af02fce3c4 docs(deploy): degraded-rebuild repair + node-side reason write (#601 review)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:50:27 -04:00
Joseph Doherty 345f40550b fix(deploy): a node with a ConfigDb records its degraded apply on its own NodeDeploymentState row (#601 review)
The apply writes the node's row Applied/null before the address-space outcome
is known; central adds the reason only when it ACCEPTS the ack, so a discarded
(stale/timed-out) ack left a fused node's row reading clean. The prefix moves
to ApplyAck.DegradedReasonPrefix so both writers spell the row identically.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:46:18 -04:00
Joseph Doherty fe7603ec6e fix(opcua): an identical re-deploy after a degraded REBUILD repairs instead of reporting clean (#601 review)
A degraded rebuild advances _lastApplied (deliberately), so re-deploying the
same revision diffed to an empty plan and reported a CLEAN outcome - the
#601 're-deploy re-applies' repair path sealed green over an address space
still known partial. The publish actor now remembers that the last advance
was degraded and, on an empty plan, re-ensures the whole composition
(idempotent, no rebuild), reporting clean only when that pass is clean.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:44:12 -04:00
Joseph Doherty fdee129fd0 fix(docker-dev): opc-plc seed driver needs TargetNamespaceKind=SystemPlatform to initialize (#619)
Live gate: the seeded MAIN-opcua driver failed Initialize ('Equipment but no
UnsMappingTable'), so the opc-plc alarm leg could never connect.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:41:11 -04:00
Joseph Doherty 82ef89416f docs(deploy): degraded-apply ack amendment (#601)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:35:55 -04:00
Joseph Doherty f066fb9c80 feat(adminui): /fleet shows 'Applied (degraded)', /deployments 'Sealed (degraded)' (#601)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:34:58 -04:00
Joseph Doherty 49dfabc141 feat(deploy): coordinator records degraded acks; deployment seals but names degraded nodes (#601)
An Applied+Degraded ack keeps NodeDeploymentState.Status = Applied with
FailureReason 'Applied degraded: <reason>' (no enum/migration change). The
deployment still seals (the revision is running; latest-sealed drives drift)
but Deployment.FailureReason names the degraded nodes and the seal logs at
Warning. Recovered from the DB on singleton failover. Old-node acks (no field,
DPS JSON or gRPC) behave exactly as before.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:32:09 -04:00
Joseph Doherty 90d7f6dfca fix(deploy): hold the Applied ack until the address-space apply outcome is known (#601)
DriverHostActor no longer acks Applied the moment the revision advances. It
asks OpcUaPublishActor for the outcome (RebuildAddressSpace.ReportOutcome) and
acks Applied+Degraded(reason) when the apply was degraded; a missing outcome
times out (60 s, inside the coordinator's 2 min deadline) to a degraded ack,
never a clean one. Re-dispatching a revision whose apply was degraded or is
still unconfirmed re-applies instead of taking the clean idempotent
short-circuit - that re-apply is also what repairs the degraded nodes.
Tests with a bare publish probe use a clean-outcome auto-pilot probe.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:29:45 -04:00
Joseph Doherty 3bf874ae44 Merge b640-619alm: OpcUaClient native alarms route to RawPath conditions + ConditionRefresh; docker-dev opc-plc seed (#619)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:26:47 -04:00
Joseph Doherty 5af9aae2d4 Merge b640-641: native alarm state survives rebuild; projector pruned; AbCip ConditionId=RawPath (#641)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:26:47 -04:00
Joseph Doherty fcae87a5bc docs(alarms): #641 review follow-ups (comment carry, AbCip record, projector ack, residuals)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:20:38 -04:00
Joseph Doherty 17dc171940 fix(opcua): carry the condition Comment across a rebuild; the restore report never rolls Time back (#641 review)
Native conditions support AddComment / commented Acknowledge, so the comment (and its
ClientUserId) is condition state the rebuild dropped. And the deferred restore report
re-stamped Time with the carried instant even when a client method call had reported a
newer event in the restore->complete window; it now reports at the node's current Time.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:20:13 -04:00
Joseph Doherty 521e1eb89b feat(deploy): additive degraded flag on ApplyAck + publish-actor apply outcome reply (#601)
ApplyAck gains trailing Degraded/DegradedReason (defaulted, so an old node's
DPS JSON or gRPC ack decodes exactly as before). mesh_transport.proto adds
ApplyAckMessage.degraded = 6 / optional degraded_reason = 7. OpcUaPublishActor
replies RebuildCompleted(Degraded, Reason) on every handler path when the
RebuildAddressSpace carries ReportOutcome.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:19:57 -04:00
Joseph Doherty 25f02e4102 fix(runtime): the native-alarm projector learns a routed client acknowledgement (#641 review)
#641 keeps the projector's prior state across a deploy instead of resetting it to the
acked default. The projector never saw a client's ack (the SDK applies it to the node and
the host only routes it), so a Clear after ack+deploy now projected Acked=false and
un-acknowledged the operator's ack - previously masked by the per-deploy reset.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:19:24 -04:00
Joseph Doherty 5c88de078c fix(abcip): keep the ALMD last-told state across a re-subscribe so a still-faulted alarm is not re-raised (#641 review)
Now that ALMD transitions route, a re-subscribe (every reconnect, every alarm-set change)
started from an empty per-subscription record and re-RAISED every still-faulted alarm,
which the host projects as Active+unacked - un-acknowledging a condition the server had
just carried across the rebuild with its client ack. The record is now driver-owned and
pruned to the new set, mirroring MTConnect's _deliveredConditions.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:17:55 -04:00
Joseph Doherty 8f5b265db8 fix(opcuaclient): native-alarm ConditionRefresh + transition table, branch, dedup and thread-safety fixes (#619 re-review)
Re-review of 3de2cc3b/454775c6:
- AlarmTransitions could not reach (inactive, unacked): (active,acked)->(inactive,unacked) emitted Clear
  (host stayed acked) and a first-seen cleared-unacked condition emitted nothing. The table is now total;
  an exhaustive test replays every (prior, target) pair through the projector's semantics.
- The per-RawPath read-compute-emit was a lock-free TryGetValue/set across SDK publish threads; it now runs
  under one lock with the projection and emission.
- Condition BRANCH events (non-null BranchId) were projected (flipping the trunk) and remembered as the ack
  target (acking the branch); they are dropped. BranchId is select clause 9.
- EventId is copied, not aliased from the decoded notification.
- An unchanged state re-emitted Unspecified; it now emits nothing unless message/severity changed (an
  Unspecified projects from the host projector's default after a redeploy reset it).
- Exact upstream severity rides OpcUaSeverity (#626) instead of being re-bucketed.
- ShutdownAsync no longer forgets the last-told state: a reinit re-subscribes the same set and the refresh
  would otherwise re-raise every acked alarm. A CHANGED set (host rebuilt) forgets it.

ConditionRefresh: called after the alarm event item is created, after an in-place reconnect (subscriptions
found on the new session by display name, since a recreate clones them) and on RefreshRequired. Refresh
markers are never projected. BadNotSupported/BadMethodInvalid/... logs once. Wire test: a condition already
active before subscribe is reported Raise; a release+resubscribe refresh re-raises nothing (falsified by
disabling the refresh call).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:13:54 -04:00
Joseph Doherty 9b74a65b25 Merge b640-640: driver diagnostics surface on /hosts (#640)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:13:34 -04:00
Joseph Doherty 4128d9c313 test(runtime): de-flake Rediscovery_flag_persists (vacuous AwaitAssert, no publish trigger)
The AwaitAssert lambdas returned bool into an Action, so they never waited;
the test raced the rediscovery publish (5-22% failure on base too). Make the
asserts real and drive the republish with a short health poll interval.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:10:55 -04:00
Joseph Doherty bd23f557f3 docs(alarms): native condition state across a rebuild, projector prune, exact ConditionId routing (#641)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:09:24 -04:00
Joseph Doherty 64d2514e2a fix(abcip): report ALMD transitions with ConditionId = RawPath and a Kind (#641)
Found while verifying item 4 of #641. The host routes a native alarm transition
on an exact ConditionId == RawPath match (DriverHostActor.ForwardNativeAlarm),
but AbCipAlarmProjection emitted "{ref}#active" and no Kind, so every ALMD
raise/clear was dropped as "no alarm condition" — and even if routed, an
Unspecified kind projects no state change. It now reports the subscribed
reference (the RawPath) and Raise/Clear. A host test pins that a decorated id
is dropped.

FOCAS has the same id shape plus a device filter keyed by host address (a
RawPath subscription polls nothing); it needs a mapping decision and is
documented in docs/drivers/FOCAS.md rather than patched here.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:08:47 -04:00
Joseph Doherty 3fe6cbb905 fix(mtconnect): on a changed alarm set forget only the conditions that left (#641)
The driver cleared its whole "last told" record whenever the subscribed alarm
set changed, assuming the rebuild that accompanies an alarm-bearing delta had
reset every condition node. The server now carries each surviving native
condition's state (acknowledgement included) across the rebuild, so re-raising a
survivor would un-acknowledge it. Only entries that left the set are dropped; a
condition that comes back later is told again onto its fresh node.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:06:57 -04:00
Joseph Doherty 11f13f6326 fix(runtime): prune the native-alarm projector on deploy instead of clearing it (#641)
DriverHostActor cleared NativeAlarmProjector's per-condition prior state on
EVERY apply, so after any deploy a driver Acknowledge of a still-active alarm
projected Active=false (writing it inactive) and a Clear of an unacknowledged
alarm projected Acked=true (silently acknowledging it). The projector is now
pruned to the conditions the new composition still carries (RetainOnly).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:05:42 -04:00
Joseph Doherty 454775c67c fix(opcuaclient): derive AlarmTransitionKind from upstream ActiveState/AckedState (#619 review)
NativeAlarmProjector derives Active/Acked ONLY from AlarmEventArgs.Kind, and this driver
never set it - every routed event projected Unspecified, leaving the condition inactive+acked
(only Message/Severity moved). The live-gate expectation 'toggling Active as opc-plc cycles'
could not have passed. Select ActiveState/Id + AckedState/Id (two-element browse paths) and
emit the Kind sequence that moves the projector from the state last told to the host to the
event's (Raise+Acknowledge when a notification carries both).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:04:48 -04:00
Joseph Doherty baf8cc48c3 Merge b640-small: CI readme drift check, SlowUnit tags, LDAP bind pw var, historian/MTConnect docs (#606 #619 #600 #613)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:03:59 -04:00
Joseph Doherty 216dc63a28 Merge b640-focas: FOCAS Status/ subtree, PMC range coalescing, parts-count docs (#257 #266 #258, tracker #633)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:03:53 -04:00
Joseph Doherty 9244ae2c31 docs(mtconnect): feature status table; EVENT enums + DATA_SET/TABLE listed as unsupported (#613)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:03:26 -04:00
Joseph Doherty c86204b7d6 fix(focas): PMC coalescing treats any rc refusal as per-tag fallback, skips transient busy memo, one connect per batch (#266)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:03:25 -04:00
Joseph Doherty 598cb133a8 docs(historian): document deferred non-numeric types and UInt16->UInt4 fallback (#600)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:03:14 -04:00
Joseph Doherty bdf13d29ba chore(docker-dev): LDAP bind password via OTOPCUA_LDAP_BIND_PASSWORD with dev default (#619)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:02:51 -04:00
Joseph Doherty ad108f6a02 test(mqtt): tag 16 wall-clock-bound tests Category=SlowUnit; document local opt-out (#606)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:02:44 -04:00
Joseph Doherty 3de2cc3ba8 fix(opcuaclient): alarm index survives namespace reorder; match condition node first; ack with real EventId (#619 review)
The alarm index was keyed on ns=N strings resolved once at subscribe time, while an
in-place SDK reconnect transfers the subscription without re-running it - a remote
namespace reorder silently orphaned every alarm tag. Keys are now nsu= on both sides.
Events match the authored nodeId against the event's ConditionId first, then its
SourceNode, so several conditions on one source can be authored 1:1. Acknowledge
passes the EventId of the event that named the remembered condition (paired, so node
and EventId can never come from different conditions) instead of an empty one.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:02:21 -04:00
Joseph Doherty 555e8d8047 Merge b640-622: OpcUaClient history + reconnect wire tests (#622)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:02:21 -04:00
Joseph Doherty ddd7678269 fix(opcua): carry native alarm condition state across a full rebuild (#641)
A full RebuildAddressSpace recreated every native Part 9 condition inactive +
acknowledged, and when the alarm-tag set was unchanged (e.g. one
UnsTagReference added to an alarm-bearing raw tag) nothing re-told the new node
its state: the host does not re-subscribe for an unchanged set, and Galaxy /
OpcUaClient have no snapshot to replay anyway. Active alarms read normal until
their next device transition.

OtOpcUaNodeManager now captures each NATIVE condition's live state (from the
node, so a client-applied ack is kept) before the rebuild clears, restores it
when the same id is re-materialised native in the same apply, and reports ONE
condition event per restored off-normal condition at the new
IOpcUaAddressSpaceSink.CompleteAddressSpaceApply (after notifier wiring).
Unclaimed captures are discarded there, so a later re-add is fresh. Quality is
carried silently for normal conditions too (it is only re-annotated on a
connectivity edge). Scripted conditions are untouched (#487 re-asserts them).
OpcUaPublishActor calls CompleteAddressSpaceApply at the end of every apply.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 17:02:12 -04:00
Joseph Doherty 4a93d4cead test(opcuaclient): reconnect + subscription-resume wire test via restartable rig (#622)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:59:58 -04:00
Joseph Doherty 487d394cde docs(focas): Status/ flags, PMC coalescing, parts count via PARAM:6711, read-only policy (#257 #266 #258)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:59:08 -04:00
Joseph Doherty 8d916f2af7 feat(focas): coalesce adjacent PMC reads into one pmc_rdpmcrng (#266)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:59:07 -04:00
Joseph Doherty 73e54656f8 Merge b640-s7 (S7 live codec coverage + STRING/DT BadOutOfRange #632)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:57:59 -04:00
Joseph Doherty 489dc3d50a Merge b640-646 (AbLegacy indirect addressing #646)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:57:59 -04:00
Joseph Doherty c2bdb7d5de feat(hosts): driver diagnostics channel on /hosts, S7 negotiated PDU size first (#640)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:57:38 -04:00
Joseph Doherty dac0747bb4 docs(s7): fixture DB1 size 2048 references
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:57:24 -04:00
Joseph Doherty 73ec6cec0f Merge b640-642: typed native-alarm editor in RawTagModal (#642)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 16:57:05 -04:00