Skip to content

feat(common): add IConfiguration.VendorExtensions for XSD extension - #223

Draft
ottobolyos wants to merge 12 commits into
TrakHound:masterfrom
ottobolyos:feat/configuration-vendor-extensions
Draft

feat(common): add IConfiguration.VendorExtensions for XSD extension#223
ottobolyos wants to merge 12 commits into
TrakHound:masterfrom
ottobolyos:feat/configuration-vendor-extensions

Conversation

@ottobolyos

@ottobolyos ottobolyos commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a standard-compliant vendor-extension surface on IConfiguration — a new IEnumerable<XElement> VendorExtensions property — that carries fully-formed, vendor-namespaced XML elements verbatim through the MTConnect.NET XML formatter. The mechanism mirrors the MTConnect v2.7 XSD's own extension design: every standard child of ComponentConfigurationType is a substitutionGroup='AbstractConfiguration' declaration, and vendors extend the type by publishing their own XSD that likewise substitutes for AbstractConfiguration. VendorExtensions is the strongly-typed slot that carries those substitutions end-to-end.

Standard citations

MTConnectDevices_2.7.xsd:

  • ComponentConfigurationType (line 8161) declares its content model as <xs:element ref="AbstractConfiguration" minOccurs="1" maxOccurs="unbounded"/> — the sequence permits any element that substitutes for the abstract AbstractConfiguration element and rejects unknown children otherwise (no xs:any).
  • Substitution declarations (lines 8239 through 9974) mark every standard child — SensorConfiguration, Specifications, Relationships, CoordinateSystems, Motion, SolidModel, ImageFiles, PowerSources — with substitutionGroup='AbstractConfiguration'. Vendors extend the type by publishing an XSD declaring a vendor-namespaced element with the same substitutionGroup.

The MTConnect.NET XML formatter writes each VendorExtensions entry verbatim inside the <Configuration> sequence via XmlWriter.WriteRaw. Downstream strict-XSD validation continues to work when the vendor's XSD is loaded alongside the MTConnect schemas — the vendor's XSD supplies the substitution declaration that makes the element valid at the AbstractConfiguration slot.

What this PR does

  • feat(sysml-import) — marks Devices.Configurations.Configuration as IsPartial = true in the CSharp renderer, matching the pattern already applied to Component, Composition, DataItem, and other classes that carry hand-authored companion partials.
  • feat(common) — regenerates Configuration.g.cs and IConfiguration.g.cs from the v2.7 XMI to declare partial class / partial interface.
  • feat(common) — adds hand-authored Configuration.cs and IConfiguration.cs partials that declare IEnumerable<XElement> VendorExtensions { get; } on the interface and { get; set; } on the concrete.
  • feat(xml) — wires the surface through XmlConfiguration: [XmlAnyElement] capture on the read path, WriteRaw emission on the write path, ordered after the standard children.

Non-goals

This is not a raw-string RawXml passthrough — a string-typed slot bypasses namespace hygiene and would produce documents that fail strict XSD validation at the ComponentConfigurationType sequence. The XElement typing forces well-formed XML at author time and preserves namespace declarations verbatim through the formatter.

Breaking

Adds a new member (VendorExtensions { get; }) to the public IConfiguration interface — a source-compatibility break for any external implementer of IConfiguration outside the library. Acceptable on the next major version boundary.


Supersedes #214.

Dime review cycle 1

Retroactive backfill (2026-08-20). The initial 6-agent Ultrareview cycle exercised the XML round-trip surface end-to-end. Ledger reconstruction from commit history:

  • [TEST] test-coverage-audit — XML round-trip pin for Configuration.VendorExtensions (empty, single-entry, multi-entry) landed as test(xml-tests): pin XML round trip for Configuration.VendorExtensions (d3d0d88).
  • [TEST] test-coverage-audit — null-element handling on the interface surface pinned by test(xml-tests): pin null-element handling on IConfiguration.VendorExtensions (fe095bf).
  • [TEST] test-coverage-audit — HTTP Probe end-to-end coverage added via test(integration): pin HTTP Probe end-to-end for IConfiguration.VendorExtensions (e8582e8).
  • [TEST] test-coverage-audit — coverage widening for the interface implementation matrix added in test(xml-tests): widen IConfiguration.VendorExtensions coverage on interface (fe18487).

Dime review cycle 2

Retroactive backfill (2026-08-20). Second cycle after the 2026-08-19 BrE→AmE message sweep reconciliation, dispatched to close coverage FLOOR gaps surfaced by re-running test-coverage-audit on the reconciled tip. Ledger:

  • [TEST] test-coverage-audit — coverage FLOOR gaps on IConfiguration.VendorExtensions (5 test files, +1165 LOC) closed atomically in test(xml-tests): close coverage FLOOR gaps on VendorExtensions (611f482). This commit was cherry-picked on top of the new AmE tip after the BrE→AmE swept in patch content and message (behaviour→behavior, serialise→serialize, honour→honor).
  • Verified via nohup test on bluefin with --blame-hang-timeout 15min: all individual test suites green (Common 3987, XML 131, JSON 68, MqttRelay 62, Docs 67 = 4315 tests, 0 failed). Test host crashed at 15-min inactivity timeout during concurrent Docs-Tests execution — bluefin-flake pattern documented in fix: clean every Release-pack warning and add multi-TFM CI gate #219 + fix(xml): dispatch v2.6/v2.7 namespaces + default unknown to Max #229 verify reports (docs-tests hang under concurrent bluefin load, uncorrelated with PR content since PR doesn't touch docs surface).

(Zero unfixed findings — Ready-eligible.)

Dime review cycle 3

6-agent Ultrareview pass (2026-08-25) ahead of the Testing integration → Completed flip: bug-detector, code-review, security-audit, test-coverage-audit, documentation-audit, simplification.

  • [FIXED] security-audit MEDIUM — XElement.Parse on each captured vendor-extension element's OuterXml used implicit XmlReader defaults instead of the repo's explicit XXE-hardening idiom (XmiDeserializer.FromXml/FromFile: pinned DtdProcessing.Prohibit + XmlResolver = null). Not currently exploitable — a DOCTYPE cannot legally appear inside a captured element's OuterXml — but closed for defense-in-depth consistency. fix(xml): harden VendorExtensions XElement parse against XXE (13c9af7).
  • [FIXED] code-review MEDIUM — class remarks on IConfiguration.VendorExtensions misquoted ComponentConfigurationType's AbstractConfiguration ref as minOccurs="0"; the schema (MTConnectDevices_2.7.xsd:8168) declares minOccurs='1'. Corrected in both this PR description (above) and the doc comment. fix(common,xml-tests): correct XSD minOccurs citation, sweep AmE spelling (f0d1147).
  • [FIXED] code-review LOW — BrE spellings (unrecognised, behaviour, serialise(s/d), deserialiser) introduced by this PR's own commits, including two test method names, swept to AmE in the same commit as above.
  • [SKIP-rationale] security-audit MEDIUM — no depth/size ceiling on vendor-extension capture. DTD is already prohibited on both the outer document read and (after the fix above) the per-element re-parse, so no entity-expansion amplification vector exists; a large-but-linear nested payload is bounded by the same document-size limits as any other XML content in the ingestion pipeline, and no other slot on XmlConfiguration enforces a bespoke depth/size cap either. Not an inconsistency worth a one-off guard here.
  • [SKIP-rationale] security-audit LOW — vendor extension's own namespace-prefix declarations could locally shadow a prefix already open in the writer's ancestor scope. XElement.ToString() always emits well-formed, self-contained XML (not a raw-string injection risk); a non-namespace-aware downstream consumer choosing to key off prefix text rather than the resolved namespace URI is off-spec on its own terms.
  • [TRACKED] bug-detector MEDIUM — Configuration.ImageFiles/Configuration.PowerSource (both cited as standard AbstractConfiguration substitution children in this PR's own "Standard citations" section) have no XmlConfiguration wiring at all — pre-existing, repo-wide gap (confirmed absent on upstream/master before this PR; no XmlImageFile/XmlPowerSource surrogate exists anywhere in the tree). Before this PR such elements were silently dropped on read; after this PR's [XmlAnyElement] catch-all, they're captured into VendorExtensions instead — still functionally absent from the typed properties, but now mislabeled as vendor content. Filed as XML wire format missing for Configuration.ImageFiles / Configuration.PowerSource #262 rather than fixed in-PR: closing it properly means adding two new XML surrogate type families from scratch, a distinct feature from the VendorExtensions mechanism this PR delivers.
  • [SKIP-rationale] bug-detector LOW — vendor-extension write order doesn't preserve original document-order interleave with standard children (always appended after Specifications). Consistent with every other slot on XmlConfiguration, which already serializes each standard child in a fixed field order regardless of source order — not a regression this PR introduces.
  • [SKIP-rationale] bug-detector LOW — WriteRaw bypasses the writer's indent-tracking state, so raw-inserted extension content can look inconsistently indented relative to siblings when Settings.Indent = true. Cosmetic only.
  • [SKIP-rationale] documentation-audit LOW — no VitePress page documents <Configuration>'s per-element wire format at all (pre-existing gap predating this PR; docs/wire-formats/xml.md is envelope-level, not per-element, for every other Configuration child too).
  • [SKIP-rationale] simplification LOW × 2 — test-fixture scaffolding duplicated against sibling ConfigurationPolymorphicHttpProbeWorkflowTests.cs, and structurally parallel JSON/JSON-cppagent known-limit test files. Both consistent with this repo's existing one-fixture-per-file convention for independently-movable test files; not accidental duplication.
  • [CLEAN] test-coverage-audit — coverage substantively complete (enum-arm N/A, all failure/branch paths covered, no test smells). Noted a TDD-ordering process gap (feat commits landed before test commits in cycle 1) — historical, not a content gap, not re-litigated.

All local verification re-run green after the fix commits: dotnet build -warnaserror 0/0, dotnet format --verify-no-changes clean, MTConnect.NET-XML-Tests 131/131, MTConnect.NET-Common-Tests 4102/4102, MTConnect.NET-HTTP-Tests 112/112.

(Zero unfixed MEDIUM+ findings — Completed-eligible.)

Depends on

@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from 46bcdc8 to bac6c79 Compare August 19, 2026 12:12
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
…VendorExtensions

Lands the coverage-FLOOR (§1.0d-trigies-novodecies) tests missing from the
initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed

Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
…VendorExtensions

Lands the coverage-FLOOR (§1.0d-trigies-novodecies) tests missing from the
initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed

Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from 811e6d1 to 611f482 Compare August 19, 2026 22:05
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…VendorExtensions

Lands the coverage-FLOOR (§1.0d-trigies-novodecies) tests missing from the
initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed

Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from 611f482 to afa0b70 Compare August 21, 2026 06:18
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…VendorExtensions

Lands the coverage-FLOOR (§1.0d-trigies-novodecies) tests missing from the
initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed

Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from afa0b70 to e173d94 Compare August 21, 2026 08:33
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from e173d94 to 486c7b7 Compare August 21, 2026 14:18
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from 486c7b7 to c0b72bf Compare August 21, 2026 16:06
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 22, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from c0b72bf to b6d7119 Compare August 22, 2026 00:52
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 23, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from b6d7119 to 41567a2 Compare August 23, 2026 02:58
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 23, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 25, 2026
Explicitly pin DtdProcessing.Prohibit and XmlResolver = null when
parsing each captured vendor-extension element's OuterXml back into an
XElement, mirroring the repo-wide defense-in-depth convention already
established in XmiDeserializer.FromXml/FromFile.

.NET 6+ already defaults XmlResolver to null and disables DTD
processing on the outer document read, and a DOCTYPE cannot legally
appear inside a captured element's OuterXml, so this closes a
consistency gap rather than an exploitable path today — but it
survives a future framework downgrade or accidental restoration of
XmlUrlResolver, and keeps every XML parse entry point in the repo on
the same explicit hardening idiom.

Found by the security-audit pass of the PR TrakHound#223 dime review cycle.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 25, 2026
…ling

IConfiguration.VendorExtensions' class remarks misquoted
MTConnectDevices_2.7.xsd's ComponentConfigurationType content model as
minOccurs="0" on the AbstractConfiguration ref; the schema
(tests/Compliance/.../v2_7/MTConnectDevices_2.7.xsd:8168) declares
minOccurs='1' — once <Configuration> is present it must contain at
least one AbstractConfiguration-substitution child.

Also sweeps the handful of BrE spellings ("unrecognised", "behaviour",
"serialise(s/d)", "deserialiser") introduced by this PR's own commits
into AmE, including two test method names, matching the repo-wide
AmE convention already applied everywhere else in the codebase.

Found by the code-review pass of the PR TrakHound#223 dime review cycle.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 25, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
Explicitly pin DtdProcessing.Prohibit and XmlResolver = null when
parsing each captured vendor-extension element's OuterXml back into an
XElement, mirroring the repo-wide defense-in-depth convention already
established in XmiDeserializer.FromXml/FromFile.

.NET 6+ already defaults XmlResolver to null and disables DTD
processing on the outer document read, and a DOCTYPE cannot legally
appear inside a captured element's OuterXml, so this closes a
consistency gap rather than an exploitable path today — but it
survives a future framework downgrade or accidental restoration of
XmlUrlResolver, and keeps every XML parse entry point in the repo on
the same explicit hardening idiom.

Found by the security-audit pass of the PR TrakHound#223 dime review cycle.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…ling

IConfiguration.VendorExtensions' class remarks misquoted
MTConnectDevices_2.7.xsd's ComponentConfigurationType content model as
minOccurs="0" on the AbstractConfiguration ref; the schema
(tests/Compliance/.../v2_7/MTConnectDevices_2.7.xsd:8168) declares
minOccurs='1' — once <Configuration> is present it must contain at
least one AbstractConfiguration-substitution child.

Also sweeps the handful of BrE spellings ("unrecognised", "behaviour",
"serialise(s/d)", "deserialiser") introduced by this PR's own commits
into AmE, including two test method names, matching the repo-wide
AmE convention already applied everywhere else in the codebase.

Found by the code-review pass of the PR TrakHound#223 dime review cycle.
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from f0d1147 to a7495a5 Compare August 26, 2026 04:42
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…tegration/up-to-pr-223

Cascade rebuild after upstream/master advanced to 0ddc3c6 (PR TrakHound#220 merge).
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…ng PR test surfaces

integration-branch-only glue fix. PR TrakHound#221, TrakHound#223/TrakHound#224 (via TrakHound#233's
consolidation), and TrakHound#226 each add or touch test files written against
NUnit 3, whose bare-lambda Assert.Throws/DoesNotThrow calls become
ambiguous once PR TrakHound#239's NUnit 3->4 upgrade lands in the same tree.
Cast to Action explicitly at each affected call site and replace the
two CollectionAssert.AreEqual calls (removed in NUnit 4) with
Assert.That(..., Is.EqualTo(...)). None of the individual PR branches
need this fix standalone — each builds clean against current
upstream/master without NUnit 4 present.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
Explicitly pin DtdProcessing.Prohibit and XmlResolver = null when
parsing each captured vendor-extension element's OuterXml back into an
XElement, mirroring the repo-wide defense-in-depth convention already
established in XmiDeserializer.FromXml/FromFile.

.NET 6+ already defaults XmlResolver to null and disables DTD
processing on the outer document read, and a DOCTYPE cannot legally
appear inside a captured element's OuterXml, so this closes a
consistency gap rather than an exploitable path today — but it
survives a future framework downgrade or accidental restoration of
XmlUrlResolver, and keeps every XML parse entry point in the repo on
the same explicit hardening idiom.

Found by the security-audit pass of the PR TrakHound#223 dime review cycle.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…ling

IConfiguration.VendorExtensions' class remarks misquoted
MTConnectDevices_2.7.xsd's ComponentConfigurationType content model as
minOccurs="0" on the AbstractConfiguration ref; the schema
(tests/Compliance/.../v2_7/MTConnectDevices_2.7.xsd:8168) declares
minOccurs='1' — once <Configuration> is present it must contain at
least one AbstractConfiguration-substitution child.

Also sweeps the handful of BrE spellings ("unrecognised", "behaviour",
"serialise(s/d)", "deserialiser") introduced by this PR's own commits
into AmE, including two test method names, matching the repo-wide
AmE convention already applied everywhere else in the codebase.

Found by the code-review pass of the PR TrakHound#223 dime review cycle.
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from a7495a5 to e810192 Compare August 26, 2026 16:24
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…tegration/up-to-pr-223 on top of integration/up-to-pr-233
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…tegration/up-to-pr-223 on top of previous cascade tip
Adds `Devices.Configurations.Configuration` to the switch that flips
`ClassModel.IsPartial = true` in the CSharp renderer, matching the
pattern already applied to `Devices.Component`, `Devices.Composition`,
`Devices.DataItem`, and other SysML classes that carry hand-authored
partial companion files in `libraries/MTConnect.NET-Common/`.

Motivates: the follow-up regen emits `partial class Configuration` and
`partial interface IConfiguration`, allowing a hand-authored partial to
supply the vendor-extension surface (`VendorExtensions`) that lives
outside the SysML model but is required by the MTConnect v2.7 XSD
extension mechanism (`substitutionGroup='AbstractConfiguration'`).
Regenerated output of the sibling `feat(sysml-import): mark Configuration
partial for extension` commit. The two `.g.cs` files now declare
`partial class Configuration` and `partial interface IConfiguration`,
unlocking the hand-authored companion partials that supply the
vendor-extension surface.

Diff scope is intentionally narrowed to the two Configuration files
that carry the semantic change. A full regen also surfaces pre-existing
trailing-whitespace drift across ~60 other `.g.cs` files caused by
`Model.scriban` emitting an 8-space blank line between properties; that
drift is out of scope for this PR and belongs to a separate `chore` PR
that fixes the whitespace-trim markers in the templates.
Adds `IEnumerable<XElement> VendorExtensions` to `IConfiguration` via
hand-authored partial companions to the generated `.g.cs` files. The
property carries fully-formed, vendor-namespaced XML elements that are
written verbatim into the on-the-wire `<Configuration>` element by the
XML formatter (wired in the sibling `feat(xml)` commit).

Standard basis. `MTConnectDevices_2.7.xsd` declares
`ComponentConfigurationType` with
`<xs:element ref="AbstractConfiguration" minOccurs="0"
maxOccurs="unbounded"/>` and marks every standard child
(`SensorConfiguration`, `Specifications`, `Relationships`,
`CoordinateSystems`, `Motion`, `SolidModel`, `ImageFiles`,
`PowerSources`) with `substitutionGroup='AbstractConfiguration'`.
Vendors extend by publishing their own XSD declaring a
vendor-namespaced element that likewise substitutes for
`AbstractConfiguration`; `VendorExtensions` is the surface that carries
those instances end to end.

Non-goal. This is not a raw-string passthrough — the type is
`XElement`, which forces vendors to author well-formed XML rather than
a raw string that would bypass namespace hygiene.
Wires the `VendorExtensions` surface through the XML formatter's
`XmlConfiguration` surrogate:

- **Read path.** `[XmlAnyElement] XmlElement[] VendorExtensions` on the
  surrogate captures every child of `<Configuration>` that the
  strongly-typed slots (`CoordinateSystems`, `Motion`, `Relationships`,
  `SensorConfiguration`, `SolidModel`, `Specifications`) do not bind.
  Each captured element is projected to `System.Xml.Linq.XElement` (with
  whitespace preserved) and added to `Configuration.VendorExtensions`.
- **Write path.** `XmlConfiguration.WriteXml` iterates
  `configuration.VendorExtensions` after the standard children and
  writes each `XElement` verbatim via `WriteRaw` using
  `SaveOptions.DisableFormatting`, so vendor-namespaced elements
  preserve their prefixes, attributes, and mixed content exactly as
  authored.

The formatter is agnostic to whether a vendor XSD is loaded at
validation time — that responsibility sits with the caller of the
strict-load XSD gate. When the vendor XSD is present, the emitted
document validates against `MTConnectDevices_2.7.xsd`'s
`ComponentConfigurationType` via the `AbstractConfiguration`
substitution mechanism (schema element declared at
`Schemas/v2_7/MTConnectDevices_2.7.xsd` line 8161).
Locks the vendor-extension surface behavior on `XmlConfiguration`:

- Emit: a single vendor-namespaced element serializes verbatim inside
  `<Configuration>`; multiple entries preserve author order; vendor
  extensions coexist with standard children such as `<Motion>`.
- Read: an unrecognized child element is captured onto
  `IConfiguration.VendorExtensions` with its namespace and payload
  intact; a full write-then-read round trip preserves the vendor
  element's local name, namespace, attributes, and text content.
- Null / empty: `null` and empty collections emit no extra child
  element inside `<Configuration>`; a `<Configuration>` composed only
  of standard children yields `null` `VendorExtensions` (no false
  positives via the `[XmlAnyElement]` capture).

Sources cited: MTConnect v2.7 XSD `ComponentConfigurationType` +
`substitutionGroup='AbstractConfiguration'`, SysML XMI `Configuration`
UML class, Part 2 (Devices) prose on Configuration extensibility.
Adds two edge-case rows on the `XmlConfiguration` write path:

- A collection containing one or more `null` `XElement` entries writes
  the non-null entries verbatim and skips the null slot -- no stray
  empty tag emitted between real extensions.
- A collection whose entries are ALL null writes no extension element
  at all; the emitted `<Configuration>` element is empty (self-closing
  or expanded end tag).

Both rows pin the guard `if (extension == null) continue;` on the
write path against a future refactor that would emit an empty tag or
throw.
Adds a workflow fixture that boots an in-process
`MTConnectAgentBroker` plus embedded `MTConnectHttpServer`, seeds a
Device whose Linear component's Configuration carries a
vendor-namespaced `XElement`, and asserts:

- The emitted /probe response body contains the vendor element
  verbatim inside the `<Configuration>` envelope -- local name,
  namespace prefix, attributes, and child text preserved.
- A round trip through the strongly-typed `MTConnectHttpClient`
  arrives at the client model with the vendor element intact on
  `linear.Configuration.VendorExtensions` -- namespace, attributes,
  child element text preserved.

Both tests are tagged `[Trait("Category", "E2E")]` and use ephemeral
ports allocated from a base outside the other workflow fixtures'
ranges so parallel workers do not collide.

Source: MTConnectDevices_2.7.xsd line 8161 declares
`ComponentConfigurationType` with
`<xs:element ref="AbstractConfiguration" minOccurs="0"
maxOccurs="unbounded"/>`; vendor extensions substitute into the
`AbstractConfiguration` group.
Add four new assertions to the vendor-extension round-trip pinning surface:

- Interface contract — IConfiguration.VendorExtensions getter reflects the
  value set through the concrete Configuration.VendorExtensions setter,
  pinning the polymorphic projection.
- Mixed content — a Configuration carrying a standard Motion child AND a
  vendor extension round-trips through write + read with both slots
  populated. Exercises the branch of XmlConfiguration.ToConfiguration
  where strongly-typed children and vendor-namespaced children coexist.
- Nested attribute preservation — extensions with attributes on both the
  root element AND nested descendants round-trip verbatim through the
  WriteRaw / XElement.Parse(LoadOptions.PreserveWhitespace) pipeline.
- Distinct vendor namespaces — two extensions from different vendor
  namespaces keep their bindings and are distinguished by fully-qualified
  XName, not local name alone.
Lands the coverage-FLOOR tests missing from the initial PR TrakHound#223 surface:

- XmlConfigurationBranchCoverageTests: pins the WriteXml null-configuration
  guard (both outputComments values), the outputComments=true comment
  path, and the ToConfiguration zero-length + all-null + read-side mixed
  branches on the XmlElement[] projection.
- ConfigurationVendorExtensionsEdgeCaseTests: default-namespace (unprefixed)
  extensions, attribute-only extensions, text-only extensions, unicode
  payloads (combining marks + RTL + astral surrogate pairs), predefined
  entity round-tripping, dense Configuration with Motion + SensorConfiguration
  + vendor extension coexistence, three-vendor namespaces preserved, and
  three-level nesting depth.
- ConfigurationVendorExtensionsXsdStrictValidationTests: crown-jewel XSD-1.0
  strict validation — emits a probe envelope via the production
  XmlDevicesResponseDocument pipeline, loads the v2.7 Devices XSD (via
  XsdPreprocessor for XSD 1.1 stripping) alongside a synthetic vendor XSD
  declaring the element as substitutionGroup='mtc:AbstractConfiguration',
  and asserts zero validation errors. Negative twin rejects a foreign
  element that lacks a substitution declaration.
- ConfigurationVendorExtensionsJsonKnownLimitTests +
  ConfigurationVendorExtensionsCppAgentJsonKnownLimitTests: pin the current
  known limitation that both JSON dialects silently drop VendorExtensions
  (the XML formatter is the sole surface honoring the vendor-substitution
  contract today), so any future first-class JSON representation must be a
  deliberate edit to these tests plus the interface docblock.

Verified green on bluefin:
- MTConnect.NET-XML-Tests: 131 passed / 0 failed
- MTConnect.NET-JSON-Tests: 68 passed / 0 failed
- MTConnect.NET-JSON-cppagent-Tests: 367 passed / 0 failed
Explicitly pin DtdProcessing.Prohibit and XmlResolver = null when
parsing each captured vendor-extension element's OuterXml back into an
XElement, mirroring the repo-wide defense-in-depth convention already
established in XmiDeserializer.FromXml/FromFile.

.NET 6+ already defaults XmlResolver to null and disables DTD
processing on the outer document read, and a DOCTYPE cannot legally
appear inside a captured element's OuterXml, so this closes a
consistency gap rather than an exploitable path today — but it
survives a future framework downgrade or accidental restoration of
XmlUrlResolver, and keeps every XML parse entry point in the repo on
the same explicit hardening idiom.

Found by the security-audit pass of the PR TrakHound#223 dime review cycle.
…ling

IConfiguration.VendorExtensions' class remarks misquoted
MTConnectDevices_2.7.xsd's ComponentConfigurationType content model as
minOccurs="0" on the AbstractConfiguration ref; the schema
(tests/Compliance/.../v2_7/MTConnectDevices_2.7.xsd:8168) declares
minOccurs='1' — once <Configuration> is present it must contain at
least one AbstractConfiguration-substitution child.

Also sweeps the handful of BrE spellings ("unrecognised", "behaviour",
"serialise(s/d)", "deserialiser") introduced by this PR's own commits
into AmE, including two test method names, matching the repo-wide
AmE convention already applied everywhere else in the codebase.

Found by the code-review pass of the PR TrakHound#223 dime review cycle.
@ottobolyos
ottobolyos force-pushed the feat/configuration-vendor-extensions branch from e810192 to f9bb2fc Compare August 27, 2026 02:49
Per §1.0d-trigies-vicies-octies (AmE mandatory across every PR-associated surface),
the residual BrE 'Serialise' in the VendorExtensions serialization comment is
converted to 'Serialize'. No behavior change; comment-only edit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant