Add Authority Advisory AI and API Lifecycle Configuration

- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings.
- Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations.
- Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration.
- Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options.
- Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations.
- Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client.
- Updated project file to include necessary package references for the new Issuer Directory Client library.
This commit is contained in:
master
2025-11-02 13:40:38 +02:00
parent 66cb6c4b8a
commit f98cea3bcf
516 changed files with 68157 additions and 24754 deletions

View File

@@ -0,0 +1,139 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.EntryTrace;
using StellaOps.Scanner.EntryTrace.Serialization;
using StellaOps.Scanner.Storage;
using StellaOps.Scanner.Storage.Mongo;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.Storage.Services;
using Xunit;
using MongoDB.Driver;
namespace StellaOps.Scanner.Storage.Tests;
public sealed class EntryTraceResultStoreTests : IClassFixture<ScannerMongoFixture>
{
private readonly ScannerMongoFixture _fixture;
public EntryTraceResultStoreTests(ScannerMongoFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task StoreAsync_ThrowsWhenResultNull()
{
var store = CreateStore();
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
{
EntryTraceResult? result = null;
await store.StoreAsync(result!, CancellationToken.None);
});
}
[Fact]
public async Task GetAsync_ReturnsNullWhenMissing()
{
await ClearCollectionAsync();
var store = CreateStore();
var result = await store.GetAsync("scan-missing", CancellationToken.None);
Assert.Null(result);
}
[Fact]
public async Task StoreAsync_RoundTripsResult()
{
await ClearCollectionAsync();
var store = CreateStore();
var scanId = $"scan-{Guid.NewGuid():n}";
var generatedAt = new DateTimeOffset(2025, 11, 2, 10, 30, 0, TimeSpan.Zero);
var node = new EntryTraceNode(
1,
EntryTraceNodeKind.Command,
"python",
ImmutableArray.Create("python", "/app/main.py"),
EntryTraceInterpreterKind.None,
new EntryTraceEvidence("/usr/bin/python", "sha256:layer-a", "path", ImmutableDictionary<string, string>.Empty),
new EntryTraceSpan("/app/start.sh", 1, 0, 1, 14),
ImmutableDictionary<string, string>.Empty);
var plan = new EntryTracePlan(
ImmutableArray.Create("/app/main.py"),
ImmutableDictionary<string, string>.Empty,
"/workspace",
"scanner",
"/app/main.py",
EntryTraceTerminalType.Native,
"python",
0.95,
ImmutableDictionary<string, string>.Empty);
var terminal = new EntryTraceTerminal(
"/app/main.py",
EntryTraceTerminalType.Native,
"python",
0.95,
ImmutableDictionary<string, string>.Empty,
"scanner",
"/workspace",
ImmutableArray.Create("/app/main.py"));
var graph = new EntryTraceGraph(
EntryTraceOutcome.Resolved,
ImmutableArray.Create(node),
ImmutableArray<EntryTraceEdge>.Empty,
ImmutableArray<EntryTraceDiagnostic>.Empty,
ImmutableArray.Create(plan),
ImmutableArray.Create(terminal));
var ndjson = EntryTraceNdjsonWriter.Serialize(
graph,
new EntryTraceNdjsonMetadata(scanId, "sha256:image", generatedAt, Source: "storage.tests"));
var result = new EntryTraceResult(scanId, "sha256:image", generatedAt, graph, ndjson);
await store.StoreAsync(result, CancellationToken.None);
var stored = await store.GetAsync(scanId, CancellationToken.None);
Assert.NotNull(stored);
Assert.Equal(result.ScanId, stored!.ScanId);
Assert.Equal(result.ImageDigest, stored.ImageDigest);
Assert.Equal(result.GeneratedAtUtc, stored.GeneratedAtUtc);
Assert.Equal(result.Graph, stored.Graph);
Assert.Equal(result.Ndjson, stored.Ndjson);
}
private async Task ClearCollectionAsync()
{
var provider = CreateProvider();
await provider.EntryTrace.DeleteManyAsync(_ => true);
}
private EntryTraceResultStore CreateStore()
{
var provider = CreateProvider();
var repository = new EntryTraceRepository(provider);
return new EntryTraceResultStore(repository);
}
private MongoCollectionProvider CreateProvider()
{
var options = Options.Create(new ScannerStorageOptions
{
Mongo = new MongoOptions
{
ConnectionString = _fixture.Runner.ConnectionString,
DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName,
UseMajorityReadConcern = false,
UseMajorityWriteConcern = false
}
});
return new MongoCollectionProvider(_fixture.Database, options);
}
}

View File

@@ -146,7 +146,7 @@ public sealed class RustFsArtifactObjectStoreTests
}
// Materialize content to ensure downstream callers can inspect it.
_ = await request.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
_ = await request.Content.ReadAsByteArrayAsync(cancellationToken);
}
CapturedRequests.Add(new CapturedRequest(request.Method, request.RequestUri!, headerSnapshot));