using StellaOps.AirGap.Controller.Domain; using StellaOps.AirGap.Controller.Stores; using StellaOps.AirGap.Time.Models; using StellaOps.AirGap.Time.Services; namespace StellaOps.AirGap.Controller.Services; public sealed class AirGapStateService { private readonly IAirGapStateStore _store; private readonly StalenessCalculator _stalenessCalculator; public AirGapStateService(IAirGapStateStore store, StalenessCalculator stalenessCalculator) { _store = store; _stalenessCalculator = stalenessCalculator; } public async Task SealAsync( string tenantId, string policyHash, TimeAnchor timeAnchor, StalenessBudget budget, DateTimeOffset nowUtc, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(policyHash); budget.Validate(); var newState = new AirGapState { TenantId = tenantId, Sealed = true, PolicyHash = policyHash, TimeAnchor = timeAnchor, StalenessBudget = budget, LastTransitionAt = nowUtc }; await _store.SetAsync(newState, cancellationToken); return newState; } public async Task UnsealAsync( string tenantId, DateTimeOffset nowUtc, CancellationToken cancellationToken = default) { var current = await _store.GetAsync(tenantId, cancellationToken); var newState = current with { Sealed = false, LastTransitionAt = nowUtc }; await _store.SetAsync(newState, cancellationToken); return newState; } public async Task GetStatusAsync( string tenantId, DateTimeOffset nowUtc, CancellationToken cancellationToken = default) { var state = await _store.GetAsync(tenantId, cancellationToken); var staleness = _stalenessCalculator.Evaluate(state.TimeAnchor, state.StalenessBudget, nowUtc); return new AirGapStatus(state, staleness, nowUtc); } } public sealed record AirGapStatus(AirGapState State, StalenessEvaluation Staleness, DateTimeOffset EvaluatedAt);