Skip to content

fix(agent): reject non-UUID AgentUuid on override + persisted paths#207

Draft
ottobolyos wants to merge 2 commits into
TrakHound:masterfrom
ottobolyos:fix/agent-uuid-lifecycle-hardening
Draft

fix(agent): reject non-UUID AgentUuid on override + persisted paths#207
ottobolyos wants to merge 2 commits into
TrakHound:masterfrom
ottobolyos:fix/agent-uuid-lifecycle-hardening

Conversation

@ottobolyos

@ottobolyos ottobolyos commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

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.json paths. 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

  • New AgentUuidResolver.Resolve (public static, MTConnect.NET-Common) implements the three-path algorithm:
    • Path 1 — validated operator override wins;
    • Path 2 — validated persisted state wins over derivation;
    • Path 3 — deterministic UUID v5 derivation from ServiceName.
      Malformed input on Path 1 OR Path 2 logs a warning via a delegate hook (so MTConnect.NET-Common takes 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.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 renamed normalisednormalized per CONVENTIONS §1.0d-decies (AmE in committed code); XML-doc verbs updated to match. Positional / out var callers unaffected.
  • Test fixtures brought forwardAgentUuidConfigOverrideTests + AgentUuidLongitudinalInvariantsTests now route their boot simulation through AgentUuidResolver.Resolve instead of inline replay, and their fixture literals are canonical RFC 4122 UUIDs (previously non-UUID strings that survived only because the inline replay omitted TryValidate — fake-green). Every fixture marked [NonParallelizable] with orphan-.bak.* sweep.

Test plan

Commits (RED → GREEN):

  1. test(common-tests): pin AgentUuid RFC 4122 contract on both resolution paths (RED) — compile-error RED per §1.0d-vicies-semel (tests reference AgentUuidResolver which lands in commit 2).
  2. fix(agent): reject non-UUID AgentUuid on override AND persisted paths — introduces the resolver, wires RunAgent through it, renames normalisednormalized.

https://claude.ai/code/session_015FLSuNX8hZPFiTGtE8GYqH

@ottobolyos
ottobolyos force-pushed the fix/agent-uuid-lifecycle-hardening branch from 09452c1 to fadff28 Compare July 22, 2026 11:14
@ottobolyos ottobolyos changed the title fix(agent): validate operator-supplied AgentUuid; warn+fallback on malformed input fix(agent): reject non-UUID AgentUuid on override + persisted paths Jul 22, 2026
@ottobolyos
ottobolyos force-pushed the fix/agent-uuid-lifecycle-hardening branch from fadff28 to 8562d9e Compare July 22, 2026 11:17
…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 CONVENTIONS §1.0d-vicies-semel's behavioral-RED default.

Claude-Session: https://claude.ai/code/session_015FLSuNX8hZPFiTGtE8GYqH
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 normalised
  renamed to normalized per CONVENTIONS §1.0d-decies (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.

Claude-Session: https://claude.ai/code/session_015FLSuNX8hZPFiTGtE8GYqH
@ottobolyos
ottobolyos force-pushed the fix/agent-uuid-lifecycle-hardening branch from 8562d9e to 30869b3 Compare July 23, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant