DET-001/002/003: Add IGuidProvider abstraction and refactor Policy.Unknowns for determinism

- Created IGuidProvider interface and SystemGuidProvider in StellaOps.Determinism.Abstractions
- Added SequentialGuidProvider for testing deterministic GUID generation
- Added DeterminismServiceCollectionExtensions with AddDeterminismDefaults()
- Refactored Policy.Unknowns:
  - UnknownsRepository now uses TimeProvider and IGuidProvider
  - BudgetExceededEventFactory accepts optional TimeProvider parameter
  - ServiceCollectionExtensions calls AddDeterminismDefaults()
- Fixed Policy.Exceptions csproj (added ImplicitUsings, Nullable, PackageReferences)

Sprint: SPRINT_20260104_001_BE_determinism_timeprovider_injection
Tasks: DET-001 (audit), DET-002 (IGuidProvider), DET-003 (registration pattern), DET-004 (partial - Policy.Unknowns)
This commit is contained in:
StellaOps Bot
2026-01-04 12:37:12 +02:00
parent 3130cdb702
commit cb898a4ac8
9 changed files with 151 additions and 14 deletions

View File

@@ -0,0 +1,39 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace StellaOps.Determinism;
/// <summary>
/// Extension methods for registering determinism abstractions in DI.
/// </summary>
public static class DeterminismServiceCollectionExtensions
{
/// <summary>
/// Adds <see cref="TimeProvider.System"/> as a singleton.
/// </summary>
public static IServiceCollection AddSystemTimeProvider(this IServiceCollection services)
{
services.TryAddSingleton(TimeProvider.System);
return services;
}
/// <summary>
/// Adds <see cref="SystemGuidProvider"/> as the <see cref="IGuidProvider"/> singleton.
/// </summary>
public static IServiceCollection AddSystemGuidProvider(this IServiceCollection services)
{
services.TryAddSingleton<IGuidProvider, SystemGuidProvider>();
return services;
}
/// <summary>
/// Adds both <see cref="TimeProvider.System"/> and <see cref="SystemGuidProvider"/> as singletons.
/// This is the recommended setup for production services.
/// </summary>
public static IServiceCollection AddDeterminismDefaults(this IServiceCollection services)
{
services.AddSystemTimeProvider();
services.AddSystemGuidProvider();
return services;
}
}

View File

@@ -0,0 +1,64 @@
namespace StellaOps.Determinism;
/// <summary>
/// Abstraction for GUID generation to support deterministic testing.
/// Inject this instead of using <see cref="Guid.NewGuid"/> directly.
/// </summary>
public interface IGuidProvider
{
/// <summary>
/// Generates a new GUID.
/// </summary>
Guid NewGuid();
}
/// <summary>
/// Default implementation using <see cref="Guid.NewGuid"/>.
/// Register as singleton in DI: <c>services.AddSingleton&lt;IGuidProvider, SystemGuidProvider&gt;();</c>
/// </summary>
public sealed class SystemGuidProvider : IGuidProvider
{
/// <summary>
/// Shared instance for non-DI scenarios.
/// </summary>
public static readonly SystemGuidProvider Instance = new();
/// <inheritdoc />
public Guid NewGuid() => Guid.NewGuid();
}
/// <summary>
/// Deterministic GUID provider for testing. Returns GUIDs in a predictable sequence.
/// </summary>
public sealed class SequentialGuidProvider : IGuidProvider
{
private int _counter;
private readonly Guid _baseGuid;
/// <summary>
/// Creates a sequential GUID provider starting from a base GUID.
/// </summary>
/// <param name="baseGuid">Optional base GUID. Defaults to all zeros.</param>
public SequentialGuidProvider(Guid? baseGuid = null)
{
_baseGuid = baseGuid ?? Guid.Empty;
}
/// <inheritdoc />
public Guid NewGuid()
{
var bytes = _baseGuid.ToByteArray();
var counter = Interlocked.Increment(ref _counter);
// Embed counter in last 4 bytes
bytes[12] = (byte)(counter >> 24);
bytes[13] = (byte)(counter >> 16);
bytes[14] = (byte)(counter >> 8);
bytes[15] = (byte)counter;
return new Guid(bytes);
}
/// <summary>
/// Resets the counter to zero.
/// </summary>
public void Reset() => Interlocked.Exchange(ref _counter, 0);
}

View File

@@ -9,4 +9,8 @@
<Description>Attributes and abstractions for determinism enforcement in StellaOps.</Description>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
</Project>