Files
lmxopcua/docs/plans/2026-06-15-stillpending-backlog-design.md
T

125 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Still-Pending Backlog — phased completion design
> **Status:** approved 2026-06-15. Source backlog: `stillpending.md` (system-wide audit, master `151b7165`).
> **Scope:** the full actionable backlog — `stillpending.md` §1–§5 + §7/§9 cleanup. Excludes §6
> (by-design / backend-gated limitations) and §8 (live-`/run` gates, which are user-driven), plus the
> recommended-defer list below.
> **Planning cadence:** plan-and-execute **one phase at a time** — author each phase's implementation
> plan when we reach it (no up-front 60-task monolith).
## Goal
Close every genuine deferred / partial / missing functional gap catalogued in `stillpending.md`,
in priority order, without a schema migration and without regressing the actor model.
## Delivery approach
- **Phased feature branches.** One phase = one branch off master, merged back after green
`dotnet build` + `dotnet test` + the classification-driven review chain (trivial→implementer only;
small→code review; standard→spec ∥ code review; high-risk→serial reviews + final integration review).
- **Live `/run` gates stay user-driven.** The user drives docker-dev; the agent never signs in. Razor/JS
changes are proven only by live `/run`, never bUnit.
- **Plan-and-execute phase-by-phase.** Later phases (drivers, AdminUI) depend on what the early phases
settle; authoring all plans up front would be stale on arrival.
Rejected alternatives: one long-lived mega-branch (un-reviewable, conflict magnet); parallel
per-area branches (the deploy-path and driver phases share `Phase7*` / `DriverHostActor`, so they collide).
## Hard constraints (carried into every phase)
- **NO Configuration entity / EF migration.** The audit confirms the whole backlog is achievable without
one (e.g. `VirtualTag.Historize` already exists; the `HistoryUpdate` bit is a Core `[Flags]` enum, not EF;
`isHistorized` / native `HistorizeToAveva` ride the `TagConfig` JSON blob).
- Stage by path — never `git add .`. Never stage `sql_login.txt`, `src/Server/.../Host/pki/`, `pending.md`,
`current.md`, `docker-dev/docker-compose.yml`. Never echo or commit secrets. No force-push, no `--no-verify`.
- Tests: xUnit + Shouldly, TDD fail-then-pass; in-memory EF where DB-backed. **No bUnit.**
## Phased roadmap
### Phase 0 — Hygiene & no-risk cleanup *(trivial / small)*
- §9 stale comments (7 spots: `DriverHostActor.cs:45`, `OpcUaPublishActor.cs:246-252`,
`ServiceCollectionExtensions.cs:66-67`, `IScriptLogPublisher.cs:8` + `ScriptLogTopicSink.cs:10`,
`ScriptAnalysisService.cs:21-23`, the Galaxy guard-message refs) + the `docs/security.md`
write-outcome section (B1 already shipped both halves).
- §7 — mark the confirmed-shipped `.tasks.json` files completed so future audits don't re-flag them.
- §3 low-consequence residue — document/verify-benign `DraftSnapshotFactory` placeholders and
`Cluster LeaderChanged` no-op (no behavioral change expected).
### Phase 1 — Silent-deploy bugs (H1 + H5) *(high-risk)*
- **H1 (conservative):** add the `Changed*` counts to `Phase7Applier.needsRebuild`; make
`VirtualTagHostActor.OnApply` stop+respawn children whose plan changed (so an edited Expression /
DependencyRefs takes effect). A rebuild repopulates from the persisted artifact and is idempotent.
- **H5:** thread `VirtualTag.Historize` through the equipment-namespace path
(`Phase7Composer``EquipmentVirtualTagPlan``VirtualTagHostActor``VirtualTagActor`) and wire a
real production `IHistoryWriter` (today drops to `NullHistoryWriter`).
- Verify on the 2-node / docker-dev rig (user-driven `/run`).
### Phase 2 — Redundancy ServiceLevel (H3) *(high-risk)*
- Feed `DbHealthProbeActor` / `PeerOpcUaProbeActor` health into `ServiceLevelCalculator.Compute`
(already written, never invoked) → published `Server.ServiceLevel` byte via `RedundancyStateActor`.
A DB-unreachable / probe-failed primary must drop below its role-based level.
- Unit tests can't catch the wiring (per memory) — verify on the 2-node rig.
### Phase 3 — OPC UA standards completeness (H4 + H2-bit + H6) *(high-risk)*
- **H4:** `OnEnableDisable` node-manager seam → `alarm-commands` DPS topic (engine + AdminUI already
handle Enable/Disable; only the OPC UA half is missing).
- **H2 (permission bit only):** add `NodePermissions.HistoryUpdate` and fix the
`TriePermissionEvaluator:86` mapping (today a HistoryRead grant would also authorize a future
HistoryUpdate). The full HistoryUpdate *service* is **deferred** (infra-gated — no backend RPC, like
modified-value history).
- **H6:** inbound native-alarm `Acknowledge``IAlarmSource.AcknowledgeAsync` → AVEVA, recording the
authenticated principal rather than a generic `"opcua-client"`.
### Phase 4 — Driver data-type & structure coverage *(standard, parallelizable per-driver)*
Splits into per-driver sub-plans (4a4i), each independent:
- S7 wide types (Int64/UInt64/LReal/String/DateTime) + Timer/Counter areas.
- Modbus Int64/UInt64 node `DataType` (add `Int64` to `DriverDataType`) + String/BitInRegister arrays.
- Array `IsArray` discovery for S7 / AbCip / AbLegacy / TwinCAT.
- AbCip + TwinCAT UDT member-path reads/writes.
- AbLegacy + TwinCAT BOOL-within-word (bit-index) writes (read-modify-write).
- FOCAS position scaling (`10^DecimalPlaces` divide) + `Unimplemented…Factory` fail at config time, not per-read.
- Galaxy nested gobject hierarchy + writer item-handle cache shared with the subscription registry.
- Historian.Wonderware `Total` aggregate + poison-event dead-letter (don't retry forever).
- OpcUaClient `IHistoryProvider.ReadEventsAsync` event-history passthrough.
### Phase 5 — Test-Connect protocol probes *(small)*
- Replace TCP-only probes with real handshakes: Modbus FC, FOCAS `cnc_allclibhndl3`, TwinCAT ADS-state,
OpcUaClient session-open, historian handshake (§2 probes + plan `2026-05-28-adminui-driver-pages`
Phase 7 + `2026-06-12-historian-tcp-transport` task 9).
### Phase 6 — AdminUI typed editors, pickers & UX *(standard; Razor → live-`/run` only, NO bUnit)*
- OpcUaClient + Historian.Wonderware typed `TagConfig` editors in the `/uns` TagModal.
- Driver-tag `isHistorized` / `historianTagname` first-class field.
- Native-alarm `HistorizeToAveva` opt-out (TagConfig + UI) — §5.
- Galaxy picker pre-fills `alarm` fields from `DriverAttributeInfo.IsAlarm` — §5.
- Typed address pickers for Modbus / S7 / AbCip / AbLegacy / TwinCAT / FOCAS (plan Phase 9).
- UNS-tree Delete for Enterprise / Cluster node kinds.
- Hosts page per-driver-instance rows.
- Monaco `ctx.SetVirtualTag(...)` write-target completions/hover; create-new-script from the inline panel.
### Phase 7 — Client.UI alarm controls *(small)*
- AlarmsView per-row Acknowledge + Shelve + Confirm commands/buttons (backends + CLI already exist).
### Phase 8 — Per-cluster scoping *(high-risk, standalone)*
- Execute tasks 310 of the **existing** `2026-06-07-per-cluster-scoping` plan (SubscribeBulk
cluster-filtering, `OpcUaPublishActor` scoping on rebuild, multi-cluster E2E harness, docker-dev
rewrite, verify, docs). Reference/execute that plan rather than re-designing here.
## Recommended defer (YAGNI) — flagged, not silently dropped
Forward-looking reservations or out-of-repo; left out unless explicitly pulled back in:
- `AuthorizationVerdict.Denied` — v2.1 explicit-deny; no authoring path exists.
- `NamespaceKind.Simulated`, `Script.Language` — future driver / scripting engine that don't exist yet.
- Monaco InlayHints — parameter-name hints; likely intended-permanent stub.
- The full **HistoryUpdate service** — infra-gated (no backend insert/replace/delete RPC).
- Galaxy `ExecuteWrite` fire-and-forget write-outcome — lives in the **mxaccessgw sibling repo**, not here.
- **Durable AVEVA virtual-tag history sink** (the concrete `IHistoryWriter` behind H5) — infra-gated: the Wonderware historian sidecar exposes only HistoryRead + alarm-event writes, no live-data `WriteDataValues` RPC. H5 (Phase 1) wired the injectable seam with a `NullHistoryWriter` default; the durable sink needs a sidecar RPC first (same class of constraint as the HistoryUpdate service).
## Verification per phase
- Each phase: red→green unit tests for the load-bearing logic; `dotnet build` clean (production projects
are `TreatWarningsAsErrors`); full `dotnet test` green before merge.
- High-risk phases (1, 2, 3, 8) additionally gated on a user-driven live `/run` on docker-dev (and the
2-node rig for redundancy in Phase 2).
- AdminUI (Phase 6) and Client.UI (Phase 7) Razor/XAML changes proven only by live `/run`.