docs(drivers): tracker catches up with the #468 gate closure; design for the last dangling follow-up
The tracking doc and program-design §9 still showed Wave 0's live gate open —
it closed 2026-08-01 (c2c3c522). Both now record Waves 0-2 complete.
New design doc for the one residual item: the raw modals' typed tag editors
pass a '{}' stub as GetDriverConfigJson, so the AbCip/TwinCAT/FOCAS pickers'
Browse button captures against no endpoint and can never succeed. Decision:
wire the merged Driver+Device config through (the RawBrowseModal/Test-connect
seam already exists), not hide the button.
This commit is contained in:
@@ -217,9 +217,10 @@ MELSEC is out of the sequence (deferred).
|
||||
|
||||
## 9. Status
|
||||
|
||||
Research: **done** (8 reports). Designs: **done** (7 design docs). Code: **Wave 0 landed** —
|
||||
the universal Discover-backed browser (P1) is code-complete and unit-tested on
|
||||
`feat/universal-discovery-browser` (`CapturingAddressSpaceBuilder` + `CapturedTreeBrowseSession`
|
||||
+ `DiscoveryDriverBrowser`/`IUniversalDriverBrowser` + `BrowserSessionService` fallback +
|
||||
`ITagDiscovery.SupportsOnlineDiscovery` gate + AbCip/TwinCAT/FOCAS opt-ins & picker browse UI),
|
||||
with the live `/run` gate outstanding. Next action: complete the Wave-0 live gate, then Wave 1.
|
||||
Research: **done** (8 reports). Designs: **done** (7 design docs). Code: **Waves 0–2 all
|
||||
shipped and live-gated** (as of 2026-08-01) — the universal browser (merged `056887d6`; live
|
||||
gate Gitea #468 CLOSED 2026-08-01 via the patched AbCip fixture, `c2c3c522`), Modbus RTU
|
||||
(`0f38f486`/#495), SQL poll (`4ad54037`), MTConnect Agent (#506), and MQTT/Sparkplug B (P1+P2).
|
||||
Per-deliverable detail: [`2026-07-24-driver-expansion-tracking.md`](2026-07-24-driver-expansion-tracking.md).
|
||||
Next action: nothing scheduled — Wave 3 (BACnet/IP, Omron) has designs but no implementation
|
||||
plans; MELSEC stays deferred (§1).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
> **Program design (architecture / shared contract / build order):**
|
||||
> [`2026-07-15-driver-expansion-program-design.md`](2026-07-15-driver-expansion-program-design.md)
|
||||
>
|
||||
> Last updated: 2026-07-27.
|
||||
> Last updated: 2026-08-05.
|
||||
|
||||
## Legend
|
||||
|
||||
@@ -29,7 +29,7 @@ it reaches 📝.
|
||||
|
||||
| Wave | Deliverable | Design | Impl. plan | Status | Effort | Fixture / hardware |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **0** | Universal Discover-backed browser | [design](2026-07-15-universal-discovery-browser-design.md) | [plan](2026-07-15-universal-discovery-browser-implementation.md) · [tasks](2026-07-15-universal-discovery-browser-implementation.md.tasks.json) | 🟡 **Live gate open** | S–M | none (retrofits shipped drivers) |
|
||||
| **0** | Universal Discover-backed browser | [design](2026-07-15-universal-discovery-browser-design.md) | [plan](2026-07-15-universal-discovery-browser-implementation.md) · [tasks](2026-07-15-universal-discovery-browser-implementation.md.tasks.json) | ✅ **Done** — merged `056887d6`; live gate (Gitea #468) CLOSED 2026-08-01 via the patched AbCip fixture (`c2c3c522`) | S–M | patched libplctag `ab_server` (serves `@tags`) at `10.100.0.35:44818` |
|
||||
| **1** | Modbus RTU (extend Modbus) | [design](2026-07-15-modbus-rtu-driver-design.md) | [plan](2026-07-24-modbus-rtu-driver.md) · [tasks](2026-07-24-modbus-rtu-driver.md.tasks.json) | ✅ **Done** — merged to master `0f38f486` (#495), 11 tasks, live gate PASSED | **S (lowest)** | `pymodbus` `rtu_over_tcp` — CI-simulatable, no new infra |
|
||||
| **1** | SQL poll | [design](2026-07-15-sql-poll-driver-design.md) | [plan](2026-07-24-sql-poll-driver.md) · [tasks](2026-07-24-sql-poll-driver.md.tasks.json) | ✅ **Done** — merged to master `4ad54037`, 22 tasks, live gate PASSED; follow-ups #496/#497/#498 closed in `28c28667` | S–M | **existing central SQL Server** `10.100.0.35,14330` + SQLite unit |
|
||||
| **2** | MTConnect Agent | [design](2026-07-15-mtconnect-driver-design.md) | [plan](2026-07-24-mtconnect-driver.md) · [tasks](2026-07-24-mtconnect-driver.md.tasks.json) | ✅ **COMPLETE** — P1 Agent MVP, all 23 tasks + the 5-leg live gate PASSED (branch `feat/mtconnect-driver`, PR #506) | S–M | Dockerized `mtconnect/agent:2.7.0.12` (not `cppagent`) — CI-simulatable, live at `10.100.0.35:5000` |
|
||||
@@ -74,7 +74,7 @@ For a **new** driver the invocation shape is:
|
||||
|
||||
---
|
||||
|
||||
## Wave 0 — Universal Discover-backed browser 🟡
|
||||
## Wave 0 — Universal Discover-backed browser ✅
|
||||
|
||||
**What it is.** One generic `DiscoveryDriverBrowser` (+ `CapturingAddressSpaceBuilder`,
|
||||
`CapturedTreeBrowseSession`, `BrowserSessionService` fallback, and the
|
||||
@@ -85,11 +85,29 @@ marginal cost.
|
||||
|
||||
- **Design:** [`2026-07-15-universal-discovery-browser-design.md`](2026-07-15-universal-discovery-browser-design.md)
|
||||
- **Implementation plan:** [`2026-07-15-universal-discovery-browser-implementation.md`](2026-07-15-universal-discovery-browser-implementation.md) · [`.tasks.json`](2026-07-15-universal-discovery-browser-implementation.md.tasks.json)
|
||||
- **Status: 🟡 code-complete + merged, live gate open.**
|
||||
- **Status: ✅ Done — merged, live gate CLOSED.**
|
||||
- Merged to master — `056887d6` (*Merge feat/universal-discovery-browser — Wave-0 universal Discover-backed browser*), 2026-07-15.
|
||||
- Implementation plan: **19 / 19 tasks completed.**
|
||||
- Lit up AbCip / TwinCAT / FOCAS pickers with zero per-driver browse code.
|
||||
- **Outstanding:** the full tree-render live `/run` gate is **fixture-blocked** — tracked as **Gitea #468**. Complete this before leaning on browse in Wave 2/3.
|
||||
- **Live gate (Gitea #468) — CLOSED 2026-08-01** (`c2c3c522`). The fixture block was removed by
|
||||
teaching the libplctag `ab_server` sim to serve the Symbol Object `@tags` walk (vendored patches in
|
||||
`tests/Drivers/…AbCip.IntegrationTests/Docker/patches/`); all three criteria then ran green on
|
||||
docker-dev — populated nine-tag tree render, Refresh re-capture, and leaf commit round-trip. See
|
||||
"Second live-gate result" in the [implementation plan](2026-07-15-universal-discovery-browser-implementation.md).
|
||||
- **The gate earned its keep — three real defects fixed, none reachable by unit tests:**
|
||||
1. `CipSymbolObjectDecoder` padded to even byte boundaries between packed entries, so any
|
||||
odd-length symbol name silently dropped every tag behind it (the test builder padded
|
||||
identically, so all tests agreed with each other and none with a controller).
|
||||
2. **Gitea #536** — browse-commit discarded the discovered data type (a browsed REAL committed as
|
||||
`DInt`), fixed by carrying the driver's own type name via `NativeDataType`; affected
|
||||
AbCip/TwinCAT/FOCAS.
|
||||
3. AbCip Test Connect misclassified the no-authored-tags probe **on real hardware too** — the
|
||||
reachable-exception matcher looked for native C status spellings libplctag.NET never emits
|
||||
(plus a sim-side 0x03/0x04 CIP status mismatch).
|
||||
- **Residual follow-up (open):** the retired-flow driver pages (`AbCipDriverPage`/`TwinCATDriverPage`/
|
||||
`FOCASDriverPage`) host the picker body without passing `DriverType`/`GetConfigJson`, so their
|
||||
Browse button captures against `"{}"` — wire the live form config through or hide the affordance
|
||||
there. Recorded in the implementation plan's first-gate follow-ups.
|
||||
|
||||
---
|
||||
|
||||
@@ -249,8 +267,9 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
|
||||
## Next actions
|
||||
|
||||
1. **Close the Wave-0 live gate** (Gitea #468) — unblocks browse verification for BACnet (MTConnect
|
||||
shipped its own Task-21 live `/run` gate independently — see the plan file).
|
||||
1. **Wave 0 is done.** The live gate (Gitea #468) CLOSED 2026-08-01 (`c2c3c522`) — browse
|
||||
verification is no longer a blocker for BACnet. Residual follow-up: the driver-page picker
|
||||
config pass-through (see the Wave 0 section above).
|
||||
2. **Wave 1 is done.** Both deliverables shipped and live-gated: **Modbus RTU-over-TCP** (merged
|
||||
`0f38f486` / #495) and **SQL poll** (merged `4ad54037`; follow-ups #496/#497/#498 closed in
|
||||
`28c28667`). Remaining SQL work is optional follow-on — Postgres/ODBC dialects behind
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Raw-modal address pickers: pass live merged config to browse — design
|
||||
|
||||
**Status:** design, 2026-08-05. Closes the last dangling follow-up from the #468 universal-browser
|
||||
live gate ("driver-page address pickers pass no live config").
|
||||
|
||||
## 1. Problem
|
||||
|
||||
The #468 first-gate follow-up named `AbCipDriverPage`/`TwinCATDriverPage`/`FOCASDriverPage` as
|
||||
hosting `*AddressPickerBody` without passing `DriverType`/`GetConfigJson`. Those page shells were
|
||||
deleted when v3 Batch 2 retired the routed `/clusters/{id}/drivers` flow — but the defect pattern
|
||||
survived the migration into the two v3 hosts of the typed tag editors:
|
||||
|
||||
- `Components/Shared/Raw/RawTagModal.razor` (edit an existing raw tag) — `BuildEditorParameters()`
|
||||
passes `["GetDriverConfigJson"] = (Func<string>)(() => "{}")`.
|
||||
- `Components/Shared/Raw/RawManualTagEntryModal.razor` (manual multi-row tag entry) — same stub,
|
||||
per row.
|
||||
|
||||
The editors (`AbCipTagConfigEditor`, `TwinCATTagConfigEditor`, `FocasTagConfigEditor`) forward that
|
||||
`Func` to their `*AddressPickerBody`, whose browse gate is
|
||||
`BrowserService.CanBrowse(DriverType, GetConfigJson())`. `DiscoveryDriverBrowser.CanBrowse` creates
|
||||
a throwaway driver from the JSON — and `"{}"` parses fine into default options for AbCip / TwinCAT /
|
||||
FOCAS, all `ITagDiscovery { SupportsOnlineDiscovery: true }`. So the **"Browse controller" button
|
||||
renders enabled, and every click captures against an empty config** (no endpoint) — a guaranteed
|
||||
failure chip. The affordance is a lie in exactly the hosts where an operator would re-point a tag.
|
||||
|
||||
## 2. Decision — wire it through (not hide)
|
||||
|
||||
The #468 note offered "wire the live form config through, or hide the Browse affordance". Wire it
|
||||
through:
|
||||
|
||||
- Both hosts already hold the device context. `RawManualTagEntryModal` takes `DeviceId` as a
|
||||
parameter; `RawTagModal` receives `RawTagEditDto.DeviceId` from `LoadTagForEditAsync`.
|
||||
- The resolver already exists and is proven: `IRawTreeService.LoadMergedProbeConfigAsync(deviceId)`
|
||||
→ `(DriverType, MergedConfigJson)?` — the **same Driver+Device merge** the Test-connect probe and
|
||||
`RawBrowseModal` dial (endpoint lives in `DeviceConfig` in v3, so the merged blob is the only
|
||||
JSON a browse capture can succeed against).
|
||||
- Hiding would delete a legitimate workflow (browse while editing/creating a tag) whose whole
|
||||
supporting machinery shipped with Wave 0.
|
||||
|
||||
## 3. Design
|
||||
|
||||
Per modal, identically:
|
||||
|
||||
1. **Resolve on open.** When the modal becomes visible with a device in hand (RawTagModal: after
|
||||
`LoadTagForEditAsync` returns the dto; RawManualTagEntryModal: on open, from the `DeviceId`
|
||||
parameter), call `LoadMergedProbeConfigAsync(deviceId)` and cache the result in a nullable
|
||||
field `_mergedConfigJson`. Reset the cache when the modal closes/reopens for a different target.
|
||||
2. **Forward it.** `["GetDriverConfigJson"] = (Func<string>)(() => _mergedConfigJson ?? "{}")`.
|
||||
The `Func` is synchronous by contract (the picker body calls it inline), which is why the
|
||||
resolve must be prefetched, not lazy.
|
||||
3. **Fail soft.** A null resolve (device gone, service error) leaves the stub in place — the picker
|
||||
degrades to today's behaviour (graceful error chip on click), and the modal itself never blocks
|
||||
or errors on the resolve. Wrap the call in try/catch; log at debug.
|
||||
|
||||
No changes to the picker bodies, the editors, `IRawTreeService`, or the browse services — the seam
|
||||
already exists end-to-end; only the two stubs change.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- The MQTT/Sparkplug and OpcUaClient pickers (bespoke browsers with their own config paths — not
|
||||
hosted through these two stubs' editors, or already receiving config).
|
||||
- Making the resolve lazy/async in the picker body (`Func<string>` contract change) — YAGNI.
|
||||
- The two documented MQTT browse-UI gaps (tracked in `docs/drivers/Mqtt.md`).
|
||||
|
||||
## 4. Testing
|
||||
|
||||
The three MQTT live gates proved this defect class (hand-maintained AdminUI wiring left behind by a
|
||||
driver-side feature) is invisible to green unit suites — so test at two layers:
|
||||
|
||||
1. **Unit (falsifiable against today's code).** The AdminUI csproj has
|
||||
`InternalsVisibleTo("ZB.MOM.WW.OtOpcUa.AdminUI.Tests")` and precedent for testing razor
|
||||
component classes directly (`RawDriverTypeDialogParityTests`). Make each modal's editor-parameter
|
||||
construction internal-testable and assert the `GetDriverConfigJson` entry returns the cached
|
||||
merged JSON when one is loaded — this test **fails against the current stub**. Also assert the
|
||||
null-resolve fallback returns `"{}"`.
|
||||
2. **Live `/run` gate (docker-dev).** Against the patched AbCip fixture (`10.100.0.35:44818`, the
|
||||
#468 second-gate `ab_server` that serves `@tags`):
|
||||
- Open the **edit modal** for an existing AbCip raw tag → "Browse controller" → the nine-tag
|
||||
tree renders (was: error chip).
|
||||
- Open **manual entry** for the same device → same result on a row's picker.
|
||||
- Negative: a device whose endpoint is unreachable still shows the graceful error chip, and the
|
||||
modal itself stays usable.
|
||||
|
||||
## 5. Bookkeeping
|
||||
|
||||
On completion, close the loop in the two places that record the follow-up: the "Residual follow-up"
|
||||
bullet in `docs/plans/2026-07-24-driver-expansion-tracking.md` (Wave 0 section) and the
|
||||
"Follow-ups discovered during the first gate" item 2 in
|
||||
`docs/plans/2026-07-15-universal-discovery-browser-implementation.md` — both should note the pages
|
||||
were already deleted and the surviving instance (the raw modals' stubs) is fixed.
|
||||
Reference in New Issue
Block a user