feat: Add MongoIdempotencyStoreOptions for MongoDB configuration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

feat: Implement BsonJsonConverter for converting BsonDocument and BsonArray to JSON

fix: Update project file to include MongoDB.Bson package

test: Add GraphOverlayExporterTests to validate NDJSON export functionality

refactor: Refactor Program.cs in Attestation Tool for improved argument parsing and error handling

docs: Update README for stella-forensic-verify with usage instructions and exit codes

feat: Enhance HmacVerifier with clock skew and not-after checks

feat: Add MerkleRootVerifier and ChainOfCustodyVerifier for additional verification methods

fix: Update DenoRuntimeShim to correctly handle file paths

feat: Introduce ComposerAutoloadData and related parsing in ComposerLockReader

test: Add tests for Deno runtime execution and verification

test: Enhance PHP package tests to include autoload data verification

test: Add unit tests for HmacVerifier and verification logic
This commit is contained in:
StellaOps Bot
2025-11-22 16:42:56 +02:00
parent 967ae0ab16
commit dc7c75b496
85 changed files with 2272 additions and 917 deletions

View File

@@ -1,34 +0,0 @@
using System;
using System.Collections.Immutable;
namespace StellaOps.Excititor.Core.Observations;
/// <summary>
/// Minimal observation reference used in linkset updates while preserving Aggregation-Only semantics.
/// </summary>
public sealed record VexLinksetObservationRefCore(
string ObservationId,
string ProviderId,
string Status,
double? Confidence,
ImmutableDictionary<string, string> Attributes)
{
public static VexLinksetObservationRefCore Create(
string observationId,
string providerId,
string status,
double? confidence,
ImmutableDictionary<string, string>? attributes = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(observationId);
ArgumentException.ThrowIfNullOrWhiteSpace(providerId);
ArgumentException.ThrowIfNullOrWhiteSpace(status);
return new VexLinksetObservationRefCore(
observationId.Trim(),
providerId.Trim(),
status.Trim(),
confidence,
attributes ?? ImmutableDictionary<string, string>.Empty);
}
}

View File

@@ -5,6 +5,13 @@ using System.Linq;
namespace StellaOps.Excititor.Core.Observations;
public sealed record VexLinksetObservationRefCore(
string ObservationId,
string ProviderId,
string Status,
double? Confidence,
ImmutableDictionary<string, string> Attributes);
public static class VexLinksetUpdatedEventFactory
{
public const string EventType = "vex.linkset.updated";
@@ -26,10 +33,11 @@ public static class VexLinksetUpdatedEventFactory
var observationRefs = (observations ?? Enumerable.Empty<VexObservation>())
.Where(obs => obs is not null)
.SelectMany(obs => obs.Statements.Select(statement => new VexLinksetObservationRefCore(
observationId: obs.ObservationId,
providerId: obs.ProviderId,
status: statement.Status.ToString().ToLowerInvariant(),
confidence: statement.Signals?.Severity?.Score)))
ObservationId: obs.ObservationId,
ProviderId: obs.ProviderId,
Status: statement.Status.ToString().ToLowerInvariant(),
Confidence: null,
Attributes: obs.Attributes)))
.Distinct(VexLinksetObservationRefComparer.Instance)
.OrderBy(refItem => refItem.ProviderId, StringComparer.OrdinalIgnoreCase)
.ThenBy(refItem => refItem.ObservationId, StringComparer.Ordinal)

View File

@@ -491,7 +491,7 @@ public sealed record VexObservationLinkset
}
var normalizedJustification = VexObservation.TrimToNull(disagreement.Justification);
var clampedConfidence = disagreement.Confidence is null
double? clampedConfidence = disagreement.Confidence is null
? null
: Math.Clamp(disagreement.Confidence.Value, 0.0, 1.0);
@@ -529,7 +529,7 @@ public sealed record VexObservationLinkset
continue;
}
var clamped = item.Confidence is null ? null : Math.Clamp(item.Confidence.Value, 0.0, 1.0);
double? clamped = item.Confidence is null ? null : Math.Clamp(item.Confidence.Value, 0.0, 1.0);
set.Add(new VexLinksetObservationRefModel(obsId, provider, status, clamped));
}

View File

@@ -67,9 +67,9 @@ internal sealed class MongoVexObservationLookup : IVexObservationLookup
if (cursor is not null)
{
var cursorFilter = Builders<VexObservationRecord>.Filter.Or(
Builders<VexObservationRecord>.Filter.Lt(r => r.CreatedAt, cursor.CreatedAtUtc.UtcDateTime),
Builders<VexObservationRecord>.Filter.Lt(r => r.CreatedAt, cursor.CreatedAt.UtcDateTime),
Builders<VexObservationRecord>.Filter.And(
Builders<VexObservationRecord>.Filter.Eq(r => r.CreatedAt, cursor.CreatedAtUtc.UtcDateTime),
Builders<VexObservationRecord>.Filter.Eq(r => r.CreatedAt, cursor.CreatedAt.UtcDateTime),
Builders<VexObservationRecord>.Filter.Lt(r => r.ObservationId, cursor.ObservationId)));
filters.Add(cursorFilter);
}
@@ -117,7 +117,7 @@ internal sealed class MongoVexObservationLookup : IVexObservationLookup
record.Upstream.Signature.Present,
record.Upstream.Signature.Subject,
record.Upstream.Signature.Issuer,
record.Upstream.Signature.VerifiedAt);
signature: null);
var upstream = record.Upstream is null
? new VexObservationUpstream(
@@ -141,7 +141,7 @@ internal sealed class MongoVexObservationLookup : IVexObservationLookup
record.Document.Signature.Present,
record.Document.Signature.Subject,
record.Document.Signature.Issuer,
record.Document.Signature.VerifiedAt);
signature: null);
var content = record.Content is null
? new VexObservationContent("unknown", null, new JsonObject())
@@ -182,17 +182,10 @@ internal sealed class MongoVexObservationLookup : IVexObservationLookup
justification: justification,
introducedVersion: record.IntroducedVersion,
fixedVersion: record.FixedVersion,
detail: record.Detail,
signals: new VexSignalSnapshot(
severity: record.ScopeScore.HasValue ? new VexSeveritySignal("scope", record.ScopeScore, "n/a", null) : null,
Kev: record.Kev,
Epss: record.Epss),
confidence: null,
metadata: ImmutableDictionary<string, string>.Empty,
purl: null,
cpe: null,
evidence: null,
anchors: VexObservationAnchors.Empty,
additionalMetadata: ImmutableDictionary<string, string>.Empty,
signature: null);
metadata: ImmutableDictionary<string, string>.Empty);
}
private static VexObservationDisagreement MapDisagreement(VexLinksetDisagreementRecord record)
@@ -206,11 +199,11 @@ internal sealed class MongoVexObservationLookup : IVexObservationLookup
var references = record?.References?.Select(r => new VexObservationReference(r.Type, r.Url)).ToImmutableArray() ?? ImmutableArray<VexObservationReference>.Empty;
var reconciledFrom = record?.ReconciledFrom?.Where(NotNullOrWhiteSpace).Select(r => r.Trim()).ToImmutableArray() ?? ImmutableArray<string>.Empty;
var disagreements = record?.Disagreements?.Select(MapDisagreement).ToImmutableArray() ?? ImmutableArray<VexObservationDisagreement>.Empty;
var observationRefs = record?.Observations?.Select(o => new VexLinksetObservationRef(
var observationRefs = record?.Observations?.Select(o => new VexLinksetObservationRefModel(
o.ObservationId,
o.ProviderId,
o.Status,
o.Confidence)).ToImmutableArray() ?? ImmutableArray<VexLinksetObservationRef>.Empty;
o.Confidence)).ToImmutableArray() ?? ImmutableArray<VexLinksetObservationRefModel>.Empty;
return new VexObservationLinkset(aliases, purls, cpes, references, reconciledFrom, disagreements, observationRefs);
}

View File

@@ -4,7 +4,8 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Storage.Mongo.Migrations;
using StellaOps.Excititor.Storage.Mongo.Migrations;
using StellaOps.Excititor.Core.Observations;
namespace StellaOps.Excititor.Storage.Mongo;