diff --git a/docs/plans/2026-10-07-native-ab-transport.md.tasks.json b/docs/plans/2026-10-07-native-ab-transport.md.tasks.json index d54eda43..5c639925 100644 --- a/docs/plans/2026-10-07-native-ab-transport.md.tasks.json +++ b/docs/plans/2026-10-07-native-ab-transport.md.tasks.json @@ -29,7 +29,7 @@ { "id": 4, "subject": "Task 4: Capture golden frames (libplctag \u21c4 ab_server)", - "status": "pending", + "status": "completed", "description": "**Goal:** Capture, once, the exact request/reply bytes that libplctag 1.5.2 exchanges with `ab_server` for every operation the native stack must reproduce. Store them as hex fixtures that Tasks 7 and 8 assert parity against.\n\n**Files:**\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/README.md` (how each capture was taken: commit, image tag, command)\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/*.hex` (one file per exchange, as below)\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/CaptureFile.cs` (loader)\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/CaptureFileTests.cs`\n- Create: `scripts/ab-capture/capture.sh` (re-runnable recipe)\n\n**Acceptance Criteria:**\n- [ ] Each `.hex` file holds alternating `> ` (client\u2192server) and `< ` (server\u2192client) lines of **encapsulation frames**. Each line is one full EIP frame (the 24-byte header plus data) as uppercase hex with no spaces. Lines starting with `#` are comments.\n- [ ] These captures exist and are non-empty:\n - `logix-register-session.hex`\n - `logix-forward-open.hex` (both a Large FO 0x5B and, if libplctag falls back or uses it, a 0x54)\n - `logix-read-dint.hex`, `logix-read-dint-array16.hex`, `logix-read-string.hex`, `logix-read-bool.hex`\n - `logix-write-dint.hex`, `logix-write-string.hex`\n - `logix-read-unknown-tag.hex` (CIP 0x04, via patch 0002)\n - `logix-tags-listing.hex` (`@tags`, via patch 0001)\n - `logix-forward-close.hex`\n - `micro800-read-dint.hex` (UCMM, no Forward Open)\n - `slc500-read-n7.hex`, `slc500-write-n7.hex`, `slc500-read-f8.hex`, `micrologix-read-n7.hex`, `plc5-read-n7.hex`\n- [ ] `CaptureFileTests` loads every `.hex` file, parses every frame header, and asserts that each frame's encapsulation length field equals its data length.\n- [ ] `Captures/README.md` records, for each file: the libplctag version (1.5.2), the ab_server image (`otopcua-ab-server:libplctag-release`), the compose profile, and the libplctag tag string used.\n- [ ] `Captures/README.md` has a **\"Routing observed\"** table, one row per family (controllogix, micro800, slc500, micrologix, plc5, plus logixpccc if reachable), with the columns: ForwardOpen yes/no \u00b7 CIP request wrapped in Unconnected Send yes/no \u00b7 route bytes \u00b7 requestor-id bytes (PCCC). Tasks 9, 12 and 14 read their `Routing` choice from this table.\n\n**Verify:** `dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests --filter CaptureFile` \u2192 pass, and `ls tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/*.hex | wc -l` \u2192 \u2265 17.\n\nSteps + code: docs/plans/2026-10-07-native-ab-transport.md \u00a7 Task 4.\n\n```json:metadata\n{\"files\": [\"scripts/ab-capture/capture.sh\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/*.hex\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/CaptureFile.cs\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/CaptureFileTests.cs\", \"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/README.md\"], \"verifyCommand\": \"dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests --filter CaptureFile && ls tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Captures/*.hex | wc -l\", \"modelTier\": \"frontier\", \"model\": \"opus\", \"acceptanceCriteriaCount\": 5}\n```", "blockedBy": [ 1 @@ -96,7 +96,7 @@ { "id": 11, "subject": "Task 11: libplctag-confinement guard", - "status": "pending", + "status": "completed", "description": "**Goal:** A source-scan test that fails if any production `.cs` file outside the libplctag adapters references `libplctag`, so the eventual deletion stays mechanical.\n\n**Files:**\n- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/LibplctagConfinementGuardTests.cs`\n\n**Acceptance Criteria:**\n- [ ] The test walks up from `AppContext.BaseDirectory` to the directory containing `ZB.MOM.WW.OtOpcUa.slnx`, then scans every `*.cs` under `src/` (excluding `bin`/`obj`) for **code** references: `using libplctag` or `libplctag.(Tag|Status|PlcType|Protocol|LibPlcTagException|NativeImport)`. Prose such as the CLI help string `\"via libplctag. Second of six\u2026\"` (`AbCip.Cli/Program.cs:9`, `AbLegacy.Cli/Program.cs:8`) must **not** match, or the guard is red from wave 2 until T15/T16 (review finding 6).\n- [ ] Allowed files: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/Libplctag*.cs` and `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/Libplctag*.cs` only. Comments and XML docs are ignored by stripping `//` line comments before matching.\n- [ ] The failure message lists each offending file and line.\n- [ ] A second fact, `Prose_mentions_of_libplctag_do_not_trip_the_guard`, runs the regex against the literal `\"\u2026via libplctag. Second of six driver CLIs\"` and asserts no match.\n- [ ] **Falsifiability check:** temporarily add `using libplctag;` to `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs`, run the test and see it go red, then revert. Record that you did this in the commit message.\n\n**Verify:** `dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests --filter LibplctagConfinementGuard` \u2192 pass.\n\nSteps + code: docs/plans/2026-10-07-native-ab-transport.md \u00a7 Task 11.\n\n```json:metadata\n{\"files\": [\"tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/LibplctagConfinementGuardTests.cs\"], \"verifyCommand\": \"dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests --filter LibplctagConfinementGuard\", \"modelTier\": \"mechanical\", \"model\": \"haiku\", \"acceptanceCriteriaCount\": 5}\n```", "blockedBy": [ 2,