Commit Graph

441 Commits

Author SHA1 Message Date
Joseph Doherty 1219b7b13e fix(opcuaclient): review fixes - drop driver-side OperationLimits chunking (SDK batches), diagnostics as OTel counter, safe discovery selection, options validation at load (#273 #275 #276 #277 #278)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:55:53 -04:00
Joseph Doherty 935ba8d1d4 Merge branch 'master' into b626-opcuaclient 2026-10-08 14:47:49 -04:00
Joseph Doherty 5c6e2d1b59 Merge b626-631 (batch #626+)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:43:45 -04:00
Joseph Doherty 723392e3da Merge b626-mtconnect (batch #626+)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:43:45 -04:00
Joseph Doherty 7a9b51aeca Merge b626-ablegacy-focas (batch #626+)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:43:44 -04:00
Joseph Doherty ba62022e88 feat(opcuaclient): subscription tuning, OperationLimits chunking, diagnostics counters, revocation options, DiscoveryUrl (#273 #275 #276 #277 #278)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:40:20 -04:00
Joseph Doherty ff055ebd79 fix(s7): numeric TSAP loads in form, shared TSAP conflict error, Test Connect read detects PUT/GET disabled, pin Int32 ms rounding (#632 #638)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:38:49 -04:00
Joseph Doherty d8f3dd7394 fix(ablegacy): review fixes - one family-aware address authority in Contracts, deploy gate + editor check, malformed address -> BadNodeIdUnknown warned once, probe normalised, octal/migration docs (#244 #245 #247)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:38:25 -04:00
Joseph Doherty 34c52defb3 feat(s7): STRING/WSTRING/CHAR/WCHAR and DTL/DATE/TOD/TIME/S5TIME codecs, TSAP/connection-type override, negotiated PDU surfaced (#632 #288 #289 #294 #295)
New S7DataType members WString/Char/WChar/Dtl/Date/Tod/Time/S5Time mapped onto existing
DriverDataType (String / DateTime / Int32 ms); writes refuse out-of-range with BadOutOfRange.
Optional localTsap/remoteTsap/connectionType through options, factory JSON, probe and AdminUI form.
NegotiatedPduSize snapshotted at connect and exposed via GetDiagnostics (DriverHealth has no bag).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:31:44 -04:00
Joseph Doherty dcc827ca8c feat(mtconnect): one agent client per driver — a second /raw Device is a deploy error (#628)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:29:50 -04:00
Joseph Doherty da33f27544 feat(mtconnect): serve TIME_SERIES observations as variable-length double[] (#627)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:25:45 -04:00
Joseph Doherty da203d5d7e fix(adminui): modal backdrop offset came from .rise's retained transform, not 100vw (#631)
Live-measured on docker-dev: the backdrop was viewport-wide (1911px) but started at the
panel's left edge (241px) because the theme's .rise animation (fill-mode both) leaves an
identity transform on section.panel, re-basing position:fixed descendants. Override the
fill mode to backwards; drops the speculative backdrop/dialog sizing overrides.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:22:32 -04:00
Joseph Doherty c9fdcc3ea6 fix(adminui): clear stale one-time key banner on delete; modal backdrop no longer overruns viewport (#631)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 14:21:20 -04:00
Joseph Doherty 1475991fd6 fix(adminui,client): error boundary inside the interactive scope, stale-commit OnSaved, AbCip browse delay emit, DisposeBridge cancel (review R3)
M1: PageErrorBoundary wraps the body of every InteractiveServer page; the inert layout boundary is removed.
m1: DisposeBridge cancels its CTS when the 5 s wait times out.
m2: a CSV import that commits after the modal closed still raises OnSaved.
m3: document SecurityMode=None no-fallback in Client.CLI.md / Client.UI.md.
m4: browsePageDelayMs written only when loaded or changed; negative flagged.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 07:31:19 -04:00
Joseph Doherty e9e35baa70 Merge #600.1/#603: provision changed-to-historized tags; retire dormant VirtualTagEngine; cache ScriptSandbox references; one LocalDb replication predicate for registration and drain gate
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 07:01:41 -04:00
Joseph Doherty 80ed056d41 Merge #602(S7)/#610.2: S7 connect backoff, write-path worst-state-wins, Volatile health, factory logging, driver-side array writes
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 07:01:32 -04:00
Joseph Doherty 439752d3b8 feat(adminui): S7 tag editor rejects wide-type and writable-Bool arrays at author time (#610)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:59:29 -04:00
Joseph Doherty db10e9d2e3 Merge #604: AdminUI stale-open guards on 5 modals, ValidationSummary, API-key delete confirm, ErrorBoundary
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:55:40 -04:00
Joseph Doherty d3bca7afd4 fix(adminui): stale-open guards, validation summaries, key-delete confirm, error boundary (#604)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:55:23 -04:00
Joseph Doherty 0fb194a096 Merge #608/#602/#596: stale WaveC TODOs resolved, FOCAS/TwinCAT factory logging, Modbus health asymmetry documented, AbCip browsePageDelayMs field, Device1 by-design
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:53:59 -04:00
Joseph Doherty 9c39f2b6c4 fix(drivers): ILoggerFactory on FOCAS+TwinCAT factories; replace stale TODO(v3 WaveC); AbCip browsePageDelayMs form field; document Device1 + Modbus health (#608 #602 #596)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:53:38 -04:00
Joseph Doherty b78c7ff9db feat(mtconnect): authoring DataType warning + loud ignored-DeviceName warning (#613 slice)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:51:40 -04:00
Joseph Doherty a8703c6afe refactor(virtualtags): retire dormant VirtualTagEngine/Source/Definition + TimerTriggerScheduler (#603)
TimerTriggerScheduler was welded to the engine and has no production consumer.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:51:22 -04:00
Joseph Doherty bafbcbee7e fix(adminui): AB forms read/write the factory-bound *Ms duration keys, migrate legacy TimeSpan strings (#576)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 23:03:38 -04:00
Joseph Doherty 3d081ad29c fix(adminui,cli): /raw device-name placeholder + no-prerender tree, CLI read prints Bad status (#577)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:56:47 -04:00
Joseph Doherty e588443ced fix(abcip,adminui): ParseEnum device wording + libplctag probe assertion (#574); AB forms preserve unknown keys + MaxConcurrentReads (#576); probe off for new AbCip driver (#577); docs T4:0.DN (#584)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 22:53:04 -04:00
Joseph Doherty b75e013d0a feat(abcip): final review I1 — node-level Drivers:AbCip:DefaultTransport
The old AdminUI cannot author the per-driver transport key (#576), so the
'pin before upgrading' recipe had no working path. A node-level appsetting
(env Drivers__AbCip__DefaultTransport) now supplies the default for AbCip
drivers with no key: explicit key > node default > compiled Native. Bound
and validated unconditionally (bad value fails host start); the driver
factory and the Test Connect probe both read it so they agree. AbCip.md
upgrade recipe rewritten around the key; Configuration.md documents it.
Also documents first-wins for AllowPacking/probe and host-alias throttles
(final review M6) and moves the status-bit note out of AbCip.md (M5).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 21:34:48 -04:00
Joseph Doherty b66972b6f5 fix(ablegacy): final review I3 — default reverts to libplctag; Native is opt-in
On a wire the native PCCC path is proven only for N-file reads/writes and
one F8 read (ab_server). An absent transport key now resolves to Libplctag
for every family; explicit Native still wins and still warns once per
instance on PLC-5/LogixPccc. AdminUI dropdown, CLI help, evidence claims
(PcccFileType, AbLegacy.md wire-evidence table, release note) corrected:
S:0 (default probe) and B/T/C/R/S/L/ST/A/O/I are unverified natively.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 21:34:04 -04:00
Joseph Doherty ed0881d672 feat(ab): native managed transport is the default for AbCip and AbLegacy; libplctag remains as fallback
Task 22 (spec section 8 P5), with the controller rulings that exclude PLC-5 and LogixPccc.

- Transport is nullable on both option records; null (key absent) means "use the default" and is
  never serialized. Factory DTO parse: absent -> null; a bad value still fails via ParseEnum.
- One resolver per driver (AbCipTransportResolver / AbLegacyTransportResolver), used by the driver
  constructor, the Test Connect probe and (through the driver) the CLI.
  AbCip: null -> Native. AbLegacy: null -> Native unless any device is Plc5 or LogixPccc, then the
  whole driver resolves to Libplctag. Explicit values always win; explicit Native with a
  Plc5/LogixPccc device logs one Warning at init.
- Init log shows configured vs effective, e.g. transport=Native (default).
- AdminUI forms: "Default (...)" / "Native (managed)" / "libplctag (fallback)". Default omits the
  key on save (reverses Task 19's explicit re-emit, which would have pinned every edited driver).
- CLIs: --transport default|native|libplctag, default "default" via the resolver, so
  --plc-type Plc5 with no --transport runs libplctag.
- Tests pinned to Libplctag where a default-path runtime was created: found by instrumenting the
  native factories with the current xUnit test name (one unit test, AbLegacyReinitConfigAdoption;
  plus the two fixture-gated AbCip probe E2E tests whose exact messages were proven on libplctag).
  TransportMatrix and the differential harness already pass explicit transports.
- Switch/resolver/CLI/form tests are falsified by mutating the resolver (default -> Libplctag,
  exclusion removed, AbLegacy default -> Libplctag): each mutation turns them red.
- Replaces the inline role-name literal in AbCipDriverFactoryExtensions' ParseEnum call with a named
  constant; RoleAuthorityGuardTests was already red on the base commit for that line.
- Docs: AbCip.md / AbLegacy.md default + release note; CLAUDE.md AB section.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-07 20:49:33 -04:00
Joseph Doherty b8ebafc642 feat(adminui): Transport dropdown on AbCip/AbLegacy driver forms (live-verified on docker-dev)
- AbCip/AbLegacy driver forms gain a Transport InputSelect; FromOptions/ToOptions now carry
  Transport, so saving any other field no longer silently reverts Native to libplctag.
- Both drivers log the constructed transport once per init.
- AbCip factory now takes the host ILoggerFactory (it ran on NullLogger in production, hiding
  every AbCip log line); DriverFactoryBootstrap passes it.
- Gate results: AdminUI /run (T19) native, inverted (dead port) and libplctag legs.
2026-10-07 19:05:50 -04:00
Joseph Doherty e46c1db5e5 fix(cluster): gossiped-role predicates compare case-insensitively — a miscased peer no longer un-elects the Primary 2026-08-20 10:04:16 -04:00
Joseph Doherty c149410ba3 test(adminui): SQL-Server-backed pins for the drift watermark aggregate and the rename scan's collation widening 2026-08-17 03:53:16 -04:00
Joseph Doherty 445bb855e2 fix: release the alarm drain latch on escape; cancel before disposing the page poll timers 2026-08-16 05:40:27 -04:00
Joseph Doherty b9e9df4081 docs(code): honest rationales — Akka restart semantics, per-key shards, EF literal inlining; retire or annotate orphaned members 2026-08-16 05:33:33 -04:00
Joseph Doherty ca87329a83 fix(adminui): dedup + chunk the rename script-scan OR-chain 2026-08-16 04:36:03 -04:00
Joseph Doherty 684e59d871 perf(adminui): drift chip recomposes only when the config-edit watermark moves 2026-08-16 04:25:56 -04:00
Joseph Doherty fe8d132c64 perf(adminui): rename script scan filters server-side, stops shipping every script body 2026-08-16 04:24:10 -04:00
Joseph Doherty 63081d52d8 fix(adminui): fleet page disposes its poll timer asynchronously like hosts 2026-08-16 04:15:05 -04:00
Joseph Doherty 3795899d10 perf(adminui): 5s snapshot cache in front of the script tag catalog 2026-08-16 04:05:07 -04:00
Joseph Doherty ba977dbe0d perf(adminui): raw tree renders tag rows from a projection without TagConfig 2026-08-16 04:04:32 -04:00
Joseph Doherty fa36d64e8f perf(adminui): one guard round-trip per bulk reference add 2026-08-16 04:03:49 -04:00
Joseph Doherty 4b6a93bb78 fix(adminui): fleet/hosts poll ticks no longer overlap; cancel on dispose 2026-08-16 04:03:31 -04:00
Joseph Doherty dbc9a132e2 perf(adminui): /deployments lists a projection, not 50 artifact blobs 2026-08-15 21:38:11 -04:00
Joseph Doherty 259d9621ba feat(adminui): gate the Secrets rail link on the page's own policy
v2-ci / build (push) Successful in 4m44s
v2-ci / unit-tests (push) Failing after 17m18s
The /admin/secrets link was already in the side rail, but ungated — every
authenticated user saw it, and a Designer/Operator clicking it was bounced by
the page's own [Authorize(Policy = SecretsAuthorization.ManagePolicy)]. Hide
the advertised dead end.

Gated on that same policy constant rather than Roles="Administrator". The two
are equivalent today (the RCL's AdminRole is "Administrator", read off the same
ClaimTypes.Role claim FleetAdmin uses), so this is not about reach — it is that
the rail cannot drift if the RCL ever changes its policy.

Live-gated on docker-dev, both directions, because the positive half alone
proves nothing here: the link rendered for an admin BEFORE this change too, so
"admin still sees it" is satisfied equally by a working gate and by an inert
one. Negative control — gate swapped to Roles="NoSuchRoleFalsifyProbe", rebuilt,
item went to 0 while /role-grants and /apikeys stayed at 1 (the item is hidden,
not the rail broken). Restored build hashed to the same image ID as the
pre-falsification one, proving the restore was byte-exact. This also rules out
AuthorizeView being inert inside ThemeShell's <Nav> slot under this host's
static router, which would have silently hidden the link from everyone.

SecretsNavLinkTests covers it in the AdminUI's actual idiom — source scan +
metadata cross-check, NOT bUnit (the bunit PackageVersion pin has zero
PackageReferences repo-wide). The load-bearing assertion is index ordering
(AuthorizeView open < NavRailItem < close): a scan that merely finds both tags
in the file passes when they are not nested, which is the ungated state.

Scope: Secrets only. /role-grants and /apikeys are FleetAdmin (Administrator-
only) too and stay visible to everyone, so the rail is now inconsistent —
deliberate, as hiding those is a UX call for the whole rail rather than
something to smuggle into this change. Noted in the markup.

Family-wide task from the scadaproj index session (ScadaBridge ee193cd2,
ignitionoee 3627bb8, HistorianGateway 58b6418, mxgw 87d575d).
2026-08-13 09:59:31 -04:00
Joseph Doherty 8ab520ef57 fix(scripts): gate the source dialog's existence on the held id, not the resolve
The modal's existence was a function of _rows (FindSourceScript() as the
visibility gate), so any render where the row is momentarily unresolvable —
a future reload nulling or replacing the list — unmounts the subtree and
disposes the Close handler's binding id mid-click (GetRequiredEventBindingEntry).
Latent here (_rows is written once, in OnInitializedAsync) but the identical
shape bit ScadaBridge live the moment a page refresh landed, and it presents
as an unattributable intermittent once one does.

ScadaBridge's shape, ported by the family sweep: intent (_sourceScriptId)
gates existence, the resolve drives only content; an unresolvable script
renders a notice with the frame and Close still mounted, and closing remains
the operator's action rather than a side effect of list state.
2026-08-11 06:06:49 -04:00
Joseph Doherty 01c2c8a1b7 chore(theme): bump ZB.MOM.WW.Theme 0.3.1 → 0.4.0 — kit ships the .btn sizing block (local copy deleted); drop inert Accent param (kit-default value, inline --accent would beat a future dark override) 2026-08-11 02:56:14 -04:00
Joseph Doherty 7f7798cd8c merge: ui(density) Task 3 — EquipmentPage panel idiom + bounded RawPath 2026-08-10 18:01:39 -04:00
Joseph Doherty ac419cf2b7 merge: ui(density) Task 2 — Scripts inline expander → source dialog + stacked identity 2026-08-10 18:01:39 -04:00
Joseph Doherty 564f859215 merge: ui(density) Task 1 — truncation sweep (Abbreviate + title on unbounded text) 2026-08-10 18:01:39 -04:00
Joseph Doherty 88aaf1d947 ui(density): truncation sweep — Abbreviate + title on unbounded backend/free text (§1d Task 1) 2026-08-10 18:01:07 -04:00