tenant fixes

This commit is contained in:
master
2026-02-23 23:44:50 +02:00
parent bdb1438654
commit 4f947a8b61
159 changed files with 1064 additions and 556 deletions

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using Microsoft.Extensions.Options;
using StellaOps.SbomService.Auth;
using StellaOps.SbomService.Models;
@@ -27,6 +28,7 @@ builder.Services.AddSingleton<IGuidProvider>(SystemGuidProvider.Instance);
builder.Services.AddAuthentication(HeaderAuthenticationHandler.SchemeName)
.AddScheme<AuthenticationSchemeOptions, HeaderAuthenticationHandler>(HeaderAuthenticationHandler.SchemeName, _ => { });
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddAuthorization(options =>
{
// SbomService uses HeaderAuthenticationHandler (x-tenant-id). Policies require authenticated tenant context.
@@ -257,6 +259,7 @@ if (app.Environment.IsDevelopment())
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }))
@@ -287,7 +290,8 @@ app.MapGet("/entrypoints", async Task<IResult> (
})
.WithName("ListSbomEntrypoints")
.WithDescription("Returns all registered service entrypoints for the tenant, listing artifact, service, path, scope, and runtime flag for each. The tenant query parameter is required.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapPost("/entrypoints", async Task<IResult> (
[FromServices] IEntrypointRepository repo,
@@ -324,7 +328,8 @@ app.MapPost("/entrypoints", async Task<IResult> (
})
.WithName("UpsertSbomEntrypoint")
.WithDescription("Creates or updates a service entrypoint for the tenant linking an artifact to a service path. Returns the full updated entrypoint list for the tenant. Requires tenant, artifact, service, and path fields.")
.RequireAuthorization(SbomPolicies.Write);
.RequireAuthorization(SbomPolicies.Write)
.RequireTenant();
app.MapGet("/console/sboms", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -372,7 +377,8 @@ app.MapGet("/console/sboms", async Task<IResult> (
})
.WithName("ListConsoleSboms")
.WithDescription("Returns a paginated SBOM catalog for the console UI, optionally filtered by artifact name, license, scope, and asset tag. Supports cursor-based pagination. Limit must be between 1 and 200.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/components/lookup", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -424,7 +430,8 @@ app.MapGet("/components/lookup", async Task<IResult> (
})
.WithName("LookupSbomComponent")
.WithDescription("Looks up all SBOM entries that include a specific component PURL, optionally filtered by artifact. Returns paginated results with cursor support. Requires purl query parameter. Limit must be between 1 and 200.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/context", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -489,7 +496,8 @@ app.MapGet("/sbom/context", async Task<IResult> (
})
.WithName("GetSbomContext")
.WithDescription("Returns an assembled SBOM context for an artifact including version timeline and dependency paths for a specific PURL. Combines timeline and path data into a single response for UI rendering. Requires artifactId query parameter.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/paths", async Task<IResult> (
[FromServices] IServiceProvider services,
@@ -541,7 +549,8 @@ app.MapGet("/sbom/paths", async Task<IResult> (
})
.WithName("GetSbomPaths")
.WithDescription("Returns paginated dependency paths for a specific component PURL across SBOMs, optionally filtered by artifact, scope, and environment. Requires purl query parameter. Limit must be between 1 and 200.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/versions", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -588,7 +597,8 @@ app.MapGet("/sbom/versions", async Task<IResult> (
})
.WithName("GetSbomVersions")
.WithDescription("Returns the paginated version timeline for a specific artifact, listing SBOM snapshots in chronological order. Requires artifact query parameter. Limit must be between 1 and 200.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
var sbomUploadHandler = async Task<IResult> (
[FromBody] SbomUploadRequest request,
@@ -613,11 +623,13 @@ var sbomUploadHandler = async Task<IResult> (
app.MapPost("/sbom/upload", sbomUploadHandler)
.WithName("UploadSbom")
.WithDescription("Uploads and ingests a new SBOM for the specified artifact, validating the payload and persisting it to the ledger. Returns 202 Accepted with the artifact reference and ledger entry on success. Returns 400 if validation fails.")
.RequireAuthorization(SbomPolicies.Write);
.RequireAuthorization(SbomPolicies.Write)
.RequireTenant();
app.MapPost("/api/v1/sbom/upload", sbomUploadHandler)
.WithName("UploadSbomV1")
.WithDescription("Canonical v1 API path alias for UploadSbom. Uploads and ingests a new SBOM for the specified artifact, validating the payload and persisting it to the ledger. Returns 202 Accepted with the artifact reference and ledger entry on success.")
.RequireAuthorization(SbomPolicies.Write);
.RequireAuthorization(SbomPolicies.Write)
.RequireTenant();
app.MapGet("/sbom/ledger/history", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -648,7 +660,8 @@ app.MapGet("/sbom/ledger/history", async Task<IResult> (
})
.WithName("GetSbomLedgerHistory")
.WithDescription("Returns the paginated ledger history for a specific artifact, listing SBOM versions in chronological order with ledger metadata. Requires artifact query parameter. Returns 404 if no history is found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/ledger/point", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -676,7 +689,8 @@ app.MapGet("/sbom/ledger/point", async Task<IResult> (
})
.WithName("GetSbomLedgerPoint")
.WithDescription("Returns the SBOM ledger entry for a specific artifact at a given point in time. Requires artifact and at (ISO-8601 timestamp) query parameters. Returns 404 if no ledger entry exists for the specified time.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/ledger/range", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -719,7 +733,8 @@ app.MapGet("/sbom/ledger/range", async Task<IResult> (
})
.WithName("GetSbomLedgerRange")
.WithDescription("Returns paginated SBOM ledger entries for a specific artifact within a time range defined by start and end ISO-8601 timestamps. Requires artifact, start, and end query parameters. Returns 404 if no data is found for the range.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/ledger/diff", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -743,7 +758,8 @@ app.MapGet("/sbom/ledger/diff", async Task<IResult> (
})
.WithName("GetSbomLedgerDiff")
.WithDescription("Returns a component-level diff between two SBOM ledger entries identified by their GUIDs (before and after). Highlights added, removed, and changed components between two SBOM versions. Returns 404 if either entry is not found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sbom/ledger/lineage", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -765,7 +781,8 @@ app.MapGet("/sbom/ledger/lineage", async Task<IResult> (
})
.WithName("GetSbomLedgerLineage")
.WithDescription("Returns the full artifact lineage chain from the SBOM ledger for a specific artifact, showing the provenance ancestry of SBOM versions. Requires artifact query parameter. Returns 404 if lineage is not found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
// -----------------------------------------------------------------------------
// Lineage Graph API Endpoints (LIN-BE-013/014)
@@ -817,7 +834,8 @@ app.MapGet("/api/v1/lineage/{artifactDigest}", async Task<IResult> (
})
.WithName("GetLineageGraph")
.WithDescription("Returns the lineage graph for a specific artifact by digest for the given tenant, including upstream provenance nodes up to maxDepth levels, optional trust badges, and an optional deterministic replay hash. Returns 404 if the graph is not found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/api/v1/lineage/diff", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
@@ -857,7 +875,8 @@ app.MapGet("/api/v1/lineage/diff", async Task<IResult> (
})
.WithName("GetLineageDiff")
.WithDescription("Returns a graph-level diff between two artifact lineage graphs identified by their digests (from and to) for the given tenant. Highlights added and removed nodes and edges between two artifact versions. Returns 404 if either graph is not found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/api/v1/lineage/hover", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
@@ -894,7 +913,8 @@ app.MapGet("/api/v1/lineage/hover", async Task<IResult> (
})
.WithName("GetLineageHoverCard")
.WithDescription("Returns a lightweight hover card summary of the lineage relationship between two artifact digests for the given tenant. Used for fast UI hover popups. Cached for low-latency responses. Returns 404 if no hover card data is available.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/api/v1/lineage/{artifactDigest}/children", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
@@ -925,7 +945,8 @@ app.MapGet("/api/v1/lineage/{artifactDigest}/children", async Task<IResult> (
})
.WithName("GetLineageChildren")
.WithDescription("Returns the direct child artifacts in the lineage graph for a specific artifact digest and tenant. Lists artifacts that were built from or derived from the specified artifact.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/api/v1/lineage/{artifactDigest}/parents", async Task<IResult> (
[FromServices] ISbomLineageGraphService lineageService,
@@ -956,7 +977,8 @@ app.MapGet("/api/v1/lineage/{artifactDigest}/parents", async Task<IResult> (
})
.WithName("GetLineageParents")
.WithDescription("Returns the direct parent artifacts in the lineage graph for a specific artifact digest and tenant. Lists artifacts from which the specified artifact was built or derived.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapPost("/api/v1/lineage/export", async Task<IResult> (
[FromServices] ILineageExportService exportService,
@@ -994,7 +1016,8 @@ app.MapPost("/api/v1/lineage/export", async Task<IResult> (
})
.WithName("ExportLineage")
.WithDescription("Exports the lineage evidence pack between two artifact digests for the given tenant as a structured bundle. Enforces a 50 MB size limit on the export payload. Returns 413 if the export exceeds the size limit. Requires fromDigest, toDigest, and tenantId in the request body.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
// -----------------------------------------------------------------------------
// Lineage Compare API (LIN-BE-028)
@@ -1053,7 +1076,8 @@ app.MapGet("/api/v1/lineage/compare", async Task<IResult> (
})
.WithName("CompareLineage")
.WithDescription("Returns a rich comparison between two artifact versions by lineage digest (a and b) for the given tenant. Optionally includes SBOM diff, VEX deltas, reachability deltas, attestations, and replay hashes. Returns 404 if comparison data is not found.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
// -----------------------------------------------------------------------------
// Replay Verification API (LIN-BE-033)
@@ -1098,7 +1122,8 @@ app.MapPost("/api/v1/lineage/verify", async Task<IResult> (
})
.WithName("VerifyLineageReplay")
.WithDescription("Verifies a deterministic replay hash against the current policy and SBOM state to confirm the release decision is reproducible. Optionally re-evaluates the policy against current feeds. Requires replayHash and tenantId in the request body.")
.RequireAuthorization(SbomPolicies.Write);
.RequireAuthorization(SbomPolicies.Write)
.RequireTenant();
app.MapPost("/api/v1/lineage/compare-drift", async Task<IResult> (
[FromServices] IReplayVerificationService verificationService,
@@ -1128,7 +1153,8 @@ app.MapPost("/api/v1/lineage/compare-drift", async Task<IResult> (
})
.WithName("CompareLineageDrift")
.WithDescription("Compares two replay hashes (hashA and hashB) for the given tenant to detect drift between two release decision points. Returns a structured drift report indicating whether the two points are equivalent. Requires hashA, hashB, and tenantId.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -1189,7 +1215,8 @@ app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
})
.WithName("GetSbomProjection")
.WithDescription("Returns the structured SBOM projection for a specific snapshot ID and tenant. The projection contains the full normalized component graph with schema version and a deterministic hash. Used by the policy engine and reachability graph for decision-making.")
.RequireAuthorization(SbomPolicies.Read);
.RequireAuthorization(SbomPolicies.Read)
.RequireTenant();
app.MapGet("/internal/sbom/events", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1207,7 +1234,8 @@ app.MapGet("/internal/sbom/events", async Task<IResult> (
})
.WithName("ListSbomEvents")
.WithDescription("Internal endpoint. Returns all SBOM version-created events in the in-memory event store backlog. Logs a warning if the backlog exceeds 100 entries. Used by orchestrators to process pending SBOM ingestion events.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/asset-events", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1226,7 +1254,8 @@ app.MapGet("/internal/sbom/asset-events", async Task<IResult> (
})
.WithName("ListSbomAssetEvents")
.WithDescription("Internal endpoint. Returns all SBOM asset-level events from the in-memory event store. Used by orchestrators to process asset lifecycle changes associated with SBOM versions.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/ledger/audit", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -1243,7 +1272,8 @@ app.MapGet("/internal/sbom/ledger/audit", async Task<IResult> (
})
.WithName("GetSbomLedgerAudit")
.WithDescription("Internal endpoint. Returns the chronologically ordered audit trail for a specific artifact from the SBOM ledger, listing all state transitions and operations. Requires artifact query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/analysis/jobs", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -1260,7 +1290,8 @@ app.MapGet("/internal/sbom/analysis/jobs", async Task<IResult> (
})
.WithName("ListSbomAnalysisJobs")
.WithDescription("Internal endpoint. Returns the chronologically ordered list of SBOM analysis jobs for a specific artifact. Requires artifact query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/sbom/events/backfill", async Task<IResult> (
[FromServices] IProjectionRepository repository,
@@ -1293,7 +1324,8 @@ app.MapPost("/internal/sbom/events/backfill", async Task<IResult> (
})
.WithName("BackfillSbomEvents")
.WithDescription("Internal endpoint. Replays all known SBOM projections as version-created events into the event store backlog. Used for backfill and recovery scenarios after store resets. Returns the count of successfully published events.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/inventory", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1305,7 +1337,8 @@ app.MapGet("/internal/sbom/inventory", async Task<IResult> (
})
.WithName("ListSbomInventory")
.WithDescription("Internal endpoint. Returns all SBOM inventory entries from the event store, representing the known set of artifacts and their SBOM state across tenants.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/sbom/inventory/backfill", async Task<IResult> (
[FromServices] ISbomQueryService service,
@@ -1325,7 +1358,8 @@ app.MapPost("/internal/sbom/inventory/backfill", async Task<IResult> (
})
.WithName("BackfillSbomInventory")
.WithDescription("Internal endpoint. Clears and replays the SBOM inventory by re-fetching projections for known snapshot/tenant pairs. Used for recovery after inventory store resets. Returns the count of replayed entries.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/resolver-feed", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1336,7 +1370,8 @@ app.MapGet("/internal/sbom/resolver-feed", async Task<IResult> (
})
.WithName("GetSbomResolverFeed")
.WithDescription("Internal endpoint. Returns all resolver feed candidates from the event store. The resolver feed is used by the policy engine and scanner to resolve component identities across SBOM versions.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/sbom/resolver-feed/backfill", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1354,7 +1389,8 @@ app.MapPost("/internal/sbom/resolver-feed/backfill", async Task<IResult> (
})
.WithName("BackfillSbomResolverFeed")
.WithDescription("Internal endpoint. Clears and replays the resolver feed by re-fetching projections for known snapshot/tenant pairs. Used for recovery after resolver store resets. Returns the count of re-published resolver feed entries.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/sbom/resolver-feed/export", async Task<IResult> (
[FromServices] ISbomEventStore store,
@@ -1367,7 +1403,8 @@ app.MapGet("/internal/sbom/resolver-feed/export", async Task<IResult> (
})
.WithName("ExportSbomResolverFeed")
.WithDescription("Internal endpoint. Exports all resolver feed candidates as a newline-delimited JSON (NDJSON) stream. Used for bulk export and offline processing of the resolver feed by external consumers.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/sbom/retention/prune", async Task<IResult> (
[FromServices] ISbomLedgerService ledgerService,
@@ -1383,7 +1420,8 @@ app.MapPost("/internal/sbom/retention/prune", async Task<IResult> (
})
.WithName("PruneSbomRetention")
.WithDescription("Internal endpoint. Applies the configured retention policy to the SBOM ledger, pruning old versions beyond the configured min/max version counts. Records pruned version counts in metrics. Returns a retention result summary.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/orchestrator/sources", async Task<IResult> (
[FromQuery] string? tenant,
@@ -1400,7 +1438,8 @@ app.MapGet("/internal/orchestrator/sources", async Task<IResult> (
})
.WithName("ListOrchestratorSources")
.WithDescription("Internal endpoint. Returns all registered orchestrator artifact sources for the given tenant. Requires tenant query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/orchestrator/sources", async Task<IResult> (
RegisterOrchestratorSourceRequest request,
@@ -1425,7 +1464,8 @@ app.MapPost("/internal/orchestrator/sources", async Task<IResult> (
})
.WithName("RegisterOrchestratorSource")
.WithDescription("Internal endpoint. Registers a new orchestrator artifact source for the given tenant linking an artifact digest to a source type. Requires tenantId, artifactDigest, and sourceType in the request body.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/orchestrator/control", async Task<IResult> (
[FromQuery] string? tenant,
@@ -1442,7 +1482,8 @@ app.MapGet("/internal/orchestrator/control", async Task<IResult> (
})
.WithName("GetOrchestratorControl")
.WithDescription("Internal endpoint. Returns the current orchestrator control state for the given tenant including pause/resume flags and scheduling overrides. Requires tenant query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/orchestrator/control", async Task<IResult> (
OrchestratorControlRequest request,
@@ -1459,7 +1500,8 @@ app.MapPost("/internal/orchestrator/control", async Task<IResult> (
})
.WithName("UpdateOrchestratorControl")
.WithDescription("Internal endpoint. Updates the orchestrator control state for the given tenant, allowing operators to pause, resume, or adjust scheduling parameters. Requires tenantId in the request body.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapGet("/internal/orchestrator/watermarks", async Task<IResult> (
[FromQuery] string? tenant,
@@ -1476,7 +1518,8 @@ app.MapGet("/internal/orchestrator/watermarks", async Task<IResult> (
})
.WithName("GetOrchestratorWatermarks")
.WithDescription("Internal endpoint. Returns the current ingestion watermark state for the given tenant, indicating the last successfully processed position in the artifact stream. Requires tenant query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.MapPost("/internal/orchestrator/watermarks", async Task<IResult> (
[FromQuery] string? tenant,
@@ -1494,7 +1537,8 @@ app.MapPost("/internal/orchestrator/watermarks", async Task<IResult> (
})
.WithName("SetOrchestratorWatermark")
.WithDescription("Internal endpoint. Sets the ingestion watermark for the given tenant to the specified value, marking the last processed position in the artifact stream. Requires tenant query parameter.")
.RequireAuthorization(SbomPolicies.Internal);
.RequireAuthorization(SbomPolicies.Internal)
.RequireTenant();
app.TryRefreshStellaRouterEndpoints(routerEnabled);
app.Run();