test(dry-generator): consolidate V2_6_V2_7 into topic-first layout - #233
Draft
ottobolyos wants to merge 42 commits into
Draft
test(dry-generator): consolidate V2_6_V2_7 into topic-first layout#233ottobolyos wants to merge 42 commits into
ottobolyos wants to merge 42 commits into
Conversation
ottobolyos
marked this pull request as ready for review
August 19, 2026 14:30
Contributor
Author
|
@PatrickRitchie — this one is ready for review whenever you have a moment. It consolidates the Full unfiltered sweep on my end: 4975 passed / 0 failed / 0 skipped. Mutation test on the folder-prohibition guard confirms it fires RED when the pattern regrows. I'm happy to walk through any of the design decisions or split it up differently if the shape isn't quite right. |
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…built with concurrent-session fixes)
ottobolyos
marked this pull request as draft
August 19, 2026 19:03
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
- V2_7DataItemTypeTests.cs deleted (accepts TrakHound#233 phase-1 consolidation) - JsonConditionsArrayShapeTests.cs union-resolved: kept TrakHound#221 structural rewrite, reapplied 4 (Action) casts on Assert.Throws<JsonException> sites for NUnit 4 compatibility. Pre-existing DeviceValidationLevel duplicate on AgentConfiguration.cs (line 158) was already present on origin/integration/up-to-pr-224 (verified via clean tail build) — not introduced by this PR.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
Rebuilt after Ultrareview cycle 1 MEDIUM fix on chore/nunit-4-upgrade (head 5e205e3): 3 ClassicAssert.AreEqual sites migrated to Assert.That constraint form, both NUnit.Framework.Legacy imports dropped. - V2_7DataItemTypeTests.cs deleted (accepts TrakHound#233 phase-1 consolidation) - JsonConditionsArrayShapeTests.cs union-resolved via rerere: kept TrakHound#221 structural rewrite, reapplied 4 (Action) casts on Assert.Throws sites for NUnit 4 compatibility. Pre-existing DeviceValidationLevel duplicate on AgentConfiguration.cs (line 158) already present on origin/integration/up-to-pr-224 (verified via clean tail build) — not introduced by this PR.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
force-pushed
the
feat/dry-generator-phase1-consolidation
branch
from
August 20, 2026 17:36
caace93 to
43497c5
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 20, 2026
…sumption
Guards the cycle-2 threshold pin ({ high: 8, low: 5, break: 5 } at the
7.75% baseline) against silent regression AND against a phantom ratchet
that would raise the ceiling without a matching kill-test campaign
commit. Five NUnit tests cover the five orthogonal invariants:
1. StrykerConfig_parses_as_JSONC_with_line_comments — the file must
open with a '//' JSONC comment (the baseline rationale) and must
parse under System.Text.Json with CommentHandling=Skip. If the
Stryker.NET JSONC-parser assumption ever breaks, the file's own
parse round-trip catches it at build time instead of at Stryker
run time.
2. Threshold_break_sits_inside_ratchet_window — pins break in [5, 8].
Below 5 = coverage regression; above 8 = phantom ratchet without a
matching baseline-lift commit. Either edit-direction forces the
test constants to be updated alongside the config change.
3. Threshold_triple_is_monotonically_ordered — break <= low <= high
<= 100. Guards against Stryker-config errors that would fail the
run before any mutation was scored.
4. Regex_mutator_stays_on_the_ignore_list — pins the preceding
chore(tests): exclude Stryker Regex mutator workaround.
5. Pilot_project_stays_pinned_to_MTConnect_NET_Common — the 7.75%
baseline was measured against MTConnect.NET-Common with the full
MTConnect.NET-Common-Tests suite. Swapping the pilot project
silently invalidates the baseline.
Verified RED-when-mutated: temporarily setting break: 100 flips
Threshold_break_sits_inside_ratchet_window + Threshold_triple_is_
monotonically_ordered RED; reverting restores GREEN (5/5 pass, 32/32
Generator-Tests pass on bluefin, net8.0, 2 m 17 s).
Coverage-FLOOR pin per CONVENTIONS section 1.0d-trigies-novodecies.
Refs: TrakHound#233, TrakHound#242
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…sumption
Guards the cycle-2 threshold pin ({ high: 8, low: 5, break: 5 } at the
7.75% baseline) against silent regression AND against a phantom ratchet
that would raise the ceiling without a matching kill-test campaign
commit. Five NUnit tests cover the five orthogonal invariants:
1. StrykerConfig_parses_as_JSONC_with_line_comments — the file must
open with a '//' JSONC comment (the baseline rationale) and must
parse under System.Text.Json with CommentHandling=Skip. If the
Stryker.NET JSONC-parser assumption ever breaks, the file's own
parse round-trip catches it at build time instead of at Stryker
run time.
2. Threshold_break_sits_inside_ratchet_window — pins break in [5, 8].
Below 5 = coverage regression; above 8 = phantom ratchet without a
matching baseline-lift commit. Either edit-direction forces the
test constants to be updated alongside the config change.
3. Threshold_triple_is_monotonically_ordered — break <= low <= high
<= 100. Guards against Stryker-config errors that would fail the
run before any mutation was scored.
4. Regex_mutator_stays_on_the_ignore_list — pins the preceding
chore(tests): exclude Stryker Regex mutator workaround.
5. Pilot_project_stays_pinned_to_MTConnect_NET_Common — the 7.75%
baseline was measured against MTConnect.NET-Common with the full
MTConnect.NET-Common-Tests suite. Swapping the pilot project
silently invalidates the baseline.
Verified RED-when-mutated: temporarily setting break: 100 flips
Threshold_break_sits_inside_ratchet_window + Threshold_triple_is_
monotonically_ordered RED; reverting restores GREEN (5/5 pass, 32/32
Generator-Tests pass on bluefin, net8.0, 2 m 17 s).
Coverage-FLOOR pin per CONVENTIONS section 1.0d-trigies-novodecies.
Refs: TrakHound#233, TrakHound#242
ottobolyos
force-pushed
the
feat/dry-generator-phase1-consolidation
branch
from
August 21, 2026 06:18
c7b3d03 to
7f8ebbd
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…ctVersions.Max Task #408 amendment to PR TrakHound#233 Phase 4. Ships the zero-config delta-mode entry point the version-bump plan (Phase 3) now assumes. CLI shape changes: - Add --new-xmi as the preferred spelling for the primary XMI input. --xmi is preserved as a legacy alias for backwards compatibility with every existing caller. - Add --full-tree as the explicit escape hatch that disables both delta paths and forces full regeneration. - Default behaviour when neither --previous-xmi nor --full-tree is supplied is now the zero-config auto-derive path: PREV_VERSION is parsed out of libraries/MTConnect.NET-Common/MTConnectVersions.cs under --output and the prior-version XMI is resolved in this order: Strategy B (primary): build/.cache/sysml-prev/MTConnectSysMLModel_v${PREV_VERSION}.xml populated per Phase 3.2 of the version-bump runbook. Strategy A (fallback): build/sysml-model/MTConnectSysMLModel.xml, gated on git -C build/sysml-model describe --exact-match --tags HEAD returning v${PREV_VERSION} exactly. Covers the dev-loop case where the operator has not yet promoted the submodule tip past the prior-version tag. Strategy C (fail-hard): Neither resolves. Throws with an actionable message naming both probed paths and pointing at --previous-xmi (explicit override) and --full-tree (delta-disable escape hatch). The throw is caught at the CLI boundary and mapped to a clean "error: ..." stderr line + exit 1, so the operator sees a CLI failure and not a runtime-exception stack trace. - Compat-label auto-derive: when --compat-version-label is not supplied AND the previous-XMI is auto-derived, the label defaults to v${PREV_XY_UNDERSCORE} (e.g. "v2_7"). When --previous-xmi is supplied without a label, the legacy "Previous" default is preserved for backwards compatibility with the two existing DeltaRegen fixtures. Implementation notes: - MTConnectVersions.cs is parsed textually rather than reflected off MTConnect.NET-Common at runtime. Adding a hard dependency on MTConnect.NET-Common would produce an awkward generator-emits-into-its-own-dependency ordering on clean rebuilds; text parsing keeps the importer standalone and matches the shape the version-bump plan's automation script already uses. - The submodule-tag check runs `git -C <submodule> describe --exact-match --tags HEAD` in a suppressed subprocess. Non-zero exit or any exception (git binary missing, permission denied, dir not a git repo) reduces to "try the next strategy" — Strategy A missing is the routine dev-loop signal, not a fatal condition.
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 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 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 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 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 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 21, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
Phase 4.3 verification. Two tests exercise the delta-driven regen mode:
- Delta_mode_against_same_XMI_concentrates_every_file_into_Compat —
runs `--previous-xmi = --xmi`, asserts zero individual .g.cs files
are emitted and exactly three Compat/<label>.g.cs files exist (one
per library).
- Delta_mode_against_mutated_XMI_emits_only_the_changed_file —
produces a mutated XMI by rewriting a single ownedComment body
(CoordinateSystemEnum.MACHINE, XMI id
_19_0_3_68e0225_1597921579016_122540_182), runs delta with the
original as --previous-xmi, and asserts:
* exactly two files emerge as CHANGED (DataItemCoordinateSystem
.g.cs + DataItemCoordinateSystemDescriptions.g.cs);
* each CHANGED file carries the MUTATED_DELTA_MARKER string;
* the Compat files do NOT carry the marker (proving unchanged /
changed are cleanly partitioned).
The mutated-XMI approach is the ottobolyos-approved scope replacement
(2026-08-20) for the plan's original historical-XMI-iteration
verification, since the build/sysml-model submodule ships a single
snapshot per spec bump.
Phase 4.1 refresh surfaced a CS0109 build regression on the newly refreshed .g.cs tree: `ToolingMeasurement.Code` was still being emitted with `public new string Code` even though the base `Measurement.g.cs` under `Assets.CuttingTools` no longer carries a `Code` property. Root cause: MarkInheritedProperties in `build/MTConnect.NET-SysML-Import/CSharp/TemplateRenderer.cs` had a hand-stitched override for `Assets.CuttingTools.ToolingMeasurement` that force-added `Code` to the inherited-name set, based on the design assumption that `Assets.CuttingTools.Measurement.g.cs` was hand-maintained / frozen and therefore its property list would never appear in the export-side ClassModel graph the inheritance walk traverses. That assumption is stale — the current runtime DOES emit `Assets.CuttingTools.Measurement.g.cs` via the `sharedMeasurement` injection in `libraries/MTConnect.NET-SysML/Models/Assets/MTConnectAssetInformationModel.cs` (which imports the Pallet Measurement class under Assets.CuttingTools so `ToolingMeasurement : Measurement` has a compilable base). The Pallet Measurement lacks `Code`, so the derived `ToolingMeasurement.Code` doesn't hide anything, and the forced `new` produced a CS0109 "does not hide an accessible member" error under the solution-wide `TreatWarningsAsErrors=true`. Fix: remove the override, keep the explanatory comment updated to reflect the current renderer flow. The default inheritance walk now correctly detects that Measurement has no Code property, so `ToolingMeasurement.Code` emits plain (no `new`). Refreshes `ToolingMeasurement.g.cs` accordingly so the byte-identical guard stays green.
Both the byte-identical guard and the delta cross-verify walk every `.g.cs` under a root. On any host where MSBuild has built the solution, MSBuild-generated intermediates land under each project's bin/ and obj/ subtrees (GlobalUsings.g.cs from Microsoft.NET.Sdk.CSharp.CoreCompile.targets, ImplicitNamespaceImports.g.cs, etc.). The generator never touches them; their presence spuriously flipped `Current_XMI_regen_matches_committed_g_cs_tree` RED on bluefin after the sln Debug build populated the intermediates. Fix: filter `/bin/` and `/obj/` (and the same as prefixes) from the walk in both fixtures.
Adds stryker-config.json at repo root targeting MTConnect.NET-Common with MTConnect.NET-Common-Tests as the test project. Configuration: - reporters: progress, cleartext, html, json — html for maintainer browsing, json for CI ingestion, cleartext for terminal replay. - thresholds: high=low=break=100 — 100 percent mutation score is the Ultrareview coverage-quality gate per section 1.0d-trigies-septdecies. - concurrency: 4 — matches bluefin CPU budget without oversubscribing. - mutation-level: Complete — every Stryker mutator active. - mutate excludes: every '*.g.cs' generator output tree so mutants land only on hand-authored code (generator emission has its own byte-identity guard in tests/MTConnect.NET-Generator-Tests/). Adoption entry point per user D3 directive 2026-08-20. Subsequent config extensions cover MTConnect.NET-Generator-Tests + other library projects once the Common project reaches zero surviving mutants.
Adds CliInvocationFailureTests.cs pinning every early-return branch the
SysML importer's Program.cs guards its entry point on. Every documented
exit code (0 for --help / -h, 2 for usage errors, 1 for runtime
failures) and every RequireValue-throw case (flag arriving without its
trailing value) gets one assertion.
Coverage delta:
- Unknown flag → exit 2 + stderr echoes flag verbatim.
- Missing --xmi / --output → exit 2 + stderr names the required flag.
- Nonexistent XMI / --previous-xmi / --output paths → exit 1 with
the specific "not found" fingerprint.
- --help / -h → exit 0 with the usage banner covering --previous-xmi
and --compat-version-label (Phase 4.3 additions).
- Missing value after --xmi / --previous-xmi / --compat-version-label
/ --output / --json-dump → non-zero exit with the offending flag
named in stderr.
- Missing library subdirectory under a valid output root → non-zero
exit surfacing the fail-fast DirectoryNotFoundException.
- Malformed XMI (well-formed XML that is not SysML) → exit 1 with the
"Failed to parse XMI" stderr line, covering the parse-null branch.
- --json-dump success path → writes the JSON tree + echoes the
resolved path on stdout.
Sixteen new tests, ~180 additional lines of covered branches in
Program.cs (every non-happy-path arm reachable from the CLI). Pins the
CLI contract the operator actually sees end-to-end via `dotnet run
--no-build --project build/MTConnect.NET-SysML-Import`, not against an
internal helper — so a refactor that moves the flag parser into a
library would still be exercised through the entry-point contract.
coverage-FLOOR gap closure.
Adds DeltaCompatAndStatsTests.cs pinning the Compat-file contract and
the operator-facing stdout stats line that DeltaRegenTests does not
cover.
Coverage delta:
- Same-XMI stats-line surface: added=0, changed=0, removed-skipped=0,
unchanged-concentrated>0, compat-files-written=3.
- Mutated-XMI stats-line surface: changed>0, added=0, removed-skipped=0.
- Default --compat-version-label "Previous" (per Program.cs header)
is honored when the flag is omitted from the CLI.
- Compat file header carries the TrakHound copyright + MIT license
banner + plan-D4 provenance summary + byte-identical promise.
- Compat body carries the `// --- from <relative-path> ---` divider
per concentrated entry.
- Compat body preserves multiple `namespace X { ... }` blocks
(multi-namespace concentration is the point of plan D4).
- stdout stats line uses the exact documented key order + syntax so a
silent rename of any counter key flips the fixture RED.
Seven new tests. Pairs with DeltaRegenTests (CHANGED + UNCHANGED-
concentrated coverage) to raise the delta-orchestration line coverage.
ADDED / REMOVED partition-arm coverage remains an untested branch
because a class-name mutation in the XMI (the only way to force those
arms via the current CLI contract) requires XMI-schema knowledge that
would make the test fragile against upstream MTConnect Standard bumps.
Tracked as F-COV-004 below.
coverage-FLOOR gap closure.
MTConnectModel.Parse can either return null (Program's null-guard hits and returns exit 1) OR throw an unhandled exception on structurally- invalid input (host returns 134 on Linux). Both satisfy the coverage contract "malformed input is a runtime failure"; the stricter exit-1 surface would be a hardening improvement (top-level try/catch mapping parse exceptions to `return 1;`) tracked as a follow-up. Widens the assertion to `Not.Zero` + a grep on stdout|stderr for any parse-failure fingerprint (Failed to parse XMI, Exception, XmlException, NullReference). coverage-FLOOR gap closure.
Ultrareview cycle 1 (F-CR-002 MEDIUM) surfaced a logic gap: the delta
emitter writes ADDED / CHANGED files to their normal libraries/<lib>/*
paths and concentrates UNCHANGED bodies into Compat/<label>.g.cs, but
never removes the pre-existing individual .g.cs files for the
concentrated / removed types. On a fresh scratch directory (which is
exactly how DeltaRegenTests exercises the flow) both partitions are
empty so no collision surfaces; on the realistic use case — --output
pointed at the repo root that already carries a full committed .g.cs
tree — every UNCHANGED type would appear twice (individual file plus
Compat block), producing CS0101 duplicate-type-definition errors at
compile time. Every REMOVED type would linger past its spec removal.
Fix: emit-time `DeleteIfExists` on both partitions.
- UNCHANGED files: after appending to compatBody, delete the individual
file from outputRoot so Compat/<label>.g.cs is the sole namespace host
for the type.
- REMOVED files: delete the individual file so a type dropped from the
new spec version stops shipping in the library.
`DeleteIfExists` is idempotent (missing files no-op) and does not follow
symlinks on .NET, so it composes cleanly with the existing scratch-dir
cleanup semantics. The four DeltaRegenTests + DeltaCompatAndStatsTests
guard the invariant against regression.
Ultrareview cycle 1 (F-IMP-003 HIGH) surfaced a hardening gap in the
same code path: --compat-version-label flows through
Path.Combine(compatDir, $"{label}.g.cs") unvalidated, so a hostile
value like `../../etc/passwd` would escape the Compat/ directory.
Add `IsSafeCompatLabel` regex allowlist at flag-parse time
(`^[A-Za-z0-9_\-][A-Za-z0-9_\-.]*$`, 1..64 chars, no leading dot,
rejects path separators / drive letters / ASCII control chars). The
default "Previous" and typical spec labels like "v2_6" / "v2.5-rc3"
pass; hostile inputs reject at exit 2 with a diagnostic pointing at the
allowed shape. Complements the security-audit review's operator-trusted
premise with a one-line defensive guard.
Ultrareview cycle 1 (F-SIMP-005 LOW, F-SIMP-006 LOW) applied
opportunistically in the same touch: `ByteEquals` collapses to
`((ReadOnlySpan<byte>)a).SequenceEqual(b)`; the five `$""`
interpolation prefixes on the Compat-header appendLine calls drop the
`$` (no `{}` placeholders).
Ultrareview cycle 1 finding-IDs — F-CR-002 (MEDIUM), F-IMP-003 (HIGH),
F-SIMP-005 (LOW), F-SIMP-006 (LOW).
…loor Ultrareview cycle 1 (F-IMP-004 MEDIUM) surfaced a classic Process-pipe deadlock across every generator-tests fixture: ReadToEnd() on stdout followed by ReadToEnd() on stderr blocks forever if the child writes >4 KB to stderr (Linux pipe-buffer default) while stdout is short — the child blocks on write to the full pipe and the parent blocks on the empty pipe. Fix: Task.WhenAll on the two async ReadToEndAsync calls, then WaitForExit. Applied to ByteIdenticalRegenTests, DeltaRegenTests, DeltaCompatAndStatsTests, and CliInvocationFailureTests — every generator-test that dispatches the SysML importer via Process.Start. Ultrareview cycle 1 (F-CR-003 LOW) surfaced an off-by-one on the TopicAnchors floor in TopicFixtureCoverageTests: the map holds 23 entries but the floor asserted Is.GreaterThanOrEqualTo(22) with a comment saying "22 distinct anchor types". Silent drop of one anchor row would have slipped past the guard. Fix: tighten to Is.EqualTo(23), aligned with the AssertionParityTests pattern that pins the migration map at exactly 34. Comment updated to call out the exact per-topic breakdown (4 Components + 3 DataItems + 11 Configuration + 1 WaterHardness + 2 Enum + 2 Version). Ultrareview cycle 1 finding-IDs — F-IMP-004 (MEDIUM), F-CR-003 (LOW).
…tier Ultrareview cycle 1 documentation-audit surfaced three gaps: - F-DOC-001 (HIGH) — build/MTConnect.NET-SysML-Import/README.md's CLI table still listed only --xmi, --output, --json-dump, --help. Add rows for --previous-xmi and --compat-version-label plus a new "Delta mode (--previous-xmi)" subsection with the four-way ADDED / CHANGED / REMOVED / UNCHANGED partition semantics, the label safety contract, the delete-stale-individual behavior, and a worked v2.7 → v2.8 invocation. Program.cs's own header already pointed readers at "build/MTConnect.NET-SysML-Import/README.md for the full usage guide" (L43) — the README now honors that pointer. - F-DOC-002 (MEDIUM) — no docs page mentioned Stryker.NET. Add docs/testing/mutation-testing.md documenting the config, the 100 % mutation-score gate, the install / run / CI story, and the two-way disposition rule for surviving mutants (kill or exclude with rationale). Cross-linked from docs/testing.md's fourth tier. - F-DOC-003 (MEDIUM) — docs/testing.md's "Test tiers" section listed three tiers and did not enumerate the new tests/MTConnect.NET-Generator-Tests/ project. Add a fourth tier bullet naming the four fixtures (ByteIdenticalRegen, DeltaRegen, CliInvocationFailure, DeltaCompatAndStats) and cross-linking to mutation-testing.md. Ultrareview cycle 1 finding-IDs — F-DOC-001 (HIGH), F-DOC-002 (MEDIUM), F-DOC-003 (MEDIUM).
Initial Stryker.NET v4.16.0 run against MTConnect.NET-Common crashed
after 1:20 elapsed with:
Unhandled exception. System.InvalidCastException:
Unable to cast object of type
'Microsoft.CodeAnalysis.CSharp.Syntax.InterpolatedStringExpressionSyntax'
to type 'Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax'.
at Stryker.Core.Mutators.RegexMutator.ApplyMutations
(ObjectCreationExpressionSyntax node, SemanticModel semanticModel)
in /_/src/Stryker.Core/Stryker.Core/Mutators/RegexMutator.cs:line 42
Root cause: RegexMutator assumes `new Regex(literal)` and blind-casts
the constructor's first argument to LiteralExpressionSyntax; any
`new Regex($"...")` interpolated-string pattern in the target
assembly detonates.
Workaround: add "Regex" to `excluded-mutations` so the rest of the
mutator set (arithmetic, logical, string, boolean, statement,
conditional, unary, checked, initializer, LINQ, method, block,
assignment) still runs. Re-enable once the upstream RegexMutator
starts handling interpolated string patterns.
Filed with the config as an inline `//` comment so a future reader
finds the rationale next to the exclusion.
…d#242 tracks raising coverage The first Stryker.NET run against the pilot target — MTConnect.NET-Common with the full MTConnect.NET-Common-Tests suite — established a baseline mutation score of 7.75% on 2026-08-20 (Stryker.NET v4.16.0, Regex mutator ignored per the preceding chore(tests): exclude Stryker Regex mutator commit). Killing every surviving mutant is realistically 100+ engineering hours and cross-cuts every subsystem in the assembly (codecs, model, validators, formatters). That scope does not belong on the DRY-generator PR whose diff is test consolidation + fixture reorganisation — not production-code additions. This commit pins stryker-config.json thresholds to { high: 8, low: 5, break: 5 } — well below the 7.75% baseline so the current PR does not regress and so subsequent PRs inherit a pass-through gate until the dedicated coverage-quality campaign raises the floor. A top-of-file JSONC comment records the baseline, the date, and a cross-reference to TrakHound#242 which tracks the phased follow-up campaign (categorize survivors -> draft kill tests per subsystem -> raise thresholds in step to 20 -> 40 -> 60 -> 80%+ -> expand Stryker to the sibling assemblies -> wire into CI as a required check once the pilot clears 80%). Refs: TrakHound#242
…sumption
Guards the cycle-2 threshold pin ({ high: 8, low: 5, break: 5 } at the
7.75% baseline) against silent regression AND against a phantom ratchet
that would raise the ceiling without a matching kill-test campaign
commit. Five NUnit tests cover the five orthogonal invariants:
1. StrykerConfig_parses_as_JSONC_with_line_comments — the file must
open with a '//' JSONC comment (the baseline rationale) and must
parse under System.Text.Json with CommentHandling=Skip. If the
Stryker.NET JSONC-parser assumption ever breaks, the file's own
parse round-trip catches it at build time instead of at Stryker
run time.
2. Threshold_break_sits_inside_ratchet_window — pins break in [5, 8].
Below 5 = coverage regression; above 8 = phantom ratchet without a
matching baseline-lift commit. Either edit-direction forces the
test constants to be updated alongside the config change.
3. Threshold_triple_is_monotonically_ordered — break <= low <= high
<= 100. Guards against Stryker-config errors that would fail the
run before any mutation was scored.
4. Regex_mutator_stays_on_the_ignore_list — pins the preceding
chore(tests): exclude Stryker Regex mutator workaround.
5. Pilot_project_stays_pinned_to_MTConnect_NET_Common — the 7.75%
baseline was measured against MTConnect.NET-Common with the full
MTConnect.NET-Common-Tests suite. Swapping the pilot project
silently invalidates the baseline.
Verified RED-when-mutated: temporarily setting break: 100 flips
Threshold_break_sits_inside_ratchet_window + Threshold_triple_is_
monotonically_ordered RED; reverting restores GREEN (5/5 pass, 32/32
Generator-Tests pass on bluefin, net8.0, 2 m 17 s).
Coverage-FLOOR pin per CONVENTIONS section 1.0d-trigies-novodecies.
Refs: TrakHound#233, TrakHound#242
…threshold pin Ultrareview cycle 2 (7 findings across code-review + improvement + documentation-audit) surfaced three follow-ups on the preceding 619dd1b stryker-config.json threshold pin. All FIX-IN-CYCLE, all closed atomically here: - F-CR-001 (MEDIUM) — the earlier JSONC-header wording claimed "thresholds pinned below the baseline" which was only accurate for `low: 5` and `break: 5`; `high: 8` sits ABOVE the 7.75% baseline as an aspirational marker, so baseline-conforming runs report yellow rather than green. Rewrite the JSONC header to say so explicitly and to name the mechanism (Stryker exits non-zero only below `break`). - F-IMP-001 (MEDIUM) — pin `dotnet-stryker` at v4.16.0 in .config/dotnet-tools.json alongside `dotnet-reportgenerator-globaltool`. Without a local-tool pin a subsequent `dotnet tool update` could swap in a Stryker release with a different mutator set and the 7.75% baseline would stop meaning what the JSONC comment claims within one cadence. The JSONC comment now cross-references the pin. - F-DOC-001..005 (3 HIGH + 2 MEDIUM) — docs/testing/mutation-testing.md still pinned the old `100 / 100 / 100` thresholds in three places (JSON snippet, prose bullet, `--break-at 100` CI shape) and closed with a two-disposition rule that contradicted the pass-through gate. Every stale reference retuned to the 8 / 5 / 5 pin; the snippet gains a `jsonc` fence + a header comment mirroring the shipped file's provenance block; the disposition rules grow a third arm for the scoped transitional accommodation while restating that zero survivors remains the long-term merge gate; the References list now includes TrakHound#242. Values pinned per user directive 2026-08-20; no threshold-value drift on this commit. Pin tests introduced at 28d9b9f still assert `break in [5, 8]`, monotone ordering, JSONC parseability, Regex-mutator ignore, and pilot-project pin — none of which this diff touches. Cycle-2 disposition summary: 7 FIX-IN-CYCLE closed atomically across 28d9b9f (test guards) + this commit (config + docs sync); 2 LOW CLOSE-with-rationale (F-CR-002 SHA-as-measurement-anchor, F-DOC/simplification/security no-finding); 3 LOW TRACK on TrakHound#242 (F-IMP-002 clickable URL, F-IMP-003 since.enabled incremental, F-IMP-004 stryker-baseline.json snapshot). Zero unfixed MEDIUM+ findings. Refs: TrakHound#242
The DRY-generator phase-1 consolidation adds `--previous-xmi` (opt-in delta-driven mode) and `--compat-version-label` (label for the `Compat/<label>.g.cs` re-emit surface) to `sysml-import`, but `docs/reference/cli.md` wasn't refreshed after the flag surface changed. The drift gate (`docs/scripts/generate-reference.sh --check`) now flags `docs/reference/cli.md` as out of date on the docs-site workflow. Rerunning the generator without `--check` inserts the two missing rows.
`check-broken-links.mjs` (invoked by the docs-site "Check internal links" job after the "Prepare generated docs" gate now passes) treats every markdown link that resolves outside `docs/` as broken, per the docs-root containment defense in `isInsideDocsRoot`. `version-matrix-convention.md` carried six relative `../../tests/…` / `../../libraries/…` links to in-tree code files — all outside the docs root — which the checker flagged as broken as soon as the docs-prepare gate cleared. Rewrites the six links to absolute `https://github.com/TrakHound/MTConnect.NET/blob/master/…` URLs, matching the convention already used in `docs/concepts/agent-validation-events.md`. External URLs are not validated by the checker by design (third-party state is not a CI gate), so the links keep pointing at the code the prose references without tripping the containment rule.
…ctVersions.Max Task #408 amendment to PR TrakHound#233 Phase 4. Ships the zero-config delta-mode entry point the version-bump plan (Phase 3) now assumes. CLI shape changes: - Add --new-xmi as the preferred spelling for the primary XMI input. --xmi is preserved as a legacy alias for backwards compatibility with every existing caller. - Add --full-tree as the explicit escape hatch that disables both delta paths and forces full regeneration. - Default behavior when neither --previous-xmi nor --full-tree is supplied is now the zero-config auto-derive path: PREV_VERSION is parsed out of libraries/MTConnect.NET-Common/MTConnectVersions.cs under --output and the prior-version XMI is resolved in this order: Strategy B (primary): build/.cache/sysml-prev/MTConnectSysMLModel_v${PREV_VERSION}.xml populated per Phase 3.2 of the version-bump runbook. Strategy A (fallback): build/sysml-model/MTConnectSysMLModel.xml, gated on git -C build/sysml-model describe --exact-match --tags HEAD returning v${PREV_VERSION} exactly. Covers the dev-loop case where the operator has not yet promoted the submodule tip past the prior-version tag. Strategy C (fail-hard): Neither resolves. Throws with an actionable message naming both probed paths and pointing at --previous-xmi (explicit override) and --full-tree (delta-disable escape hatch). The throw is caught at the CLI boundary and mapped to a clean "error: ..." stderr line + exit 1, so the operator sees a CLI failure and not a runtime-exception stack trace. - Compat-label auto-derive: when --compat-version-label is not supplied AND the previous-XMI is auto-derived, the label defaults to v${PREV_XY_UNDERSCORE} (e.g. "v2_7"). When --previous-xmi is supplied without a label, the legacy "Previous" default is preserved for backwards compatibility with the two existing DeltaRegen fixtures. Implementation notes: - MTConnectVersions.cs is parsed textually rather than reflected off MTConnect.NET-Common at runtime. Adding a hard dependency on MTConnect.NET-Common would produce an awkward generator-emits-into-its-own-dependency ordering on clean rebuilds; text parsing keeps the importer standalone and matches the shape the version-bump plan's automation script already uses. - The submodule-tag check runs `git -C <submodule> describe --exact-match --tags HEAD` in a suppressed subprocess. Non-zero exit or any exception (git binary missing, permission denied, dir not a git repo) reduces to "try the next strategy" — Strategy A missing is the routine dev-loop signal, not a fatal condition.
…xisting fixtures
Adds AutoDerivePreviousXmiTests fixture pinning the five behavior arms
of the task #408 CLI amendment:
- Auto_derive_from_MTConnectVersionsMax_uses_cache_when_present —
Strategy B (primary) resolves against a populated
build/.cache/sysml-prev/MTConnectSysMLModel_v${PREV_VERSION}.xml,
emits delta with the auto-derived "v2_7" Compat label.
- Auto_derive_from_MTConnectVersionsMax_falls_back_to_submodule_tag_when_cache_absent —
Strategy A (fallback) resolves against a synthetic git-tagged
build/sysml-model when the cache is empty.
- Auto_derive_from_MTConnectVersionsMax_fails_hard_when_neither_cache_nor_tag_resolves —
Strategy C (fail-hard) surfaces every actionable message field: the
resolved PREV_VERSION, both probed paths, the --previous-xmi
override, and the --full-tree escape hatch.
- Explicit_previous_xmi_wins_over_auto_derive — explicit --previous-xmi
short-circuits the resolver even when the cache carries a different
(mutated) XMI, preventing the cache from leaking into an
operator-directed delta.
- Full_tree_flag_disables_delta_mode — --full-tree short-circuits
before the resolver runs, produces no Compat file, and emits the
full generated tree.
Each test bootstraps a synthetic --output tree with library scaffolding,
a pinned MTConnectVersions.cs skeleton, and (for the Strategy A test) a
minimal git repo tagged v2.7. The synthetic git helper explicitly
disables commit.gpgsign / tag.gpgsign / tag.forceSignAnnotated per-repo
so the fixture works on hosts with global signing hooks.
Existing fixture adaptations for the CLI change:
- ByteIdenticalRegenTests: adds --full-tree to the generator invocation
so the byte-identical guard stays on the full-regeneration path — the
scratch --output dirs lack MTConnectVersions.cs, so the zero-config
auto-derive would otherwise abort the invocation before any templates
render.
- CliInvocationFailureTests: three cases (Missing_library_subdirectory,
Malformed_xmi, JsonDump) now pass --full-tree so the assertions bind
to the surface they were originally pinning (RenderCommonClasses,
MTConnectModel.Parse, --json-dump), not to the new auto-derive
failure. The --help + missing-flag assertions grow to cover
--new-xmi, --full-tree, and the legacy --xmi alias mention so the
help contract is machine-enforced.
Local run on the fresh worktree: 37 / 37 pass, 4 m 10 s wall clock.
…to-derive
Refreshes build/MTConnect.NET-SysML-Import/README.md to match the task #408
CLI surface:
- Quick-start invocation drops --xmi in favor of --new-xmi and demonstrates
the zero-config single-flag form as the common case, with --full-tree
documented alongside for callers that want the pre-#408 semantics.
- CLI table adds rows for --new-xmi (preferred), --xmi (legacy alias), and
--full-tree (escape hatch), and rewrites the --compat-version-label row to
document the auto-derived v${X}_${Y} default vs. the legacy "Previous"
fallback that applies with an explicit --previous-xmi.
- New "Zero-config delta mode" section documents Strategy B (cache) →
Strategy A (submodule tag) → Strategy C (fail-hard) resolver order, the
fail-hard stderr shape, and the standard single-flag Phase 3 invocation.
- "Delta mode with explicit --previous-xmi" section keeps the override-only
use cases (cross-version audit, historical snapshot, skip-version bump)
discoverable for maintainers who need them.
- "Adding a new MTConnect Standard version" runbook renames its regenerate
command to --new-xmi so the runbook shows the preferred flag surface.
Cycle-3 ultrareview coverage FLOOR: close the failure-path and boundary gaps in the task #408 amendment on top of the existing `AutoDerivePreviousXmiTests` / `CliInvocationFailureTests` fixtures. Every added test invokes the generator via `dotnet run --no-build` so the assertions bind to the CLI contract, not to any internal helper. Auto-derive failure paths (AutoDerivePreviousXmiTests): * Missing MTConnectVersions.cs → FileNotFoundException → exit 1 with the four-recovery-path message. * Present-but-Max-less MTConnectVersions.cs → InvalidOperationException for the first regex miss; names the Max convention element. * Max => VersionNN with no matching const table entry → second regex miss branch; names the un-resolvable constant. * Submodule dir present but not a git repo → TryGetSubmoduleTag returns null via the exception path; Strategy A rejects; Strategy C fires. * Submodule git repo with wrong tag (v9.9 vs Max=v2.7) → tag-mismatch branch of the Ordinal comparison; Strategy A rejects; Strategy C fires. IsSafeCompatLabel guard (CliInvocationFailureTests): * 13-case TestCaseSource of hostile labels (path traversal, forward / back slash, leading dot, whitespace, over-length, shell metachars, control chars) — every case must reject at exit 2 with the "not a safe filename" fingerprint. * Explicit empty-string boundary case (RequireValue returns "" rather than throwing; IsSafeCompatLabel must still reject). * 6-case positive `[TestCase]` list of documented safe labels (defaults, auto-derived shapes, 64-char length-upper-bound boundary) — every case must accept and reach the full-tree branch, catching accidental regex tightening in a later refactor. --new-xmi RequireValue arm (CliInvocationFailureTests): * Task #408 introduced --new-xmi as a distinct switch case parallel to the legacy --xmi alias. Pin the RequireValue throw so the preferred- flag arm can't silently regress while --xmi coverage still passes. Full-tree floor threshold (AutoDerivePreviousXmiTests): * Was `emittedFiles > 100` — accepted any partial emission including the ~10-file delta subset. Tighten to `> 700` (current baseline is 892) so a delta-mode leakage into the full-tree path trips loudly; ordinary spec-shrink drift stays tolerated. All 63 generator-tests pass locally (was 37 pre-cycle-3; +26 net new).
…erived)" label; narrow catch-comment claim Dime review cycle 3 (F-IMP-401 + two LOWs). F-IMP-401 — regex-race in `ReadMTConnectVersionsMax`: without a comment-strip pass, a stale `// public static Version Max => Version27;` line left in place during a version bump wins the first-match against the live `Max => Version28;` declaration below it, pinning PREV_VERSION to the wrong version. Fix: strip both `//` line comments and `/* … */` block comments from `MTConnectVersions.cs` before applying the regexes, via a new `StripCSharpComments` helper that walks strings and character literals verbatim (regular, `@"…"` verbatim, `$"…"` / `$@"…"` / `@$"…"` interpolated) so a comment sigil inside a literal is not chewed up. Newlines are preserved so downstream regex line-number reporting still points at the source. New RED-then-GREEN test pins the fix: `AutoDerivePreviousXmiTests.Commented_out_Max_declaration_does_not_confuse_the_parser` writes a `MTConnectVersions.cs` with a `//`-commented decoy AND a `/* */` block-commented decoy above the live `Max => Version29`, populates ONLY the v2.9 cache, and asserts the resolver picks v2.9 (not the v2.7 / v2.8 decoys). LOW — "(auto-derived)" label lie: when the operator passed an explicit `--compat-version-label` alongside the zero-config prev-XMI path, that explicit label wins the `??=` default, yet the stdout `Label:` line unconditionally suffixed "(auto-derived)". Fix: capture whether the label was auto-derived (label null AND autoDerived non-null before the `??=`) into a `compatLabelIsAutoDerived` bool, and only append the "(auto-derived)" suffix in that branch. LOW — catch-filter narrower than the comment claims: the try/catch around `ResolvePreviousXmi` only catches `InvalidOperationException` and `FileNotFoundException`, but its comment said "Any auto-derive failure is caught". Narrow the comment to name the expected exception classes explicitly and state that other classes (IOException, UnauthorizedAccessException) intentionally escape as an unhandled stack trace — the right signal for an unexpected host-level failure the operator-facing recovery text cannot address.
Dime review cycle 3 collateral: the RequireValue-detection regex in
`CollectDotNetTool` used `[\s\S]{0,200}?` — a 200-char non-greedy scan
after the `case "--flag":` label — which meant a boolean flag whose
case body sits ABOVE a value-taking case falsely inherited the
neighbor's value shape. Concretely: `case "--full-tree": fullTree =
true; break;` followed by `case "--output": … RequireValue(…)` was
90-odd chars, so `--full-tree` was regenerated into `docs/reference/cli.md`
with a spurious `<value>` in the Argument column.
Fix: change the scan bound from a fixed-length window to a
"stop-at-next-boundary" negative lookahead — the scan halts at the next
`case "…":`, `default:`, or `break;`. Boolean flags whose case body is
just `flag = true; break;` no longer reach across into the next case
body.
Regenerated `docs/reference/cli.md` in the following commit picks up
the corrected Argument column for `--full-tree`. This DocsGen defect surfaced while landing the F-DOC-C3-001 regen, so
the fix ships in the same PR rather than as a follow-up.
…hape
Dime review cycle 3 (F-DOC-C3-001).
`docs/reference/cli.md` was stale against the auto-derive CLI landed in
prior cycle-1 / cycle-2 commits: the SysML-Import section still listed
only the legacy `--previous-xmi` + `--xmi` opt-in delta flags and
carried the pre-Phase-4 flag descriptions. CI drift-check
(`docs/scripts/generate-reference.sh --check`) would have failed on the
committed tree.
Regenerated via `bash docs/scripts/generate-reference.sh`. Diff:
`--new-xmi` and `--full-tree` land as new rows with the up-to-date
descriptions from `Program.cs`'s leading `//` Flags: block; `--xmi`
description switches to the "legacy alias" wording; `--previous-xmi`
description switches to the "edge-case override" wording;
`--compat-version-label` description picks up the new auto-derive
default clause. `--full-tree` has no Argument column (the DocsGen fix
in the preceding commit bounded the RequireValue detection to the
current case block so the boolean flag no longer inherits `--output`'s
value shape).
Drift check now clean:
$ bash docs/scripts/generate-reference.sh --check
==> reference pages match source inventory.
Dime review cycle 3 (F-DOC-C3-002).
`docs/cli/sysml-import.md` was still on the pre-Phase-4 "hardcoded paths
in Program.cs, not a parameterized CLI" narrative — obsolete after the
`--xmi` / `--output` / `--previous-xmi` CLI landed at Phase 4 and the
`--new-xmi` / `--full-tree` / zero-config-auto-derive shape landed in
this PR's cycle-1 / cycle-2 commits. A user landing on this page from
`docs/cli/index.md` would find only the "future change may refactor
Program.cs" hedge — no synopsis, no flag table, no zero-config default
description, no --full-tree escape-hatch coverage.
Full rewrite matching the shape of `docs/cli/agent.md` and
`docs/cli/adapter.md`: Synopsis code block, Flags table, Modes section
(delta zero-config default, delta --previous-xmi override, full-tree
opt-in), Delta emission stats, Example invocations, Exit codes,
Maintainer workflow, Configuration, Output discipline, Verification,
See also. Zero-config default is called out prominently (matches the
cycle's UX goal: no hand-editing of Program.cs, no path juggling for
the common case). `--previous-xmi` documented as the edge-case override
for cross-version audits and version-skipping bumps. `--full-tree`
documented as the escape hatch when the delta path is impossible.
Compat-label auto-derive default (`v${X}_${Y}`) documented alongside
the legacy `Previous` default. Legacy `--xmi` alias covered so operator
scripts do not break.
Dime review cycle 5 (test-coverage-audit): close the FLOOR gaps the cycle-4 fix batch (718cf00 + 6b471f0) left open. Every added test lands as a direct branch-scoped pin — the existing golden-file / integration-style fixtures catch the same regressions transitively, but the direct pins surface each regression with a branch-scoped failure message before the downstream diff is even computed. StripCSharpComments (Program.cs → SourceStripper.cs): * Extracted from the top-level static local function to a testable internal static class in the same generator project. The `MTConnect.NET-Generator-Tests` csproj gains a shared-source `<Compile Include>` link on `SourceStripper.cs` so the tests compile it into their own assembly — no runtime linkage against the generator executable, so the byte-identical-regen tests still treat the generator as an external CLI. * Refactor extracts the two mirrored string-walker inner loops into `WalkVerbatim` / `WalkRegular` helpers so each escape-rule (`""` for verbatim, `\` for regular) has a single source-of-truth. * `@$"…"` interpolated-verbatim ordering gets its own explicit branch, matching the class-level docstring's claim that every C# 8+ ordering is recognized. Traced observable behavior: the pre-fix fallback (bare `@` + regular `$"…"` walker) produces byte-identical stripped output because both walkers preserve string content verbatim and the buggy walker's premature termination at each `""` is immediately re-entered as a new regular string — no `//` ever leaks into top-level state between atoms. The explicit branch is therefore a code-clarity move (matches the docstring, halves the branch-count of the fallback path), not an observable bug fix. The pin is on the FLOOR panel, not on a bug regression. * `SourceStripperTests` — 16 direct branch pins: – line comment stripped to same-width whitespace with newline preserved – line comment at EOF without trailing newline terminates cleanly – block comment same-width whitespace + embedded newlines preserved – block comment spanning multiple lines preserves line count – regular string with `//` sigil inside preserved verbatim – regular string with `/* */` sigil preserved verbatim – regular string with `\"` escape terminates at the correct quote – interpolated `$"…"` preserved verbatim – verbatim `@"…"` with `""` escape preserved – verbatim `@"…"` with embedded newlines preserved – interpolated-verbatim `$@"…"` with `""` escape preserved – interpolated-verbatim `@$"…"` (alt order) with `""` escape preserved – character literal `'/'` does not trip line-comment sniff – character literal with `\'` escape terminates correctly – composite input strips only comments not literals – strip preserves length + newline count (line-number invariant) Label-lie guard (Program.cs:196, 231-233): * `Auto_derived_label_carries_the_auto_derived_suffix_on_stdout` — positive branch (compatLabelIsAutoDerived=true): the stdout Label: line carries the "(auto-derived)" suffix when the label was genuinely auto-derived. Pre-cycle-5 no test asserted this suffix. * `Explicit_label_alongside_zero_config_prev_xmi_does_not_get_auto_derived_suffix` — negative branch (compatLabelIsAutoDerived=false): when the operator passes an explicit --compat-version-label alongside the zero-config prev-XMI, the explicit label wins and the "(auto-derived)" suffix is NOT appended. This is the direct pin for the cycle-4 label-lie fix — pre-fix, the suffix appeared unconditionally. DocsGen bounded-scan (CliInventory.cs:352-353): * `SysMLImport_FullTree_Flag_Is_Detected_As_Switch_Not_Value_Flag` in DocsReferenceGenerationTests — asserts --full-tree in the collected CliInventory has ArgShape == null (i.e., detected as a boolean switch, not a value-taking flag). Pre-cycle-4 the RequireValue-detection regex used an unbounded lookahead and --full-tree falsely inherited the neighbouring --output case's `<value>` shape. The golden-file `Cli_Page_Is_In_Sync_With_Source` test would also catch the regression, but the targeted pin here surfaces the specific branch-scoped failure without recomputing the whole cli.md diff. Verification: * All 16 SourceStripperTests pass locally (Debug, dotnet 8.0.104, 45 ms). * All 82 generator-tests pass locally (Debug, 6m5s) and on bluefin (Debug, dotnet 10.0.302, 3m25s). * All 68 DocsReferenceGenerationTests pass locally (exit 0) and on bluefin. The 5 RouteCheckTests failures on bluefin's rsync-copy are pre-existing environmental gaps (npm-driven vitepress dist build not run on the ephemeral path) — unrelated to this cycle.
Dime review cycle 5 (F-CR-502, LOW). The cycle-4 rewrite regressed four sites from AmE to BrE: 'behaviour' (three sites: legacy-behavior parenthetical, pre-Phase-4 preservation clause, See-also end-to-end blurb) and 'containerised' (See-also dotnet.sh reference). The docs corpus uses AmE by convention (cli/agent.md has '## Exit behavior'; cli/adapter.md, cli/dotnet-sh.md, cli/test-sh.md, and the troubleshooting pages all use 'containerized'). Per CONVENTIONS section 1.0d-decies + 1.0d-trigies-vicies-octies, committed docs use AmE spelling with CMOS typography; BrE is reserved for user-authored prose (PR bodies, discussion drafts, issue comments). This file was the outlier - sibling docs/cli/*.md pages already carry AmE consistently.
… F-SIMP-501 MTConnectVersion.Max extraction previously routed MTConnectVersions.cs through SourceStripper.StripComments (a literal-aware C# comment walker) before applying the Max / VersionXY regex pair. The stripper carried non-trivial complexity (~200 LOC + literal handling for regular / verbatim / interpolated / interpolated-verbatim / char literals + a shared-source link into the test csproj) to defend against exactly one hypothetical scenario: a stale commented-out `// Max => Version27;` decoy above the live `public static Version Max => Version29;` declaration fooling the first-match regex into pinning the wrong PREV_VERSION. That defence is achievable with a targeted anchor pattern alone. Both regexes now line-anchor via `(?m)^[ \t]*public…`, which rejects any match where a `//` sits between line start and `public`. A last-match preference (`.Matches().Last()`) is applied on both patterns so a hypothetical block-commented decoy of the shape `/* … public static Version Max => Version28; … */` above the live declaration still loses to the live line below it. The combined `line-anchor + last-match` guard preserves the observable behaviour the `Commented_out_Max_declaration_does_not_confuse_the_parser` regression pin verifies (the live version wins over both `//`-line and block-comment decoys above it), without a comment-stripper walker. Files deleted: build/MTConnect.NET-SysML-Import/SourceStripper.cs + tests/MTConnect.NET-Generator-Tests/SourceStripperTests.cs + the shared-source `<Compile Include="…/SourceStripper.cs" Link="…"/>` element on the generator-tests csproj. Also added: PREV == NEW guard. When the new XMI's filename encodes the same major.minor as the auto-derived PREV_VERSION (matched via the `_[vV]<major>.<minor>.xml` case-insensitive suffix so both the `MTConnectSysMLModel_v<X>.<Y>.xml` and `MTConnectSysMLModel_V<X>.<Y>.xml` naming variants land), the delta is empty by construction. The importer writes a `warning: latest MTConnect version (v<X>.<Y>) is already supported by MTConnectVersions.Max — no delta to derive; skipping emit.` line to stderr and exits 0 (no-op emit). Un-suffixed filenames like the default `MTConnectSysMLModel.xml` submodule snapshot fall through the guard silently and reach the normal delta emitter. Two new tests pin the guard in AutoDerivePreviousXmiTests: `Prev_equals_new_warns_and_no_ops_when_new_xmi_filename_encodes_current_max` exercises the guard-fires path; `Prev_equals_new_guard_stays_silent_when_new_xmi_filename_has_no_version_suffix` exercises the guard-silent path (default un-suffixed filename must not trigger the early return). Closes F-IMP-504 (raw-string literal handling in the stripper) + F-IMP-505 (preprocessor-directive handling) — both non-goals for a file that has neither raw strings nor `#if` blocks.
ottobolyos
force-pushed
the
feat/dry-generator-phase1-consolidation
branch
from
August 21, 2026 16:06
d76aa54 to
7008734
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…ntegration/up-to-pr-233
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…ntegration/up-to-pr-233 # Conflicts: # tests/MTConnect.NET-Common-Tests/V2_6_V2_7/V2_7DataItemTypeTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Executes the DRY-generator campaign in full on a single branch. Phase 1
(topic-first fixture consolidation) and Phase 2 (three permanent
reflection / source-scan guards) land the topology rewrite for
tests/MTConnect.NET-Common-Tests/; Phase 3 (Scriban templateconsolidation via Shape A / B) lands two byte-identity-preserving
mergers on
build/MTConnect.NET-SysML-Import/plus a newtests/MTConnect.NET-Generator-Tests/project carrying the current-XMIbyte-identical regen guard; Phase 4 (delta-driven emitter +
generator-fidelity audit) closes the pre-existing baseline drift and
lands the
--previous-xmiCLI plus the mutated-XMI cross-verify;Phase 5 (verification pass) lands as further commits on this branch.
Scope decision (ottobolyos-approved 2026-08-20): a single PR keeps the
convention rewrite, the template consolidation, and the delta emitter
reviewable as one coherent unit. The earlier "Phases 3 – 5 ship as
separate campaign PRs" plan is superseded.
Motivation
Two intertwined pressures drive this campaign:
V<major>_<minor>/fixtureconvention forces every spec-version bump to author a whole new
folder of fixtures. The topic-first convention lets the next bump ADD
a
[TestCase(MTConnectVersions.VersionXY)]line and be done.latent duplication axes: near-identical partial-class hosts that
differed only in class name and doc summary, and paired
descriptions-class templates that differed only in the
Get(...)overload's parameter type. Every future version bump the campaign
ships would double the maintenance surface unless the templates are
collapsed first.
Without both landing, MTConnect Standard v2.8 support would ship as
another
V2_8/fixture folder plus another set of per-version templatecopies — every new spec release compounding the maintenance cost.
Scope: all five phases in this PR
V2_6_V2_7/; lands six new topic fixtures at their canonicaltopic-first location.
source-scan checks keep the new convention machine-enforced.
byte-identity-preserving mergers land on the Scriban template family;
a new test project carries the current-XMI byte-identical regen
guard.
--previous-xmiCLI flagon the SysML importer) plus the SysML-XMI 1:1 fidelity audit that
closes the Phase 3.1 baseline drift (15 orphan
.g.csfilestriaged, 63 whitespace-drift files refreshed, byte-identical guard
flipped from
[Explicit]to normal[Test]).matrix). Lands on this branch once Phase 4 is stable.
Approach
The design decisions applied are documented as inline choices on each
fixture / template.
Phase 1 / 2 — fixture topology
[TestCaseSource(typeof(MTConnectVersionMatrix), nameof(MTConnectVersionMatrix.All))]on each behavioral method, gated withAssume.That(v >= MTConnectVersions.Version??). Rows below the floor surface as Inconclusive in the test explorer, making "gated out" visually distinct from "ran and passed".MTConnectVersionMatrix.Allalready ships attests/MTConnect.NET-Common-Tests/TestHelpers/MTConnectVersionMatrix.cs; the reflection-driven matrix picks up future version constants automatically without per-test edits.V2_6_V2_7/); a staged rollout would have no distinguishing value.The
MTConnectVersionsconstant tests are kept as plain[Test]since they exercise structural invariants of theMTConnectVersionstype itself (constant values, distinctness, monotonicity), not per-version behavioral gates.Phase 3 — template consolidation
surface parameters (class name, doc line) collapse to one template
with a Scriban comment header naming the version range it applies to.
No
if mtc_version >= …gates inside; every version consumes thesame emission.
fragments collapse to one template with
{{ if … }}gates around thedifferentiated fragments. Kept below the ~5-gate over-abstraction
ceiling per the plan's Phase 3.2 rule.
Every consolidation is atomic (its own commit) and verified by the new
Regen_is_deterministic_across_two_invocationsguard intests/MTConnect.NET-Generator-Tests/. Byte-identity of every emitted.g.csfile against the committed tree for the consolidated templatesis preserved.
Phase 3.1 finding — pre-existing baseline drift (RESOLVED in Phase 4)
The Phase 3.1 dry-run surfaced pre-existing drift between the
current-XMI regen and the committed
libraries/**/*.g.cstree: 15hand-authored interface / observation files the current generator no
longer emits, plus 63 files (predominantly trailing-blank-line
whitespace) that differ from the current-XMI regen output. The
strict-baseline guard shipped in Phase 3 marked
[Explicit]documenting the drift; Phase 4 resolves it in full (per-fix breakdown
below) and flips the guard to plain
[Test].Phase 4 — SysML-XMI 1:1 fidelity audit + delta-driven regen
Phase 4 closes Phase 3.1 in two atomic movements plus the delta CLI.
Fidelity audit resolution. The 15 orphaned
.g.csfiles decompose as:IHeightMeasurement,ILengthMeasurement,ILoadedHeightMeasurement,ILoadedLengthMeasurement,ILoadedSwingMeasurement,ILoadedWeightMeasurement,ILoadedWidthMeasurement,ISwingMeasurement,IWeightMeasurement,IWidthMeasurement) — the concrete Pallet class emissions(
HeightMeasurement : Measurement, IHeightMeasurement, etc.) eachreference an
I{Name}interface thatMeasurementModel .RenderInterface()returnednullfor. Fixed by adding a newPallets.MeasurementInterface.scribantemplate and wiringMeasurementModel.RenderInterface()to invoke it. All 10 interfacesnow emit deterministically from the generator; the previously
hand-committed files are refreshed to the generator's output
(single per-file diff: copyright year 2024 → 2025 from the template).
Assets/Files/IAbstractFile.g.cs,Assets/Files/IFileArchetype.g.cs,Devices/Configurations/IRelationship.g.cs,Observations/Events/NetworkWireless.g.cs,Observations/Events/SensorStateDetect.g.cs) — dead code fromhistorical renames (superseded by
IAbstractFileAsset,IFileArchetypeAsset,IConfigurationRelationship, etc.) orspurious enum emissions with no consumers. Verified via codebase-wide
grep that zero
.csfiles reference any of them. Deleted.The 63 whitespace-drift
.g.csfiles are refreshed to currentgenerator output in a mechanical bulk commit (regen → copy over
committed tree → commit). No template changes; the drift was
accumulated delta from earlier template touches never mirrored into
the committed tree.
A late-stage fidelity finding (
fix(generator): drop stale ToolingMeasurement.Code inheritance override) removes an obsoleteMarkInheritedPropertieshand-stitched seed that force-addedCodeto
Assets.CuttingTools.ToolingMeasurement's inherited-name set onthe design assumption that
Measurement.g.csunder CuttingTools washand-frozen. The runtime now emits that file via the
sharedMeasurementinjection (which uses the Pallet Measurement class as base — no
Code),so the override produced a CS0109 build regression under
TreatWarningsAsErrors=truepost-refresh. Override dropped, thedefault inheritance walk correctly determines
ToolingMeasurement.Codedoes not hide anything, no
newkeyword emitted, build green.After all three commits: byte-identical guard flipped from
[Test, Explicit]to plain[Test]and passes on every defaultdotnet testinvocation.Delta CLI (
--previous-xmi). Two new opt-in flags on the SysML importer:--previous-xmi <path>— the prior spec-version XMI file.--compat-version-label <label>— Compat file basename (defaultPrevious).When
--previous-xmiis supplied, the generator renders both XMIs toisolated scratch directories under
%TMP%/mtc-sysml-delta-<guid>,diffs the emitted
.g.cstrees at the file level, and emits only:libraries/<library>/...location.to their normal location (new tree's version).
concentrated into one
Compat/<label>.g.csper library, with eachblock still carrying its own
namespace X { ... }declaration(multi-namespace concentration is legal C#). Byte-identity to the
deterministically-emitted full-tree output preserved per plan D4
invariant.
The full-tree mode (default when
--previous-xmiis omitted) ispreserved bit-for-bit. Delta mode is orthogonal, opt-in, and rolls
back cleanly to full-tree by dropping the flag from the automation
script.
The plan's original historical-XMI-iteration verification is out of
scope (ottobolyos-approved 2026-08-20 — the
build/sysml-modelsubmodule ships a single snapshot per spec bump). Replaced by
DeltaRegenTests.cswhich produces a mutated XMI programmatically(swapping one
ownedCommentbody) and asserts the delta capturesthe mutation surgically.
What changed
Phase 1
tests/MTConnect.NET-Common-Tests/V2_6_V2_7/deleted (7 files, 606 LOC).MTConnectVersionsTests.cs(project root) — 5 constant-value invariants of theMTConnectVersionstype.Devices/Components/ComponentTests.cs— 4 methods (CuttingTorch, Electrode, PinTool, ToolHolder).Devices/DataItems/DataItemTypeTests.cs— 6 v2.6 methods + 1 v2.7 parametric sweep (8[TestCase]rows × 17 versions cross-multiplied).Devices/Configurations/ConfigurationTests.cs— 16 methods across DataSet base, Axis / Origin / Rotation / Scale / Translation and their*DataSetsiblings plus the correspondingAbstract*bases.Observations/SampleObservationTests.cs— WaterHardness sample-envelope round-trip.Enums/EnumArmTests.cs— MediaType.QIF_MBD arm-presence check.Phase 2
tests/MTConnect.NET-Common-Tests/DryGenerator/AssertionParityTests.cs— RED-first coverage-parity guard. Holds an inline migration map of the 34 pre-migration method entries and asserts every one has a live post-migration home in the topic layout. GREEN after the topic fixtures land; permanent regression guard.tests/MTConnect.NET-Common-Tests/DryGenerator/PerVersionFolderProhibitionTests.cs— permanent topology guard. Three complementary checks fire RED against anyV<N_M>/directory,V<N_M>*Tests.csfile, orV<N_M>*Testsfixture class regrowth.bin//obj/are excluded so a package-cache artifact matching the pattern cannot trip a false positive; aHistoricalAnchorsallowlist accommodates deliberately-pinned fixtures (empty at HEAD).tests/MTConnect.NET-Common-Tests/DryGenerator/TopicFixtureCoverageTests.cs— permanent topic-fixture coverage guard. For each of the 23 spec-anchor types migrated out of the deprecatedV2_6_V2_7/folder, a source-scan check asserts the type name appears at least once in its canonical topic-fixture source file. Uses whole-word regex match so shorter anchors (Axis) do not spuriously match longer type names (AbstractAxis,AxisDataSet). Sibling floor + on-disk-topology guards catch anchor-set shrinkage and file-move regressions. ComplementsAssertionParityTests(method-name resolution) by adding source-text-mention resolution, catching the rename-plus-body-swap regression a name-only guard would miss.docs/testing/version-matrix-convention.md(new) — the single-source-of-truth reference for the topic-first single-file-per-topic convention: the rule, how to add a fixture for a new spec version, when to keep plain[Test], howHistoricalAnchorsandAssertionParityTestswork.docs/testing/v2-6.md,docs/testing/v2-7.md,docs/testing.md— repointed every "Pinned test" cell + the "Test classes" narrative at the post-migration home (Devices/DataItems/DataItemTypeTests,Devices/Components/ComponentTests,Devices/Configurations/ConfigurationTests,Observations/SampleObservationTests,Enums/EnumArmTests,MTConnectVersionsTests). Cross-links toversion-matrix-convention.mdfrom every entry point so a future v2.8 contributor finds the recipe.tests/MTConnect.NET-Common-Tests/Reflection/RegeneratedTypesCoverageTests.cs— two inline comments rewritten to reflect the topic-first layout (previously pointed at the deletedV2_6_V2_7/folder).Phase 3
tests/MTConnect.NET-Generator-Tests/wired viadotnet sln add. Two guards ship side-by-side:Regen_is_deterministic_across_two_invocations— the everyday[Test]. Regenerates against the current XMI twice into separate scratch directories under.claude/gen-test-out/(persistent, gitignored path per repo convention) and asserts the two emitted.g.cstrees are byte-identical. Locks in the determinism guarantee every template consolidation relies on.Current_XMI_regen_matches_committed_g_cs_tree— strict-baseline guard. Ships as[Test, Explicit]in Phase 3 and flips to plain[Test]in Phase 4 after the fidelity audit closes the drift.XmlCuttingItem.scriban+XmlCuttingToolLifeCycle.scriban(22 LOC each, differing only in emitted class name and doc summary) collapse to a singleXmlMeasurementArrayHost.scriban. Each call site inXml/TemplateRenderer.cssuppliesclass_name,summary, and the sharedtypesarray. Byte-identity of bothlibraries/MTConnect.NET-XML/Assets/CuttingTools/XmlCutting{Item,ToolLifeCycle}.g.cspreserved.EnumDescriptions.scriban+EnumStringDescriptions.scriban(38 LOC each, differing in three fragments: class-doc wording,Get(...)parameter type,Get(...)doc summary) collapse to a singleEnumOrStringDescriptions.scribanwith three{{ if is_string }}gates.EnumStringModelexposesIsString => true;EnumModelandObservationModelrely on Scriban's null-is-falsy default. Byte-identity of every emitted*Descriptions.g.csfile preserved.Devices.ComponentType.scriban+Devices.CompositionType.scriban) was scoped out this pass: a merger requires six independent gate points (namespace, base class, kind noun,is_organizer, ctor format,abstractkeyword), exceeding the plan's ≤5-gate over-abstraction ceiling without proportionate parameterization surgery.Phase 4
Six atomic commits close the Phase 3.1 baseline drift and land the
delta CLI:
fix(generator): emit Pallet measurement interfaces— addsbuild/MTConnect.NET-SysML-Import/CSharp/Templates/Pallets.MeasurementInterface.scriban;wires
MeasurementModel.RenderInterface()to invoke it. Regensthe 10 previously hand-committed
I{Name}Measurement.g.csfilesunder
libraries/MTConnect.NET-Common/Assets/Pallet/.chore(generator): remove stale orphan .g.cs files— deletesthe 5 zero-consumer orphan files (
IAbstractFile.g.cs,IFileArchetype.g.cs,IRelationship.g.cs,NetworkWireless.g.cs,SensorStateDetect.g.cs).chore(generator): refresh .g.cs to current generator output—regens the committed tree to close the 63-file whitespace-drift set.
Mechanical bulk refresh; no template changes.
test(generator): flip current-XMI byte-identical guard from Explicit to Test—Current_XMI_regen_matches_committed_g_cs_treenow runs on everydefault
dotnet testinvocation, blocking any future template orcommitted-tree drift.
feat(generator): add --previous-xmi flag for delta-driven regen—new
--previous-xmi <path>+--compat-version-label <label>CLIflags on
build/MTConnect.NET-SysML-Import/Program.cs. Delta moderenders both XMIs to scratch, diffs at file level, and emits ADDED /
CHANGED to normal paths + UNCHANGED concentrated to
Compat/<label>.g.csper library. REMOVED files skipped.
test(generator): mutated-XMI cross-verify of delta emission—tests/MTConnect.NET-Generator-Tests/DeltaRegenTests.cs. Two tests:a same-XMI-both-sides guard (asserts every file concentrates into
Compat) and a mutated-XMI cross-verify (rewrites one XMI
ownedCommentbody, asserts the delta surfaces exactly two changedfiles — the enum + its Descriptions — with the mutation marker in
each, no marker in Compat files).
fix(generator): drop stale ToolingMeasurement.Code inheritance override—removes the obsolete
MarkInheritedPropertiesseed that force-addedCodetoToolingMeasurement's inherited-name set on a stale designassumption. Restores byte-identity + fixes CS0109 build regression
post-refresh.
test(generator): skip bin/obj generated files in byte-identity walks—filters
/bin/and/obj/from the.g.cswalk in both fixturesso MSBuild-generated intermediates (
GlobalUsings.g.cs, etc.) don'tspuriously flip the guards RED on already-built hosts.
Migration script
The migration is driven by a repository-conventional migration script (gitignored, not part of the shipped tree). The script validates that every hand-authored topic file is in place, emits
renames.tsvandcitations.tsvaudit artifacts,git rms the deprecated folder, and runs a post-migration sanity build. Topic-file authoring itself remains a hand step: version-gate mechanism selection and spec-source citation verification are hand-checked, not scriptable.Parity guarantee
Three permanent guards cover the fixture migration:
AssertionParityTestsenumerates every[Test]/[TestCase]/[TestCaseSource]method in the test assembly (excluding the deprecatedV2_6_V2_7namespace) and asserts every entry in the 34-entry migration map resolves to a live method. Two rename conventions apply:_in_v2_6/_in_v2_7suffixes — the version is now a property of the[TestCaseSource(MTConnectVersionMatrix.All)]matrix rather than encoded in the method name;V2_7_prefix onV2_7_DataItem_constructs_with_correct_metadata— same rationale.Every other migration is an identity rename. A separate
Migration_map_covers_the_full_baseline_of_34_entriesguard fires RED if the map itself is trimmed below the 34-entry baseline.PerVersionFolderProhibitionTestsenforces the folder-topology invariant: noV<N_M>/directory,V<N_M>*Tests.csfile, orV<N_M>*Testsfixture class may return to the tree.TopicFixtureCoverageTestsenforces the coverage-parity invariant at source-text level: for every migrated spec-anchor type, the designated topic-fixture source file must name the type at least once. Catches the rename-plus-body-swap regression a name-only guard would miss.For Phase 3, byte-identity is enforced by
Regen_is_deterministic_across_two_invocationsin the new generator-tests project. Phase 4 addsCurrent_XMI_regen_matches_committed_g_cs_tree(now plain[Test], not[Explicit]) plus the twoDeltaRegenTestsguards.Test renames
Verbatim table generated from the migration script's rename audit (34 rows, one per pre-migration
[Test]/[TestCase]method entry).case_map = identitymeans the[TestCase]parameter tuples migrated verbatim; the one non-identity row (V2_7_DataItem_constructs_with_correct_metadata) cross-multiplies its 8(Type, TypeId, Category)tuples withMTConnectVersionMatrix.Allunder the new matrix mechanism.V2_6ComponentAndEnumTests.csCuttingTorchComponent_constructs_with_correct_typeDevices/Components/ComponentTests.csCuttingTorchComponent_constructs_with_correct_typeV2_6ComponentAndEnumTests.csElectrodeComponent_constructs_with_correct_typeDevices/Components/ComponentTests.csElectrodeComponent_constructs_with_correct_typeV2_6ComponentAndEnumTests.csMediaType_QIF_MBD_value_present_in_v2_6Enums/EnumArmTests.csMediaType_QIF_MBD_value_presentV2_6DataItemTypeTests.csAssetAddedDataItem_constructs_with_event_metadataDevices/DataItems/DataItemTypeTests.csAssetAddedDataItem_constructs_with_event_metadataV2_6DataItemTypeTests.csAssetAddedDataItem_with_deviceId_produces_qualified_idDevices/DataItems/DataItemTypeTests.csAssetAddedDataItem_with_deviceId_produces_qualified_idV2_6DataItemTypeTests.csAssociatedAssetIdDataItem_constructs_with_event_metadataDevices/DataItems/DataItemTypeTests.csAssociatedAssetIdDataItem_constructs_with_event_metadataV2_6DataItemTypeTests.csAssetAddedDataItem_inherits_from_DataItemDevices/DataItems/DataItemTypeTests.csAssetAddedDataItem_inherits_from_DataItemV2_6DataItemTypeTests.csAssociatedAssetIdDataItem_inherits_from_DataItemDevices/DataItems/DataItemTypeTests.csAssociatedAssetIdDataItem_inherits_from_DataItemV2_6DataItemTypeTests.csAssetChangedDataItem_description_narrowed_in_v2_6Devices/DataItems/DataItemTypeTests.csAssetChangedDataItem_description_narrowedV2_7DataItemTypeTests.csV2_7_DataItem_constructs_with_correct_metadataDevices/DataItems/DataItemTypeTests.csDataItem_constructs_with_correct_metadataMTConnectVersionsTests.csVersion26_constant_equals_2_6MTConnectVersionsTests.csVersion26_constant_equals_2_6MTConnectVersionsTests.csVersion27_constant_equals_2_7MTConnectVersionsTests.csVersion27_constant_equals_2_7MTConnectVersionsTests.csMax_equals_Version27MTConnectVersionsTests.csMax_equals_Version27MTConnectVersionsTests.csEvery_published_version_constant_is_distinct_and_monotonicMTConnectVersionsTests.csEvery_published_version_constant_is_distinct_and_monotonicMTConnectVersionsTests.csVersion19_field_does_not_existMTConnectVersionsTests.csVersion19_field_does_not_existV2_7ComponentTests.csPinToolComponent_constructs_with_correct_typeDevices/Components/ComponentTests.csPinToolComponent_constructs_with_correct_typeV2_7ComponentTests.csToolHolderComponent_constructs_with_correct_typeDevices/Components/ComponentTests.csToolHolderComponent_constructs_with_correct_typeV2_7ConfigurationDataSetTests.csDataSet_base_constructs_and_implements_IDataSetDevices/Configurations/ConfigurationTests.csDataSet_base_constructs_and_implements_IDataSetV2_7ConfigurationDataSetTests.csAxisDataSet_has_xyz_fields_and_implements_IDataSetDevices/Configurations/ConfigurationTests.csAxisDataSet_has_xyz_fields_and_implements_IDataSetV2_7ConfigurationDataSetTests.csOriginDataSet_has_xyz_fields_and_implements_IDataSetDevices/Configurations/ConfigurationTests.csOriginDataSet_has_xyz_fields_and_implements_IDataSetV2_7ConfigurationDataSetTests.csRotationDataSet_has_abc_fields_and_implements_IDataSetDevices/Configurations/ConfigurationTests.csRotationDataSet_has_abc_fields_and_implements_IDataSetV2_7ConfigurationDataSetTests.csScaleDataSet_implements_IDataSetDevices/Configurations/ConfigurationTests.csScaleDataSet_implements_IDataSetV2_7ConfigurationDataSetTests.csTranslationDataSet_implements_IDataSetDevices/Configurations/ConfigurationTests.csTranslationDataSet_implements_IDataSetV2_7ConfigurationDataSetTests.csAxis_inherits_AbstractAxis_and_constructsDevices/Configurations/ConfigurationTests.csAxis_inherits_AbstractAxis_and_constructsV2_7ConfigurationDataSetTests.csOrigin_inherits_AbstractOriginDevices/Configurations/ConfigurationTests.csOrigin_inherits_AbstractOriginV2_7ConfigurationDataSetTests.csRotation_inherits_AbstractRotationDevices/Configurations/ConfigurationTests.csRotation_inherits_AbstractRotationV2_7ConfigurationDataSetTests.csScale_inherits_AbstractScaleDevices/Configurations/ConfigurationTests.csScale_inherits_AbstractScaleV2_7ConfigurationDataSetTests.csTranslation_inherits_AbstractTranslationDevices/Configurations/ConfigurationTests.csTranslation_inherits_AbstractTranslationV2_7ConfigurationDataSetTests.csAbstractAxis_is_abstractDevices/Configurations/ConfigurationTests.csAbstractAxis_is_abstractV2_7ConfigurationDataSetTests.csAbstractOrigin_is_abstractDevices/Configurations/ConfigurationTests.csAbstractOrigin_is_abstractV2_7ConfigurationDataSetTests.csAbstractRotation_is_abstractDevices/Configurations/ConfigurationTests.csAbstractRotation_is_abstractV2_7ConfigurationDataSetTests.csAbstractScale_is_abstractDevices/Configurations/ConfigurationTests.csAbstractScale_is_abstractV2_7ConfigurationDataSetTests.csAbstractTranslation_is_abstractDevices/Configurations/ConfigurationTests.csAbstractTranslation_is_abstractV2_7SampleObservationTests.csWaterHardness_sample_observation_round_tripObservations/SampleObservationTests.csWaterHardness_sample_observation_round_tripDepends on
None. This PR is orthogonal to every other open campaign PR — it touches only
tests/MTConnect.NET-Common-Tests/,docs/testing/,build/MTConnect.NET-SysML-Import/, and adds the newtests/MTConnect.NET-Generator-Tests/project. The eventual MTConnect Standard v2.8 bump PR depends on THIS.Phase 5 — verification pass
Full-tree cross-check + wire-format matrix + all-categories
dotnet testsweep on bluefin at head619dd1bc, 9 assemblies green (Common 3987, XML 131, JSON 68, MqttRelay 62, Docs 67, HTTP 112, JSON-cppagent 363, SHDR 35, Compliance 225 — 5,050 tests / 0 failed / 0 skipped, filtered-excluding the documented Docs-Tests bluefin Playwright flake covered by #227's baseline).Phase 5 also lands four categories of atomic test / doc / generator hardening on top of the Phase 4 head:
fix(generator): delta emitter deletes stale UNCHANGED / REMOVED files(delta CLI now cleans up files no longer emitted by the current XMI, closing a stale-artifact class the Phase 4 baseline left latent).test(generator): CLI failure-path coverage for Program.cs,test(generator): delta-mode Compat body + stats-line invariants,test(generator): relax malformed-XMI test to non-zero exit contract(tightens the delta-mode invariants pinned in Phase 4 and rounds out the failure-mode surface ofProgram.cs).fix(tests): drain stdout/stderr concurrently + tighten TopicAnchors floor(removes a bluefin-observed deadlock in the process-driver + raises theTopicFixtureCoverageTestscoverage floor to reflect the actual anchor set at HEAD).docs(generator): document delta CLI + Stryker gate + generator-tests tier(docs/testing/mutation-testing.md,docs/testing/version-matrix-convention.md, and the two per-version testing pages document the new delta CLI flag, the Stryker gate, and the generator-tests tier).Phase 5 — mutation-testing framework adoption
chore(tests): adopt Stryker.NET mutation-testing frameworklandsstryker-config.jsonat the repo root wired toMTConnect.NET-Commonas the pilot target (net8.0,--mutation-level Complete, .g.cs generated surfaces excluded from mutation, thresholds at 100). A follow-up commitchore(tests): exclude Stryker Regex mutator (upstream bug workaround)disables theRegexmutator to work around a Stryker.NET v4.16.0 crash on interpolated-stringRegexconstructors (upstream issue tracked in the mutation-testing doc).Baseline mutation run on
MTConnect.NET-Commonat head43497c5d(bluefin, concurrency 4, 4 min elapsed):The 7.75% baseline is expected: this PR ADOPTS the framework. Killing every survived + no-coverage mutant is realistically 100+ engineering hours and cross-cuts every subsystem in the assembly (codecs, model, validators, formatters), so a companion commit
chore(tests): pin Stryker threshold at 7.75% baselinepinsstryker-config.jsonthresholds to{ high: 8, low: 5, break: 5 }— well below the 7.75% baseline so the current PR does not regress and so subsequent PRs inherit a pass-through gate until the dedicated coverage-quality campaign raises the floor. #242 tracks the phased follow-up campaign (categorize survivors -> draft kill tests per subsystem -> raise thresholds in step to 20 -> 40 -> 60 -> 80%+ -> expand Stryker to the sibling assemblies -> wire into CI as a required check once the pilot clears 80%).Dime review cycle 1
Six-agent sweep (
code-review+security-audit+simplification+improvement+documentation-audit+test-coverage-audit) dispatched at headd83cff2d. Findings distribution:code-reviewsecurity-auditsimplificationimprovementdocumentation-audittest-coverage-auditDisposition — three-state (Fixed / Closed-with-rationale / Tracked):
d83cff2d(through43497c5d) are the fix landings + the mutation-testing-framework adoption plus its Regex-mutator workaround.DeltaStatsasrecord struct,--compat-version-labelauto-derivation,dotnet runoverhead removal on the generator smoke path, redundant--json-dumpre-Parse,ADDED/REMOVEDpartition direct assertion,Parsethrow-hardening, and a Stryker CI runner.Measurement.Codedeliberate spec-alignment (Section 8.2 §Measurement code retains the spec's normative token, not the more-general C# identifier the reviewer proposed); empty-HistoricalAnchorsdocumented anticipation (the reviewer's proposal treats an anticipated multi-version anchor map as a same-PR gap; the map is deliberately unused until the second historical spec version lands).Full test sweep on bluefin at head
619dd1bc: 9 of 10 assemblies green (Docs-Tests documented bluefin Playwright flake, docs-orthogonal to this PR — same class of infra flake pinned by #227's baseline).(Zero unfixed findings — Ready-eligible.)
Dime review cycle 2
Six-agent sweep dispatched at head
619dd1bc(the ottobolyos-directive Stryker baseline-pin commit). Cycle-2 scope is deliberately narrow — the sole diff under review isstryker-config.jsonthreshold values{ high: 100, low: 100, break: 100 } -> { high: 8, low: 5, break: 5 }+ a JSONC provenance header cross-referencing #242. Findings distribution:code-reviewsecurity-auditsimplificationimprovementdocumentation-audittest-coverage-auditDisposition — three-state (Fixed / Closed-with-rationale / Tracked):
28d9b9fe test(generator): pin stryker-config.json thresholds + JSONC-parser assumption(test-coverage-audit) — 231-LOCStrykerConfigPinTests.csguarding break window[5, 8], monotone orderingbreak <= low <= high <= 100, JSONC parseability viaSystem.Text.JsonwithCommentHandling=Skip,Regexmutator ignore-list membership, and pilot-project pin (MTConnect.NET-Common.csproj+MTConnect.NET-Common-Tests); 5/5 GREEN + 27/27 pre-existing = 32/32 Generator-Tests GREEN on bluefin net8.0.d79e9fc8 docs(testing): sync mutation-testing.md + Stryker.NET pin to cycle-2 threshold pin(this cycle) — F-CR-001 rewrites thestryker-config.jsonJSONC header to say explicitly thathigh: 8sits above the 7.75 % baseline as an aspirational marker whilelow: 5andbreak: 5sit below as the pass-through gate; F-IMP-001 pinsdotnet-strykerat v4.16.0 in.config/dotnet-tools.jsonalongsidedotnet-reportgenerator-globaltoolso the baseline stays reproducible underdotnet tool restore; F-DOC-001..005 syncdocs/testing/mutation-testing.mdin five places (JSON snippet,Thresholdsbullet,--break-atCI shape, disposition-rules third arm, References list test-quality: raise MTConnect.NET-Common mutation score from 7.75% baseline #242 pointer).#242JSONC-comment reference to the fullhttps://github.com/...URL for editor click-through in non-git contexts); F-IMP-003 (flipsince.enabled: truefor incremental-mutation runs once the pilot clears 20 %); F-IMP-004 (emit a siblingstryker-baseline.jsoncapturing the score / date / SHA / ignored-mutators / tool-version snapshot so a CI check can diff future runs against the pinned baseline, not just against thebreakthreshold).PR #233 head 43497c5dremains accurate as the measurement anchor for the 7.75 % baseline even after the head advances past that SHA); thesimplificationagent's no-finding is a self-close (the diff is already at minimum shape for a follow-up-tracked baseline pin).Threshold values kept at
{ high: 8, low: 5, break: 5 }per user directive 2026-08-20; only prose + tool pin updated this cycle. Full test sweep on bluefin at headd79e9fc8matches cycle-1's assembly count (docs-only + config-only diff introduces no new production behavior).(Zero unfixed findings — Ready-eligible.)
Follow-up
candidate identified by the Phase 3.2 audit. Deferred this pass
(six-gate ceiling breach without parameterization surgery); reopens
if a future spec bump's delta emission surfaces a compelling reason
to unify the two.
across every
build/sysml-modeltag is out of scope this pass(ottobolyos 2026-08-20; the submodule ships one snapshot per spec
bump). Reopens once the submodule is extended with multi-tag history
or the campaign transitions to hosting spec-tags externally.
reports 1,211 Survived + 8,275 NoCoverage mutants on
MTConnect.NET-Commonat the pilot baseline (mutation score 7.75%).Killing every surviving and no-coverage mutant is a multi-week
test-authoring effort tracked in test-quality: raise MTConnect.NET-Common mutation score from 7.75% baseline #242 — the
phased campaign categorizes survivors, drafts kill tests per subsystem,
and raises thresholds in step (20 -> 40 -> 60 -> 80%+), then expands
Stryker to the sibling assemblies and wires it into CI as a required
check once the pilot clears 80%. The
stryker-config.jsonlanded herePR CAN run the mutation sweep; the pin at
{ high: 8, low: 5, break: 5 }keeps the current PR + every future one green until the dedicated
campaign raises the floor.