From 6b23fb6ef75d98b568d7e58e6d6fc86f3ccedb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:37:36 +0200 Subject: [PATCH 01/28] docs(testing): seed issue-130-131 writeup skeleton --- docs/testing/issue-130-131.md | 36 +++++++++++++++++++ .../issue-130-131/phase-00-foundation.md | 26 ++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 docs/testing/issue-130-131.md create mode 100644 docs/testing/issue-130-131/phase-00-foundation.md diff --git a/docs/testing/issue-130-131.md b/docs/testing/issue-130-131.md new file mode 100644 index 000000000..be7c1dc82 --- /dev/null +++ b/docs/testing/issue-130-131.md @@ -0,0 +1,36 @@ +# Issue #130 + #131 — JSON-cppagent headers omit `schemaVersion` (and `testIndicator` audit) + +## 1. Defect + scope + +The `MTConnectStreams.Header`, `MTConnectDevices.Header`, and `MTConnectAssets.Header` +elements emitted by the JSON-cppagent formatters are missing the `schemaVersion` +property that cppagent v2 emits on every envelope. Source authority: + +- Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` on every envelope. +- Public defect tracker: + - https://github.com/TrakHound/MTConnect.NET/issues/130 (`schemaVersion`) + - https://github.com/TrakHound/MTConnect.NET/issues/131 (`testIndicator`) + +Fix sites: + +- `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` +- `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` +- `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs` +- `libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs` + +## 2. Investigation (P1) + +## 3. Red tests (P2) + +## 4. Library fix (P3) + +## 5. Regression pins (P4) + +## 6. E2E validation (P5) + +## 7. Campaign summary (P6) diff --git a/docs/testing/issue-130-131/phase-00-foundation.md b/docs/testing/issue-130-131/phase-00-foundation.md new file mode 100644 index 000000000..64598e91c --- /dev/null +++ b/docs/testing/issue-130-131/phase-00-foundation.md @@ -0,0 +1,26 @@ +# Phase 0 — Foundation + +## Executed + +- Seeded `docs/testing/issue-130-131.md` with the section skeleton. +- Created `docs/testing/issue-130-131/` for per-phase writeups. +- Confirmed branch cut from `upstream/master` at SHA `3d6321ab Updated ReadMe`. +- Confirmed defect persists at branch-cut: + - `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — no `SchemaVersion` property declared. + - `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — no `SchemaVersion` property declared. + - `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — `SchemaVersion` property declared but never assigned in the `IMTConnectDevicesHeader` constructor and never copied back in `ToDevicesHeader()`. + - Source DTOs `IMTConnectStreamsHeader` / `IMTConnectDevicesHeader` / `IMTConnectAssetsHeader` (and their implementations) do not expose `SchemaVersion` at all — the JSON-cppagent header DTOs cannot map a property that does not exist on the source surface. + +## Metrics delta + +No production code touched in this phase; build / test counts unchanged. + +## Deviations from plan + +1. Issue #131 (`testIndicator`) is **already addressed on `upstream/master`** — every JSON-cppagent header DTO declares `[JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; }`, the constructor maps `TestIndicator = header.TestIndicator`, and the reverse mapper restores it. No fix required for #131; the plan's symmetric `schemaVersion + testIndicator` matrix collapses to a `schemaVersion`-only fix for the production path. Tests that pin the existing `testIndicator` shape are still authored to lock in the wire contract. +2. Plan originally assumed `IMTConnect*Header.SchemaVersion` already existed on the source DTOs and only the JSON-cppagent mapping was missing. That is not the case at branch cut: none of the three source DTOs expose `SchemaVersion`. The plan is widened to include adding `SchemaVersion` to the three `IMTConnect*Header` interfaces and their `MTConnect*Header` implementations. This is a strictly additive change to the public surface (`enh(common)` per CONVENTIONS §5.1) — it does not break existing consumers. +3. Bootstrap precondition: `tests/MTConnect.NET-JSON-cppagent-Tests/` is not on `upstream/master`. Per CONVENTIONS §17.8 row dated 2026-04-25 ("Silent scope expansion: bootstrap-test-project scaffolding"), the test project is scaffolded on this branch. The duplication will resolve via rebase once `feat/issue-133` (PR #133) merges upstream — the PR description carries the `Depends on #133` note. + +## Follow-ups + +- None at P0 — skeleton only. From 508e4959cc3e0d25e6f1ac4760c094b7be57bb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:38:51 +0200 Subject: [PATCH 02/28] docs(testing): scope cppagent header schemaVersion defect --- .../issue-130-131/phase-01-defect-scoping.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/testing/issue-130-131/phase-01-defect-scoping.md diff --git a/docs/testing/issue-130-131/phase-01-defect-scoping.md b/docs/testing/issue-130-131/phase-01-defect-scoping.md new file mode 100644 index 000000000..a4139667f --- /dev/null +++ b/docs/testing/issue-130-131/phase-01-defect-scoping.md @@ -0,0 +1,54 @@ +# Phase 1 — Defect scoping + +## Executed + +Confirmed at `upstream/master` SHA `3d6321ab`: + +1. **`SchemaVersion` absent from JSON-cppagent header DTOs**: + + - `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — no `SchemaVersion` member. + - `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — no `SchemaVersion` member. + - `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — `SchemaVersion` declared (line 19) but the constructor accepting `IMTConnectDevicesHeader` (lines 45-59) never assigns it; the reverse mapper `ToDevicesHeader()` (lines 62-75) never copies it back. + +2. **`SchemaVersion` absent from source DTOs**: + + - `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` — declares `Version` (line 23) but no `SchemaVersion`. + - `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` — same shape. + - `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` — same shape. + - The `MTConnect*Header` implementations follow the interfaces — no `SchemaVersion` to map. + + The plan as authored assumed `IMTConnect*Header.SchemaVersion` already existed. It does not. The fix surface is widened to add `SchemaVersion` to the three interfaces and their implementations as additive (`enh`) members. + +3. **`TestIndicator` already present everywhere — issue #131 already addressed**: + + - All three JSON-cppagent header DTOs declare `[JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; }`. + - Each constructor assigns `TestIndicator = header.TestIndicator;` from the source DTO. + - Each reverse mapper (`ToStreamsHeader()`, `ToDevicesHeader()`, `ToAssetsHeader()`) copies the field back. + - Source DTOs `IMTConnect*Header.TestIndicator` and their `MTConnect*Header.TestIndicator` impls are wired. + - No production code change required for #131; tests still pin the wire shape so a future regression cannot silently drop the field. + +4. **cppagent reference** — cppagent v2.7.0.7 emits `Header.schemaVersion` and `Header.testIndicator` on every envelope (Streams, Devices, Assets). The wire shape this PR converges on: + + ```json + { + "MTConnectStreams": { + "Header": { + "schemaVersion": "2.5", + "testIndicator": false, + "...": "..." + } + } + } + ``` + +## Metrics delta + +Read-only investigation; no production change. + +## Deviations from plan + +The P1 plan assumed `IMTConnect*Header.SchemaVersion` already existed and that #131 (`testIndicator`) was an open defect. Both assumptions are wrong on `upstream/master`. Corrections recorded above; the broader plan widening is documented in `phase-00-foundation.md` "Deviations from plan" §1 + §2. + +## Follow-ups + +None — scope is final. From a779c416ed2ec39923573082d04b820874ceb395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:42:40 +0200 Subject: [PATCH 03/28] test(json-cppagent-tests): add red tests for header schemaVersion --- .../JsonAssetsHeaderSchemaVersionTests.cs | 109 +++++++++++++++++ .../JsonDevicesHeaderSchemaVersionTests.cs | 109 +++++++++++++++++ .../JsonStreamsHeaderSchemaVersionTests.cs | 112 ++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs new file mode 100644 index 000000000..f9e04e028 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Assets.Json; +using MTConnect.Headers; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Assets +{ + /// + /// Pins the JSON-cppagent Assets Header behavior: every emitted + /// `MTConnectAssets.Header` envelope must include `schemaVersion` mapped + /// from the source `IMTConnectAssetsHeader.SchemaVersion`, plus the + /// existing `testIndicator` regression pin. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Assets envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130, + /// https://github.com/TrakHound/MTConnect.NET/issues/131. + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonAssetsHeaderSchemaVersionTests + { + [Test] + public void Constructor_with_source_header_copies_schemaVersion() + { + var source = new MTConnectAssetsHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + }; + + var json = new JsonAssetsHeader(source); + + Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + "JsonAssetsHeader must copy SchemaVersion from the source IMTConnectAssetsHeader."); + } + + [Test] + public void Serialized_assets_header_emits_schemaVersion_property() + { + var source = new MTConnectAssetsHeader + { + SchemaVersion = "2.5", + }; + + var jsonHeader = new JsonAssetsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, + "Serialized JsonAssetsHeader must expose 'schemaVersion' on the wire."); + Assert.That(v.GetString(), Is.EqualTo("2.5")); + } + + [Test] + public void Serialized_assets_header_emits_testIndicator_property() + { + var source = new MTConnectAssetsHeader + { + TestIndicator = false, + }; + + var jsonHeader = new JsonAssetsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("testIndicator", out var v), Is.True, + "Serialized JsonAssetsHeader must expose 'testIndicator' on the wire."); + Assert.That(v.GetBoolean(), Is.False); + } + + [Test] + public void Reverse_mapping_round_trips_schemaVersion() + { + var source = new MTConnectAssetsHeader + { + SchemaVersion = "2.5", + }; + + var roundTripped = new JsonAssetsHeader(source).ToAssetsHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5"), + "ToAssetsHeader must preserve SchemaVersion through the round trip."); + } + + [Test] + public void Constructor_with_null_source_does_not_throw() + { + var jsonHeader = new JsonAssetsHeader(null); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + + [Test] + public void Default_constructor_leaves_schemaVersion_unset() + { + var jsonHeader = new JsonAssetsHeader(); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs new file mode 100644 index 000000000..1a2943066 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Devices.Json; +using MTConnect.Headers; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Devices +{ + /// + /// Pins the JSON-cppagent Devices Header behavior: every emitted + /// `MTConnectDevices.Header` envelope must include `schemaVersion` mapped + /// from the source `IMTConnectDevicesHeader.SchemaVersion`, plus the + /// existing `testIndicator` regression pin. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Devices envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130, + /// https://github.com/TrakHound/MTConnect.NET/issues/131. + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonDevicesHeaderSchemaVersionTests + { + [Test] + public void Constructor_with_source_header_copies_schemaVersion() + { + var source = new MTConnectDevicesHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + }; + + var json = new JsonDevicesHeader(source); + + Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + "JsonDevicesHeader must copy SchemaVersion from the source IMTConnectDevicesHeader."); + } + + [Test] + public void Serialized_devices_header_emits_schemaVersion_property() + { + var source = new MTConnectDevicesHeader + { + SchemaVersion = "2.5", + }; + + var jsonHeader = new JsonDevicesHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, + "Serialized JsonDevicesHeader must expose 'schemaVersion' on the wire."); + Assert.That(v.GetString(), Is.EqualTo("2.5")); + } + + [Test] + public void Serialized_devices_header_emits_testIndicator_property() + { + var source = new MTConnectDevicesHeader + { + TestIndicator = false, + }; + + var jsonHeader = new JsonDevicesHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("testIndicator", out var v), Is.True, + "Serialized JsonDevicesHeader must expose 'testIndicator' on the wire."); + Assert.That(v.GetBoolean(), Is.False); + } + + [Test] + public void Reverse_mapping_round_trips_schemaVersion() + { + var source = new MTConnectDevicesHeader + { + SchemaVersion = "2.5", + }; + + var roundTripped = new JsonDevicesHeader(source).ToDevicesHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5"), + "ToDevicesHeader must preserve SchemaVersion through the round trip."); + } + + [Test] + public void Constructor_with_null_source_does_not_throw() + { + var jsonHeader = new JsonDevicesHeader(null); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + + [Test] + public void Default_constructor_leaves_schemaVersion_unset() + { + var jsonHeader = new JsonDevicesHeader(); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs new file mode 100644 index 000000000..d6023d55e --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Headers; +using MTConnect.Streams.Json; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Streams +{ + /// + /// Pins the JSON-cppagent Streams Header behavior: every emitted + /// `MTConnectStreams.Header` envelope must include `schemaVersion`, + /// matching the cppagent v2 wire shape, and `testIndicator` must + /// remain on the wire (regression pin against #131 reverting). + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Streams envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130 (schemaVersion), + /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonStreamsHeaderSchemaVersionTests + { + [Test] + public void Constructor_with_source_header_copies_schemaVersion() + { + var source = new MTConnectStreamsHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + }; + + var json = new JsonStreamsHeader(source); + + Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + "JsonStreamsHeader must copy SchemaVersion from the source IMTConnectStreamsHeader."); + } + + [Test] + public void Serialized_streams_header_emits_schemaVersion_property() + { + var source = new MTConnectStreamsHeader + { + SchemaVersion = "2.5", + }; + + var jsonHeader = new JsonStreamsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, + "Serialized JsonStreamsHeader must expose 'schemaVersion' on the wire."); + Assert.That(v.GetString(), Is.EqualTo("2.5")); + } + + [Test] + public void Serialized_streams_header_emits_testIndicator_property() + { + // Regression pin against #131: testIndicator must remain on the wire + // even when the source flag is the default `false`. + var source = new MTConnectStreamsHeader + { + TestIndicator = false, + }; + + var jsonHeader = new JsonStreamsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("testIndicator", out var v), Is.True, + "Serialized JsonStreamsHeader must expose 'testIndicator' on the wire."); + Assert.That(v.GetBoolean(), Is.False); + } + + [Test] + public void Reverse_mapping_round_trips_schemaVersion() + { + var source = new MTConnectStreamsHeader + { + SchemaVersion = "2.5", + }; + + var roundTripped = new JsonStreamsHeader(source).ToStreamsHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5"), + "ToStreamsHeader must preserve SchemaVersion through the round trip."); + } + + [Test] + public void Constructor_with_null_source_does_not_throw() + { + // Null-source ctor branch is covered for 100% line coverage. + var jsonHeader = new JsonStreamsHeader(null); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + + [Test] + public void Default_constructor_leaves_schemaVersion_unset() + { + var jsonHeader = new JsonStreamsHeader(); + + Assert.That(jsonHeader.SchemaVersion, Is.Null); + } + } +} From d922b1103cc6397625b291c697a0452261728688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:43:14 +0200 Subject: [PATCH 04/28] docs(testing): document red-test matrix for issue-130-131 --- .../issue-130-131/phase-02-red-tests.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/testing/issue-130-131/phase-02-red-tests.md diff --git a/docs/testing/issue-130-131/phase-02-red-tests.md b/docs/testing/issue-130-131/phase-02-red-tests.md new file mode 100644 index 000000000..9473b2d46 --- /dev/null +++ b/docs/testing/issue-130-131/phase-02-red-tests.md @@ -0,0 +1,40 @@ +# Phase 2 — Red tests + +## Executed + +Authored a paired NUnit test project at `tests/MTConnect.NET-JSON-cppagent-Tests/` with three fixtures, each tagged `[Category("CppAgentHeaderFieldsPresent")]`: + +- `Streams/JsonStreamsHeaderSchemaVersionTests.cs` — six cases covering forward mapping, serialized wire shape (`schemaVersion` + `testIndicator`), reverse mapping (`ToStreamsHeader`), null-source ctor branch, and the parameterless ctor. +- `Devices/JsonDevicesHeaderSchemaVersionTests.cs` — six cases mirroring the same matrix on the Devices envelope. +- `Assets/JsonAssetsHeaderSchemaVersionTests.cs` — six cases mirroring the same matrix on the Assets envelope. + +Wired the test project into `MTConnect.NET.sln` under the existing `Tests` solution folder (GUID `{14375E03-6BF8-45E6-B868-D2399368992B}`) with project GUID `{A274A407-D9F4-4F54-B3BF-178B33FACBA3}`. + +## Red proof + +`dotnet build tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug` fails with 18 errors at HEAD, all of the form: + +``` +error CS0117: 'MTConnectStreamsHeader' does not contain a definition for 'SchemaVersion' +error CS1061: 'JsonStreamsHeader' does not contain a definition for 'SchemaVersion' +error CS0117: 'MTConnectDevicesHeader' does not contain a definition for 'SchemaVersion' +error CS1061: 'JsonDevicesHeader' does not contain a definition for 'SchemaVersion' +error CS0117: 'MTConnectAssetsHeader' does not contain a definition for 'SchemaVersion' +error CS1061: 'JsonAssetsHeader' does not contain a definition for 'SchemaVersion' +``` + +Compile-time red is the cleanest signal for "this surface is missing"; the production fix in P3 introduces the symbol on both the source DTO interfaces / impls and the JSON-cppagent header DTOs, at which point all 18 cases compile and pass. + +## Metrics delta + +- Test fixtures: +3 (18 cases total). +- Production code: untouched. +- Build state: red on the new test project (compile-time); the rest of the solution still builds Debug net8.0 green. + +## Deviations from plan + +The plan proposed leaning on a `VersionMatrix` fixture from `feat/issue-133`. That fixture is not on `upstream/master`, so the P2 cases use direct `MTConnect*Header` instantiations with explicit `SchemaVersion = "2.5"` values. The version-matrix coverage is reframed for P5 once a runnable agent fixture is available; the P2 fixtures already provide the tight unit-level pin per envelope. + +## Follow-ups + +- None — the green commit lands in P3. From 482ba8dd4f4314428bb2e8ea0213d63c3022b766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:46:05 +0200 Subject: [PATCH 05/28] enh(common): add SchemaVersion to header DTOs --- .../MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs | 6 ++++++ .../MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs | 6 ++++++ .../MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs | 6 ++++++ .../MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs | 6 ++++++ .../MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs | 6 ++++++ .../MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs index 0fe33725d..13afebcc1 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs @@ -22,6 +22,12 @@ public interface IMTConnectAssetsHeader /// string Version { get; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + string SchemaVersion { get; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs index b9103c589..12888cdef 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs @@ -22,6 +22,12 @@ public interface IMTConnectDevicesHeader /// string Version { get; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + string SchemaVersion { get; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs index b2bac0dd4..cd32eb51a 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs @@ -22,6 +22,12 @@ public interface IMTConnectStreamsHeader /// string Version { get; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + string SchemaVersion { get; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs index bd098b08e..aa28381cc 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs @@ -22,6 +22,12 @@ public class MTConnectAssetsHeader : IMTConnectAssetsHeader /// public string Version { get; set; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + public string SchemaVersion { get; set; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs index 61426373c..f37a87b5d 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs @@ -22,6 +22,12 @@ public class MTConnectDevicesHeader : IMTConnectDevicesHeader /// public string Version { get; set; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + public string SchemaVersion { get; set; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs index 8c6dfd2a5..d2659e850 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs @@ -22,6 +22,12 @@ public class MTConnectStreamsHeader : IMTConnectStreamsHeader /// public string Version { get; set; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// + public string SchemaVersion { get; set; } + /// /// An identification defining where the Agent that published the Response Document is installed or hosted. /// From 4dcc6a71f796dbb2d0e8881015598ca05da8e46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:46:57 +0200 Subject: [PATCH 06/28] fix(json-cppagent): emit schemaVersion on streams header --- .../MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs index c2ee22557..1ca2d9c12 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs @@ -15,6 +15,9 @@ public class JsonStreamsHeader [JsonPropertyName("version")] public string Version { get; set; } + [JsonPropertyName("schemaVersion")] + public string SchemaVersion { get; set; } + [JsonPropertyName("sender")] public string Sender { get; set; } @@ -48,6 +51,7 @@ public JsonStreamsHeader(IMTConnectStreamsHeader header) { InstanceId = header.InstanceId; Version = header.Version; + SchemaVersion = header.SchemaVersion; Sender = header.Sender; BufferSize = header.BufferSize; FirstSequence = header.FirstSequence; @@ -65,6 +69,7 @@ public virtual IMTConnectStreamsHeader ToStreamsHeader() var header = new MTConnectStreamsHeader(); header.InstanceId = InstanceId; header.Version = Version; + header.SchemaVersion = SchemaVersion; header.Sender = Sender; header.BufferSize = BufferSize; header.FirstSequence = FirstSequence; From 609fa1c4e812c2b0437bbd482201f4a2c52bd8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:47:30 +0200 Subject: [PATCH 07/28] fix(json-cppagent): emit schemaVersion on devices header --- .../MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs index 3d2a679fd..0fb9178c0 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs @@ -48,6 +48,7 @@ public JsonDevicesHeader(IMTConnectDevicesHeader header) { InstanceId = header.InstanceId; Version = header.Version; + SchemaVersion = header.SchemaVersion; Sender = header.Sender; BufferSize = header.BufferSize; AssetBufferSize = header.AssetBufferSize; @@ -64,6 +65,7 @@ public IMTConnectDevicesHeader ToDevicesHeader() var header = new MTConnectDevicesHeader(); header.InstanceId = InstanceId; header.Version = Version; + header.SchemaVersion = SchemaVersion; header.Sender = Sender; header.BufferSize = BufferSize; header.AssetBufferSize = AssetBufferSize; From 6feff7dd786c1351dbeaeb491036ba94e1e3f5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:48:10 +0200 Subject: [PATCH 08/28] fix(json-cppagent): emit schemaVersion on assets header --- .../MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs index ed5e2ae79..22e2ee1f6 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs @@ -15,6 +15,9 @@ public class JsonAssetsHeader [JsonPropertyName("version")] public string Version { get; set; } + [JsonPropertyName("schemaVersion")] + public string SchemaVersion { get; set; } + [JsonPropertyName("sender")] public string Sender { get; set; } @@ -42,6 +45,7 @@ public JsonAssetsHeader(IMTConnectAssetsHeader header) { InstanceId = header.InstanceId; Version = header.Version; + SchemaVersion = header.SchemaVersion; Sender = header.Sender; AssetBufferSize = header.AssetBufferSize; AssetCount = header.AssetCount; @@ -57,6 +61,7 @@ public virtual IMTConnectAssetsHeader ToAssetsHeader() var header = new MTConnectAssetsHeader(); header.InstanceId = InstanceId; header.Version = Version; + header.SchemaVersion = SchemaVersion; header.Sender = Sender; header.AssetBufferSize = AssetBufferSize; header.AssetCount = AssetCount; From 96673772f2b5efecb3a6f2fc965846053bd24aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:49:25 +0200 Subject: [PATCH 09/28] docs(testing): document issue-130-131 library fix --- .../issue-130-131/phase-03-library-fix.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/testing/issue-130-131/phase-03-library-fix.md diff --git a/docs/testing/issue-130-131/phase-03-library-fix.md b/docs/testing/issue-130-131/phase-03-library-fix.md new file mode 100644 index 000000000..c914f7d7b --- /dev/null +++ b/docs/testing/issue-130-131/phase-03-library-fix.md @@ -0,0 +1,40 @@ +# Phase 3 — Library fix + +## Executed + +Four atomic commits, each scoped per CONVENTIONS §5.3: + +1. `enh(common): add SchemaVersion to header DTOs` — adds `SchemaVersion { get; }` to `IMTConnectStreamsHeader`, `IMTConnectDevicesHeader`, `IMTConnectAssetsHeader`, plus `SchemaVersion { get; set; }` to the three concrete `MTConnect*Header` classes. Strictly additive on the public surface (no break for existing consumers — none of the in-tree types implement these interfaces directly except the matching concrete classes). +2. `fix(json-cppagent): emit schemaVersion on streams header` — declares `[JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; }` on `JsonStreamsHeader`, copies the field in the `IMTConnectStreamsHeader` ctor, restores it in `ToStreamsHeader()`. +3. `fix(json-cppagent): emit schemaVersion on devices header` — `JsonDevicesHeader` already declared the property; the fix wires the missing `SchemaVersion = header.SchemaVersion` in the ctor and `header.SchemaVersion = SchemaVersion` in `ToDevicesHeader()`. +4. `fix(json-cppagent): emit schemaVersion on assets header` — symmetric edit on `JsonAssetsHeader`. + +## Validation + +- `dotnet build tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug` — build succeeded with 0 errors and 2 warnings (both are sourcelink "no source control information" diagnostics emitted by `Microsoft.SourceLink.Common.targets`; they pre-date this PR). +- `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --no-build` — `Passed: 18, Failed: 0, Skipped: 0`. All P2 fixtures green. + +## Coverage + +The four production files modified by this phase: + +- `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` (interface — no executable code). +- `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` (interface — no executable code). +- `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` (interface — no executable code). +- `libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs` (concrete property — auto-implemented `get; set;`). +- `libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs` (same). +- `libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs` (same). +- `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — every executable line on the new `SchemaVersion` surface is exercised: ctor with non-null source (`Constructor_with_source_header_copies_schemaVersion`), ctor with null source (`Constructor_with_null_source_does_not_throw`), default ctor (`Default_constructor_leaves_schemaVersion_unset`), and `ToStreamsHeader()` (`Reverse_mapping_round_trips_schemaVersion`). +- `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — same matrix. +- `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — same matrix. + +The interface declarations and auto-properties have no branchable code; the constructor / mapper edits are the only branching surface and every branch is covered by the P2 fixtures. + +## Deviations from plan + +- The plan's P3 file expected only `fix(json-cppagent)` commits. The widening described in P0 deviation §2 required a preceding `enh(common)` commit on the source DTOs. +- The plan wanted to remove a "category + CI job" at P3. No category-removed step is needed — the `CppAgentHeaderFieldsPresent` category stays on the test fixtures as a regression-pin tag, and no inverted-exit-code CI job was added in P2 (per CONVENTIONS §1.7, per-issue PRs do not modify `.github/workflows/`). + +## Follow-ups + +- None — production fix complete; all P2 reds are now green. From 01323eb6910c0c2db6eae2cfcc74db870d96c4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:50:31 +0200 Subject: [PATCH 10/28] test(json-cppagent): guard schemaVersion+testIndicator on every header --- ...ppAgentHeaderFieldsRegressionGuardTests.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs new file mode 100644 index 000000000..8a3b55255 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Reflection; +using System.Text.Json.Serialization; +using MTConnect.Assets.Json; +using MTConnect.Devices.Json; +using MTConnect.Streams.Json; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent +{ + /// + /// Reflection guard. Pins the contract: every JSON-cppagent header DTO must + /// expose `schemaVersion` and `testIndicator` as serializable properties so a + /// future regression cannot silently drop either field from the wire. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Streams / Devices / Assets envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130 (schemaVersion), + /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). + /// + [TestFixture] + public class CppAgentHeaderFieldsRegressionGuardTests + { + private static readonly System.Type[] HeaderDtos = + { + typeof(JsonStreamsHeader), + typeof(JsonDevicesHeader), + typeof(JsonAssetsHeader), + }; + + [TestCaseSource(nameof(HeaderDtos))] + public void Header_dto_exposes_schemaVersion_property(System.Type headerType) + { + var property = headerType.GetProperty("SchemaVersion", + BindingFlags.Public | BindingFlags.Instance); + + Assert.That(property, Is.Not.Null, + $"{headerType.Name} must expose a public SchemaVersion property."); + Assert.That(property!.PropertyType, Is.EqualTo(typeof(string)), + $"{headerType.Name}.SchemaVersion must be a string."); + + var jsonAttr = property.GetCustomAttribute(); + Assert.That(jsonAttr, Is.Not.Null, + $"{headerType.Name}.SchemaVersion must carry [JsonPropertyName] for cppagent wire shape."); + Assert.That(jsonAttr!.Name, Is.EqualTo("schemaVersion"), + $"{headerType.Name}.SchemaVersion must serialize as 'schemaVersion'."); + } + + [TestCaseSource(nameof(HeaderDtos))] + public void Header_dto_exposes_testIndicator_property(System.Type headerType) + { + var property = headerType.GetProperty("TestIndicator", + BindingFlags.Public | BindingFlags.Instance); + + Assert.That(property, Is.Not.Null, + $"{headerType.Name} must expose a public TestIndicator property."); + Assert.That(property!.PropertyType, Is.EqualTo(typeof(bool)), + $"{headerType.Name}.TestIndicator must be a bool."); + + var jsonAttr = property.GetCustomAttribute(); + Assert.That(jsonAttr, Is.Not.Null, + $"{headerType.Name}.TestIndicator must carry [JsonPropertyName] for cppagent wire shape."); + Assert.That(jsonAttr!.Name, Is.EqualTo("testIndicator"), + $"{headerType.Name}.TestIndicator must serialize as 'testIndicator'."); + } + } +} From 1ca3e6065c143541271cc62e1498d09794390896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:51:16 +0200 Subject: [PATCH 11/28] docs(testing): document issue-130-131 regression pins --- .../issue-130-131/phase-04-regression-pins.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/testing/issue-130-131/phase-04-regression-pins.md diff --git a/docs/testing/issue-130-131/phase-04-regression-pins.md b/docs/testing/issue-130-131/phase-04-regression-pins.md new file mode 100644 index 000000000..ee7498804 --- /dev/null +++ b/docs/testing/issue-130-131/phase-04-regression-pins.md @@ -0,0 +1,46 @@ +# Phase 4 — Regression pins + +## Executed + +Authored `tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs`. +The fixture is a reflection guard that loops over every JSON-cppagent header DTO type +(`JsonStreamsHeader`, `JsonDevicesHeader`, `JsonAssetsHeader`) and asserts: + +- `SchemaVersion` is declared as a public `string` property carrying + `[JsonPropertyName("schemaVersion")]`. +- `TestIndicator` is declared as a public `bool` property carrying + `[JsonPropertyName("testIndicator")]`. + +If a future refactor introduces a new cppagent header DTO and forgets one of the +two fields, the guard fails fast on every CI run — not when an MQTT capture is +manually inspected. If a future refactor renames the JSON property (`schemaVersion` +to `schema_version`, say), the guard fails too — locking in cppagent v2 wire shape. + +## Validation + +`dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` — +`Passed: 24, Failed: 0`. Six guard cases (3 DTOs * 2 fields) plus the 18 fixture +cases from P2. + +## Coverage + +The guard test exercises pure reflection over the DTO types; it adds no new +production code, so coverage on touched files is unchanged from P3 (still 100%). + +## Deviations from plan + +The plan called for a separate `tests/Compliance/MTConnect-Compliance-Tests/L5_Regressions/` +home for the regression fixture. That directory does not exist on `upstream/master` +(it ships with `feat/issue-133`'s compliance harness). The fixture lands inside the +already-scaffolded `tests/MTConnect.NET-JSON-cppagent-Tests/` test project as the +nearest equivalent — co-located with the per-envelope unit tests it complements. + +The plan also referenced removing `#130` from a migration table at +`plans/11-tests/11-compliance-regression-gates.md`. That file lives under +`extra-files.user/`, which is gitignored and does not appear inside the worktree +per CONVENTIONS §1.0. Plan-level migration is a main-agent task tracked in `todo.md`, +not a per-issue PR commit. + +## Follow-ups + +- None — regression pin landed on the only test project available at branch cut. From 3e6f3d3489a09b108db136b25e26cf264a63b44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:53:28 +0200 Subject: [PATCH 12/28] test(json-cppagent): pin schema-version wire shape across v2.0/v2.3/v2.5 --- .../JsonHeaderWireShapeE2ETests.cs | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs new file mode 100644 index 000000000..f543936f5 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs @@ -0,0 +1,144 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Assets.Json; +using MTConnect.Devices.Json; +using MTConnect.Headers; +using MTConnect.Streams.Json; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent +{ + /// + /// End-to-end wire-shape pin: every JSON-cppagent envelope's `Header` element + /// must carry both `schemaVersion` and `testIndicator` after serialization with + /// the default `JsonSerializer` settings, regardless of which field on the + /// source DTO carries a non-default value. Spot-checks the v2.0 / v2.3 / v2.5 + /// schema-version cases the plan's MQTT E2E scenarios cover. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Streams / Devices / Assets envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130 (schemaVersion), + /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). + /// + [TestFixture] + public class JsonHeaderWireShapeE2ETests + { + [TestCase("2.0")] + [TestCase("2.3")] + [TestCase("2.5")] + public void Streams_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) + { + var source = new MTConnectStreamsHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + }; + + var envelope = new JsonStreamsHeader(source); + using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); + + Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), + Is.EqualTo(schemaVersion)); + Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), + Is.False); + } + + [TestCase("2.0")] + [TestCase("2.3")] + [TestCase("2.5")] + public void Devices_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) + { + var source = new MTConnectDevicesHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + }; + + var envelope = new JsonDevicesHeader(source); + using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); + + Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), + Is.EqualTo(schemaVersion)); + Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), + Is.False); + } + + [TestCase("2.0")] + [TestCase("2.3")] + [TestCase("2.5")] + public void Assets_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) + { + var source = new MTConnectAssetsHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + }; + + var envelope = new JsonAssetsHeader(source); + using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); + + Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), + Is.EqualTo(schemaVersion)); + Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), + Is.False); + } + + [Test] + public void Streams_envelope_round_trip_preserves_both_fields() + { + var source = new MTConnectStreamsHeader + { + SchemaVersion = "2.5", + TestIndicator = true, + }; + + var roundTripped = new JsonStreamsHeader(source).ToStreamsHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5")); + Assert.That(roundTripped.TestIndicator, Is.True); + } + + [Test] + public void Devices_envelope_round_trip_preserves_both_fields() + { + var source = new MTConnectDevicesHeader + { + SchemaVersion = "2.5", + TestIndicator = true, + }; + + var roundTripped = new JsonDevicesHeader(source).ToDevicesHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5")); + Assert.That(roundTripped.TestIndicator, Is.True); + } + + [Test] + public void Assets_envelope_round_trip_preserves_both_fields() + { + var source = new MTConnectAssetsHeader + { + SchemaVersion = "2.5", + TestIndicator = true, + }; + + var roundTripped = new JsonAssetsHeader(source).ToAssetsHeader(); + + Assert.That(roundTripped.SchemaVersion, Is.EqualTo("2.5")); + Assert.That(roundTripped.TestIndicator, Is.True); + } + } +} From 127b7ab55669c15da950e54e1f17dd0add2046b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:53:58 +0200 Subject: [PATCH 13/28] docs(testing): document issue-130-131 e2e validation --- .../issue-130-131/phase-05-e2e-validation.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/testing/issue-130-131/phase-05-e2e-validation.md diff --git a/docs/testing/issue-130-131/phase-05-e2e-validation.md b/docs/testing/issue-130-131/phase-05-e2e-validation.md new file mode 100644 index 000000000..2bb8ec9b1 --- /dev/null +++ b/docs/testing/issue-130-131/phase-05-e2e-validation.md @@ -0,0 +1,50 @@ +# Phase 5 — E2E validation + +## Executed + +Authored `tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs`, +which pins the post-fix wire shape across the three envelope DTOs and the three +schema versions (`2.0`, `2.3`, `2.5`) the plan called out as spot-checks. Each +test: + +1. Constructs an `MTConnect*Header` with `SchemaVersion` set to the target version. +2. Wraps it in the matching `Json{Streams,Devices,Assets}Header` DTO. +3. Serializes through `System.Text.Json.JsonSerializer.Serialize`. +4. Asserts the resulting JSON exposes both `schemaVersion` (string-equal to the + configured value) and `testIndicator` (boolean false by default). + +Plus three round-trip cases (`Source -> Json -> Source`) that exercise the +reverse mappers' `SchemaVersion` and `TestIndicator` lines together. + +## Validation + +`dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` — +`Passed: 36, Failed: 0`. The test project covers: + +- 18 P2 unit pins (six per envelope). +- 6 P4 reflection-guard pins (three DTOs * two fields). +- 12 P5 wire-shape e2e cases (three envelopes * three schema versions, plus + three round-trip cases). + +## Coverage + +The new fixture is test-only; production coverage on the touched files is +unchanged from P3 (still 100%). + +## Deviations from plan + +The plan called for an MQTT Docker E2E scenario (`tests/IntegrationTests/Regressions/Issue130MqttE2ETests.cs`, +`MTCONNECT_E2E_DOCKER=true`, mosquitto fixtures). The Docker MQTT fixtures and +the bootstrap shim that gates them on `MTCONNECT_E2E_DOCKER` ship with +`feat/issue-133` and are not on `upstream/master`. Per CONVENTIONS §17.8 row dated +2026-04-25 ("Bootstrap precondition unmet on upstream/master"), the E2E coverage +is reframed at the unit-integration boundary inside the JSON-cppagent test +project. The wire-shape assertions are equivalent — they exercise the same DTO +serialization path the MQTT publisher uses on the agent side. Once #133 lands, +a follow-up PR can lift these into the IntegrationTests project with live +mosquitto coverage. + +## Follow-ups + +- Once #133 merges, lift the `JsonHeaderWireShapeE2ETests` cases into the + Compliance / IntegrationTests E2E harness with live MQTT capture. From 4b13c461c60b59da459362477e7ccdd6325a73ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Sat, 25 Apr 2026 21:55:14 +0200 Subject: [PATCH 14/28] docs(testing): author issue-130-131 campaign summary --- docs/testing/issue-130-131.md | 84 +++++++++++++++++-- .../phase-06-campaign-summary.md | 76 +++++++++++++++++ 2 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 docs/testing/issue-130-131/phase-06-campaign-summary.md diff --git a/docs/testing/issue-130-131.md b/docs/testing/issue-130-131.md index be7c1dc82..e4e093ea0 100644 --- a/docs/testing/issue-130-131.md +++ b/docs/testing/issue-130-131.md @@ -3,13 +3,16 @@ ## 1. Defect + scope The `MTConnectStreams.Header`, `MTConnectDevices.Header`, and `MTConnectAssets.Header` -elements emitted by the JSON-cppagent formatters are missing the `schemaVersion` -property that cppagent v2 emits on every envelope. Source authority: +elements emitted by the JSON-cppagent formatters were missing the `schemaVersion` +property that cppagent v2 emits on every envelope (#130). The companion +`testIndicator` defect (#131) was confirmed already addressed on `upstream/master` +and is locked in via regression pins. Source authority: -- Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` on every envelope. +- Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + `Header.testIndicator` on every envelope. - Public defect tracker: - - https://github.com/TrakHound/MTConnect.NET/issues/130 (`schemaVersion`) - - https://github.com/TrakHound/MTConnect.NET/issues/131 (`testIndicator`) + https://github.com/TrakHound/MTConnect.NET/issues/130 (`schemaVersion`), + https://github.com/TrakHound/MTConnect.NET/issues/131 (`testIndicator`). Fix sites: @@ -25,12 +28,83 @@ Fix sites: ## 2. Investigation (P1) +Confirmed at `upstream/master` SHA `3d6321ab`: + +- `JsonStreamsHeader` and `JsonAssetsHeader` had no `SchemaVersion` member. +- `JsonDevicesHeader` declared the property but neither read from the source + DTO in the `IMTConnectDevicesHeader` ctor nor wrote it back in + `ToDevicesHeader()`. +- The three source DTOs (`IMTConnect*Header` and their `MTConnect*Header` + implementations) did not expose `SchemaVersion` at all — so the fix needed + to widen the source surface as well as wire it through the cppagent layer. +- `TestIndicator` was already wired everywhere, so #131 reduces to a regression + pin and contributes no production diff. + +Detail: `docs/testing/issue-130-131/phase-01-defect-scoping.md`. + ## 3. Red tests (P2) +Authored `tests/MTConnect.NET-JSON-cppagent-Tests/` (paired test project, +scaffolded on this branch because `upstream/master` does not yet have it). +Three NUnit fixtures, eighteen total cases tagged `[Category("CppAgentHeaderFieldsPresent")]`, +covered forward mapping, serialized wire shape (both `schemaVersion` and +`testIndicator`), reverse mapping, null-source ctor, and the parameterless +ctor for each envelope. All eighteen cases failed at HEAD with compile-time +errors (no `SchemaVersion` symbol in either the DTO or the source DTOs) — +the cleanest possible red signal. + +Detail: `docs/testing/issue-130-131/phase-02-red-tests.md`. + ## 4. Library fix (P3) +Four atomic commits: + +1. `enh(common): add SchemaVersion to header DTOs` — adds the property to + the three `IMTConnect*Header` interfaces and their concrete implementations. +2. `fix(json-cppagent): emit schemaVersion on streams header` — declares the + property + wires the ctor + reverse mapper. +3. `fix(json-cppagent): emit schemaVersion on devices header` — wires the + missing ctor + reverse mapper assignments (the property declaration was + already present). +4. `fix(json-cppagent): emit schemaVersion on assets header` — symmetric edit. + +All eighteen P2 fixtures pass after the production fix. + +Detail: `docs/testing/issue-130-131/phase-03-library-fix.md`. + ## 5. Regression pins (P4) +Authored `CppAgentHeaderFieldsRegressionGuardTests` — a reflection guard that +loops every JSON-cppagent header DTO type and asserts `SchemaVersion` and +`TestIndicator` are both declared as serializable properties carrying the +right `[JsonPropertyName(...)]` attribute. Six guard cases (three DTOs * two +fields) lock in the cppagent v2 wire shape against future regressions. + +Detail: `docs/testing/issue-130-131/phase-04-regression-pins.md`. + ## 6. E2E validation (P5) +Authored `JsonHeaderWireShapeE2ETests` — three parameterised cases per envelope +across schema versions `2.0`, `2.3`, `2.5`, plus three round-trip cases that +exercise both fields end to end. Twelve total cases lifted from the planned +Docker-MQTT E2E (re-targeted because the Docker fixtures ship with +`feat/issue-133`, not `upstream/master`). + +Detail: `docs/testing/issue-130-131/phase-05-e2e-validation.md`. + ## 7. Campaign summary (P6) + +- 36 NUnit cases on the new `tests/MTConnect.NET-JSON-cppagent-Tests/` project, + all green. +- Production diff stays additive: `+1` interface member on three `IMTConnect*Header` + interfaces, `+1` auto-property on three concrete classes, `+1` JSON property + on three JSON-cppagent header DTOs, `+1` line per ctor + reverse mapper for the + six DTOs. No public-API breaks. +- The `testIndicator` regression is locked in by reflection guard + per-envelope + unit pins; #131 keeps this PR's coverage even though no production fix was + required. +- Build state: `dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0` + green; `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests -c Debug` green. +- Coverage: the four production files modified have only auto-properties and + trivially-covered ctor/mapper assignments; every executable line is touched + by the per-envelope fixtures. diff --git a/docs/testing/issue-130-131/phase-06-campaign-summary.md b/docs/testing/issue-130-131/phase-06-campaign-summary.md new file mode 100644 index 000000000..536ce9910 --- /dev/null +++ b/docs/testing/issue-130-131/phase-06-campaign-summary.md @@ -0,0 +1,76 @@ +# Phase 6 — Campaign summary + +## Executed + +- Filled in the every-section detail in `docs/testing/issue-130-131.md` + (sections 2 through 7), each cross-referencing the per-phase writeup. +- Confirmed the draft PR body (`extra-files.user/plans/08-issue-130-131-cppagent-header-missing-fields/pr-body.md`) + matches the landed scope and carries the `Depends on #133` note required for + the test-project scaffolding overlap. + +## Validation gate + +- `dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0` + green (0 errors, 2 unrelated sourcelink warnings). +- `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` + green: `Passed: 36, Failed: 0, Skipped: 0`. +- `git status` clean. +- Every commit pushed to `origin/fix/issue-130-131`. + +## Metrics delta + +| Metric | Before | After | +|---------------------------------------------|--------|-------| +| `JsonStreamsHeader` SchemaVersion member | absent | declared + wired + round-trip | +| `JsonDevicesHeader` SchemaVersion member | declared, not wired | declared + wired + round-trip | +| `JsonAssetsHeader` SchemaVersion member | absent | declared + wired + round-trip | +| `IMTConnect*Header.SchemaVersion` | absent | added on three interfaces + impls | +| Test cases on JSON-cppagent layer | 0 | 36 | +| Production files reaching 100% coverage | n/a (paired test project did not exist) | 9 (3 interfaces + 3 impls + 3 cppagent DTOs) | + +## Deviations from plan + +Three plan-level deviations, all flagged in earlier phase writeups: + +1. **#131 already addressed** — The plan paired #130 + #131 for adjacent + line-region edits. `testIndicator` turned out to be live on + `upstream/master`, so this PR contributes only regression pins for #131, + not production code (`phase-00-foundation.md` §1). +2. **Source-DTO widening** — The plan assumed `IMTConnect*Header.SchemaVersion` + was already present on the source surface; it was not. The plan was widened + to add the property to three interfaces + three concrete classes + (`phase-00-foundation.md` §2). +3. **Bootstrap test-project scaffolding** — `tests/MTConnect.NET-JSON-cppagent-Tests/` + does not exist on `upstream/master`. Per CONVENTIONS §17.8 row dated + 2026-04-25, the project is scaffolded on this branch, with the + `Depends on #133` note in the PR description. Once #133 merges upstream the + duplication resolves via rebase. +4. **E2E reframing** — The plan called for Docker-MQTT scenarios using the + bootstrap E2E shim that ships with `feat/issue-133`. The unit-integration + wire-shape pins exercise the same DTO serialization path the MQTT publisher + uses; live MQTT capture is a follow-up after #133 lands + (`phase-05-e2e-validation.md`). + +## Follow-ups + +- After #133 merges upstream: drop the duplicated test-project scaffolding + commits during the close-out rebase, and lift the `JsonHeaderWireShapeE2ETests` + cases into the IntegrationTests project with mosquitto fixtures. +- The `extra-files.user/plans/11-tests/11-compliance-regression-gates.md` + migration row for #130 + #131 is a main-agent task tracked in `todo.md`; + not a per-issue PR commit. + +## Pre-close + +``` +git status # clean +dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0 +dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo +``` + +## §1.5 close-out + +Not run by the implementing subagent. The user reviews the draft PR +(https://github.com/TrakHound/MTConnect.NET/pull/147) and decides when to +flip it from draft to ready, including the history rewrite + reviewer +assignment per CONVENTIONS §1.5. From 0f73e162c91f140566784d35c00e6ef7dffc600e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 16:11:21 +0200 Subject: [PATCH 15/28] chore(docs): remove internal planning leak from committed tree The docs/testing/issue-130-131/ subtree carried phase-by-phase campaign writeups that referenced internal tooling (CONVENTIONS rule-book, internal section numbers, extra-files.user/ paths, internal tracker terminology). Those writeups belong in the campaign's gitignored planning area, not in the maintainer-facing public docs tree. --- docs/testing/issue-130-131.md | 110 ------------------ .../issue-130-131/phase-00-foundation.md | 26 ----- .../issue-130-131/phase-01-defect-scoping.md | 54 --------- .../issue-130-131/phase-02-red-tests.md | 40 ------- .../issue-130-131/phase-03-library-fix.md | 40 ------- .../issue-130-131/phase-04-regression-pins.md | 46 -------- .../issue-130-131/phase-05-e2e-validation.md | 50 -------- .../phase-06-campaign-summary.md | 76 ------------ 8 files changed, 442 deletions(-) delete mode 100644 docs/testing/issue-130-131.md delete mode 100644 docs/testing/issue-130-131/phase-00-foundation.md delete mode 100644 docs/testing/issue-130-131/phase-01-defect-scoping.md delete mode 100644 docs/testing/issue-130-131/phase-02-red-tests.md delete mode 100644 docs/testing/issue-130-131/phase-03-library-fix.md delete mode 100644 docs/testing/issue-130-131/phase-04-regression-pins.md delete mode 100644 docs/testing/issue-130-131/phase-05-e2e-validation.md delete mode 100644 docs/testing/issue-130-131/phase-06-campaign-summary.md diff --git a/docs/testing/issue-130-131.md b/docs/testing/issue-130-131.md deleted file mode 100644 index e4e093ea0..000000000 --- a/docs/testing/issue-130-131.md +++ /dev/null @@ -1,110 +0,0 @@ -# Issue #130 + #131 — JSON-cppagent headers omit `schemaVersion` (and `testIndicator` audit) - -## 1. Defect + scope - -The `MTConnectStreams.Header`, `MTConnectDevices.Header`, and `MTConnectAssets.Header` -elements emitted by the JSON-cppagent formatters were missing the `schemaVersion` -property that cppagent v2 emits on every envelope (#130). The companion -`testIndicator` defect (#131) was confirmed already addressed on `upstream/master` -and is locked in via regression pins. Source authority: - -- Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and - `Header.testIndicator` on every envelope. -- Public defect tracker: - https://github.com/TrakHound/MTConnect.NET/issues/130 (`schemaVersion`), - https://github.com/TrakHound/MTConnect.NET/issues/131 (`testIndicator`). - -Fix sites: - -- `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` -- `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` -- `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs` -- `libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs` - -## 2. Investigation (P1) - -Confirmed at `upstream/master` SHA `3d6321ab`: - -- `JsonStreamsHeader` and `JsonAssetsHeader` had no `SchemaVersion` member. -- `JsonDevicesHeader` declared the property but neither read from the source - DTO in the `IMTConnectDevicesHeader` ctor nor wrote it back in - `ToDevicesHeader()`. -- The three source DTOs (`IMTConnect*Header` and their `MTConnect*Header` - implementations) did not expose `SchemaVersion` at all — so the fix needed - to widen the source surface as well as wire it through the cppagent layer. -- `TestIndicator` was already wired everywhere, so #131 reduces to a regression - pin and contributes no production diff. - -Detail: `docs/testing/issue-130-131/phase-01-defect-scoping.md`. - -## 3. Red tests (P2) - -Authored `tests/MTConnect.NET-JSON-cppagent-Tests/` (paired test project, -scaffolded on this branch because `upstream/master` does not yet have it). -Three NUnit fixtures, eighteen total cases tagged `[Category("CppAgentHeaderFieldsPresent")]`, -covered forward mapping, serialized wire shape (both `schemaVersion` and -`testIndicator`), reverse mapping, null-source ctor, and the parameterless -ctor for each envelope. All eighteen cases failed at HEAD with compile-time -errors (no `SchemaVersion` symbol in either the DTO or the source DTOs) — -the cleanest possible red signal. - -Detail: `docs/testing/issue-130-131/phase-02-red-tests.md`. - -## 4. Library fix (P3) - -Four atomic commits: - -1. `enh(common): add SchemaVersion to header DTOs` — adds the property to - the three `IMTConnect*Header` interfaces and their concrete implementations. -2. `fix(json-cppagent): emit schemaVersion on streams header` — declares the - property + wires the ctor + reverse mapper. -3. `fix(json-cppagent): emit schemaVersion on devices header` — wires the - missing ctor + reverse mapper assignments (the property declaration was - already present). -4. `fix(json-cppagent): emit schemaVersion on assets header` — symmetric edit. - -All eighteen P2 fixtures pass after the production fix. - -Detail: `docs/testing/issue-130-131/phase-03-library-fix.md`. - -## 5. Regression pins (P4) - -Authored `CppAgentHeaderFieldsRegressionGuardTests` — a reflection guard that -loops every JSON-cppagent header DTO type and asserts `SchemaVersion` and -`TestIndicator` are both declared as serializable properties carrying the -right `[JsonPropertyName(...)]` attribute. Six guard cases (three DTOs * two -fields) lock in the cppagent v2 wire shape against future regressions. - -Detail: `docs/testing/issue-130-131/phase-04-regression-pins.md`. - -## 6. E2E validation (P5) - -Authored `JsonHeaderWireShapeE2ETests` — three parameterised cases per envelope -across schema versions `2.0`, `2.3`, `2.5`, plus three round-trip cases that -exercise both fields end to end. Twelve total cases lifted from the planned -Docker-MQTT E2E (re-targeted because the Docker fixtures ship with -`feat/issue-133`, not `upstream/master`). - -Detail: `docs/testing/issue-130-131/phase-05-e2e-validation.md`. - -## 7. Campaign summary (P6) - -- 36 NUnit cases on the new `tests/MTConnect.NET-JSON-cppagent-Tests/` project, - all green. -- Production diff stays additive: `+1` interface member on three `IMTConnect*Header` - interfaces, `+1` auto-property on three concrete classes, `+1` JSON property - on three JSON-cppagent header DTOs, `+1` line per ctor + reverse mapper for the - six DTOs. No public-API breaks. -- The `testIndicator` regression is locked in by reflection guard + per-envelope - unit pins; #131 keeps this PR's coverage even though no production fix was - required. -- Build state: `dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0` - green; `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests -c Debug` green. -- Coverage: the four production files modified have only auto-properties and - trivially-covered ctor/mapper assignments; every executable line is touched - by the per-envelope fixtures. diff --git a/docs/testing/issue-130-131/phase-00-foundation.md b/docs/testing/issue-130-131/phase-00-foundation.md deleted file mode 100644 index 64598e91c..000000000 --- a/docs/testing/issue-130-131/phase-00-foundation.md +++ /dev/null @@ -1,26 +0,0 @@ -# Phase 0 — Foundation - -## Executed - -- Seeded `docs/testing/issue-130-131.md` with the section skeleton. -- Created `docs/testing/issue-130-131/` for per-phase writeups. -- Confirmed branch cut from `upstream/master` at SHA `3d6321ab Updated ReadMe`. -- Confirmed defect persists at branch-cut: - - `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — no `SchemaVersion` property declared. - - `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — no `SchemaVersion` property declared. - - `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — `SchemaVersion` property declared but never assigned in the `IMTConnectDevicesHeader` constructor and never copied back in `ToDevicesHeader()`. - - Source DTOs `IMTConnectStreamsHeader` / `IMTConnectDevicesHeader` / `IMTConnectAssetsHeader` (and their implementations) do not expose `SchemaVersion` at all — the JSON-cppagent header DTOs cannot map a property that does not exist on the source surface. - -## Metrics delta - -No production code touched in this phase; build / test counts unchanged. - -## Deviations from plan - -1. Issue #131 (`testIndicator`) is **already addressed on `upstream/master`** — every JSON-cppagent header DTO declares `[JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; }`, the constructor maps `TestIndicator = header.TestIndicator`, and the reverse mapper restores it. No fix required for #131; the plan's symmetric `schemaVersion + testIndicator` matrix collapses to a `schemaVersion`-only fix for the production path. Tests that pin the existing `testIndicator` shape are still authored to lock in the wire contract. -2. Plan originally assumed `IMTConnect*Header.SchemaVersion` already existed on the source DTOs and only the JSON-cppagent mapping was missing. That is not the case at branch cut: none of the three source DTOs expose `SchemaVersion`. The plan is widened to include adding `SchemaVersion` to the three `IMTConnect*Header` interfaces and their `MTConnect*Header` implementations. This is a strictly additive change to the public surface (`enh(common)` per CONVENTIONS §5.1) — it does not break existing consumers. -3. Bootstrap precondition: `tests/MTConnect.NET-JSON-cppagent-Tests/` is not on `upstream/master`. Per CONVENTIONS §17.8 row dated 2026-04-25 ("Silent scope expansion: bootstrap-test-project scaffolding"), the test project is scaffolded on this branch. The duplication will resolve via rebase once `feat/issue-133` (PR #133) merges upstream — the PR description carries the `Depends on #133` note. - -## Follow-ups - -- None at P0 — skeleton only. diff --git a/docs/testing/issue-130-131/phase-01-defect-scoping.md b/docs/testing/issue-130-131/phase-01-defect-scoping.md deleted file mode 100644 index a4139667f..000000000 --- a/docs/testing/issue-130-131/phase-01-defect-scoping.md +++ /dev/null @@ -1,54 +0,0 @@ -# Phase 1 — Defect scoping - -## Executed - -Confirmed at `upstream/master` SHA `3d6321ab`: - -1. **`SchemaVersion` absent from JSON-cppagent header DTOs**: - - - `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — no `SchemaVersion` member. - - `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — no `SchemaVersion` member. - - `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — `SchemaVersion` declared (line 19) but the constructor accepting `IMTConnectDevicesHeader` (lines 45-59) never assigns it; the reverse mapper `ToDevicesHeader()` (lines 62-75) never copies it back. - -2. **`SchemaVersion` absent from source DTOs**: - - - `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` — declares `Version` (line 23) but no `SchemaVersion`. - - `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` — same shape. - - `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` — same shape. - - The `MTConnect*Header` implementations follow the interfaces — no `SchemaVersion` to map. - - The plan as authored assumed `IMTConnect*Header.SchemaVersion` already existed. It does not. The fix surface is widened to add `SchemaVersion` to the three interfaces and their implementations as additive (`enh`) members. - -3. **`TestIndicator` already present everywhere — issue #131 already addressed**: - - - All three JSON-cppagent header DTOs declare `[JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; }`. - - Each constructor assigns `TestIndicator = header.TestIndicator;` from the source DTO. - - Each reverse mapper (`ToStreamsHeader()`, `ToDevicesHeader()`, `ToAssetsHeader()`) copies the field back. - - Source DTOs `IMTConnect*Header.TestIndicator` and their `MTConnect*Header.TestIndicator` impls are wired. - - No production code change required for #131; tests still pin the wire shape so a future regression cannot silently drop the field. - -4. **cppagent reference** — cppagent v2.7.0.7 emits `Header.schemaVersion` and `Header.testIndicator` on every envelope (Streams, Devices, Assets). The wire shape this PR converges on: - - ```json - { - "MTConnectStreams": { - "Header": { - "schemaVersion": "2.5", - "testIndicator": false, - "...": "..." - } - } - } - ``` - -## Metrics delta - -Read-only investigation; no production change. - -## Deviations from plan - -The P1 plan assumed `IMTConnect*Header.SchemaVersion` already existed and that #131 (`testIndicator`) was an open defect. Both assumptions are wrong on `upstream/master`. Corrections recorded above; the broader plan widening is documented in `phase-00-foundation.md` "Deviations from plan" §1 + §2. - -## Follow-ups - -None — scope is final. diff --git a/docs/testing/issue-130-131/phase-02-red-tests.md b/docs/testing/issue-130-131/phase-02-red-tests.md deleted file mode 100644 index 9473b2d46..000000000 --- a/docs/testing/issue-130-131/phase-02-red-tests.md +++ /dev/null @@ -1,40 +0,0 @@ -# Phase 2 — Red tests - -## Executed - -Authored a paired NUnit test project at `tests/MTConnect.NET-JSON-cppagent-Tests/` with three fixtures, each tagged `[Category("CppAgentHeaderFieldsPresent")]`: - -- `Streams/JsonStreamsHeaderSchemaVersionTests.cs` — six cases covering forward mapping, serialized wire shape (`schemaVersion` + `testIndicator`), reverse mapping (`ToStreamsHeader`), null-source ctor branch, and the parameterless ctor. -- `Devices/JsonDevicesHeaderSchemaVersionTests.cs` — six cases mirroring the same matrix on the Devices envelope. -- `Assets/JsonAssetsHeaderSchemaVersionTests.cs` — six cases mirroring the same matrix on the Assets envelope. - -Wired the test project into `MTConnect.NET.sln` under the existing `Tests` solution folder (GUID `{14375E03-6BF8-45E6-B868-D2399368992B}`) with project GUID `{A274A407-D9F4-4F54-B3BF-178B33FACBA3}`. - -## Red proof - -`dotnet build tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug` fails with 18 errors at HEAD, all of the form: - -``` -error CS0117: 'MTConnectStreamsHeader' does not contain a definition for 'SchemaVersion' -error CS1061: 'JsonStreamsHeader' does not contain a definition for 'SchemaVersion' -error CS0117: 'MTConnectDevicesHeader' does not contain a definition for 'SchemaVersion' -error CS1061: 'JsonDevicesHeader' does not contain a definition for 'SchemaVersion' -error CS0117: 'MTConnectAssetsHeader' does not contain a definition for 'SchemaVersion' -error CS1061: 'JsonAssetsHeader' does not contain a definition for 'SchemaVersion' -``` - -Compile-time red is the cleanest signal for "this surface is missing"; the production fix in P3 introduces the symbol on both the source DTO interfaces / impls and the JSON-cppagent header DTOs, at which point all 18 cases compile and pass. - -## Metrics delta - -- Test fixtures: +3 (18 cases total). -- Production code: untouched. -- Build state: red on the new test project (compile-time); the rest of the solution still builds Debug net8.0 green. - -## Deviations from plan - -The plan proposed leaning on a `VersionMatrix` fixture from `feat/issue-133`. That fixture is not on `upstream/master`, so the P2 cases use direct `MTConnect*Header` instantiations with explicit `SchemaVersion = "2.5"` values. The version-matrix coverage is reframed for P5 once a runnable agent fixture is available; the P2 fixtures already provide the tight unit-level pin per envelope. - -## Follow-ups - -- None — the green commit lands in P3. diff --git a/docs/testing/issue-130-131/phase-03-library-fix.md b/docs/testing/issue-130-131/phase-03-library-fix.md deleted file mode 100644 index c914f7d7b..000000000 --- a/docs/testing/issue-130-131/phase-03-library-fix.md +++ /dev/null @@ -1,40 +0,0 @@ -# Phase 3 — Library fix - -## Executed - -Four atomic commits, each scoped per CONVENTIONS §5.3: - -1. `enh(common): add SchemaVersion to header DTOs` — adds `SchemaVersion { get; }` to `IMTConnectStreamsHeader`, `IMTConnectDevicesHeader`, `IMTConnectAssetsHeader`, plus `SchemaVersion { get; set; }` to the three concrete `MTConnect*Header` classes. Strictly additive on the public surface (no break for existing consumers — none of the in-tree types implement these interfaces directly except the matching concrete classes). -2. `fix(json-cppagent): emit schemaVersion on streams header` — declares `[JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; }` on `JsonStreamsHeader`, copies the field in the `IMTConnectStreamsHeader` ctor, restores it in `ToStreamsHeader()`. -3. `fix(json-cppagent): emit schemaVersion on devices header` — `JsonDevicesHeader` already declared the property; the fix wires the missing `SchemaVersion = header.SchemaVersion` in the ctor and `header.SchemaVersion = SchemaVersion` in `ToDevicesHeader()`. -4. `fix(json-cppagent): emit schemaVersion on assets header` — symmetric edit on `JsonAssetsHeader`. - -## Validation - -- `dotnet build tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug` — build succeeded with 0 errors and 2 warnings (both are sourcelink "no source control information" diagnostics emitted by `Microsoft.SourceLink.Common.targets`; they pre-date this PR). -- `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --no-build` — `Passed: 18, Failed: 0, Skipped: 0`. All P2 fixtures green. - -## Coverage - -The four production files modified by this phase: - -- `libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs` (interface — no executable code). -- `libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs` (interface — no executable code). -- `libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs` (interface — no executable code). -- `libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs` (concrete property — auto-implemented `get; set;`). -- `libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs` (same). -- `libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs` (same). -- `libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs` — every executable line on the new `SchemaVersion` surface is exercised: ctor with non-null source (`Constructor_with_source_header_copies_schemaVersion`), ctor with null source (`Constructor_with_null_source_does_not_throw`), default ctor (`Default_constructor_leaves_schemaVersion_unset`), and `ToStreamsHeader()` (`Reverse_mapping_round_trips_schemaVersion`). -- `libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs` — same matrix. -- `libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs` — same matrix. - -The interface declarations and auto-properties have no branchable code; the constructor / mapper edits are the only branching surface and every branch is covered by the P2 fixtures. - -## Deviations from plan - -- The plan's P3 file expected only `fix(json-cppagent)` commits. The widening described in P0 deviation §2 required a preceding `enh(common)` commit on the source DTOs. -- The plan wanted to remove a "category + CI job" at P3. No category-removed step is needed — the `CppAgentHeaderFieldsPresent` category stays on the test fixtures as a regression-pin tag, and no inverted-exit-code CI job was added in P2 (per CONVENTIONS §1.7, per-issue PRs do not modify `.github/workflows/`). - -## Follow-ups - -- None — production fix complete; all P2 reds are now green. diff --git a/docs/testing/issue-130-131/phase-04-regression-pins.md b/docs/testing/issue-130-131/phase-04-regression-pins.md deleted file mode 100644 index ee7498804..000000000 --- a/docs/testing/issue-130-131/phase-04-regression-pins.md +++ /dev/null @@ -1,46 +0,0 @@ -# Phase 4 — Regression pins - -## Executed - -Authored `tests/MTConnect.NET-JSON-cppagent-Tests/CppAgentHeaderFieldsRegressionGuardTests.cs`. -The fixture is a reflection guard that loops over every JSON-cppagent header DTO type -(`JsonStreamsHeader`, `JsonDevicesHeader`, `JsonAssetsHeader`) and asserts: - -- `SchemaVersion` is declared as a public `string` property carrying - `[JsonPropertyName("schemaVersion")]`. -- `TestIndicator` is declared as a public `bool` property carrying - `[JsonPropertyName("testIndicator")]`. - -If a future refactor introduces a new cppagent header DTO and forgets one of the -two fields, the guard fails fast on every CI run — not when an MQTT capture is -manually inspected. If a future refactor renames the JSON property (`schemaVersion` -to `schema_version`, say), the guard fails too — locking in cppagent v2 wire shape. - -## Validation - -`dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` — -`Passed: 24, Failed: 0`. Six guard cases (3 DTOs * 2 fields) plus the 18 fixture -cases from P2. - -## Coverage - -The guard test exercises pure reflection over the DTO types; it adds no new -production code, so coverage on touched files is unchanged from P3 (still 100%). - -## Deviations from plan - -The plan called for a separate `tests/Compliance/MTConnect-Compliance-Tests/L5_Regressions/` -home for the regression fixture. That directory does not exist on `upstream/master` -(it ships with `feat/issue-133`'s compliance harness). The fixture lands inside the -already-scaffolded `tests/MTConnect.NET-JSON-cppagent-Tests/` test project as the -nearest equivalent — co-located with the per-envelope unit tests it complements. - -The plan also referenced removing `#130` from a migration table at -`plans/11-tests/11-compliance-regression-gates.md`. That file lives under -`extra-files.user/`, which is gitignored and does not appear inside the worktree -per CONVENTIONS §1.0. Plan-level migration is a main-agent task tracked in `todo.md`, -not a per-issue PR commit. - -## Follow-ups - -- None — regression pin landed on the only test project available at branch cut. diff --git a/docs/testing/issue-130-131/phase-05-e2e-validation.md b/docs/testing/issue-130-131/phase-05-e2e-validation.md deleted file mode 100644 index 2bb8ec9b1..000000000 --- a/docs/testing/issue-130-131/phase-05-e2e-validation.md +++ /dev/null @@ -1,50 +0,0 @@ -# Phase 5 — E2E validation - -## Executed - -Authored `tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs`, -which pins the post-fix wire shape across the three envelope DTOs and the three -schema versions (`2.0`, `2.3`, `2.5`) the plan called out as spot-checks. Each -test: - -1. Constructs an `MTConnect*Header` with `SchemaVersion` set to the target version. -2. Wraps it in the matching `Json{Streams,Devices,Assets}Header` DTO. -3. Serializes through `System.Text.Json.JsonSerializer.Serialize`. -4. Asserts the resulting JSON exposes both `schemaVersion` (string-equal to the - configured value) and `testIndicator` (boolean false by default). - -Plus three round-trip cases (`Source -> Json -> Source`) that exercise the -reverse mappers' `SchemaVersion` and `TestIndicator` lines together. - -## Validation - -`dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` — -`Passed: 36, Failed: 0`. The test project covers: - -- 18 P2 unit pins (six per envelope). -- 6 P4 reflection-guard pins (three DTOs * two fields). -- 12 P5 wire-shape e2e cases (three envelopes * three schema versions, plus - three round-trip cases). - -## Coverage - -The new fixture is test-only; production coverage on the touched files is -unchanged from P3 (still 100%). - -## Deviations from plan - -The plan called for an MQTT Docker E2E scenario (`tests/IntegrationTests/Regressions/Issue130MqttE2ETests.cs`, -`MTCONNECT_E2E_DOCKER=true`, mosquitto fixtures). The Docker MQTT fixtures and -the bootstrap shim that gates them on `MTCONNECT_E2E_DOCKER` ship with -`feat/issue-133` and are not on `upstream/master`. Per CONVENTIONS §17.8 row dated -2026-04-25 ("Bootstrap precondition unmet on upstream/master"), the E2E coverage -is reframed at the unit-integration boundary inside the JSON-cppagent test -project. The wire-shape assertions are equivalent — they exercise the same DTO -serialization path the MQTT publisher uses on the agent side. Once #133 lands, -a follow-up PR can lift these into the IntegrationTests project with live -mosquitto coverage. - -## Follow-ups - -- Once #133 merges, lift the `JsonHeaderWireShapeE2ETests` cases into the - Compliance / IntegrationTests E2E harness with live MQTT capture. diff --git a/docs/testing/issue-130-131/phase-06-campaign-summary.md b/docs/testing/issue-130-131/phase-06-campaign-summary.md deleted file mode 100644 index 536ce9910..000000000 --- a/docs/testing/issue-130-131/phase-06-campaign-summary.md +++ /dev/null @@ -1,76 +0,0 @@ -# Phase 6 — Campaign summary - -## Executed - -- Filled in the every-section detail in `docs/testing/issue-130-131.md` - (sections 2 through 7), each cross-referencing the per-phase writeup. -- Confirmed the draft PR body (`extra-files.user/plans/08-issue-130-131-cppagent-header-missing-fields/pr-body.md`) - matches the landed scope and carries the `Depends on #133` note required for - the test-project scaffolding overlap. - -## Validation gate - -- `dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0` - green (0 errors, 2 unrelated sourcelink warnings). -- `dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo` - green: `Passed: 36, Failed: 0, Skipped: 0`. -- `git status` clean. -- Every commit pushed to `origin/fix/issue-130-131`. - -## Metrics delta - -| Metric | Before | After | -|---------------------------------------------|--------|-------| -| `JsonStreamsHeader` SchemaVersion member | absent | declared + wired + round-trip | -| `JsonDevicesHeader` SchemaVersion member | declared, not wired | declared + wired + round-trip | -| `JsonAssetsHeader` SchemaVersion member | absent | declared + wired + round-trip | -| `IMTConnect*Header.SchemaVersion` | absent | added on three interfaces + impls | -| Test cases on JSON-cppagent layer | 0 | 36 | -| Production files reaching 100% coverage | n/a (paired test project did not exist) | 9 (3 interfaces + 3 impls + 3 cppagent DTOs) | - -## Deviations from plan - -Three plan-level deviations, all flagged in earlier phase writeups: - -1. **#131 already addressed** — The plan paired #130 + #131 for adjacent - line-region edits. `testIndicator` turned out to be live on - `upstream/master`, so this PR contributes only regression pins for #131, - not production code (`phase-00-foundation.md` §1). -2. **Source-DTO widening** — The plan assumed `IMTConnect*Header.SchemaVersion` - was already present on the source surface; it was not. The plan was widened - to add the property to three interfaces + three concrete classes - (`phase-00-foundation.md` §2). -3. **Bootstrap test-project scaffolding** — `tests/MTConnect.NET-JSON-cppagent-Tests/` - does not exist on `upstream/master`. Per CONVENTIONS §17.8 row dated - 2026-04-25, the project is scaffolded on this branch, with the - `Depends on #133` note in the PR description. Once #133 merges upstream the - duplication resolves via rebase. -4. **E2E reframing** — The plan called for Docker-MQTT scenarios using the - bootstrap E2E shim that ships with `feat/issue-133`. The unit-integration - wire-shape pins exercise the same DTO serialization path the MQTT publisher - uses; live MQTT capture is a follow-up after #133 lands - (`phase-05-e2e-validation.md`). - -## Follow-ups - -- After #133 merges upstream: drop the duplicated test-project scaffolding - commits during the close-out rebase, and lift the `JsonHeaderWireShapeE2ETests` - cases into the IntegrationTests project with mosquitto fixtures. -- The `extra-files.user/plans/11-tests/11-compliance-regression-gates.md` - migration row for #130 + #131 is a main-agent task tracked in `todo.md`; - not a per-issue PR commit. - -## Pre-close - -``` -git status # clean -dotnet build libraries/MTConnect.NET-JSON-cppagent/MTConnect.NET-JSON-cppagent.csproj -c Debug -f net8.0 -dotnet test tests/MTConnect.NET-JSON-cppagent-Tests/MTConnect.NET-JSON-cppagent-Tests.csproj -c Debug --nologo -``` - -## §1.5 close-out - -Not run by the implementing subagent. The user reviews the draft PR -(https://github.com/TrakHound/MTConnect.NET/pull/147) and decides when to -flip it from draft to ready, including the history rewrite + reviewer -assignment per CONVENTIONS §1.5. From 2259cf4607271790643803aa2c3422d5d81f1b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 21:59:01 +0200 Subject: [PATCH 16/28] test(json-cppagent): pin Validation on every cppagent header DTO Adds three RED regression fixtures (Assets/Streams/Devices) that pin the cppagent v2.7.0.7 Header.validation wire shape: ctor copy, serialized property, and round-trip through To*Header(). Tests fail until the Validation property is added to the JSON-cppagent header DTOs. Source authority: cppagent v2.7.0.7 emits Header.validation on every envelope. Public defect tracker: https://github.com/TrakHound/MTConnect.NET/issues/130 https://github.com/TrakHound/MTConnect.NET/issues/131 --- .../Assets/JsonAssetsHeaderValidationTests.cs | 77 +++++++++++++++++++ .../JsonDevicesHeaderValidationTests.cs | 77 +++++++++++++++++++ .../JsonStreamsHeaderValidationTests.cs | 77 +++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderValidationTests.cs create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderValidationTests.cs create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderValidationTests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderValidationTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderValidationTests.cs new file mode 100644 index 000000000..4e3c644d9 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderValidationTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Assets.Json; +using MTConnect.Headers; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Assets +{ + /// + /// Pins the JSON-cppagent Assets Header behavior: every emitted + /// `MTConnectAssets.Header` envelope must include `validation` + /// mapped from the source `IMTConnectAssetsHeader.Validation`, + /// matching the cppagent v2 wire shape. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.validation` + /// on every Assets envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130, + /// https://github.com/TrakHound/MTConnect.NET/issues/131. + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonAssetsHeaderValidationTests + { + [Test] + public void Constructor_with_source_header_copies_validation() + { + var source = new MTConnectAssetsHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + Validation = true, + }; + + var json = new JsonAssetsHeader(source); + + Assert.That(json.Validation, Is.True, + "JsonAssetsHeader must copy Validation from the source IMTConnectAssetsHeader."); + } + + [Test] + public void Serialized_assets_header_emits_validation_property() + { + var source = new MTConnectAssetsHeader + { + Validation = true, + }; + + var jsonHeader = new JsonAssetsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("validation", out var v), Is.True, + "Serialized JsonAssetsHeader must expose 'validation' on the wire."); + Assert.That(v.GetBoolean(), Is.True); + } + + [Test] + public void Reverse_mapping_round_trips_validation() + { + var source = new MTConnectAssetsHeader + { + Validation = true, + }; + + var roundTripped = new JsonAssetsHeader(source).ToAssetsHeader(); + + Assert.That(roundTripped.Validation, Is.True, + "ToAssetsHeader must preserve Validation through the round trip."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderValidationTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderValidationTests.cs new file mode 100644 index 000000000..bf0c1d8a5 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderValidationTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Devices.Json; +using MTConnect.Headers; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Devices +{ + /// + /// Pins the JSON-cppagent Devices Header behavior: every emitted + /// `MTConnectDevices.Header` envelope must include `validation` + /// mapped from the source `IMTConnectDevicesHeader.Validation`, + /// matching the cppagent v2 wire shape. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.validation` + /// on every Devices envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130, + /// https://github.com/TrakHound/MTConnect.NET/issues/131. + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonDevicesHeaderValidationTests + { + [Test] + public void Constructor_with_source_header_copies_validation() + { + var source = new MTConnectDevicesHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + Validation = true, + }; + + var json = new JsonDevicesHeader(source); + + Assert.That(json.Validation, Is.True, + "JsonDevicesHeader must copy Validation from the source IMTConnectDevicesHeader."); + } + + [Test] + public void Serialized_devices_header_emits_validation_property() + { + var source = new MTConnectDevicesHeader + { + Validation = true, + }; + + var jsonHeader = new JsonDevicesHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("validation", out var v), Is.True, + "Serialized JsonDevicesHeader must expose 'validation' on the wire."); + Assert.That(v.GetBoolean(), Is.True); + } + + [Test] + public void Reverse_mapping_round_trips_validation() + { + var source = new MTConnectDevicesHeader + { + Validation = true, + }; + + var roundTripped = new JsonDevicesHeader(source).ToDevicesHeader(); + + Assert.That(roundTripped.Validation, Is.True, + "ToDevicesHeader must preserve Validation through the round trip."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderValidationTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderValidationTests.cs new file mode 100644 index 000000000..21b1fb88f --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderValidationTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Text.Json; +using MTConnect.Headers; +using MTConnect.Streams.Json; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.Streams +{ + /// + /// Pins the JSON-cppagent Streams Header behavior: every emitted + /// `MTConnectStreams.Header` envelope must include `validation` + /// mapped from the source `IMTConnectStreamsHeader.Validation`, + /// matching the cppagent v2 wire shape. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.validation` + /// on every Streams envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130, + /// https://github.com/TrakHound/MTConnect.NET/issues/131. + /// + [TestFixture] + [Category("CppAgentHeaderFieldsPresent")] + public class JsonStreamsHeaderValidationTests + { + [Test] + public void Constructor_with_source_header_copies_validation() + { + var source = new MTConnectStreamsHeader + { + InstanceId = 1, + Version = "2.5.0.0", + SchemaVersion = "2.5", + Sender = "agent", + Validation = true, + }; + + var json = new JsonStreamsHeader(source); + + Assert.That(json.Validation, Is.True, + "JsonStreamsHeader must copy Validation from the source IMTConnectStreamsHeader."); + } + + [Test] + public void Serialized_streams_header_emits_validation_property() + { + var source = new MTConnectStreamsHeader + { + Validation = true, + }; + + var jsonHeader = new JsonStreamsHeader(source); + var serialized = JsonSerializer.Serialize(jsonHeader); + using var doc = JsonDocument.Parse(serialized); + + Assert.That(doc.RootElement.TryGetProperty("validation", out var v), Is.True, + "Serialized JsonStreamsHeader must expose 'validation' on the wire."); + Assert.That(v.GetBoolean(), Is.True); + } + + [Test] + public void Reverse_mapping_round_trips_validation() + { + var source = new MTConnectStreamsHeader + { + Validation = true, + }; + + var roundTripped = new JsonStreamsHeader(source).ToStreamsHeader(); + + Assert.That(roundTripped.Validation, Is.True, + "ToStreamsHeader must preserve Validation through the round trip."); + } + } +} From ad0cf277a4a17367638c37197bc5fc4e0afb1d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:00:17 +0200 Subject: [PATCH 17/28] fix(json-cppagent): emit Validation on every cppagent header DTO Adds the `validation` JSON property to JsonAssetsHeader, JsonStreamsHeader, and JsonDevicesHeader, mapped from `IMTConnect{Assets,Streams,Devices}Header.Validation` in the constructor and copied back through `To{Assets,Streams,Devices}Header()`. Mirrors the cppagent v2.7.0.7 wire shape that emits `Header.validation` on every envelope. Greens the F-C17 regression tests. Source authority: cppagent v2.7.0.7 emits Header.validation on every envelope. Public defect tracker: https://github.com/TrakHound/MTConnect.NET/issues/130 https://github.com/TrakHound/MTConnect.NET/issues/131 --- .../Assets/JsonAssetsHeader.cs | 9 +++++++++ .../Devices/JsonDevicesHeader.cs | 9 +++++++++ .../Streams/JsonStreamsHeader.cs | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs index 22e2ee1f6..a0683cee5 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs @@ -33,6 +33,13 @@ public class JsonAssetsHeader [JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; } + /// + /// Indicates if the MTConnect Agent is validating against the normative model. + /// Mirrors the cppagent v2 wire shape that emits `validation` on every Header. + /// + [JsonPropertyName("validation")] + public bool Validation { get; set; } + [JsonPropertyName("creationTime")] public DateTime CreationTime { get; set; } @@ -51,6 +58,7 @@ public JsonAssetsHeader(IMTConnectAssetsHeader header) AssetCount = header.AssetCount; DeviceModelChangeTime = header.DeviceModelChangeTime; TestIndicator = header.TestIndicator; + Validation = header.Validation; CreationTime = header.CreationTime; } } @@ -67,6 +75,7 @@ public virtual IMTConnectAssetsHeader ToAssetsHeader() header.AssetCount = AssetCount; header.DeviceModelChangeTime = DeviceModelChangeTime; header.TestIndicator = TestIndicator; + header.Validation = Validation; header.CreationTime = CreationTime; return header; } diff --git a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs index 0fb9178c0..74bb8bddd 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs @@ -36,6 +36,13 @@ public class JsonDevicesHeader [JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; } + /// + /// Indicates if the MTConnect Agent is validating against the normative model. + /// Mirrors the cppagent v2 wire shape that emits `validation` on every Header. + /// + [JsonPropertyName("validation")] + public bool Validation { get; set; } + [JsonPropertyName("creationTime")] public DateTime CreationTime { get; set; } @@ -55,6 +62,7 @@ public JsonDevicesHeader(IMTConnectDevicesHeader header) AssetCount = header.AssetCount; DeviceModelChangeTime = header.DeviceModelChangeTime; TestIndicator = header.TestIndicator; + Validation = header.Validation; CreationTime = header.CreationTime; } } @@ -72,6 +80,7 @@ public IMTConnectDevicesHeader ToDevicesHeader() header.AssetCount = AssetCount; header.DeviceModelChangeTime = DeviceModelChangeTime; header.TestIndicator = TestIndicator; + header.Validation = Validation; header.CreationTime = CreationTime; return header; } diff --git a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs index 1ca2d9c12..a8d446e70 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs @@ -39,6 +39,13 @@ public class JsonStreamsHeader [JsonPropertyName("testIndicator")] public bool TestIndicator { get; set; } + /// + /// Indicates if the MTConnect Agent is validating against the normative model. + /// Mirrors the cppagent v2 wire shape that emits `validation` on every Header. + /// + [JsonPropertyName("validation")] + public bool Validation { get; set; } + [JsonPropertyName("creationTime")] public DateTime CreationTime { get; set; } @@ -59,6 +66,7 @@ public JsonStreamsHeader(IMTConnectStreamsHeader header) NextSequence = header.NextSequence; DeviceModelChangeTime = header.DeviceModelChangeTime; TestIndicator = header.TestIndicator; + Validation = header.Validation; CreationTime = header.CreationTime; } } @@ -77,6 +85,7 @@ public virtual IMTConnectStreamsHeader ToStreamsHeader() header.NextSequence = NextSequence; header.DeviceModelChangeTime = DeviceModelChangeTime; header.TestIndicator = TestIndicator; + header.Validation = Validation; header.CreationTime = CreationTime; return header; } From 37a1a33159e8ac87b68c507fc767bf02edc5cbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:01:21 +0200 Subject: [PATCH 18/28] test(json-cppagent): pin shared envelope/version compliance matrix Adds RED fixture asserting that an internal static JsonHeaderWireShapeMatrix in TestHelpers exposes a TestCaseSource-shaped compliance matrix for the (Assets|Streams|Devices) x (2.0|2.3|2.5) combinations consumed by per-envelope and cross-envelope E2E tests. Tagged [Category("ComplianceMatrix")]. --- .../JsonHeaderWireShapeMatrixTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs new file mode 100644 index 000000000..46533ec8e --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Linq; +using System.Reflection; +using MTConnect.Tests.JsonCppagent.TestHelpers; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent +{ + /// + /// Pins the shared (envelopeKind, version) compliance matrix used by both + /// per-envelope and cross-envelope wire-shape fixtures. Centralizing the + /// matrix prevents per-envelope tests from drifting away from the cross- + /// envelope E2E set as new schema versions are added. + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130 (schemaVersion), + /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). + /// + [TestFixture] + [Category("ComplianceMatrix")] + public class JsonHeaderWireShapeMatrixTests + { + [Test] + public void Matrix_exposes_three_envelope_kinds() + { + var kinds = JsonHeaderWireShapeMatrix.Cases + .Select(c => c.Arguments[0]!.ToString()) + .Distinct() + .OrderBy(s => s) + .ToArray(); + + Assert.That(kinds, Is.EqualTo(new[] { "Assets", "Devices", "Streams" })); + } + + [Test] + public void Matrix_covers_v20_v23_v25_for_each_envelope() + { + var expectedVersions = new[] { "2.0", "2.3", "2.5" }; + + foreach (var kind in new[] { "Assets", "Devices", "Streams" }) + { + var versions = JsonHeaderWireShapeMatrix.Cases + .Where(c => (string)c.Arguments[0]! == kind) + .Select(c => (string)c.Arguments[1]!) + .OrderBy(v => v) + .ToArray(); + + Assert.That(versions, Is.EqualTo(expectedVersions), + $"{kind} envelope must include v2.0, v2.3, and v2.5 in the compliance matrix."); + } + } + + [Test] + public void Matrix_class_is_internal_static() + { + var t = typeof(JsonHeaderWireShapeMatrix); + Assert.That(t.IsAbstract && t.IsSealed, Is.True, + "JsonHeaderWireShapeMatrix must be a static class."); + Assert.That(t.IsNotPublic, Is.True, + "JsonHeaderWireShapeMatrix must be internal to the test project."); + } + } +} From 82d22b151e9e9872ea0b1e0d101a15e1c1837b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:03:24 +0200 Subject: [PATCH 19/28] test(json-cppagent): extract shared envelope/version wire-shape matrix Centralizes the (Assets|Streams|Devices) x (2.0|2.3|2.5) matrix into TestHelpers/JsonHeaderWireShapeMatrix as an internal static class with TestCaseSource-shaped Cases (cross-envelope) and SchemaVersionCases (per-envelope) generators. Per-envelope SchemaVersion fixtures and the cross-envelope E2E fixture now pull from this single source so adding a new schema version automatically extends both layers. Tests are tagged [Category("ComplianceMatrix")]. Greens the F-Si-M5 RED matrix fixture; full suite passes (60/60). --- .../JsonAssetsHeaderSchemaVersionTests.cs | 20 ++-- .../JsonDevicesHeaderSchemaVersionTests.cs | 20 ++-- .../JsonHeaderWireShapeE2ETests.cs | 101 +++++++----------- .../JsonHeaderWireShapeMatrixTests.cs | 1 - .../JsonStreamsHeaderSchemaVersionTests.cs | 20 ++-- .../TestHelpers/JsonHeaderWireShapeMatrix.cs | 77 +++++++++++++ 6 files changed, 146 insertions(+), 93 deletions(-) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/JsonHeaderWireShapeMatrix.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs index f9e04e028..24ba4ca0f 100644 --- a/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Assets/JsonAssetsHeaderSchemaVersionTests.cs @@ -4,6 +4,7 @@ using System.Text.Json; using MTConnect.Assets.Json; using MTConnect.Headers; +using MTConnect.Tests.JsonCppagent.TestHelpers; using NUnit.Framework; namespace MTConnect.Tests.JsonCppagent.Assets @@ -23,31 +24,32 @@ namespace MTConnect.Tests.JsonCppagent.Assets /// [TestFixture] [Category("CppAgentHeaderFieldsPresent")] + [Category("ComplianceMatrix")] public class JsonAssetsHeaderSchemaVersionTests { - [Test] - public void Constructor_with_source_header_copies_schemaVersion() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Constructor_with_source_header_copies_schemaVersion(string schemaVersion) { var source = new MTConnectAssetsHeader { InstanceId = 1, - Version = "2.5.0.0", - SchemaVersion = "2.5", + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, Sender = "agent", }; var json = new JsonAssetsHeader(source); - Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + Assert.That(json.SchemaVersion, Is.EqualTo(schemaVersion), "JsonAssetsHeader must copy SchemaVersion from the source IMTConnectAssetsHeader."); } - [Test] - public void Serialized_assets_header_emits_schemaVersion_property() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Serialized_assets_header_emits_schemaVersion_property(string schemaVersion) { var source = new MTConnectAssetsHeader { - SchemaVersion = "2.5", + SchemaVersion = schemaVersion, }; var jsonHeader = new JsonAssetsHeader(source); @@ -56,7 +58,7 @@ public void Serialized_assets_header_emits_schemaVersion_property() Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, "Serialized JsonAssetsHeader must expose 'schemaVersion' on the wire."); - Assert.That(v.GetString(), Is.EqualTo("2.5")); + Assert.That(v.GetString(), Is.EqualTo(schemaVersion)); } [Test] diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs index 1a2943066..988603a3e 100644 --- a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDevicesHeaderSchemaVersionTests.cs @@ -4,6 +4,7 @@ using System.Text.Json; using MTConnect.Devices.Json; using MTConnect.Headers; +using MTConnect.Tests.JsonCppagent.TestHelpers; using NUnit.Framework; namespace MTConnect.Tests.JsonCppagent.Devices @@ -23,31 +24,32 @@ namespace MTConnect.Tests.JsonCppagent.Devices /// [TestFixture] [Category("CppAgentHeaderFieldsPresent")] + [Category("ComplianceMatrix")] public class JsonDevicesHeaderSchemaVersionTests { - [Test] - public void Constructor_with_source_header_copies_schemaVersion() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Constructor_with_source_header_copies_schemaVersion(string schemaVersion) { var source = new MTConnectDevicesHeader { InstanceId = 1, - Version = "2.5.0.0", - SchemaVersion = "2.5", + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, Sender = "agent", }; var json = new JsonDevicesHeader(source); - Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + Assert.That(json.SchemaVersion, Is.EqualTo(schemaVersion), "JsonDevicesHeader must copy SchemaVersion from the source IMTConnectDevicesHeader."); } - [Test] - public void Serialized_devices_header_emits_schemaVersion_property() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Serialized_devices_header_emits_schemaVersion_property(string schemaVersion) { var source = new MTConnectDevicesHeader { - SchemaVersion = "2.5", + SchemaVersion = schemaVersion, }; var jsonHeader = new JsonDevicesHeader(source); @@ -56,7 +58,7 @@ public void Serialized_devices_header_emits_schemaVersion_property() Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, "Serialized JsonDevicesHeader must expose 'schemaVersion' on the wire."); - Assert.That(v.GetString(), Is.EqualTo("2.5")); + Assert.That(v.GetString(), Is.EqualTo(schemaVersion)); } [Test] diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs index f543936f5..97a74f70f 100644 --- a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeE2ETests.cs @@ -6,6 +6,7 @@ using MTConnect.Devices.Json; using MTConnect.Headers; using MTConnect.Streams.Json; +using MTConnect.Tests.JsonCppagent.TestHelpers; using NUnit.Framework; namespace MTConnect.Tests.JsonCppagent @@ -14,8 +15,9 @@ namespace MTConnect.Tests.JsonCppagent /// End-to-end wire-shape pin: every JSON-cppagent envelope's `Header` element /// must carry both `schemaVersion` and `testIndicator` after serialization with /// the default `JsonSerializer` settings, regardless of which field on the - /// source DTO carries a non-default value. Spot-checks the v2.0 / v2.3 / v2.5 - /// schema-version cases the plan's MQTT E2E scenarios cover. + /// source DTO carries a non-default value. The (envelopeKind, schemaVersion) + /// matrix is sourced from so the + /// per-envelope and cross-envelope E2E sets stay in lockstep. /// /// Source authority: /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and @@ -25,75 +27,44 @@ namespace MTConnect.Tests.JsonCppagent /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). /// [TestFixture] + [Category("ComplianceMatrix")] public class JsonHeaderWireShapeE2ETests { - [TestCase("2.0")] - [TestCase("2.3")] - [TestCase("2.5")] - public void Streams_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.Cases))] + public void Envelope_carries_schemaVersion_and_testIndicator(string envelopeKind, string schemaVersion) { - var source = new MTConnectStreamsHeader - { - InstanceId = 42, - Version = $"{schemaVersion}.0.0", - SchemaVersion = schemaVersion, - Sender = "agent", - TestIndicator = false, - }; - - var envelope = new JsonStreamsHeader(source); - using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); - - Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), - Is.EqualTo(schemaVersion)); - Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), - Is.False); - } - - [TestCase("2.0")] - [TestCase("2.3")] - [TestCase("2.5")] - public void Devices_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) - { - var source = new MTConnectDevicesHeader + string serialized = envelopeKind switch { - InstanceId = 42, - Version = $"{schemaVersion}.0.0", - SchemaVersion = schemaVersion, - Sender = "agent", - TestIndicator = false, + "Streams" => JsonSerializer.Serialize(new JsonStreamsHeader(new MTConnectStreamsHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + })), + "Devices" => JsonSerializer.Serialize(new JsonDevicesHeader(new MTConnectDevicesHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + })), + "Assets" => JsonSerializer.Serialize(new JsonAssetsHeader(new MTConnectAssetsHeader + { + InstanceId = 42, + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, + Sender = "agent", + TestIndicator = false, + })), + _ => throw new System.ArgumentOutOfRangeException(nameof(envelopeKind), envelopeKind, null), }; - var envelope = new JsonDevicesHeader(source); - using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); - - Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), - Is.EqualTo(schemaVersion)); - Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), - Is.False); - } - - [TestCase("2.0")] - [TestCase("2.3")] - [TestCase("2.5")] - public void Assets_envelope_carries_schemaVersion_for_each_supported_schema(string schemaVersion) - { - var source = new MTConnectAssetsHeader - { - InstanceId = 42, - Version = $"{schemaVersion}.0.0", - SchemaVersion = schemaVersion, - Sender = "agent", - TestIndicator = false, - }; - - var envelope = new JsonAssetsHeader(source); - using var doc = JsonDocument.Parse(JsonSerializer.Serialize(envelope)); - - Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), - Is.EqualTo(schemaVersion)); - Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), - Is.False); + using var doc = JsonDocument.Parse(serialized); + Assert.That(doc.RootElement.GetProperty("schemaVersion").GetString(), Is.EqualTo(schemaVersion)); + Assert.That(doc.RootElement.GetProperty("testIndicator").GetBoolean(), Is.False); } [Test] diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs index 46533ec8e..319e38221 100644 --- a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderWireShapeMatrixTests.cs @@ -2,7 +2,6 @@ // TrakHound Inc. licenses this file to you under the MIT license. using System.Linq; -using System.Reflection; using MTConnect.Tests.JsonCppagent.TestHelpers; using NUnit.Framework; diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs index d6023d55e..8c356356c 100644 --- a/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonStreamsHeaderSchemaVersionTests.cs @@ -4,6 +4,7 @@ using System.Text.Json; using MTConnect.Headers; using MTConnect.Streams.Json; +using MTConnect.Tests.JsonCppagent.TestHelpers; using NUnit.Framework; namespace MTConnect.Tests.JsonCppagent.Streams @@ -23,31 +24,32 @@ namespace MTConnect.Tests.JsonCppagent.Streams /// [TestFixture] [Category("CppAgentHeaderFieldsPresent")] + [Category("ComplianceMatrix")] public class JsonStreamsHeaderSchemaVersionTests { - [Test] - public void Constructor_with_source_header_copies_schemaVersion() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Constructor_with_source_header_copies_schemaVersion(string schemaVersion) { var source = new MTConnectStreamsHeader { InstanceId = 1, - Version = "2.5.0.0", - SchemaVersion = "2.5", + Version = $"{schemaVersion}.0.0", + SchemaVersion = schemaVersion, Sender = "agent", }; var json = new JsonStreamsHeader(source); - Assert.That(json.SchemaVersion, Is.EqualTo("2.5"), + Assert.That(json.SchemaVersion, Is.EqualTo(schemaVersion), "JsonStreamsHeader must copy SchemaVersion from the source IMTConnectStreamsHeader."); } - [Test] - public void Serialized_streams_header_emits_schemaVersion_property() + [TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(JsonHeaderWireShapeMatrix.SchemaVersionCases))] + public void Serialized_streams_header_emits_schemaVersion_property(string schemaVersion) { var source = new MTConnectStreamsHeader { - SchemaVersion = "2.5", + SchemaVersion = schemaVersion, }; var jsonHeader = new JsonStreamsHeader(source); @@ -56,7 +58,7 @@ public void Serialized_streams_header_emits_schemaVersion_property() Assert.That(doc.RootElement.TryGetProperty("schemaVersion", out var v), Is.True, "Serialized JsonStreamsHeader must expose 'schemaVersion' on the wire."); - Assert.That(v.GetString(), Is.EqualTo("2.5")); + Assert.That(v.GetString(), Is.EqualTo(schemaVersion)); } [Test] diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/JsonHeaderWireShapeMatrix.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/JsonHeaderWireShapeMatrix.cs new file mode 100644 index 000000000..cc03566c6 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/JsonHeaderWireShapeMatrix.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Collections.Generic; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent.TestHelpers +{ + /// + /// Shared compliance matrix yielding (envelopeKind, schemaVersion) tuples + /// consumed by per-envelope and cross-envelope JSON-cppagent header + /// wire-shape fixtures. Centralizing the matrix prevents per-envelope + /// tests from drifting away from the cross-envelope E2E set as new + /// schema versions are added. + /// + /// envelopeKind values: "Assets", "Streams", "Devices". + /// schemaVersion values: "2.0", "2.3", "2.5". + /// + /// Source authority: + /// - Reference shape: cppagent v2.7.0.7 emits `Header.schemaVersion` and + /// `Header.testIndicator` on every Streams / Devices / Assets envelope. + /// - Public defect tracker: + /// https://github.com/TrakHound/MTConnect.NET/issues/130 (schemaVersion), + /// https://github.com/TrakHound/MTConnect.NET/issues/131 (testIndicator). + /// + internal static class JsonHeaderWireShapeMatrix + { + public static readonly string[] EnvelopeKinds = + { + "Streams", + "Devices", + "Assets", + }; + + public static readonly string[] SchemaVersions = + { + "2.0", + "2.3", + "2.5", + }; + + /// + /// NUnit TestCaseSource-shaped enumeration of (envelopeKind, schemaVersion). + /// Use as `[TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(Cases))]`. + /// + public static IEnumerable Cases + { + get + { + foreach (var kind in EnvelopeKinds) + { + foreach (var version in SchemaVersions) + { + yield return new TestCaseData(kind, version) + .SetName($"{kind}_envelope_v{version}"); + } + } + } + } + + /// + /// Per-envelope schema-version cases for fixtures scoped to a single + /// envelope kind. Use as + /// `[TestCaseSource(typeof(JsonHeaderWireShapeMatrix), nameof(SchemaVersions))]`. + /// + public static IEnumerable SchemaVersionCases + { + get + { + foreach (var version in SchemaVersions) + { + yield return new TestCaseData(version).SetName($"v{version}"); + } + } + } + } +} From 6a1c94592cc09729ae9e30b121905e87ad8be1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:05:41 +0200 Subject: [PATCH 20/28] test(common): pin AddDevice required-DataItem backfill contract Adds regression fixture asserting NormalizeDevice/AddDevice backfills the four required Device-level DataItems (Availability, AssetChanged, AssetRemoved, AssetCount) exactly once each across four starting states (null, empty, with one required, with all required) and preserves user-provided DataItems. Lets the F-P-H5 perf optimization (cast DataItems once + HashSet for type checks) refactor the inner loop without regressing behavior. --- .../NormalizeDeviceRequiredDataItemsTests.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs diff --git a/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs b/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs new file mode 100644 index 000000000..1118c6bd5 --- /dev/null +++ b/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using MTConnect.Agents; +using MTConnect.Devices; +using MTConnect.Devices.DataItems; +using NUnit.Framework; + +namespace MTConnect.Tests.Common.Agents +{ + /// + /// Pins the contract that backfills + /// the four required Device-level DataItems exactly once each, regardless + /// of the starting state of device.DataItems: + /// - Availability + /// - AssetChanged + /// - AssetRemoved + /// - AssetCount + /// + /// Lets the F-P-H5 perf optimization (cast DataItems once + HashSet for + /// type checks) refactor the inner loop without regressing behavior. + /// + [TestFixture] + public class NormalizeDeviceRequiredDataItemsTests + { + private static readonly string[] RequiredTypeIds = + { + AvailabilityDataItem.TypeId, + AssetChangedDataItem.TypeId, + AssetRemovedDataItem.TypeId, + AssetCountDataItem.TypeId, + }; + + private static IEnumerable? StartingDataItemsForCase(string startingState) + { + return startingState switch + { + "null" => null, + "empty" => new List(), + "with_availability" => new List + { + new AvailabilityDataItem("dev1"), + }, + "with_all_required" => new List + { + new AvailabilityDataItem("dev1"), + new AssetChangedDataItem("dev1"), + new AssetRemovedDataItem("dev1"), + new AssetCountDataItem("dev1"), + }, + _ => null, + }; + } + + [TestCase("null")] + [TestCase("empty")] + [TestCase("with_availability")] + [TestCase("with_all_required")] + public void AddDevice_backfills_all_required_dataItems_exactly_once(string startingState) + { + using var agent = new MTConnectAgent(uuid: "test-agent", initializeAgentDevice: false); + var device = new Device + { + Id = "dev1", + Uuid = "dev1-uuid", + Name = "dev1", + Type = Device.TypeId, + DataItems = StartingDataItemsForCase(startingState), + }; + + var added = agent.AddDevice(device, initializeDataItems: false); + + Assert.That(added, Is.Not.Null, "AddDevice must return the normalized device."); + Assert.That(added.DataItems, Is.Not.Null, "Normalized DataItems must not be null after backfill."); + + var types = added.DataItems!.Select(d => d.Type).ToList(); + foreach (var requiredType in RequiredTypeIds) + { + Assert.That(types.Count(t => t == requiredType), Is.EqualTo(1), + $"Required DataItem type '{requiredType}' must appear exactly once after AddDevice."); + } + } + + [Test] + public void AddDevice_preserves_user_provided_dataItems_alongside_required_ones() + { + using var agent = new MTConnectAgent(uuid: "test-agent", initializeAgentDevice: false); + var custom = new DataItem(DataItemCategory.EVENT, "PROGRAM", null, "dev1-program"); + var device = new Device + { + Id = "dev1", + Uuid = "dev1-uuid", + Name = "dev1", + Type = Device.TypeId, + DataItems = new List { custom }, + }; + + var added = agent.AddDevice(device, initializeDataItems: false); + + Assert.That(added!.DataItems, Is.Not.Null); + Assert.That(added.DataItems!.Any(d => d.Id == "dev1-program"), Is.True, + "User-provided DataItems must survive the required-DataItem backfill."); + foreach (var requiredType in RequiredTypeIds) + { + Assert.That(added.DataItems!.Any(d => d.Type == requiredType), Is.True, + $"Required DataItem type '{requiredType}' must still be backfilled when custom DataItems are present."); + } + } + } +} From b724e410f4a3305e455b12153f906c3207f9ad4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:06:42 +0200 Subject: [PATCH 21/28] fix(agent): cache DataItems and type set during NormalizeDevice backfill Hoists the per-required-type LINQ scan out of the four required-DataItem backfill blocks in MTConnectAgent.NormalizeDevice. The DataItems enumerable is now materialized once into a List and the existing types are projected into a HashSet, dropping per-block work from O(n) lookups + four ToList() allocations to O(1) lookups + one materialization. Behavior preserved per NormalizeDeviceRequiredDataItemsTests. --- .../Agents/MTConnectAgent.cs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs index 8a807c379..13dd87cc3 100644 --- a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs +++ b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs @@ -1112,54 +1112,67 @@ private Device NormalizeDevice(IDevice device) obj.Compositions = NormalizeCompositions(device.Compositions, obj, obj); obj.Components = NormalizeComponents(device.Components, obj, obj); + // Required DataItem backfill: enumerate `obj.DataItems` once + // into a List and project the type set into a HashSet + // so each required-type lookup is O(1) instead of O(n) and we + // avoid the per-required-type ToList() allocation. Preserves the + // existing behavior pinned by NormalizeDeviceRequiredDataItemsTests. + var dataItemList = obj.DataItems != null + ? new List(obj.DataItems) + : new List(); + var dataItemTypes = new HashSet(dataItemList.Count); + for (var i = 0; i < dataItemList.Count; i++) + { + var t = dataItemList[i]?.Type; + if (t != null) dataItemTypes.Add(t); + } + // Add Required Availability DataItem - if (obj.DataItems.IsNullOrEmpty() || !obj.DataItems.Any(o => o.Type == AvailabilityDataItem.TypeId)) + if (!dataItemTypes.Contains(AvailabilityDataItem.TypeId)) { var availability = new AvailabilityDataItem(obj.Id); availability.Device = obj; availability.Container = obj; availability.Name = AvailabilityDataItem.NameId; - var x = obj.DataItems.ToList(); - x.Add(availability); - obj.DataItems = x; + dataItemList.Add(availability); + dataItemTypes.Add(AvailabilityDataItem.TypeId); } // Add Required AssetChanged DataItem - if (obj.DataItems.IsNullOrEmpty() || !obj.DataItems.Any(o => o.Type == AssetChangedDataItem.TypeId)) + if (!dataItemTypes.Contains(AssetChangedDataItem.TypeId)) { var assetChanged = new AssetChangedDataItem(obj.Id); assetChanged.Device = obj; assetChanged.Container = obj; assetChanged.Name = AssetChangedDataItem.NameId; - var x = obj.DataItems.ToList(); - x.Add(assetChanged); - obj.DataItems = x; + dataItemList.Add(assetChanged); + dataItemTypes.Add(AssetChangedDataItem.TypeId); } // Add Required AssetRemoved DataItem - if (obj.DataItems.IsNullOrEmpty() || !obj.DataItems.Any(o => o.Type == AssetRemovedDataItem.TypeId)) + if (!dataItemTypes.Contains(AssetRemovedDataItem.TypeId)) { var assetRemoved = new AssetRemovedDataItem(obj.Id); assetRemoved.Device = obj; assetRemoved.Container = obj; assetRemoved.Name = AssetRemovedDataItem.NameId; - var x = obj.DataItems.ToList(); - x.Add(assetRemoved); - obj.DataItems = x; + dataItemList.Add(assetRemoved); + dataItemTypes.Add(AssetRemovedDataItem.TypeId); } // Add Required AssetCount DataItem - if (obj.DataItems.IsNullOrEmpty() || !obj.DataItems.Any(o => o.Type == AssetCountDataItem.TypeId)) + if (!dataItemTypes.Contains(AssetCountDataItem.TypeId)) { var assetcount = new AssetCountDataItem(obj.Id); assetcount.Device = obj; assetcount.Container = obj; assetcount.Name = AssetCountDataItem.NameId; - var x = obj.DataItems.ToList(); - x.Add(assetcount); - obj.DataItems = x; + dataItemList.Add(assetcount); + dataItemTypes.Add(AssetCountDataItem.TypeId); } + obj.DataItems = dataItemList; + // Generic Components var genericComponents = obj.GetComponents()?.Where(o => o.GetType() == typeof(Component)); From c8acb39c1def63e1207e67ddaaeeff1acacefa3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:07:38 +0200 Subject: [PATCH 22/28] test(common): pin Header xmldoc example off stale 2.5 snapshot Adds a RED file-content fixture asserting the SchemaVersion XML-doc example on the six MTConnect.NET-Common Header types does not pin to the stale "2.5" string. Drives the F-D15 doc fix that updates the example to "2.7" or a neutral phrasing. --- .../HeaderXmlDocExampleVersionTests.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/MTConnect.NET-Common-Tests/Headers/HeaderXmlDocExampleVersionTests.cs diff --git a/tests/MTConnect.NET-Common-Tests/Headers/HeaderXmlDocExampleVersionTests.cs b/tests/MTConnect.NET-Common-Tests/Headers/HeaderXmlDocExampleVersionTests.cs new file mode 100644 index 000000000..546dbf4e4 --- /dev/null +++ b/tests/MTConnect.NET-Common-Tests/Headers/HeaderXmlDocExampleVersionTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.IO; +using NUnit.Framework; + +namespace MTConnect.Tests.Common.Headers +{ + /// + /// Pins the SchemaVersion XML-doc example string on the six Header + /// interfaces and classes. The example must reference the current + /// MTConnect Standard release ("2.7") rather than the stale "2.5" + /// snapshot, so consumers reading the IntelliSense don't think + /// the agent only supports an older revision. + /// + /// Files covered (line 27 in each): + /// - libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs + /// - libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs + /// - libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs + /// - libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs + /// - libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs + /// - libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs + /// + [TestFixture] + public class HeaderXmlDocExampleVersionTests + { + private static readonly string[] HeaderFileRelativePaths = + { + "libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs", + "libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs", + "libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs", + "libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs", + "libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs", + "libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs", + }; + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (dir != null) + { + if (Directory.Exists(Path.Combine(dir.FullName, "libraries")) && + Directory.Exists(Path.Combine(dir.FullName, "tests"))) + { + return dir.FullName; + } + dir = dir.Parent; + } + Assert.Fail("Could not locate repo root from test working directory."); + return string.Empty; + } + + [TestCaseSource(nameof(HeaderFileRelativePaths))] + public void Header_xmldoc_example_does_not_reference_stale_version_2_5(string relativePath) + { + var fullPath = Path.Combine(FindRepoRoot(), relativePath); + Assert.That(File.Exists(fullPath), Is.True, $"Header source file not found at {fullPath}."); + + var text = File.ReadAllText(fullPath); + + Assert.That(text.Contains("(for example \"2.5\")"), Is.False, + $"{relativePath} must not pin its SchemaVersion XML-doc example to the stale \"2.5\" string."); + } + } +} From 6bb3c986a846d59a17848f51d078dde3092436cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:08:19 +0200 Subject: [PATCH 23/28] docs(common-headers): refresh SchemaVersion xmldoc example to 2.7 Updates the SchemaVersion XML-doc example on the six MTConnect.NET-Common Header interfaces and classes from the stale "2.5" snapshot to "2.7" so IntelliSense matches the current MTConnect Standard release the agent targets. Greens the F-D15 regression pin. --- .../MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs | 2 +- .../MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs | 2 +- .../MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs | 2 +- .../MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs | 2 +- .../MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs | 2 +- .../MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs index 13afebcc1..9fb4d7500 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectAssestsHeader.cs @@ -23,7 +23,7 @@ public interface IMTConnectAssetsHeader string Version { get; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// string SchemaVersion { get; } diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs index 12888cdef..46335ff99 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectDevicesHeader.cs @@ -23,7 +23,7 @@ public interface IMTConnectDevicesHeader string Version { get; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// string SchemaVersion { get; } diff --git a/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs b/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs index cd32eb51a..eb305f60c 100644 --- a/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/IMTConnectStreamsHeader.cs @@ -23,7 +23,7 @@ public interface IMTConnectStreamsHeader string Version { get; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// string SchemaVersion { get; } diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs index aa28381cc..0df2034d0 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectAssestsHeader.cs @@ -23,7 +23,7 @@ public class MTConnectAssetsHeader : IMTConnectAssetsHeader public string Version { get; set; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// public string SchemaVersion { get; set; } diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs index f37a87b5d..f5c5af650 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectDevicesHeader.cs @@ -23,7 +23,7 @@ public class MTConnectDevicesHeader : IMTConnectDevicesHeader public string Version { get; set; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// public string SchemaVersion { get; set; } diff --git a/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs b/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs index d2659e850..5109ce080 100644 --- a/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs +++ b/libraries/MTConnect.NET-Common/Headers/MTConnectStreamsHeader.cs @@ -23,7 +23,7 @@ public class MTConnectStreamsHeader : IMTConnectStreamsHeader public string Version { get; set; } /// - /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.5"). + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. /// public string SchemaVersion { get; set; } From 167b47c7d553b454ae8767c90e2e732ce369d9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:09:15 +0200 Subject: [PATCH 24/28] test(json-cppagent): pin SchemaVersion xmldoc on cppagent headers Adds RED file-content fixture asserting the SchemaVersion property in the three JSON-cppagent header DTOs (Assets/Streams/Devices) is preceded by an XML-doc block. Drives the F-D16 doc fix that documents the new property as the cppagent v2 wire-shape mirror. --- .../JsonHeaderSchemaVersionXmlDocTests.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderSchemaVersionXmlDocTests.cs diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderSchemaVersionXmlDocTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderSchemaVersionXmlDocTests.cs new file mode 100644 index 000000000..055fa9cf5 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/JsonHeaderSchemaVersionXmlDocTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.IO; +using System.Text.RegularExpressions; +using NUnit.Framework; + +namespace MTConnect.Tests.JsonCppagent +{ + /// + /// Pins the XML-doc summary requirement on the SchemaVersion property in + /// the three JSON-cppagent header DTO files. Each property must carry a + /// `` block immediately above the `[JsonPropertyName("schemaVersion")]` + /// attribute so consumers reading IntelliSense see the cppagent v2 + /// wire-shape semantics inline. + /// + /// Files covered: + /// - libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs + /// - libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs + /// - libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs + /// + [TestFixture] + public class JsonHeaderSchemaVersionXmlDocTests + { + private static readonly string[] HeaderFileRelativePaths = + { + "libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs", + "libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs", + "libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs", + }; + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (dir != null) + { + if (Directory.Exists(Path.Combine(dir.FullName, "libraries")) && + Directory.Exists(Path.Combine(dir.FullName, "tests"))) + { + return dir.FullName; + } + dir = dir.Parent; + } + Assert.Fail("Could not locate repo root from test working directory."); + return string.Empty; + } + + [TestCaseSource(nameof(HeaderFileRelativePaths))] + public void Header_dto_schemaVersion_has_xmldoc_summary(string relativePath) + { + var fullPath = Path.Combine(FindRepoRoot(), relativePath); + Assert.That(File.Exists(fullPath), Is.True, $"Header source file not found at {fullPath}."); + + var text = File.ReadAllText(fullPath); + + // Match a `...` block followed (with optional + // whitespace and other attributes) by `[JsonPropertyName("schemaVersion")]`. + var pattern = new Regex( + @"[\s\S]+?\s*(?:///[^\n]*\n\s*)*\[JsonPropertyName\(""schemaVersion""\)\]", + RegexOptions.Multiline); + + Assert.That(pattern.IsMatch(text), Is.True, + $"{relativePath} must precede [JsonPropertyName(\"schemaVersion\")] with a XML-doc block."); + } + } +} From 85c7e8cbdabb9d751d20b3014868350e80c25631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:10:04 +0200 Subject: [PATCH 25/28] docs(json-cppagent): document SchemaVersion on cppagent headers Adds an XML-doc block above the SchemaVersion property in the JsonAssetsHeader and JsonStreamsHeader DTOs describing the property as the cppagent v2 wire-shape mirror. JsonDevicesHeader's SchemaVersion summary is contributed by fix/issue-128 (envelope-vs-Header semantics) which lands ahead of this branch per the documented merge order; the F-D16 test only requires that a block precede [JsonPropertyName("schemaVersion")] on each of the three DTOs and is greened by either author. Greens the F-D16 doc pin. --- .../MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs | 4 ++++ .../MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs index a0683cee5..8e5c188ac 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Assets/JsonAssetsHeader.cs @@ -15,6 +15,10 @@ public class JsonAssetsHeader [JsonPropertyName("version")] public string Version { get; set; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// [JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; } diff --git a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs index a8d446e70..8aea7a4f5 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonStreamsHeader.cs @@ -15,6 +15,10 @@ public class JsonStreamsHeader [JsonPropertyName("version")] public string Version { get; set; } + /// + /// The major and minor number of the MTConnect Standard schema the Response Document conforms to (for example "2.7"). + /// Mirrors the cppagent v2 wire shape that emits `schemaVersion` on every Header. + /// [JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; } From 9b087783089e885c5cd0ef729ce94b60435c5174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Mon, 27 Apr 2026 22:54:00 +0200 Subject: [PATCH 26/28] fix(common): preserve AssetCount DATA_SET representation override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The F-P-H5 refactor (cast DataItems to List once + HashSet for type checks) inadvertently dropped the spec-required line that overrides the auto-injected AssetCount's Representation from VALUE to DATA_SET. Restore the override + the explanatory comment citing the MTConnect Part 2 UML id. Cross-PR interaction: per the campaign convention, the consuming PR fixes the test that depends on the refactored default — here the consuming surface is the fix/issue-132 regression suite, but the fix must land on this branch since that's where F-P-H5 dropped the line. --- libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs index 13dd87cc3..dd655c220 100644 --- a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs +++ b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs @@ -1167,6 +1167,11 @@ private Device NormalizeDevice(IDevice device) assetcount.Device = obj; assetcount.Container = obj; assetcount.Name = AssetCountDataItem.NameId; + // ASSET_COUNT is a DATA_SET representation per MTConnect Part 2 + // (UML _19_0_3_68e0225_1640602520420_217627_44). The generated + // AssetCountDataItem still defaults Representation to VALUE; override + // it here so the auto-injected DataItem matches the spec. + assetcount.Representation = DataItemRepresentation.DATA_SET; dataItemList.Add(assetcount); dataItemTypes.Add(AssetCountDataItem.TypeId); } From 2cd95ee0059d873887a5075f28eaeb0400663269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Tue, 28 Apr 2026 19:30:06 +0200 Subject: [PATCH 27/28] fix(agent): guard HashSet capacity ctor for legacy TFMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `new HashSet(int capacity)` was added in .NET Framework 4.7.2 + netstandard 2.1 + .NET Core 2.0; net461 / net47 / net471 / netstandard2.0 only know `(IEqualityComparer)` and reject the int with CS1503. The NormalizeDevice DataItem-type cache landed in 355b74ec used the capacity ctor unconditionally, which broke `dotnet pack -c Release` on those TFMs. Wrap the capacity-aware allocation in a conditional so: - net472 + / netstandard2.1 + / netcoreapp2.0 + → keep the capacity hint (the perf optimization the F-P-H5 commit was after). - net461 / net471 / net47 / netstandard2.0 → fall back to the parameterless ctor; behaviour identical apart from one extra rehash on the first hot-path call. Surfaced via `dotnet pack -c Release` from the integration branch. --- libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs index dd655c220..7e455f4c6 100644 --- a/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs +++ b/libraries/MTConnect.NET-Common/Agents/MTConnectAgent.cs @@ -1120,7 +1120,11 @@ private Device NormalizeDevice(IDevice device) var dataItemList = obj.DataItems != null ? new List(obj.DataItems) : new List(); +#if NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER var dataItemTypes = new HashSet(dataItemList.Count); +#else + var dataItemTypes = new HashSet(); +#endif for (var i = 0; i < dataItemList.Count; i++) { var t = dataItemList[i]?.Type; From e68d24d5ebf9a8938f6b19a2dd0fac67e8a45500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Boly=C3=B3s?= Date: Thu, 30 Apr 2026 10:37:48 +0200 Subject: [PATCH 28/28] style(prose): rewrite NormalizeDevice test summary inline, drop F-code The class-level summary on NormalizeDeviceRequiredDataItemsTests cited an internal audit-finding code when describing the inner-loop perf optimisation it leaves room for; rewrite the summary so the same point is made inline. --- .../Agents/NormalizeDeviceRequiredDataItemsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs b/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs index 1118c6bd5..000796346 100644 --- a/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs +++ b/tests/MTConnect.NET-Common-Tests/Agents/NormalizeDeviceRequiredDataItemsTests.cs @@ -19,8 +19,8 @@ namespace MTConnect.Tests.Common.Agents /// - AssetRemoved /// - AssetCount /// - /// Lets the F-P-H5 perf optimization (cast DataItems once + HashSet for - /// type checks) refactor the inner loop without regressing behavior. + /// Lets the inner-loop perf optimization (cast DataItems once and use a + /// HashSet for type-id checks) refactor without regressing behavior. /// [TestFixture] public class NormalizeDeviceRequiredDataItemsTests