Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -68,13 +68,38 @@ public sealed class AirgapAndOrchestratorServiceTests
private sealed class InMemoryAirgapImportRepository : IAirgapImportRepository
{
private readonly List<AirgapImportRecord> _records = new();
public AirgapImportRecord? LastRecord { get; private set; }
public Task InsertAsync(AirgapImportRecord record, CancellationToken cancellationToken)
{
LastRecord = record;
_records.Add(record);
return Task.CompletedTask;
}
public Task<AirgapImportRecord?> GetLatestByDomainAsync(string tenantId, string domainId, CancellationToken cancellationToken)
{
var record = _records
.Where(r => r.TenantId == tenantId)
.OrderByDescending(r => r.ImportedAt)
.FirstOrDefault();
return Task.FromResult(record);
}
public Task<IReadOnlyList<AirgapImportRecord>> GetAllLatestByDomainAsync(string tenantId, CancellationToken cancellationToken)
{
var records = _records
.Where(r => r.TenantId == tenantId)
.ToList();
return Task.FromResult<IReadOnlyList<AirgapImportRecord>>(records);
}
public Task<int> GetBundleCountByDomainAsync(string tenantId, string domainId, CancellationToken cancellationToken)
{
var count = _records.Count(r => r.TenantId == tenantId);
return Task.FromResult(count);
}
}
private sealed class InMemoryOrchestratorExportRepository : IOrchestratorExportRepository

View File

@@ -1,12 +0,0 @@
<Project>
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);**/tools/**/*</DefaultItemExcludes>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
<MSBuildProjectExtensionsPath>$(MSBuildThisFileDirectory)obj/$(MSBuildProjectName)/</MSBuildProjectExtensionsPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="**/tools/**/*.cs" />
<None Remove="**/tools/**/*" />
<None Include="**/tools/**/*" Pack="false" CopyToOutputDirectory="Never" />
</ItemGroup>
</Project>

View File

@@ -1,3 +1,6 @@
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
using StellaOps.Findings.Ledger.Options;
using StellaOps.Findings.Ledger.WebService.Contracts;
using StellaOps.Findings.Ledger.WebService.Services;
using Xunit;
@@ -6,7 +9,17 @@ namespace StellaOps.Findings.Ledger.Tests.Exports;
public class ExportFiltersHashTests
{
private readonly ExportQueryService _service = new(new TestDataSource(), new Microsoft.Extensions.Logging.Abstractions.NullLogger<ExportQueryService>());
private readonly ExportQueryService _service;
public ExportFiltersHashTests()
{
var options = Microsoft.Extensions.Options.Options.Create(new LedgerServiceOptions
{
Database = { ConnectionString = "Host=localhost;Username=test;Password=test;Database=test" }
});
var dataSource = new LedgerDataSource(options, NullLogger<LedgerDataSource>.Instance);
_service = new ExportQueryService(dataSource, NullLogger<ExportQueryService>.Instance);
}
[Fact]
public void VexFiltersHash_IsDeterministic()
@@ -33,16 +46,4 @@ public class ExportFiltersHashTests
Assert.Equal(left, right);
}
private sealed class TestDataSource : StellaOps.Findings.Ledger.Infrastructure.Postgres.LedgerDataSource
{
public TestDataSource() : base(
Microsoft.Extensions.Options.Options.Create(new StellaOps.Findings.Ledger.Options.LedgerServiceOptions
{
Database = { ConnectionString = "Host=localhost;Username=test;Password=test;Database=test" }
}),
new Microsoft.Extensions.Logging.Abstractions.NullLogger<StellaOps.Findings.Ledger.Infrastructure.Postgres.LedgerDataSource>())
{
}
}
}

View File

@@ -1,71 +1,66 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// FindingsLedgerIntegrationTests.cs
// Sprint: SPRINT_5100_0010_0001_evidencelocker_tests
// Task: FINDINGS-5100-005
// Description: Integration test: event stream ledger state replay verify identical state
// Description: Integration test: event stream → ledger state → replay → verify identical state
// -----------------------------------------------------------------------------
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using StellaOps.Findings.Ledger.Core.Domain;
using StellaOps.Findings.Ledger.Core.Events;
using StellaOps.Findings.Ledger.Core.Projection;
using StellaOps.Findings.Ledger.Core.Repositories;
using StellaOps.TestKit;
namespace StellaOps.Findings.Ledger.Tests;
/// <summary>
/// Integration Tests for Findings Ledger
/// Task FINDINGS-5100-005: event stream ledger state replay verify identical state
/// Task FINDINGS-5100-005: event stream → ledger state → replay → verify identical state
/// </summary>
public sealed class FindingsLedgerIntegrationTests
{
#region FINDINGS-5100-005: Event Stream Ledger State Replay Verify Identical
#region FINDINGS-5100-005: Event Stream â Ledger State â Replay â Verify Identical
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EventStream_ToLedgerState_Replay_ProducesIdenticalState()
{
// Arrange
var repository = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
// Create a sequence of events
var events = new List<LedgerEvent>
var events = new List<TestLedgerEvent>
{
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.FindingCreated,
EventType: TestLedgerEventType.FindingCreated,
Timestamp: now,
Sequence: 1,
Payload: JsonSerializer.Serialize(new { cveId = "CVE-2024-1234", severity = "critical" }),
Hash: ComputeEventHash(1, "FindingCreated", now)
),
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.StatusChanged,
EventType: TestLedgerEventType.StatusChanged,
Timestamp: now.AddMinutes(5),
Sequence: 2,
Payload: JsonSerializer.Serialize(new { previousStatus = "open", newStatus = "investigating" }),
Hash: ComputeEventHash(2, "StatusChanged", now.AddMinutes(5))
),
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.VexApplied,
EventType: TestLedgerEventType.VexApplied,
Timestamp: now.AddMinutes(10),
Sequence: 3,
Payload: JsonSerializer.Serialize(new { vexStatus = "not_affected", justification = "vulnerable_code_not_present" }),
@@ -98,9 +93,9 @@ public sealed class FindingsLedgerIntegrationTests
public async Task EventStream_WithSameEvents_ProducesSameStateHash()
{
// Arrange
var repository1 = new InMemoryLedgerEventRepository();
var repository2 = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository1 = new TestInMemoryLedgerEventRepository();
var repository2 = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
@@ -128,9 +123,9 @@ public sealed class FindingsLedgerIntegrationTests
public async Task EventStream_DifferentEvents_ProducesDifferentStateHash()
{
// Arrange
var repository1 = new InMemoryLedgerEventRepository();
var repository2 = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository1 = new TestInMemoryLedgerEventRepository();
var repository2 = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
@@ -159,8 +154,8 @@ public sealed class FindingsLedgerIntegrationTests
public async Task ReplayMultipleTimes_AlwaysProducesIdenticalState()
{
// Arrange
var repository = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
@@ -171,7 +166,7 @@ public sealed class FindingsLedgerIntegrationTests
await repository.AppendAsync(evt, CancellationToken.None);
// Act - Replay 10 times
var projections = new List<LedgerProjection>();
var projections = new List<TestLedgerProjection>();
for (int i = 0; i < 10; i++)
{
var projection = await ProjectLedgerStateAsync(repository, reducer, tenantId, findingId);
@@ -188,21 +183,21 @@ public sealed class FindingsLedgerIntegrationTests
public async Task EventStream_AfterAppendingMore_StateUpdatesCorrectly()
{
// Arrange
var repository = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
// Initial events
var initialEvents = new List<LedgerEvent>
var initialEvents = new List<TestLedgerEvent>
{
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.FindingCreated,
EventType: TestLedgerEventType.FindingCreated,
Timestamp: now,
Sequence: 1,
Payload: "{}",
@@ -217,11 +212,11 @@ public sealed class FindingsLedgerIntegrationTests
var initialProjection = await ProjectLedgerStateAsync(repository, reducer, tenantId, findingId);
// Append more events
var additionalEvent = new LedgerEvent(
var additionalEvent = new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.StatusChanged,
EventType: TestLedgerEventType.StatusChanged,
Timestamp: now.AddMinutes(5),
Sequence: 2,
Payload: JsonSerializer.Serialize(new { newStatus = "resolved" }),
@@ -243,8 +238,8 @@ public sealed class FindingsLedgerIntegrationTests
public async Task ConcurrentReplays_ProduceIdenticalResults()
{
// Arrange
var repository = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
@@ -275,8 +270,8 @@ public sealed class FindingsLedgerIntegrationTests
public async Task LedgerState_AtPointInTime_IsReproducible()
{
// Arrange
var repository = new InMemoryLedgerEventRepository();
var reducer = new LedgerProjectionReducer();
var repository = new TestInMemoryLedgerEventRepository();
var reducer = new TestLedgerProjectionReducer();
var tenantId = Guid.NewGuid().ToString("D");
var findingId = Guid.NewGuid();
@@ -300,9 +295,9 @@ public sealed class FindingsLedgerIntegrationTests
#region Helpers
private static async Task<LedgerProjection> ProjectLedgerStateAsync(
InMemoryLedgerEventRepository repository,
LedgerProjectionReducer reducer,
private static async Task<TestLedgerProjection> ProjectLedgerStateAsync(
TestInMemoryLedgerEventRepository repository,
TestLedgerProjectionReducer reducer,
string tenantId,
Guid findingId)
{
@@ -310,9 +305,9 @@ public sealed class FindingsLedgerIntegrationTests
return reducer.Project(events.ToList());
}
private static async Task<LedgerProjection> ProjectLedgerStateAtTimeAsync(
InMemoryLedgerEventRepository repository,
LedgerProjectionReducer reducer,
private static async Task<TestLedgerProjection> ProjectLedgerStateAtTimeAsync(
TestInMemoryLedgerEventRepository repository,
TestLedgerProjectionReducer reducer,
string tenantId,
Guid findingId,
DateTimeOffset asOf)
@@ -322,35 +317,35 @@ public sealed class FindingsLedgerIntegrationTests
return reducer.Project(filteredEvents);
}
private static List<LedgerEvent> CreateStandardEventSequence(string tenantId, Guid findingId, DateTimeOffset baseTime)
private static List<TestLedgerEvent> CreateStandardEventSequence(string tenantId, Guid findingId, DateTimeOffset baseTime)
{
return new List<LedgerEvent>
return new List<TestLedgerEvent>
{
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.FindingCreated,
EventType: TestLedgerEventType.FindingCreated,
Timestamp: baseTime,
Sequence: 1,
Payload: JsonSerializer.Serialize(new { cveId = "CVE-2024-1234" }),
Hash: ComputeEventHash(1, "FindingCreated", baseTime)
),
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.StatusChanged,
EventType: TestLedgerEventType.StatusChanged,
Timestamp: baseTime.AddMinutes(5),
Sequence: 2,
Payload: JsonSerializer.Serialize(new { newStatus = "investigating" }),
Hash: ComputeEventHash(2, "StatusChanged", baseTime.AddMinutes(5))
),
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.VexApplied,
EventType: TestLedgerEventType.VexApplied,
Timestamp: baseTime.AddMinutes(10),
Sequence: 3,
Payload: JsonSerializer.Serialize(new { vexStatus = "not_affected" }),
@@ -359,25 +354,25 @@ public sealed class FindingsLedgerIntegrationTests
};
}
private static List<LedgerEvent> CreateAlternateEventSequence(string tenantId, Guid findingId, DateTimeOffset baseTime)
private static List<TestLedgerEvent> CreateAlternateEventSequence(string tenantId, Guid findingId, DateTimeOffset baseTime)
{
return new List<LedgerEvent>
return new List<TestLedgerEvent>
{
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.FindingCreated,
EventType: TestLedgerEventType.FindingCreated,
Timestamp: baseTime,
Sequence: 1,
Payload: JsonSerializer.Serialize(new { cveId = "CVE-2024-5678" }), // Different CVE
Hash: ComputeEventHash(1, "FindingCreated", baseTime)
),
new LedgerEvent(
new TestLedgerEvent(
EventId: Guid.NewGuid(),
TenantId: tenantId,
FindingId: findingId,
EventType: LedgerEventType.StatusChanged,
EventType: TestLedgerEventType.StatusChanged,
Timestamp: baseTime.AddMinutes(5),
Sequence: 2,
Payload: JsonSerializer.Serialize(new { newStatus = "resolved" }), // Different status
@@ -396,17 +391,17 @@ public sealed class FindingsLedgerIntegrationTests
#endregion
}
#region Supporting Types (if not available in the project)
#region Supporting Types (test-local implementations)
/// <summary>
/// Simplified in-memory repository for testing.
/// </summary>
internal class InMemoryLedgerEventRepository
internal class TestInMemoryLedgerEventRepository
{
private readonly List<LedgerEvent> _events = new();
private readonly List<TestLedgerEvent> _events = new();
private readonly object _lock = new();
public Task AppendAsync(LedgerEvent evt, CancellationToken ct)
public Task AppendAsync(TestLedgerEvent evt, CancellationToken ct)
{
lock (_lock)
{
@@ -415,7 +410,7 @@ internal class InMemoryLedgerEventRepository
return Task.CompletedTask;
}
public Task<IEnumerable<LedgerEvent>> GetEventsAsync(string tenantId, Guid findingId, CancellationToken ct)
public Task<IEnumerable<TestLedgerEvent>> GetEventsAsync(string tenantId, Guid findingId, CancellationToken ct)
{
lock (_lock)
{
@@ -423,7 +418,7 @@ internal class InMemoryLedgerEventRepository
.Where(e => e.TenantId == tenantId && e.FindingId == findingId)
.OrderBy(e => e.Sequence)
.ToList();
return Task.FromResult<IEnumerable<LedgerEvent>>(filtered);
return Task.FromResult<IEnumerable<TestLedgerEvent>>(filtered);
}
}
}
@@ -431,12 +426,12 @@ internal class InMemoryLedgerEventRepository
/// <summary>
/// Simplified projection reducer for testing.
/// </summary>
internal class LedgerProjectionReducer
internal class TestLedgerProjectionReducer
{
public LedgerProjection Project(IList<LedgerEvent> events)
public TestLedgerProjection Project(IList<TestLedgerEvent> events)
{
if (events.Count == 0)
return new LedgerProjection(Guid.Empty, "unknown", "", 0, DateTimeOffset.MinValue);
return new TestLedgerProjection(Guid.Empty, "unknown", "", 0, DateTimeOffset.MinValue);
var findingId = events[0].FindingId;
var status = "open";
@@ -444,7 +439,7 @@ internal class LedgerProjectionReducer
foreach (var evt in events)
{
if (evt.EventType == LedgerEventType.StatusChanged)
if (evt.EventType == TestLedgerEventType.StatusChanged)
{
using var doc = JsonDocument.Parse(evt.Payload);
if (doc.RootElement.TryGetProperty("newStatus", out var newStatus))
@@ -459,13 +454,12 @@ internal class LedgerProjectionReducer
// Compute cycle hash from all events
var cycleHash = ComputeCycleHash(events);
return new LedgerProjection(findingId, status, cycleHash, events.Count, lastTimestamp);
return new TestLedgerProjection(findingId, status, cycleHash, events.Count, lastTimestamp);
}
private static string ComputeCycleHash(IList<LedgerEvent> events)
private static string ComputeCycleHash(IList<TestLedgerEvent> events)
{
using var sha256 = SHA256.Create();
using StellaOps.TestKit;
var combined = new StringBuilder();
foreach (var evt in events.OrderBy(e => e.Sequence))
@@ -481,7 +475,7 @@ using StellaOps.TestKit;
/// <summary>
/// Ledger event type enumeration.
/// </summary>
internal enum LedgerEventType
internal enum TestLedgerEventType
{
FindingCreated,
StatusChanged,
@@ -493,11 +487,11 @@ internal enum LedgerEventType
/// <summary>
/// Ledger event record.
/// </summary>
internal record LedgerEvent(
internal record TestLedgerEvent(
Guid EventId,
string TenantId,
Guid FindingId,
LedgerEventType EventType,
TestLedgerEventType EventType,
DateTimeOffset Timestamp,
int Sequence,
string Payload,
@@ -507,7 +501,7 @@ internal record LedgerEvent(
/// <summary>
/// Ledger projection record.
/// </summary>
internal record LedgerProjection(
internal record TestLedgerProjection(
Guid FindingId,
string Status,
string CycleHash,

View File

@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// FindingsLedgerWebServiceContractTests.cs
// Sprint: SPRINT_5100_0010_0001_evidencelocker_tests
// Task: FINDINGS-5100-004
@@ -272,7 +272,6 @@ public sealed class FindingsLedgerWebServiceContractTests : IDisposable
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
using StellaOps.TestKit;
// Navigate to FindingSummary schema
if (doc.RootElement.TryGetProperty("components", out var components) &&
components.TryGetProperty("schemas", out var schemas) &&

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Findings.Ledger.Options;
using StellaOps.Findings.Ledger.Services.Incident;
using StellaOps.Findings.Ledger.Tests.Observability;
@@ -18,7 +12,7 @@ public class LedgerIncidentCoordinatorTests
[Fact]
public async Task Activation_updates_state_and_notifies()
{
var options = Options.Create(new LedgerIncidentOptions { RetentionExtensionDays = 45, LagTraceThresholdSeconds = 0.0 });
var options = Microsoft.Extensions.Options.Options.Create(new LedgerIncidentOptions { RetentionExtensionDays = 45, LagTraceThresholdSeconds = 0.0 });
var logger = new TestLogger<LedgerIncidentCoordinator>();
var notifier = new TestNotifier();
var incidentService = new StubIncidentModeService();
@@ -36,7 +30,7 @@ public class LedgerIncidentCoordinatorTests
[Fact]
public async Task RecordProjectionLag_emits_when_active_and_above_threshold()
{
var options = Options.Create(new LedgerIncidentOptions { LagTraceThresholdSeconds = 0.1, RetentionExtensionDays = 5 });
var options = Microsoft.Extensions.Options.Options.Create(new LedgerIncidentOptions { LagTraceThresholdSeconds = 0.1, RetentionExtensionDays = 5 });
var logger = new TestLogger<LedgerIncidentCoordinator>();
var notifier = new TestNotifier();
var incidentService = new StubIncidentModeService();
@@ -116,7 +110,7 @@ public class LedgerIncidentCoordinatorTests
}
public Task<DateTimeOffset?> ExtendTtlAsync(TimeSpan extension, string actor, CancellationToken ct = default) =>
Task.FromResult<DateTimeOffset?>(_state?.ExpiresAt?.Add(extension));
Task.FromResult<DateTimeOffset?>(_state?.ExpiresAt.Add(extension));
public IReadOnlyDictionary<string, string> GetIncidentTags() => new Dictionary<string, string>();
}

View File

@@ -116,7 +116,7 @@ public sealed class LedgerReplayDeterminismTests
var baseTime = DateTimeOffset.UtcNow;
var events = CreateFindingEventSequence(tenantId, findingId, chainId, baseTime);
var reversedEvents = events.Reverse().ToList();
var reversedEvents = events.AsEnumerable().Reverse().ToList();
// Act - Replay in forward and reverse order
var projectionForward = ReplayEvents(events);
@@ -212,7 +212,7 @@ public sealed class LedgerReplayDeterminismTests
var canonicalJson = CreateCanonicalProjectionJson(projection!);
// Assert - Multiple calls should produce identical JSON
var canonicalJson2 = CreateCanonicalProjectionJson(projection);
var canonicalJson2 = CreateCanonicalProjectionJson(projection!);
canonicalJson.Should().Be(canonicalJson2);
}
@@ -417,9 +417,10 @@ public sealed class LedgerReplayDeterminismTests
}
};
var canonicalJson = LedgerCanonicalJsonSerializer.Serialize(eventBody);
var eventHash = LedgerHashing.ComputeEventHash(canonicalJson, previousHash);
var merkleLeaf = LedgerHashing.ComputeMerkleLeaf(eventBody);
var hashResult = LedgerHashing.ComputeHashes(eventBody, sequence);
var eventHash = hashResult.EventHash;
var merkleLeaf = hashResult.MerkleLeafHash;
var canonicalJson = hashResult.CanonicalJson;
return new LedgerEventRecord(
TenantId: tenantId,

View File

@@ -11,8 +11,8 @@ public class LedgerMetricsTests
[Fact]
public void RecordProjectionApply_emits_histogram_and_counter_with_tags()
{
var histogramValues = new List<Measurement<double>>();
var counterValues = new List<Measurement<long>>();
var histogramValues = new List<(double Value, KeyValuePair<string, object?>[] Tags)>();
var counterValues = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
using var listener = new MeterListener
{
@@ -25,19 +25,19 @@ public class LedgerMetricsTests
}
};
listener.SetMeasurementEventCallback<double>((instrument, measurement, _) =>
listener.SetMeasurementEventCallback<double>((instrument, measurement, tags, state) =>
{
if (instrument.Name is "ledger_projection_apply_seconds" or "ledger_projection_lag_seconds")
{
histogramValues.Add(measurement);
histogramValues.Add((measurement, tags.ToArray()));
}
});
listener.SetMeasurementEventCallback<long>((instrument, measurement, _) =>
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
if (instrument.Name == "ledger_projection_events_total")
{
counterValues.Add(measurement);
counterValues.Add((measurement, tags.ToArray()));
}
});

View File

@@ -18,7 +18,7 @@ internal sealed class TestLogger<T> : ILogger<T>
_entries.Add(new LogEntry(logLevel, eventId, state));
}
internal sealed record LogEntry(LogLevel Level, EventId EventId, object State);
internal sealed record LogEntry(LogLevel Level, EventId EventId, object? State);
private sealed class NullScope : IDisposable
{

View File

@@ -23,25 +23,10 @@ public class LedgerEventWriteServiceIncidentTests
.ReturnsAsync((LedgerEventRecord?)null);
var chainId = Guid.NewGuid();
var chainHead = new LedgerEventRecord(
"tenant-a",
chainId,
1,
Guid.NewGuid(),
LedgerEventConstants.EventFindingCreated,
"v1",
"finding-1",
"artifact-1",
null,
"actor-1",
"operator",
DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow,
new JsonObject(),
"hash-prev",
LedgerEventConstants.EmptyHash,
"leaf-hash",
"{}");
var chainHead = new LedgerChainHead(
SequenceNumber: 1,
EventHash: "hash-prev",
RecordedAt: DateTimeOffset.UtcNow);
repo.Setup(r => r.GetChainHeadAsync("tenant-a", chainId, It.IsAny<CancellationToken>()))
.ReturnsAsync(chainHead);

View File

@@ -5,16 +5,26 @@
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<!-- Project-specific settings (from deleted Directory.Build.props) -->
<DefaultItemExcludes>$(DefaultItemExcludes);**/tools/**/*</DefaultItemExcludes>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<Compile Remove="**/tools/**/*.cs" />
<None Remove="**/tools/**/*" />
<None Include="**/tools/**/*" Pack="false" CopyToOutputDirectory="Never" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.5.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<ProjectReference Include="../StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj" />
<ProjectReference Include="../StellaOps.Findings.Ledger.WebService/StellaOps.Findings.Ledger.WebService.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<ProjectReference Include="../../Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core.csproj" />
</ItemGroup>
</Project>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
</Project>

View File

@@ -1,3 +1,4 @@
#pragma warning disable CS0436 // Type conflicts with imported type - local Program class is intentionally used
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
@@ -1942,7 +1943,7 @@ static async Task<Results<FileStreamHttpResult, JsonHttpResult<ExportPage<T>>, P
}
httpContext.Response.Headers["X-Stella-Result-Count"] = page.Items.Count.ToString();
var acceptsNdjson = httpContext.Request.Headers.Accept.Any(h => h.Contains("application/x-ndjson", StringComparison.OrdinalIgnoreCase));
var acceptsNdjson = httpContext.Request.Headers.Accept.Any(h => h?.Contains("application/x-ndjson", StringComparison.OrdinalIgnoreCase) == true);
if (acceptsNdjson)
{
httpContext.Response.ContentType = "application/x-ndjson";
@@ -1975,32 +1976,32 @@ static bool TryGetTenant(HttpContext httpContext, out ProblemHttpResult? problem
return true;
}
static int? ParseInt(string value)
static int? ParseInt(string? value)
{
return int.TryParse(value, out var result) ? result : null;
}
static long? ParseLong(string value)
static long? ParseLong(string? value)
{
return long.TryParse(value, out var result) ? result : null;
}
static DateTimeOffset? ParseDate(string value)
static DateTimeOffset? ParseDate(string? value)
{
return DateTimeOffset.TryParse(value, out var result) ? result : null;
}
static decimal? ParseDecimal(string value)
static decimal? ParseDecimal(string? value)
{
return decimal.TryParse(value, out var result) ? result : null;
}
static bool? ParseBool(string value)
static bool? ParseBool(string? value)
{
return bool.TryParse(value, out var result) ? result : null;
}
static Guid? ParseGuid(string value)
static Guid? ParseGuid(string? value)
{
return Guid.TryParse(value, out var result) ? result : null;
}

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"StellaOps.Findings.Ledger.WebService": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:62523;http://localhost:62524"
}
}
}

View File

@@ -297,7 +297,7 @@ public sealed class FindingScoringService : IFindingScoringService
CancellationToken ct)
{
var cacheKey = GetCacheKey(findingId);
if (_cache.TryGetValue<EvidenceWeightedScoreResponse>(cacheKey, out var cached))
if (_cache.TryGetValue<EvidenceWeightedScoreResponse>(cacheKey, out var cached) && cached is not null)
{
return Task.FromResult<EvidenceWeightedScoreResponse?>(cached with { FromCache = true });
}

View File

@@ -7,8 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.Sinks.Console" />
</ItemGroup>
<ItemGroup>
@@ -21,7 +21,7 @@
<ProjectReference Include="..\..\Telemetry\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core.csproj" />
<ProjectReference Include="..\..\Scanner\__Libraries\StellaOps.Scanner.Reachability\StellaOps.Scanner.Reachability.csproj" />
<ProjectReference Include="..\..\Scanner\__Libraries\StellaOps.Scanner.Analyzers.Native\StellaOps.Scanner.Analyzers.Native.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj" />
<ProjectReference Include="..\..\Router/__Libraries/StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj" />
<ProjectReference Include="..\..\Signals\StellaOps.Signals\StellaOps.Signals.csproj" />
</ItemGroup>

View File

@@ -42,7 +42,7 @@ public sealed class AttachmentEncryptionService : IAttachmentEncryptionService
var nonce = RandomNumberGenerator.GetBytes(12);
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];
using var aes = new AesGcm(masterKey);
using var aes = new AesGcm(masterKey, 16); // Specify tag size for AES-256-GCM
aes.Encrypt(nonce, plaintext, ciphertext, tag);
return new AttachmentEncryptionResult(

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@@ -18,12 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Npgsql" Version="7.0.7" />
<PackageReference Include="Npgsql" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,6 +10,6 @@
<ProjectReference Include="..\..\StellaOps.Findings.Ledger.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,7 @@
using System.CommandLine;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -13,6 +14,7 @@ using StellaOps.Findings.Ledger.Domain;
using StellaOps.Findings.Ledger.Hashing;
using StellaOps.Findings.Ledger.Infrastructure;
using StellaOps.Findings.Ledger.Infrastructure.Merkle;
using StellaOps.Findings.Ledger.Infrastructure.Policy;
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
using StellaOps.Findings.Ledger.Infrastructure.Projection;
using StellaOps.Findings.Ledger.Options;
@@ -20,55 +22,66 @@ using StellaOps.Findings.Ledger.Observability;
using StellaOps.Findings.Ledger.Services;
// Command-line options
var fixturesOption = new Option<FileInfo[]>(
name: "--fixture",
description: "NDJSON fixtures containing canonical ledger envelopes (sequence-ordered)")
var fixturesOption = new Option<FileInfo[]>("--fixture")
{
IsRequired = true
};
fixturesOption.AllowMultipleArgumentsPerToken = true;
var connectionOption = new Option<string>(
name: "--connection",
description: "PostgreSQL connection string for ledger DB")
{
IsRequired = true
Description = "NDJSON fixtures containing canonical ledger envelopes (sequence-ordered)",
Required = true,
AllowMultipleArgumentsPerToken = true
};
var tenantOption = new Option<string>(
name: "--tenant",
getDefaultValue: () => "tenant-a",
description: "Tenant identifier for appended events");
var connectionOption = new Option<string>("--connection")
{
Description = "PostgreSQL connection string for ledger DB",
Required = true
};
var maxParallelOption = new Option<int>(
name: "--maxParallel",
getDefaultValue: () => 4,
description: "Maximum concurrent append operations");
var tenantOption = new Option<string>("--tenant")
{
Description = "Tenant identifier for appended events",
DefaultValueFactory = _ => "tenant-a"
};
var reportOption = new Option<FileInfo?>(
name: "--report",
description: "Path to write harness report JSON (with DSSE placeholder)");
var maxParallelOption = new Option<int>("--maxParallel")
{
Description = "Maximum concurrent append operations",
DefaultValueFactory = _ => 4
};
var metricsOption = new Option<FileInfo?>(
name: "--metrics",
description: "Optional path to write metrics snapshot JSON");
var reportOption = new Option<FileInfo?>("--report")
{
Description = "Path to write harness report JSON (with DSSE placeholder)"
};
var expectedChecksumOption = new Option<FileInfo?>(
name: "--expected-checksum",
description: "Optional JSON file containing expected eventStream/projection checksums");
var metricsOption = new Option<FileInfo?>("--metrics")
{
Description = "Optional path to write metrics snapshot JSON"
};
var expectedChecksumOption = new Option<FileInfo?>("--expected-checksum")
{
Description = "Optional JSON file containing expected eventStream/projection checksums"
};
var root = new RootCommand("Findings Ledger Replay Harness (LEDGER-29-008)");
root.AddOption(fixturesOption);
root.AddOption(connectionOption);
root.AddOption(tenantOption);
root.AddOption(maxParallelOption);
root.AddOption(reportOption);
root.AddOption(metricsOption);
root.AddOption(expectedChecksumOption);
root.Add(fixturesOption);
root.Add(connectionOption);
root.Add(tenantOption);
root.Add(maxParallelOption);
root.Add(reportOption);
root.Add(metricsOption);
root.Add(expectedChecksumOption);
root.SetHandler(async (FileInfo[] fixtures, string connection, string tenant, int maxParallel, FileInfo? reportFile, FileInfo? metricsFile, FileInfo? expectedChecksumsFile) =>
root.SetAction(async (parseResult, ct) =>
{
await using var host = BuildHost(connection);
var fixtures = parseResult.GetValue(fixturesOption)!;
var connection = parseResult.GetValue(connectionOption)!;
var tenant = parseResult.GetValue(tenantOption)!;
var maxParallel = parseResult.GetValue(maxParallelOption);
var reportFile = parseResult.GetValue(reportOption);
var metricsFile = parseResult.GetValue(metricsOption);
var expectedChecksumsFile = parseResult.GetValue(expectedChecksumOption);
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
using var host = BuildHost(connection);
using var scope = host.Services.CreateScope();
var writeService = scope.ServiceProvider.GetRequiredService<ILedgerEventWriteService>();
@@ -77,7 +90,6 @@ root.SetHandler(async (FileInfo[] fixtures, string connection, string tenant, in
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("Harness");
var timeProvider = scope.ServiceProvider.GetRequiredService<TimeProvider>();
var cts = new CancellationTokenSource();
var projectionTask = projectionWorker.StartAsync(cts.Token);
var anchorTask = anchorWorker.StartAsync(cts.Token);
@@ -124,7 +136,7 @@ root.SetHandler(async (FileInfo[] fixtures, string connection, string tenant, in
fixtures.Select(f => f.FullName).ToArray(),
eventsWritten,
sw.Elapsed.TotalSeconds,
status: verification.Success ? "pass" : "fail",
Status: verification.Success ? "pass" : "fail",
WriteLatencyP95Ms: writeLatencyP95Ms,
ProjectionRebuildP95Ms: rebuildP95Ms,
ProjectionLagSecondsMax: projectionLagSeconds,
@@ -154,9 +166,9 @@ root.SetHandler(async (FileInfo[] fixtures, string connection, string tenant, in
cts.Cancel();
await Task.WhenAll(projectionTask, anchorTask).WaitAsync(TimeSpan.FromSeconds(5));
}, fixturesOption, connectionOption, tenantOption, maxParallelOption, reportOption, metricsOption);
});
await root.InvokeAsync(args);
await root.Parse(args).InvokeAsync();
static async Task WriteDssePlaceholderAsync(string reportPath, string json, string? policyHash, CancellationToken cancellationToken)
{
@@ -246,9 +258,9 @@ static async IAsyncEnumerable<LedgerEventDraft> ReadDraftsAsync(FileInfo file, s
using var reader = new StreamReader(stream);
var recordedAtBase = timeProvider.GetUtcNow();
while (!reader.EndOfStream)
string? line;
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) is not null)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(line))
{
continue;
@@ -281,7 +293,7 @@ static LedgerEventDraft ToDraft(JsonObject node, string defaultTenant, DateTimeO
var findingId = required("finding_id");
var artifactId = required("artifact_id");
var sourceRunId = node.TryGetPropertyValue("source_run_id", out var sourceRunNode) && sourceRunNode is not null && !string.IsNullOrWhiteSpace(sourceRunNode.GetValue<string>())
? Guid.Parse(sourceRunNode!.GetValue<string>())
? (Guid?)Guid.Parse(sourceRunNode!.GetValue<string>())
: null;
var actorId = required("actor_id");
var actorType = required("actor_type");
@@ -331,7 +343,8 @@ static async Task<VerificationResult> VerifyLedgerAsync(IServiceProvider service
await using (var countCommand = new Npgsql.NpgsqlCommand("select count(*) from ledger_events where tenant_id = @tenant", connection))
{
countCommand.Parameters.AddWithValue("tenant", tenant);
var count = (long)await countCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
var countResult = await countCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
var count = countResult is long l ? l : 0L;
if (count < expectedEvents)
{
errors.Add($"event_count_mismatch:{count}/{expectedEvents}");
@@ -464,6 +477,21 @@ static double Percentile(IEnumerable<double> values, double percentile)
return data[lowerIndex] + (data[upperIndex] - data[lowerIndex]) * fraction;
}
// Local function - must be before type declarations
static ExpectedChecksums LoadExpectedChecksums(FileInfo? file)
{
if (file is null)
{
return ExpectedChecksums.Empty;
}
using var doc = JsonDocument.Parse(File.ReadAllText(file.FullName));
var root = doc.RootElement;
var eventStream = root.TryGetProperty("eventStream", out var ev) ? ev.GetString() : null;
var projection = root.TryGetProperty("projection", out var pr) ? pr.GetString() : null;
return new ExpectedChecksums(eventStream, projection);
}
internal sealed record HarnessReport(
string Tenant,
IReadOnlyList<string> Fixtures,
@@ -508,20 +536,6 @@ internal sealed class MetricsBag
};
}
static ExpectedChecksums LoadExpectedChecksums(FileInfo? file)
{
if (file is null)
{
return ExpectedChecksums.Empty;
}
using var doc = JsonDocument.Parse(File.ReadAllText(file.FullName));
var root = doc.RootElement;
var eventStream = root.TryGetProperty("eventStream", out var ev) ? ev.GetString() : null;
var projection = root.TryGetProperty("projection", out var pr) ? pr.GetString() : null;
return new ExpectedChecksums(eventStream, projection);
}
// Harness lightweight no-op implementations for projection/merkle to keep replay fast
internal sealed class NoOpPolicyEvaluationService : IPolicyEvaluationService
{
@@ -535,6 +549,7 @@ internal sealed class NoOpPolicyEvaluationService : IPolicyEvaluationService
RiskSeverity: current?.RiskSeverity,
RiskProfileVersion: current?.RiskProfileVersion,
RiskExplanationId: current?.RiskExplanationId,
RiskEventSequence: null,
Labels: labels,
ExplainRef: null,
Rationale: new JsonArray()));
@@ -556,6 +571,21 @@ internal sealed class NoOpProjectionRepository : IFindingProjectionRepository
Task.FromResult(new ProjectionCheckpoint(DateTimeOffset.MinValue, Guid.Empty, DateTimeOffset.MinValue));
public Task UpsertAsync(FindingProjection projection, CancellationToken cancellationToken) => Task.CompletedTask;
public Task<FindingStatsResult> GetFindingStatsSinceAsync(string tenantId, DateTimeOffset since, CancellationToken cancellationToken) =>
Task.FromResult(new FindingStatsResult(0, 0, 0, 0, 0, 0));
public Task<(IReadOnlyList<FindingProjection> Projections, int TotalCount)> QueryScoredAsync(ScoredFindingsQuery query, CancellationToken cancellationToken) =>
Task.FromResult<(IReadOnlyList<FindingProjection>, int)>((Array.Empty<FindingProjection>(), 0));
public Task<SeverityDistribution> GetSeverityDistributionAsync(string tenantId, string? policyVersion, CancellationToken cancellationToken) =>
Task.FromResult(new SeverityDistribution());
public Task<ScoreDistribution> GetScoreDistributionAsync(string tenantId, string? policyVersion, CancellationToken cancellationToken) =>
Task.FromResult(new ScoreDistribution());
public Task<(int Total, int Scored, decimal AvgScore, decimal MaxScore)> GetRiskAggregatesAsync(string tenantId, string? policyVersion, CancellationToken cancellationToken) =>
Task.FromResult((0, 0, 0m, 0m));
}
internal sealed class NoOpMerkleAnchorRepository : IMerkleAnchorRepository

View File

@@ -0,0 +1,705 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Findings.Ledger", "StellaOps.Findings.Ledger", "{DBE4FCDA-B1A6-F61C-4DAB-6F814B25A158}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Findings.Ledger.Tests", "StellaOps.Findings.Ledger.Tests", "{A3DEA15D-11D3-CC57-BF26-7F162261228B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Findings.Ledger.WebService", "StellaOps.Findings.Ledger.WebService", "{F9890C81-CDBE-C84D-D3D4-B7A862B78606}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{7CC40687-561F-4A18-09A2-19EB6C9A5EDD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LedgerReplayHarness", "LedgerReplayHarness", "{DEF71302-8A95-66E5-5BCE-C61CCD2880C5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__External", "__External", "{5B52EF8A-3661-DCFF-797D-BC4D6AC60BDA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AirGap", "AirGap", "{F310596E-88BB-9E54-885E-21C61971917E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.AirGap.Policy", "StellaOps.AirGap.Policy", "{D9492ED1-A812-924B-65E4-F518592B49BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.AirGap.Policy", "StellaOps.AirGap.Policy", "{3823DE1E-2ACE-C956-99E1-00DB786D9E1D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Attestor", "Attestor", "{5AC09D9A-F2A5-9CFA-B3C5-8D25F257651C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor", "StellaOps.Attestor", "{33B1AE27-692A-1778-48C1-CCEC2B9BC78F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Envelope", "StellaOps.Attestor.Envelope", "{018E0E11-1CCE-A2BE-641D-21EE14D2E90D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.Core", "StellaOps.Attestor.Core", "{5F27FB4E-CF09-3A6B-F5B4-BF5A709FA609}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{AB67BDB9-D701-3AC9-9CDF-ECCDCCD8DB6D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.GraphRoot", "StellaOps.Attestor.GraphRoot", "{3F605548-87E2-8A1D-306D-0CE6960B8242}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Attestor.ProofChain", "StellaOps.Attestor.ProofChain", "{45F7FA87-7451-6970-7F6E-F8BAE45E081B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authority", "Authority", "{C1DCEFBD-12A5-EAAE-632E-8EEB9BE491B6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Authority", "StellaOps.Authority", "{A6928CBA-4D4D-AB2B-CA19-FEE6E73ECE70}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Auth.Abstractions", "StellaOps.Auth.Abstractions", "{F2E6CB0E-DF77-1FAA-582B-62B040DF3848}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Auth.Client", "StellaOps.Auth.Client", "{C494ECBE-DEA5-3576-D2AF-200FF12BC144}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Auth.ServerIntegration", "StellaOps.Auth.ServerIntegration", "{7E890DF9-B715-B6DF-2498-FD74DDA87D71}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Authority.Plugins.Abstractions", "StellaOps.Authority.Plugins.Abstractions", "{64689413-46D7-8499-68A6-B6367ACBC597}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Concelier", "Concelier", "{157C3671-CA0B-69FA-A7C9-74A1FDA97B99}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{F39E09D6-BF93-B64A-CFE7-2BA92815C0FE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Concelier.SourceIntel", "StellaOps.Concelier.SourceIntel", "{F2B58F4E-6F28-A25F-5BFB-CDEBAD6B9A3E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Feedser", "Feedser", "{C4A90603-BE42-0044-CAB4-3EB910AD51A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.BinaryAnalysis", "StellaOps.Feedser.BinaryAnalysis", "{054761F9-16D3-B2F8-6F4D-EFC2248805CD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Feedser.Core", "StellaOps.Feedser.Core", "{B54CE64C-4167-1DD1-B7D6-2FD7A5AEF715}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Policy", "Policy", "{8E6B774C-CC4E-CE7C-AD4B-8AF7C92889A6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy.RiskProfile", "StellaOps.Policy.RiskProfile", "{BC12ED55-6015-7C8B-8384-B39CE93C76D6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{FF70543D-AFF9-1D38-4950-4F8EE18D60BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Policy", "StellaOps.Policy", "{831265B0-8896-9C95-3488-E12FD9F6DC53}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Provenance", "Provenance", "{316BBD0A-04D2-85C9-52EA-7993CC6C8930}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Provenance.Attestation", "StellaOps.Provenance.Attestation", "{9D6AB85A-85EA-D85A-5566-A121D34016E6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Router", "Router", "{FC018E5B-1E2F-DE19-1E97-0C845058C469}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{1BE5B76C-B486-560B-6CB2-44C6537249AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Messaging", "StellaOps.Messaging", "{F4F1CBE2-1CDD-CAA4-41F0-266DB4677C05}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Microservice", "StellaOps.Microservice", "{3DE1DCDC-C845-4AC7-7B66-34B0A9E8626B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Microservice.AspNetCore", "StellaOps.Microservice.AspNetCore", "{6FA01E92-606B-0CB8-8583-6F693A903CFC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Router.AspNet", "StellaOps.Router.AspNet", "{A5994E92-7E0E-89FE-5628-DE1A0176B8BA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Router.Common", "StellaOps.Router.Common", "{54C11B29-4C54-7255-AB44-BEB63AF9BD1F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scanner", "Scanner", "{5896C4B3-31D1-1EDD-11D0-C46DB178DC12}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Analyzers.Native", "StellaOps.Scanner.Analyzers.Native", "{B469ABBF-DC3D-4A71-7AA7-BD1839F4D793}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{D4D193A8-47D7-0B1A-1327-F9C580E7AD07}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Analyzers.Native", "StellaOps.Scanner.Analyzers.Native", "{612BA831-66B7-FC6C-9035-DB4368589E92}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Cache", "StellaOps.Scanner.Cache", "{76EA64F4-C653-981E-CF8B-596DF7DC64AB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Core", "StellaOps.Scanner.Core", "{C9BCCEDF-7B8A-BCD8-A6B4-75EB25689FE8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Evidence", "StellaOps.Scanner.Evidence", "{C858A6E9-AEDF-1B98-0578-7761D09C2E97}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Explainability", "StellaOps.Scanner.Explainability", "{18E8E925-7269-0AC8-8621-836C42E6F7F1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.ProofSpine", "StellaOps.Scanner.ProofSpine", "{9F30DC58-7747-31D8-2403-D7D0F5454C87}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Reachability", "StellaOps.Scanner.Reachability", "{47C8324C-B8C1-6E1A-C749-BCACF4BE3D71}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.SmartDiff", "StellaOps.Scanner.SmartDiff", "{269FC82B-1702-1933-65BC-D3F90CBB9643}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Storage.Oci", "StellaOps.Scanner.Storage.Oci", "{0E8DA218-E337-6D7F-8B78-36900DF402AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Scanner.Surface.Env", "StellaOps.Scanner.Surface.Env", "{336213F7-1241-D268-8EA5-1C73F0040714}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signals", "Signals", "{AD65DDE7-9FEA-7380-8C10-FA165F745354}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signals", "StellaOps.Signals", "{076B8074-5735-5367-1EEA-CA16A5B8ABD7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signer", "Signer", "{3247EE0D-B3E9-9C11-B0AE-FE719410390B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer", "StellaOps.Signer", "{CD7C09DA-FEC8-2CC5-D00C-E525638DFF4A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Signer.Core", "StellaOps.Signer.Core", "{79B10804-91E9-972E-1913-EE0F0B11663E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{E9A667F9-9627-4297-EF5E-0333593FDA14}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Telemetry.Core", "StellaOps.Telemetry.Core", "{B81E0B20-6C85-AC09-1DB6-5BD6CBB8AA62}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Telemetry.Core", "StellaOps.Telemetry.Core", "{74C64C1F-14F4-7B75-C354-9F252494A758}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Libraries", "__Libraries", "{1345DD29-BB3A-FB5F-4B3D-E29F6045A27A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Auth.Security", "StellaOps.Auth.Security", "{9C2DD234-FA33-FDB6-86F0-EF9B75A13450}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Canonical.Json", "StellaOps.Canonical.Json", "{79E122F4-2325-3E92-438E-5825A307B594}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Configuration", "StellaOps.Configuration", "{538E2D98-5325-3F54-BE74-EFE5FC1ECBD8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography", "StellaOps.Cryptography", "{66557252-B5C4-664B-D807-07018C627474}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.DependencyInjection", "StellaOps.Cryptography.DependencyInjection", "{7203223D-FF02-7BEB-2798-D1639ACC01C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Kms", "StellaOps.Cryptography.Kms", "{5AC9EE40-1881-5F8A-46A2-2C303950D3C8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.CryptoPro", "StellaOps.Cryptography.Plugin.CryptoPro", "{3C69853C-90E3-D889-1960-3B9229882590}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.OpenSslGost", "StellaOps.Cryptography.Plugin.OpenSslGost", "{643E4D4C-BC96-A37F-E0EC-488127F0B127}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.Pkcs11Gost", "StellaOps.Cryptography.Plugin.Pkcs11Gost", "{6F2CA7F5-3E7C-C61B-94E6-E7DD1227B5B1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.PqSoft", "StellaOps.Cryptography.Plugin.PqSoft", "{F04B7DBB-77A5-C978-B2DE-8C189A32AA72}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.SimRemote", "StellaOps.Cryptography.Plugin.SimRemote", "{7C72F22A-20FF-DF5B-9191-6DFD0D497DB2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.SmRemote", "StellaOps.Cryptography.Plugin.SmRemote", "{C896CC0A-F5E6-9AA4-C582-E691441F8D32}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.SmSoft", "StellaOps.Cryptography.Plugin.SmSoft", "{0AA3A418-AB45-CCA4-46D4-EEBFE011FECA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.Plugin.WineCsp", "StellaOps.Cryptography.Plugin.WineCsp", "{225D9926-4AE8-E539-70AD-8698E688F271}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Cryptography.PluginLoader", "StellaOps.Cryptography.PluginLoader", "{D6E8E69C-F721-BBCB-8C39-9716D53D72AD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.DependencyInjection", "StellaOps.DependencyInjection", "{589A43FD-8213-E9E3-6CFF-9CBA72D53E98}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Evidence.Bundle", "StellaOps.Evidence.Bundle", "{2BACF7E3-1278-FE99-8343-8221E6FBA9DE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Evidence.Core", "StellaOps.Evidence.Core", "{75E47125-E4D7-8482-F1A4-726564970864}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Plugin", "StellaOps.Plugin", "{772B02B5-6280-E1D4-3E2E-248D0455C2FB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Replay.Core", "StellaOps.Replay.Core", "{083067CF-CE89-EF39-9BD3-4741919E26F3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.TestKit", "StellaOps.TestKit", "{8380A20C-A5B8-EE91-1A58-270323688CB9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Tests", "__Tests", "{BB76B5A5-14BA-E317-828D-110B711D71F5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Findings.Ledger.Tests", "StellaOps.Findings.Ledger.Tests", "{2E456655-42A9-55BB-D240-2DBD7B1B4E0E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{F9D35D43-770D-3909-2A66-3E665E82AE1D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LedgerReplayHarness", "LedgerReplayHarness", "{227B8E37-08C4-699B-7432-95ECA602F68C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LedgerReplayHarness", "StellaOps.Findings.Ledger\tools\LedgerReplayHarness\LedgerReplayHarness.csproj", "{F5FB90E2-4621-B51E-84C4-61BD345FD31C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LedgerReplayHarness", "tools\LedgerReplayHarness\LedgerReplayHarness.csproj", "{D18D1912-6E44-8578-C851-983BA0F6CD9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.AirGap.Policy", "E:\dev\git.stella-ops.org\src\AirGap\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy.csproj", "{AD31623A-BC43-52C2-D906-AC1D8784A541}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Core", "E:\dev\git.stella-ops.org\src\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj", "{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.Envelope", "E:\dev\git.stella-ops.org\src\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj", "{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.GraphRoot", "E:\dev\git.stella-ops.org\src\Attestor\__Libraries\StellaOps.Attestor.GraphRoot\StellaOps.Attestor.GraphRoot.csproj", "{2609BC1A-6765-29BE-78CC-C0F1D2814F10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Attestor.ProofChain", "E:\dev\git.stella-ops.org\src\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj", "{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Abstractions", "E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj", "{55D9B653-FB76-FCE8-1A3C-67B1BEDEC214}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client", "E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj", "{DE5BF139-1E5C-D6EA-4FAA-661EF353A194}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Security", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Auth.Security\StellaOps.Auth.Security.csproj", "{335E62C0-9E69-A952-680B-753B1B17C6D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegration", "E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj", "{ECA25786-A3A8-92C4-4AA3-D4A73C69FDCA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugins.Abstractions", "E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj", "{97F94029-5419-6187-5A63-5C8FD9232FAE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Canonical.Json", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj", "{AF9E7F02-25AD-3540-18D7-F6A4F8BA5A60}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.SourceIntel", "E:\dev\git.stella-ops.org\src\Concelier\__Libraries\StellaOps.Concelier.SourceIntel\StellaOps.Concelier.SourceIntel.csproj", "{EB093C48-CDAC-106B-1196-AE34809B34C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Configuration", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Configuration\StellaOps.Configuration.csproj", "{92C62F7B-8028-6EE1-B71B-F45F459B8E97}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj", "{F664A948-E352-5808-E780-77A03F19E93E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.DependencyInjection", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.DependencyInjection\StellaOps.Cryptography.DependencyInjection.csproj", "{FA83F778-5252-0B80-5555-E69F790322EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Kms", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Kms\StellaOps.Cryptography.Kms.csproj", "{F3A27846-6DE0-3448-222C-25A273E86B2E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.CryptoPro", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.CryptoPro\StellaOps.Cryptography.Plugin.CryptoPro.csproj", "{C53E0895-879A-D9E6-0A43-24AD17A2F270}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.OpenSslGost", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj", "{0AED303F-69E6-238F-EF80-81985080EDB7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.Pkcs11Gost", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.Pkcs11Gost\StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj", "{2904D288-CE64-A565-2C46-C2E85A96A1EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.PqSoft", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.PqSoft\StellaOps.Cryptography.Plugin.PqSoft.csproj", "{A6667CC3-B77F-023E-3A67-05F99E9FF46A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.SimRemote", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.SimRemote\StellaOps.Cryptography.Plugin.SimRemote.csproj", "{A26E2816-F787-F76B-1D6C-E086DD3E19CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.SmRemote", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.SmRemote\StellaOps.Cryptography.Plugin.SmRemote.csproj", "{B3DEC619-67AC-1B5A-4F3E-A1F24C3F6877}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.SmSoft", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj", "{90DB65B4-8F6E-FB8E-0281-505AD8BC6BA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Plugin.WineCsp", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.WineCsp\StellaOps.Cryptography.Plugin.WineCsp.csproj", "{059FBB86-DEE6-8207-3F23-2A1A3EC00DEA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.PluginLoader", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.PluginLoader\StellaOps.Cryptography.PluginLoader.csproj", "{8BBA3159-C4CC-F685-A28C-7FE6CBD3D2A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.DependencyInjection", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj", "{632A1F0D-1BA5-C84B-B716-2BE638A92780}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Evidence.Bundle", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Evidence.Bundle\StellaOps.Evidence.Bundle.csproj", "{9DE7852B-7E2D-257E-B0F1-45D2687854ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Evidence.Core", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Evidence.Core\StellaOps.Evidence.Core.csproj", "{DC2AFC89-C3C8-4E9B-13A7-027EB6386EFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.BinaryAnalysis", "E:\dev\git.stella-ops.org\src\Feedser\StellaOps.Feedser.BinaryAnalysis\StellaOps.Feedser.BinaryAnalysis.csproj", "{CB296A20-2732-77C1-7F23-27D5BAEDD0C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core", "E:\dev\git.stella-ops.org\src\Feedser\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj", "{0DBEC9BA-FE1D-3898-B2C6-E4357DC23E0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Findings.Ledger", "StellaOps.Findings.Ledger\StellaOps.Findings.Ledger.csproj", "{356E10E9-4223-A6BC-BE0C-0DC376DDC391}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Findings.Ledger.Tests", "__Tests\StellaOps.Findings.Ledger.Tests\StellaOps.Findings.Ledger.Tests.csproj", "{09D88001-1724-612D-3B2D-1F3AC6F49690}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Findings.Ledger.Tests", "StellaOps.Findings.Ledger.Tests\StellaOps.Findings.Ledger.Tests.csproj", "{0066F933-EBB7-CF9D-0A28-B35BBDC24CC6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Findings.Ledger.WebService", "StellaOps.Findings.Ledger.WebService\StellaOps.Findings.Ledger.WebService.csproj", "{BC1D62FA-C2B1-96BD-3EFF-F944CDA26ED3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Messaging", "E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Messaging\StellaOps.Messaging.csproj", "{97998C88-E6E1-D5E2-B632-537B58E00CBF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Microservice", "E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Microservice\StellaOps.Microservice.csproj", "{BAD08D96-A80A-D27F-5D9C-656AEEB3D568}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Microservice.AspNetCore", "E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Microservice.AspNetCore\StellaOps.Microservice.AspNetCore.csproj", "{F63694F1-B56D-6E72-3F5D-5D38B1541F0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Plugin", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj", "{38A9EE9B-6FC8-93BC-0D43-2A906E678D66}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy", "E:\dev\git.stella-ops.org\src\Policy\__Libraries\StellaOps.Policy\StellaOps.Policy.csproj", "{19868E2D-7163-2108-1094-F13887C4F070}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Policy.RiskProfile", "E:\dev\git.stella-ops.org\src\Policy\StellaOps.Policy.RiskProfile\StellaOps.Policy.RiskProfile.csproj", "{CC319FC5-F4B1-C3DD-7310-4DAD343E0125}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Provenance.Attestation", "E:\dev\git.stella-ops.org\src\Provenance\StellaOps.Provenance.Attestation\StellaOps.Provenance.Attestation.csproj", "{A78EBC0F-C62C-8F56-95C0-330E376242A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Replay.Core", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj", "{6D26FB21-7E48-024B-E5D4-E3F0F31976BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Router.AspNet", "E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj", "{79104479-B087-E5D0-5523-F1803282A246}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Router.Common", "E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Common\StellaOps.Router.Common.csproj", "{F17A6F0B-3120-2BA9-84D8-5F8BA0B9705D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Analyzers.Native", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Analyzers.Native\StellaOps.Scanner.Analyzers.Native.csproj", "{F22333B6-7E27-679B-8475-B4B9AB1CB186}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Analyzers.Native", "E:\dev\git.stella-ops.org\src\Scanner\StellaOps.Scanner.Analyzers.Native\StellaOps.Scanner.Analyzers.Native.csproj", "{CE042F3A-6851-FAAB-9E9C-AD905B4AAC8D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Cache", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Cache\StellaOps.Scanner.Cache.csproj", "{BA492274-A505-BCD5-3DA5-EE0C94DD5748}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Core", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Core\StellaOps.Scanner.Core.csproj", "{58D8630F-C0F4-B772-8572-BCC98FF0F0D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Evidence", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Evidence\StellaOps.Scanner.Evidence.csproj", "{37F1D83D-073C-C165-4C53-664AD87628E6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Explainability", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Explainability\StellaOps.Scanner.Explainability.csproj", "{ACC2785F-F4B9-13E4-EED2-C5D067242175}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.ProofSpine", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.ProofSpine\StellaOps.Scanner.ProofSpine.csproj", "{7CB7FEA8-8A12-A5D6-0057-AA65DB328617}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Reachability", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Reachability\StellaOps.Scanner.Reachability.csproj", "{35A06F00-71AB-8A31-7D60-EBF41EA730CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.SmartDiff", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.SmartDiff\StellaOps.Scanner.SmartDiff.csproj", "{7F0FFA06-EAC8-CC9A-3386-389638F12B59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Storage.Oci", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Storage.Oci\StellaOps.Scanner.Storage.Oci.csproj", "{A80D212B-7E80-4251-16C0-60FA3670A5B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Surface.Env", "E:\dev\git.stella-ops.org\src\Scanner\__Libraries\StellaOps.Scanner.Surface.Env\StellaOps.Scanner.Surface.Env.csproj", "{52698305-D6F8-C13C-0882-48FC37726404}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Signals", "E:\dev\git.stella-ops.org\src\Signals\StellaOps.Signals\StellaOps.Signals.csproj", "{A79CBC0C-5313-4ECF-A24E-27CE236BCF2C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Signer.Core", "E:\dev\git.stella-ops.org\src\Signer\StellaOps.Signer\StellaOps.Signer.Core\StellaOps.Signer.Core.csproj", "{0AF13355-173C-3128-5AFC-D32E540DA3EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Telemetry.Core", "E:\dev\git.stella-ops.org\src\Telemetry\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core.csproj", "{8CD19568-1638-B8F6-8447-82CFD4F17ADF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.TestKit", "E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj", "{AF043113-CCE3-59C1-DF71-9804155F26A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F5FB90E2-4621-B51E-84C4-61BD345FD31C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5FB90E2-4621-B51E-84C4-61BD345FD31C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5FB90E2-4621-B51E-84C4-61BD345FD31C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5FB90E2-4621-B51E-84C4-61BD345FD31C}.Release|Any CPU.Build.0 = Release|Any CPU
{D18D1912-6E44-8578-C851-983BA0F6CD9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D18D1912-6E44-8578-C851-983BA0F6CD9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D18D1912-6E44-8578-C851-983BA0F6CD9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D18D1912-6E44-8578-C851-983BA0F6CD9F}.Release|Any CPU.Build.0 = Release|Any CPU
{AD31623A-BC43-52C2-D906-AC1D8784A541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD31623A-BC43-52C2-D906-AC1D8784A541}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD31623A-BC43-52C2-D906-AC1D8784A541}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD31623A-BC43-52C2-D906-AC1D8784A541}.Release|Any CPU.Build.0 = Release|Any CPU
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B4DF41E-C8CC-2606-FA2D-967118BD3C59}.Release|Any CPU.Build.0 = Release|Any CPU
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D8C5A6C-462D-7487-5BD0-A3EF6B657EB6}.Release|Any CPU.Build.0 = Release|Any CPU
{2609BC1A-6765-29BE-78CC-C0F1D2814F10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2609BC1A-6765-29BE-78CC-C0F1D2814F10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2609BC1A-6765-29BE-78CC-C0F1D2814F10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2609BC1A-6765-29BE-78CC-C0F1D2814F10}.Release|Any CPU.Build.0 = Release|Any CPU
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6822231-A4F4-9E69-6CE2-4FDB3E81C728}.Release|Any CPU.Build.0 = Release|Any CPU
{55D9B653-FB76-FCE8-1A3C-67B1BEDEC214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55D9B653-FB76-FCE8-1A3C-67B1BEDEC214}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55D9B653-FB76-FCE8-1A3C-67B1BEDEC214}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55D9B653-FB76-FCE8-1A3C-67B1BEDEC214}.Release|Any CPU.Build.0 = Release|Any CPU
{DE5BF139-1E5C-D6EA-4FAA-661EF353A194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE5BF139-1E5C-D6EA-4FAA-661EF353A194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE5BF139-1E5C-D6EA-4FAA-661EF353A194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE5BF139-1E5C-D6EA-4FAA-661EF353A194}.Release|Any CPU.Build.0 = Release|Any CPU
{335E62C0-9E69-A952-680B-753B1B17C6D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{335E62C0-9E69-A952-680B-753B1B17C6D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{335E62C0-9E69-A952-680B-753B1B17C6D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{335E62C0-9E69-A952-680B-753B1B17C6D0}.Release|Any CPU.Build.0 = Release|Any CPU
{ECA25786-A3A8-92C4-4AA3-D4A73C69FDCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECA25786-A3A8-92C4-4AA3-D4A73C69FDCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECA25786-A3A8-92C4-4AA3-D4A73C69FDCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECA25786-A3A8-92C4-4AA3-D4A73C69FDCA}.Release|Any CPU.Build.0 = Release|Any CPU
{97F94029-5419-6187-5A63-5C8FD9232FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97F94029-5419-6187-5A63-5C8FD9232FAE}.Debug|Any CPU.Build.0 = Debug|Any CPU

View File

@@ -1,4 +1,4 @@
using System.Text.Json;
using System.Text.Json;
using LedgerReplayHarness;
using FluentAssertions;
using Xunit;
@@ -23,7 +23,6 @@ public class HarnessRunnerTests
var json = await File.ReadAllTextAsync(tempReport);
using var doc = JsonDocument.Parse(json);
using StellaOps.TestKit;
doc.RootElement.GetProperty("eventsWritten").GetInt64().Should().BeGreaterThan(0);
doc.RootElement.GetProperty("status").GetString().Should().Be("pass");
doc.RootElement.GetProperty("tenant").GetString().Should().Be("tenant-test");

View File

@@ -1,4 +1,4 @@
using System.Diagnostics.Metrics;
using System.Diagnostics.Metrics;
using System.Linq;
using FluentAssertions;
using StellaOps.Findings.Ledger.Observability;
@@ -195,7 +195,6 @@ public class LedgerMetricsTests
public void VersionInfoGauge_EmitsConstantOne()
{
using var listener = CreateListener();
using StellaOps.TestKit;
var measurements = new List<(long Value, KeyValuePair<string, object?>[] Tags)>();
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>

View File

@@ -1,4 +1,4 @@
using System.Net;
using System.Net;
using System.Net.Http;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging.Abstractions;
@@ -90,7 +90,6 @@ public sealed class PolicyEngineEvaluationServiceTests
var factory = new TestHttpClientFactory(handler);
var options = CreateOptions(baseAddress: null);
using var cache = new PolicyEvaluationCache(options.PolicyEngine, NullLogger<PolicyEvaluationCache>.Instance);
using StellaOps.TestKit;
var inline = new InlinePolicyEvaluationService(NullLogger<InlinePolicyEvaluationService>.Instance);
var service = new PolicyEngineEvaluationService(factory, inline, cache, Microsoft.Extensions.Options.Options.Create(options), NullLogger<PolicyEngineEvaluationService>.Instance);

View File

@@ -89,10 +89,12 @@ public sealed class OpenApiSchemaTests
// Arrange
var request = new DecisionRequest
{
Decision = "accept_risk",
Rationale = "Test rationale",
JustificationCode = null,
Metadata = null
DecisionStatus = "accept_risk",
ReasonCode = "vulnerable_code_not_in_execute_path",
ReasonText = "Test reason text",
EvidenceHashes = null,
PolicyContext = null,
RulesVersion = null
};
// Act
@@ -100,12 +102,12 @@ public sealed class OpenApiSchemaTests
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// Assert - decision and rationale are required per OpenAPI spec
root.TryGetProperty("decision", out var decision).Should().BeTrue();
decision.GetString().Should().NotBeNullOrEmpty();
// Assert - decision_status and reason_code are required per OpenAPI spec
root.TryGetProperty("decision_status", out var decisionStatus).Should().BeTrue();
decisionStatus.GetString().Should().NotBeNullOrEmpty();
root.TryGetProperty("rationale", out var rationale).Should().BeTrue();
rationale.GetString().Should().NotBeNullOrEmpty();
root.TryGetProperty("reason_code", out var reasonCode).Should().BeTrue();
reasonCode.GetString().Should().NotBeNullOrEmpty();
}
[Fact(DisplayName = "BundleVerificationResponse includes all fields")]
@@ -168,8 +170,8 @@ public sealed class OpenApiSchemaTests
{
var request = new DecisionRequest
{
Decision = decision,
Rationale = "Test rationale"
DecisionStatus = decision,
ReasonCode = "vulnerable_code_not_in_execute_path"
};
var json = JsonSerializer.Serialize(request, JsonOptions);

View File

@@ -12,14 +12,7 @@
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="8.0.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,3 +1,5 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using StellaOps.Findings.Ledger.Domain;
@@ -32,7 +34,6 @@ public sealed class HarnessRunner
var hashesValid = true;
DateTimeOffset? earliest = null;
DateTimeOffset? latest = null;
var latencies = new List<double>();
var leafHashes = new List<string>();
string? expectedMerkleRoot = null;
var latencies = new ConcurrentBag<double>();
@@ -139,8 +140,7 @@ public sealed class HarnessRunner
{
await using var stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string? line;
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested && (line = await reader.ReadLineAsync()) is not null)
while (!cancellationToken.IsCancellationRequested && await reader.ReadLineAsync() is { } line)
{
yield return line;
}

View File

@@ -9,6 +9,6 @@
<ProjectReference Include="..\\..\\StellaOps.Findings.Ledger\\StellaOps.Findings.Ledger.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>

View File

@@ -1,22 +1,47 @@
using System.CommandLine;
using LedgerReplayHarness;
var fixtureOption = new Option<string[]>("--fixture", "NDJSON fixture path(s)") { IsRequired = true, AllowMultipleArgumentsPerToken = true };
var tenantOption = new Option<string>("--tenant", () => "default", "Tenant identifier");
var reportOption = new Option<string>("--report", () => "harness-report.json", "Path to write JSON report");
var parallelOption = new Option<int>("--maxParallel", () => 4, "Maximum parallelism when sending events");
var fixtureOption = new Option<string[]>("--fixture")
{
Description = "NDJSON fixture path(s)",
Required = true,
AllowMultipleArgumentsPerToken = true
};
var tenantOption = new Option<string>("--tenant")
{
Description = "Tenant identifier",
DefaultValueFactory = _ => "default"
};
var reportOption = new Option<string>("--report")
{
Description = "Path to write JSON report",
DefaultValueFactory = _ => "harness-report.json"
};
var parallelOption = new Option<int>("--maxParallel")
{
Description = "Maximum parallelism when sending events",
DefaultValueFactory = _ => 4
};
var root = new RootCommand("Findings Ledger replay & determinism harness");
root.AddOption(fixtureOption);
root.AddOption(tenantOption);
root.AddOption(reportOption);
root.AddOption(parallelOption);
root.Add(fixtureOption);
root.Add(tenantOption);
root.Add(reportOption);
root.Add(parallelOption);
root.SetHandler(async (fixtures, tenant, report, maxParallel) =>
root.SetAction(async (parseResult, ct) =>
{
var runner = new HarnessRunner(new InMemoryLedgerClient(), maxParallel);
var exitCode = await runner.RunAsync(fixtures, tenant, report, CancellationToken.None);
Environment.Exit(exitCode);
}, fixtureOption, tenantOption, reportOption, parallelOption);
var fixtures = parseResult.GetValue(fixtureOption)!;
var tenant = parseResult.GetValue(tenantOption)!;
var report = parseResult.GetValue(reportOption)!;
var maxParallel = parseResult.GetValue(parallelOption);
return await root.InvokeAsync(args);
var runner = new HarnessRunner(new InMemoryLedgerClient(), maxParallel);
var exitCode = await runner.RunAsync(fixtures, tenant, report, ct);
return exitCode;
});
return await root.Parse(args).InvokeAsync();