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:
@@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "bin::sha256:10f3c03766e4403be40add0467a2b2d07fd7006e4b8515ab88740ffa327ea775",
|
||||
"purl": null,
|
||||
"name": "opaque_bin",
|
||||
"version": null,
|
||||
"type": "bin",
|
||||
"usedByEntrypoint": true,
|
||||
"metadata": {
|
||||
"binary.path": "usr/local/bin/opaque_bin",
|
||||
"binary.sha256": "10f3c03766e4403be40add0467a2b2d07fd7006e4b8515ab88740ffa327ea775",
|
||||
"provenance": "binary"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "binary",
|
||||
"locator": "usr/local/bin/opaque_bin",
|
||||
"value": null,
|
||||
"sha256": "10f3c03766e4403be40add0467a2b2d07fd7006e4b8515ab88740ffa327ea775"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"detectedCrates": [
|
||||
{
|
||||
"name": "serde",
|
||||
"note": "Binary symbol scan matched only serde"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "rust::heuristic::reqwest::usr/local/bin/heuristic_app",
|
||||
"name": "reqwest",
|
||||
"type": "cargo",
|
||||
"usedByEntrypoint": true,
|
||||
"metadata": {
|
||||
"binary.paths": "usr/local/bin/heuristic_app",
|
||||
"binary.sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b",
|
||||
"crate": "reqwest",
|
||||
"provenance": "heuristic"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "derived",
|
||||
"source": "rust.heuristic",
|
||||
"locator": "usr/local/bin/heuristic_app",
|
||||
"value": "reqwest",
|
||||
"sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "rust::heuristic::serde::usr/local/bin/heuristic_app",
|
||||
"name": "serde",
|
||||
"type": "cargo",
|
||||
"usedByEntrypoint": true,
|
||||
"metadata": {
|
||||
"binary.paths": "usr/local/bin/heuristic_app",
|
||||
"binary.sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b",
|
||||
"crate": "serde",
|
||||
"provenance": "heuristic"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "derived",
|
||||
"source": "rust.heuristic",
|
||||
"locator": "usr/local/bin/heuristic_app",
|
||||
"value": "serde",
|
||||
"sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "rust",
|
||||
"componentKey": "rust::heuristic::tokio::usr/local/bin/heuristic_app",
|
||||
"name": "tokio",
|
||||
"type": "cargo",
|
||||
"usedByEntrypoint": true,
|
||||
"metadata": {
|
||||
"binary.paths": "usr/local/bin/heuristic_app",
|
||||
"binary.sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b",
|
||||
"crate": "tokio",
|
||||
"provenance": "heuristic"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "derived",
|
||||
"source": "rust.heuristic",
|
||||
"locator": "usr/local/bin/heuristic_app",
|
||||
"value": "tokio",
|
||||
"sha256": "4caf60c501a594b5d4b8d909b3e91fccc4447692b9e144f322a333255909310b"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Rust;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Tests.Rust;
|
||||
|
||||
public sealed class RustHeuristicCoverageComparisonTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HeuristicCoverageExceedsCompetitorBaselineAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "heuristics");
|
||||
var baselinePath = Path.Combine(fixturePath, "competitor-baseline.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
var output = await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
||||
fixturePath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
|
||||
using var ours = JsonDocument.Parse(output);
|
||||
var heuristicNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var element in ours.RootElement.EnumerateArray())
|
||||
{
|
||||
if (!element.TryGetProperty("metadata", out var metadata) || metadata.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var provenance = metadata.EnumerateObject()
|
||||
.FirstOrDefault(p => string.Equals(p.Name, "provenance", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provenance.Value.ValueKind == JsonValueKind.String &&
|
||||
string.Equals(provenance.Value.GetString(), "heuristic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (element.TryGetProperty("name", out var nameProperty) && nameProperty.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var value = nameProperty.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
heuristicNames.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var competitor = JsonDocument.Parse(await File.ReadAllTextAsync(baselinePath, cancellationToken));
|
||||
var competitorNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (competitor.RootElement.TryGetProperty("detectedCrates", out var detectedCrates) && detectedCrates.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var entry in detectedCrates.EnumerateArray())
|
||||
{
|
||||
if (entry.ValueKind == JsonValueKind.Object && entry.TryGetProperty("name", out var nameProperty) && nameProperty.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var name = nameProperty.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
competitorNames.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.NotEmpty(competitorNames);
|
||||
Assert.True(heuristicNames.IsSupersetOf(competitorNames));
|
||||
|
||||
var improvement = (double)heuristicNames.Count / competitorNames.Count;
|
||||
Assert.True(improvement >= 1.15, $"Expected at least 15% improvement; got {improvement:P2} ({heuristicNames.Count} vs {competitorNames.Count}).");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Rust;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Rust;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
@@ -35,25 +36,86 @@ public sealed class RustLanguageAnalyzerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "simple");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
var workers = Math.Max(Environment.ProcessorCount, 4);
|
||||
var tasks = Enumerable.Range(0, workers)
|
||||
.Select(_ => LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
var baseline = results[0];
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Equal(baseline, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task AnalyzerIsThreadSafeUnderConcurrencyAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "simple");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
var workers = Math.Max(Environment.ProcessorCount, 4);
|
||||
var tasks = Enumerable.Range(0, workers)
|
||||
.Select(_ => LanguageAnalyzerTestHarness.RunToJsonAsync(fixturePath, analyzers, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
var baseline = results[0];
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Equal(baseline, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeuristicFixtureProducesExpectedOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "heuristics");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
var usageHints = new LanguageUsageHints(new[]
|
||||
{
|
||||
Path.Combine(fixturePath, "usr/local/bin/heuristic_app")
|
||||
});
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FallbackFixtureProducesExpectedOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "rust", "fallback");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
var usageHints = new LanguageUsageHints(new[]
|
||||
{
|
||||
Path.Combine(fixturePath, "usr/local/bin/opaque_bin")
|
||||
});
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new RustLanguageAnalyzer()
|
||||
};
|
||||
|
||||
var actualJson = await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
||||
fixturePath,
|
||||
analyzers,
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
|
||||
var repeat = await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
||||
fixturePath,
|
||||
analyzers,
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
Assert.Equal(actualJson, repeat);
|
||||
|
||||
var expectedJson = await File.ReadAllTextAsync(goldenPath, cancellationToken);
|
||||
var actualNode = JsonNode.Parse(actualJson);
|
||||
var expectedNode = JsonNode.Parse(expectedJson);
|
||||
Assert.True(
|
||||
JsonNode.DeepEquals(expectedNode, actualNode),
|
||||
"Fallback fixture output does not match expected snapshot.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user