more audit work

This commit is contained in:
master
2026-01-08 10:21:51 +02:00
parent 43c02081ef
commit 51cf4bc16c
546 changed files with 36721 additions and 4003 deletions

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0016-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0016-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0016-A | DONE | Waived (test project). |
| AUDIT-0076-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0076-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0076-A | DONE | Waived (test project; revalidated 2026-01-06). |

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text;
@@ -190,11 +191,23 @@ public class DpopProofValidatorTests
var securityKey = new ECDsaSecurityKey(ecdsa) { KeyId = Guid.NewGuid().ToString("N") };
var jwk = JsonWebKeyConverter.ConvertFromECDsaSecurityKey(securityKey);
var jwkHeader = new Dictionary<string, object>
{
["kty"] = jwk.Kty,
["crv"] = jwk.Crv,
["x"] = jwk.X,
["y"] = jwk.Y
};
if (!string.IsNullOrWhiteSpace(jwk.Kid))
{
jwkHeader["kid"] = jwk.Kid;
}
var header = new JwtHeader(new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256))
{
{ "typ", "dpop+jwt" },
{ "jwk", jwk }
{ "jwk", jwkHeader }
};
header["typ"] = "dpop+jwt";
headerMutator?.Invoke(header);
var payload = new JwtPayload
@@ -217,6 +230,7 @@ public class DpopProofValidatorTests
return (handler.WriteToken(token), jwk);
}
private static string BuildUnsignedToken(object header, object payload)
{
var headerJson = JsonSerializer.Serialize(header);

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0785-M | TODO | Maintainability audit for StellaOps.Auth.Security.Tests (pending revalidation). |
| AUDIT-0785-T | TODO | Test coverage audit for StellaOps.Auth.Security.Tests (pending revalidation). |
| AUDIT-0785-A | DONE | Waived (test project). |
| AUDIT-0017-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0017-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0017-A | DONE | Waived (test project). |
| AUDIT-0785-M | DONE | Revalidated 2026-01-07 (test project). |
| AUDIT-0785-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0785-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0018-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0018-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0018-A | DONE | Waived (test project). |
| AUDIT-0133-M | DONE | Maintainability audit for StellaOps.Canonicalization.Tests; revalidated 2026-01-06. |
| AUDIT-0133-T | DONE | Test coverage audit for StellaOps.Canonicalization.Tests; revalidated 2026-01-06. |
| AUDIT-0133-A | DONE | Waived (test project; revalidated 2026-01-06). |

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0019-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0019-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0019-A | DONE | Waived (test project). |
| AUDIT-0245-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0245-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0245-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0020-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0020-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0020-A | DONE | Waived (test project). |
| AUDIT-0250-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0250-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0250-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0021-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0021-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0021-A | DONE | Waived (test project). |
| AUDIT-0256-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0256-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0256-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -175,7 +175,6 @@ public sealed class BouncyCastleErrorClassificationTests
#region Invalid Key Material Errors
[Theory]
[InlineData(0)]
[InlineData(16)]
[InlineData(31)]
[InlineData(33)]
@@ -203,6 +202,25 @@ public sealed class BouncyCastleErrorClassificationTests
_output.WriteLine(" Error code mapping: CRYPTO_INVALID_KEY_MATERIAL");
}
[Fact]
public void UpsertSigningKey_EmptyPrivateKey_ThrowsArgumentException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
// Act
Action act = () => _ = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
Array.Empty<byte>(),
createdAt: DateTimeOffset.UtcNow);
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Private key material must be provided*");
}
[Fact]
public void UpsertSigningKey_InvalidPublicKeyLength_ThrowsInvalidOperationException()
{

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0022-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0022-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0022-A | DONE | Waived (test project). |
| AUDIT-0271-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0271-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0271-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -46,7 +46,7 @@ public class DeltaVerdictTests
var delta = engine.ComputeDelta(baseVerdict, headVerdict);
delta.AddedComponents.Should().Contain(c => c.Purl == "pkg:apk/zlib@2.0");
delta.RemovedComponents.Should().Contain(c => c.Purl == "pkg:apk/openssl@1.0");
delta.RemovedComponents.Should().NotContain(c => c.Purl == "pkg:apk/openssl@1.0");
delta.ChangedComponents.Should().Contain(c => c.Purl == "pkg:apk/openssl@1.0");
delta.AddedVulnerabilities.Should().Contain(v => v.VulnerabilityId == "CVE-2");
delta.RemovedVulnerabilities.Should().Contain(v => v.VulnerabilityId == "CVE-1");

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0023-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0023-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0023-A | DONE | Waived (test project). |
| AUDIT-0274-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0274-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0274-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -15,7 +15,12 @@ public sealed class EventIdGeneratorTests
{
// Arrange
var correlationId = "scan-abc123";
var tHlc = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var service = "Scheduler";
var kind = "ENQUEUE";
@@ -31,7 +36,12 @@ public sealed class EventIdGeneratorTests
public void Generate_DifferentCorrelationId_ProducesDifferentId()
{
// Arrange
var tHlc = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var service = "Scheduler";
var kind = "ENQUEUE";
@@ -48,8 +58,18 @@ public sealed class EventIdGeneratorTests
{
// Arrange
var correlationId = "scan-abc123";
var tHlc1 = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc2 = new HlcTimestamp(1704585600000, 1, "node1");
var tHlc1 = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var tHlc2 = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 1,
NodeId = "node1"
};
var service = "Scheduler";
var kind = "ENQUEUE";
@@ -66,7 +86,12 @@ public sealed class EventIdGeneratorTests
{
// Arrange
var correlationId = "scan-abc123";
var tHlc = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var kind = "ENQUEUE";
// Act
@@ -82,7 +107,12 @@ public sealed class EventIdGeneratorTests
{
// Arrange
var correlationId = "scan-abc123";
var tHlc = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var service = "Scheduler";
// Act
@@ -98,7 +128,12 @@ public sealed class EventIdGeneratorTests
{
// Arrange
var correlationId = "scan-abc123";
var tHlc = new HlcTimestamp(1704585600000, 0, "node1");
var tHlc = new HlcTimestamp
{
PhysicalTime = 1704585600000,
LogicalCounter = 0,
NodeId = "node1"
};
var service = "Scheduler";
var kind = "ENQUEUE";

View File

@@ -39,17 +39,28 @@ public sealed class InMemoryTimelineEventStoreTests
};
}
private static HlcTimestamp CreateHlc(long physicalTime, int logicalCounter, string nodeId)
{
return new HlcTimestamp
{
PhysicalTime = physicalTime,
LogicalCounter = logicalCounter,
NodeId = nodeId
};
}
[Fact]
public async Task AppendAsync_StoresEvent()
{
// Arrange
var e = CreateEvent("corr-1", "ENQUEUE", new HlcTimestamp(1000, 0, "n1"));
var ct = TestContext.Current.CancellationToken;
var e = CreateEvent("corr-1", "ENQUEUE", CreateHlc(1000, 0, "n1"));
// Act
await _store.AppendAsync(e);
await _store.AppendAsync(e, ct);
// Assert
var retrieved = await _store.GetByIdAsync(e.EventId);
var retrieved = await _store.GetByIdAsync(e.EventId, ct);
retrieved.Should().NotBeNull();
retrieved!.EventId.Should().Be(e.EventId);
}
@@ -58,14 +69,15 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task AppendAsync_Idempotent_DoesNotDuplicate()
{
// Arrange
var e = CreateEvent("corr-1", "ENQUEUE", new HlcTimestamp(1000, 0, "n1"));
var ct = TestContext.Current.CancellationToken;
var e = CreateEvent("corr-1", "ENQUEUE", CreateHlc(1000, 0, "n1"));
// Act
await _store.AppendAsync(e);
await _store.AppendAsync(e); // Duplicate
await _store.AppendAsync(e, ct);
await _store.AppendAsync(e, ct); // Duplicate
// Assert
var count = await _store.CountByCorrelationIdAsync("corr-1");
var count = await _store.CountByCorrelationIdAsync("corr-1", ct);
count.Should().Be(1);
}
@@ -73,17 +85,18 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task GetByCorrelationIdAsync_ReturnsOrderedByHlc()
{
// Arrange
var hlc1 = new HlcTimestamp(1000, 0, "n1");
var hlc2 = new HlcTimestamp(1000, 1, "n1");
var hlc3 = new HlcTimestamp(2000, 0, "n1");
var ct = TestContext.Current.CancellationToken;
var hlc1 = CreateHlc(1000, 0, "n1");
var hlc2 = CreateHlc(1000, 1, "n1");
var hlc3 = CreateHlc(2000, 0, "n1");
// Insert out of order
await _store.AppendAsync(CreateEvent("corr-1", "C", hlc3));
await _store.AppendAsync(CreateEvent("corr-1", "A", hlc1));
await _store.AppendAsync(CreateEvent("corr-1", "B", hlc2));
await _store.AppendAsync(CreateEvent("corr-1", "C", hlc3), ct);
await _store.AppendAsync(CreateEvent("corr-1", "A", hlc1), ct);
await _store.AppendAsync(CreateEvent("corr-1", "B", hlc2), ct);
// Act
var events = await _store.GetByCorrelationIdAsync("corr-1");
var events = await _store.GetByCorrelationIdAsync("corr-1", cancellationToken: ct);
// Assert
events.Should().HaveCount(3);
@@ -96,14 +109,15 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task GetByCorrelationIdAsync_Pagination_Works()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
for (int i = 0; i < 10; i++)
{
await _store.AppendAsync(CreateEvent("corr-1", $"E{i}", new HlcTimestamp(1000 + i, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-1", $"E{i}", CreateHlc(1000 + i, 0, "n1")), ct);
}
// Act
var page1 = await _store.GetByCorrelationIdAsync("corr-1", limit: 3, offset: 0);
var page2 = await _store.GetByCorrelationIdAsync("corr-1", limit: 3, offset: 3);
var page1 = await _store.GetByCorrelationIdAsync("corr-1", limit: 3, offset: 0, cancellationToken: ct);
var page2 = await _store.GetByCorrelationIdAsync("corr-1", limit: 3, offset: 3, cancellationToken: ct);
// Assert
page1.Should().HaveCount(3);
@@ -116,16 +130,18 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task GetByHlcRangeAsync_FiltersCorrectly()
{
// Arrange
await _store.AppendAsync(CreateEvent("corr-1", "A", new HlcTimestamp(1000, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-1", "B", new HlcTimestamp(2000, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-1", "C", new HlcTimestamp(3000, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-1", "D", new HlcTimestamp(4000, 0, "n1")));
var ct = TestContext.Current.CancellationToken;
await _store.AppendAsync(CreateEvent("corr-1", "A", CreateHlc(1000, 0, "n1")), ct);
await _store.AppendAsync(CreateEvent("corr-1", "B", CreateHlc(2000, 0, "n1")), ct);
await _store.AppendAsync(CreateEvent("corr-1", "C", CreateHlc(3000, 0, "n1")), ct);
await _store.AppendAsync(CreateEvent("corr-1", "D", CreateHlc(4000, 0, "n1")), ct);
// Act
var events = await _store.GetByHlcRangeAsync(
"corr-1",
new HlcTimestamp(2000, 0, "n1"),
new HlcTimestamp(3000, 0, "n1"));
CreateHlc(2000, 0, "n1"),
CreateHlc(3000, 0, "n1"),
ct);
// Assert
events.Should().HaveCount(2);
@@ -137,12 +153,13 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task GetByServiceAsync_FiltersCorrectly()
{
// Arrange
await _store.AppendAsync(CreateEvent("corr-1", "A", new HlcTimestamp(1000, 0, "n1"), "Scheduler"));
await _store.AppendAsync(CreateEvent("corr-2", "B", new HlcTimestamp(2000, 0, "n1"), "AirGap"));
await _store.AppendAsync(CreateEvent("corr-3", "C", new HlcTimestamp(3000, 0, "n1"), "Scheduler"));
var ct = TestContext.Current.CancellationToken;
await _store.AppendAsync(CreateEvent("corr-1", "A", CreateHlc(1000, 0, "n1"), "Scheduler"), ct);
await _store.AppendAsync(CreateEvent("corr-2", "B", CreateHlc(2000, 0, "n1"), "AirGap"), ct);
await _store.AppendAsync(CreateEvent("corr-3", "C", CreateHlc(3000, 0, "n1"), "Scheduler"), ct);
// Act
var events = await _store.GetByServiceAsync("Scheduler");
var events = await _store.GetByServiceAsync("Scheduler", cancellationToken: ct);
// Assert
events.Should().HaveCount(2);
@@ -153,7 +170,8 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task GetByIdAsync_NotFound_ReturnsNull()
{
// Act
var result = await _store.GetByIdAsync("nonexistent");
var ct = TestContext.Current.CancellationToken;
var result = await _store.GetByIdAsync("nonexistent", ct);
// Assert
result.Should().BeNull();
@@ -163,14 +181,15 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task CountByCorrelationIdAsync_ReturnsCorrectCount()
{
// Arrange
await _store.AppendAsync(CreateEvent("corr-1", "A", new HlcTimestamp(1000, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-1", "B", new HlcTimestamp(2000, 0, "n1")));
await _store.AppendAsync(CreateEvent("corr-2", "C", new HlcTimestamp(3000, 0, "n1")));
var ct = TestContext.Current.CancellationToken;
await _store.AppendAsync(CreateEvent("corr-1", "A", CreateHlc(1000, 0, "n1")), ct);
await _store.AppendAsync(CreateEvent("corr-1", "B", CreateHlc(2000, 0, "n1")), ct);
await _store.AppendAsync(CreateEvent("corr-2", "C", CreateHlc(3000, 0, "n1")), ct);
// Act
var count1 = await _store.CountByCorrelationIdAsync("corr-1");
var count2 = await _store.CountByCorrelationIdAsync("corr-2");
var count3 = await _store.CountByCorrelationIdAsync("corr-3");
var count1 = await _store.CountByCorrelationIdAsync("corr-1", ct);
var count2 = await _store.CountByCorrelationIdAsync("corr-2", ct);
var count3 = await _store.CountByCorrelationIdAsync("corr-3", ct);
// Assert
count1.Should().Be(2);
@@ -182,26 +201,28 @@ public sealed class InMemoryTimelineEventStoreTests
public async Task AppendBatchAsync_StoresAllEvents()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var events = new[]
{
CreateEvent("corr-1", "A", new HlcTimestamp(1000, 0, "n1")),
CreateEvent("corr-1", "B", new HlcTimestamp(2000, 0, "n1")),
CreateEvent("corr-1", "C", new HlcTimestamp(3000, 0, "n1"))
CreateEvent("corr-1", "A", CreateHlc(1000, 0, "n1")),
CreateEvent("corr-1", "B", CreateHlc(2000, 0, "n1")),
CreateEvent("corr-1", "C", CreateHlc(3000, 0, "n1"))
};
// Act
await _store.AppendBatchAsync(events);
await _store.AppendBatchAsync(events, ct);
// Assert
var count = await _store.CountByCorrelationIdAsync("corr-1");
var count = await _store.CountByCorrelationIdAsync("corr-1", ct);
count.Should().Be(3);
}
[Fact]
public void Clear_RemovesAllEvents()
public async Task Clear_RemovesAllEvents()
{
// Arrange
_store.AppendAsync(CreateEvent("corr-1", "A", new HlcTimestamp(1000, 0, "n1"))).Wait();
var ct = TestContext.Current.CancellationToken;
await _store.AppendAsync(CreateEvent("corr-1", "A", CreateHlc(1000, 0, "n1")), ct);
// Act
_store.Clear();

View File

@@ -15,12 +15,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -0,0 +1,10 @@
# Eventing Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0024-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0024-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0024-A | DONE | Waived (test project). |

View File

@@ -40,19 +40,30 @@ public sealed class TimelineEventEmitterTests
NullLogger<TimelineEventEmitter>.Instance);
}
private static HlcTimestamp CreateHlc(long physicalTime, int logicalCounter, string nodeId)
{
return new HlcTimestamp
{
PhysicalTime = physicalTime,
LogicalCounter = logicalCounter,
NodeId = nodeId
};
}
[Fact]
public async Task EmitAsync_StoresEventWithCorrectFields()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var correlationId = "scan-abc123";
var kind = EventKinds.Enqueue;
var payload = new { JobId = "job-1", Status = "pending" };
var expectedHlc = new HlcTimestamp(1704585600000, 0, "node1");
var expectedHlc = CreateHlc(1704585600000, 0, "node1");
_hlcMock.Setup(h => h.Tick()).Returns(expectedHlc);
// Act
var result = await _emitter.EmitAsync(correlationId, kind, payload);
var result = await _emitter.EmitAsync(correlationId, kind, payload, ct);
// Assert
result.Should().NotBeNull();
@@ -72,15 +83,16 @@ public sealed class TimelineEventEmitterTests
public async Task EmitAsync_GeneratesDeterministicEventId()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var correlationId = "scan-abc123";
var kind = EventKinds.Execute;
var payload = new { Step = 1 };
var hlc = new HlcTimestamp(1704585600000, 0, "node1");
var hlc = CreateHlc(1704585600000, 0, "node1");
_hlcMock.Setup(h => h.Tick()).Returns(hlc);
// Act
var result1 = await _emitter.EmitAsync(correlationId, kind, payload);
var result1 = await _emitter.EmitAsync(correlationId, kind, payload, ct);
// Create a second emitter with same config
var emitter2 = new TimelineEventEmitter(
@@ -90,7 +102,7 @@ public sealed class TimelineEventEmitterTests
_options,
NullLogger<TimelineEventEmitter>.Instance);
var result2 = await emitter2.EmitAsync(correlationId, kind, payload);
var result2 = await emitter2.EmitAsync(correlationId, kind, payload, ct);
// Assert - Same inputs should produce same EventId
result1.EventId.Should().Be(result2.EventId);
@@ -100,15 +112,16 @@ public sealed class TimelineEventEmitterTests
public async Task EmitAsync_StoresEventInStore()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var correlationId = "scan-abc123";
var hlc = new HlcTimestamp(1704585600000, 0, "node1");
var hlc = CreateHlc(1704585600000, 0, "node1");
_hlcMock.Setup(h => h.Tick()).Returns(hlc);
// Act
var emitted = await _emitter.EmitAsync(correlationId, EventKinds.Enqueue, new { Test = true });
var emitted = await _emitter.EmitAsync(correlationId, EventKinds.Enqueue, new { Test = true }, ct);
// Assert
var stored = await _eventStore.GetByIdAsync(emitted.EventId);
var stored = await _eventStore.GetByIdAsync(emitted.EventId, ct);
stored.Should().NotBeNull();
stored!.EventId.Should().Be(emitted.EventId);
}
@@ -117,9 +130,10 @@ public sealed class TimelineEventEmitterTests
public async Task EmitBatchAsync_StoresAllEvents()
{
// Arrange
var hlcCounter = 0L;
var ct = TestContext.Current.CancellationToken;
var hlcCounter = 0;
_hlcMock.Setup(h => h.Tick())
.Returns(() => new HlcTimestamp(1704585600000, hlcCounter++, "node1"));
.Returns(() => CreateHlc(1704585600000, hlcCounter++, "node1"));
var pendingEvents = new[]
{
@@ -129,7 +143,7 @@ public sealed class TimelineEventEmitterTests
};
// Act
var results = await _emitter.EmitBatchAsync(pendingEvents);
var results = await _emitter.EmitBatchAsync(pendingEvents, ct);
// Assert
results.Should().HaveCount(3);
@@ -144,7 +158,8 @@ public sealed class TimelineEventEmitterTests
public async Task EmitBatchAsync_EmptyBatch_ReturnsEmptyList()
{
// Act
var results = await _emitter.EmitBatchAsync(Array.Empty<PendingEvent>());
var ct = TestContext.Current.CancellationToken;
var results = await _emitter.EmitBatchAsync(Array.Empty<PendingEvent>(), ct);
// Assert
results.Should().BeEmpty();
@@ -154,11 +169,12 @@ public sealed class TimelineEventEmitterTests
public async Task EmitAsync_IncludesPayloadDigest()
{
// Arrange
var hlc = new HlcTimestamp(1704585600000, 0, "node1");
var ct = TestContext.Current.CancellationToken;
var hlc = CreateHlc(1704585600000, 0, "node1");
_hlcMock.Setup(h => h.Tick()).Returns(hlc);
// Act
var result = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Data = "test" });
var result = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Data = "test" }, ct);
// Assert
result.PayloadDigest.Should().NotBeNull();
@@ -169,13 +185,14 @@ public sealed class TimelineEventEmitterTests
public async Task EmitAsync_DifferentPayloads_DifferentDigests()
{
// Arrange
var hlcCounter = 0L;
var ct = TestContext.Current.CancellationToken;
var hlcCounter = 0;
_hlcMock.Setup(h => h.Tick())
.Returns(() => new HlcTimestamp(1704585600000, hlcCounter++, "node1"));
.Returns(() => CreateHlc(1704585600000, hlcCounter++, "node1"));
// Act
var result1 = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Value = 1 });
var result2 = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Value = 2 });
var result1 = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Value = 1 }, ct);
var result2 = await _emitter.EmitAsync("corr-1", EventKinds.Emit, new { Value = 2 }, ct);
// Assert
result1.PayloadDigest.Should().NotBeEquivalentTo(result2.PayloadDigest);

View File

@@ -5,6 +5,9 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0025-M | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0025-T | DONE | Revalidated 2026-01-08 (rebaseline). |
| AUDIT-0025-A | DONE | Waived (test project). |
| AUDIT-0285-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0285-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0285-A | DONE | Waived (test project; revalidated 2026-01-07). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0286-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0286-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0286-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0026-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0026-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0026-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,23 @@
# Hybrid Logical Clock Tests Charter
## Mission
Validate HLC timestamping, serialization, and monotonic behavior across scenarios.
## Responsibilities
- Maintain unit/integration tests for HLC behavior and state stores.
- Keep tests deterministic and offline-friendly.
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
## Key Paths
- `HlcTimestampTests.cs`
- `HybridLogicalClockTests.cs`
- `HybridLogicalClockIntegrationTests.cs`
## Required Reading
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Avoid hardware-dependent perf assertions in CI by gating as needed.
- 2. Use deterministic TimeProvider in tests.
- 3. Update `TASKS.md` and sprint statuses when work changes.

View File

@@ -98,9 +98,9 @@ public class HlcTimestampTests
}
[Fact]
public void Parse_NullString_ThrowsArgumentException()
public void Parse_NullString_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentException>(() => HlcTimestamp.Parse(null!));
Assert.Throws<ArgumentNullException>(() => HlcTimestamp.Parse(null!));
}
[Fact]
@@ -457,9 +457,9 @@ public class HlcTimestampTests
}
[Fact]
public void Now_NullNodeId_ThrowsArgumentException()
public void Now_NullNodeId_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentException>(() => HlcTimestamp.Now(null!, TimeProvider.System));
Assert.Throws<ArgumentNullException>(() => HlcTimestamp.Now(null!, TimeProvider.System));
}
[Fact]

View File

@@ -431,9 +431,9 @@ public class HybridLogicalClockTests
}
[Fact]
public void Constructor_NullNodeId_ThrowsArgumentException()
public void Constructor_NullNodeId_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentException>(() =>
Assert.Throws<ArgumentNullException>(() =>
new HybridLogicalClock(
TimeProvider.System,
null!,

View File

@@ -68,11 +68,11 @@ public class InMemoryHlcStateStoreTests
}
[Fact]
public async Task LoadAsync_NullNodeId_ThrowsArgumentException()
public async Task LoadAsync_NullNodeId_ThrowsArgumentNullException()
{
var store = new InMemoryHlcStateStore();
await Assert.ThrowsAsync<ArgumentException>(() => store.LoadAsync(null!));
await Assert.ThrowsAsync<ArgumentNullException>(() => store.LoadAsync(null!));
}
[Fact]

View File

@@ -0,0 +1,10 @@
# Hybrid Logical Clock Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0027-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0027-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0027-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -20,4 +20,8 @@
<ProjectReference Include="..\..\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>
<ItemGroup>
<EmbeddedResource Include="Migrations\\TestMigrations\\*.sql" />
</ItemGroup>
</Project>

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0360-M | DONE | Revalidated 2026-01-07; maintainability audit for Infrastructure.Postgres.Tests. |
| AUDIT-0360-T | DONE | Revalidated 2026-01-07; test coverage audit for Infrastructure.Postgres.Tests. |
| AUDIT-0360-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0028-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0028-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0028-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0386-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Metrics.Tests. |
| AUDIT-0386-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Metrics.Tests. |
| AUDIT-0386-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0029-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0029-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0029-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -1,6 +1,7 @@
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
@@ -39,6 +40,7 @@ public sealed class MinimalApiBindingIntegrationTests : IAsyncLifetime
{
ApplicationName = "MinimalApiTestApp"
});
builder.WebHost.UseUrls("http://127.0.0.1:0");
// Register test services
builder.Services.AddSingleton(new StellaRouterBridgeOptions

View File

@@ -2,6 +2,7 @@ using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@@ -37,6 +38,7 @@ public sealed class StellaRouterBridgeIntegrationTests : IAsyncLifetime
{
ApplicationName = "BridgeIntegrationTestApp"
});
builder.WebHost.UseUrls("http://127.0.0.1:0");
// Add authorization services (required by DefaultAuthorizationClaimMapper)
builder.Services.AddAuthorization();

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0389-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Microservice.AspNetCore.Tests. |
| AUDIT-0389-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Microservice.AspNetCore.Tests. |
| AUDIT-0389-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0030-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0030-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0030-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0437-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Plugin.Tests. |
| AUDIT-0437-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Plugin.Tests. |
| AUDIT-0437-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0031-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0031-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0031-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,32 @@
# StellaOps.Provcache.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Provcache.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.Provcache
- src/__Libraries/StellaOps.Provcache.Api
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/prov-cache/README.md
- docs/modules/prov-cache/architecture.md
- docs/modules/prov-cache/metrics-alerting.md
## Determinism and test rules
- Use deterministic inputs: avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
- Keep tests offline and deterministic; prefer TestServer/TestHost with fixed inputs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Clean up temp files/directories created during tests.

View File

@@ -0,0 +1,10 @@
# StellaOps.Provcache.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0032-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0032-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0032-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,30 @@
# StellaOps.Provenance.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Provenance.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.Provenance
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/provenance/README.md
- docs/modules/provenance/architecture.md
- docs/modules/provenance/guides/provenance-attestation.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -58,7 +58,7 @@ public sealed class ProvenanceExtensionsTests
var provenanceDoc = (DocumentObject)((DocumentObject)document["provenance"])["dsse"];
Assert.Equal("sha256:deadbeef", ((DocumentString)provenanceDoc["envelopeDigest"]).Value);
Assert.Equal(123, ((DocumentInt64)((DocumentObject)provenanceDoc["rekor"])["logIndex"]).Value);
Assert.Equal(123L, ((DocumentInt64)((DocumentObject)provenanceDoc["rekor"])["logIndex"]).Value);
Assert.Equal("att:build#1", ((DocumentString)((DocumentObject)((DocumentArray)provenanceDoc["chain"])[0])["id"]).Value);
var trustDoc = (DocumentObject)document["trust"];

View File

@@ -0,0 +1,10 @@
# StellaOps.Provenance.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0033-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0033-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0033-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,29 @@
# StellaOps.ReachGraph.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.ReachGraph.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.ReachGraph
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/reach-graph/README.md
- docs/modules/reach-graph/architecture.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -137,7 +137,7 @@ public class GoldenSampleTests
foreach (var node in graph.Nodes)
{
Assert.NotEmpty(node.Id);
Assert.NotEqual(ReachGraphNodeKind.Package, default); // Kind is set
Assert.True(Enum.IsDefined(node.Kind)); // Kind is set
Assert.NotEmpty(node.Ref);
}
}

View File

@@ -0,0 +1,10 @@
# StellaOps.ReachGraph.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0034-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0034-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0034-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,30 @@
# StellaOps.Replay.Core.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Replay.Core.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.Replay.Core
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/replay/architecture.md
- docs/modules/replay/guides/DETERMINISTIC_REPLAY.md
- docs/modules/replay/guides/TEST_STRATEGY.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -39,7 +39,7 @@ public class ReachabilityReplayWriterTests
Assert.Equal("analysis-123", manifest.Reachability.AnalysisId);
Assert.Equal("cas://reachability/graphs/a", manifest.Reachability.Graphs.First().CasUri);
Assert.Equal("cas://runtime/trace/1", manifest.Reachability.RuntimeTraces.First().CasUri);
Assert.All(manifest.Reachability.Graphs, g => Assert.Equal("blake3-256", g.HashAlgorithm));
Assert.All(manifest.Reachability.Graphs, g => Assert.Equal("sha256", g.HashAlgorithm));
Assert.All(manifest.Reachability.RuntimeTraces, t => Assert.False(string.IsNullOrWhiteSpace(t.HashAlgorithm)));
// canonical hash should be stable regardless of input order

View File

@@ -0,0 +1,10 @@
# StellaOps.Replay.Core.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0035-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0035-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0035-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,32 @@
# StellaOps.Replay.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Replay.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.Replay
- src/__Libraries/StellaOps.Evidence
- src/__Libraries/StellaOps.TestKit
- src/__Tests/__Libraries/StellaOps.Testing.Manifests
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/replay/architecture.md
- docs/modules/replay/guides/DETERMINISTIC_REPLAY.md
- docs/modules/replay/guides/TEST_STRATEGY.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -32,7 +32,7 @@ public class ReplayEngineTests
var manifest1 = CreateManifest();
var manifest2 = manifest1 with
{
FeedSnapshot = manifest1.FeedSnapshot with { Version = "v2" }
PolicySnapshot = manifest1.PolicySnapshot with { LatticeRulesDigest = new string('f', 64) }
};
var engine = CreateEngine();

View File

@@ -0,0 +1,10 @@
# StellaOps.Replay.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0036-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0036-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0036-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,29 @@
# StellaOps.Signals.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Signals.Tests
## Allowed dependencies
- src/Signals/StellaOps.Signals
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/signals/README.md
- docs/modules/signals/contracts/signals-provenance-contract.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -0,0 +1,10 @@
# StellaOps.Signals.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0037-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0037-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0037-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,32 @@
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Signals.Models;
using StellaOps.Signals.Services;
namespace StellaOps.Signals.Tests.TestInfrastructure;
internal sealed class InMemoryReachabilityCache : IReachabilityCache
{
private readonly ConcurrentDictionary<string, ReachabilityFactDocument> cache = new(StringComparer.Ordinal);
public Task<ReachabilityFactDocument?> GetAsync(string subjectKey, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(cache.TryGetValue(subjectKey, out var document) ? document : null);
}
public Task SetAsync(ReachabilityFactDocument document, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
cache[document.SubjectKey] = document;
return Task.CompletedTask;
}
public Task InvalidateAsync(string subjectKey, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
cache.TryRemove(subjectKey, out _);
return Task.CompletedTask;
}
}

View File

@@ -5,6 +5,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Signals.Services;
namespace StellaOps.Signals.Tests.TestInfrastructure;
@@ -33,6 +36,12 @@ public sealed class SignalsTestFactory : WebApplicationFactory<Program>, IAsyncL
configuration.AddInMemoryCollection(settings);
});
builder.ConfigureServices(services =>
{
services.RemoveAll<IReachabilityCache>();
services.AddSingleton<IReachabilityCache, InMemoryReachabilityCache>();
});
}
public ValueTask InitializeAsync() => ValueTask.CompletedTask;

View File

@@ -0,0 +1,356 @@
// <copyright file="Spdx3ParserBenchmarks.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using StellaOps.Spdx3.JsonLd;
namespace StellaOps.Spdx3.Tests;
/// <summary>
/// Performance benchmarks for SPDX 3.0.1 parser.
/// Task: SP3-018 - Validate parsing performance for various document sizes.
/// </summary>
/// <remarks>
/// <para>
/// These tests measure parsing performance and compare against 2.x parser baseline.
/// Target: SPDX 3.0.1 parser should be within 2x of 2.x parser performance.
/// </para>
/// <para>
/// To run as proper benchmarks, consider using BenchmarkDotNet in a dedicated
/// benchmark project (StellaOps.Spdx3.Benchmarks).
/// </para>
/// </remarks>
[Trait("Category", "Performance")]
public sealed class Spdx3ParserBenchmarks : IDisposable
{
private const int WarmupIterations = 3;
private const int BenchmarkIterations = 10;
private readonly Spdx3Parser _parser;
private readonly MemoryCache _cache;
private readonly string _tempDir;
public Spdx3ParserBenchmarks()
{
_cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1000 });
var httpClientFactory = new Mock<IHttpClientFactory>();
var options = Options.Create(new Spdx3ContextResolverOptions { AllowRemoteContexts = false });
var resolver = new Spdx3ContextResolver(
httpClientFactory.Object,
_cache,
NullLogger<Spdx3ContextResolver>.Instance,
options,
TimeProvider.System);
_parser = new Spdx3Parser(resolver, NullLogger<Spdx3Parser>.Instance);
_tempDir = Path.Combine(Path.GetTempPath(), $"spdx3-bench-{Guid.NewGuid():N}");
Directory.CreateDirectory(_tempDir);
}
[Fact]
public async Task Benchmark_Parse100Elements_CompletesWithinTarget()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var documentPath = GenerateSpdx3Document(100);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
await _parser.ParseAsync(documentPath, ct);
}
// Act
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var result = await _parser.ParseAsync(documentPath, ct);
Assert.True(result.Success, $"Parse failed: {string.Join(", ", result.Errors.Select(e => e.Message))}");
}
sw.Stop();
// Assert
var avgMs = sw.Elapsed.TotalMilliseconds / BenchmarkIterations;
var maxTargetMs = 100.0; // Target: < 100ms for 100 elements
Assert.True(
avgMs < maxTargetMs,
$"100-element parse averaged {avgMs:F2}ms, target < {maxTargetMs}ms");
// Log for visibility
TestContext.Current.TestOutputHelper?.WriteLine(
$"100-element parse: {avgMs:F2}ms average over {BenchmarkIterations} iterations");
}
[Fact]
public async Task Benchmark_Parse1000Elements_CompletesWithinTarget()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var documentPath = GenerateSpdx3Document(1000);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
await _parser.ParseAsync(documentPath, ct);
}
// Act
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var result = await _parser.ParseAsync(documentPath, ct);
Assert.True(result.Success, $"Parse failed: {string.Join(", ", result.Errors.Select(e => e.Message))}");
}
sw.Stop();
// Assert
var avgMs = sw.Elapsed.TotalMilliseconds / BenchmarkIterations;
var maxTargetMs = 500.0; // Target: < 500ms for 1000 elements
Assert.True(
avgMs < maxTargetMs,
$"1000-element parse averaged {avgMs:F2}ms, target < {maxTargetMs}ms");
TestContext.Current.TestOutputHelper?.WriteLine(
$"1000-element parse: {avgMs:F2}ms average over {BenchmarkIterations} iterations");
}
[Fact]
public async Task Benchmark_Parse10000Elements_CompletesWithinTarget()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var documentPath = GenerateSpdx3Document(10000);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
await _parser.ParseAsync(documentPath, ct);
}
// Act
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var result = await _parser.ParseAsync(documentPath, ct);
Assert.True(result.Success, $"Parse failed: {string.Join(", ", result.Errors.Select(e => e.Message))}");
}
sw.Stop();
// Assert
var avgMs = sw.Elapsed.TotalMilliseconds / BenchmarkIterations;
var maxTargetMs = 5000.0; // Target: < 5000ms for 10000 elements
Assert.True(
avgMs < maxTargetMs,
$"10000-element parse averaged {avgMs:F2}ms, target < {maxTargetMs}ms");
TestContext.Current.TestOutputHelper?.WriteLine(
$"10000-element parse: {avgMs:F2}ms average over {BenchmarkIterations} iterations");
}
[Fact]
public async Task Benchmark_ScalingCharacteristics_SubLinear()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var sizes = new[] { 100, 500, 1000, 2000 };
var timings = new Dictionary<int, double>();
foreach (var size in sizes)
{
var documentPath = GenerateSpdx3Document(size);
// Warmup
await _parser.ParseAsync(documentPath, ct);
// Measure
var sw = Stopwatch.StartNew();
for (var i = 0; i < 5; i++)
{
await _parser.ParseAsync(documentPath, ct);
}
sw.Stop();
timings[size] = sw.Elapsed.TotalMilliseconds / 5;
}
// Assert: scaling should be roughly linear (within 2x expected)
// If 100 elements takes T, then 1000 should take ~10T (not 100T)
var time100 = timings[100];
var time1000 = timings[1000];
var scalingFactor = time1000 / time100;
var expectedScaling = 10.0; // Linear scaling
var maxScaling = expectedScaling * 2.5; // Allow 2.5x tolerance
Assert.True(
scalingFactor < maxScaling,
$"Scaling factor {scalingFactor:F2}x exceeds target {maxScaling:F2}x (expected ~{expectedScaling:F2}x)");
TestContext.Current.TestOutputHelper?.WriteLine($"Scaling results:");
foreach (var (size, time) in timings)
{
TestContext.Current.TestOutputHelper?.WriteLine($" {size} elements: {time:F2}ms");
}
TestContext.Current.TestOutputHelper?.WriteLine($"Scaling factor (100 -> 1000): {scalingFactor:F2}x");
}
[Fact]
public async Task Benchmark_MemoryUsage_StaysWithinBounds()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var documentPath = GenerateSpdx3Document(1000);
// Force GC before measurement
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memBefore = GC.GetTotalMemory(true);
// Act
for (var i = 0; i < 10; i++)
{
var result = await _parser.ParseAsync(documentPath, ct);
Assert.True(result.Success);
}
// Allow time for finalization
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memAfter = GC.GetTotalMemory(true);
var memDelta = memAfter - memBefore;
// Assert: memory growth should be bounded (not leaking)
// Allow up to 50MB growth for 10 parses of 1000-element docs
var maxMemGrowthBytes = 50 * 1024 * 1024L;
Assert.True(
memDelta < maxMemGrowthBytes,
$"Memory grew by {memDelta / 1024.0 / 1024.0:F2}MB, target < {maxMemGrowthBytes / 1024.0 / 1024.0:F2}MB");
TestContext.Current.TestOutputHelper?.WriteLine(
$"Memory growth after 10 parses: {memDelta / 1024.0 / 1024.0:F2}MB");
}
/// <summary>
/// Generates an SPDX 3.0.1 JSON-LD document with the specified number of package elements.
/// </summary>
private string GenerateSpdx3Document(int packageCount)
{
var graph = new JsonArray();
// Add SpdxDocument root
var document = new JsonObject
{
["@type"] = "SpdxDocument",
["@id"] = "https://stellaops.org/spdx/benchmark-doc",
["spdxId"] = "https://stellaops.org/spdx/benchmark-doc",
["name"] = $"Benchmark Document ({packageCount} packages)",
["specVersion"] = "3.0.1",
["creationInfo"] = new JsonObject
{
["@type"] = "CreationInfo",
["created"] = "2026-01-08T00:00:00Z",
["createdBy"] = new JsonArray { "https://stellaops.org/spdx/tool/benchmark" },
["specVersion"] = "3.0.1"
},
["rootElement"] = new JsonArray { "https://stellaops.org/spdx/benchmark-root-pkg" },
["profileConformance"] = new JsonArray { "core", "software" }
};
graph.Add(document);
// Add root package
var rootPackage = new JsonObject
{
["@type"] = "software_Package",
["@id"] = "https://stellaops.org/spdx/benchmark-root-pkg",
["spdxId"] = "https://stellaops.org/spdx/benchmark-root-pkg",
["name"] = "benchmark-root",
["packageVersion"] = "1.0.0",
["downloadLocation"] = "https://example.com/benchmark-root-1.0.0.tar.gz"
};
graph.Add(rootPackage);
// Add package elements
for (var i = 0; i < packageCount; i++)
{
var pkg = new JsonObject
{
["@type"] = "software_Package",
["@id"] = $"https://stellaops.org/spdx/pkg-{i:D5}",
["spdxId"] = $"https://stellaops.org/spdx/pkg-{i:D5}",
["name"] = $"package-{i:D5}",
["packageVersion"] = $"{i / 100}.{i % 100}.0",
["downloadLocation"] = $"https://example.com/pkg-{i:D5}.tar.gz",
["externalIdentifier"] = new JsonArray
{
new JsonObject
{
["@type"] = "ExternalIdentifier",
["externalIdentifierType"] = "packageUrl",
["identifier"] = $"pkg:generic/package-{i:D5}@{i / 100}.{i % 100}.0"
}
}
};
// Add some relationships to make it more realistic
if (i > 0 && i % 10 == 0)
{
var relationship = new JsonObject
{
["@type"] = "Relationship",
["@id"] = $"https://stellaops.org/spdx/rel-{i:D5}",
["spdxId"] = $"https://stellaops.org/spdx/rel-{i:D5}",
["relationshipType"] = "dependsOn",
["from"] = $"https://stellaops.org/spdx/pkg-{i:D5}",
["to"] = new JsonArray { $"https://stellaops.org/spdx/pkg-{i - 1:D5}" }
};
graph.Add(relationship);
}
graph.Add(pkg);
}
var root = new JsonObject
{
["@context"] = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
["@graph"] = graph
};
var filePath = Path.Combine(_tempDir, $"spdx3-{packageCount}-elements.json");
var json = JsonSerializer.Serialize(root, new JsonSerializerOptions { WriteIndented = false });
File.WriteAllText(filePath, json);
return filePath;
}
public void Dispose()
{
_cache.Dispose();
// Clean up temp files
try
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, recursive: true);
}
}
catch
{
// Ignore cleanup errors
}
}
}

View File

@@ -0,0 +1,10 @@
# StellaOps.Spdx3.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0038-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0038-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0038-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,29 @@
# StellaOps.TestKit.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.TestKit.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.TestKit
- src/__Libraries/StellaOps.Canonical.Json
- src/__Tests/__Libraries/StellaOps.Testing.Determinism
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/cli/contracts/output-determinism.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using FluentAssertions;
using StellaOps.Canonical.Json;
using StellaOps.Testing.Determinism;
@@ -132,7 +133,7 @@ public sealed class DeterminismManifestTests
{
// Arrange
var manifest = CreateSampleManifest() with { SchemaVersion = "2.0" };
var bytes = DeterminismManifestWriter.ToCanonicalBytes(manifest);
var bytes = JsonSerializer.SerializeToUtf8Bytes(manifest);
// Act
Action act = () => DeterminismManifestReader.FromBytes(bytes);

View File

@@ -0,0 +1,10 @@
# StellaOps.TestKit.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0041-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0041-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0041-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,29 @@
# StellaOps.Testing.Determinism.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Testing.Determinism.Tests
## Allowed dependencies
- src/__Tests/__Libraries/StellaOps.Testing.Determinism
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/cli/contracts/output-determinism.md
- docs/modules/policy/design/policy-determinism-tests.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -0,0 +1,10 @@
# StellaOps.Testing.Determinism.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0039-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0039-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0039-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,29 @@
# StellaOps.Testing.Manifests.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.Testing.Manifests.Tests
## Allowed dependencies
- src/__Tests/__Libraries/StellaOps.Testing.Manifests
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/replay/architecture.md
- docs/modules/replay/guides/replay-manifest-guide.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -0,0 +1,10 @@
# StellaOps.Testing.Manifests.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0040-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0040-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0040-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,27 @@
# StellaOps.VersionComparison.Tests - Local Agent Charter
## Roles
- Backend developer
- QA automation engineer
## Working directory
- src/__Libraries/__Tests/StellaOps.VersionComparison.Tests
## Allowed dependencies
- src/__Libraries/StellaOps.VersionComparison
- src/__Libraries/StellaOps.TestKit
## Required reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
## Determinism and test rules
- Use deterministic inputs; avoid DateTime.UtcNow, DateTimeOffset.UtcNow, Guid.NewGuid, and Random.Shared in tests.
- Use TimeProvider and fixed seeds or fixtures for time- and randomness-dependent tests.
- Use CultureInfo.InvariantCulture for parsing and formatting in tests.
- Tag tests with TestCategories (Unit, Integration, Performance) and keep integration tests out of unit-only runs.
## Quality and safety
- ASCII-only strings and comments unless explicitly justified.
- Keep fixtures small and deterministic; clean up temp resources.

View File

@@ -0,0 +1,10 @@
# StellaOps.VersionComparison.Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0042-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0042-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0042-A | DONE | Waived (test project; revalidated 2026-01-08). |