docs: MTConnect write-back recorded; stale MQTT write-through/browse-gap records reconciled
This commit is contained in:
@@ -114,6 +114,21 @@ Pure function in `.Contracts` (shared by driver, browser-commit, and the typed e
|
||||
|
||||
### 3.6 `IWritable` — **not implemented (v1)**
|
||||
|
||||
> **SUPERSEDED 2026-08-05 — write-back shipped, by a different mechanism.**
|
||||
> `MTConnectDriver` now implements `IWritable` (`b29623cf`) over the Agent's optional
|
||||
> **`AllowPut`** surface (`POST {agent}/{device}?{dataItemId}={value}`), gated off by
|
||||
> default at both the Agent (`AllowPut`) and the driver (`Write.Enabled`). That is
|
||||
> **observation injection into the Agent's buffer, not machine command** — see
|
||||
> [`2026-08-05-mtconnect-writeback-design.md`](2026-08-05-mtconnect-writeback-design.md)
|
||||
> §2.2 and `docs/drivers/MTConnect.md` §Write-back.
|
||||
> **The paragraph below is retained because its rationale still holds for *Interfaces*,
|
||||
> which remains deferred** (design §2.1 of the write-back doc re-affirms it: rarely
|
||||
> deployed, needs adapter-side cooperation, a state machine rather than a value write).
|
||||
> Only its two incidental claims about this build are out of date — the driver is no
|
||||
> longer `IWritable`-less, and nodes no longer materialize without `CurrentWrite` *because
|
||||
> of a missing capability*; the `CurrentWrite` bit was in fact always composed from the
|
||||
> authored `Tag.AccessLevel` alone, never from driver capability.
|
||||
|
||||
Justified per research §2.1: the MTConnect Agent surface is read-only by design; write-back exists only via optional, rarely-deployed MTConnect *Interfaces* (a request/response state-machine handshake, not a "set value" — modelling it as `IWritable` would mislead). Capability interfaces are composable, so omitting `IWritable` is idiomatic (Galaxy's write path is even fire-and-forget). Nodes materialize **without** the `AccessLevels.CurrentWrite` bit automatically. Revisit Interfaces only if a concrete deployment needs it.
|
||||
|
||||
### 3.7 `IHostConnectivityProbe` + `IRediscoverable`
|
||||
|
||||
@@ -179,7 +179,8 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
surface, data-plane precedence rules, quality-code mapping, and (most load-bearing) the **Known
|
||||
limitations** section.
|
||||
- **Scope shipped:** `IDriver`+`ITagDiscovery`+`IReadable`+`ISubscribable`+`IHostConnectivityProbe`+`IRediscoverable`
|
||||
(deliberately no `IWritable` — read-only Agent surface), browse **free via the Wave-0 universal
|
||||
(P1 shipped deliberately without `IWritable`; **`IWritable` added 2026-08-05, `b29623cf`** — see the
|
||||
Deferred line below), browse **free via the Wave-0 universal
|
||||
browser** (`SupportsOnlineDiscovery=true`, no browser project), typed AdminUI tag editor,
|
||||
`UNAVAILABLE→BadNoCommunication` mapping, CONDITION-as-`String`, ring-buffer re-baseline paging
|
||||
(both the sequence-gap and `OUT_OF_RANGE` legs).
|
||||
@@ -191,7 +192,18 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
- **Test results:** MTConnect unit suite 491/491; integration suite 12/12 against a real Agent
|
||||
(`mtconnect/agent:2.7.0.12`, **not** `mtconnect/cppagent` — that Docker Hub repo doesn't exist);
|
||||
skips cleanly (12 Skipped) offline. AdminUI 749/749; full solution 0 build errors.
|
||||
- **Deferred (documented, not built):** write-back via MTConnect Interfaces (design §3.6); P1.5
|
||||
- **Write-back — SHIPPED 2026-08-05, not via Interfaces** (`b29623cf`, branch
|
||||
`feature/mqtt-mtconnect-write`; design
|
||||
[`2026-08-05-mtconnect-writeback-design.md`](2026-08-05-mtconnect-writeback-design.md)).
|
||||
`IWritable` over the Agent's optional **`AllowPut`** surface
|
||||
(`POST {agent}/{device}?{dataItemId}={value}`) — **observation injection into the Agent's
|
||||
buffer, never a machine command**, gated off by default at both the Agent (`AllowPut`) and the
|
||||
driver (`Write.Enabled`), CONDITION items refused. **MTConnect *Interfaces* remains deferred**
|
||||
— design §3.6's rationale still holds for it (rare, adapter-side cooperation, a state machine
|
||||
rather than a value write), and it is a different mechanism, not this one. Caveat recorded in
|
||||
the driver guide: the OPC UA `CurrentWrite` bit still comes from the authored
|
||||
`Tag.AccessLevel` alone, so an operator must author **ReadWrite**.
|
||||
- **Deferred (documented, not built):** MTConnect *Interfaces* (design §3.6, above); P1.5
|
||||
CONDITION→native Part-9 alarms, `TIME_SERIES` array materialization, EVENT→enum vocab (design §9);
|
||||
P2 SHDR adapter ingest (design §9). Also two pre-existing fleet-wide gaps this build surfaced
|
||||
rather than caused: `IRediscoverable`/`IHostConnectivityProbe` have no server-side consumer (eight
|
||||
@@ -210,7 +222,11 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
`OnDataChange`, retained last-value read, `#`-observation browser); P2 Sparkplug B ingest
|
||||
(vendored Tahu proto + `Grpc.Tools` codegen, birth/alias/seq-gap/rebirth state machine,
|
||||
death→STALE, birth-driven browse tree, scoped Request-rebirth NCMD, typed tag editor).
|
||||
**Write-through (`IWritable`) is deferred to P3** — every MQTT node materializes read-only.
|
||||
**P3 write-through (`IWritable`) shipped 2026-07-31** — `72be1922`
|
||||
([#508](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/508), live-gated): plain
|
||||
`writeTopic` publish + Sparkplug NCMD/DCMD, with a node discovered `ViewOnly` when the encoder
|
||||
would refuse the write. Superseded the "every MQTT node materializes read-only" line this entry
|
||||
carried; see `docs/drivers/Mqtt.md` §Write-through.
|
||||
- **Fixture:** Mosquitto TLS+auth broker + JSON publisher sidecar, **live** at `10.100.0.35:8883`
|
||||
(`:1883` plaintext-but-authenticated); stack `/opt/otopcua-mqtt`, compose in
|
||||
`tests/Drivers/….Driver.Mqtt.IntegrationTests/Docker/`. Env-gated live suite
|
||||
@@ -236,13 +252,31 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
Sparkplug keys, merge-not-replace on save, group-id segment validation) + 8 falsifiable
|
||||
`MqttDriverFormModelTests` cases. **Three gates, three instances of the same defect class: a
|
||||
hand-maintained AdminUI surface left behind by a driver-side feature.**
|
||||
- **P2 live gate — two open UI gaps, documented not fixed** (see `docs/drivers/Mqtt.md` §Known gaps):
|
||||
- **A completely empty browse tree cannot arm a rebirth.** The scope comes from a tree click, so on
|
||||
a plant that has not birthed since the window opened `Request rebirth…` stays disabled — the very
|
||||
case the affordance exists for. `MqttBrowseSession.ResolveRebirthTargets` already accepts a bare
|
||||
`{group}/{edgeNode}` ("the prime rebirth target"); only a UI path to enter one is missing.
|
||||
Mitigation shipped: a **Refresh** button on the browse tree (it re-reads the same session, which
|
||||
previously rendered exactly once at open and never again).
|
||||
- **P2 live gate — four UI gaps recorded, ALL FOUR NOW CLOSED 2026-07-31** (`7f53bd96`; see
|
||||
`docs/drivers/Mqtt.md` §Known gaps, which lists the closures). Recorded as found, with what
|
||||
actually fixed each:
|
||||
- **A completely empty browse tree cannot arm a rebirth**
|
||||
([#514](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/514)). The scope comes from a tree
|
||||
click, so on a plant that has not birthed since the window opened `Request rebirth…` stays
|
||||
disabled — the very case the affordance exists for. `MqttBrowseSession.ResolveRebirthTargets`
|
||||
already accepts a bare `{group}/{edgeNode}` ("the prime rebirth target"); only a UI path to
|
||||
enter one was missing. Mitigation shipped at the time: a **Refresh** button on the browse tree
|
||||
(it re-reads the same session, which previously rendered exactly once at open and never again).
|
||||
**FIXED —** `RawBrowseModal` now offers a `group/edgeNode` scope input whenever no tree scope is
|
||||
armed; edge-node only, never a typed group broadcast.
|
||||
- **A metric name containing `/` cannot be browse-committed**
|
||||
([#509](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/509)). The derived raw **tag Name**
|
||||
is a RawPath segment and may not contain `/`, while `metricName` legitimately may — so the
|
||||
spec-mandatory `Node Control/Rebirth` was refused at commit (`Row N: Name must not contain '/'`).
|
||||
The refusal was loud and all-or-nothing, never a silently mis-bound tag, and **Manual entry** was
|
||||
the working path. Fixing it needed a name-sanitisation policy with a collision answer, so it was
|
||||
recorded rather than guessed at. **FIXED —** `RawBrowseCommitMapper.ToRawSegment` sanitises the
|
||||
segment and refuses a collision explicitly.
|
||||
- **`_canRebirth` is captured at browse-open**, so a reaped session still drew the button
|
||||
([#510](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/510)). **FIXED.**
|
||||
- **The tag editor injects the Plain-only `payloadFormat` key into a Sparkplug blob**
|
||||
([#513](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/513)) — cosmetic; the factory
|
||||
routes on the Sparkplug tuple and ignored it. **FIXED.**
|
||||
- **P2 live gate also found a PRE-EXISTING, cross-driver AdminUI defect — FIXED:** every node label in
|
||||
the shared `DriverBrowseTree` was an `<a href="#">`. In a Blazor Web App, `blazor.web.js`'s
|
||||
enhanced-navigation click interceptor resolves a bare `#` against `<base href="/">`, so **clicking
|
||||
@@ -252,19 +286,14 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
now `<button type="button">`. It dates to the component's introduction (2026-05-28) and affects
|
||||
**every** driver's picker; it only surfaced now because Sparkplug's Request-rebirth is the first
|
||||
feature that requires clicking a *label* rather than the ▶ toggle (always a `<button>`, always fine).
|
||||
- **A metric name containing `/` cannot be browse-committed.** The derived raw **tag Name** is a
|
||||
RawPath segment and may not contain `/`, while `metricName` legitimately may — so the
|
||||
spec-mandatory `Node Control/Rebirth` is refused at commit (`Row N: Name must not contain '/'`).
|
||||
The refusal is loud and all-or-nothing, never a silently mis-bound tag, and **Manual entry** is
|
||||
the working path (the Sparkplug tag editor accepts a slashed metric name). Fixing it needs a
|
||||
name-sanitisation policy with a collision answer, so it was recorded rather than guessed at.
|
||||
- `_canRebirth` is captured at browse-open, so a reaped session still draws the button.
|
||||
- The tag editor injects the Plain-only `payloadFormat` key into a Sparkplug blob — cosmetic; the
|
||||
factory routes on the Sparkplug tuple and ignores it.
|
||||
- **Rediscovery is inert in v3 (platform gap, not MQTT's).** The Sparkplug driver raises
|
||||
`OnRediscoveryNeeded` on a changed birth metric-set, but **nothing subscribes to it** and
|
||||
`DriverHostActor.HandleDiscoveredNodes` hard-returns. A DBIRTH introducing a metric does **not**
|
||||
change the OPC UA tree — redeploy. Driver-side behaviour is log-observable only.
|
||||
- **Rediscovery was inert in v3 (platform gap, not MQTT's) — HALF-CLOSED since 2026-07-27.** The
|
||||
Sparkplug driver raises `OnRediscoveryNeeded` on a changed birth metric-set; at the time nothing
|
||||
subscribed to it and `DriverHostActor.HandleDiscoveredNodes` hard-returned. `DriverInstanceActor`
|
||||
now **consumes** the raise (`09a401b8`) and it rides the driver-health snapshot to the AdminUI
|
||||
`/hosts` page as a **re-browse chip**, while the injection path was **deleted** rather than
|
||||
revived (#507, `adce27e7` — its two inputs are structurally empty in v3). So the standing part is
|
||||
unchanged and deliberate: a DBIRTH introducing a metric still does **not** change the served OPC
|
||||
UA tree; an operator re-browses and commits through `/raw`.
|
||||
- **Redundant-pair hazard (documented, not a code change):** both nodes of a pair run the driver, so a
|
||||
**fixed `clientId` makes them evict each other forever** — the broker logs
|
||||
`already connected, closing old connection` and both nodes reconnect every ~2 s while still
|
||||
@@ -285,7 +314,24 @@ Both CI-simulatable on the shared docker host, no hardware.
|
||||
`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
|
||||
`ISqlDialect`, and `SqlTagModel.Query` (P3, currently rejected end-to-end by design).
|
||||
3. **Wave 2 is done.** Both deliverables shipped and live-gated: **MQTT / Sparkplug B** (P1 + P2) and **MTConnect Agent** (P1 Agent MVP, PR #506). Remaining work on both is optional follow-on — MQTT P3 write-through (`IWritable`: NCMD/DCMD + plain publish) plus its two browse-UI
|
||||
gaps, and MTConnect write-back (deliberately deferred; the Agent surface is read-only).
|
||||
3. **Wave 2 is done, and so is the follow-on this entry used to list.** Both deliverables shipped and
|
||||
live-gated: **MQTT / Sparkplug B** (P1 + P2) and **MTConnect Agent** (P1 Agent MVP, PR #506).
|
||||
Since then:
|
||||
- **MQTT P3 write-through shipped and was live-gated 2026-07-31** — `72be1922`
|
||||
([#508](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/508)): plain `writeTopic` publish
|
||||
+ Sparkplug NCMD/DCMD.
|
||||
- **Both browse-UI gaps are closed**, with the other two P2 records, in `7f53bd96`
|
||||
([#509](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/509),
|
||||
[#510](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/510),
|
||||
[#513](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/513),
|
||||
[#514](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/514)) — see the P2 section above.
|
||||
- **MTConnect write-back is landing on `feature/mqtt-mtconnect-write`** (`b29623cf`), by
|
||||
**observation injection** through the Agent's `AllowPut` surface rather than the Interfaces
|
||||
handshake the original deferral assumed — design
|
||||
[`2026-08-05-mtconnect-writeback-design.md`](2026-08-05-mtconnect-writeback-design.md), which
|
||||
is also the record for this reconciliation. MTConnect *Interfaces* stays deferred.
|
||||
|
||||
Remaining optional follow-on for the pair: MTConnect P1.5 (CONDITION→Part-9 alarms,
|
||||
`TIME_SERIES` arrays, EVENT→enum vocab) and P2 SHDR ingest.
|
||||
|
||||
Update this file's Summary table and per-wave status whenever a deliverable changes state.
|
||||
|
||||
Reference in New Issue
Block a user