more audit work

This commit is contained in:
master
2026-01-08 10:21:51 +02:00
parent 43c02081ef
commit 51cf4bc16c
546 changed files with 36721 additions and 4003 deletions

View File

@@ -25,8 +25,8 @@ internal static class AirGapEndpointExtensions
// GET /api/v1/concelier/airgap/catalog - Aggregated bundle catalog
group.MapGet("/catalog", async (
HttpContext context,
IBundleCatalogService catalogService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleCatalogService catalogService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromQuery] string? cursor,
[FromQuery] int? limit,
CancellationToken cancellationToken) =>
@@ -46,8 +46,8 @@ internal static class AirGapEndpointExtensions
// GET /api/v1/concelier/airgap/sources - List registered sources
group.MapGet("/sources", (
HttpContext context,
IBundleSourceRegistry sourceRegistry,
IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
[FromServices] IBundleSourceRegistry sourceRegistry,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
{
var airGapOptions = optionsMonitor.CurrentValue.AirGap;
if (!airGapOptions.Enabled)
@@ -62,8 +62,8 @@ internal static class AirGapEndpointExtensions
// POST /api/v1/concelier/airgap/sources - Register new source
group.MapPost("/sources", async (
HttpContext context,
IBundleSourceRegistry sourceRegistry,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleSourceRegistry sourceRegistry,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromBody] BundleSourceRegistration registration,
CancellationToken cancellationToken) =>
{
@@ -87,8 +87,8 @@ internal static class AirGapEndpointExtensions
// GET /api/v1/concelier/airgap/sources/{sourceId} - Get specific source
group.MapGet("/sources/{sourceId}", (
HttpContext context,
IBundleSourceRegistry sourceRegistry,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleSourceRegistry sourceRegistry,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string sourceId) =>
{
var airGapOptions = optionsMonitor.CurrentValue.AirGap;
@@ -109,8 +109,8 @@ internal static class AirGapEndpointExtensions
// DELETE /api/v1/concelier/airgap/sources/{sourceId} - Unregister source
group.MapDelete("/sources/{sourceId}", async (
HttpContext context,
IBundleSourceRegistry sourceRegistry,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleSourceRegistry sourceRegistry,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string sourceId,
CancellationToken cancellationToken) =>
{
@@ -131,8 +131,8 @@ internal static class AirGapEndpointExtensions
// POST /api/v1/concelier/airgap/sources/{sourceId}/validate - Validate source
group.MapPost("/sources/{sourceId}/validate", async (
HttpContext context,
IBundleSourceRegistry sourceRegistry,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleSourceRegistry sourceRegistry,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string sourceId,
CancellationToken cancellationToken) =>
{
@@ -151,8 +151,8 @@ internal static class AirGapEndpointExtensions
// GET /api/v1/concelier/airgap/status - Sealed-mode status
group.MapGet("/status", (
HttpContext context,
ISealedModeEnforcer sealedModeEnforcer,
IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
[FromServices] ISealedModeEnforcer sealedModeEnforcer,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
{
var airGapOptions = optionsMonitor.CurrentValue.AirGap;
if (!airGapOptions.Enabled)
@@ -168,9 +168,9 @@ internal static class AirGapEndpointExtensions
// Per CONCELIER-WEB-AIRGAP-58-001
group.MapPost("/bundles/{bundleId}/import", async (
HttpContext context,
IBundleCatalogService catalogService,
IBundleTimelineEmitter timelineEmitter,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleCatalogService catalogService,
[FromServices] IBundleTimelineEmitter timelineEmitter,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string bundleId,
[FromBody] BundleImportRequestDto requestDto,
CancellationToken cancellationToken) =>

View File

@@ -30,8 +30,8 @@ internal static class CanonicalAdvisoryEndpointExtensions
// GET /api/v1/canonical/{id} - Get canonical advisory by ID
group.MapGet("/{id:guid}", async (
Guid id,
ICanonicalAdvisoryService service,
IInterestScoringService? scoringService,
[FromServices] ICanonicalAdvisoryService service,
[FromServices] IInterestScoringService? scoringService,
HttpContext context,
CancellationToken ct) =>
{
@@ -63,7 +63,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
[FromQuery] string? mergeHash,
[FromQuery] int? offset,
[FromQuery] int? limit,
ICanonicalAdvisoryService service,
[FromServices] ICanonicalAdvisoryService service,
HttpContext context,
CancellationToken ct) =>
{
@@ -126,7 +126,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
group.MapPost("/ingest/{source}", async (
string source,
[FromBody] RawAdvisoryRequest request,
ICanonicalAdvisoryService service,
[FromServices] ICanonicalAdvisoryService service,
HttpContext context,
CancellationToken ct) =>
{
@@ -187,7 +187,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
group.MapPost("/ingest/{source}/batch", async (
string source,
[FromBody] IEnumerable<RawAdvisoryRequest> requests,
ICanonicalAdvisoryService service,
[FromServices] ICanonicalAdvisoryService service,
HttpContext context,
CancellationToken ct) =>
{
@@ -246,7 +246,7 @@ internal static class CanonicalAdvisoryEndpointExtensions
group.MapPatch("/{id:guid}/status", async (
Guid id,
[FromBody] UpdateStatusRequest request,
ICanonicalAdvisoryService service,
[FromServices] ICanonicalAdvisoryService service,
HttpContext context,
CancellationToken ct) =>
{
@@ -267,8 +267,8 @@ internal static class CanonicalAdvisoryEndpointExtensions
// GET /api/v1/canonical/{id}/provenance - Get provenance scopes for canonical
group.MapGet("/{id:guid}/provenance", async (
Guid id,
IProvenanceScopeService? provenanceService,
ICanonicalAdvisoryService canonicalService,
[FromServices] IProvenanceScopeService? provenanceService,
[FromServices] ICanonicalAdvisoryService canonicalService,
HttpContext context,
CancellationToken ct) =>
{

View File

@@ -23,8 +23,8 @@ internal static class FederationEndpointExtensions
// GET /api/v1/federation/export - Export delta bundle
group.MapGet("/export", async (
HttpContext context,
IBundleExportService exportService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleExportService exportService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "since_cursor")] string? sinceCursor = null,
[FromQuery] bool sign = true,
@@ -83,8 +83,8 @@ internal static class FederationEndpointExtensions
// GET /api/v1/federation/export/preview - Preview export statistics
group.MapGet("/export/preview", async (
HttpContext context,
IBundleExportService exportService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleExportService exportService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "since_cursor")] string? sinceCursor = null) =>
{
@@ -114,7 +114,7 @@ internal static class FederationEndpointExtensions
// GET /api/v1/federation/status - Federation status
group.MapGet("/status", (
HttpContext context,
IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor) =>
{
var options = optionsMonitor.CurrentValue;
@@ -134,8 +134,8 @@ internal static class FederationEndpointExtensions
// Per SPRINT_8200_0014_0003_CONCEL_bundle_import_merge Task 25-26.
group.MapPost("/import", async (
HttpContext context,
IBundleImportService importService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleImportService importService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "dry_run")] bool dryRun = false,
[FromQuery(Name = "skip_signature")] bool skipSignature = false,
@@ -230,8 +230,8 @@ internal static class FederationEndpointExtensions
// POST /api/v1/federation/import/validate - Validate bundle without importing
group.MapPost("/import/validate", async (
HttpContext context,
IBundleImportService importService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleImportService importService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken) =>
{
var options = optionsMonitor.CurrentValue;
@@ -264,8 +264,8 @@ internal static class FederationEndpointExtensions
// POST /api/v1/federation/import/preview - Preview import
group.MapPost("/import/preview", async (
HttpContext context,
IBundleImportService importService,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IBundleImportService importService,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken) =>
{
var options = optionsMonitor.CurrentValue;
@@ -313,8 +313,8 @@ internal static class FederationEndpointExtensions
// Per SPRINT_8200_0014_0003_CONCEL_bundle_import_merge Task 30.
group.MapGet("/sites", async (
HttpContext context,
ISyncLedgerRepository ledgerRepository,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] ISyncLedgerRepository ledgerRepository,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
CancellationToken cancellationToken,
[FromQuery(Name = "enabled_only")] bool enabledOnly = false) =>
{
@@ -350,8 +350,8 @@ internal static class FederationEndpointExtensions
// GET /api/v1/federation/sites/{siteId} - Get site details
group.MapGet("/sites/{siteId}", async (
HttpContext context,
ISyncLedgerRepository ledgerRepository,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] ISyncLedgerRepository ledgerRepository,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string siteId,
CancellationToken cancellationToken) =>
{
@@ -404,8 +404,8 @@ internal static class FederationEndpointExtensions
// Per SPRINT_8200_0014_0003_CONCEL_bundle_import_merge Task 31.
group.MapPut("/sites/{siteId}/policy", async (
HttpContext context,
ISyncLedgerRepository ledgerRepository,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] ISyncLedgerRepository ledgerRepository,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string siteId,
[FromBody] SitePolicyUpdateRequest request,
CancellationToken cancellationToken) =>

View File

@@ -79,8 +79,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> CreateSnapshotAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromBody] CreateSnapshotRequest? request,
CancellationToken cancellationToken)
{
@@ -129,8 +129,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> ListSnapshotsAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromQuery] int? limit,
[FromQuery] string? cursor,
CancellationToken cancellationToken)
@@ -165,8 +165,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> GetSnapshotAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string snapshotId,
CancellationToken cancellationToken)
{
@@ -201,8 +201,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> ExportSnapshotAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string snapshotId,
[FromQuery] string? format,
CancellationToken cancellationToken)
@@ -242,8 +242,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> ImportSnapshotAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
IFormFile file,
[FromQuery] bool? validate,
CancellationToken cancellationToken)
@@ -293,8 +293,8 @@ internal static class FeedSnapshotEndpointExtensions
private static async Task<IResult> ValidateSnapshotAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
string snapshotId,
CancellationToken cancellationToken)
{
@@ -330,8 +330,8 @@ internal static class FeedSnapshotEndpointExtensions
private static IResult ListSourcesAsync(
HttpContext context,
IFeedSnapshotCoordinator coordinator,
IOptionsMonitor<ConcelierOptions> optionsMonitor)
[FromServices] IFeedSnapshotCoordinator coordinator,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor)
{
var options = optionsMonitor.CurrentValue;

View File

@@ -28,7 +28,7 @@ internal static class InterestScoreEndpointExtensions
// GET /api/v1/canonical/{id}/score - Get interest score for a canonical advisory
group.MapGet("/canonical/{id:guid}/score", async (
Guid id,
IInterestScoringService scoringService,
[FromServices] IInterestScoringService scoringService,
CancellationToken ct) =>
{
var score = await scoringService.GetScoreAsync(id, ct).ConfigureAwait(false);
@@ -48,7 +48,7 @@ internal static class InterestScoreEndpointExtensions
[FromQuery] double? maxScore,
[FromQuery] int? offset,
[FromQuery] int? limit,
IInterestScoreRepository repository,
[FromServices] IInterestScoreRepository repository,
CancellationToken ct) =>
{
var scores = await repository.GetAllAsync(offset ?? 0, limit ?? 50, ct).ConfigureAwait(false);
@@ -80,7 +80,7 @@ internal static class InterestScoreEndpointExtensions
// GET /api/v1/scores/distribution - Get score distribution statistics
group.MapGet("/scores/distribution", async (
IInterestScoreRepository repository,
[FromServices] IInterestScoreRepository repository,
CancellationToken ct) =>
{
var distribution = await repository.GetScoreDistributionAsync(ct).ConfigureAwait(false);
@@ -103,7 +103,7 @@ internal static class InterestScoreEndpointExtensions
// POST /api/v1/canonical/{id}/score/compute - Compute score for a canonical
group.MapPost("/canonical/{id:guid}/score/compute", async (
Guid id,
IInterestScoringService scoringService,
[FromServices] IInterestScoringService scoringService,
CancellationToken ct) =>
{
var score = await scoringService.ComputeScoreAsync(id, ct).ConfigureAwait(false);
@@ -118,7 +118,7 @@ internal static class InterestScoreEndpointExtensions
// POST /api/v1/scores/recalculate - Admin endpoint to trigger full recalculation
group.MapPost("/scores/recalculate", async (
[FromBody] RecalculateRequest? request,
IInterestScoringService scoringService,
[FromServices] IInterestScoringService scoringService,
CancellationToken ct) =>
{
int updated;
@@ -147,8 +147,8 @@ internal static class InterestScoreEndpointExtensions
// POST /api/v1/scores/degrade - Admin endpoint to run stub degradation
group.MapPost("/scores/degrade", async (
[FromBody] DegradeRequest? request,
IInterestScoringService scoringService,
Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
[FromServices] IInterestScoringService scoringService,
[FromServices] Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
CancellationToken ct) =>
{
var threshold = request?.Threshold ?? options.Value.DegradationPolicy.DegradationThreshold;
@@ -169,8 +169,8 @@ internal static class InterestScoreEndpointExtensions
// POST /api/v1/scores/restore - Admin endpoint to restore stubs
group.MapPost("/scores/restore", async (
[FromBody] RestoreRequest? request,
IInterestScoringService scoringService,
Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
[FromServices] IInterestScoringService scoringService,
[FromServices] Microsoft.Extensions.Options.IOptions<InterestScoreOptions> options,
CancellationToken ct) =>
{
var threshold = request?.Threshold ?? options.Value.DegradationPolicy.RestorationThreshold;

View File

@@ -1,6 +1,7 @@
using System.Globalization;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.WebService.Diagnostics;
using StellaOps.Concelier.WebService.Options;
@@ -18,9 +19,9 @@ internal static class MirrorEndpointExtensions
public static void MapConcelierMirrorEndpoints(this WebApplication app, bool authorityConfigured, bool enforceAuthority)
{
app.MapGet("/concelier/exports/index.json", async (
MirrorFileLocator locator,
MirrorRateLimiter limiter,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] MirrorFileLocator locator,
[FromServices] MirrorRateLimiter limiter,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
HttpContext context,
CancellationToken cancellationToken) =>
{
@@ -51,9 +52,9 @@ internal static class MirrorEndpointExtensions
app.MapGet("/concelier/exports/{**relativePath}", async (
string? relativePath,
MirrorFileLocator locator,
MirrorRateLimiter limiter,
IOptionsMonitor<ConcelierOptions> optionsMonitor,
[FromServices] MirrorFileLocator locator,
[FromServices] MirrorRateLimiter limiter,
[FromServices] IOptionsMonitor<ConcelierOptions> optionsMonitor,
HttpContext context,
CancellationToken cancellationToken) =>
{

View File

@@ -25,7 +25,7 @@ internal static class SbomEndpointExtensions
// POST /api/v1/learn/sbom - Register and learn from an SBOM
group.MapPost("/learn/sbom", async (
[FromBody] LearnSbomRequest request,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
var input = new SbomRegistrationInput
@@ -62,7 +62,7 @@ internal static class SbomEndpointExtensions
// GET /api/v1/sboms/{digest}/affected - Get advisories affecting an SBOM
group.MapGet("/sboms/{digest}/affected", async (
string digest,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
var registration = await registryService.GetByDigestAsync(digest, ct).ConfigureAwait(false);
@@ -103,7 +103,7 @@ internal static class SbomEndpointExtensions
[FromQuery] int? offset,
[FromQuery] int? limit,
[FromQuery] string? tenantId,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
var registrations = await registryService.ListAsync(
@@ -140,7 +140,7 @@ internal static class SbomEndpointExtensions
// GET /api/v1/sboms/{digest} - Get SBOM registration details
group.MapGet("/sboms/{digest}", async (
string digest,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
var registration = await registryService.GetByDigestAsync(digest, ct).ConfigureAwait(false);
@@ -174,7 +174,7 @@ internal static class SbomEndpointExtensions
// DELETE /api/v1/sboms/{digest} - Unregister an SBOM
group.MapDelete("/sboms/{digest}", async (
string digest,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
await registryService.UnregisterAsync(digest, ct).ConfigureAwait(false);
@@ -187,7 +187,7 @@ internal static class SbomEndpointExtensions
// POST /api/v1/sboms/{digest}/rematch - Rematch SBOM against current advisories
group.MapPost("/sboms/{digest}/rematch", async (
string digest,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
try
@@ -216,7 +216,7 @@ internal static class SbomEndpointExtensions
group.MapPatch("/sboms/{digest}", async (
string digest,
[FromBody] SbomDeltaRequest request,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
try
@@ -258,7 +258,7 @@ internal static class SbomEndpointExtensions
// GET /api/v1/sboms/stats - Get SBOM registry statistics
group.MapGet("/sboms/stats", async (
[FromQuery] string? tenantId,
ISbomRegistryService registryService,
[FromServices] ISbomRegistryService registryService,
CancellationToken ct) =>
{
var stats = await registryService.GetStatsAsync(tenantId, ct).ConfigureAwait(false);

View File

@@ -104,6 +104,10 @@ builder.Host.ConfigureAppConfiguration((context, cfg) =>
#pragma warning restore ASP0013
var JsonOptions = CreateJsonOptions();
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
builder.Configuration.AddStellaOpsDefaults(options =>
{
@@ -155,6 +159,26 @@ if (builder.Environment.IsEnvironment("Testing"))
}
ConcelierOptionsPostConfigure.Apply(concelierOptions, contentRootPath);
concelierOptions.Authority ??= new ConcelierOptions.AuthorityOptions();
concelierOptions.Authority.RequiredScopes ??= new List<string>();
concelierOptions.Authority.ClientScopes ??= new List<string>();
if (concelierOptions.Authority.RequiredScopes.Count == 0)
{
concelierOptions.Authority.RequiredScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
}
if (concelierOptions.Authority.ClientScopes.Count == 0)
{
foreach (var scope in concelierOptions.Authority.RequiredScopes)
{
concelierOptions.Authority.ClientScopes.Add(scope);
}
}
if (concelierOptions.Authority.ClientScopes.Count == 0)
{
concelierOptions.Authority.ClientScopes.Add(StellaOpsScopes.ConcelierJobsTrigger);
}
// Skip validation in Testing to allow factory-provided wiring.
}
else
@@ -473,6 +497,7 @@ builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
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));
@@ -514,6 +539,7 @@ app.MapConcelierMirrorEndpoints(authorityConfigured, enforceAuthority);
// Canonical advisory endpoints (Sprint 8200.0012.0003)
app.MapCanonicalAdvisoryEndpoints();
app.MapInterestScoreEndpoints();
app.MapGet("/.well-known/openapi", ([FromServices] OpenApiDiscoveryDocumentProvider provider, HttpContext context) =>
{
@@ -559,6 +585,53 @@ app.MapGet("/.well-known/openapi", ([FromServices] OpenApiDiscoveryDocumentProvi
}
}).WithName("GetConcelierOpenApiDocument");
if (swaggerEnabled)
{
app.MapGet("/swagger/v1/swagger.json", ([FromServices] OpenApiDiscoveryDocumentProvider provider, HttpContext context) =>
{
var (payload, etag) = provider.GetDocument();
if (context.Request.Headers.IfNoneMatch.Count > 0)
{
foreach (var candidate in context.Request.Headers.IfNoneMatch)
{
if (Matches(candidate, etag))
{
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=300, immutable";
return HttpResults.StatusCode(StatusCodes.Status304NotModified);
}
}
}
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=300, immutable";
return HttpResults.Text(payload, "application/json");
static bool Matches(string? candidate, string expected)
{
if (string.IsNullOrWhiteSpace(candidate))
{
return false;
}
var trimmed = candidate.Trim();
if (string.Equals(trimmed, expected, StringComparison.Ordinal))
{
return true;
}
if (trimmed.StartsWith("W/", StringComparison.OrdinalIgnoreCase))
{
var weakValue = trimmed[2..].TrimStart();
return string.Equals(weakValue, expected, StringComparison.Ordinal);
}
return false;
}
}).WithName("GetConcelierSwaggerDocument");
}
var orchestratorGroup = app.MapGroup("/internal/orch");
if (authorityConfigured)
{

View File

@@ -272,7 +272,10 @@ internal sealed class AdvisoryChunkBuilder
AdvisoryStructuredFieldContent content,
AdvisoryProvenance provenance)
{
var fingerprint = string.Concat(documentId, '|', fieldPath);
var normalizedMask = NormalizeFieldMask(provenance.FieldMask);
var observationPath = normalizedMask.Count > 0 ? normalizedMask[0] : fieldPath;
var resolvedMask = normalizedMask.Count > 0 ? normalizedMask : new[] { fieldPath };
var fingerprint = string.Concat(documentId, '|', observationPath);
var chunkId = CreateChunkId(fingerprint);
return new AdvisoryStructuredFieldEntry(
@@ -281,16 +284,27 @@ internal sealed class AdvisoryChunkBuilder
content,
new AdvisoryStructuredFieldProvenance(
documentId,
fieldPath,
observationPath,
provenance.Source,
provenance.Kind,
provenance.Value,
provenance.RecordedAt,
NormalizeFieldMask(provenance.FieldMask)));
resolvedMask));
}
private static IReadOnlyList<string> NormalizeFieldMask(ImmutableArray<string> mask)
=> mask.IsDefaultOrEmpty ? Array.Empty<string>() : mask;
{
if (mask.IsDefaultOrEmpty)
{
return Array.Empty<string>();
}
return mask
.Select(static entry => entry?.Trim())
.Where(static entry => !string.IsNullOrWhiteSpace(entry))
.Select(static entry => entry!)
.ToArray();
}
private string CreateChunkId(string input)
{

View File

@@ -104,13 +104,26 @@ internal sealed class OpenApiDiscoveryDocumentProvider
pathsObject[path] = pathItem;
}
var components = new JsonObject
{
["securitySchemes"] = new JsonObject
{
["Bearer"] = new JsonObject
{
["type"] = "http",
["scheme"] = "bearer",
["bearerFormat"] = "JWT"
}
}
};
return new JsonObject
{
["openapi"] = "3.1.0",
["info"] = info,
["servers"] = servers,
["paths"] = pathsObject,
["components"] = new JsonObject() // ready for future schemas
["components"] = components
};
}