Merge remote changes (theirs)

This commit is contained in:
Codex Assistant
2026-01-08 09:01:53 +02:00
4195 changed files with 249446 additions and 83444 deletions

View File

@@ -2,12 +2,10 @@
// Sprint: SPRINT_20251226_003_BE_exception_approval
// Task: EXCEPT-05, EXCEPT-06, EXCEPT-07 - Exception approval API endpoints
using System.Globalization;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Persistence.Postgres.Models;
using StellaOps.Policy.Persistence.Postgres.Repositories;
@@ -91,8 +89,7 @@ public static class ExceptionApprovalEndpoints
CreateApprovalRequestDto request,
IExceptionApprovalRepository repository,
IExceptionApprovalRulesService rulesService,
TimeProvider timeProvider,
IGuidProvider guidProvider,
[FromServices] TimeProvider timeProvider,
ILogger<ExceptionApprovalRequestEntity> logger,
CancellationToken cancellationToken)
{
@@ -114,8 +111,7 @@ public static class ExceptionApprovalEndpoints
}
// Generate request ID
var now = timeProvider.GetUtcNow();
var requestId = $"EAR-{now.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}-{guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture)[..8].ToUpperInvariant()}";
var requestId = $"EAR-{timeProvider.GetUtcNow():yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
// Parse gate level
if (!Enum.TryParse<GateLevel>(request.GateLevel, ignoreCase: true, out var gateLevel))
@@ -144,9 +140,10 @@ public static class ExceptionApprovalEndpoints
});
}
var now = timeProvider.GetUtcNow();
var entity = new ExceptionApprovalRequestEntity
{
Id = guidProvider.NewGuid(),
Id = Guid.NewGuid(),
RequestId = requestId,
TenantId = tenantId,
ExceptionId = request.ExceptionId,
@@ -208,7 +205,7 @@ public static class ExceptionApprovalEndpoints
// Record audit entry
await repository.RecordAuditAsync(new ExceptionApprovalAuditEntity
{
Id = guidProvider.NewGuid(),
Id = Guid.NewGuid(),
RequestId = requestId,
TenantId = tenantId,
SequenceNumber = 1,

View File

@@ -8,7 +8,6 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Repositories;
using StellaOps.Policy.Gateway.Contracts;
@@ -135,8 +134,7 @@ public static class ExceptionEndpoints
CreateExceptionRequest request,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
IGuidProvider guidProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
if (request is null)
@@ -148,10 +146,8 @@ public static class ExceptionEndpoints
});
}
var now = timeProvider.GetUtcNow();
// Validate expiry is in future
if (request.ExpiresAt <= now)
if (request.ExpiresAt <= timeProvider.GetUtcNow())
{
return Results.BadRequest(new ProblemDetails
{
@@ -162,7 +158,7 @@ public static class ExceptionEndpoints
}
// Validate expiry is not more than 1 year
if (request.ExpiresAt > now.AddYears(1))
if (request.ExpiresAt > timeProvider.GetUtcNow().AddYears(1))
{
return Results.BadRequest(new ProblemDetails
{
@@ -175,7 +171,7 @@ public static class ExceptionEndpoints
var actorId = GetActorId(context);
var clientInfo = GetClientInfo(context);
var exceptionId = $"EXC-{guidProvider.NewGuid():N}"[..20];
var exceptionId = $"EXC-{Guid.NewGuid():N}"[..20];
var exception = new ExceptionObject
{
@@ -193,8 +189,8 @@ public static class ExceptionEndpoints
},
OwnerId = request.OwnerId,
RequesterId = actorId,
CreatedAt = now,
UpdatedAt = now,
CreatedAt = timeProvider.GetUtcNow(),
UpdatedAt = timeProvider.GetUtcNow(),
ExpiresAt = request.ExpiresAt,
ReasonCode = ParseReasonRequired(request.ReasonCode),
Rationale = request.Rationale,
@@ -215,7 +211,7 @@ public static class ExceptionEndpoints
UpdateExceptionRequest request,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var existing = await repository.GetByIdAsync(id, cancellationToken);
@@ -264,7 +260,7 @@ public static class ExceptionEndpoints
ApproveExceptionRequest? request,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var existing = await repository.GetByIdAsync(id, cancellationToken);
@@ -297,13 +293,12 @@ public static class ExceptionEndpoints
});
}
var now = timeProvider.GetUtcNow();
var updated = existing with
{
Version = existing.Version + 1,
Status = ExceptionStatus.Approved,
UpdatedAt = now,
ApprovedAt = now,
UpdatedAt = timeProvider.GetUtcNow(),
ApprovedAt = timeProvider.GetUtcNow(),
ApproverIds = existing.ApproverIds.Add(actorId)
};
@@ -318,7 +313,7 @@ public static class ExceptionEndpoints
string id,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var existing = await repository.GetByIdAsync(id, cancellationToken);
@@ -359,7 +354,7 @@ public static class ExceptionEndpoints
ExtendExceptionRequest request,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var existing = await repository.GetByIdAsync(id, cancellationToken);
@@ -410,7 +405,7 @@ public static class ExceptionEndpoints
[FromBody] RevokeExceptionRequest? request,
HttpContext context,
IExceptionRepository repository,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
var existing = await repository.GetByIdAsync(id, cancellationToken);

View File

@@ -2,12 +2,10 @@
// Sprint: SPRINT_20251226_001_BE_cicd_gate_integration
// Task: CICD-GATE-01 - Create POST /api/v1/policy/gate/evaluate endpoint
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Audit;
using StellaOps.Policy.Deltas;
using StellaOps.Policy.Engine.Gates;
@@ -41,8 +39,7 @@ public static class GateEndpoints
IBaselineSelector baselineSelector,
IGateBypassAuditor bypassAuditor,
IMemoryCache cache,
TimeProvider timeProvider,
IGuidProvider guidProvider,
[FromServices] TimeProvider timeProvider,
ILogger<DriftGateEvaluator> logger,
CancellationToken cancellationToken) =>
{
@@ -83,7 +80,7 @@ public static class GateEndpoints
return Results.Ok(new GateEvaluateResponse
{
DecisionId = $"gate:{timeProvider.GetUtcNow().ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}:{guidProvider.NewGuid():N}",
DecisionId = $"gate:{timeProvider.GetUtcNow():yyyyMMddHHmmss}:{Guid.NewGuid():N}",
Status = GateStatus.Pass,
ExitCode = GateExitCodes.Pass,
ImageDigest = request.ImageDigest,
@@ -228,7 +225,8 @@ public static class GateEndpoints
.WithDescription("Retrieve a previous gate evaluation decision by ID");
// GET /api/v1/policy/gate/health - Health check for gate service
gates.MapGet("/health", (TimeProvider timeProvider) => Results.Ok(new { status = "healthy", timestamp = timeProvider.GetUtcNow() }))
gates.MapGet("/health", ([FromServices] TimeProvider timeProvider) =>
Results.Ok(new { status = "healthy", timestamp = timeProvider.GetUtcNow() }))
.WithName("GateHealth")
.WithDescription("Health check for the gate evaluation service");
}

View File

@@ -3,10 +3,9 @@
// Task: GOV-018 - Sealed mode overrides and risk profile events endpoints
using System.Collections.Concurrent;
using System.Globalization;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Determinism.Abstractions;
namespace StellaOps.Policy.Gateway.Endpoints;
@@ -102,11 +101,11 @@ public static class GovernanceEndpoints
private static Task<IResult> GetSealedModeStatusAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
[FromQuery] string? tenantId)
{
var tenant = tenantId ?? GetTenantId(httpContext) ?? "default";
var state = SealedModeStates.GetOrAdd(tenant, _ => new SealedModeState());
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var response = new SealedModeStatusResponse
{
@@ -121,7 +120,7 @@ public static class GovernanceEndpoints
.Select(MapOverrideToResponse)
.ToList(),
VerificationStatus = "verified",
LastVerifiedAt = timeProvider.GetUtcNow().ToString("O")
LastVerifiedAt = timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
};
return Task.FromResult(Results.Ok(response));
@@ -143,20 +142,21 @@ public static class GovernanceEndpoints
private static Task<IResult> ToggleSealedModeAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
SealedModeToggleRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
var state = SealedModeStates.GetOrAdd(tenant, _ => new SealedModeState());
if (request.Enable)
{
state = new SealedModeState
{
IsSealed = true,
SealedAt = now.ToString("O"),
SealedAt = now.ToString("O", CultureInfo.InvariantCulture),
SealedBy = actor,
Reason = request.Reason,
TrustRoots = request.TrustRoots ?? [],
@@ -168,7 +168,7 @@ public static class GovernanceEndpoints
state = new SealedModeState
{
IsSealed = false,
LastUnsealedAt = now.ToString("O")
LastUnsealedAt = now.ToString("O", CultureInfo.InvariantCulture)
};
}
@@ -176,7 +176,7 @@ public static class GovernanceEndpoints
// Audit
RecordAudit(tenant, actor, "sealed_mode_toggled", "sealed-mode", "system_config",
$"{(request.Enable ? "Enabled" : "Disabled")} sealed mode: {request.Reason}", timeProvider, guidProvider);
$"{(request.Enable ? "Enabled" : "Disabled")} sealed mode: {request.Reason}", timeProvider);
var response = new SealedModeStatusResponse
{
@@ -188,7 +188,7 @@ public static class GovernanceEndpoints
AllowedSources = state.AllowedSources,
Overrides = [],
VerificationStatus = "verified",
LastVerifiedAt = now.ToString("O")
LastVerifiedAt = now.ToString("O", CultureInfo.InvariantCulture)
};
return Task.FromResult(Results.Ok(response));
@@ -196,15 +196,14 @@ public static class GovernanceEndpoints
private static Task<IResult> CreateSealedModeOverrideAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
SealedModeOverrideRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
var overrideId = $"override-{guidProvider.NewGuid():N}";
var overrideId = $"override-{Guid.NewGuid():N}";
var entity = new SealedModeOverrideEntity
{
Id = overrideId,
@@ -212,30 +211,29 @@ public static class GovernanceEndpoints
Type = request.Type,
Target = request.Target,
Reason = request.Reason,
ApprovalId = $"approval-{guidProvider.NewGuid():N}",
ApprovalId = $"approval-{Guid.NewGuid():N}",
ApprovedBy = [actor],
ExpiresAt = now.AddHours(request.DurationHours).ToString("O"),
CreatedAt = now.ToString("O"),
ExpiresAt = now.AddHours(request.DurationHours).ToString("O", CultureInfo.InvariantCulture),
CreatedAt = now.ToString("O", CultureInfo.InvariantCulture),
Active = true
};
Overrides[overrideId] = entity;
RecordAudit(tenant, actor, "sealed_mode_override_created", overrideId, "sealed_mode_override",
$"Created override for {request.Target}: {request.Reason}", timeProvider, guidProvider);
$"Created override for {request.Target}: {request.Reason}", timeProvider);
return Task.FromResult(Results.Ok(MapOverrideToResponse(entity)));
}
private static Task<IResult> RevokeSealedModeOverrideAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
string overrideId,
RevokeOverrideRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
if (!Overrides.TryGetValue(overrideId, out var entity) || entity.TenantId != tenant)
{
@@ -250,7 +248,7 @@ public static class GovernanceEndpoints
Overrides[overrideId] = entity;
RecordAudit(tenant, actor, "sealed_mode_override_revoked", overrideId, "sealed_mode_override",
$"Revoked override: {request.Reason}", timeProvider, guidProvider);
$"Revoked override: {request.Reason}", timeProvider);
return Task.FromResult(Results.NoContent());
}
@@ -296,15 +294,14 @@ public static class GovernanceEndpoints
private static Task<IResult> CreateRiskProfileAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
CreateRiskProfileRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
var profileId = $"profile-{guidProvider.NewGuid():N}";
var profileId = $"profile-{Guid.NewGuid():N}";
var entity = new RiskProfileEntity
{
Id = profileId,
@@ -317,8 +314,8 @@ public static class GovernanceEndpoints
Signals = request.Signals ?? [],
SeverityOverrides = request.SeverityOverrides ?? [],
ActionOverrides = request.ActionOverrides ?? [],
CreatedAt = now.ToString("O"),
ModifiedAt = now.ToString("O"),
CreatedAt = now.ToString("O", CultureInfo.InvariantCulture),
ModifiedAt = now.ToString("O", CultureInfo.InvariantCulture),
CreatedBy = actor,
ModifiedBy = actor
};
@@ -326,20 +323,19 @@ public static class GovernanceEndpoints
RiskProfiles[profileId] = entity;
RecordAudit(tenant, actor, "risk_profile_created", profileId, "risk_profile",
$"Created risk profile: {request.Name}", timeProvider, guidProvider);
$"Created risk profile: {request.Name}", timeProvider);
return Task.FromResult(Results.Created($"/api/v1/governance/risk-profiles/{profileId}", MapProfileToResponse(entity)));
}
private static Task<IResult> UpdateRiskProfileAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
string profileId,
UpdateRiskProfileRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
if (!RiskProfiles.TryGetValue(profileId, out var existing))
@@ -358,26 +354,25 @@ public static class GovernanceEndpoints
Signals = request.Signals ?? existing.Signals,
SeverityOverrides = request.SeverityOverrides ?? existing.SeverityOverrides,
ActionOverrides = request.ActionOverrides ?? existing.ActionOverrides,
ModifiedAt = now.ToString("O"),
ModifiedAt = now.ToString("O", CultureInfo.InvariantCulture),
ModifiedBy = actor
};
RiskProfiles[profileId] = entity;
RecordAudit(tenant, actor, "risk_profile_updated", profileId, "risk_profile",
$"Updated risk profile: {entity.Name}", timeProvider, guidProvider);
$"Updated risk profile: {entity.Name}", timeProvider);
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
}
private static Task<IResult> DeleteRiskProfileAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
string profileId)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
if (!RiskProfiles.TryRemove(profileId, out var removed))
{
@@ -389,19 +384,18 @@ public static class GovernanceEndpoints
}
RecordAudit(tenant, actor, "risk_profile_deleted", profileId, "risk_profile",
$"Deleted risk profile: {removed.Name}", timeProvider, guidProvider);
$"Deleted risk profile: {removed.Name}", timeProvider);
return Task.FromResult(Results.NoContent());
}
private static Task<IResult> ActivateRiskProfileAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
string profileId)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
if (!RiskProfiles.TryGetValue(profileId, out var existing))
@@ -416,27 +410,26 @@ public static class GovernanceEndpoints
var entity = existing with
{
Status = "active",
ModifiedAt = now.ToString("O"),
ModifiedAt = now.ToString("O", CultureInfo.InvariantCulture),
ModifiedBy = actor
};
RiskProfiles[profileId] = entity;
RecordAudit(tenant, actor, "risk_profile_activated", profileId, "risk_profile",
$"Activated risk profile: {entity.Name}", timeProvider, guidProvider);
$"Activated risk profile: {entity.Name}", timeProvider);
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
}
private static Task<IResult> DeprecateRiskProfileAsync(
HttpContext httpContext,
[FromServices] TimeProvider timeProvider,
string profileId,
DeprecateProfileRequest request)
{
var tenant = GetTenantId(httpContext) ?? "default";
var actor = GetActorId(httpContext) ?? "system";
var timeProvider = httpContext.RequestServices.GetRequiredService<TimeProvider>();
var guidProvider = httpContext.RequestServices.GetRequiredService<IGuidProvider>();
var now = timeProvider.GetUtcNow();
if (!RiskProfiles.TryGetValue(profileId, out var existing))
@@ -451,7 +444,7 @@ public static class GovernanceEndpoints
var entity = existing with
{
Status = "deprecated",
ModifiedAt = now.ToString("O"),
ModifiedAt = now.ToString("O", CultureInfo.InvariantCulture),
ModifiedBy = actor,
DeprecationReason = request.Reason
};
@@ -459,7 +452,7 @@ public static class GovernanceEndpoints
RiskProfiles[profileId] = entity;
RecordAudit(tenant, actor, "risk_profile_deprecated", profileId, "risk_profile",
$"Deprecated risk profile: {entity.Name} - {request.Reason}", timeProvider, guidProvider);
$"Deprecated risk profile: {entity.Name} - {request.Reason}", timeProvider);
return Task.FromResult(Results.Ok(MapProfileToResponse(entity)));
}
@@ -559,7 +552,7 @@ public static class GovernanceEndpoints
{
if (RiskProfiles.IsEmpty)
{
var now = TimeProvider.System.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture);
var now = DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture);
RiskProfiles["profile-default"] = new RiskProfileEntity
{
Id = "profile-default",
@@ -599,15 +592,15 @@ public static class GovernanceEndpoints
?? httpContext.Request.Headers["X-StellaOps-Actor"].FirstOrDefault();
}
private static void RecordAudit(string tenantId, string actor, string eventType, string targetId, string targetType, string summary, TimeProvider timeProvider, IGuidProvider guidProvider)
private static void RecordAudit(string tenantId, string actor, string eventType, string targetId, string targetType, string summary, TimeProvider timeProvider)
{
var id = $"audit-{guidProvider.NewGuid():N}";
var id = $"audit-{Guid.NewGuid():N}";
AuditEntries[id] = new GovernanceAuditEntry
{
Id = id,
TenantId = tenantId,
Type = eventType,
Timestamp = timeProvider.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture),
Timestamp = timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
Actor = actor,
ActorType = "user",
TargetResource = targetId,

View File

@@ -50,7 +50,7 @@ internal static class RegistryWebhookEndpoints
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleDockerRegistryWebhook(
[FromBody] DockerRegistryNotification notification,
IGateEvaluationQueue evaluationQueue,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
ILogger<RegistryWebhookEndpointMarker> logger,
CancellationToken ct)
{
@@ -101,7 +101,7 @@ internal static class RegistryWebhookEndpoints
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleHarborWebhook(
[FromBody] HarborWebhookEvent notification,
IGateEvaluationQueue evaluationQueue,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
ILogger<RegistryWebhookEndpointMarker> logger,
CancellationToken ct)
{
@@ -161,7 +161,7 @@ internal static class RegistryWebhookEndpoints
private static async Task<Results<Accepted<WebhookAcceptedResponse>, ProblemHttpResult>> HandleGenericWebhook(
[FromBody] GenericRegistryWebhook notification,
IGateEvaluationQueue evaluationQueue,
TimeProvider timeProvider,
[FromServices] TimeProvider timeProvider,
ILogger<RegistryWebhookEndpointMarker> logger,
CancellationToken ct)
{

View File

@@ -5,11 +5,9 @@
// Description: In-memory queue for gate evaluation jobs with background processing
// -----------------------------------------------------------------------------
using System.Globalization;
using System.Threading.Channels;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Determinism.Abstractions;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Gateway.Endpoints;
@@ -24,14 +22,14 @@ public sealed class InMemoryGateEvaluationQueue : IGateEvaluationQueue
private readonly Channel<GateEvaluationJob> _channel;
private readonly ILogger<InMemoryGateEvaluationQueue> _logger;
private readonly TimeProvider _timeProvider;
private readonly IGuidProvider _guidProvider;
public InMemoryGateEvaluationQueue(ILogger<InMemoryGateEvaluationQueue> logger, TimeProvider timeProvider, IGuidProvider guidProvider)
public InMemoryGateEvaluationQueue(
ILogger<InMemoryGateEvaluationQueue> logger,
TimeProvider? timeProvider = null)
{
ArgumentNullException.ThrowIfNull(logger);
_logger = logger;
_timeProvider = timeProvider;
_guidProvider = guidProvider;
_timeProvider = timeProvider ?? TimeProvider.System;
// Bounded channel to prevent unbounded memory growth
_channel = Channel.CreateBounded<GateEvaluationJob>(new BoundedChannelOptions(1000)
@@ -74,8 +72,8 @@ public sealed class InMemoryGateEvaluationQueue : IGateEvaluationQueue
private string GenerateJobId()
{
// Format: gate-{timestamp}-{random}
var timestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
var random = _guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture)[..8];
var timestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds();
var random = Guid.NewGuid().ToString("N")[..8];
return $"gate-{timestamp}-{random}";
}
}
@@ -92,7 +90,7 @@ public sealed record GateEvaluationJob
/// <summary>
/// Background service that processes gate evaluation jobs from the queue.
/// Orchestrates: image analysis drift delta computation gate evaluation.
/// Orchestrates: image analysis -> drift delta computation -> gate evaluation.
/// </summary>
public sealed class GateEvaluationWorker : BackgroundService
{

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0445-M | DONE | Maintainability audit for StellaOps.Policy.Gateway. |
| AUDIT-0445-T | DONE | Test coverage audit for StellaOps.Policy.Gateway. |
| AUDIT-0445-A | TODO | APPLY pending approval for StellaOps.Policy.Gateway. |
| AUDIT-0445-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Policy.Gateway. |
| AUDIT-0445-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Policy.Gateway. |
| AUDIT-0445-A | TODO | Revalidated 2026-01-07 (open findings). |