feat(commons): TrackedOperationId strong type (#23 M3)

This commit is contained in:
Joseph Doherty
2026-05-20 13:47:40 -04:00
parent 4ca0b3ce2a
commit 1c38dd540f
2 changed files with 133 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
namespace ScadaLink.Commons.Types;
/// <summary>
/// Strongly-typed identifier for a cached outbound operation
/// (<c>ExternalSystem.CachedCall</c> / <c>Database.CachedWrite</c>) — the unified
/// tracking handle introduced by Audit Log #23 (M3). The same id is the
/// idempotency key end-to-end: it is stamped on every <c>AuditLog</c> row
/// produced for the operation's lifecycle (CachedSubmit → ApiCallCached /
/// DbWriteCached × N attempts → CachedResolve) and is the PK on the central
/// <c>SiteCalls</c> row that mirrors the operation's operational state.
/// </summary>
/// <remarks>
/// <para>
/// The struct wraps a <see cref="Guid"/> so it serialises identically to a
/// 36-character "D"-format string anywhere the existing GUID conventions are
/// used (gRPC strings, JSON, SQL TEXT columns). <see cref="ToString"/> returns
/// the lower-case 8-4-4-4-12 form unconditionally; never the brace- / parens-
/// wrapped variants — central ingest parses with <see cref="Guid.Parse"/>, which
/// is format-tolerant but the wire shape is fixed for log readability.
/// </para>
/// </remarks>
public readonly record struct TrackedOperationId(Guid Value)
{
/// <summary>Mint a fresh id at the call site (script-thread safe).</summary>
public static TrackedOperationId New() => new(Guid.NewGuid());
/// <summary>
/// Parse a serialised id back into the strong type. Throws when the input
/// is not a valid GUID — callers crossing untrusted boundaries should use
/// <see cref="TryParse"/> instead.
/// </summary>
public static TrackedOperationId Parse(string s) => new(Guid.Parse(s));
/// <summary>
/// Attempt to parse a serialised id. Returns <c>false</c> for null, empty
/// or non-GUID input; <paramref name="result"/> is <c>default</c> on
/// failure.
/// </summary>
public static bool TryParse(string? s, out TrackedOperationId result)
{
if (Guid.TryParse(s, out var g))
{
result = new TrackedOperationId(g);
return true;
}
result = default;
return false;
}
/// <inheritdoc/>
public override string ToString() => Value.ToString("D");
}