Commit Graph

1053 Commits

Author SHA1 Message Date
Joseph Doherty 5fdf205caa test(historian): live gate PASSED on docker-dev (SqlServer backend); CLI prints node-level HistoryUpdate result
Gate: Primary-only typed recording with real status, raw+UNS HistoryRead/Processed parity, HistoryUpdate
insert/duplicate/replace/delete + WriteOperate-only and anonymous denied, failover at 1x, Modbus device loss
stored as BadCommunicationError then a BadNoCommunication outage marker. Gate found the CLI hid the
node-level refusal code (fixed + pinned) and a pre-existing OpcUaClient comms-loss gap (filed separately).
docker-dev compose gains ServerHistorian__Backend / __SqlServer__ConnectionString (default Gateway).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 15:14:46 -04:00
Joseph Doherty 159d18de59 docs(plans): record the fix-wave rulings in the design's as-built notes
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 14:33:45 -04:00
Joseph Doherty 60a1e7e669 docs(historian): fix-wave docs and stale comments
- Historian.md: Empty samples always Bad (writer/updater/read clamp),
  long-name refusal, 5 s bootstrap failure cache, AtTime after now, info-bit
  masking, HistoryUpdate does NOT type-check (M-1 decision), DeleteRaw
  needs both bounds, reverse Raw paging, the historian/historization metric
  prefixes, and provisioning numeric-only lines qualified as Gateway.
- CLAUDE.md numeric-only lines qualified as Gateway backend.
- Stale 'sole historian' lines in DriverLifecycle, ServiceHosting and the
  retired Wonderware stub; Client.CLI exit-code contract.
- ServerHistorianOptions: Endpoint/Backend XML docs untangled, Validate()
  indented; Program.cs comments are backend-neutral; T10 week-test day count.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 14:33:30 -04:00
Joseph Doherty cc34daf181 docs(historian): pluggable backends, SQL Server backend, HistoryUpdate, outbox v2, spec corrections
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 13:51:56 -04:00
Joseph Doherty d6afb39413 docs(historian): design §4 AtTime — inside an outage returns the Bad neighbour's code (T10 review)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 13:25:31 -04:00
Joseph Doherty 65e6352dad Merge ph-T8 (pluggable historian)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 13:00:51 -04:00
Joseph Doherty 1447a50140 Merge ph-T7 (pluggable historian)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 13:00:22 -04:00
Joseph Doherty 17962d4233 feat(historian): SQL Server backend skeleton - schema DDL, locked bootstrapper, options, connection/mapper/tag-catalog helpers, status consts, env-gated fixture
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 12:52:15 -04:00
Joseph Doherty 9973ae08be docs(historian): design §9.10 — comms-loss IS historized as an outage marker (T3 review correction)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 12:51:34 -04:00
Joseph Doherty 70dde115f0 feat(cli): historyupdate command (insert/replace/update/delete-raw/delete-at-time)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 12:47:53 -04:00
Joseph Doherty a6643cedf9 docs(historian): fold Fable pre-execution review into design + plan (3 blockers, 12 should-fix, 11 nits)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 12:40:35 -04:00
Joseph Doherty ba727da2dd docs(historian): pluggable historian backends — design + implementation plan
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 12:21:34 -04:00
Joseph Doherty 1ea1da8236 refactor(runtime): delete the dead in-place ApplyDelta seam; config change = respawn only (#665)
Since #516 a DriverConfig change is stop + respawn, so DriverSpawnPlan.ToApplyDelta
was always empty and the in-place path had no producer. #663 and #664 were both
latent bugs in it. This deletes it:
- host: ApplyChildDelta, _pendingDelta, HandleApplyResult and HandleDeltaApplied
- plan: DriverSpawnPlan.ToApplyDelta
- actor: the ApplyDelta, ApplyResult and DeltaApplied messages, the Connected
  reinit handler and the mid-connect AdoptConfigDuringInit
- tests: the actor tests that drove the seam

IDriver.ReinitializeAsync and each driver's implementation and unit tests stay.
Comments and docs that cited AdoptConfigDuringInit as the reason a driver must
release prior state at init now cite the IDriver contract and overlapping
retries, which still apply.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 11:23:16 -04:00
Joseph Doherty 97fcf624ae fix(drivers): mid-connect ApplyDelta adopts config in Calculation, refuses it in Galaxy (#664)
AdoptConfigDuringInit re-runs InitializeAsync(newConfig) and acks success, but
Calculation and Galaxy ignored the JSON there: Calculation kept its old tag
set, and Galaxy connected on its old endpoint. Calculation now rebuilds its tag
table from a config with a body (sharing AdoptConfig with ReinitializeAsync and
stopping any running timer scheduler first). Galaxy, which cannot hot-swap its
options, refuses a non-equivalent one the way ReinitializeAsync does. The actor
now sends DeltaApplied once the adopted init succeeds, so a dependency
consumer's mux interest is re-registered. Unreachable from deploys since #516.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 11:05:58 -04:00
Joseph Doherty 39c5af3d7f fix(runtime): in-place ApplyDelta releases and re-subscribes data + alarms (#663)
A Connected ApplyDelta called ReinitializeAsync and kept the actor's data and
native-alarm handles, though every driver's reinit drops the driver-side
subscriptions behind them (#630). The reconcile only re-subscribes a missing
handle, so the driver went silent until a reconnect. Release both at the
driver before the reinit and re-subscribe both in a finally. Unreachable from
deploys since #516; pinned by a two-case test, each case failing against a
different wrong fix.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 07:49:56 -04:00
Joseph Doherty 5067509dd4 fix(runtime): keep native-alarm feed across data re-subscribes; reconcile retries alarms (#662)
A data re-subscribe (every deploy's bulk SetDesiredSubscriptions push to a
kept driver) dropped the prior data subscription through the full
DetachSubscription, which also unhooked OnAlarmEvent and forgot the alarm
handle. On any deploy that did not respawn the driver, the alarm
subscription kept logging "established" while every event reached nobody
until a reconnect or respawn. The data path now uses DetachDataSubscription;
only a lost connection and PostStop detach both.

The #488 health-poll reconcile only looked at data, so an alarm subscribe
that failed on (re)connect gated the feed for the life of the connection.
ReconcileAlarmSubscription now re-attaches a missing handler and
re-subscribes a stale/missing alarm handle every tick.

OpcUaClient removes a half-built alarm subscription from its session when
SubscribeAlarmsAsync throws, so the new retries cannot accumulate.

Tests: actor-level data-resubscribe + reconcile-retry tests and an
end-to-end NativeAlarmFeedSurvivesDeployTests through DriverHostActor; all
three fail against the unfixed actor.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 07:30:25 -04:00
Joseph Doherty ea220363f3 Merge hw5-657 (#657)
v2-ci / build (push) Successful in 5m44s
v2-ci / unit-tests (push) Failing after 24m4s
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 07:15:30 -04:00
Joseph Doherty b394413e30 Merge hw5-592 (#592)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 07:15:30 -04:00
Joseph Doherty 24eed8addd docs(opcuaclient): #657 review — correct stale rebuild comment, note cold-host residual
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 07:15:26 -04:00
Joseph Doherty a7c27e978f docs(abcip): #592 native array-element write on Z35162 (_SEQ_EP[31], user-approved, restored)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:41:14 -04:00
Joseph Doherty c36224d956 test(abcip): hw5 review fixes — HardwareGate guard, (host, tag) allow-list, differential-run refusal (#592)
- HardwareGateGuardTests: only StringWriteGateTests may name a write path in HardwareGate/; the
  allow-list is exactly the two Z-35162 (10.209.70.160) STRING tags; the opt-in, the (host, tag)
  check and the AB_DIFF_ENDPOINT refusal are present. Proven red by a WriteAsync mutation.
- StringWriteGateTests: allow-list entries are (host, tag); any other endpoint is refused; skips
  while AB_DIFF_ENDPOINT is set, like IntegrationWriteGate.
- AbCip.md #566: the close happened somewhere in the 16-60 s window; RST vs FIN not captured.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:21:55 -04:00
Joseph Doherty 1f983d610f test(abcip): hw5 hardware gate — #566 idle close drops TCP; native STRING write on Z-35162 (#592)
A. #566 on a real CompactLogix (Z-35162, read-only): a class-3 connection idled 60 s past its
16 s timeout is closed at the TCP level, not with CIP 0x0107/0x0203; the next request fails on
its write (Broken pipe -> BadConnection) in 9 ms and the one after reopens. The transport already
handles this, so no production behaviour changed; the shape is pinned offline by
CipConnectionTests.A_target_that_resets_an_idle_session_... (FakeEipServer.ResetAllConnections),
proven red with a reconnect-skipping mutation. The diagnostics-only exchange tap now also sees
Forward Open replies (granted RPIs).

B. Native STRING write through the driver on Program:ES03_TemperatureControl._StrTmp: write,
read back, restored byte-exact (user-approved). Array write NOT run: the census's unreferenced
DINT[32] _SEQ_EP arrays are aliased sequencer step registers (raw L5X grep), so none qualifies.
#567 still impossible (a 128 B array / 88 B STRING never fragments).

New env-gated harness: AbCip.IntegrationTests/HardwareGate (IdleConnectionEvidenceTests,
StringWriteGateTests). Docs: gate results (hw5 section + raw output), AbCip.md, CLAUDE.md.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:16:16 -04:00
Joseph Doherty 48a80b5962 fix(alarms): atomic InactiveUnacknowledged + flagged Clear/Acknowledge first sight for OpcUaClient (#657)
After an OpcUaClient respawn an upstream inactive+unacked condition went out as
Retrigger+Clear (duplicate Activated+Cleared rows, Active flicker), and a
first-sight inactive+acked one as Unspecified (an Activated row for an inactive
alarm; a retained-Active host condition never cleared).

- New AlarmTransitionKind.InactiveUnacknowledged: one projection, rows only for
  what changed (Activated+Cleared from acked, Cleared from active+unacked, none
  when already inactive+unacked).
- OpcUaClient uses it for moves into inactive+unacked; first sight of an
  inactive condition is InactiveUnacknowledged or Clear+Acknowledge, flagged.
- Projector: a flagged Acknowledge on an already-acked condition writes no row.
- E2E tests through DriverHostActor.ForwardNativeAlarm (red-proven).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:08:39 -04:00
Joseph Doherty c0fbe74693 fix(ablegacy): Test Connect normalises the probe address like the driver (#660)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 06:01:35 -04:00
Joseph Doherty 7ba86f7857 fix(ablegacy): refuse every libplctag write the #659 translation changes (fix round 2)
Review M1: round 1 refused only the rewritten status bits, so S:/O:/I: writes - which
never worked on libplctag because tag creation failed - would have started reaching
the PLC (outputs, status-file words) on the default transport. WriteRefusal now keys
on LibplctagLegacyName.IsTranslated: file number supplied (S/O/I, incl. bit writes
like O:0/3, refused ahead of the parent-word RMW) or status bit rewritten. Reads
unaffected. Docs/comments now true; gate doc notes R bit-number wire evidence is
non-discriminating (L1). 12 new cases fail with the round-1 predicate.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 05:43:50 -04:00
Joseph Doherty 7dbb060f92 fix(ablegacy): refuse libplctag writes to translated status bits (#659 fix round 1)
The read translation R6:0.DN -> R6:0.CON/13 made those names parse in libplctag, which
would have turned a write - previously impossible, tag creation failed - into a masked
write to the control word. LibplctagLegacyTagRuntime.WriteRefusal (via
IAbLegacyTagFactory.WriteRefusal, same plumbing as native) now refuses writes to every
rewritten status bit (R EN/EU/DN/EM/ER/UL/IN/FD, C UA) with BadNotSupported before a
runtime is created; the runtime also refuses as defence in depth. Reads stay
translated. T/C status bits libplctag parses itself keep their pre-existing behaviour.
13 new tests fail with the predicate disabled.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 05:38:24 -04:00
Joseph Doherty 0abce36ca3 fix(ablegacy): translate S/O/I and R status bits for libplctag's PCCC parser (#659)
libplctag 2.6.0 (bundled by libplctag 1.5.2 / NativeImport 1.0.41) pccc.c needs an
explicit file number (parse_pccc_file_num; S is only the status file when a digit
follows) and has no R-file status-bit mnemonics (sub_element_lookup: con/len/pos only;
a /bit is refused on a 6-byte element). So the canonical S:0 (the default probe),
O:0/3, I:0/0 and R6:0.DN failed with BadCommunicationError on a real MicroLogix 1400
while native read them.

LibplctagLegacyName (libplctag adapter only) now emits S2:/O0:/I1: (the native
encoder's implicit file numbers) and R6:0.CON/<bit> for status bits libplctag lacks
(R EN/EU/DN/EM/ER/UL/IN/FD, C UA), bit numbers taken from the native table. The
authored grammar is unchanged. Unit tests pin the translation per family and check,
through a model of the pccc.c parser, that the translated name addresses exactly
what the native encoder does (46 fail without the fix). Live: Z-33323 idle, reads
only - libplctag now equals native for S/O/I/R status bits; controls unchanged.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 05:34:09 -04:00
Joseph Doherty e0e8c4267a docs(ablegacy): drop stale simulator-only claims after the MicroLogix evidence
v2-ci / build (push) Successful in 4m54s
v2-ci / unit-tests (push) Failing after 20m29s
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 05:23:40 -04:00
Joseph Doherty beedac8218 docs(ablegacy): #592 real MicroLogix 1400 evidence (native reads/writes pass; libplctag fails S/O/I/status bits)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 05:23:28 -04:00
Joseph Doherty 12aa293cdc docs(abcip): #611 live member-write gate on Z35162 (native DINT/BIT/REAL members, restored)
v2-ci / build (push) Successful in 4m47s
v2-ci / unit-tests (push) Failing after 23m3s
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:49:19 -04:00
Joseph Doherty 6c6800e513 Merge hw4-udtwrite (#592/#611)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

# Conflicts:
#	docs/drivers/AbCip.md
2026-10-09 04:32:03 -04:00
Joseph Doherty bd20978568 Merge hw4-alma (#592/#611)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:31:54 -04:00
Joseph Doherty f85fc8fbeb fix(abcip): #611 review round 1 — native keeps BOOL type distinction, WriteWidthRefusal tests, doc caveats
- L2: WriteWidthRefusal leaves a BOOL over a wider value to a runtime that sees the CIP
  type (native): mistyped integer member -> BadTypeMismatch (as its read), 0xD3 BOOL-array
  DWORD -> BadNotSupported; libplctag keeps BadNotSupported for both (size only).
- L4: direct theory over WriteWidthRefusal (1/2/4/8 widths incl LINT/ULINT/LREAL, String/Dt,
  unknown size) + array/bit-selector exemptions with a non-vacuous control.
- L1: CLAUDE.md member/BIT-member writes are ab_server-proven only.
- L3: patch 0003 blind spots documented (Docker README, AbServer-Test-Fixture.md); nits.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:31:45 -04:00
Joseph Doherty 5d7c53bb19 fix(abcip): ALMA review round 2 - IsRestatement contract docs, ALMD no-InAlarm warning, un-ack retrigger
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:30:56 -04:00
Joseph Doherty 5018627b35 feat(abcip): #611 discovered UDT member writes + whole-UDT refusal by decision
Item 2 — controller-discovered UDT members are writable on both transports once
browse-committed and authored ReadWrite (writability is the operator's: the commit
lands AccessLevel Read with no writable key). The write names the member (one CIP
symbolic segment per member), its own type, its own bytes. BIT members are supported:
Z-35162 (read-only probe) exposes a UDT BIT member as a 1-byte 0xC1 BOOL, so the
controller applies the bit and the hidden host's other bits are never in the request.

- AbCipDriver: pre-write width check on BOTH transports (libplctag patched part of
  its read buffer and sent it all): BadTypeMismatch, Bool over a wider value
  BadNotSupported; nothing sent, handle kept, health untouched, warn once.
- A runtime NotSupportedException is a refusal: no longer latches Degraded.

Item 3 — whole-UDT writes refused BadNotSupported before any runtime exists, by
decision (a structure write overwrites hidden hosts + padding), documented.

ab_server patch 0003: serve member paths from dotted flat tags (UdtSeg.*), and
accept libplctag's pad byte on 1-byte writes (stock ab_server refused every
libplctag BOOL/SINT write with 0x03). New AbCipUdtMemberWriteTests (both
transports), AbCipDiscoveredMemberWriteTests, native request-byte theory, and a
browse-commit mapper pin. Docs: AbCip.md Member writes / Whole-UDT writes / Known
gaps, fixture docs, CLAUDE.md AB section.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:23:59 -04:00
Joseph Doherty 83d8de303c fix(abcip): ALMA review round 1 + ALMD polls InAlarm (not InFaulted)
M1 drop-out is a no-row Unspecified+IsRestatement update (projector case); M2 ack judged over every declared
limit; L1 ActiveAcknowledged for an already-acked new limit; L2 Bad Acked/severity carried/skipped; L3 host
end-to-end test; L4 BOOL member-by-member tests + mixed ALMD/ALMA; L5 detection requires no plain Acked.
ALMD active bit is InAlarm per Rockwell's ALMD reference; InFaulted is input health.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:22:45 -04:00
Joseph Doherty 9e894decb0 docs(ab): hw4 evidence fix round 1 - bit-write gap provenance, README/CLI/fixture docs, libplctag-deletion gap
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:12:53 -04:00
Joseph Doherty 14ec145487 test(ab): hw4 evidence - mixed MSP read (#582), listing order (#572) on the press; AbLegacy bit-write test; gate-results + docs
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:10:22 -04:00
Joseph Doherty b3838fcce9 feat(abcip): ALMA (ALARM_ANALOG) alarm projection (#611 item 1)
Detect ALMA by declared *InAlarm members; one condition per tag keyed on RawPath; severity/message from
the most severe active limit; Raise/ActiveAcknowledged/Retrigger/Clear/Acknowledge; ack writes OperAckAll.
Unit-tested over fake runtimes only (no ab_server ALMA support, Z35162 has none).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 04:09:47 -04:00
Joseph Doherty 82750cccb9 Merge hw3-alarms (#656 atomic ActiveAcknowledged + re-statement flag for native alarms)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 00:14:47 -04:00
Joseph Doherty 862d8a756b Merge hw3-history (#655 delete dead driver-side history capability + client surfaces Bad HistoryRead)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-09 00:14:47 -04:00
Joseph Doherty e138e39778 docs(alarms): #656 re-statement flag + ActiveAcknowledged kind (AlarmTracking, OpcUaClient, Galaxy, ScriptedAlarms, CLAUDE.md)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 23:43:09 -04:00
Joseph Doherty 2659a690a6 fix(#654): review fixes - non-vacuous stop-on-publish test, Error only when retry budget refuses
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 23:39:06 -04:00
Joseph Doherty 67059e479f docs(#655): history is server-side only; rewrite OPC-009 and stale driver-history references
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 23:36:25 -04:00
Joseph Doherty 9dc6390d50 fix(#654): bounded fast retry while the known-cluster gate is unpublished
CentralCommunicationActor: while the PSK provider is unresolved or the first
ClusterNode load failed, re-arm RefreshContacts every 2 s (max 15) until the
first publish; Information log on first publish; 'NEVER be published' Error
now only after the retry budget is exhausted. Corrected false comments.
Tests: late provider (Grpc+Dps), failed first load, retries stop, bounded budget;
ControlPlaneStarterOrderTests back to the default 60 s refresh.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 23:32:38 -04:00
Joseph Doherty 585fd8e29a Merge hw2-makino: hardware live-gate fixes (hw2-makino)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 21:35:10 -04:00
Joseph Doherty a92fb81f00 fix(twincat): review — a connect failure no longer burns the sum-chunk warning; doc precision
- ReadBucketAsync: the once-per-device "sum-read chunk failed" Warning fires only after a successful
  connect; a connect/backoff failure is the connect path's to report. TwinCATSumChunkWarningTests pins
  it (no warning across connect failures, exactly one across two later failing sum polls; red without
  the guard) plus a source guard that ReadGroupAsync builds its SumRead only via CreateSumRead.
- Docs: diagnostics live-gated via the driver harness (/hosts not exercised); TC2 costs 8 reads on the
  first tick and after each reconnect, then <= 4; read counts attributed to the unit tests.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 21:26:26 -04:00
Joseph Doherty 664ff75ee2 fix(twincat): #645 live gate — sum-read never worked on hardware; TC2 diagnostics fallback
Read-only live gate on Elma presses Z35474 (TC3 3.1.4024) and Z34834 (TC2 2.11):

- Sum-read: every chunk came back BadCommunicationError. Beckhoff's
  SumRead(IList<IgIoSumReadEntity>) ctor casts to IList<SumDataEntity>, which
  throws InvalidCastException for a List<> (only an array survives via
  covariance). The fake-client tests never built a real SumRead. New
  AdsTwinCATClient.CreateSumRead passes an array; TwinCATSumReadConstructionTests
  pins it (red on the List form) plus a falsifiability control on the library.
  After the fix: 59 tags, status identical everywhere, values identical on every
  non-moving tag, ~0.35-0.66 s vs ~2.7 s per poll; TC2 CE 2.11 answers sum reads.
- An unexpected sum chunk failure now logs one Warning per device (it was
  invisible: the health message was overwritten by the next good read).
- Diagnostics: TC2 has no TwinCAT_SystemInfoVarList; fall back to .SYSTEMINFO /
  .SYSTEMTASKINFOARR[1] once the TC3 name answers not-found (live: all six keys).
- #612 browse: Flat verified on TC3 (10,770 leaves; VirtualTree 1,459 strict
  subset) — comment and docs updated, no load-mode option needed.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 21:17:44 -04:00
Joseph Doherty c940349fe8 fix(focas): serialize per-device connect so concurrent loops share one session; docs: signed PMC Byte in the #259 override recipe + Makino live notes
The hw2 read-only Makino gate (Z-34184, 31i-B) logged the fixed-tree bootstrap failing with
ObjectDisposedException: FocasWireClient. EnsureConnectedAsync was unserialized: the alarm poll
loop and the fixed-tree loop both reached it right after init, the second saw the first's
still-connecting client as not connected, disposed it and opened a second session, and the first
caller's catch then disposed the second caller's client (and recorded a spurious backoff failure).
A per-device ConnectGate now serializes the connect with a re-check after acquire, and the failure
path disposes only its own client. FocasConcurrentConnectTests goes red without the gate.

Docs: a Byte PMC tag is signed, so G12 at 100% feed override reads -101 on the Makino and the
recipe's 255 - value gave 356; recipe now masks. Adds the live statinfo/alarm-watch observations.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 21:16:31 -04:00
Joseph Doherty e7dd742541 Merge b647-focas-alarms: FOCAS native alarms reach Part 9 conditions (ALARM: address) + respawn keeps acks host-side (#647)
v2-ci / build (push) Successful in 4m57s
v2-ci / unit-tests (push) Failing after 21m6s
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 20:46:13 -04:00