Add new features and tests for AirGap and Time modules
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Introduced `SbomService` tasks documentation.
- Updated `StellaOps.sln` to include new projects: `StellaOps.AirGap.Time` and `StellaOps.AirGap.Importer`.
- Added unit tests for `BundleImportPlanner`, `DsseVerifier`, `ImportValidator`, and other components in the `StellaOps.AirGap.Importer.Tests` namespace.
- Implemented `InMemoryBundleRepositories` for testing bundle catalog and item repositories.
- Created `MerkleRootCalculator`, `RootRotationPolicy`, and `TufMetadataValidator` tests.
- Developed `StalenessCalculator` and `TimeAnchorLoader` tests in the `StellaOps.AirGap.Time.Tests` namespace.
- Added `fetch-sbomservice-deps.sh` script for offline dependency fetching.
This commit is contained in:
master
2025-11-20 23:29:54 +02:00
parent 65b1599229
commit 79b8e53441
182 changed files with 6660 additions and 1242 deletions

View File

@@ -117,6 +117,8 @@ builder.Services.AddOptions<AdvisoryObservationEventPublisherOptions>()
.ValidateOnStart();
builder.Services.AddConcelierAocGuards();
builder.Services.AddConcelierLinksetMappers();
builder.Services.AddSingleton<IMeterFactory>(MeterProvider.Default.GetMeterProvider());
builder.Services.AddSingleton<LinksetCacheTelemetry>();
builder.Services.AddAdvisoryRawServices();
builder.Services.AddSingleton<IAdvisoryObservationQueryService, AdvisoryObservationQueryService>();
builder.Services.AddSingleton<AdvisoryChunkBuilder>();
@@ -460,6 +462,66 @@ var observationsEndpoint = app.MapGet("/concelier/observations", async (
return Results.Ok(response);
}).WithName("GetConcelierObservations");
app.MapGet("/v1/lnm/linksets/{advisoryId}", async (
HttpContext context,
string advisoryId,
[FromQuery(Name = "source")] string source,
[FromQuery(Name = "includeConflicts")] bool includeConflicts,
[FromServices] IAdvisoryLinksetLookup linksetLookup,
[FromServices] LinksetCacheTelemetry telemetry,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
ApplyNoCache(context.Response);
if (!TryResolveTenant(context, requireHeader: true, out var tenant, out var tenantError))
{
return tenantError;
}
var authorizationError = EnsureTenantAuthorized(context, tenant);
if (authorizationError is not null)
{
return authorizationError;
}
if (string.IsNullOrWhiteSpace(advisoryId) || string.IsNullOrWhiteSpace(source))
{
return Results.BadRequest("advisoryId and source are required.");
}
var stopwatch = Stopwatch.StartNew();
var options = new AdvisoryLinksetQueryOptions(tenant!, Source: source.Trim(), AdvisoryId: advisoryId.Trim());
var linksets = await linksetLookup.FindByTenantAsync(options.TenantId, options.Source, options.AdvisoryId, cancellationToken).ConfigureAwait(false);
if (linksets.Count == 0)
{
return Results.NotFound();
}
var linkset = linksets[0];
var response = new LnmLinksetResponse(
linkset.AdvisoryId,
linkset.Source,
linkset.Observations,
linkset.Normalized is null
? null
: new LnmLinksetNormalized(linkset.Normalized.Purls, linkset.Normalized.Versions, linkset.Normalized.Ranges, linkset.Normalized.Severities),
includeConflicts ? linkset.Conflicts : Array.Empty<LnmLinksetConflict>(),
linkset.Provenance is null
? null
: new LnmLinksetProvenance(linkset.Provenance.ObservationHashes, linkset.Provenance.ToolVersion, linkset.Provenance.PolicyHash),
linkset.CreatedAt,
linkset.BuiltByJobId,
Cached: true);
telemetry.RecordHit(tenant, linkset.Source);
telemetry.RecordRebuild(tenant, linkset.Source, stopwatch.Elapsed.TotalMilliseconds);
return Results.Ok(response);
}).WithName("GetLnmLinkset");
if (authorityConfigured)
{
observationsEndpoint.RequireAuthorization(ObservationsPolicyName);