sprints work
This commit is contained in:
93
src/__Libraries/StellaOps.Resolver/NodeId.cs
Normal file
93
src/__Libraries/StellaOps.Resolver/NodeId.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* NodeId - Content-Addressed Node Identifier
|
||||
* Sprint: SPRINT_9100_0001_0001 (Core Resolver Package)
|
||||
* Task: RESOLVER-9100-002
|
||||
*
|
||||
* A content-addressed identifier for graph nodes.
|
||||
* NodeId = sha256(normalize(kind + ":" + key))
|
||||
*
|
||||
* Guarantees:
|
||||
* - Same (kind, key) → same NodeId
|
||||
* - Different (kind, key) → different NodeId (collision resistant)
|
||||
* - Deterministic ordering via ordinal string comparison
|
||||
*/
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Resolver;
|
||||
|
||||
/// <summary>
|
||||
/// Content-addressed node identifier computed as SHA256 of normalized kind:key.
|
||||
/// Immutable value type for deterministic graph operations.
|
||||
/// </summary>
|
||||
public readonly record struct NodeId : IComparable<NodeId>, IEquatable<NodeId>
|
||||
{
|
||||
private readonly string _value;
|
||||
|
||||
/// <summary>
|
||||
/// The SHA256 hex digest (lowercase, 64 characters).
|
||||
/// </summary>
|
||||
public string Value => _value ?? string.Empty;
|
||||
|
||||
private NodeId(string value) => _value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NodeId from a pre-computed digest value.
|
||||
/// Use <see cref="From(string, string)"/> for computing from kind/key.
|
||||
/// </summary>
|
||||
/// <param name="digest">A valid SHA256 hex digest (64 lowercase hex chars).</param>
|
||||
public static NodeId FromDigest(string digest)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(digest);
|
||||
if (digest.Length != 64)
|
||||
throw new ArgumentException("NodeId digest must be 64 hex characters", nameof(digest));
|
||||
|
||||
return new NodeId(digest.ToLowerInvariant());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a NodeId from kind and key.
|
||||
/// Applies NFC normalization before hashing.
|
||||
/// </summary>
|
||||
/// <param name="kind">Node kind (e.g., "package", "file", "symbol").</param>
|
||||
/// <param name="key">Node key (e.g., PURL, file path, symbol name).</param>
|
||||
/// <returns>Content-addressed NodeId.</returns>
|
||||
public static NodeId From(string kind, string key)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(kind);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
||||
|
||||
// NFC normalize inputs for Unicode consistency
|
||||
var normalizedKind = kind.Normalize(NormalizationForm.FormC);
|
||||
var normalizedKey = key.Normalize(NormalizationForm.FormC);
|
||||
|
||||
var input = $"{normalizedKind}:{normalizedKey}";
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
|
||||
var digest = Convert.ToHexString(hash).ToLowerInvariant();
|
||||
|
||||
return new NodeId(digest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ordinal comparison for deterministic ordering.
|
||||
/// </summary>
|
||||
public int CompareTo(NodeId other)
|
||||
=> string.Compare(Value, other.Value, StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Equality is based on digest value.
|
||||
/// </summary>
|
||||
public bool Equals(NodeId other)
|
||||
=> string.Equals(Value, other.Value, StringComparison.Ordinal);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Value.GetHashCode(StringComparison.Ordinal);
|
||||
|
||||
public override string ToString() => Value;
|
||||
|
||||
public static bool operator <(NodeId left, NodeId right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(NodeId left, NodeId right) => left.CompareTo(right) > 0;
|
||||
public static bool operator <=(NodeId left, NodeId right) => left.CompareTo(right) <= 0;
|
||||
public static bool operator >=(NodeId left, NodeId right) => left.CompareTo(right) >= 0;
|
||||
}
|
||||
Reference in New Issue
Block a user