docs(claude): refresh CLAUDE.md + record historian-gateway integration/dev-rig state
v2-ci / build (push) Failing after 42s
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

CLAUDE.md: add scadaproj sister-project index, fix mxaccessgw path to
~/Desktop/MxAccessGateway, note the temporary CVE-2025-6965 NuGetAuditSuppress,
and correct the driver-typed tag-editor list (OpcUaClient is now mapped; only
Galaxy falls back). Also captures docker-dev data-plane GroupToRole + ServerHistorian
live-verify config and the HistorianGateway integration/backlog notes.
This commit is contained in:
Joseph Doherty
2026-07-07 11:46:02 -04:00
parent 4dd47ce707
commit 384dbd7d36
6 changed files with 559 additions and 8 deletions
+269
View File
@@ -0,0 +1,269 @@
# Still Pending — RECONCILED 2026-06-18
> **★ UPDATE 2026-06-19 (master `a325ec54`) — NON-ARCH FOLLOW-UPS BATCH (A/B) SHIPPED.** Six code
> tasks merged via `feat/followups-batch`: OpcUaClient stale-session race (re-resolve session inside
> `_gate` in 5 history/subscribe/ack paths); Client.CLI `enable`/`disable` condition commands (H4
> client path); cert-audit review nits; Galaxy re-pick preserves edited alarm/historize/scale via
> pure `TagConfigJson.SetFullName`; inline-script dropdown label reorder fix. **The "write-outcome
> residuals" item (Bad blip + AuditWriteUpdateEvent + sync fail-fast) was found ALREADY-SHIPPED in
> `bb59fd4e` (2026-06-15) — delivered the missing behavioural test coverage instead.** Live-`/run`:
> T5 PASS; T4 substantial (picker + live gateway browse confirmed; final pick-and-assert blocked by
> the Galaxy object-browser `href=#` nav quirk under automation — preservation unit-tested).
> **RECONSIDER GATES still OPEN-by-choice (NOT built): F10b surgical DataType/IsArray writes;
> alarm-severity `SetSeverity`.** See `docs/plans/2026-06-19-followups-batch*` + memory.
>
> **Reconciled 2026-06-18** against master HEAD `651018f9` by a 7-way parallel per-subsystem
> code audit of the original 2026-06-15 backlog (which was generated at the older HEAD `151b7165`).
> Three days and a large run of "Phase 4 / H1H6 / driver" commits landed in between, so **most of
> the original backlog is now SHIPPED.** Every item below was re-verified in code (commit cited).
>
> **Legend** — `OPEN` (genuine, actionable code gap) · `DEFERRED` (intentional scope boundary —
> needs infra, a future version, an SDK/gateway/sidecar change, or a by-design decision) ·
> `SHIPPED` (the cited gap no longer exists; recorded for the audit trail so it is not re-flagged).
---
## A. Genuinely OPEN — actionable code work (ranked)
1. **H2 — OPC UA `HistoryUpdate` service****RECLASSIFIED INFRA-GATED / DEFERRED (user decision 2026-06-19).**
The server-side override (mirror the 5 HistoryRead overrides: `HistoryUpdateData`/`HistoryUpdateEvents`/
`HistoryDeleteRawModified`/`HistoryDeleteAtTime`/`HistoryDeleteEvent`, resolve nodes via the same
`TryGetHistorizedTagname`/event-notifier maps, gate on the already-shipped `NodePermissions.HistoryUpdate =
1<<12` / commit `c236263e`) is buildable + offline-testable, BUT **no backend can durably write history**:
`IHistorianDataSource` is read-only (Raw/Processed/AtTime/Events + health — no insert/replace/delete) and
the AVEVA Wonderware TCP sidecar exposes no history-update RPC (the same permanent SDK/sidecar boundary as
H5b durable-write + no-modified-reads). So any `IHistoryUpdateSink` wired today could only reject — a custom
override would mostly restate the SDK's own default `HistoryUpdate` rejection. **Deferred** until a writable
historian backend exists; if/when one does, build the full service surface + the `IHistoryUpdateSink` seam
(Phase-C shape) at that time. _Moved out of §A "Genuinely OPEN" — see the Infra-gated OPEN note below + §B._
2. ~~**OpcUaClient — `IHistoryProvider.ReadEventsAsync`** event-history passthrough~~ — **SHIPPED master
`7a3d2712` 2026-06-18** (`feat/opcuaclient-read-events`). NO interface change (the "param extension" framing
was overcautious): the driver builds its OWN canonical 6-field BaseEventType `EventFilter` and maps the
upstream `HistoryEvent``HistoricalEvent` (the server re-projects only those 6 fields). Driver-internal;
live-proven vs opc-plc (wire path; non-empty events infra-gated — opc-plc isn't a historian).
3. ~~**FOCAS — `cnc_getfigure` wire command**~~ **SHIPPED master (branch
`feat/focas-figure-ww-poison-abcip-gate`) 2026-06-18.** `FocasWireClient.ReadPositionFiguresAsync`
(wire command `0x00D3`, self-delimiting per-axis `short dec` payload) + the `WireFocasClient`
stub wired to it; matching `focas_mock` `_wire_payload` handler + `position_figures` default-0 state.
The managed wire backend (production path to real CNCs) now auto-fetches per-axis figures; the
`PositionDecimalPlaces` knob is the graceful per-axis fallback (`FocasDriver.SafeProbe` → empty on
failure → knob, so it cannot regress). **LIVE-PROVEN** against the local focas-mock
(`Position_figures_scale_axis_via_wire_backend`: 12345 ÷ 10^3 = 12.345, auto figure beat the knob).
Real-Fanuc wire-fidelity stays bench-CNC-gated (whole-backend caveat, not unique to this command).
4. ~~**Galaxy — writer item-handle cache not shared with the subscription registry**~~
**SHIPPED master `8480e301` 2026-06-18.** `SubscriptionRegistry.TryResolveItemHandle` (authoritative
forward lookup) + writer `subscribedHandleSource` delegate → first write to an already-subscribed tag
borrows the live handle, skipping the redundant `AddItem`. Live-proven vs `10.100.0.48:5120` (3/3).
5. ~~**Historian.Wonderware — poison-event sidecar wire protocol**~~ **SHIPPED master (branch
`feat/focas-figure-ww-poison-abcip-gate`) 2026-06-18.** Additive `[Key(4)] byte[] PerEventStatus`
(0=Ack/1=Retry/2=Permanent) on `WriteAlarmEventsReply` (BOTH in-repo IPC ends — MessagePack/TCP, NOT a
Commons proto; `PerEventOk [Key(3)]` kept for rolling-deploy back-compat). The sidecar
`HistorianFrameHandler` now classifies **structurally-malformed** events (empty SourceName/AlarmType,
`EventTimeUtcTicks ≤ 0`) as **Permanent** and excludes them from the writer batch; the client
`WriteBatchAsync` prefers `PerEventStatus` (→ `PermanentFail` → the sink's existing immediate
dead-letter) and falls back to `PerEventOk`. **NO `IAlarmEventWriter` / Commons / Core.Abstractions
change** (`HistorianWriteOutcome.PermanentFail` already existed). Offline-proven (sidecar classifier
tests + client round-trip + sink). _Documented boundary (follow-up):_ an SDK-**semantic** permanent
rejection of a structurally-valid event still maps to Retry→cap until the infra-gated
`AahClientManagedAlarmEventWriter` surfaces richer per-event status.
6. ~~**AbCip — nested-struct member expansion in production**~~ **SHIPPED master (branch
`feat/hosts-rows-abcip-nested-hygiene`) 2026-06-18** (`3d8ce4e8`+`d203f31c`). FINDING: simpler than this
item assumed — the CIP Template Object member-info `u16` ALREADY carries the nested template id for a
struct member (bit 15 = struct flag, lower 12 bits = template instance id, same encoding as the Symbol
Object; the decoder's own remarks documented it). The prior fix DISCARDED it (`templateInstanceId: null`).
The close: carry `uint? NestedTemplateId` on `AbCipUdtMember` (decoded via the FULL 12-bit mask, not the
byte-cast typeCode), thread it into the existing `FetchUdtShapeAsync`/`@udt/{id}` path — NO new CIP query.
Offline-proven (decoder + no-seam threading test, AbCip 301/301). **Live-gate FORMALIZED 2026-06-18
(branch `feat/focas-figure-ww-poison-abcip-gate`):** a skip-gated Emulate-tier integration smoke
`AbCipEmulateNestedUdtTests` (mirrors `AbCipEmulateUdtReadTests` + `AbServerProfileGate.SkipUnless(Emulate)`)
asserts nested leaves addressable; `docs/drivers/AbCip.md` documents nested-struct as Emulate-tier
verified. A LOCAL gate is architecturally impossible — `ab_server` lacks CIP Template Object (class 0x6C);
the test compiles + skips locally and runs only against Logix Emulate / real ControlLogix.
7. ~~**AdminUI — Certificates page actions**~~ **SHIPPED master `bd791e79` 2026-06-18.** FleetAdmin-gated
Trust (rejected→trusted) / Untrust (trusted→rejected) / Delete via a new in-process `CertificateStoreManager`
(filesystem moves by thumbprint, honored live); inline Blazor confirm + server-side FleetAdmin re-check.
Live-proven on docker-dev. **CERT-ACTION AUDIT LOGGING SHIPPED master `3eb370d4` (2026-06-19):**
Trust/Untrust/Delete now write a `ConfigAuditLog` row (`Certificate:Trust|Untrust|Delete`, Outcome
Success/Failure on every path incl. failures) via the first live structured `AuditEvent` emit site —
new `ActorAuditWriter : IAuditWriter` (AdminUI adapter Telling the `AuditWriterActor` cluster singleton
resolved from `ActorRegistry`, mirrors `AdminOperationsClient`) + pure `CertAuditEvents.Build` factory,
audited at the `CertificateStoreManager` choke point (`DeleteCore` refactor); actor sourced from
`AuthenticationState`. NO EF migration (table + actor pre-existed), NO Commons/proto change.
LIVE-PROVEN end-to-end on docker-dev: a Trust on the Certificates page wrote
`multi-role-test | Certificate:Trust | Success | <thumbprint>` to `ConfigAuditLog`. (Was: only cert
*validation* went to Serilog.)
8. ~~**AdminUI — Hosts page per-driver-instance rows**~~ **SHIPPED master (branch
`feat/hosts-rows-abcip-nested-hygiene`) 2026-06-18** (`cb062fce`+`6457eba8`+`07bd63f8`). A new
cluster-grouped "Driver Instances" section on `/hosts` reusing the existing driver-health DPS pipeline
(added only an AdminUI-internal `IDriverStatusSnapshotStore.GetAll()` + a pure `HostsDriverView.Build`
grouping view-model + the Razor section) — NO Commons/interface/EF change. Grouped by `ClusterId` (the
reliable key; the health feed carries no per-Akka-member identity), nodes from ConfigDB `ClusterNode`,
driver rows enriched with Name/DriverType from `DriverInstance`, live status chips via the in-process
store's `SnapshotChanged` (dodges the Traefik self-hub trap) + the 5 s timer. LIVE `/run` PASS (both
central nodes rebuilt; MAIN cluster shows Galaxy/Modbus/OpcUaClient HEALTHY enriched + grouped; SITE-A/B
show nodes + "No drivers"). DEFERRED follow-up: TRUE per-Akka-member nesting (needs a hosting-node field
on `DriverHealthChanged` — a Commons change — or a per-node `DriverHostActor`-children Ask).
9. ~~**AdminUI driver-pages plan — Phase 6 + Phase 10**~~ **SHIPPED — RECONCILED 2026-06-18.** A
seam-by-seam audit confirmed the WHOLE driver-pages plan (Phases 010) is shipped; §A.9 was stale
(it over-counted from the stale `.tasks.json`). Phase 6 (live `DriverStatusPanel`) + Phase 8
(Reconnect/Restart) are wired end-to-end (DriverHealthChanged → AkkaDriverHealthPublisher invoked at
`DriverInstanceActor.PublishHealthSnapshot` → admin-gated `DriverStatusSignalRBridge` → snapshot store
→ panel in all 9 driver pages; page `DriverInstanceId` ⟷ actor `_driverInstanceId` keys match). Phase
10 E2E tests (DriverTestConnect/DriverReconnect/DriverStatusHub) shipped at `494da22c`. The one genuine
remnant — a **deployed-driver Healthy→Reconnecting→Healthy E2E** + the harness-fidelity fix
(`AddOtOpcUaRuntime` so harness drivers actually publish health) + a controllable fake driver factory —
SHIPPED on `feat/driver-pages-reconnect-e2e` (`docs/plans/2026-06-18-driver-pages-reconnect-e2e.md`),
driver E2E suite GREEN 18/0/4. The `.tasks.json` is now reconciled. DEFERRED follow-ups: full-stack
WebSocket+JWT DriverStatusHub test (no repo precedent; mock-hub test + manual runbook cover it); the
§8.3 manual browser smoke recording (10.4); the dotnet-test-testhost-can't-reach-remote-docker skip
quirk on this dev Mac.
10. ~~**AdminUI — Monaco `ctx.SetVirtualTag(...)` completions**~~ **SHIPPED master `70e1bde9` 2026-06-18**
(predates this audit — was stale here). The `ctx`-receiver guard is in
`ScriptAnalysisService.TryGetTagPathLiteral` (`:224`, mirrors the `ctx`-anchored
`EquipmentScriptPaths.GetTagRefRegex`) + a truthful `ctx.SetVirtualTag` hover note (it's a no-op in the
live single-tag evaluator).
11. ~~**F10b — in-place property writes vs full rebuild**~~ **FIRST SLICE SHIPPED master (branch
`feat/f10b-vtag-skip-rebuild-galaxy-comment`) 2026-06-18** (`6c6a2c42`+`b05c2811`). The provably-safe,
zero-node-mutation slice: `Phase7Applier.Apply` now SKIPS `RebuildAddressSpace` when a deploy's only
changes are VirtualTag deltas differing *solely* in `Expression`/`DependencyRefs`/`Historize` — a vtag's
materialised node depends ONLY on `{EquipmentId,FolderPath,Name,DataType}` (Historize is write-side
only), and the vtag ENGINE adopts the edit via `VirtualTagHostActor`'s INDEPENDENT respawn
(`DriverHostActor``ApplyVirtualTags`), so editing a script no longer tears down + rebuilds the whole
server address space (preserving every client's subscriptions + live values). Whitelist-of-may-differ
(`(prev with {those 3 = cur's}).Equals(cur)`) defaults to REBUILD for any other/structural change;
`EnsureVariable`'s existing-node early-return preserves the live node when no rebuild runs. RecordingSink
unit tests pin the boundary (skip cases + DataType/Name/FolderPath/EquipmentId/mixed ⇒ rebuild).
Code-review verified all 6 safety claims; no H1a stale-node regression.
**SECOND SLICE (surgical tag-attribute writes) SHIPPED master (branch `feat/f10b-surgical-tag-attribute-writes`)
2026-06-18** (`3fc258bd`+`f6618144`+`b472bba3`+fix `22b0611f`): equipment-tag **`Writable`** +
**`IsHistorized`/`HistorianTagname`** toggles now update the EXISTING node IN PLACE (AccessLevel + write
handler + Historizing + historian-map + `ClearChangeMasks`) via a new optional `ISurgicalAddressSpaceSink`
(`OtOpcUaNodeManager.UpdateTagAttributes`, mirrors `EnsureVariable` via a shared `ComposeAccessLevel`),
instead of a full rebuild — `Phase7Applier.TagDeltaIsSurgicalEligible` (no-alarm value variable, only those
3 differ) with rebuild fallback for anything else / missing node / non-surgical sink. **LIVE-PROVEN** on
docker-dev (toggled FastUInt1 isHistorized → `changed=1, surgicalTags=1, rebuild=False` on BOTH central
nodes, server healthy). **CRITICAL bug the live-run caught (all unit tests + 3 reviews missed it): the
optimization was INERT in prod** — actors inject `DeferredAddressSpaceSink` (the runtime swap-in wrapper),
which forwarded `IOpcUaAddressSpaceSink` but NOT `ISurgicalAddressSpaceSink`, so `_sink is ISurgical…` was
false → silent rebuild fallback (`rebuild=True, surgicalTags=0`). Fixed by forwarding the surgical
capability through the wrapper (`22b0611f`) + a regression test. _Still DEFERRED (dirty for rare edits /
no value): surgical `DataType`/`IsArray` writes (brief value-type mismatch; no ModelChangeEvents) +
alarm-severity `SetSeverity` (live-shadowed by the alarm engine, operationally invisible)._
12. ~~**Client.UI — Galaxy sub-attribute fallback collapses operator comments**~~ **CLOSED as a documented +
tested ACCEPTED-LIMITATION master (branch `feat/f10b-vtag-skip-rebuild-galaxy-comment`) 2026-06-18**
(`fac44187`). Investigation confirmed it is NOT code-fixable: Galaxy's `AckMsg` attribute is WRITE-ONLY
(no readable attribute to recover the last comment) and the OPC UA event SelectClause carries no comment
field — the operator comment is only available via the gateway's NATIVE alarm feed
(`GalaxyAlarmTransition.OperatorComment`, the Galaxy driver path), not this OPC UA client fallback. Closed
with a clarifying inline comment at the fallback `AlarmEvent?.Invoke` site + a tightened
`AlarmEventArgs.OperatorComment` doc naming both concrete reasons + a pinning test
(`OnAlarmEvent_GalaxyFallback_LeavesOperatorCommentNull`) so the null behavior is intentional + regression-guarded.
A behavioral fix would need a server-side change (out of scope).
13. ~~**§9 hygiene — Galaxy stale forward-ref comments**~~ **SHIPPED master (branch
`feat/hosts-rows-abcip-nested-hygiene`) 2026-06-18** (`9cf88e33`). All 5 sites
(`GalaxyDriver.cs:52,92,669`, `GalaxyDriverFactoryExtensions.cs:19`, `Health/HostStatusAggregator.cs:21`)
rewritten to the shipped standard-driver reality (each claim re-verified against the live code:
`_dataReader` reads via `ReadViaSubscribeOnceAsync`, `_ownedMxSession` built in
`BuildProductionRuntimeAsync`, `SyncPlatformsAsync` refreshes the probe watcher post-discovery, only
`GalaxyMxGateway` exists — no `Galaxy:Backend` switch, host-status re-raise wired in the ctor). The
unshipped "PR 4.W"/`Galaxy:Backend`/"legacy-host" forward-refs are gone. Comment-only; §9 now fully clean.
> **Infra-gated OPEN (code path exists or is buildable; only a no-op until a deployment supplies the backend):**
> **H5b — production `IHistoryWriter` for equipment-vtag historization.** The `Historize` flag is now
> threaded end-to-end (commits `fc8121cb`/`9c5a0913`/`4501f126`) but DI defaults to `NullHistoryWriter` —
> no durable AVEVA sink is written/wired. Same infra-gated pattern as the alarm historian: writing a real
> sink is possible but only exercisable against the historian backend.
> **H2 — OPC UA `HistoryUpdate` service** (reclassified 2026-06-19, see §A.1). Server-side override is
> buildable but no historian backend can durably insert/replace/delete — `IHistorianDataSource` is read-only
> and the Wonderware sidecar has no update RPC; build the service + `IHistoryUpdateSink` seam when a writable
> backend exists.
---
## B. DEFERRED — intentional scope boundaries (not actionable without infra / future version)
**Driver write/array boundaries (reads shipped):**
- Array **writes** across all 5 drivers (OPC UA layer forces array nodes read-only — `Phase7Applier`
`writable = tag.Writable && !tag.IsArray`, commit `3bb2031d`).
- S7 wide-type **arrays** (Int64/UInt64/LReal/String/DateTime as arrays); S7 **Timer/Counter writes**
(read shipped); Modbus String/BitInRegister **array writes**.
- AbCip **whole-UDT bare-container** read (`BadNotSupported`) + write (throws) — operators address
individual member paths. `Structure→DriverDataType.String` placeholder retained.
- TwinCAT **pre-declared `Structure` tags** rejected at parse (discovery is the supported path);
**Flat-mode `SubSymbols` live caveat** — on some TC3 firmware Flat mode may not populate struct
SubSymbols → switch `SymbolsLoadMode.Flat``VirtualTree` (deliberately NOT changed; needs a real ADS
target to verify; would risk regressing the Phase 4c array discovery).
- AbLegacy I/O-image bit writes are NOT pre-rejected — they RMW and surface the real PCCC device status.
**Gateway-scope (mxaccessgw sibling repo, not this repo):**
- Galaxy `ExecuteWrite` fire-and-forget — the .NET writer fully propagates `WriteResult` status; the
invisible-failure concern is gateway-side.
**Reserved seams / future versions (no behavioral gap today):**
- `AuthorizationVerdict.Denied` (v2.1 explicit-deny), `NamespaceKind.Simulated` (replay driver),
`Script.Language` (multi-engine), `IDriverConfigEditor` (superseded by `TagConfigEditorMap`),
`DraftSnapshotFactory` placeholders (conservative fallbacks), Cluster `LeaderChanged` no-op
(ServiceLevel lives in `RedundancyStateActor`).
- AdminUI Monaco **InlayHints** endpoint — intentional no-op (`[]`).
- Historian **no modified-value history** (sidecar has no modified-read RPC); **Processed/AtTime
single-shot** (spec-conformant, no count cap).
- Redundancy **`ServerUriArray`** child of `Server.ServerRedundancy` — pending an SDK object-type upgrade
(read `Server.ServerArray` today).
- VirtualTags **serialized eval** under `_evalGate` (decision #19); **unbounded script memory / post-timeout
thread leak** — out-of-process runner is a v3 concern.
---
## C. Live `/run` verification gates — code done, user/operator-driven
- Galaxy Phase C historian T7 live HistoryRead (needs Wonderware sidecar + AVEVA on `10.100.0.48`).
- Galaxy Phase B native alarms T9 live `/run` (equivalent proof already achieved via `dcb0be65` + `/alerts`).
- Driver-reconfigure-while-faulted T3 live `/run`.
- AVEVA Historian rig smokes (skip-gated `rig-required` tests).
---
## D. SHIPPED since 2026-06-15 (audit trail — do NOT re-flag)
**§1:** H1a changed-only-deploy rebuild (`1e95856b`) · H1b vtag-respawn (`ada01e1a`) · H2 permission bit
(`c236263e`) · H3 health-aware ServiceLevel now live (`3e609a2b`/`70e6d3d2`) · H4 OPC UA Enable/Disable
(`328bd1b9`) · H5a Historize flag threaded (`fc8121cb`/`9c5a0913`) · H6 native-ack→AVEVA + authenticated
identity (`418663b3`/`ed941c51`/…).
**§2 drivers:** S7 wide scalars Int64/UInt64/LReal/String/DateTime r+w (`06b858eb`/`286be5df`/`5db08e9e`) ·
S7 Timer/Counter read (`8cfb8e92`) · Modbus Int64/UInt64 node type (`DriverDataType.Int64/UInt64`) ·
Wonderware `Total` aggregate (client-side `ScaleAverageToTotal`) · cross-driver 1-D array **reads** all 5
drivers (Phase 4c `a82c22c6`/`f4d5a5ee`/`95006939`/`3e742395`/`8d3dc321`) · Modbus String/Bit array read ·
AbCip controller-discovered UDT **member paths** (`4e141402`/`4a7b0fde`) · TwinCAT discovered struct
member expansion (`3699fc16`) · AbLegacy B/I/O bit writes via RMW (`5c6b7cd6`) · TwinCAT BOOL-within-word
RMW (`a73e20fb`) · FOCAS axis scaling logic + knob (`49730752`/`6855be28`) · FOCAS fail-fast on
unimplemented backend (`5c5aaef6`) · Galaxy parent-gobject nesting (`21c7645d`/`bec37848`) · all 4 (+more)
Test-Connect probes upgraded TCP→protocol handshake (`961b2b55` & per-driver).
**§4/§5 AdminUI:** typed TagConfig editors for OpcUaClient + Historian.Wonderware (`72d414ad`) ·
isHistorized/historianTagname first-class UI (`c00a5471`) · UNS-tree delete Enterprise+Cluster
(`526eebb3`) · create-new-script from inline vtag panel (`c98625fd`) · native-alarm `HistorizeToAveva`
opt-out + UI knob (`6a8020e7`) · Galaxy picker pre-fills native-alarm fields (`069a5f31`) · Client.UI
alarm Acknowledge + Shelve + Confirm surfaces (`4f55d894`/`60da9224`/`a774bb08`/`6a61f35a`).
**§6/§7:** Historian #400 within-timestamp tie-cluster paging (`2e6c6d3a`/`0f929ae6`) · per-cluster-scoping
plan COMPLETE (`.tasks.json` reconciled) · historian-tcp-transport plan COMPLETE (stale json) ·
adminui-followups plan COMPLETE — role-mapper (RoleGrants.razor + `LdapGroupRoleMappingService` +
`FleetAdmin` + `LdapGroupRoleMapping` entity/migration) + typed editors all shipped · adminui-driver-pages
Phases 7 (probes) + 9 (9 typed address pickers) shipped · Wonderware poison-event SQLite dead-letter half
(`fcb38014`).
**§9 stale comments corrected:** DriverHostActor, OpcUaPublishActor, ServiceCollectionExtensions,
IScriptLogPublisher/ScriptLogTopicSink, ScriptAnalysisService, docs/security.md (6 of 7 — only the Galaxy
PR-4.W forward-refs remain; see A.13).