From 1d0989d3b4c977851f73dac8185fe80e68308c51 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 24 Jul 2026 20:55:55 -0400 Subject: [PATCH] =?UTF-8?q?test(mtconnect):=20live=20gate=20on=20an=20isol?= =?UTF-8?q?ated=20rig=20=E2=80=94=204/5=20legs=20pass,=20deploy=20leg=20bl?= =?UTF-8?q?ocked=20on=20a=20cookie=20collision=20(Task=2021)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-dev/docker-compose.mtconnect.yml | 45 ++++++++++++++++ docs/plans/2026-07-24-mtconnect-driver.md | 62 +++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 docker-dev/docker-compose.mtconnect.yml diff --git a/docker-dev/docker-compose.mtconnect.yml b/docker-dev/docker-compose.mtconnect.yml new file mode 100644 index 00000000..d5abca14 --- /dev/null +++ b/docker-dev/docker-compose.mtconnect.yml @@ -0,0 +1,45 @@ +# Isolated MTConnect live-gate overlay (Task 21). +# +# Runs the MAIN central pair ONLY, from this worktree's image, under its own +# compose project + offset host ports, so the shared `otopcua-dev` rig and the +# three sibling driver worktrees that share `otopcua-host:dev` are untouched. +# Mirrors how the mqtt-sparkplug worktree isolated itself (`otopcua-host:mqtt`, +# project `otopcua-mqtt`, ports 9210/4850). +# +# docker build -f docker-dev/Dockerfile --target runtime -t otopcua-host:mtconnect . +# docker compose -p otopcua-mtc -f docker-dev/docker-compose.yml \ +# -f docker-dev/docker-compose.mtconnect.yml \ +# up -d sql migrator cluster-seed central-1 central-2 +# +# AdminUI -> http://localhost:9220 (login disabled; auto-admin) +# OPC UA -> opc.tcp://localhost:4860 (central-1) / :4861 (central-2) +# SQL -> localhost,14340 +# +# Both MAIN nodes run because `cluster-seed` declares MAIN as a 2-node cluster: +# ConfigPublishCoordinator sources its expected-ack set from enabled ClusterNode +# rows, so deploying with one node down fails at the apply deadline. +# +# Traefik is deliberately not started — central-1 publishes its AdminUI port +# directly, which removes the :9200 round-robin that otherwise makes it +# ambiguous which node served a request. + +# NOTE: `!override` is required on every `ports` list. Compose MERGES list-valued +# keys by appending, so a plain re-declaration would keep the base file's +# "14330:1433" / "4840:4840" alongside the offsets and collide with the running +# `otopcua-dev` rig ("Bind for 0.0.0.0:14330 failed: port is already allocated"). +services: + + sql: + ports: !override + - "14340:1433" + + central-1: + image: otopcua-host:mtconnect + ports: !override + - "4860:4840" + - "9220:9000" + + central-2: + image: otopcua-host:mtconnect + ports: !override + - "4861:4840" diff --git a/docs/plans/2026-07-24-mtconnect-driver.md b/docs/plans/2026-07-24-mtconnect-driver.md index 4e6b733b..a6c12026 100644 --- a/docs/plans/2026-07-24-mtconnect-driver.md +++ b/docs/plans/2026-07-24-mtconnect-driver.md @@ -894,6 +894,68 @@ git commit -m "test(mtconnect): live /run gate on docker-dev — browse/read/sub --- +### LIVE-GATE RESULT (2026-07-24) — 4 of 5 legs PASSED; the deploy leg is BLOCKED on a rig artifact, not a defect + +**Rig.** The shared `otopcua-dev` rig was NOT used: three sibling driver worktrees +(`modbus-rtu`, `mqtt-sparkplug`, `sql-poll-driver`) share its `otopcua-host:dev` image, and it had +been up for hours. Instead an isolated stack was built from this worktree — +`otopcua-host:mtconnect`, compose project `otopcua-mtc`, overlay +`docker-dev/docker-compose.mtconnect.yml`, MAIN pair only, ports AdminUI **9220** / OPC UA **4860** +/ SQL **14340**. Mirrors how the mqtt worktree isolated itself. The `otopcua-dev` rig and every +sibling worktree were left untouched. + +**Fixture.** `mtconnect/agent:2.7.0.12` + the SHDR adapter deployed to +`/opt/otopcua-mtconnect/` on the shared docker host and brought up healthy; `http://10.100.0.35:5000` +serves the seeded `OtFixtureCnc` model with live-moving values. Note it answers in the MTConnect +**2.0** namespace while the canned fixtures are 1.3 — the namespace-agnostic `LocalName` parsing +(Task 6) is what makes both work, now demonstrated rather than argued. + +| Leg | Result | Evidence | +|---|---|---| +| Integration suite vs. a REAL Agent | ✅ **12/12** | incl. `Discovery_types_an_upper_snake_numeric_event_as_Int64` (the `PART_COUNT` regression), `Read_and_subscribe_key_on_RawPath_when_the_artifact_supplies_RawTags` (the blocker fix), `Subscribe_delivers_a_changed_value_from_the_live_sample_stream`, `Discovery_binds_FullName_to_the_DataItem_id_when_the_name_differs` | +| 1. Driver creatable in `/raw` | ✅ | `MTConnect` present in `RawDriverTypeDialog`; `mtc-1` created. Proves the Host `Register` call is genuinely wired — **the one thing no test guards** (deleting it leaves the guard test green and substitutes a `StubbedDriver` while the deploy still seals green). | +| 2. Typed config modal renders | ✅ | "Configure driver · MTConnect" with all four sections (Agent / Polling / Connectivity probe / Reconnect), **not** the "no typed config form" banner. **This is the mutation that survived all 749 AdminUI tests** — the `switch` in `DriverConfigModal.razor` compiles to `BuildRenderTree` and is unreachable by reflection. | +| 3. Config persists + round-trips | ✅ | Stored `{"agentUri":"http://10.100.0.35:5000","requestTimeoutMs":5000,…,"probe":{"enabled":true,…},"reconnect":{…}}`; reopening the modal displays the saved URI (Razor binding verified — no bUnit in this repo). | +| 4. Browse picker → commit | ✅ | "Connect & browse" dialled the live Agent; tree rendered `Agent` + `OtFixtureCnc` with `Favail` (name ≠ id) beside id-named leaves, proving `browseName = Name ?? Id`. Committed TagConfig is **`{"fullName":"fixture_asset_changed","dataType":"String"}`** — the `fullName`-not-`address` fix, live. Wave-0 #468 did **not** block this. | +| 5. Deploy → OPC UA read/subscribe | ⛔ **BLOCKED — rig artifact** | see below | + +**Why leg 5 is blocked, and why it is not a product defect.** Browser cookies are scoped to a +**host, not a port**, so the AdminUI antiforgery cookie issued by the still-running `otopcua-dev` +rig on `localhost:9200` is sent to the isolated stack on `localhost:9220`, whose data-protection +keys differ: + +``` +Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery token could not be decrypted. +``` + +The `Deploy current configuration` POST is rejected before reaching any MTConnect code — no +`Deployment` row is created, and the UI surfaces nothing. Clearing JS-visible cookies does not help +(the antiforgery cookie is `HttpOnly`). This is a two-stacks-on-one-host collision, entirely outside +the driver. + +**To finish leg 5**, do any one of: (a) use a distinct host so cookies don't collide (a hosts entry, +or bind `127.0.0.2`), (b) drive `:9220` from an incognito/separate browser profile, or (c) stop the +`otopcua-dev` rig first. Then Deploy and: +`dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- read -u opc.tcp://localhost:4860 -n "ns=2;s=docker-dev/mtc-1/vmc/fixture_asset_changed"`. +Note the RawPath keying is already proven at the driver seam by the live integration test above; +what leg 5 adds is the `DeploymentArtifact` → address-space → OPC UA materialisation on top. + +**Two incidental findings from the rig work, worth separate follow-up:** +1. **`ClusterNode` in this schema has no `MaintenanceMode` column** (nor `AkkaPort`/`GrpcPort`), + although CLAUDE.md documents `MaintenanceMode` as the landed maintenance hatch and the mesh + Phase-1 columns as present. Reducing the seeded topology required deleting the SITE-A/SITE-B + rows instead. Either the doc or the migration is ahead of the other — reconcile. +2. `sqlcmd` against this schema needs `-I` (`SET QUOTED_IDENTIFIER ON`); without it every DML + statement fails with `Msg 1934` because of the filtered/computed indexes. + +**Teardown when done:** +```bash +docker compose -p otopcua-mtc -f docker-dev/docker-compose.yml -f docker-dev/docker-compose.mtconnect.yml down -v +ssh dohertj2@10.100.0.35 'cd /opt/otopcua-mtconnect && docker compose down' +``` + +--- + ## Task 22: Docs + deferred-writeback note; update the tracking doc **Classification:** trivial