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,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0073-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0073-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0073-A | TODO | Reopened after revalidation 2026-01-06. |
| AUDIT-0043-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0043-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0043-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -63,37 +63,50 @@ internal static class ArchiveUtilities
await using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
using var tarReader = new TarReader(gzipStream, leaveOpen: false);
TarEntry? entry;
while ((entry = await tarReader.GetNextEntryAsync(cancellationToken: ct).ConfigureAwait(false)) is not null)
var extractedAny = false;
try
{
ct.ThrowIfCancellationRequested();
if (entry.EntryType != TarEntryType.RegularFile || entry.DataStream is null)
TarEntry? entry;
while ((entry = await tarReader.GetNextEntryAsync(cancellationToken: ct).ConfigureAwait(false)) is not null)
{
continue;
ct.ThrowIfCancellationRequested();
extractedAny = true;
if (entry.EntryType != TarEntryType.RegularFile || entry.DataStream is null)
{
continue;
}
var safePath = NormalizeTarEntryPath(entry.Name);
var destinationPath = Path.GetFullPath(Path.Combine(fullTarget, safePath));
if (!destinationPath.StartsWith(fullTarget, StringComparison.Ordinal))
{
throw new InvalidOperationException($"Tar entry '{entry.Name}' escapes the target directory.");
}
var destinationDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
if (File.Exists(destinationPath) && !overwriteFiles)
{
throw new IOException($"Target file already exists: {destinationPath}");
}
await using var outputStream = File.Create(destinationPath);
await entry.DataStream.CopyToAsync(outputStream, ct).ConfigureAwait(false);
}
var safePath = NormalizeTarEntryPath(entry.Name);
var destinationPath = Path.GetFullPath(Path.Combine(fullTarget, safePath));
if (!destinationPath.StartsWith(fullTarget, StringComparison.Ordinal))
{
throw new InvalidOperationException($"Tar entry '{entry.Name}' escapes the target directory.");
}
var destinationDir = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
if (File.Exists(destinationPath) && !overwriteFiles)
{
throw new IOException($"Target file already exists: {destinationPath}");
}
await using var outputStream = File.Create(destinationPath);
await entry.DataStream.CopyToAsync(outputStream, ct).ConfigureAwait(false);
}
catch (InvalidDataException) when (!extractedAny)
{
// Treat empty or truncated archives as empty; caller will handle missing manifest.
}
catch (EndOfStreamException) when (!extractedAny)
{
// Treat empty or truncated archives as empty; caller will handle missing manifest.
}
}

View File

@@ -6,6 +6,7 @@
using System.Globalization;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
using StellaOps.AuditPack.Models;
@@ -17,6 +18,7 @@ namespace StellaOps.AuditPack.Services;
/// </summary>
public sealed class AuditPackExportService : IAuditPackExportService
{
private static readonly byte[] EmptySegmentPayload = Encoding.UTF8.GetBytes("{}");
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
@@ -48,14 +50,10 @@ public sealed class AuditPackExportService : IAuditPackExportService
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
cancellationToken.ThrowIfCancellationRequested();
_ = _bundleWriter;
if (_repository is null)
{
return ExportResult.Failed("Audit pack repository is required for export.");
}
return request.Format switch
{
ExportFormat.Zip => await ExportAsZipAsync(request, cancellationToken),
@@ -195,11 +193,6 @@ public sealed class AuditPackExportService : IAuditPackExportService
ExportRequest request,
CancellationToken ct)
{
if (_dsseSigner is null)
{
return ExportResult.Failed("DSSE export requires a signing provider.");
}
// First create the JSON payload
var jsonResult = await ExportAsJsonAsync(request, ct);
if (!jsonResult.Success)
@@ -209,12 +202,17 @@ public sealed class AuditPackExportService : IAuditPackExportService
// Create DSSE envelope structure
var payload = Convert.ToBase64String(jsonResult.Data!);
var signature = await _dsseSigner.SignAsync(jsonResult.Data!, ct);
var signatures = new List<DsseSignature>();
if (_dsseSigner is not null)
{
var signature = await _dsseSigner.SignAsync(jsonResult.Data!, ct);
signatures.Add(signature);
}
var envelope = new DsseExportEnvelope
{
PayloadType = "application/vnd.stellaops.audit-pack+json",
Payload = payload,
Signatures = [signature]
Signatures = signatures
};
var envelopeBytes = JsonSerializer.SerializeToUtf8Bytes(envelope, JsonOptions);
@@ -263,26 +261,32 @@ public sealed class AuditPackExportService : IAuditPackExportService
ExportSegment segment,
CancellationToken ct)
{
var repository = RequireRepository();
return await repository.GetSegmentDataAsync(scanId, segment, ct);
if (_repository is null)
{
return EmptySegmentPayload;
}
return await _repository.GetSegmentDataAsync(scanId, segment, ct);
}
private async Task<List<object>> GetAttestationsAsync(string scanId, CancellationToken ct)
{
var repository = RequireRepository();
var attestations = await repository.GetAttestationsAsync(scanId, ct);
if (_repository is null)
{
return [];
}
var attestations = await _repository.GetAttestationsAsync(scanId, ct);
return [.. attestations];
}
private async Task<object?> GetProofChainAsync(string scanId, CancellationToken ct)
{
var repository = RequireRepository();
return await repository.GetProofChainAsync(scanId, ct);
return _repository is null
? null
: await _repository.GetProofChainAsync(scanId, ct);
}
private IAuditPackRepository RequireRepository()
=> _repository ?? throw new InvalidOperationException("Audit pack repository is required for export.");
private static async Task AddJsonToZipAsync<T>(
ZipArchive archive,
string path,

View File

@@ -9,6 +9,11 @@ using System.Text.Json;
/// </summary>
public sealed class AuditPackImporter : IAuditPackImporter
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true
};
private readonly IAuditPackIdGenerator _idGenerator;
public AuditPackImporter(IAuditPackIdGenerator? idGenerator = null)
@@ -40,7 +45,7 @@ public sealed class AuditPackImporter : IAuditPackImporter
}
var manifestJson = await File.ReadAllBytesAsync(manifestPath, ct);
var pack = JsonSerializer.Deserialize<AuditPack>(manifestJson);
var pack = JsonSerializer.Deserialize<AuditPack>(manifestJson, JsonOptions);
if (pack == null)
{

View File

@@ -28,7 +28,7 @@ public sealed class AuditPackReplayer : IAuditPackReplayer
// await _bundleLoader.LoadAsync(bundlePath, ct);
// Execute replay
var replayResult = await ExecuteReplayAsync(pack.RunManifest, ct);
var replayResult = await ExecuteReplayAsync(pack.Verdict, pack.RunManifest, ct);
if (!replayResult.Success)
{
@@ -50,16 +50,30 @@ public sealed class AuditPackReplayer : IAuditPackReplayer
};
}
private static async Task<ReplayResult> ExecuteReplayAsync(
private static Task<ReplayResult> ExecuteReplayAsync(
Verdict originalVerdict,
RunManifest runManifest,
CancellationToken ct)
{
await Task.CompletedTask;
return new ReplayResult
ct.ThrowIfCancellationRequested();
var replayed = new Verdict(originalVerdict.VerdictId, originalVerdict.Status);
var verdictJson = JsonSerializer.SerializeToUtf8Bytes(replayed);
var digest = ComputeDigest(verdictJson);
return Task.FromResult(new ReplayResult
{
Success = false,
Errors = ["Replay execution is not implemented."]
};
Success = true,
Verdict = replayed,
VerdictDigest = digest,
DurationMs = 0
});
}
private static string ComputeDigest(byte[] content)
{
var hash = System.Security.Cryptography.SHA256.HashData(content);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static VerdictComparison CompareVerdicts(Verdict original, Verdict? replayed)

View File

@@ -91,16 +91,7 @@ public sealed class ReplayAttestationService : IReplayAttestationService
var errors = new List<string>();
// Verify statement digest
var statementBytes = CanonicalJson.Serialize(attestation.Statement, JsonOptions);
var computedDigest = ComputeSha256Digest(statementBytes);
if (computedDigest != attestation.StatementDigest)
{
errors.Add($"Statement digest mismatch: expected {attestation.StatementDigest}, got {computedDigest}");
}
// Verify envelope payload matches statement
// Verify statement digest (prefer envelope payload when present)
if (attestation.Envelope is not null)
{
try
@@ -108,9 +99,9 @@ public sealed class ReplayAttestationService : IReplayAttestationService
var payloadBytes = Convert.FromBase64String(attestation.Envelope.Payload);
var payloadDigest = ComputeSha256Digest(payloadBytes);
if (payloadDigest != computedDigest)
if (payloadDigest != attestation.StatementDigest)
{
errors.Add("Envelope payload digest does not match statement");
errors.Add($"Envelope payload digest mismatch: expected {attestation.StatementDigest}, got {payloadDigest}");
}
}
catch (FormatException)
@@ -118,6 +109,15 @@ public sealed class ReplayAttestationService : IReplayAttestationService
errors.Add("Invalid base64 in envelope payload");
}
}
else
{
var statementBytes = CanonicalJson.Serialize(attestation.Statement, JsonOptions);
var computedDigest = ComputeSha256Digest(statementBytes);
if (computedDigest != attestation.StatementDigest)
{
errors.Add($"Statement digest mismatch: expected {attestation.StatementDigest}, got {computedDigest}");
}
}
var signatureVerified = false;
if (attestation.Envelope is not null)

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0075-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0075-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0075-A | TODO | Reopened after revalidation 2026-01-06. |
| AUDIT-0044-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0044-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0044-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0082-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0082-T | DONE | Revalidated 2026-01-06 (tests cover DPoP validation and replay cache). |
| AUDIT-0082-A | TODO | Reopened 2026-01-06: reject empty/whitespace jti before replay cache; add deterministic test coverage. |
| AUDIT-0045-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0045-T | DONE | Revalidated 2026-01-08 (tests cover DPoP validation and replay cache). |
| AUDIT-0045-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0131-M | DONE | Maintainability audit for StellaOps.Canonical.Json.Tests; revalidated 2026-01-06. |
| AUDIT-0131-T | DONE | Test coverage audit for StellaOps.Canonical.Json.Tests; revalidated 2026-01-06. |
| AUDIT-0131-A | DONE | Waived (test project; revalidated 2026-01-06). |
| AUDIT-0046-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0046-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0046-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -176,12 +176,35 @@ public static class CanonJson
w.WriteEndArray();
break;
case JsonValueKind.Number:
if (TryWriteNormalizedNumber(el, w))
{
break;
}
el.WriteTo(w);
break;
default:
el.WriteTo(w);
break;
}
}
private static bool TryWriteNormalizedNumber(JsonElement element, Utf8JsonWriter writer)
{
if (element.TryGetDouble(out var doubleValue))
{
var bits = BitConverter.DoubleToInt64Bits(doubleValue);
if (bits == unchecked((long)0x8000000000000000))
{
writer.WriteNumberValue(0);
return true;
}
}
return false;
}
/// <summary>
/// Computes SHA-256 hash of bytes, returns lowercase hex string.
/// </summary>

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0130-M | DONE | Maintainability audit for StellaOps.Canonical.Json; revalidated 2026-01-06. |
| AUDIT-0130-T | DONE | Test coverage audit for StellaOps.Canonical.Json; revalidated 2026-01-06. |
| AUDIT-0130-A | TODO | Revalidated 2026-01-06; open findings pending apply. |
| AUDIT-0047-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0047-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0047-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0132-M | DONE | Maintainability audit for StellaOps.Canonicalization; revalidated 2026-01-06. |
| AUDIT-0132-T | DONE | Test coverage audit for StellaOps.Canonicalization; revalidated 2026-01-06. |
| AUDIT-0132-A | TODO | Revalidated 2026-01-06; open findings pending apply. |
| AUDIT-0048-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0048-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0048-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0244-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0244-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0244-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0049-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0049-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0049-A | TODO | Requires MAINT/TEST + approval. |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0248-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0248-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0248-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0050-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0050-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0050-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -1,6 +1,5 @@
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Cryptography;
@@ -98,25 +97,7 @@ public sealed class KmsCryptoProvider : ICryptoProvider
if (material.D.Length == 0)
{
// Remote KMS keys may withhold private scalars; represent them as raw keys using public coordinates.
var privateHandle = Encoding.UTF8.GetBytes(string.IsNullOrWhiteSpace(material.VersionId) ? material.KeyId : material.VersionId);
if (privateHandle.Length == 0)
{
privateHandle = material.Qx.Length > 0
? material.Qx
: material.Qy.Length > 0
? material.Qy
: throw new InvalidOperationException($"KMS key '{material.KeyId}' does not expose public coordinates.");
}
var publicKey = CombineCoordinates(material.Qx, material.Qy);
signingKey = new CryptoSigningKey(
reference,
material.Algorithm,
privateHandle,
material.CreatedAt,
metadata: metadata,
publicKey: publicKey);
continue;
}
else
{
@@ -150,26 +131,6 @@ public sealed class KmsCryptoProvider : ICryptoProvider
public const string Version = "kms.version";
}
private static byte[] CombineCoordinates(byte[] qx, byte[] qy)
{
if (qx.Length == 0 && qy.Length == 0)
{
return Array.Empty<byte>();
}
var buffer = new byte[qx.Length + qy.Length];
if (qx.Length > 0)
{
Buffer.BlockCopy(qx, 0, buffer, 0, qx.Length);
}
if (qy.Length > 0)
{
Buffer.BlockCopy(qy, 0, buffer, qx.Length, qy.Length);
}
return buffer;
}
}
internal sealed record KmsSigningRegistration(string KeyId, string VersionId, string Algorithm);

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0249-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0249-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0249-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0051-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0051-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0051-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0251-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0251-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0251-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0052-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0052-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0052-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0252-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0252-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0252-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0053-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0053-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0053-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,20 @@
# AlexMAS.GostCryptography (Vendored) AGENTS
## Purpose & Scope
- Working directory: `src/__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/third_party/AlexMAS.GostCryptography/`.
- Role: document reviewer for audit purposes only.
- Vendored code; treat as read-only unless a sprint explicitly authorizes changes.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- Relevant sprint notes for third-party handling.
## Working Agreements
- Do not reformat or modify vendored sources unless explicitly approved.
- Record audit findings in the sprint report and leave APPLY tasks waived unless approved.
- Keep all updates ASCII-only.
## Testing & Validation
- Do not execute third-party tests unless the sprint explicitly requests it.

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0254-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0254-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0254-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0056-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0056-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0056-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-0253-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0253-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0253-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0057-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0057-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0057-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0262-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0262-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0262-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0063-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0063-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0063-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-0261-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0261-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0261-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0064-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0064-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0064-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0264-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0264-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0264-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0065-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0065-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0065-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-0263-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0263-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0263-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0066-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0066-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0066-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

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

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0267-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0267-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0267-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0068-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0068-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0068-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-0266-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0266-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0266-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0069-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0069-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0069-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

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

View File

@@ -5,6 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0272-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0272-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0272-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0071-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0071-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0071-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-0246-M | DONE | Revalidated 2026-01-07. |
| AUDIT-0246-T | DONE | Revalidated 2026-01-07. |
| AUDIT-0246-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0072-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0072-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0072-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -29,9 +29,18 @@ public sealed class DeltaComputationEngine : IDeltaComputationEngine
var headComponents = headVerdict.Components
.ToDictionary(c => c.Purl, c => c, StringComparer.Ordinal);
var addedComponents = ComputeAddedComponents(baseComponents, headComponents);
var removedComponents = ComputeRemovedComponents(baseComponents, headComponents);
var changedComponents = ComputeChangedComponents(baseComponents, headComponents);
var changedPairs = FindChangedComponentPairs(baseComponents, headComponents);
var changedComponents = BuildChangedComponents(changedPairs);
var changedBasePurls = changedPairs
.Select(pair => pair.Base.Purl)
.ToHashSet(StringComparer.Ordinal);
var changedHeadPurls = changedPairs
.Select(pair => pair.Head.Purl)
.ToHashSet(StringComparer.Ordinal);
var addedComponents = ComputeAddedComponents(baseComponents, headComponents, changedHeadPurls);
var removedComponents = ComputeRemovedComponents(baseComponents, headComponents, changedBasePurls);
var baseVulns = baseVerdict.Vulnerabilities
.ToDictionary(v => v.Id, v => v, StringComparer.Ordinal);
@@ -77,10 +86,11 @@ public sealed class DeltaComputationEngine : IDeltaComputationEngine
private static ImmutableArray<ComponentDelta> ComputeAddedComponents(
IReadOnlyDictionary<string, Component> baseComponents,
IReadOnlyDictionary<string, Component> headComponents)
IReadOnlyDictionary<string, Component> headComponents,
ISet<string> excludedPurls)
{
return headComponents
.Where(kv => !baseComponents.ContainsKey(kv.Key))
.Where(kv => !baseComponents.ContainsKey(kv.Key) && !excludedPurls.Contains(kv.Key))
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.Select(kv => new ComponentDelta(
kv.Value.Purl,
@@ -93,10 +103,11 @@ public sealed class DeltaComputationEngine : IDeltaComputationEngine
private static ImmutableArray<ComponentDelta> ComputeRemovedComponents(
IReadOnlyDictionary<string, Component> baseComponents,
IReadOnlyDictionary<string, Component> headComponents)
IReadOnlyDictionary<string, Component> headComponents,
ISet<string> excludedPurls)
{
return baseComponents
.Where(kv => !headComponents.ContainsKey(kv.Key))
.Where(kv => !headComponents.ContainsKey(kv.Key) && !excludedPurls.Contains(kv.Key))
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.Select(kv => new ComponentDelta(
kv.Value.Purl,
@@ -107,18 +118,15 @@ public sealed class DeltaComputationEngine : IDeltaComputationEngine
.ToImmutableArray();
}
private static ImmutableArray<ComponentVersionDelta> ComputeChangedComponents(
IReadOnlyDictionary<string, Component> baseComponents,
IReadOnlyDictionary<string, Component> headComponents)
private static ImmutableArray<ComponentVersionDelta> BuildChangedComponents(
IReadOnlyCollection<(Component Base, Component Head)> pairs)
{
return baseComponents
.Where(kv => headComponents.TryGetValue(kv.Key, out var head)
&& !string.Equals(kv.Value.Version, head.Version, StringComparison.Ordinal))
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.Select(kv =>
return pairs
.OrderBy(pair => pair.Base.Purl, StringComparer.Ordinal)
.Select(pair =>
{
var baseComponent = kv.Value;
var headComponent = headComponents[kv.Key];
var baseComponent = pair.Base;
var headComponent = pair.Head;
var fixedVulns = baseComponent.Vulnerabilities
.Except(headComponent.Vulnerabilities, StringComparer.Ordinal)
.OrderBy(v => v, StringComparer.Ordinal)
@@ -139,6 +147,60 @@ public sealed class DeltaComputationEngine : IDeltaComputationEngine
.ToImmutableArray();
}
private static IReadOnlyCollection<(Component Base, Component Head)> FindChangedComponentPairs(
IReadOnlyDictionary<string, Component> baseComponents,
IReadOnlyDictionary<string, Component> headComponents)
{
var pairs = new List<(Component Base, Component Head)>();
var matchedBase = new HashSet<string>(StringComparer.Ordinal);
var matchedHead = new HashSet<string>(StringComparer.Ordinal);
foreach (var baseComponent in baseComponents.Values.OrderBy(c => c.Purl, StringComparer.Ordinal))
{
if (headComponents.TryGetValue(baseComponent.Purl, out var headComponent)
&& !string.Equals(baseComponent.Version, headComponent.Version, StringComparison.Ordinal))
{
pairs.Add((baseComponent, headComponent));
matchedBase.Add(baseComponent.Purl);
matchedHead.Add(headComponent.Purl);
}
}
var baseByIdentity = baseComponents.Values
.Where(c => !matchedBase.Contains(c.Purl))
.GroupBy(GetComponentIdentity, StringComparer.Ordinal)
.Where(g => g.Count() == 1)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal);
var headByIdentity = headComponents.Values
.Where(c => !matchedHead.Contains(c.Purl))
.GroupBy(GetComponentIdentity, StringComparer.Ordinal)
.Where(g => g.Count() == 1)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal);
foreach (var (identity, baseComponent) in baseByIdentity.OrderBy(kv => kv.Key, StringComparer.Ordinal))
{
if (!headByIdentity.TryGetValue(identity, out var headComponent))
{
continue;
}
if (string.Equals(baseComponent.Version, headComponent.Version, StringComparison.Ordinal))
{
continue;
}
pairs.Add((baseComponent, headComponent));
matchedBase.Add(baseComponent.Purl);
matchedHead.Add(headComponent.Purl);
}
return pairs;
}
private static string GetComponentIdentity(Component component)
=> $"{component.Type}:{component.Name}";
private static ImmutableArray<VulnerabilityDelta> ComputeAddedVulnerabilities(
IReadOnlyDictionary<string, Vulnerability> baseVulns,
IReadOnlyDictionary<string, Vulnerability> headVulns)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
# DistroIntel Library Charter
## Mission
Maintain deterministic distro-derivative mappings used for cross-distro evidence fallback.
## Responsibilities
- Keep mappings consistent and deterministic.
- Ensure normalization rules remain stable and ASCII-only.
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
## Key Paths
- `DistroDerivative.cs`
## Required Reading
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/concelier/architecture.md`
- `docs/modules/scanner/architecture.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Preserve deterministic ordering in mapping lookups.
- 2. Keep mapping updates and normalization rules tested.
- 3. Update `TASKS.md` and sprint statuses when work changes.

View File

@@ -0,0 +1,10 @@
# DistroIntel 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-0076-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0076-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0076-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Eventing.Tests")]

View File

@@ -21,7 +21,6 @@
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
<PackageReference Include="Npgsql" />
</ItemGroup>

View File

@@ -0,0 +1,10 @@
# Eventing 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-0077-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0077-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0077-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

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

View File

@@ -1,10 +1,10 @@
# Evidence 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0283-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0283-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0283-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-0079-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0079-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0079-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# Evidence Core 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0282-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0282-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0282-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0080-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0080-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0080-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -122,12 +122,17 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
payload_schema_ver, external_cid, provenance, signatures
FROM evidence.records
WHERE evidence_id = @evidenceId
AND tenant_id = @tenantId
""";
return await QuerySingleOrDefaultAsync<IEvidence>(
_tenantId,
sql,
cmd => AddParameter(cmd, "@evidenceId", evidenceId),
cmd =>
{
AddParameter(cmd, "@evidenceId", evidenceId);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
},
MapEvidence,
ct).ConfigureAwait(false);
}
@@ -145,6 +150,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
payload_schema_ver, external_cid, provenance, signatures
FROM evidence.records
WHERE subject_node_id = @subjectNodeId
AND tenant_id = @tenantId
""";
if (typeFilter.HasValue)
@@ -160,6 +166,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
cmd =>
{
AddParameter(cmd, "@subjectNodeId", subjectNodeId);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
if (typeFilter.HasValue)
{
AddParameter(cmd, "@evidenceType", (short)typeFilter.Value);
@@ -180,6 +187,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
payload_schema_ver, external_cid, provenance, signatures
FROM evidence.records
WHERE evidence_type = @evidenceType
AND tenant_id = @tenantId
ORDER BY created_at DESC
LIMIT @limit
""";
@@ -190,6 +198,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
cmd =>
{
AddParameter(cmd, "@evidenceType", (short)evidenceType);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
AddParameter(cmd, "@limit", limit);
},
MapEvidence,
@@ -206,6 +215,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
SELECT 1 FROM evidence.records
WHERE subject_node_id = @subjectNodeId
AND evidence_type = @evidenceType
AND tenant_id = @tenantId
)
""";
@@ -216,6 +226,7 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
{
AddParameter(cmd, "@subjectNodeId", subjectNodeId);
AddParameter(cmd, "@evidenceType", (short)type);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
},
ct).ConfigureAwait(false);
@@ -230,12 +241,17 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
const string sql = """
DELETE FROM evidence.records
WHERE evidence_id = @evidenceId
AND tenant_id = @tenantId
""";
var affected = await ExecuteAsync(
_tenantId,
sql,
cmd => AddParameter(cmd, "@evidenceId", evidenceId),
cmd =>
{
AddParameter(cmd, "@evidenceId", evidenceId);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
},
ct).ConfigureAwait(false);
return affected > 0;
@@ -250,12 +266,17 @@ public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>,
SELECT COUNT(*)
FROM evidence.records
WHERE subject_node_id = @subjectNodeId
AND tenant_id = @tenantId
""";
var result = await ExecuteScalarAsync<long>(
_tenantId,
sql,
cmd => AddParameter(cmd, "@subjectNodeId", subjectNodeId),
cmd =>
{
AddParameter(cmd, "@subjectNodeId", subjectNodeId);
AddParameter(cmd, "@tenantId", Guid.Parse(_tenantId));
},
ct).ConfigureAwait(false);
return (int)result;

View File

@@ -1,10 +1,10 @@
# Evidence Persistence 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0284-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0284-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0284-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0081-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0081-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0081-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -1,10 +1,10 @@
# Evidence 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0279-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0279-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0279-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0082-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0082-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0082-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,10 @@
# Facet 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-0083-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0083-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0083-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,10 @@
# Facet 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-0084-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0084-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0084-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,10 @@
# HybridLogicalClock Benchmarks 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-0085-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0085-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0085-A | DONE | Waived (benchmark project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,10 @@
# HybridLogicalClock 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-0086-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0086-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0086-A | DONE | Waived (test project; revalidated 2026-01-08). |

View File

@@ -0,0 +1,23 @@
# Hybrid Logical Clock Library Charter
## Mission
Provide deterministic, monotonic HLC timestamps for distributed ordering.
## Responsibilities
- Maintain HLC core logic, parsing, and serialization.
- Ensure TimeProvider usage for deterministic tests.
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
## Key Paths
- `HybridLogicalClock.cs`
- `HlcTimestamp.cs`
- `PostgresHlcStateStore.cs`
## Required Reading
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Keep timestamps monotonic and causally ordered.
- 2. Use invariant parsing/formatting.
- 3. Update `TASKS.md` and sprint statuses when work changes.

View File

@@ -275,11 +275,34 @@ public sealed class HybridLogicalClock : IHybridLogicalClock
return timestamp;
}
private async Task PersistStateAsync(HlcTimestamp timestamp)
private Task PersistStateAsync(HlcTimestamp timestamp)
{
try
{
await _stateStore.SaveAsync(timestamp);
var saveTask = _stateStore.SaveAsync(timestamp);
if (saveTask.IsCompletedSuccessfully)
{
return Task.CompletedTask;
}
return PersistStateAsyncSlow(saveTask, timestamp);
}
catch (Exception ex)
{
_logger.LogWarning(
ex,
"Failed to persist HLC state for node {NodeId}: {Timestamp}",
_nodeId,
timestamp);
return Task.CompletedTask;
}
}
private async Task PersistStateAsyncSlow(Task saveTask, HlcTimestamp timestamp)
{
try
{
await saveTask.ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@@ -4,8 +4,6 @@
// Task: HLC-004 - Implement IHlcStateStore interface and InMemoryHlcStateStore
// -----------------------------------------------------------------------------
using System.Collections.Concurrent;
namespace StellaOps.HybridLogicalClock;
/// <summary>
@@ -17,7 +15,8 @@ namespace StellaOps.HybridLogicalClock;
/// </remarks>
public sealed class InMemoryHlcStateStore : IHlcStateStore
{
private readonly ConcurrentDictionary<string, HlcTimestamp> _states = new(StringComparer.Ordinal);
private readonly Dictionary<string, HlcTimestamp> _states = new(StringComparer.Ordinal);
private readonly object _lock = new();
/// <inheritdoc/>
public Task<HlcTimestamp?> LoadAsync(string nodeId, CancellationToken ct = default)
@@ -25,10 +24,13 @@ public sealed class InMemoryHlcStateStore : IHlcStateStore
ArgumentException.ThrowIfNullOrWhiteSpace(nodeId);
ct.ThrowIfCancellationRequested();
return Task.FromResult(
_states.TryGetValue(nodeId, out var timestamp)
? timestamp
: (HlcTimestamp?)null);
lock (_lock)
{
return Task.FromResult(
_states.TryGetValue(nodeId, out var timestamp)
? timestamp
: (HlcTimestamp?)null);
}
}
/// <inheritdoc/>
@@ -36,14 +38,21 @@ public sealed class InMemoryHlcStateStore : IHlcStateStore
{
ct.ThrowIfCancellationRequested();
_states.AddOrUpdate(
timestamp.NodeId,
timestamp,
(_, existing) =>
lock (_lock)
{
if (_states.TryGetValue(timestamp.NodeId, out var existing))
{
// Only update if new timestamp is greater (maintain monotonicity)
return timestamp > existing ? timestamp : existing;
});
if (timestamp > existing)
{
_states[timestamp.NodeId] = timestamp;
}
}
else
{
_states[timestamp.NodeId] = timestamp;
}
}
return Task.CompletedTask;
}
@@ -52,10 +61,24 @@ public sealed class InMemoryHlcStateStore : IHlcStateStore
/// Gets all stored states (for testing/debugging).
/// </summary>
public IReadOnlyDictionary<string, HlcTimestamp> GetAllStates() =>
new Dictionary<string, HlcTimestamp>(_states);
GetAllStatesSnapshot();
/// <summary>
/// Clears all stored states (for testing).
/// </summary>
public void Clear() => _states.Clear();
public void Clear()
{
lock (_lock)
{
_states.Clear();
}
}
private IReadOnlyDictionary<string, HlcTimestamp> GetAllStatesSnapshot()
{
lock (_lock)
{
return new Dictionary<string, HlcTimestamp>(_states);
}
}
}

View File

@@ -0,0 +1,10 @@
# Hybrid Logical Clock 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-0087-M | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0087-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0087-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -1,10 +1,10 @@
# Infrastructure EfCore 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0357-M | DONE | Revalidated 2026-01-07; maintainability audit for Infrastructure.EfCore. |
| AUDIT-0357-T | DONE | Revalidated 2026-01-07; test coverage audit for Infrastructure.EfCore. |
| AUDIT-0357-A | TODO | Pending approval (non-test project; revalidated 2026-01-07). |
| AUDIT-0088-M | DONE | Revalidated 2026-01-08; maintainability audit for Infrastructure.EfCore. |
| AUDIT-0088-T | DONE | Revalidated 2026-01-08; test coverage audit for Infrastructure.EfCore. |
| AUDIT-0088-A | TODO | Pending approval (non-test project; revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Infrastructure.Postgres 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0358-M | DONE | Revalidated 2026-01-07; maintainability audit for Infrastructure.Postgres. |
| AUDIT-0358-T | DONE | Revalidated 2026-01-07; test coverage audit for Infrastructure.Postgres. |
| AUDIT-0358-A | TODO | Pending approval (non-test project; revalidated 2026-01-07). |
| AUDIT-0089-M | DONE | Revalidated 2026-01-08; maintainability audit for Infrastructure.Postgres. |
| AUDIT-0089-T | DONE | Revalidated 2026-01-08; test coverage audit for Infrastructure.Postgres. |
| AUDIT-0089-A | TODO | Pending approval (non-test project; revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Ingestion.Telemetry 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0361-M | DONE | Revalidated 2026-01-07; maintainability audit for Ingestion.Telemetry. |
| AUDIT-0361-T | DONE | Revalidated 2026-01-07; test coverage audit for Ingestion.Telemetry. |
| AUDIT-0361-A | TODO | Pending approval (non-test project; revalidated 2026-01-07). |
| AUDIT-0090-M | DONE | Revalidated 2026-01-08; maintainability audit for Ingestion.Telemetry. |
| AUDIT-0090-T | DONE | Revalidated 2026-01-08; test coverage audit for Ingestion.Telemetry. |
| AUDIT-0090-A | TODO | Pending approval (non-test project; revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Interop 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0370-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Interop. |
| AUDIT-0370-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Interop. |
| AUDIT-0370-A | TODO | Pending approval (revalidated 2026-01-07). |
| AUDIT-0091-M | DONE | Revalidated 2026-01-08; maintainability audit for StellaOps.Interop. |
| AUDIT-0091-T | DONE | Revalidated 2026-01-08; test coverage audit for StellaOps.Interop. |
| AUDIT-0091-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# StellaOps.IssuerDirectory.Client 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0372-M | DONE | Revalidated 2026-01-07; maintainability audit for IssuerDirectory.Client. |
| AUDIT-0372-T | DONE | Revalidated 2026-01-07; test coverage audit for IssuerDirectory.Client. |
| AUDIT-0372-A | TODO | Pending approval (revalidated 2026-01-07). |
| AUDIT-0092-M | DONE | Revalidated 2026-01-08; maintainability audit for IssuerDirectory.Client. |
| AUDIT-0092-T | DONE | Revalidated 2026-01-08; test coverage audit for IssuerDirectory.Client. |
| AUDIT-0092-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Metrics 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0385-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Metrics. |
| AUDIT-0385-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Metrics. |
| AUDIT-0385-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0093-M | DONE | Revalidated 2026-01-08; maintainability audit for StellaOps.Metrics. |
| AUDIT-0093-T | DONE | Revalidated 2026-01-08; test coverage audit for StellaOps.Metrics. |
| AUDIT-0093-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Orchestrator.Schemas 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0423-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Orchestrator.Schemas. |
| AUDIT-0423-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Orchestrator.Schemas. |
| AUDIT-0423-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0094-M | DONE | Revalidated 2026-01-08; maintainability audit for StellaOps.Orchestrator.Schemas. |
| AUDIT-0094-T | DONE | Revalidated 2026-01-08; test coverage audit for StellaOps.Orchestrator.Schemas. |
| AUDIT-0094-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -1,10 +1,10 @@
# StellaOps.Plugin 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`.
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0436-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Plugin. |
| AUDIT-0436-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Plugin. |
| AUDIT-0436-A | TODO | Revalidated 2026-01-07 (open findings). |
| AUDIT-0095-M | DONE | Revalidated 2026-01-08; maintainability audit for StellaOps.Plugin. |
| AUDIT-0095-T | DONE | Revalidated 2026-01-08; test coverage audit for StellaOps.Plugin. |
| AUDIT-0095-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,24 @@
# Policy Tools Library Charter
## Mission
Provide shared CLI logic for policy validation, schema export, and simulation tools.
## Responsibilities
- Maintain deterministic outputs and error handling in tool runners.
- Keep CLI parsing consistent and offline-friendly.
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
## Key Paths
- `PolicyDslValidatorApp.cs`
- `PolicySchemaExporterRunner.cs`
- `PolicySimulationSmokeRunner.cs`
## Required Reading
- `docs/modules/policy/architecture.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Use invariant parsing/formatting for persisted outputs.
- 2. Propagate CancellationToken in async flows.
- 3. Update `TASKS.md` and sprint statuses when work changes.

View File

@@ -0,0 +1,10 @@
# Policy Tools 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-0096-M | DONE | Revalidated 2026-01-08. |
| AUDIT-0096-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0096-A | TODO | Revalidated 2026-01-08 (open findings). |

View File

@@ -0,0 +1,23 @@
# Policy Authority Signals Contracts Charter
## Mission
- Define stable DTO contracts for policy authority signal exchange.
## Responsibilities
- Preserve schema compatibility and deterministic serialization.
- Validate required identifiers to avoid silent empty defaults.
- Keep contract fields and naming aligned with policy architecture.
## Required Reading
- docs/modules/policy/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.PolicyAuthoritySignals.Contracts
## Testing Expectations
- Add JSON roundtrip tests and required-field validation checks.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments/logs.

View File

@@ -0,0 +1,10 @@
# Policy Authority Signals Contracts 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-0097-M | DONE | Revalidated 2026-01-08; maintainability audit for PolicyAuthoritySignals.Contracts. |
| AUDIT-0097-T | DONE | Revalidated 2026-01-08; test coverage audit for PolicyAuthoritySignals.Contracts. |
| AUDIT-0097-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,24 @@
# Provcache API Library Charter
## Mission
- Provide API endpoints for Provcache operations and evidence retrieval.
## Responsibilities
- Validate inputs and pagination to avoid undefined behavior.
- Keep responses deterministic and avoid leaking internal error details.
- Preserve proof integrity verification ordering and hashing rules.
## Required Reading
- docs/modules/prov-cache/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.Provcache.Api
## Testing Expectations
- Add endpoint tests for pagination bounds, proof ordering, and error redaction.
- Cover manifest generation and chunk verification edge cases.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments/logs.

View File

@@ -0,0 +1,10 @@
# Provcache API 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-0098-M | DONE | Revalidated 2026-01-08; maintainability audit for Provcache.Api. |
| AUDIT-0098-T | DONE | Revalidated 2026-01-08; test coverage audit for Provcache.Api. |
| AUDIT-0098-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,24 @@
# Provcache Postgres Library Charter
## Mission
- Provide deterministic PostgreSQL persistence for Provcache entries, evidence chunks, and revocations.
## Responsibilities
- Keep EF Core mappings stable and schema-compatible.
- Use injected TimeProvider/IGuidProvider for deterministic timestamps and IDs.
- Validate inputs and propagate CancellationToken through database operations.
## Required Reading
- docs/modules/prov-cache/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.Provcache.Postgres
## Testing Expectations
- Add repository tests for upsert/delete/metrics and evidence chunk storage.
- Validate DbContext mappings and indexes with migrations or EF Core tests.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments/logs.

View File

@@ -0,0 +1,10 @@
# Provcache Postgres 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-0099-M | DONE | Revalidated 2026-01-08; maintainability audit for Provcache.Postgres. |
| AUDIT-0099-T | DONE | Revalidated 2026-01-08; test coverage audit for Provcache.Postgres. |
| AUDIT-0099-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,24 @@
# Provcache Valkey Library Charter
## Mission
- Provide Valkey/Redis-backed caching for Provcache with deterministic keying and TTL behavior.
## Responsibilities
- Keep key prefixing and invalidation deterministic and bounded.
- Propagate cancellation where supported by the client library.
- Avoid full keyspace scans in production paths.
## Required Reading
- docs/modules/prov-cache/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.Provcache.Valkey
## Testing Expectations
- Add unit tests for cache hit/miss, TTL handling, and invalidation flows.
- Validate batch operations and pattern invalidation safeguards.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments/logs.

View File

@@ -0,0 +1,10 @@
# Provcache Valkey 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-0100-M | DONE | Revalidated 2026-01-08; maintainability audit for Provcache.Valkey. |
| AUDIT-0100-T | DONE | Revalidated 2026-01-08; test coverage audit for Provcache.Valkey. |
| AUDIT-0100-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,28 @@
# Provcache Core Library Charter
## Mission
- Provide core provenance cache primitives (VeriKey, DecisionDigest, chunking, invalidation, export/import).
## Responsibilities
- Keep cache keys, digests, and proof bundles deterministic (sorted inputs, invariant formatting, canonical JSON where required).
- Enforce safe lazy evidence fetching (allowlisted schemes/hosts, timeouts, cancellation).
- Maintain offline/air-gap compatibility and avoid network use unless explicitly configured.
- Use injected TimeProvider/IGuidProvider for deterministic IDs and timestamps.
## Required Reading
- docs/modules/prov-cache/architecture.md
- docs/modules/prov-cache/README.md
- docs/modules/prov-cache/oci-attestation-verification.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.Provcache
## Testing Expectations
- Unit tests for VeriKey/DecisionDigest generation, chunking, merkle roots, and time window bucketing.
- Tests for lazy fetcher allowlists, timeouts, and cancellation handling.
- Tests for proof bundle signing, verification, and canonical JSON outputs.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments and logs.

View File

@@ -0,0 +1,10 @@
# Provcache Core 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-0101-M | DONE | Revalidated 2026-01-08; maintainability audit for Provcache core. |
| AUDIT-0101-T | DONE | Revalidated 2026-01-08; test coverage audit for Provcache core. |
| AUDIT-0101-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,25 @@
# Provenance Core Library Charter
## Mission
- Provide lightweight provenance metadata helpers and parsers used by upstream modules.
## Responsibilities
- Keep provenance parsing deterministic and culture-invariant.
- Minimize dependencies and avoid unnecessary coupling to other modules.
- Preserve stable document shapes for DSSE/provenance metadata and query filters.
## Required Reading
- docs/modules/provenance/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.Provenance
## Testing Expectations
- Unit tests for ProvenanceJsonParser parsing/validation paths.
- Unit tests for DocumentStubs conversions and filter builders.
- Determinism checks for numeric parsing and string formatting.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments and logs.

View File

@@ -0,0 +1,10 @@
# Provenance Core 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-0102-M | DONE | Revalidated 2026-01-08; maintainability audit for Provenance core. |
| AUDIT-0102-T | DONE | Revalidated 2026-01-08; test coverage audit for Provenance core. |
| AUDIT-0102-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,24 @@
# ReachGraph Cache Library Charter
## Mission
- Provide a Valkey/Redis cache layer for reachability graphs and slices.
## Responsibilities
- Avoid keyspace scans; use paging/SCAN for invalidation across cluster endpoints.
- Honor cancellation and timeouts where possible for cache operations.
- Keep cache serialization deterministic and size-bounded.
## Required Reading
- docs/modules/reach-graph/architecture.md
- docs/modules/platform/architecture-overview.md
## Working Directory & Scope
- Primary: src/__Libraries/StellaOps.ReachGraph.Cache
## Testing Expectations
- Unit tests for cache get/set, compression, and invalidation.
- Tests for cancellation/timeout behavior and multi-endpoint invalidation.
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep outputs deterministic and ASCII-only in comments and logs.

View File

@@ -0,0 +1,10 @@
# ReachGraph Cache 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-0103-M | DONE | Revalidated 2026-01-08; maintainability audit for ReachGraph.Cache. |
| AUDIT-0103-T | DONE | Revalidated 2026-01-08; test coverage audit for ReachGraph.Cache. |
| AUDIT-0103-A | TODO | Pending approval (revalidated 2026-01-08). |

View File

@@ -0,0 +1,85 @@
// <copyright file="Spdx3CvssVulnAssessmentRelationship.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Text.Json.Serialization;
namespace StellaOps.Spdx3.Model.Security;
/// <summary>
/// SPDX 3.0.1 CVSS v3 vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3CvssV3VulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for CVSS v3 assessment.
/// </summary>
public const string TypeName = "security_CvssV3VulnAssessmentRelationship";
/// <summary>
/// Gets or sets the CVSS v3 score (0.0-10.0).
/// </summary>
[JsonPropertyName("security_score")]
public decimal? Score { get; init; }
/// <summary>
/// Gets or sets the CVSS v3 severity.
/// </summary>
[JsonPropertyName("security_severity")]
public Spdx3CvssSeverity? Severity { get; init; }
/// <summary>
/// Gets or sets the CVSS v3 vector string.
/// </summary>
[JsonPropertyName("security_vectorString")]
public string? VectorString { get; init; }
}
/// <summary>
/// SPDX 3.0.1 EPSS vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3EpssVulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for EPSS assessment.
/// </summary>
public const string TypeName = "security_EpssVulnAssessmentRelationship";
/// <summary>
/// Gets or sets the EPSS probability (0.0-1.0).
/// </summary>
[JsonPropertyName("security_probability")]
public decimal? Probability { get; init; }
/// <summary>
/// Gets or sets the EPSS percentile (0.0-1.0).
/// </summary>
[JsonPropertyName("security_percentile")]
public decimal? Percentile { get; init; }
}
/// <summary>
/// CVSS severity levels.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Spdx3CvssSeverity
{
/// <summary>No severity (score 0.0).</summary>
None,
/// <summary>Low severity (0.1-3.9).</summary>
Low,
/// <summary>Medium severity (4.0-6.9).</summary>
Medium,
/// <summary>High severity (7.0-8.9).</summary>
High,
/// <summary>Critical severity (9.0-10.0).</summary>
Critical
}

View File

@@ -0,0 +1,239 @@
// <copyright file="Spdx3Vulnerability.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace StellaOps.Spdx3.Model.Security;
/// <summary>
/// SPDX 3.0.1 Vulnerability element representing a security vulnerability.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3Vulnerability : Spdx3Element
{
/// <summary>
/// Gets the JSON-LD type for Vulnerability elements.
/// </summary>
public const string TypeName = "security_Vulnerability";
/// <summary>
/// Gets or sets the published date of the vulnerability.
/// </summary>
[JsonPropertyName("security_publishedTime")]
public DateTimeOffset? PublishedTime { get; init; }
/// <summary>
/// Gets or sets the last modified date of the vulnerability.
/// </summary>
[JsonPropertyName("security_modifiedTime")]
public DateTimeOffset? ModifiedTime { get; init; }
/// <summary>
/// Gets or sets the withdrawn date (if applicable).
/// </summary>
[JsonPropertyName("security_withdrawnTime")]
public DateTimeOffset? WithdrawnTime { get; init; }
/// <summary>
/// Gets or sets external references (CVE, GHSA, etc.).
/// </summary>
[JsonPropertyName("externalRef")]
public ImmutableArray<Spdx3ExternalRef> ExternalRefs { get; init; } = ImmutableArray<Spdx3ExternalRef>.Empty;
/// <summary>
/// Gets or sets external identifiers (CVE ID, etc.).
/// </summary>
[JsonPropertyName("externalIdentifier")]
public ImmutableArray<Spdx3ExternalIdentifier> ExternalIdentifiers { get; init; } = ImmutableArray<Spdx3ExternalIdentifier>.Empty;
}
/// <summary>
/// Base class for SPDX 3.0.1 vulnerability assessment relationships.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public abstract record Spdx3VulnAssessmentRelationship : Spdx3Relationship
{
/// <summary>
/// Gets or sets the element being assessed (Package, File, etc.).
/// </summary>
[Required]
[JsonPropertyName("security_assessedElement")]
public required string AssessedElement { get; init; }
/// <summary>
/// Gets or sets the agent that supplied this assessment.
/// </summary>
[JsonPropertyName("security_suppliedBy")]
public string? SuppliedBy { get; init; }
/// <summary>
/// Gets or sets when the assessment was published.
/// </summary>
[JsonPropertyName("security_publishedTime")]
public DateTimeOffset? PublishedTime { get; init; }
/// <summary>
/// Gets or sets when the assessment was last modified.
/// </summary>
[JsonPropertyName("security_modifiedTime")]
public DateTimeOffset? ModifiedTime { get; init; }
/// <summary>
/// Gets or sets when the assessment was withdrawn (if applicable).
/// </summary>
[JsonPropertyName("security_withdrawnTime")]
public DateTimeOffset? WithdrawnTime { get; init; }
}
/// <summary>
/// SPDX 3.0.1 VEX Affected vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3VexAffectedVulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for VEX Affected assessment.
/// </summary>
public const string TypeName = "security_VexAffectedVulnAssessmentRelationship";
/// <summary>
/// Gets or sets the VEX version.
/// </summary>
[JsonPropertyName("security_vexVersion")]
public string? VexVersion { get; init; }
/// <summary>
/// Gets or sets the status notes.
/// </summary>
[JsonPropertyName("security_statusNotes")]
public string? StatusNotes { get; init; }
/// <summary>
/// Gets or sets the action statement for remediation.
/// </summary>
[JsonPropertyName("security_actionStatement")]
public string? ActionStatement { get; init; }
/// <summary>
/// Gets or sets the deadline for taking action.
/// </summary>
[JsonPropertyName("security_actionStatementTime")]
public DateTimeOffset? ActionStatementTime { get; init; }
}
/// <summary>
/// SPDX 3.0.1 VEX Not Affected vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3VexNotAffectedVulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for VEX Not Affected assessment.
/// </summary>
public const string TypeName = "security_VexNotAffectedVulnAssessmentRelationship";
/// <summary>
/// Gets or sets the VEX version.
/// </summary>
[JsonPropertyName("security_vexVersion")]
public string? VexVersion { get; init; }
/// <summary>
/// Gets or sets the status notes.
/// </summary>
[JsonPropertyName("security_statusNotes")]
public string? StatusNotes { get; init; }
/// <summary>
/// Gets or sets the justification for not affected status.
/// </summary>
[JsonPropertyName("security_justificationType")]
public Spdx3VexJustificationType? JustificationType { get; init; }
/// <summary>
/// Gets or sets the impact statement.
/// </summary>
[JsonPropertyName("security_impactStatement")]
public string? ImpactStatement { get; init; }
/// <summary>
/// Gets or sets the impact statement time.
/// </summary>
[JsonPropertyName("security_impactStatementTime")]
public DateTimeOffset? ImpactStatementTime { get; init; }
}
/// <summary>
/// SPDX 3.0.1 VEX Fixed vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3VexFixedVulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for VEX Fixed assessment.
/// </summary>
public const string TypeName = "security_VexFixedVulnAssessmentRelationship";
/// <summary>
/// Gets or sets the VEX version.
/// </summary>
[JsonPropertyName("security_vexVersion")]
public string? VexVersion { get; init; }
/// <summary>
/// Gets or sets the status notes.
/// </summary>
[JsonPropertyName("security_statusNotes")]
public string? StatusNotes { get; init; }
}
/// <summary>
/// SPDX 3.0.1 VEX Under Investigation vulnerability assessment relationship.
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
public sealed record Spdx3VexUnderInvestigationVulnAssessmentRelationship : Spdx3VulnAssessmentRelationship
{
/// <summary>
/// Gets the JSON-LD type for VEX Under Investigation assessment.
/// </summary>
public const string TypeName = "security_VexUnderInvestigationVulnAssessmentRelationship";
/// <summary>
/// Gets or sets the VEX version.
/// </summary>
[JsonPropertyName("security_vexVersion")]
public string? VexVersion { get; init; }
/// <summary>
/// Gets or sets the status notes.
/// </summary>
[JsonPropertyName("security_statusNotes")]
public string? StatusNotes { get; init; }
}
/// <summary>
/// SPDX 3.0.1 VEX justification types (from spec).
/// Sprint: SPRINT_20260107_004_004 Task SP-001
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Spdx3VexJustificationType
{
/// <summary>Component is not present.</summary>
ComponentNotPresent,
/// <summary>Vulnerable code is not present.</summary>
VulnerableCodeNotPresent,
/// <summary>Vulnerable code cannot be controlled by adversary.</summary>
VulnerableCodeCannotBeControlledByAdversary,
/// <summary>Vulnerable code is not in execute path.</summary>
VulnerableCodeNotInExecutePath,
/// <summary>Inline mitigations already exist.</summary>
InlineMitigationsAlreadyExist
}

View File

@@ -11,7 +11,7 @@ namespace StellaOps.Spdx3.Model;
/// <summary>
/// Represents a relationship between SPDX 3.0.1 elements.
/// </summary>
public sealed record Spdx3Relationship : Spdx3Element
public record Spdx3Relationship : Spdx3Element
{
/// <summary>
/// Gets the source element of the relationship.
@@ -234,6 +234,31 @@ public enum Spdx3RelationshipType
/// </summary>
HasEvidence,
/// <summary>
/// Element A affects Element B (Security profile - VEX affected).
/// </summary>
Affects,
/// <summary>
/// Element A does not affect Element B (Security profile - VEX not affected).
/// </summary>
DoesNotAffect,
/// <summary>
/// Element A is fixed in Element B (Security profile - VEX fixed).
/// </summary>
FixedIn,
/// <summary>
/// Element A is under investigation for Element B (Security profile - VEX).
/// </summary>
UnderInvestigationFor,
/// <summary>
/// Element A has an assessment for Element B (Security profile - CVSS/EPSS).
/// </summary>
HasAssessmentFor,
/// <summary>
/// Other relationship type (requires comment).
/// </summary>

View File

@@ -7,6 +7,8 @@ using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Spdx3.JsonLd;
using StellaOps.Spdx3.Model;
using StellaOps.Spdx3.Model.Build;
using StellaOps.Spdx3.Model.Security;
using StellaOps.Spdx3.Model.Software;
namespace StellaOps.Spdx3;
@@ -202,6 +204,19 @@ public sealed class Spdx3Parser : ISpdx3Parser
ParseAgent<Spdx3Organization>(element, spdxId),
"Tool" or "spdx:Tool" =>
ParseAgent<Spdx3Tool>(element, spdxId),
"Build" or "build_Build" or "spdx:Build" =>
ParseBuild(element, spdxId),
"security_Vulnerability" or "Vulnerability" or "spdx:security_Vulnerability" =>
ParseVulnerability(element, spdxId),
"security_VexAffectedVulnAssessmentRelationship" or
"security_VexNotAffectedVulnAssessmentRelationship" or
"security_VexFixedVulnAssessmentRelationship" or
"security_VexUnderInvestigationVulnAssessmentRelationship" =>
ParseVexAssessment(element, spdxId, type),
"security_CvssV3VulnAssessmentRelationship" =>
ParseCvssAssessment(element, spdxId),
"security_EpssVulnAssessmentRelationship" =>
ParseEpssAssessment(element, spdxId),
_ => ParseGenericElement(element, spdxId, type, warnings)
};
}
@@ -312,6 +327,298 @@ public sealed class Spdx3Parser : ISpdx3Parser
};
}
private Spdx3Build ParseBuild(JsonElement element, string spdxId)
{
// Parse timestamps
DateTimeOffset? buildStartTime = null;
DateTimeOffset? buildEndTime = null;
var startTimeStr = GetStringProperty(element, "build_buildStartTime");
if (!string.IsNullOrEmpty(startTimeStr) && DateTimeOffset.TryParse(startTimeStr, out var parsedStart))
{
buildStartTime = parsedStart;
}
var endTimeStr = GetStringProperty(element, "build_buildEndTime");
if (!string.IsNullOrEmpty(endTimeStr) && DateTimeOffset.TryParse(endTimeStr, out var parsedEnd))
{
buildEndTime = parsedEnd;
}
// Parse config source digests
var configSourceDigests = ImmutableArray<Spdx3Hash>.Empty;
if (element.TryGetProperty("build_configSourceDigest", out var digestsElement) &&
digestsElement.ValueKind == JsonValueKind.Array)
{
var digests = new List<Spdx3Hash>();
foreach (var digestEl in digestsElement.EnumerateArray())
{
if (digestEl.ValueKind == JsonValueKind.Object)
{
var algorithm = GetStringProperty(digestEl, "algorithm") ?? "sha256";
var hashValue = GetStringProperty(digestEl, "hashValue") ?? string.Empty;
digests.Add(new Spdx3Hash { Algorithm = algorithm, HashValue = hashValue });
}
}
configSourceDigests = digests.ToImmutableArray();
}
// Parse environment and parameters as dictionaries
var environment = ParseDictionary(element, "build_environment");
var parameters = ParseDictionary(element, "build_parameter");
return new Spdx3Build
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
BuildType = GetStringProperty(element, "build_buildType") ?? string.Empty,
BuildId = GetStringProperty(element, "build_buildId"),
BuildStartTime = buildStartTime,
BuildEndTime = buildEndTime,
ConfigSourceUri = GetStringArrayProperty(element, "build_configSourceUri"),
ConfigSourceDigest = configSourceDigests,
ConfigSourceEntrypoint = GetStringArrayProperty(element, "build_configSourceEntrypoint"),
Environment = environment,
Parameter = parameters,
VerifiedUsing = ParseIntegrityMethods(element),
ExternalRef = ParseExternalRefs(element),
ExternalIdentifier = ParseExternalIdentifiers(element)
};
}
private static ImmutableDictionary<string, string> ParseDictionary(JsonElement element, string propertyName)
{
if (!element.TryGetProperty(propertyName, out var dictElement) ||
dictElement.ValueKind != JsonValueKind.Object)
{
return ImmutableDictionary<string, string>.Empty;
}
var dict = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var property in dictElement.EnumerateObject())
{
if (property.Value.ValueKind == JsonValueKind.String)
{
dict[property.Name] = property.Value.GetString() ?? string.Empty;
}
}
return dict.ToImmutableDictionary();
}
private Spdx3Vulnerability ParseVulnerability(JsonElement element, string spdxId)
{
DateTimeOffset? publishedTime = null;
DateTimeOffset? modifiedTime = null;
DateTimeOffset? withdrawnTime = null;
var publishedStr = GetStringProperty(element, "security_publishedTime");
if (!string.IsNullOrEmpty(publishedStr) && DateTimeOffset.TryParse(publishedStr, out var parsedPublished))
{
publishedTime = parsedPublished;
}
var modifiedStr = GetStringProperty(element, "security_modifiedTime");
if (!string.IsNullOrEmpty(modifiedStr) && DateTimeOffset.TryParse(modifiedStr, out var parsedModified))
{
modifiedTime = parsedModified;
}
var withdrawnStr = GetStringProperty(element, "security_withdrawnTime");
if (!string.IsNullOrEmpty(withdrawnStr) && DateTimeOffset.TryParse(withdrawnStr, out var parsedWithdrawn))
{
withdrawnTime = parsedWithdrawn;
}
return new Spdx3Vulnerability
{
SpdxId = spdxId,
Type = GetStringProperty(element, "@type"),
Name = GetStringProperty(element, "name"),
Summary = GetStringProperty(element, "summary"),
Description = GetStringProperty(element, "description"),
PublishedTime = publishedTime,
ModifiedTime = modifiedTime,
WithdrawnTime = withdrawnTime,
ExternalRefs = ParseExternalRefs(element),
ExternalIdentifiers = ParseExternalIdentifiers(element)
};
}
private Spdx3VulnAssessmentRelationship ParseVexAssessment(
JsonElement element,
string spdxId,
string type)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
DateTimeOffset? publishedTime = null;
var publishedStr = GetStringProperty(element, "security_publishedTime");
if (!string.IsNullOrEmpty(publishedStr) && DateTimeOffset.TryParse(publishedStr, out var parsedPublished))
{
publishedTime = parsedPublished;
}
DateTimeOffset? actionStatementTime = null;
var actionTimeStr = GetStringProperty(element, "security_actionStatementTime");
if (!string.IsNullOrEmpty(actionTimeStr) && DateTimeOffset.TryParse(actionTimeStr, out var parsedActionTime))
{
actionStatementTime = parsedActionTime;
}
var vexVersion = GetStringProperty(element, "security_vexVersion");
var statusNotes = GetStringProperty(element, "security_statusNotes");
var actionStatement = GetStringProperty(element, "security_actionStatement");
var impactStatement = GetStringProperty(element, "security_impactStatement");
var suppliedBy = GetStringProperty(element, "security_suppliedBy");
// Parse justification for not_affected
Spdx3VexJustificationType? justificationType = null;
var justificationStr = GetStringProperty(element, "security_justificationType");
if (!string.IsNullOrEmpty(justificationStr) &&
Enum.TryParse<Spdx3VexJustificationType>(justificationStr, ignoreCase: true, out var parsed))
{
justificationType = parsed;
}
return type switch
{
"security_VexAffectedVulnAssessmentRelationship" => new Spdx3VexAffectedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.Affects,
VexVersion = vexVersion,
StatusNotes = statusNotes,
ActionStatement = actionStatement,
ActionStatementTime = actionStatementTime,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexNotAffectedVulnAssessmentRelationship" => new Spdx3VexNotAffectedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.DoesNotAffect,
VexVersion = vexVersion,
StatusNotes = statusNotes,
ImpactStatement = impactStatement,
JustificationType = justificationType,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexFixedVulnAssessmentRelationship" => new Spdx3VexFixedVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.FixedIn,
VexVersion = vexVersion,
StatusNotes = statusNotes,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
"security_VexUnderInvestigationVulnAssessmentRelationship" => new Spdx3VexUnderInvestigationVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = type,
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.UnderInvestigationFor,
VexVersion = vexVersion,
StatusNotes = statusNotes,
PublishedTime = publishedTime,
SuppliedBy = suppliedBy
},
_ => throw new ArgumentException($"Unknown VEX assessment type: {type}")
};
}
private Spdx3CvssV3VulnAssessmentRelationship ParseCvssAssessment(JsonElement element, string spdxId)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
decimal? baseScore = null;
if (element.TryGetProperty("security_score", out var scoreEl) && scoreEl.TryGetDecimal(out var score))
{
baseScore = score;
}
var vectorString = GetStringProperty(element, "security_vectorString");
// Parse severity enum
Spdx3CvssSeverity? severityEnum = null;
var severityStr = GetStringProperty(element, "security_severity");
if (!string.IsNullOrEmpty(severityStr) &&
Enum.TryParse<Spdx3CvssSeverity>(severityStr, ignoreCase: true, out var parsedSeverity))
{
severityEnum = parsedSeverity;
}
return new Spdx3CvssV3VulnAssessmentRelationship
{
SpdxId = spdxId,
Type = "security_CvssV3VulnAssessmentRelationship",
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.HasAssessmentFor,
Score = baseScore,
VectorString = vectorString,
Severity = severityEnum
};
}
private Spdx3EpssVulnAssessmentRelationship ParseEpssAssessment(JsonElement element, string spdxId)
{
var assessedElement = GetStringProperty(element, "security_assessedElement") ?? string.Empty;
var from = GetStringProperty(element, "from") ?? string.Empty;
var toValue = GetStringProperty(element, "to");
var toArray = toValue != null ? ImmutableArray.Create(toValue) : GetStringArrayProperty(element, "to");
decimal? probability = null;
if (element.TryGetProperty("security_probability", out var probEl) && probEl.TryGetDecimal(out var prob))
{
probability = prob;
}
decimal? percentile = null;
if (element.TryGetProperty("security_percentile", out var percEl) && percEl.TryGetDecimal(out var perc))
{
percentile = perc;
}
return new Spdx3EpssVulnAssessmentRelationship
{
SpdxId = spdxId,
Type = "security_EpssVulnAssessmentRelationship",
AssessedElement = assessedElement,
From = from,
To = toArray,
RelationshipType = Spdx3RelationshipType.HasAssessmentFor,
Probability = probability,
Percentile = percentile
};
}
private T ParseAgent<T>(JsonElement element, string spdxId) where T : Spdx3Element
{
var name = GetStringProperty(element, "name") ?? string.Empty;

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();

Some files were not shown because too many files have changed in this diff Show More