audit, advisories and doctors/setup work
This commit is contained in:
@@ -6,9 +6,9 @@ namespace StellaOps.Concelier.WebService.Diagnostics;
|
||||
/// </summary>
|
||||
public static class ErrorCodes
|
||||
{
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Validation Errors (4xx)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Generic validation failure.</summary>
|
||||
public const string ValidationFailed = "VALIDATION_FAILED";
|
||||
@@ -34,9 +34,9 @@ public static class ErrorCodes
|
||||
/// <summary>Invalid pagination parameters.</summary>
|
||||
public const string InvalidPagination = "INVALID_PAGINATION";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Resource Errors (404)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Requested resource was not found.</summary>
|
||||
public const string ResourceNotFound = "RESOURCE_NOT_FOUND";
|
||||
@@ -80,9 +80,9 @@ public static class ErrorCodes
|
||||
/// <summary>Feature is disabled.</summary>
|
||||
public const string FeatureDisabled = "FEATURE_DISABLED";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// AOC (Aggregation-Only Contract) Errors
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>AOC violation occurred.</summary>
|
||||
public const string AocViolation = "AOC_VIOLATION";
|
||||
@@ -99,9 +99,9 @@ public static class ErrorCodes
|
||||
/// <summary>Unknown field detected (ERR_AOC_007).</summary>
|
||||
public const string AocUnknownField = "AOC_UNKNOWN_FIELD";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Conflict Errors (409)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resource already exists.</summary>
|
||||
public const string ResourceConflict = "RESOURCE_CONFLICT";
|
||||
@@ -112,9 +112,9 @@ public static class ErrorCodes
|
||||
/// <summary>Lease already held by another client.</summary>
|
||||
public const string LeaseConflict = "LEASE_CONFLICT";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// State Errors (423 Locked)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Resource is locked.</summary>
|
||||
public const string ResourceLocked = "RESOURCE_LOCKED";
|
||||
@@ -122,9 +122,9 @@ public static class ErrorCodes
|
||||
/// <summary>Lease rejected.</summary>
|
||||
public const string LeaseRejected = "LEASE_REJECTED";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// AirGap/Sealed Mode Errors
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>AirGap mode is disabled.</summary>
|
||||
public const string AirGapDisabled = "AIRGAP_DISABLED";
|
||||
@@ -138,9 +138,9 @@ public static class ErrorCodes
|
||||
/// <summary>Source blocked by sealed mode.</summary>
|
||||
public const string SourceBlocked = "SOURCE_BLOCKED";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rate Limiting (429)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Rate limit exceeded.</summary>
|
||||
public const string RateLimitExceeded = "RATE_LIMIT_EXCEEDED";
|
||||
@@ -148,9 +148,9 @@ public static class ErrorCodes
|
||||
/// <summary>Quota exceeded.</summary>
|
||||
public const string QuotaExceeded = "QUOTA_EXCEEDED";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Server Errors (5xx)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Internal server error.</summary>
|
||||
public const string InternalError = "INTERNAL_ERROR";
|
||||
|
||||
@@ -127,6 +127,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
|
||||
string source,
|
||||
[FromBody] RawAdvisoryRequest request,
|
||||
[FromServices] ICanonicalAdvisoryService service,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext context,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
@@ -158,7 +159,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
|
||||
Summary = request.Summary,
|
||||
VendorStatus = request.VendorStatus,
|
||||
RawPayloadJson = request.RawPayloadJson,
|
||||
FetchedAt = request.FetchedAt ?? DateTimeOffset.UtcNow
|
||||
FetchedAt = request.FetchedAt ?? timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
var result = await service.IngestAsync(source, rawAdvisory, ct).ConfigureAwait(false);
|
||||
@@ -188,6 +189,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
|
||||
string source,
|
||||
[FromBody] IEnumerable<RawAdvisoryRequest> requests,
|
||||
[FromServices] ICanonicalAdvisoryService service,
|
||||
TimeProvider timeProvider,
|
||||
HttpContext context,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
@@ -196,6 +198,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
|
||||
return HttpResults.BadRequest(new { error = "Source is required" });
|
||||
}
|
||||
|
||||
var defaultFetchedAt = timeProvider.GetUtcNow();
|
||||
var rawAdvisories = requests.Select(request => new RawAdvisory
|
||||
{
|
||||
SourceAdvisoryId = request.SourceAdvisoryId ?? $"{source.ToUpperInvariant()}-{request.Cve}",
|
||||
@@ -209,7 +212,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
|
||||
Summary = request.Summary,
|
||||
VendorStatus = request.VendorStatus,
|
||||
RawPayloadJson = request.RawPayloadJson,
|
||||
FetchedAt = request.FetchedAt ?? DateTimeOffset.UtcNow
|
||||
FetchedAt = request.FetchedAt ?? defaultFetchedAt
|
||||
}).ToList();
|
||||
|
||||
var results = await service.IngestBatchAsync(source, rawAdvisories, ct).ConfigureAwait(false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Federation.Export;
|
||||
@@ -25,6 +26,7 @@ internal static class FederationEndpointExtensions
|
||||
HttpContext context,
|
||||
[FromServices] IBundleExportService exportService,
|
||||
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken,
|
||||
[FromQuery(Name = "since_cursor")] string? sinceCursor = null,
|
||||
[FromQuery] bool sign = true,
|
||||
@@ -57,8 +59,9 @@ internal static class FederationEndpointExtensions
|
||||
|
||||
// Set response headers for streaming
|
||||
context.Response.ContentType = "application/zstd";
|
||||
var exportTimestamp = timeProvider.GetUtcNow().UtcDateTime;
|
||||
context.Response.Headers.ContentDisposition =
|
||||
$"attachment; filename=\"feedser-bundle-{DateTime.UtcNow:yyyyMMdd-HHmmss}.zst\"";
|
||||
$"attachment; filename=\"feedser-bundle-{exportTimestamp.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.zst\"";
|
||||
|
||||
// Export directly to response stream
|
||||
var result = await exportService.ExportToStreamAsync(
|
||||
|
||||
@@ -119,6 +119,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
group.MapPost("/scores/recalculate", async (
|
||||
[FromBody] RecalculateRequest? request,
|
||||
[FromServices] IInterestScoringService scoringService,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
int updated;
|
||||
@@ -137,7 +138,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
{
|
||||
Updated = updated,
|
||||
Mode = request?.CanonicalIds?.Count > 0 ? "batch" : "full",
|
||||
StartedAt = DateTimeOffset.UtcNow
|
||||
StartedAt = timeProvider.GetUtcNow()
|
||||
});
|
||||
})
|
||||
.WithName("RecalculateScores")
|
||||
@@ -149,6 +150,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
[FromBody] DegradeRequest? request,
|
||||
[FromServices] IInterestScoringService scoringService,
|
||||
[FromServices] Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var threshold = request?.Threshold ?? options.Value.DegradationPolicy.DegradationThreshold;
|
||||
@@ -159,7 +161,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
{
|
||||
Degraded = degraded,
|
||||
Threshold = threshold,
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
ExecutedAt = timeProvider.GetUtcNow()
|
||||
});
|
||||
})
|
||||
.WithName("DegradeToStubs")
|
||||
@@ -171,6 +173,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
[FromBody] RestoreRequest? request,
|
||||
[FromServices] IInterestScoringService scoringService,
|
||||
[FromServices] Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var threshold = request?.Threshold ?? options.Value.DegradationPolicy.RestorationThreshold;
|
||||
@@ -181,7 +184,7 @@ internal static class InterestScoreEndpointExtensions
|
||||
{
|
||||
Restored = restored,
|
||||
Threshold = threshold,
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
ExecutedAt = timeProvider.GetUtcNow()
|
||||
});
|
||||
})
|
||||
.WithName("RestoreFromStubs")
|
||||
|
||||
@@ -497,6 +497,7 @@ builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
var app = builder.Build();
|
||||
var appTimeProvider = app.Services.GetRequiredService<TimeProvider>();
|
||||
var swaggerEnabled = app.Configuration.GetValue<bool>("Swagger:Enabled");
|
||||
|
||||
app.Logger.LogWarning("Authority enabled: {AuthorityEnabled}, test signing secret configured: {HasTestSecret}", authorityConfigured, !string.IsNullOrWhiteSpace(concelierOptions.Authority?.TestSigningSecret));
|
||||
@@ -724,6 +725,7 @@ orchestratorGroup.MapPost("/commands", async (
|
||||
HttpContext context,
|
||||
[FromBody] OrchestratorCommandRequest request,
|
||||
[FromServices] IOrchestratorRegistryStore store,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveTenant(context, requireHeader: true, out var tenant, out var tenantError))
|
||||
@@ -757,7 +759,7 @@ orchestratorGroup.MapPost("/commands", async (
|
||||
request.Backfill is null
|
||||
? null
|
||||
: new OrchestratorBackfillRange(request.Backfill.FromCursor, request.Backfill.ToCursor),
|
||||
DateTimeOffset.UtcNow,
|
||||
timeProvider.GetUtcNow(),
|
||||
request.ExpiresAt);
|
||||
|
||||
await store.EnqueueCommandAsync(command, cancellationToken).ConfigureAwait(false);
|
||||
@@ -1226,7 +1228,7 @@ advisoryIngestEndpoint.RequireAocGuard<AdvisoryIngestRequest>(request =>
|
||||
return Array.Empty<object?>();
|
||||
}
|
||||
|
||||
var guardDocument = AdvisoryRawRequestMapper.Map(request, "guard-tenant", TimeProvider.System);
|
||||
var guardDocument = AdvisoryRawRequestMapper.Map(request, "guard-tenant", appTimeProvider);
|
||||
return new object?[] { guardDocument };
|
||||
}, guardOptions: advisoryIngestGuardOptions);
|
||||
|
||||
@@ -3399,7 +3401,7 @@ void ApplyNoCache(HttpResponse response)
|
||||
response.Headers["Expires"] = "0";
|
||||
}
|
||||
|
||||
await InitializePostgresAsync(app);
|
||||
await InitializePostgresAsync(app, app.Lifetime.ApplicationStopping);
|
||||
|
||||
app.MapGet("/health", ([FromServices] IOptions<ConcelierOptions> opts, [FromServices] StellaOps.Concelier.WebService.Diagnostics.ServiceStatus status, HttpContext context) =>
|
||||
{
|
||||
@@ -4164,7 +4166,7 @@ static SignalsSymbolSetResponse ToSymbolSetResponse(AffectedSymbolSet symbolSet)
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
static async Task InitializePostgresAsync(WebApplication app)
|
||||
static async Task InitializePostgresAsync(WebApplication app, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = app.Services.GetService<ConcelierDataSource>();
|
||||
var status = app.Services.GetRequiredService<StellaOps.Concelier.WebService.Diagnostics.ServiceStatus>();
|
||||
@@ -4178,7 +4180,7 @@ static async Task InitializePostgresAsync(WebApplication app)
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
var (ready, latency, error) = await CheckPostgresAsync(dataSource, CancellationToken.None).ConfigureAwait(false);
|
||||
var (ready, latency, error) = await CheckPostgresAsync(dataSource, cancellationToken).ConfigureAwait(false);
|
||||
stopwatch.Stop();
|
||||
status.RecordStorageCheck(ready, latency, error);
|
||||
if (ready)
|
||||
|
||||
@@ -46,9 +46,9 @@ public static class ConcelierProblemResultFactory
|
||||
return Microsoft.AspNetCore.Http.Results.Json(envelope, statusCode: statusCode);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Validation Errors (400)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 400 Bad Request response for validation failure.
|
||||
@@ -132,9 +132,9 @@ public static class ConcelierProblemResultFactory
|
||||
"cursor");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Not Found Errors (404)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 404 Not Found response for resource not found.
|
||||
@@ -307,9 +307,9 @@ public static class ConcelierProblemResultFactory
|
||||
detail ?? "The requested resource was not found.");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Conflict Errors (409)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 409 Conflict response.
|
||||
@@ -338,9 +338,9 @@ public static class ConcelierProblemResultFactory
|
||||
return Conflict(context, ErrorCodes.LeaseConflict, detail);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Locked Errors (423)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 423 Locked response.
|
||||
@@ -373,9 +373,9 @@ public static class ConcelierProblemResultFactory
|
||||
detail);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// AirGap/Sealed Mode Errors
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 404 Not Found response for AirGap disabled.
|
||||
@@ -483,9 +483,9 @@ public static class ConcelierProblemResultFactory
|
||||
return Microsoft.AspNetCore.Http.Results.Json(envelope, statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rate Limiting (429)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 429 Too Many Requests response.
|
||||
@@ -511,9 +511,9 @@ public static class ConcelierProblemResultFactory
|
||||
return Microsoft.AspNetCore.Http.Results.Json(envelope, statusCode: StatusCodes.Status429TooManyRequests);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
// Server Errors (5xx)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 500 Internal Server Error response.
|
||||
|
||||
@@ -7,4 +7,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0242-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0242-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0242-A | TODO | Revalidated 2026-01-07 (open findings). |
|
||||
| AUDIT-0242-A | DONE | Applied 2026-01-13; TimeProvider defaults, ASCII cleanup, federation tests. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Concelier – Link-Not-Merge Policy APIs
|
||||
title: StellaOps Concelier - Link-Not-Merge Policy APIs
|
||||
version: "1.0.0"
|
||||
description: |
|
||||
Fact-only advisory/linkset retrieval for Policy Engine consumers.
|
||||
|
||||
Reference in New Issue
Block a user