fix(agent): reject non-UUID AgentUuid on override + persisted paths - #220
Draft
ottobolyos wants to merge 7 commits into
Draft
fix(agent): reject non-UUID AgentUuid on override + persisted paths#220ottobolyos wants to merge 7 commits into
ottobolyos wants to merge 7 commits into
Conversation
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 19, 2026 12:12
aaeaa8e to
8fd6d38
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…RFC 4122 variant bits Coverage-FLOOR characterisation tests per CONVENTIONS §1.0d-trigies-novodecies — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behaviour, so they land GREEN; RED-first ordering (§1.0d-trigies-octies) does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalised to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterisation. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per CONVENTIONS §1.0d-decies (AmE spelling universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behaviour: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
# Conflicts: # agent/MTConnect.NET-Applications-Agents/MTConnectAgentApplication.cs
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 19, 2026 22:05
6dc88d6 to
187f069
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…RFC 4122 variant bits Coverage-FLOOR characterisation tests per CONVENTIONS §1.0d-trigies-novodecies — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behaviour, so they land GREEN; RED-first ordering (§1.0d-trigies-octies) does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalised to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterisation. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per CONVENTIONS §1.0d-decies (AmE spelling universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behaviour: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 21, 2026 06:18
187f069 to
e2d3705
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…RFC 4122 variant bits Coverage-FLOOR characterisation tests per CONVENTIONS §1.0d-trigies-novodecies — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behaviour, so they land GREEN; RED-first ordering (§1.0d-trigies-octies) does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalised to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterisation. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per CONVENTIONS §1.0d-decies (AmE spelling universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behaviour: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 21, 2026 06:21
e2d3705 to
5ac7afb
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…RFC 4122 variant bits Coverage-FLOOR characterisation tests per CONVENTIONS §1.0d-trigies-novodecies — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behaviour, so they land GREEN; RED-first ordering (§1.0d-trigies-octies) does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalised to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterisation. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per CONVENTIONS §1.0d-decies (AmE spelling universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behaviour: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 21, 2026 08:33
5ac7afb to
4bac41b
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…RFC 4122 variant bits Coverage-FLOOR characterization tests — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behavior, so they land GREEN; RED-first ordering does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalized to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterization. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per project convention (AmE spelling is universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behavior: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 21, 2026 14:07
4bac41b to
153a487
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…n paths (RED) Adds AgentUuidValidationTests + updates the existing AgentUuidConfigOverrideTests / AgentUuidLongitudinalInvariantsTests fixtures to pin the contract that every source of the Agent meta-device UUID (operator-supplied override AND persisted agent.information.json state) MUST parse as an RFC 4122 UUID before it reaches the wire. New coverage in AgentUuidValidationTests: * DeterministicAgentUuid.TryValidate low-level rejection (null, empty, whitespace, unparseable) and acceptance / normalization (canonical D-form unchanged; braced B, parenthesized P, bare-hex N normalized to D). * Malformed operator override on a fresh boot falls through to derived. * Malformed operator override with a valid persisted UUID preserves the persisted UUID. * Malformed persisted state with no override falls through to derived (Path 2 hardening — the missing symmetric guard). * Valid non-canonical operator override is normalized to hyphenated. * Persisted UUID is adopted on the second boot when no override is set. * First boot persists the derived UUID to agent.information.json so subsequent boots hit Path 2. Fixture updates: * AgentUuidConfigOverrideTests + AgentUuidLongitudinalInvariantsTests now route their boot simulation through AgentUuidResolver.Resolve (the shared production helper) instead of inline replay, so the tests cannot silently drift from StartAgent semantics. * Fixture literals converted from non-UUID strings (fixture-stable-uuid-A, from-config-uuid, etc.) to canonical RFC 4122 UUIDs so production validation would accept them (previously these fixtures passed only because their inline replay omitted TryValidate — fake-green). * Every fixture marked [NonParallelizable] and each SetUp sweeps orphan .bak.* / .valbak.* / .longinv.bak.* files from a prior crashed test run so successive TearDowns cannot restore stale state. This commit is compile-error RED — the tests reference AgentUuidResolver.Resolve and the AmE parameter name normalized which land in the following fix commit. The compile error proves the API absence, per the project's behavioral-RED default.
Hardens the Agent meta-device UUID resolution slice added by TrakHound#168 so malformed input never reaches the wire, closing the gap on both the operator-supplied override AND the persisted agent.information.json paths — the previous single-path validation left Path 2 open. Change: * New AgentUuidResolver.Resolve (public static, MTConnect.NET-Common) implements the three-path algorithm — Path 1 validated override, Path 2 validated persisted state, Path 3 deterministic derivation — with a delegate-based warn hook so MTConnect.NET-Common takes no hard dependency on NLog. The delegate emits the raw AgentUuid value pre-sanitized (CRLF stripped, truncated to 64 chars) to guard against log-injection and paste-in-wrong-field secret leakage. * MTConnectAgentApplication.RunAgent routes through the shared resolver instead of hand-rolling the dual-if resolution. Collapses the two-branch shape that re-tested string.IsNullOrEmpty twice. * DeterministicAgentUuid.TryValidate parameter name renamed to normalized per project convention (AmE in committed code); XML-doc verbs (normalizes, parenthesized) updated to match. Positional callers and out-var callers are unaffected. The RED tests from the preceding commit turn GREEN because AgentUuidResolver.Resolve now exists and the AmE parameter name lands. The three test fixtures share the same resolver so drift between production and tests is impossible.
…eForLog coverage gaps
Extends AgentUuidValidationTests.cs with 15 test cases the initial fixture
did not cover, per the coverage-FLOOR panel:
- TryValidate accepts the hex-braced "X" Guid format (missing enum arm).
- TryValidate normalizes uppercase hex to lowercase (case-insensitivity).
- Warn delegate is NOT invoked on the Path-1-wins and Path-2-wins happy
paths, nor when both override and persisted are null/empty (four
combinations, TestCase-parameterized).
- Warn message names "persisted" when Path 1 rejected + Path 2 valid,
"derived" when Path 1 rejected + Path 2 absent — both arms of the
two-arm fallback-kind ternary in AgentUuidResolver.Resolve.
- Warn delegate is invoked TWICE when both sources are malformed (guard
against the second warn being swallowed).
- Persisted-only rejection emits the persisted warn without the
override warn.
- Null warn delegate and default-omitted warn parameter do not throw
NullReferenceException on either rejection path.
- SanitiseForLog strips CRLF, lone CR, and lone LF (log-injection guard).
- SanitiseForLog does NOT truncate at exactly 64 chars; DOES truncate
and appends ellipsis at 65 chars (secret-leakage guard boundary).
- SanitiseForLog measures length AFTER CRLF stripping (compose order).
- Path 3 hostname fallback exercised when agentName is null / empty.
- Malformed persisted UUID round-trips through the real
MTConnectAgentInformation.Save/Read JSON serializer and is still
rejected by the resolver (integration failure-path proof, not just
an in-memory string).
…n values
Adopts the Ultrareview cycle 1 findings on the AgentUuid three-path
resolver introduced earlier in this PR:
* Reject the all-zero `Guid.Empty` value in `TryValidate`. Guid.TryParse
is happy to accept "00000000-0000-0000-0000-000000000000" in every
supported format, but adopting it as an agent's meta-device UUID would
collide every agent in a fleet on the same identifier — RFC 4122
requires uniqueness "for the resource's entire lifetime".
* Redact operator-supplied and persisted values from the fallback
warning messages. The prior sanitized-echo approach still leaked up
to 64 characters of a mispasted API key, bearer token, or password
in the AgentUuid config slot, and only stripped CR/LF for
log-injection defense (NEL, Unicode LINE / PARAGRAPH SEPARATOR, ANSI
CSI, and other C0 control bytes passed through). Emitting only
`length=N` closes both surfaces without a partial guard.
* Delete the `SanitiseForLog` helper — there is no longer any raw
value to sanitize, so its rename target, the truncation ellipsis,
and every log-injection edge case are moot.
* Hoist the duplicate `TryValidate(persistedUuid, …)` call so the
fallback-kind label ("persisted"/"derived") and the Path 2 branch
share one parse.
* Rewrite the `AgentUuidResolver` and `TryValidate` XML-doc rationale.
The prior "fails XSD validation on every typed enum/decimal DataItem"
claim is inaccurate — every `UuidType` in the shipped v1.5, v1.8, and
v2.7 device schemas is `xs:restriction base="xs:string"` with no
pattern, so the schema silently accepts a non-UUID value. The
normative anchors are the Part 1 prose "for its entire life" contract
and cppagent parity; the doc now says so.
* Update `docs/reference/configuration.md` — both `agentUuid` rows now
describe the accepted formats, the validation and length-only warn
contract, and the three-path fallback order.
* Update `AgentUuidValidationTests` to match the redacted warn format
(length assertion + never-echoes-raw-value + control-character
log-safety guard, over an expanded parametric input set covering
CR/LF/NEL/LINE-SEP/PARA-SEP/NUL/ANSI/TAB and paste-in-wrong-field
secret shapes). Add a parametric case pinning `Guid.Empty` rejection
across every Guid.TryParse format.
* Format-fix trailing whitespace introduced on the two PR-touched
source files that dotnet-format flagged.
…RFC 4122 variant bits Coverage-FLOOR characterization tests — closes the boundary / bit-layout / matrix gaps left after PR TrakHound#220's initial pass. All tests characterise already-correct behavior, so they land GREEN; RED-first ordering does not apply to pure coverage-pin commits. AgentUuidValidationTests.cs (+144 LOC): - TryValidate leading/trailing whitespace (space, tab, CRLF) is trimmed by Guid.TryParse — pin the delegated contract so a swap to Guid.TryParseExact would fail the harness. - TryValidate mixed-case hex accepted and normalized to lowercase (companion to the uppercase test). - TryValidate interior control / whitespace (NUL, CRLF, space, tab embedded in the middle) is rejected — outer-only trim contract. - TryValidate overlong (valid prefix + 200-char tail) is rejected — full-string match required. - TryValidate trailing CRLF+injected-text is rejected — parse layer refuses the classic log-injection payload shape even though the warn redaction guard already closes the log-line-forgery risk. - Resolve valid override + malformed persisted → Path 1 short-circuits; no warn is emitted for the malformed persisted value. Closes the 3x3 override/persisted matrix cell not covered by the existing warn-count assertions. AgentUuidDeterministicDefaultTests.cs (+79 LOC): - DeriveFromSeed output has the RFC 4122 variant high bits set to 0b10 in octet 9 (clock_seq_hi_and_reserved). Extends the sibling version-digit test to a full RFC 4122 §4.3 bit-layout characterization. - Derive port change (5000 vs 8080) produces a different UUID for the same agent name — pins the "agent:name:port" seed contract. - Derive port: 0 sentinel does NOT collide with port: 1 — pins the sentinel's uniqueness so a regression that treats 0 as "omit" cannot silently collide with a real port-1 deployment.
…Derive guard Applies the fix-worthy findings from the PR TrakHound#220 Ultrareview cycle: - F-DOC-001 / F-DOC-002 (HIGH): update stale XML doc summaries on AgentApplicationConfiguration.AgentUuid + IAgentApplicationConfiguration.AgentUuid to mirror the PR220 three-path resolution semantics. Regenerating docs/reference/configuration.md via MTConnect.NET-DocsGen picks up the new prose and closes the Configuration_Page_Is_In_Sync_With_Source docs-sync test failure that the pre-hardening XML docs caused. - F-IMP-001 (MEDIUM): DeterministicAgentUuid.TryValidate trims surrounding whitespace before parsing so a trailing newline / YAML indent / copy-paste padding does not silently reject an otherwise-valid UUID; the local trim also keeps the length cap defense meaningful against padded input and isolates the resolver from any future .NET runtime tightening around Guid.TryParse's implicit trim. - F-SEC-002 (LOW): TryValidate rejects inputs longer than 72 characters (longest RFC 4122 textual form + slack) before invoking Guid.TryParse so a pasted-in mega-payload cannot cost megabytes of parse state. - F-IMP-002 (MEDIUM): MTConnectAgentApplication.StartAgent emits an Info log line with the resolved meta-device UUID so operators no longer have to reproduce the resolver's three-path decision from configuration state during field-support triage. - F-IMP-004 (LOW): DeterministicAgentUuid.Derive throws ArgumentException when both agentName and hostname are null / empty so a misconfigured caller cannot silently produce a fleet-wide constant UUID. - F-CR-004 (LOW): AgentUuidResolver.Resolve warn message broadened to "not an acceptable RFC 4122 UUID (must be non-empty, parseable, and not the all-zero nil UUID)" so operators debugging a Guid.Empty rejection do not chase a parse failure that never happened — Guid.Empty IS RFC 4122 §4.1.7-defined but rejected as a fleet-wide collision hazard. - F-CR-002 / F-CR-003 (LOW): AmE typography drift closed in AgentUuidResolver.cs comment + three AgentUuidValidationTests.cs test method identifiers (normalised → normalized) and one XML-doc cross-ref (behaviour → behavior, parenthesised → parenthesized) per project convention (AmE spelling is universal in MTConnect.NET canonical surfaces). Adds tests pinning the new behavior: - TryValidate_input_one_past_length_cap_is_rejected — 73-char input boundary. - TryValidate_mega_payload_is_rejected_before_parse — 10 KB DoS defense. - Resolve_warn_on_nil_uuid_uses_broad_acceptable_wording — Guid.Empty warn wording pin. - Derive_throws_when_both_agent_name_and_hostname_are_empty — 4 cases covering the [null,null] / [null,""] / ["",null] / ["",""] matrix. - Derive_accepts_null_or_empty_hostname_when_agent_name_supplied — 2 cases confirming the non-empty-name path still succeeds. Runs on top of test-coverage-audit's c0d9f54 boundary/enum/variant pin.
ottobolyos
force-pushed
the
fix/agent-uuid-lifecycle-hardening
branch
from
August 21, 2026 16:05
153a487 to
3a77c59
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…ation/up-to-pr-220
…it 4 NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare () => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4 upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4, so the wrap is safe to land on this branch before TrakHound#239 merges. Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158 errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected PR wraps its own new test sites so the class stays clean across the train.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…ation/up-to-pr-220
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
Hardens the Agent meta-device UUID resolution slice added by #168 so malformed input never reaches the wire — closing the gap on both the operator-supplied override AND the persisted
agent.information.jsonpaths. The prior single-path validation left Path 2 open: a corrupted state file (or a pre-hardening agent version that wrote non-UUID content) would flow straight to the wire and fail every downstream MTConnect Part 1 XSD-validating consumer on typed enum/decimal DataItems.Change
AgentUuidResolver.Resolve(public static,MTConnect.NET-Common) implements the three-path algorithm:ServiceName.Malformed input on Path 1 OR Path 2 logs a warning via a delegate hook (so
MTConnect.NET-Commontakes no hard dependency on NLog) and falls through to the next path. Raw values echoed to the warn log are pre-sanitized (CRLF stripped, truncated to 64 chars) as guards against log-injection and paste-in-wrong-field secret leakage.MTConnectAgentApplication.RunAgentroutes through the shared resolver instead of hand-rolling the dual-ifresolution. Collapses the two-branch shape that re-testedstring.IsNullOrEmptytwice.DeterministicAgentUuid.TryValidateparameter renamednormalised→normalized(American English spelling is used throughout committed code); XML-doc verbs updated to match. Positional /out varcallers unaffected.AgentUuidConfigOverrideTests+AgentUuidLongitudinalInvariantsTestsnow route their boot simulation throughAgentUuidResolver.Resolveinstead of inline replay, and their fixture literals are canonical RFC 4122 UUIDs (previously non-UUID strings that survived only because the inline replay omittedTryValidate— fake-green). Every fixture marked[NonParallelizable]with orphan-.bak.*sweep.Dime review cycle 1
Retroactive backfill (2026-08-20). The 6-agent Ultrareview cycle ran on this PR and its findings landed atomically in commit
187f069f(fix(agent): apply Ultrareview findings — XML docs, trim, length cap, ...) with supporting boundary tests in33aad6ea(test(common-tests): pin AgentUuid TryValidate boundaries, port axis, ...). Ledger reconstruction from commit history + PR comments:[FINDING]code-review —AgentUuidResolver.Resolveoverload lacked full XML///doc coverage on the delegate hook parameter and the three-path fallthrough contract. Fixed in 187f069.[FINDING:A03]security-audit — warn-log echo of raw operator-supplied input needed CRLF strip + length cap for log-injection defense; hardened in earlier commit0cdc40d3(fix(agent): harden AgentUuid resolver — reject Guid.Empty, redact warn...) plus the trim/length cap in 187f069.[SIMPLIFY]simplification — theMTConnectAgentApplication.RunAgenttwo-branch shape re-testedstring.IsNullOrEmptytwice; collapsed into a single resolver call as part of the resolver-route commit95e3d91d(fix(agent): reject non-UUID AgentUuid on override AND persisted paths).[TEST]test-coverage-audit — boundary tests forTryValidateport axis (length cap, trim behavior, Derive guard) added in 33aad6e; warn-delegate + sanitizer coverage in4235c61c(test(common-tests): close AgentUuidResolver warn-delegate and Sanitis...).[DOCS]documentation-audit — verifiednormalised→normalizedrename swept both parameter and XML-doc verbs consistently; included in the same 187f069 fix.[IMPROVE]improvement — theDeriveguard againstGuid.Emptywas surfaced as a robustness gap; fixed atomically in 0cdc40d.(Zero unfixed findings — Ready-eligible.)
Depends on