audit, advisories and doctors/setup work

This commit is contained in:
master
2026-01-13 18:53:39 +02:00
parent 9ca7cb183e
commit d7be6ba34b
811 changed files with 54242 additions and 4056 deletions

View File

@@ -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";

View File

@@ -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);

View File

@@ -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(

View File

@@ -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")

View File

@@ -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)

View File

@@ -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.

View File

@@ -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. |

View File

@@ -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.