save checkpoint: save features

This commit is contained in:
master
2026-02-12 10:27:23 +02:00
parent dca86e1248
commit 5bca406787
8837 changed files with 1796879 additions and 5294 deletions

View File

@@ -0,0 +1,255 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"StellaOps.Attestation/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"StellaOps.Attestor.Envelope": "1.0.0"
},
"runtime": {
"StellaOps.Attestation.dll": {}
}
},
"Blake3/1.1.0": {
"runtime": {
"lib/net7.0/Blake3.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.1.0.0"
}
},
"runtimeTargets": {
"runtimes/linux-arm/native/libblake3_dotnet.so": {
"rid": "linux-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm64/native/libblake3_dotnet.so": {
"rid": "linux-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x64/native/libblake3_dotnet.so": {
"rid": "linux-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-arm64/native/libblake3_dotnet.dylib": {
"rid": "osx-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-x64/native/libblake3_dotnet.dylib": {
"rid": "osx-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm64/native/blake3_dotnet.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/blake3_dotnet.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/blake3_dotnet.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"BouncyCastle.Cryptography/2.6.2": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.2.46322"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Primitives/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.IdentityModel.Logging": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"StellaOps.Attestor.Envelope/1.0.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"StellaOps.Cryptography": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.Envelope.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Cryptography/1.0.0": {
"dependencies": {
"Blake3": "1.1.0",
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.IdentityModel.Tokens": "8.15.0"
},
"runtime": {
"StellaOps.Cryptography.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"StellaOps.Attestation/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Blake3/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/gWRFsXYeIFof8YAoFJwzv2fYjSTCo+6vvTSL6pyXw2ZLXQdRvEyXhO43jyDfEFBCTxMxWpoHbIcIEIF6a3QdQ==",
"path": "blake3/1.1.0",
"hashPath": "blake3.1.1.0.nupkg.sha512"
},
"BouncyCastle.Cryptography/2.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==",
"path": "bouncycastle.cryptography/2.6.2",
"hashPath": "bouncycastle.cryptography.2.6.2.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==",
"path": "microsoft.extensions.dependencyinjection.abstractions/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==",
"path": "microsoft.extensions.logging.abstractions/10.0.1",
"hashPath": "microsoft.extensions.logging.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==",
"path": "microsoft.extensions.options/10.0.1",
"hashPath": "microsoft.extensions.options.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==",
"path": "microsoft.extensions.primitives/10.0.1",
"hashPath": "microsoft.extensions.primitives.10.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==",
"path": "microsoft.identitymodel.abstractions/8.15.0",
"hashPath": "microsoft.identitymodel.abstractions.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==",
"path": "microsoft.identitymodel.logging/8.15.0",
"hashPath": "microsoft.identitymodel.logging.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==",
"path": "microsoft.identitymodel.tokens/8.15.0",
"hashPath": "microsoft.identitymodel.tokens.8.15.0.nupkg.sha512"
},
"StellaOps.Attestor.Envelope/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Cryptography/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -0,0 +1,238 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"StellaOps.Attestor.Envelope/1.0.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"StellaOps.Cryptography": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.Envelope.dll": {}
}
},
"Blake3/1.1.0": {
"runtime": {
"lib/net7.0/Blake3.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.1.0.0"
}
},
"runtimeTargets": {
"runtimes/linux-arm/native/libblake3_dotnet.so": {
"rid": "linux-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm64/native/libblake3_dotnet.so": {
"rid": "linux-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x64/native/libblake3_dotnet.so": {
"rid": "linux-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-arm64/native/libblake3_dotnet.dylib": {
"rid": "osx-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-x64/native/libblake3_dotnet.dylib": {
"rid": "osx-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm64/native/blake3_dotnet.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/blake3_dotnet.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/blake3_dotnet.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"BouncyCastle.Cryptography/2.6.2": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.2.46322"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Primitives/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.IdentityModel.Logging": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"StellaOps.Cryptography/1.0.0": {
"dependencies": {
"Blake3": "1.1.0",
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.IdentityModel.Tokens": "8.15.0"
},
"runtime": {
"StellaOps.Cryptography.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"StellaOps.Attestor.Envelope/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Blake3/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/gWRFsXYeIFof8YAoFJwzv2fYjSTCo+6vvTSL6pyXw2ZLXQdRvEyXhO43jyDfEFBCTxMxWpoHbIcIEIF6a3QdQ==",
"path": "blake3/1.1.0",
"hashPath": "blake3.1.1.0.nupkg.sha512"
},
"BouncyCastle.Cryptography/2.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==",
"path": "bouncycastle.cryptography/2.6.2",
"hashPath": "bouncycastle.cryptography.2.6.2.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==",
"path": "microsoft.extensions.dependencyinjection.abstractions/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==",
"path": "microsoft.extensions.logging.abstractions/10.0.1",
"hashPath": "microsoft.extensions.logging.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==",
"path": "microsoft.extensions.options/10.0.1",
"hashPath": "microsoft.extensions.options.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==",
"path": "microsoft.extensions.primitives/10.0.1",
"hashPath": "microsoft.extensions.primitives.10.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==",
"path": "microsoft.identitymodel.abstractions/8.15.0",
"hashPath": "microsoft.identitymodel.abstractions.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==",
"path": "microsoft.identitymodel.logging/8.15.0",
"hashPath": "microsoft.identitymodel.logging.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==",
"path": "microsoft.identitymodel.tokens/8.15.0",
"hashPath": "microsoft.identitymodel.tokens.8.15.0.nupkg.sha512"
},
"StellaOps.Cryptography/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -93,4 +93,20 @@ public sealed partial class ChangeTraceAttestationService
_ => ExploitabilityImpact.Unchanged
};
}
private static bool IsHysteresisSuppressed(PackageDelta delta, double threshold)
{
if (threshold <= 0 || delta.TrustDelta is null)
{
return false;
}
var isOscillationCandidate = delta.ChangeType is PackageChangeType.Modified or PackageChangeType.Rebuilt;
if (!isOscillationCandidate)
{
return false;
}
return Math.Abs(delta.TrustDelta.Score) < threshold;
}
}

View File

@@ -22,7 +22,11 @@ public sealed partial class ChangeTraceAttestationService
ChangeTraceModel trace,
ChangeTraceAttestationOptions options)
{
var deltas = trace.Deltas
var filteredDeltas = trace.Deltas
.Where(delta => !IsHysteresisSuppressed(delta, options.HysteresisThreshold))
.ToImmutableArray();
var deltas = filteredDeltas
.Take(options.MaxDeltas)
.Select(d => new ChangeTraceDeltaEntry
{
@@ -40,14 +44,14 @@ public sealed partial class ChangeTraceAttestationService
})
.ToImmutableArray();
var proofSteps = trace.Deltas
var proofSteps = filteredDeltas
.Where(d => d.TrustDelta is not null)
.SelectMany(d => d.TrustDelta!.ProofSteps)
.Distinct()
.Take(options.MaxProofSteps)
.ToImmutableArray();
var aggregateReachability = AggregateReachabilityImpact(trace.Deltas);
var aggregateReachability = AggregateReachabilityImpact(filteredDeltas);
var aggregateExploitability = DetermineExploitabilityFromScore(trace.Summary.RiskDelta);
return new ChangeTracePredicate

View File

@@ -55,4 +55,10 @@ public sealed record ChangeTraceAttestationOptions
/// Maximum number of deltas to include in the predicate.
/// </summary>
public int MaxDeltas { get; init; } = 1000;
/// <summary>
/// Suppress minor modified/rebuilt deltas whose absolute trust score is below this threshold.
/// This dampens noisy flip-flop changes between near-identical snapshots.
/// </summary>
public double HysteresisThreshold { get; init; } = 0.05;
}

View File

@@ -4,12 +4,20 @@ namespace StellaOps.Attestor.ProofChain.Generators;
public sealed partial class BackportProofGenerator
{
private enum ProofStrength
{
Heuristic = 40,
StaticAnalysis = 60,
BinaryProof = 80,
Authoritative = 100
}
private static double ComputeAggregateConfidence(IReadOnlyList<ProofEvidence> evidences)
{
// Confidence aggregation strategy:
// 1. Start with highest individual confidence
// 2. Add bonus for multiple independent sources
// 3. Cap at 0.98 (never 100% certain)
// 1. Start from strongest proof tier (Authoritative > Binary > Static > Heuristic)
// 2. Add small diminishing bonus for independent corroboration
// 3. Cap at 0.98 (never claim perfect certainty)
var baseConfidence = evidences.Count switch
{
@@ -18,8 +26,7 @@ public sealed partial class BackportProofGenerator
_ => evidences.Max(e => DetermineEvidenceConfidence(e.Type))
};
// Bonus for multiple sources (diminishing returns)
var multiSourceBonus = evidences.Count switch
var corroborationBonus = evidences.Count switch
{
<= 1 => 0.0,
2 => 0.05,
@@ -27,22 +34,23 @@ public sealed partial class BackportProofGenerator
_ => 0.10
};
return Math.Min(baseConfidence + multiSourceBonus, 0.98);
return Math.Min(baseConfidence + corroborationBonus, 0.98);
}
private static double DetermineEvidenceConfidence(EvidenceType type)
{
return type switch
=> (double)ResolveStrength(type) / 100.0;
private static ProofStrength ResolveStrength(EvidenceType type)
=> type switch
{
EvidenceType.DistroAdvisory => 0.98,
EvidenceType.ChangelogMention => 0.80,
EvidenceType.PatchHeader => 0.85,
EvidenceType.BinaryFingerprint => 0.70,
EvidenceType.VersionComparison => 0.95,
EvidenceType.BuildCatalog => 0.90,
_ => 0.50
EvidenceType.DistroAdvisory => ProofStrength.Authoritative,
EvidenceType.VersionComparison => ProofStrength.BinaryProof,
EvidenceType.BuildCatalog => ProofStrength.BinaryProof,
EvidenceType.PatchHeader => ProofStrength.StaticAnalysis,
EvidenceType.ChangelogMention => ProofStrength.StaticAnalysis,
EvidenceType.BinaryFingerprint => ProofStrength.Heuristic,
_ => ProofStrength.Heuristic
};
}
private static string DetermineMethod(IReadOnlyList<ProofEvidence> evidences)
{

View File

@@ -11,6 +11,18 @@ public sealed partial class InMemoryProofGraphService
string targetId,
ProofGraphEdgeType edgeType,
CancellationToken ct = default)
=> AddEdgeAsync(sourceId, targetId, edgeType, provenance: null, ct);
/// <summary>
/// Add a provenance-aware edge. Duplicate edges are merged by semantic key
/// and provenance values are merged deterministically.
/// </summary>
public Task<ProofGraphEdge> AddEdgeAsync(
string sourceId,
string targetId,
ProofGraphEdgeType edgeType,
IEnumerable<string>? provenance,
CancellationToken ct = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sourceId);
ArgumentException.ThrowIfNullOrWhiteSpace(targetId);
@@ -26,6 +38,7 @@ public sealed partial class InMemoryProofGraphService
}
var edgeId = $"{sourceId}->{edgeType}->{targetId}";
var normalizedProvenance = NormalizeProvenance(provenance);
var edge = new ProofGraphEdge
{
@@ -33,7 +46,8 @@ public sealed partial class InMemoryProofGraphService
SourceId = sourceId,
TargetId = targetId,
Type = edgeType,
CreatedAt = _timeProvider.GetUtcNow()
CreatedAt = _timeProvider.GetUtcNow(),
Provenance = normalizedProvenance
};
if (_edges.TryAdd(edgeId, edge))
@@ -51,8 +65,38 @@ public sealed partial class InMemoryProofGraphService
else
{
edge = _edges[edgeId];
if (normalizedProvenance.Count != 0)
{
var merged = edge.Provenance
.Concat(normalizedProvenance)
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
if (merged.Length != edge.Provenance.Count || !merged.SequenceEqual(edge.Provenance, StringComparer.Ordinal))
{
edge = edge with { Provenance = merged };
_edges[edgeId] = edge;
}
}
}
return Task.FromResult(edge);
}
private static IReadOnlyList<string> NormalizeProvenance(IEnumerable<string>? provenance)
{
if (provenance is null)
{
return [];
}
return provenance
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value.Trim())
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
}
}

View File

@@ -31,4 +31,10 @@ public sealed record ProofGraphEdge
/// When this edge was created.
/// </summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// Provenance sources associated with this edge.
/// When duplicate edges are ingested, provenance is merged deterministically.
/// </summary>
public IReadOnlyList<string> Provenance { get; init; } = [];
}

View File

@@ -0,0 +1,135 @@
using System.Collections.Immutable;
using System.Globalization;
namespace StellaOps.Attestor.ProofChain.Predicates;
public sealed partial record DeltaVerdictPredicate
{
public const string ChangeTypeNew = "New";
public const string ChangeTypeResolved = "Resolved";
public const string ChangeTypeConfidenceUp = "ConfidenceUp";
public const string ChangeTypeConfidenceDown = "ConfidenceDown";
public const string ChangeTypePolicyImpact = "PolicyImpact";
/// <summary>
/// Returns a copy where every change has a normalized change type.
/// </summary>
public DeltaVerdictPredicate CategorizeChanges()
=> this with
{
Changes = Changes.Select(CategorizeChange).ToImmutableArray()
};
internal static DeltaVerdictChange CategorizeChange(DeltaVerdictChange change)
{
ArgumentNullException.ThrowIfNull(change);
var explicitType = NormalizeExplicitType(change.ChangeType);
if (explicitType is not null)
{
return change with { ChangeType = explicitType };
}
var inferred = InferChangeType(change);
return change with { ChangeType = inferred };
}
private static string InferChangeType(DeltaVerdictChange change)
{
if (IsPolicyImpact(change))
{
return ChangeTypePolicyImpact;
}
if (IsNew(change))
{
return ChangeTypeNew;
}
if (IsResolved(change))
{
return ChangeTypeResolved;
}
if (TryParseScoreDelta(change.PreviousValue, change.CurrentValue, out var delta))
{
if (delta > 0)
{
return ChangeTypeConfidenceUp;
}
if (delta < 0)
{
return ChangeTypeConfidenceDown;
}
}
var direction = change.Direction ?? string.Empty;
if (direction.Contains("increase", StringComparison.OrdinalIgnoreCase)
|| direction.Contains("up", StringComparison.OrdinalIgnoreCase))
{
return ChangeTypeConfidenceUp;
}
if (direction.Contains("decrease", StringComparison.OrdinalIgnoreCase)
|| direction.Contains("down", StringComparison.OrdinalIgnoreCase))
{
return ChangeTypeConfidenceDown;
}
return ChangeTypePolicyImpact;
}
private static bool IsPolicyImpact(DeltaVerdictChange change)
=> ContainsAny(change.Rule, "policy", "rule")
|| ContainsAny(change.Reason, "policy", "gate", "rule");
private static bool IsNew(DeltaVerdictChange change)
=> ContainsAny(change.Direction, "new", "added", "introduced")
|| string.IsNullOrWhiteSpace(change.PreviousValue) && !string.IsNullOrWhiteSpace(change.CurrentValue);
private static bool IsResolved(DeltaVerdictChange change)
=> ContainsAny(change.Direction, "resolved", "removed", "eliminated")
|| !string.IsNullOrWhiteSpace(change.PreviousValue) && string.IsNullOrWhiteSpace(change.CurrentValue);
private static bool ContainsAny(string? value, params string[] needles)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
return needles.Any(needle => value.Contains(needle, StringComparison.OrdinalIgnoreCase));
}
private static bool TryParseScoreDelta(string? before, string? after, out double delta)
{
delta = 0;
if (!double.TryParse(before, NumberStyles.Float, CultureInfo.InvariantCulture, out var beforeValue)
|| !double.TryParse(after, NumberStyles.Float, CultureInfo.InvariantCulture, out var afterValue))
{
return false;
}
delta = afterValue - beforeValue;
return true;
}
private static string? NormalizeExplicitType(string? rawType)
{
if (string.IsNullOrWhiteSpace(rawType))
{
return null;
}
return rawType.Trim().ToLowerInvariant() switch
{
"new" => ChangeTypeNew,
"resolved" => ChangeTypeResolved,
"confidenceup" => ChangeTypeConfidenceUp,
"confidencedown" => ChangeTypeConfidenceDown,
"policyimpact" => ChangeTypePolicyImpact,
_ => null
};
}
}

View File

@@ -1,10 +1,14 @@
# Attestor ProofChain Task Board
# Attestor ProofChain Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| QA-ATTESTOR-VERIFY-006 | DONE | `ai-remediation-plan-attestation` verified with run-001 Tier 0/1/2 evidence; fixed remediation threshold fixture and completed retest (`17/17`). |
| QA-ATTESTOR-VERIFY-007 | DONE | `asn-1-native-rfc-3161-timestamp-token-parsing` run-001 completed with terminal `not_implemented`; dossier moved to `docs/features/unimplemented/attestor/`. |
| QA-ATTESTOR-VERIFY-008 | DONE | `attestable-exception-objects-with-expiries-and-audit-trails` run-001 reached terminal `not_implemented`; dossier moved to `docs/features/unimplemented/attestor/`. |
| QA-ATTESTOR-VERIFY-009 | DONE | `attestable-reachability-slices` run-001 verified with targeted reachability witness DSSE behavior tests (`5/5`) and moved to `docs/features/checked/attestor/`. |
| QA-ATTESTOR-VERIFY-001 | DONE | `adaptive-noise-gating-for-vulnerability-graphs` verified with run-002 evidence and moved to checked. |
| QA-ATTESTOR-VERIFY-002 | DONE | `ai-assisted-explanation-and-classification` verified with run-001 evidence and moved to checked. |
| QA-ATTESTOR-VERIFY-003 | DONE | `ai-authority-classification-engine` revalidated with run-002 (`11/11`) and docs/state synchronized. |
@@ -13,3 +17,6 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0062-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0062-A | TODO | Reopened after revalidation 2026-01-06. |

View File

@@ -0,0 +1,558 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"StellaOps.Attestor.ProofChain/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"StellaOps.Attestor.Envelope": "1.0.0",
"StellaOps.Canonical.Json": "1.0.0",
"StellaOps.Concelier.SourceIntel": "1.0.0",
"StellaOps.Feedser.BinaryAnalysis": "1.0.0",
"StellaOps.Feedser.Core": "1.0.0",
"StellaOps.Scanner.ChangeTrace": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.ProofChain.dll": {}
}
},
"Blake3/1.1.0": {
"runtime": {
"lib/net7.0/Blake3.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.1.0.0"
}
},
"runtimeTargets": {
"runtimes/linux-arm/native/libblake3_dotnet.so": {
"rid": "linux-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm64/native/libblake3_dotnet.so": {
"rid": "linux-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x64/native/libblake3_dotnet.so": {
"rid": "linux-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-arm64/native/libblake3_dotnet.dylib": {
"rid": "osx-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-x64/native/libblake3_dotnet.dylib": {
"rid": "osx-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm64/native/blake3_dotnet.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/blake3_dotnet.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/blake3_dotnet.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"BouncyCastle.Cryptography/2.6.2": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.2.46322"
}
}
},
"Microsoft.Extensions.Caching.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Caching.Memory/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration.Binder/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration": "10.0.1",
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.DependencyInjection/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Diagnostics/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration": "10.0.1",
"Microsoft.Extensions.Diagnostics.Abstractions": "10.0.1",
"Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Diagnostics.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Http/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Diagnostics": "10.0.1",
"Microsoft.Extensions.Logging": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Http.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.Configuration.Binder": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Primitives/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.IdentityModel.Logging": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"StellaOps.Attestor.Envelope/1.0.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"StellaOps.Cryptography": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.Envelope.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Canonical.Json/1.0.0": {
"runtime": {
"StellaOps.Canonical.Json.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Concelier.SourceIntel/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
"Microsoft.Extensions.Caching.Memory": "10.0.1",
"Microsoft.Extensions.Http": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1"
},
"runtime": {
"StellaOps.Concelier.SourceIntel.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Cryptography/1.0.0": {
"dependencies": {
"Blake3": "1.1.0",
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.IdentityModel.Tokens": "8.15.0"
},
"runtime": {
"StellaOps.Cryptography.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Feedser.BinaryAnalysis/1.0.0": {
"runtime": {
"StellaOps.Feedser.BinaryAnalysis.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Feedser.Core/1.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1"
},
"runtime": {
"StellaOps.Feedser.Core.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Scanner.ChangeTrace/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"StellaOps.Canonical.Json": "1.0.0"
},
"runtime": {
"StellaOps.Scanner.ChangeTrace.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"StellaOps.Attestor.ProofChain/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Blake3/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/gWRFsXYeIFof8YAoFJwzv2fYjSTCo+6vvTSL6pyXw2ZLXQdRvEyXhO43jyDfEFBCTxMxWpoHbIcIEIF6a3QdQ==",
"path": "blake3/1.1.0",
"hashPath": "blake3.1.1.0.nupkg.sha512"
},
"BouncyCastle.Cryptography/2.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==",
"path": "bouncycastle.cryptography/2.6.2",
"hashPath": "bouncycastle.cryptography.2.6.2.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Vb1vVAQDxHpXVdL9fpOX2BzeV7bbhzG4pAcIKRauRl0/VfkE8mq0f+fYC+gWICh3dlzTZInJ/cTeBS2MgU/XvQ==",
"path": "microsoft.extensions.caching.abstractions/10.0.1",
"hashPath": "microsoft.extensions.caching.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Memory/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NxqSP0Ky4dZ5ybszdZCqs1X2C70s+dXflqhYBUh/vhcQVTIooNCXIYnLVbafoAFGZMs51d9+rHxveXs0ZC3SQQ==",
"path": "microsoft.extensions.caching.memory/10.0.1",
"hashPath": "microsoft.extensions.caching.memory.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==",
"path": "microsoft.extensions.configuration/10.0.1",
"hashPath": "microsoft.extensions.configuration.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==",
"path": "microsoft.extensions.configuration.abstractions/10.0.1",
"hashPath": "microsoft.extensions.configuration.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Binder/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==",
"path": "microsoft.extensions.configuration.binder/10.0.1",
"hashPath": "microsoft.extensions.configuration.binder.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==",
"path": "microsoft.extensions.dependencyinjection/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==",
"path": "microsoft.extensions.dependencyinjection.abstractions/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YaocqxscJLxLit0F5yq2XyB+9C7rSRfeTL7MJIl7XwaOoUO3i0EqfO2kmtjiRduYWw7yjcSINEApYZbzjau2gQ==",
"path": "microsoft.extensions.diagnostics/10.0.1",
"hashPath": "microsoft.extensions.diagnostics.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==",
"path": "microsoft.extensions.diagnostics.abstractions/10.0.1",
"hashPath": "microsoft.extensions.diagnostics.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Http/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZXJup9ReE1Ot3M8jqcw1b/lnc8USxyYS3cyLsssU39u04TES9JNGviWUGIvP3K7mMU3TF7kQl2aS0SmVwegflw==",
"path": "microsoft.extensions.http/10.0.1",
"hashPath": "microsoft.extensions.http.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==",
"path": "microsoft.extensions.logging/10.0.1",
"hashPath": "microsoft.extensions.logging.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==",
"path": "microsoft.extensions.logging.abstractions/10.0.1",
"hashPath": "microsoft.extensions.logging.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==",
"path": "microsoft.extensions.options/10.0.1",
"hashPath": "microsoft.extensions.options.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options.ConfigurationExtensions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==",
"path": "microsoft.extensions.options.configurationextensions/10.0.1",
"hashPath": "microsoft.extensions.options.configurationextensions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==",
"path": "microsoft.extensions.primitives/10.0.1",
"hashPath": "microsoft.extensions.primitives.10.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==",
"path": "microsoft.identitymodel.abstractions/8.15.0",
"hashPath": "microsoft.identitymodel.abstractions.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==",
"path": "microsoft.identitymodel.logging/8.15.0",
"hashPath": "microsoft.identitymodel.logging.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==",
"path": "microsoft.identitymodel.tokens/8.15.0",
"hashPath": "microsoft.identitymodel.tokens.8.15.0.nupkg.sha512"
},
"StellaOps.Attestor.Envelope/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Canonical.Json/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Concelier.SourceIntel/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Cryptography/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Feedser.BinaryAnalysis/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Feedser.Core/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Scanner.ChangeTrace/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -0,0 +1,646 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"StellaOps.Attestor.StandardPredicates/1.0.0": {
"dependencies": {
"JsonSchema.Net": "8.0.4",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"StellaOps.Attestor.Envelope": "1.0.0",
"StellaOps.Attestor.ProofChain": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.StandardPredicates.dll": {}
}
},
"Blake3/1.1.0": {
"runtime": {
"lib/net7.0/Blake3.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.1.0.0"
}
},
"runtimeTargets": {
"runtimes/linux-arm/native/libblake3_dotnet.so": {
"rid": "linux-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm64/native/libblake3_dotnet.so": {
"rid": "linux-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x64/native/libblake3_dotnet.so": {
"rid": "linux-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-arm64/native/libblake3_dotnet.dylib": {
"rid": "osx-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-x64/native/libblake3_dotnet.dylib": {
"rid": "osx-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm64/native/blake3_dotnet.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/blake3_dotnet.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/blake3_dotnet.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"BouncyCastle.Cryptography/2.6.2": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.6.2.46322"
}
}
},
"Humanizer.Core/3.0.1": {
"runtime": {
"lib/net10.0/Humanizer.dll": {
"assemblyVersion": "3.0.0.0",
"fileVersion": "3.0.1.28244"
}
}
},
"Json.More.Net/2.2.0": {
"runtime": {
"lib/net10.0/Json.More.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.2.0.0"
}
}
},
"JsonPointer.Net/6.0.1": {
"dependencies": {
"Humanizer.Core": "3.0.1",
"Json.More.Net": "2.2.0"
},
"runtime": {
"lib/net10.0/JsonPointer.Net.dll": {
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.1.0"
}
}
},
"JsonSchema.Net/8.0.4": {
"dependencies": {
"JsonPointer.Net": "6.0.1"
},
"runtime": {
"lib/net9.0/JsonSchema.Net.dll": {
"assemblyVersion": "8.0.4.0",
"fileVersion": "8.0.4.0"
}
}
},
"Microsoft.Extensions.Caching.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Caching.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Caching.Memory/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Caching.Memory.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Configuration.Binder/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration": "10.0.1",
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Configuration.Binder.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.DependencyInjection/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Diagnostics/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration": "10.0.1",
"Microsoft.Extensions.Diagnostics.Abstractions": "10.0.1",
"Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Diagnostics.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Diagnostics.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Http/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Diagnostics": "10.0.1",
"Microsoft.Extensions.Logging": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Http.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options/10.0.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions/10.0.1": {
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "10.0.1",
"Microsoft.Extensions.Configuration.Binder": "10.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.Extensions.Primitives": "10.0.1"
},
"runtime": {
"lib/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.Extensions.Primitives/10.0.1": {
"runtime": {
"lib/net10.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "10.0.0.0",
"fileVersion": "10.0.125.57005"
}
}
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.IdentityModel.Logging": "8.15.0"
},
"runtime": {
"lib/net10.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.15.0.0",
"fileVersion": "8.15.0.61118"
}
}
},
"StellaOps.Attestor.Envelope/1.0.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2",
"StellaOps.Cryptography": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.Envelope.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Attestor.ProofChain/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"StellaOps.Attestor.Envelope": "1.0.0",
"StellaOps.Canonical.Json": "1.0.0",
"StellaOps.Concelier.SourceIntel": "1.0.0",
"StellaOps.Feedser.BinaryAnalysis": "1.0.0",
"StellaOps.Feedser.Core": "1.0.0",
"StellaOps.Scanner.ChangeTrace": "1.0.0"
},
"runtime": {
"StellaOps.Attestor.ProofChain.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Canonical.Json/1.0.0": {
"runtime": {
"StellaOps.Canonical.Json.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Concelier.SourceIntel/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
"Microsoft.Extensions.Caching.Memory": "10.0.1",
"Microsoft.Extensions.Http": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1"
},
"runtime": {
"StellaOps.Concelier.SourceIntel.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Cryptography/1.0.0": {
"dependencies": {
"Blake3": "1.1.0",
"BouncyCastle.Cryptography": "2.6.2",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"Microsoft.IdentityModel.Tokens": "8.15.0"
},
"runtime": {
"StellaOps.Cryptography.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Feedser.BinaryAnalysis/1.0.0": {
"runtime": {
"StellaOps.Feedser.BinaryAnalysis.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Feedser.Core/1.0.0": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
"Microsoft.Extensions.Logging.Abstractions": "10.0.1"
},
"runtime": {
"StellaOps.Feedser.Core.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"StellaOps.Scanner.ChangeTrace/1.0.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
"Microsoft.Extensions.Options": "10.0.1",
"StellaOps.Canonical.Json": "1.0.0"
},
"runtime": {
"StellaOps.Scanner.ChangeTrace.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"StellaOps.Attestor.StandardPredicates/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Blake3/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/gWRFsXYeIFof8YAoFJwzv2fYjSTCo+6vvTSL6pyXw2ZLXQdRvEyXhO43jyDfEFBCTxMxWpoHbIcIEIF6a3QdQ==",
"path": "blake3/1.1.0",
"hashPath": "blake3.1.1.0.nupkg.sha512"
},
"BouncyCastle.Cryptography/2.6.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==",
"path": "bouncycastle.cryptography/2.6.2",
"hashPath": "bouncycastle.cryptography.2.6.2.nupkg.sha512"
},
"Humanizer.Core/3.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-scB3+KcxNmEjZK5V8rKCW2gIiL8m8KH91w14FuuExyhi9xTyAJ+jr+DDxGdy12mHmioe2uvjxTfMgM7WmSUFlw==",
"path": "humanizer.core/3.0.1",
"hashPath": "humanizer.core.3.0.1.nupkg.sha512"
},
"Json.More.Net/2.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fiyEZJNgiCzDa7/N9bZ+CnM5qnawyJ54+CkHNZ2svwxWBWNNFzydJ7RlroqMdOjokGBiLcKIxdajNvOklzlYqQ==",
"path": "json.more.net/2.2.0",
"hashPath": "json.more.net.2.2.0.nupkg.sha512"
},
"JsonPointer.Net/6.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-EbSJkd/1y9r0WBIptjUctA6BzjgKh1aNU/g6QGgdMZVZ8wc0S/ysQhfiQerP9/VFTeENFHFdCQaoOk9fZqwnFQ==",
"path": "jsonpointer.net/6.0.1",
"hashPath": "jsonpointer.net.6.0.1.nupkg.sha512"
},
"JsonSchema.Net/8.0.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NSJ1iu+7Dg6fxaev90bWLvHi5mCqrGMgJuRzXr9mIaRt54Niox3D5g3uKH7ic7vcfkyhDU9opkAPGvek+cG53g==",
"path": "jsonschema.net/8.0.4",
"hashPath": "jsonschema.net.8.0.4.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Vb1vVAQDxHpXVdL9fpOX2BzeV7bbhzG4pAcIKRauRl0/VfkE8mq0f+fYC+gWICh3dlzTZInJ/cTeBS2MgU/XvQ==",
"path": "microsoft.extensions.caching.abstractions/10.0.1",
"hashPath": "microsoft.extensions.caching.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Caching.Memory/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NxqSP0Ky4dZ5ybszdZCqs1X2C70s+dXflqhYBUh/vhcQVTIooNCXIYnLVbafoAFGZMs51d9+rHxveXs0ZC3SQQ==",
"path": "microsoft.extensions.caching.memory/10.0.1",
"hashPath": "microsoft.extensions.caching.memory.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==",
"path": "microsoft.extensions.configuration/10.0.1",
"hashPath": "microsoft.extensions.configuration.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==",
"path": "microsoft.extensions.configuration.abstractions/10.0.1",
"hashPath": "microsoft.extensions.configuration.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Configuration.Binder/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==",
"path": "microsoft.extensions.configuration.binder/10.0.1",
"hashPath": "microsoft.extensions.configuration.binder.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==",
"path": "microsoft.extensions.dependencyinjection/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==",
"path": "microsoft.extensions.dependencyinjection.abstractions/10.0.1",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YaocqxscJLxLit0F5yq2XyB+9C7rSRfeTL7MJIl7XwaOoUO3i0EqfO2kmtjiRduYWw7yjcSINEApYZbzjau2gQ==",
"path": "microsoft.extensions.diagnostics/10.0.1",
"hashPath": "microsoft.extensions.diagnostics.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Diagnostics.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==",
"path": "microsoft.extensions.diagnostics.abstractions/10.0.1",
"hashPath": "microsoft.extensions.diagnostics.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Http/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZXJup9ReE1Ot3M8jqcw1b/lnc8USxyYS3cyLsssU39u04TES9JNGviWUGIvP3K7mMU3TF7kQl2aS0SmVwegflw==",
"path": "microsoft.extensions.http/10.0.1",
"hashPath": "microsoft.extensions.http.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==",
"path": "microsoft.extensions.logging/10.0.1",
"hashPath": "microsoft.extensions.logging.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Logging.Abstractions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==",
"path": "microsoft.extensions.logging.abstractions/10.0.1",
"hashPath": "microsoft.extensions.logging.abstractions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==",
"path": "microsoft.extensions.options/10.0.1",
"hashPath": "microsoft.extensions.options.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Options.ConfigurationExtensions/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==",
"path": "microsoft.extensions.options.configurationextensions/10.0.1",
"hashPath": "microsoft.extensions.options.configurationextensions.10.0.1.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/10.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==",
"path": "microsoft.extensions.primitives/10.0.1",
"hashPath": "microsoft.extensions.primitives.10.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==",
"path": "microsoft.identitymodel.abstractions/8.15.0",
"hashPath": "microsoft.identitymodel.abstractions.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==",
"path": "microsoft.identitymodel.logging/8.15.0",
"hashPath": "microsoft.identitymodel.logging.8.15.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==",
"path": "microsoft.identitymodel.tokens/8.15.0",
"hashPath": "microsoft.identitymodel.tokens.8.15.0.nupkg.sha512"
},
"StellaOps.Attestor.Envelope/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Attestor.ProofChain/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Canonical.Json/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Concelier.SourceIntel/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Cryptography/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Feedser.BinaryAnalysis/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Feedser.Core/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"StellaOps.Scanner.ChangeTrace/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -0,0 +1,418 @@
using StellaOps.Attestor.ProofChain.Predicates.AI;
using Xunit;
namespace StellaOps.Attestor.ProofChain.Tests.AI;
/// <summary>
/// Tests for AIAuthorityClassifier.
/// Sprint: SPRINT_20251226_018_AI_attestations
/// Task: AIATTEST-22
/// </summary>
public sealed class AIAuthorityClassifierTests
{
private static readonly AIModelIdentifier TestModelId = new()
{
Provider = "anthropic",
Model = "claude-3-opus",
Version = "20240229"
};
private static readonly AIDecodingParameters TestDecodingParams = new()
{
Temperature = 0.0,
Seed = 12345
};
[Fact]
public void ClassifyExplanation_HighCitationRate_ReturnsEvidenceBacked()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 0.85, confidenceScore: 0.8, verifiedRate: 0.95);
// Act
var result = classifier.ClassifyExplanation(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.EvidenceBacked, result.Authority);
Assert.True(result.QualityScore > 0.7);
}
[Fact]
public void ClassifyExplanation_LowCitationRate_ReturnsSuggestion()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 0.5, confidenceScore: 0.6, verifiedRate: 0.7);
// Act
var result = classifier.ClassifyExplanation(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
}
[Fact]
public void ClassifyExplanation_VeryHighQuality_ReturnsAuthorityThreshold()
{
// Arrange
var thresholds = new AIAuthorityThresholds { AuthorityThresholdScore = 0.9 };
var classifier = new AIAuthorityClassifier(thresholds);
var predicate = CreateExplanationPredicate(
citationRate: 0.98,
confidenceScore: 0.95,
verifiedRate: 1.0,
contentLength: 700);
// Act
var result = classifier.ClassifyExplanation(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.AuthorityThreshold, result.Authority);
Assert.True(result.CanAutoProcess);
}
[Fact]
public void ClassifyRemediationPlan_WithResolvableEvidence_ReturnsEvidenceBacked()
{
// Arrange
Func<string, bool> resolver = _ => true; // All evidence is resolvable
var classifier = new AIAuthorityClassifier(evidenceResolver: resolver);
var predicate = CreateRemediationPredicate(evidenceCount: 5, prReady: true);
// Act
var result = classifier.ClassifyRemediationPlan(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.EvidenceBacked, result.Authority);
Assert.Equal(5, result.ResolvableEvidenceCount);
Assert.Equal(0, result.UnresolvableEvidenceCount);
}
[Fact]
public void ClassifyRemediationPlan_WithUnresolvableEvidence_ReturnsSuggestion()
{
// Arrange
Func<string, bool> resolver = evidenceRef => evidenceRef.Contains("valid"); // Only some evidence is resolvable
var classifier = new AIAuthorityClassifier(evidenceResolver: resolver);
var predicate = CreateRemediationPredicate(evidenceCount: 5, prReady: false);
// Act
var result = classifier.ClassifyRemediationPlan(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
}
[Fact]
public void ClassifyVexDraft_AutoApprovable_CanAutoProcess()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreateVexDraftPredicate(
avgConfidence: 0.95,
evidenceCount: 3,
hasConflicts: false);
// Act
var result = classifier.ClassifyVexDraft(predicate);
// Assert
// Note: CanAutoProcess depends on AutoApprovable in the predicate
Assert.True(result.QualityScore > 0.5);
}
[Fact]
public void ClassifyVexDraft_UnresolvableEvidence_DropsToSuggestion()
{
// Arrange
Func<string, bool> resolver = evidenceRef => evidenceRef.EndsWith("0", StringComparison.Ordinal);
var classifier = new AIAuthorityClassifier(evidenceResolver: resolver);
var predicate = CreateVexDraftPredicate(
avgConfidence: 0.95,
evidenceCount: 3,
hasConflicts: false);
// Act
var result = classifier.ClassifyVexDraft(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
Assert.Equal(1, result.ResolvableEvidenceCount);
Assert.Equal(2, result.UnresolvableEvidenceCount);
}
[Fact]
public void ClassifyPolicyDraft_AllTestsPassed_HighQuality()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreatePolicyDraftPredicate(
avgConfidence: 0.9,
passedTestCount: 5,
totalTestCount: 5,
validationPassed: true);
// Act
var result = classifier.ClassifyPolicyDraft(predicate);
// Assert
Assert.True(result.QualityScore > 0.7);
}
[Fact]
public void ClassifyPolicyDraft_QualityAtAuthorityThreshold_ReturnsAuthorityThreshold()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreatePolicyDraftPredicate(
avgConfidence: 1.0,
passedTestCount: 5,
totalTestCount: 5,
validationPassed: true);
// Act
var result = classifier.ClassifyPolicyDraft(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.AuthorityThreshold, result.Authority);
Assert.True(result.CanAutoProcess);
}
[Fact]
public void ClassifyPolicyDraft_FailedTests_LowerQuality()
{
// Arrange
var classifier = new AIAuthorityClassifier();
var predicate = CreatePolicyDraftPredicate(
avgConfidence: 0.9,
passedTestCount: 2,
totalTestCount: 5,
validationPassed: false);
// Act
var result = classifier.ClassifyPolicyDraft(predicate);
// Assert
Assert.True(result.QualityScore < 0.7);
Assert.False(result.CanAutoProcess);
}
[Fact]
public void CustomThresholds_AreRespected()
{
// Arrange
var thresholds = new AIAuthorityThresholds
{
MinCitationRate = 0.5,
MinConfidenceScore = 0.5,
MinVerifiedCitationRate = 0.5
};
var classifier = new AIAuthorityClassifier(thresholds);
var predicate = CreateExplanationPredicate(citationRate: 0.6, confidenceScore: 0.6, verifiedRate: 0.6);
// Act
var result = classifier.ClassifyExplanation(predicate);
// Assert
Assert.Equal(AIArtifactAuthority.EvidenceBacked, result.Authority);
}
private static AIExplanationPredicate CreateExplanationPredicate(
double citationRate,
double confidenceScore,
double verifiedRate,
int contentLength = 220)
{
var totalCitations = 10;
var verifiedCitations = (int)(totalCitations * verifiedRate);
var citations = new List<AIExplanationCitation>();
for (int i = 0; i < totalCitations; i++)
{
citations.Add(new AIExplanationCitation
{
ClaimIndex = i,
ClaimText = $"Claim {i}",
EvidenceId = $"sha256:evidence{i}",
EvidenceType = "sbom",
Verified = i < verifiedCitations
});
}
return new AIExplanationPredicate
{
ArtifactId = "sha256:test123",
ModelId = TestModelId,
PromptTemplateVersion = "explanation@v1",
DecodingParams = TestDecodingParams,
InputHashes = ["sha256:input1"],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2025-12-26T00:00:00Z",
OutputHash = "sha256:output1",
ExplanationType = AIExplanationType.Exploitability,
Content = new string('x', contentLength),
Citations = citations,
ConfidenceScore = confidenceScore,
CitationRate = citationRate,
Subject = "CVE-2025-1234"
};
}
private static AIRemediationPlanPredicate CreateRemediationPredicate(int evidenceCount, bool prReady)
{
var evidenceRefs = new List<string>();
for (int i = 0; i < evidenceCount; i++)
{
evidenceRefs.Add($"sha256:evidence{i}");
}
return new AIRemediationPlanPredicate
{
ArtifactId = "sha256:test123",
ModelId = TestModelId,
PromptTemplateVersion = "remediation@v1",
DecodingParams = TestDecodingParams,
InputHashes = ["sha256:input1"],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2025-12-26T00:00:00Z",
OutputHash = "sha256:output1",
VulnerabilityId = "CVE-2025-1234",
AffectedComponent = "pkg:npm/example@1.0.0",
Steps =
[
new RemediationStep
{
Order = 1,
ActionType = RemediationActionType.PackageUpgrade,
Description = "Upgrade package",
Target = "pkg:npm/example@1.0.0",
ProposedValue = "1.0.1",
RiskReduction = 0.8,
CanAutomate = true
}
],
ExpectedDelta = 0.7,
RiskAssessment = new RemediationRiskAssessment
{
RiskBefore = 0.9,
RiskAfter = 0.2,
BreakingChanges = []
},
VerificationStatus = RemediationVerificationStatus.Verified,
PrReady = prReady,
EvidenceRefs = evidenceRefs
};
}
private static AIVexDraftPredicate CreateVexDraftPredicate(
double avgConfidence,
int evidenceCount,
bool hasConflicts)
{
var evidenceRefs = new List<string>();
for (int i = 0; i < evidenceCount; i++)
{
evidenceRefs.Add($"sha256:evidence{i}");
}
return new AIVexDraftPredicate
{
ArtifactId = "sha256:test123",
ModelId = TestModelId,
PromptTemplateVersion = "vexdraft@v1",
DecodingParams = TestDecodingParams,
InputHashes = ["sha256:input1"],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2025-12-26T00:00:00Z",
OutputHash = "sha256:output1",
VexStatements =
[
new AIVexStatementDraft
{
VulnerabilityId = "CVE-2025-1234",
ProductId = "pkg:npm/example@1.0.0",
Status = "not_affected",
Justification = "vulnerable_code_not_in_execute_path",
Confidence = avgConfidence,
SupportingEvidence = evidenceRefs
}
],
Justifications =
[
new AIVexJustification
{
StatementIndex = 0,
Reasoning = "Code path analysis shows function is never called",
EvidencePoints = ["Reachability analysis", "Call graph"],
ConflictsWithExisting = hasConflicts
}
],
EvidenceRefs = evidenceRefs,
TargetFormat = "openvex",
AutoApprovable = !hasConflicts && avgConfidence > 0.9,
Scope = "image",
ScopeId = "sha256:image123"
};
}
private static AIPolicyDraftPredicate CreatePolicyDraftPredicate(
double avgConfidence,
int passedTestCount,
int totalTestCount,
bool validationPassed)
{
var testCases = new List<PolicyRuleTestCase>();
for (int i = 0; i < totalTestCount; i++)
{
testCases.Add(new PolicyRuleTestCase
{
TestId = $"test-{i}",
RuleId = "rule-1",
Description = $"Test case {i}",
Input = "{}",
ExpectedOutcome = "pass",
Passed = i < passedTestCount
});
}
return new AIPolicyDraftPredicate
{
ArtifactId = "sha256:test123",
ModelId = TestModelId,
PromptTemplateVersion = "policydraft@v1",
DecodingParams = TestDecodingParams,
InputHashes = ["sha256:input1"],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2025-12-26T00:00:00Z",
OutputHash = "sha256:output1",
NaturalLanguageInput = "Block critical CVEs in production",
Rules =
[
new AIPolicyRuleDraft
{
RuleId = "rule-1",
RuleType = PolicyRuleType.Gate,
Name = "Block Critical CVEs",
Description = "Block deployments with critical vulnerabilities",
Condition = "severity == 'critical' && environment == 'prod'",
Action = "block",
Priority = 100,
OriginalInput = "Block critical CVEs in production",
Confidence = avgConfidence
}
],
TestCases = testCases,
ValidationResult = new PolicyValidationResult
{
SyntaxValid = true,
SemanticsValid = validationPassed,
OverallPassed = validationPassed
},
TargetPolicyPack = "default",
TargetVersion = "1.0.0",
DetectedIntents = ["gate", "severity-filter", "environment-scope"],
DeployReady = validationPassed
};
}
}

View File

@@ -0,0 +1,205 @@
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Attestor.ProofChain.Identifiers;
using StellaOps.Attestor.ProofChain.Predicates.AI;
using StellaOps.Attestor.ProofChain.Verification;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.AI;
public sealed class AIExplanationAndVerificationBehaviorTests
{
private static readonly AIModelIdentifier ModelId = new()
{
Provider = "anthropic",
Model = "claude-3-opus",
Version = "20240229"
};
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyExplanation_HighCitationAndVerifiedRate_ReturnsEvidenceBacked()
{
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 0.85, confidenceScore: 0.80, verifiedCount: 9, totalCitations: 10, contentLength: 220);
var result = classifier.ClassifyExplanation(predicate);
Assert.Equal(AIArtifactAuthority.EvidenceBacked, result.Authority);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyExplanation_LowCitationRate_ReturnsSuggestion()
{
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 0.40, confidenceScore: 0.90, verifiedCount: 10, totalCitations: 10, contentLength: 220);
var result = classifier.ClassifyExplanation(predicate);
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyExplanation_HighQualityScore_ReturnsAuthorityThreshold()
{
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 1.0, confidenceScore: 1.0, verifiedCount: 10, totalCitations: 10, contentLength: 700);
var result = classifier.ClassifyExplanation(predicate);
Assert.Equal(AIArtifactAuthority.AuthorityThreshold, result.Authority);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyExplanation_UnverifiedCitationsBelowThreshold_ReturnsSuggestion()
{
var classifier = new AIAuthorityClassifier();
var predicate = CreateExplanationPredicate(citationRate: 0.90, confidenceScore: 0.95, verifiedCount: 5, totalCitations: 10, contentLength: 220);
var result = classifier.ClassifyExplanation(predicate);
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIModelIdentifier_ToString_UsesCanonicalProviderModelVersionFormat()
{
var model = new AIModelIdentifier
{
Provider = "openai",
Model = "gpt-4.1",
Version = "2026-01-01"
};
Assert.Equal("openai:gpt-4.1:2026-01-01", model.ToString());
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExecuteAsync_WithValidAIExplanationStatement_PassesAndStoresResults()
{
var bundleId = new ProofBundleId(new string('a', 64));
var predicate = CreateExplanationPredicate(citationRate: 0.90, confidenceScore: 0.90, verifiedCount: 10, totalCitations: 10, contentLength: 300);
var statement = new ProofStatement
{
StatementId = "stmt-1",
PredicateType = "ai-explanation.stella/v1",
Predicate = predicate
};
var store = new StubProofBundleStore(new ProofBundle
{
Statements = [statement],
Envelopes = []
});
var step = new AIArtifactVerificationStep(store, NullLogger<AIArtifactVerificationStep>.Instance);
var context = new VerificationContext
{
ProofBundleId = bundleId,
VerifyRekor = false
};
var result = await step.ExecuteAsync(context);
var artifacts = context.GetData<List<AIArtifactVerificationResult>>("aiArtifactResults");
Assert.True(result.Passed);
Assert.NotNull(artifacts);
Assert.Single(artifacts!);
Assert.True(artifacts[0].IsValid);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExecuteAsync_WithMalformedAIPredicate_FailsVerification()
{
var bundleId = new ProofBundleId(new string('b', 64));
var statement = new ProofStatement
{
StatementId = "stmt-2",
PredicateType = "ai-explanation.stella/v1",
Predicate = new
{
// Missing required explanation predicate fields on purpose.
foo = "bar"
}
};
var store = new StubProofBundleStore(new ProofBundle
{
Statements = [statement],
Envelopes = []
});
var step = new AIArtifactVerificationStep(store, NullLogger<AIArtifactVerificationStep>.Instance);
var context = new VerificationContext
{
ProofBundleId = bundleId,
VerifyRekor = false
};
var result = await step.ExecuteAsync(context);
Assert.False(result.Passed);
Assert.Contains("failed", result.Details ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
private static AIExplanationPredicate CreateExplanationPredicate(
double citationRate,
double confidenceScore,
int verifiedCount,
int totalCitations,
int contentLength)
{
var citations = new List<AIExplanationCitation>(capacity: totalCitations);
for (var i = 0; i < totalCitations; i++)
{
citations.Add(new AIExplanationCitation
{
ClaimIndex = i,
ClaimText = $"Claim {i}",
EvidenceId = $"sha256:{new string('c', 64)}",
EvidenceType = "sbom",
Verified = i < verifiedCount
});
}
return new AIExplanationPredicate
{
ArtifactId = $"sha256:{new string('d', 64)}",
ModelId = ModelId,
PromptTemplateVersion = "explanation@v1",
DecodingParams = new AIDecodingParameters
{
Temperature = 0.0,
Seed = 12345
},
InputHashes = [$"sha256:{new string('e', 64)}"],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2026-02-11T00:00:00Z",
OutputHash = $"sha256:{new string('f', 64)}",
ExplanationType = AIExplanationType.Exploitability,
Content = new string('x', contentLength),
Citations = citations,
ConfidenceScore = confidenceScore,
CitationRate = citationRate,
Subject = "CVE-2026-0001"
};
}
private sealed class StubProofBundleStore : IProofBundleStore
{
private readonly ProofBundle bundle;
public StubProofBundleStore(ProofBundle bundle)
{
this.bundle = bundle;
}
public Task<ProofBundle?> GetBundleAsync(ProofBundleId bundleId, CancellationToken ct = default)
{
return Task.FromResult<ProofBundle?>(bundle);
}
}
}

View File

@@ -0,0 +1,276 @@
using System.Text.Json;
using StellaOps.Attestor.ProofChain.MediaTypes;
using StellaOps.Attestor.ProofChain.Predicates.AI;
using StellaOps.Attestor.ProofChain.Replay;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.Attestor.ProofChain.Statements.AI;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.AI;
public sealed class AIExplanationAttestationTypesBehaviorTests
{
[Trait("Intent", "Regulatory")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationPredicate_SerializesWithExpectedContractFields()
{
var predicate = CreatePredicate(AIExplanationType.PolicyDecision);
var json = JsonSerializer.Serialize(predicate);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
Assert.Equal("PolicyDecision", root.GetProperty("explanationType").GetString());
Assert.Equal(predicate.Content, root.GetProperty("content").GetString());
Assert.Equal(predicate.ConfidenceScore, root.GetProperty("confidenceScore").GetDouble(), 6);
Assert.Equal(predicate.CitationRate, root.GetProperty("citationRate").GetDouble(), 6);
Assert.Equal(predicate.Subject, root.GetProperty("subject").GetString());
Assert.Equal(predicate.ContextScope, root.GetProperty("contextScope").GetString());
Assert.Equal(predicate.ArtifactId, root.GetProperty("artifactId").GetString());
Assert.Equal(predicate.PromptTemplateVersion, root.GetProperty("promptTemplateVersion").GetString());
Assert.Equal(predicate.GeneratedAt, root.GetProperty("generatedAt").GetString());
Assert.Equal(predicate.OutputHash, root.GetProperty("outputHash").GetString());
var modelId = root.GetProperty("modelId");
Assert.Equal("openai", modelId.GetProperty("provider").GetString());
Assert.Equal("gpt-4.1", modelId.GetProperty("model").GetString());
Assert.Equal("2026-02-01", modelId.GetProperty("version").GetString());
var decodingParams = root.GetProperty("decodingParams");
Assert.Equal(0.0, decodingParams.GetProperty("temperature").GetDouble(), 6);
Assert.Equal(0.95, decodingParams.GetProperty("topP").GetDouble(), 6);
Assert.Equal(16, decodingParams.GetProperty("topK").GetInt32());
Assert.Equal(1024, decodingParams.GetProperty("maxTokens").GetInt32());
Assert.Equal(12345L, decodingParams.GetProperty("seed").GetInt64());
var citations = root.GetProperty("citations");
Assert.Equal(5, citations.GetArrayLength());
Assert.Contains(
citations.EnumerateArray().Select(c => c.GetProperty("verified").GetBoolean()),
verified => verified == false);
}
[Trait("Intent", "Safety")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationPredicate_RoundTripSerialization_PreservesFields()
{
var original = CreatePredicate(AIExplanationType.EvidenceChain);
var json = JsonSerializer.Serialize(original);
var roundTripped = JsonSerializer.Deserialize<AIExplanationPredicate>(json);
Assert.NotNull(roundTripped);
Assert.Equal(original.ExplanationType, roundTripped!.ExplanationType);
Assert.Equal(original.Content, roundTripped.Content);
Assert.Equal(original.ConfidenceScore, roundTripped.ConfidenceScore, 6);
Assert.Equal(original.CitationRate, roundTripped.CitationRate, 6);
Assert.Equal(original.Subject, roundTripped.Subject);
Assert.Equal(original.ContextScope, roundTripped.ContextScope);
Assert.Equal(original.ArtifactId, roundTripped.ArtifactId);
Assert.Equal(original.ModelId, roundTripped.ModelId);
Assert.Equal(original.DecodingParams, roundTripped.DecodingParams);
Assert.Equal(original.InputHashes, roundTripped.InputHashes);
Assert.Equal(original.Citations, roundTripped.Citations);
}
[Trait("Intent", "Operational")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationType_EnumValues_SerializeAndDeserializeAsStableStrings()
{
var expectedNames = Enum.GetNames<AIExplanationType>();
foreach (var explanationType in Enum.GetValues<AIExplanationType>())
{
var json = JsonSerializer.Serialize(explanationType);
var serialized = JsonSerializer.Deserialize<string>(json);
var deserialized = JsonSerializer.Deserialize<AIExplanationType>(json);
Assert.Contains(serialized, expectedNames);
Assert.Equal(explanationType, deserialized);
}
}
[Trait("Intent", "Safety")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationCitation_MixedVerification_ProducesDeterministicCitationRate()
{
var citations = CreateCitations(total: 5, verified: 3);
var citationRate = citations.Count(c => c.Verified) / (double)citations.Count;
var predicate = CreatePredicate(AIExplanationType.Exploitability, citations, citationRate);
Assert.Equal(0.6, citationRate, 6);
Assert.Equal(citationRate, predicate.CitationRate, 6);
}
[Trait("Intent", "Regulatory")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationStatement_SerializesAsInTotoStatementWithExplanationPredicateType()
{
var statement = new AIExplanationStatement
{
Subject =
[
new Subject
{
Name = "pkg:oci/stellaops/image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Digest = new Dictionary<string, string>
{
["sha256"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
],
Predicate = CreatePredicate(AIExplanationType.RiskFactors)
};
var json = JsonSerializer.Serialize(statement);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
Assert.Equal("https://in-toto.io/Statement/v1", root.GetProperty("_type").GetString());
Assert.Equal("ai-explanation.stella/v1", root.GetProperty("predicateType").GetString());
Assert.Equal("RiskFactors", root.GetProperty("predicate").GetProperty("explanationType").GetString());
Assert.Equal(1, root.GetProperty("subject").GetArrayLength());
}
[Trait("Intent", "Operational")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIArtifactReplayManifest_FromExplanationPredicate_PreservesModelAndDecodingInputs()
{
var predicate = CreatePredicate(AIExplanationType.PlainLanguageSummary);
var manifest = CreateReplayManifest(predicate);
Assert.Equal(predicate.ArtifactId, manifest.ArtifactId);
Assert.Equal(predicate.ModelId, manifest.ModelId);
Assert.Equal(predicate.DecodingParams, manifest.DecodingParams);
Assert.Equal(predicate.GeneratedAt, manifest.GeneratedAt);
Assert.Equal(predicate.OutputHash, manifest.ExpectedOutputHash);
Assert.True(manifest.Replayable);
Assert.Equal(2, manifest.Inputs.Count);
}
[Trait("Intent", "Safety")]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIArtifactMediaTypes_ExplanationPredicateType_MappingIsBidirectional()
{
var mediaType = AIArtifactMediaTypes.GetMediaTypeForPredicateType("ai-explanation.stella/v1");
var predicateType = AIArtifactMediaTypes.GetPredicateTypeForMediaType(AIArtifactMediaTypes.AIExplanation);
Assert.Equal(AIArtifactMediaTypes.AIExplanation, mediaType);
Assert.Equal("ai-explanation.stella/v1", predicateType);
Assert.True(AIArtifactMediaTypes.IsAIArtifactMediaType(AIArtifactMediaTypes.AIReplayManifest));
}
private static AIExplanationPredicate CreatePredicate(
AIExplanationType explanationType,
IReadOnlyList<AIExplanationCitation>? citations = null,
double? citationRate = null)
{
var items = citations ?? CreateCitations(total: 5, verified: 4);
var rate = citationRate ?? (items.Count(c => c.Verified) / (double)items.Count);
return new AIExplanationPredicate
{
ArtifactId = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
ModelId = new AIModelIdentifier
{
Provider = "openai",
Model = "gpt-4.1",
Version = "2026-02-01",
WeightsDigest = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
PromptTemplateVersion = "explain@v3",
DecodingParams = new AIDecodingParameters
{
Temperature = 0.0,
TopP = 0.95,
TopK = 16,
MaxTokens = 1024,
Seed = 12345
},
InputHashes =
[
"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
],
Authority = AIArtifactAuthority.EvidenceBacked,
GeneratedAt = "2026-02-11T12:00:00Z",
OutputHash = "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
ExplanationType = explanationType,
Content = "Evidence-backed explanation content.",
Citations = items,
ConfidenceScore = 0.92,
CitationRate = rate,
Subject = "CVE-2026-12345",
ContextScope = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
};
}
private static IReadOnlyList<AIExplanationCitation> CreateCitations(int total, int verified)
{
var citations = new List<AIExplanationCitation>(capacity: total);
for (var i = 0; i < total; i++)
{
citations.Add(new AIExplanationCitation
{
ClaimIndex = i,
ClaimText = $"Claim {i}",
EvidenceId = $"sha256:{new string((char)('a' + i), 64)}",
EvidenceType = i % 2 == 0 ? "sbom" : "vex",
Verified = i < verified
});
}
return citations;
}
private static AIArtifactReplayManifest CreateReplayManifest(AIExplanationPredicate predicate)
{
return new AIArtifactReplayManifest
{
ManifestId = "replay:ai-expl-0001",
ArtifactId = predicate.ArtifactId,
ArtifactType = "explanation",
ModelId = predicate.ModelId,
DecodingParams = predicate.DecodingParams,
PromptTemplate = new ReplayPromptTemplate
{
Name = "explain",
Version = predicate.PromptTemplateVersion,
Hash = "sha256:1212121212121212121212121212121212121212121212121212121212121212",
Location = "oci://stellaops/prompts/explain@v3"
},
Inputs =
[
new ReplayInputArtifact
{
Hash = "sha256:0202020202020202020202020202020202020202020202020202020202020202",
Type = "sbom",
MediaType = "application/spdx+json",
Size = 4096,
Location = "cas://sbom/0101",
Order = 0
},
new ReplayInputArtifact
{
Hash = "sha256:0404040404040404040404040404040404040404040404040404040404040404",
Type = "reachability",
MediaType = "application/json",
Size = 2048,
Location = "cas://reach/0303",
Order = 1
}
],
ExpectedOutputHash = predicate.OutputHash,
GeneratedAt = predicate.GeneratedAt,
Replayable = true
};
}
}

View File

@@ -0,0 +1,242 @@
using System.Text.Json;
using StellaOps.Attestor.ProofChain.Predicates.AI;
using StellaOps.Attestor.ProofChain.Replay;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.Attestor.ProofChain.Statements.AI;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.AI;
public sealed class AIExplanationAttestationTypesTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationPredicate_Serialization_UsesExpectedContractFieldNames()
{
var predicate = CreateExplanationPredicate();
var json = JsonSerializer.Serialize(predicate);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.True(root.TryGetProperty("artifactId", out _));
Assert.True(root.TryGetProperty("modelId", out var modelId));
Assert.True(modelId.TryGetProperty("provider", out _));
Assert.True(modelId.TryGetProperty("model", out _));
Assert.True(modelId.TryGetProperty("version", out _));
Assert.True(root.TryGetProperty("decodingParams", out var decoding));
Assert.True(decoding.TryGetProperty("temperature", out _));
Assert.True(decoding.TryGetProperty("seed", out _));
Assert.True(root.TryGetProperty("explanationType", out var explanationType));
Assert.Equal("Exploitability", explanationType.GetString());
Assert.True(root.TryGetProperty("content", out _));
Assert.True(root.TryGetProperty("citations", out var citations));
Assert.Equal(JsonValueKind.Array, citations.ValueKind);
Assert.True(root.TryGetProperty("confidenceScore", out _));
Assert.True(root.TryGetProperty("citationRate", out _));
Assert.True(root.TryGetProperty("subject", out _));
Assert.True(root.TryGetProperty("contextScope", out _));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationPredicate_Serialization_RoundTrips()
{
var original = CreateExplanationPredicate();
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<AIExplanationPredicate>(json);
Assert.NotNull(deserialized);
Assert.Equal(original.ArtifactId, deserialized!.ArtifactId);
Assert.Equal(original.ModelId.Provider, deserialized.ModelId.Provider);
Assert.Equal(original.ModelId.Model, deserialized.ModelId.Model);
Assert.Equal(original.ModelId.Version, deserialized.ModelId.Version);
Assert.Equal(original.DecodingParams.Temperature, deserialized.DecodingParams.Temperature);
Assert.Equal(original.DecodingParams.Seed, deserialized.DecodingParams.Seed);
Assert.Equal(original.ExplanationType, deserialized.ExplanationType);
Assert.Equal(original.Content, deserialized.Content);
Assert.Equal(original.Citations.Count, deserialized.Citations.Count);
Assert.Equal(original.Citations[0].EvidenceId, deserialized.Citations[0].EvidenceId);
Assert.Equal(original.ConfidenceScore, deserialized.ConfidenceScore);
Assert.Equal(original.CitationRate, deserialized.CitationRate);
Assert.Equal(original.Subject, deserialized.Subject);
Assert.Equal(original.ContextScope, deserialized.ContextScope);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIArtifactReplayManifest_Serialization_CapturesModelAndDecodingParameters()
{
var predicate = CreateExplanationPredicate();
var manifest = new AIArtifactReplayManifest
{
ManifestId = "manifest-ai-explanation-001",
ArtifactId = predicate.ArtifactId,
ArtifactType = "explanation",
ModelId = predicate.ModelId,
DecodingParams = predicate.DecodingParams,
PromptTemplate = new ReplayPromptTemplate
{
Name = "explanation",
Version = "v1",
Hash = "sha256:1111111111111111111111111111111111111111111111111111111111111111",
Location = "oci://stellaops/prompt/explanation:v1"
},
Inputs =
[
new ReplayInputArtifact
{
Hash = "sha256:2222222222222222222222222222222222222222222222222222222222222222",
Type = "sbom",
MediaType = "application/spdx+json",
Size = 2048,
Location = "blob://sbom/1",
Order = 0
}
],
ExpectedOutputHash = predicate.OutputHash,
GeneratedAt = predicate.GeneratedAt,
Replayable = true
};
var json = JsonSerializer.Serialize(manifest);
var parsed = JsonSerializer.Deserialize<AIArtifactReplayManifest>(json);
Assert.NotNull(parsed);
Assert.Equal(predicate.ModelId, parsed!.ModelId);
Assert.Equal(predicate.DecodingParams, parsed.DecodingParams);
Assert.Equal("explanation", parsed.ArtifactType);
Assert.True(parsed.Replayable);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationCitation_MixedVerifiedState_SupportsCitationRateAccounting()
{
var citations = new List<AIExplanationCitation>
{
new()
{
ClaimIndex = 0,
ClaimText = "Claim 0",
EvidenceId = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
EvidenceType = "sbom",
Verified = true
},
new()
{
ClaimIndex = 1,
ClaimText = "Claim 1",
EvidenceId = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
EvidenceType = "vex",
Verified = false
},
new()
{
ClaimIndex = 2,
ClaimText = "Claim 2",
EvidenceId = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
EvidenceType = "reachability",
Verified = true
}
};
var verifiedCount = citations.Count(c => c.Verified);
var citationRate = (double)verifiedCount / citations.Count;
Assert.Equal(2, verifiedCount);
Assert.Equal(2d / 3d, citationRate, 4);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationType_Serialization_UsesStringValues_ForAllMembers()
{
foreach (var explanationType in Enum.GetValues<AIExplanationType>())
{
var json = JsonSerializer.Serialize(new { explanationType });
using var document = JsonDocument.Parse(json);
var serializedValue = document.RootElement.GetProperty("explanationType").GetString();
Assert.Equal(explanationType.ToString(), serializedValue);
}
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIExplanationStatement_UsesExpectedPredicateType_AndInTotoStatementShape()
{
var statement = new AIExplanationStatement
{
Subject =
[
new Subject
{
Name = "pkg:oci/stellaops/app@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
Digest = new Dictionary<string, string>
{
["sha256"] = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
}
],
Predicate = CreateExplanationPredicate()
};
var json = JsonSerializer.Serialize(statement);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.Equal("ai-explanation.stella/v1", statement.PredicateType);
Assert.Equal("https://in-toto.io/Statement/v1", root.GetProperty("_type").GetString());
Assert.Equal("ai-explanation.stella/v1", root.GetProperty("predicateType").GetString());
Assert.True(root.TryGetProperty("predicate", out var predicate));
Assert.Equal("Exploitability", predicate.GetProperty("explanationType").GetString());
}
private static AIExplanationPredicate CreateExplanationPredicate()
{
return new AIExplanationPredicate
{
ArtifactId = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ModelId = new AIModelIdentifier
{
Provider = "openai",
Model = "gpt-4.1",
Version = "2026-01-01"
},
PromptTemplateVersion = "explanation@v1",
DecodingParams = new AIDecodingParameters
{
Temperature = 0.0,
TopP = 0.95,
MaxTokens = 1024,
Seed = 424242
},
InputHashes =
[
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
],
Authority = AIArtifactAuthority.EvidenceBacked,
GeneratedAt = "2026-02-11T12:00:00Z",
OutputHash = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
ExplanationType = AIExplanationType.Exploitability,
Content = "Evidence-backed exploitability explanation.",
Citations =
[
new AIExplanationCitation
{
ClaimIndex = 0,
ClaimText = "Reachability confirms vulnerable path.",
EvidenceId = "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
EvidenceType = "reachability",
Verified = true
}
],
ConfidenceScore = 0.92,
CitationRate = 1.0,
Subject = "CVE-2026-12345",
ContextScope = "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
};
}
}

View File

@@ -0,0 +1,222 @@
using System.Text.Json;
using StellaOps.Attestor.ProofChain.Predicates.AI;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.Attestor.ProofChain.Statements.AI;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.AI;
public sealed class AIRemediationPlanAttestationBehaviorTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIRemediationPlanPredicate_Serialization_UsesExpectedContractFields()
{
var predicate = CreateRemediationPredicate();
var json = JsonSerializer.Serialize(predicate);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.True(root.TryGetProperty("vulnerabilityId", out _));
Assert.True(root.TryGetProperty("affectedComponent", out _));
Assert.True(root.TryGetProperty("steps", out var steps));
Assert.Equal(JsonValueKind.Array, steps.ValueKind);
Assert.Equal(3, steps.GetArrayLength());
Assert.Equal("PackageUpgrade", steps[0].GetProperty("actionType").GetString());
Assert.Equal("ConfigurationChange", steps[1].GetProperty("actionType").GetString());
Assert.Equal("CompensatingControl", steps[2].GetProperty("actionType").GetString());
Assert.True(root.TryGetProperty("riskAssessment", out var riskAssessment));
Assert.True(riskAssessment.TryGetProperty("riskBefore", out _));
Assert.True(riskAssessment.TryGetProperty("riskAfter", out _));
Assert.True(root.TryGetProperty("verificationStatus", out var verificationStatus));
Assert.Equal("Verified", verificationStatus.GetString());
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIRemediationPlanPredicate_Serialization_RoundTripsRiskAndVerificationStatus()
{
var original = CreateRemediationPredicate();
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<AIRemediationPlanPredicate>(json);
Assert.NotNull(deserialized);
Assert.Equal(0.9, deserialized!.RiskAssessment.RiskBefore);
Assert.Equal(0.2, deserialized.RiskAssessment.RiskAfter);
Assert.Equal(RemediationVerificationStatus.Verified, deserialized.VerificationStatus);
Assert.Equal(3, deserialized.Steps.Count);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyRemediationPlan_HighResolvableEvidence_ReturnsEvidenceBacked()
{
Func<string, bool> resolver = _ => true;
var classifier = new AIAuthorityClassifier(evidenceResolver: resolver);
var predicate = CreateRemediationPredicate() with
{
RiskAssessment = new RemediationRiskAssessment
{
RiskBefore = 0.92,
RiskAfter = 0.18,
BreakingChanges = ["Config format changed"],
RequiredTestCoverage = ["integration:release-validation", "e2e:smoke"]
}
};
var result = classifier.ClassifyRemediationPlan(predicate);
Assert.Equal(AIArtifactAuthority.EvidenceBacked, result.Authority);
Assert.Equal(predicate.EvidenceRefs.Count, result.ResolvableEvidenceCount);
Assert.Equal(0, result.UnresolvableEvidenceCount);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ClassifyRemediationPlan_LowEvidenceBacking_ReturnsSuggestion()
{
Func<string, bool> resolver = evidenceRef => evidenceRef.EndsWith("0", StringComparison.Ordinal);
var classifier = new AIAuthorityClassifier(evidenceResolver: resolver);
var predicate = CreateRemediationPredicate();
var result = classifier.ClassifyRemediationPlan(predicate);
Assert.Equal(AIArtifactAuthority.Suggestion, result.Authority);
Assert.Equal(1, result.ResolvableEvidenceCount);
Assert.Equal(3, result.UnresolvableEvidenceCount);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AIRemediationPlanStatement_UsesInTotoShapeAndPredicateType()
{
var statement = new AIRemediationPlanStatement
{
Subject =
[
new Subject
{
Name = "pkg:oci/stellaops/app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Digest = new Dictionary<string, string>
{
["sha256"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
],
Predicate = CreateRemediationPredicate()
};
var json = JsonSerializer.Serialize(statement);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.Equal("https://in-toto.io/Statement/v1", root.GetProperty("_type").GetString());
Assert.Equal("ai-remediation.stella/v1", root.GetProperty("predicateType").GetString());
Assert.True(root.TryGetProperty("predicate", out var predicate));
Assert.Equal("CVE-2026-1001", predicate.GetProperty("vulnerabilityId").GetString());
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RemediationStepStatus_AndVerificationStatus_PersistThroughSerialization()
{
var predicate = CreateRemediationPredicate() with
{
Steps =
[
CreateStep(1, RemediationActionType.PackageUpgrade, RemediationStepStatus.Pending),
CreateStep(2, RemediationActionType.ConfigurationChange, RemediationStepStatus.InProgress),
CreateStep(3, RemediationActionType.CompensatingControl, RemediationStepStatus.Complete)
],
VerificationStatus = RemediationVerificationStatus.Applied
};
var json = JsonSerializer.Serialize(predicate);
using var document = JsonDocument.Parse(json);
var steps = document.RootElement.GetProperty("steps");
Assert.Equal("Pending", steps[0].GetProperty("status").GetString());
Assert.Equal("InProgress", steps[1].GetProperty("status").GetString());
Assert.Equal("Complete", steps[2].GetProperty("status").GetString());
Assert.Equal("Applied", document.RootElement.GetProperty("verificationStatus").GetString());
}
private static AIRemediationPlanPredicate CreateRemediationPredicate()
{
return new AIRemediationPlanPredicate
{
ArtifactId = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ModelId = new AIModelIdentifier
{
Provider = "openai",
Model = "gpt-4.1",
Version = "2026-01-01"
},
PromptTemplateVersion = "remediation@v1",
DecodingParams = new AIDecodingParameters
{
Temperature = 0.0,
TopP = 0.9,
MaxTokens = 2048,
Seed = 7001
},
InputHashes =
[
"sha256:1111111111111111111111111111111111111111111111111111111111111111",
"sha256:2222222222222222222222222222222222222222222222222222222222222222"
],
Authority = AIArtifactAuthority.Suggestion,
GeneratedAt = "2026-02-11T12:30:00Z",
OutputHash = "sha256:3333333333333333333333333333333333333333333333333333333333333333",
VulnerabilityId = "CVE-2026-1001",
AffectedComponent = "pkg:nuget/Example.Component@1.2.3",
Steps =
[
CreateStep(1, RemediationActionType.PackageUpgrade, RemediationStepStatus.Pending),
CreateStep(2, RemediationActionType.ConfigurationChange, RemediationStepStatus.InProgress),
CreateStep(3, RemediationActionType.CompensatingControl, RemediationStepStatus.Complete)
],
ExpectedDelta = 0.68,
RiskAssessment = new RemediationRiskAssessment
{
RiskBefore = 0.9,
RiskAfter = 0.2,
BreakingChanges = ["Config format changed"],
RequiredTestCoverage = ["integration:release-validation", "e2e:smoke"]
},
VerificationStatus = RemediationVerificationStatus.Verified,
PrReady = true,
FixBranchCommit = "9d7f1ab",
EvidenceRefs =
[
"sha256:evidence0",
"sha256:evidence1",
"sha256:evidence2",
"sha256:evidence3"
]
};
}
private static RemediationStep CreateStep(
int order,
RemediationActionType actionType,
RemediationStepStatus status)
{
return new RemediationStep
{
Order = order,
ActionType = actionType,
Description = $"Step {order}",
Target = $"target-{order}",
CurrentValue = "old",
ProposedValue = "new",
RiskReduction = 0.2 * order,
CanAutomate = true,
AutomationScript = $"stella remediate --step {order}",
Status = status,
EvidenceRefs = [$"sha256:step{order}"]
};
}
}

View File

@@ -7,12 +7,13 @@ namespace StellaOps.Attestor.ProofChain.Tests;
public sealed class BackportProofGeneratorTests
{
private static readonly DateTimeOffset FixedTime = new(2026, 01, 02, 12, 45, 30, TimeSpan.Zero);
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Unknown_UsesTimeProviderForTimestamps()
{
var fixedTime = new DateTimeOffset(2026, 01, 02, 12, 45, 30, TimeSpan.Zero);
var timeProvider = new FixedTimeProvider(fixedTime);
var timeProvider = new FixedTimeProvider(FixedTime);
var proof = BackportProofGenerator.Unknown(
"CVE-2026-0001",
@@ -21,10 +22,83 @@ public sealed class BackportProofGeneratorTests
Array.Empty<ProofEvidence>(),
timeProvider);
proof.CreatedAt.Should().Be(fixedTime);
proof.CreatedAt.Should().Be(FixedTime);
proof.SnapshotId.Should().Be("20260102-124530-UTC");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CombineEvidence_UsesStrengthHierarchyForConfidence()
{
var timeProvider = new FixedTimeProvider(FixedTime);
var authoritative = BackportProofGenerator.CombineEvidence(
"CVE-2026-1000",
"pkg:npm/sample@1.0.0",
[CreateEvidence(EvidenceType.DistroAdvisory, "authority-feed")],
timeProvider);
var binary = BackportProofGenerator.CombineEvidence(
"CVE-2026-1000",
"pkg:npm/sample@1.0.0",
[CreateEvidence(EvidenceType.BuildCatalog, "catalog-feed")],
timeProvider);
var staticAnalysis = BackportProofGenerator.CombineEvidence(
"CVE-2026-1000",
"pkg:npm/sample@1.0.0",
[CreateEvidence(EvidenceType.PatchHeader, "patch-feed")],
timeProvider);
var heuristic = BackportProofGenerator.CombineEvidence(
"CVE-2026-1000",
"pkg:npm/sample@1.0.0",
[CreateEvidence(EvidenceType.BinaryFingerprint, "binary-feed")],
timeProvider);
authoritative.Confidence.Should().BeGreaterThan(binary.Confidence);
binary.Confidence.Should().BeGreaterThan(staticAnalysis.Confidence);
staticAnalysis.Confidence.Should().BeGreaterThan(heuristic.Confidence);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CombineEvidence_MultipleIndependentSources_IncreaseConfidenceWithinCap()
{
var timeProvider = new FixedTimeProvider(FixedTime);
var single = BackportProofGenerator.CombineEvidence(
"CVE-2026-1001",
"pkg:npm/sample@1.0.0",
[CreateEvidence(EvidenceType.BinaryFingerprint, "binary-feed")],
timeProvider);
var corroborated = BackportProofGenerator.CombineEvidence(
"CVE-2026-1001",
"pkg:npm/sample@1.0.0",
[
CreateEvidence(EvidenceType.BinaryFingerprint, "binary-feed"),
CreateEvidence(EvidenceType.PatchHeader, "patch-feed"),
CreateEvidence(EvidenceType.DistroAdvisory, "authority-feed")
],
timeProvider);
corroborated.Confidence.Should().BeGreaterThan(single.Confidence);
corroborated.Confidence.Should().BeLessThanOrEqualTo(0.98);
}
private static ProofEvidence CreateEvidence(EvidenceType type, string source)
{
return new ProofEvidence
{
EvidenceId = $"evidence:{source}:{type}",
Type = type,
Source = source,
Timestamp = FixedTime,
Data = System.Text.Json.JsonDocument.Parse("{\"source\":\"fixture\"}").RootElement.Clone(),
DataHash = "sha256:fixture"
};
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _utcNow;

View File

@@ -0,0 +1,157 @@
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.Attestor.ProofChain.ChangeTrace;
using StellaOps.Attestor.ProofChain.Signing;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.Scanner.ChangeTrace.Models;
using StellaOps.TestKit;
using ChangeTraceModel = StellaOps.Scanner.ChangeTrace.Models.ChangeTrace;
namespace StellaOps.Attestor.ProofChain.Tests.ChangeTrace;
public sealed class ChangeTraceAttestationServiceTests
{
private static readonly DateTimeOffset FixedTime = new(2026, 02, 11, 12, 0, 0, TimeSpan.Zero);
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task GenerateAttestationAsync_SuppressesMinorFlipFlops_WithHysteresisThreshold()
{
var signer = new CapturingProofChainSigner();
var service = new ChangeTraceAttestationService(signer, new FixedTimeProvider(FixedTime));
var trace = CreateTrace(
CreateDelta("pkg:npm/minor@1.0.0", PackageChangeType.Modified, score: 0.02),
CreateDelta("pkg:npm/major@2.0.0", PackageChangeType.Added, score: 0.70));
await service.GenerateAttestationAsync(
trace,
new ChangeTraceAttestationOptions { HysteresisThreshold = 0.05 });
var statement = signer.LastSignedStatement.Should().BeOfType<ChangeTraceStatement>().Subject;
statement.Predicate.Deltas.Should().HaveCount(1);
statement.Predicate.Deltas[0].Purl.Should().Be("pkg:npm/major@2.0.0");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task GenerateAttestationAsync_DisablesHysteresis_WhenThresholdIsZero()
{
var signer = new CapturingProofChainSigner();
var service = new ChangeTraceAttestationService(signer, new FixedTimeProvider(FixedTime));
var trace = CreateTrace(
CreateDelta("pkg:npm/minor@1.0.0", PackageChangeType.Modified, score: 0.02),
CreateDelta("pkg:npm/major@2.0.0", PackageChangeType.Added, score: 0.70));
await service.GenerateAttestationAsync(
trace,
new ChangeTraceAttestationOptions { HysteresisThreshold = 0.0 });
var statement = signer.LastSignedStatement.Should().BeOfType<ChangeTraceStatement>().Subject;
statement.Predicate.Deltas.Should().HaveCount(2);
}
private static ChangeTraceModel CreateTrace(params PackageDelta[] deltas)
{
return new ChangeTraceModel
{
Subject = new ChangeTraceSubject
{
Type = "oci.image",
Digest = "sha256:subject",
Purl = "pkg:oci/example@1.0.0",
Name = "example"
},
Basis = new ChangeTraceBasis
{
ScanId = "scan-123",
FromScanId = "scan-122",
ToScanId = "scan-123",
Policies = ["policy-a"],
DiffMethod = ["pkg"],
EngineVersion = "1.0.0",
AnalyzedAt = FixedTime
},
Deltas = deltas.ToImmutableArray(),
Summary = new ChangeTraceSummary
{
ChangedPackages = deltas.Length,
ChangedSymbols = 1,
ChangedBytes = 128,
RiskDelta = 0.6,
Verdict = ChangeTraceVerdict.RiskUp,
BeforeRiskScore = 0.2,
AfterRiskScore = 0.8
}
};
}
private static PackageDelta CreateDelta(string purl, PackageChangeType changeType, double score)
{
return new PackageDelta
{
Purl = purl,
Name = purl,
FromVersion = "1.0.0",
ToVersion = "1.0.1",
ChangeType = changeType,
Explain = PackageChangeExplanation.SecurityPatch,
Evidence = new PackageDeltaEvidence
{
SymbolsChanged = 1,
BytesChanged = 64,
Confidence = 0.8
},
TrustDelta = new TrustDelta
{
ReachabilityImpact = ReachabilityImpact.Increased,
ExploitabilityImpact = ExploitabilityImpact.Up,
Score = score,
BeforeScore = 0.3,
AfterScore = 0.3 + score,
ProofSteps = ["step-1"]
}
};
}
private sealed class CapturingProofChainSigner : IProofChainSigner
{
public InTotoStatement? LastSignedStatement { get; private set; }
public Task<DsseEnvelope> SignStatementAsync<T>(
T statement,
SigningKeyProfile keyProfile,
CancellationToken ct = default) where T : InTotoStatement
{
LastSignedStatement = statement;
return Task.FromResult(new DsseEnvelope
{
PayloadType = "application/vnd.in-toto+json",
Payload = Convert.ToBase64String([]),
Signatures =
[
new DsseSignature
{
KeyId = "test-key",
Sig = Convert.ToBase64String([1, 2, 3])
}
]
});
}
public Task<SignatureVerificationResult> VerifyEnvelopeAsync(
DsseEnvelope envelope,
IReadOnlyList<string> allowedKeyIds,
CancellationToken ct = default)
=> Task.FromResult(new SignatureVerificationResult
{
IsValid = true,
KeyId = "test-key"
});
}
private sealed class FixedTimeProvider(DateTimeOffset utcNow) : TimeProvider
{
public override DateTimeOffset GetUtcNow() => utcNow;
}
}

View File

@@ -0,0 +1,86 @@
using StellaOps.Attestor.ProofChain.Graph;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.Graph;
public sealed class InMemoryProofGraphServiceBehaviorTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task AddEdgeAsync_DuplicateEdge_IsDeduplicated()
{
var sut = new InMemoryProofGraphService();
var artifact = await sut.AddNodeAsync(ProofGraphNodeType.Artifact, "sha256:artifact");
var sbom = await sut.AddNodeAsync(ProofGraphNodeType.SbomDocument, "sha256:sbom");
var edge1 = await sut.AddEdgeAsync(artifact.Id, sbom.Id, ProofGraphEdgeType.DescribedBy);
var edge2 = await sut.AddEdgeAsync(artifact.Id, sbom.Id, ProofGraphEdgeType.DescribedBy);
Assert.Equal(edge1.Id, edge2.Id);
Assert.Equal(2, sut.NodeCount);
Assert.Equal(1, sut.EdgeCount);
Assert.Empty(edge1.Provenance);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task AddEdgeAsync_DuplicateEdge_MergesProvenanceSets()
{
var sut = new InMemoryProofGraphService();
var artifact = await sut.AddNodeAsync(ProofGraphNodeType.Artifact, "sha256:artifact-with-prov");
var sbom = await sut.AddNodeAsync(ProofGraphNodeType.SbomDocument, "sha256:sbom-with-prov");
await sut.AddEdgeAsync(
artifact.Id,
sbom.Id,
ProofGraphEdgeType.DescribedBy,
provenance: ["scanner-a", "scanner-b"]);
var merged = await sut.AddEdgeAsync(
artifact.Id,
sbom.Id,
ProofGraphEdgeType.DescribedBy,
provenance: ["scanner-b", "scanner-c"]);
Assert.Equal(1, sut.EdgeCount);
Assert.Equal(["scanner-a", "scanner-b", "scanner-c"], merged.Provenance);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetArtifactSubgraphAsync_DepthLimit_ExcludesDeeperNodes()
{
var sut = new InMemoryProofGraphService();
var root = await sut.AddNodeAsync(ProofGraphNodeType.Artifact, "sha256:root");
var level1 = await sut.AddNodeAsync(ProofGraphNodeType.SbomDocument, "sha256:level1");
var level2 = await sut.AddNodeAsync(ProofGraphNodeType.InTotoStatement, "sha256:level2");
await sut.AddEdgeAsync(root.Id, level1.Id, ProofGraphEdgeType.DescribedBy);
await sut.AddEdgeAsync(level1.Id, level2.Id, ProofGraphEdgeType.AttestedBy);
var subgraph = await sut.GetArtifactSubgraphAsync(root.Id, maxDepth: 1);
Assert.Equal(root.Id, subgraph.RootNodeId);
Assert.Contains(subgraph.Nodes, n => n.Id == root.Id);
Assert.Contains(subgraph.Nodes, n => n.Id == level1.Id);
Assert.DoesNotContain(subgraph.Nodes, n => n.Id == level2.Id);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetArtifactSubgraphAsync_ReturnsOnlyReachableNodesFromRoot()
{
var sut = new InMemoryProofGraphService();
var root = await sut.AddNodeAsync(ProofGraphNodeType.Artifact, "sha256:root2");
var related = await sut.AddNodeAsync(ProofGraphNodeType.SbomDocument, "sha256:related");
var unrelated = await sut.AddNodeAsync(ProofGraphNodeType.Artifact, "sha256:unrelated");
await sut.AddEdgeAsync(root.Id, related.Id, ProofGraphEdgeType.DescribedBy);
var subgraph = await sut.GetArtifactSubgraphAsync(root.Id, maxDepth: 3);
Assert.Contains(subgraph.Nodes, n => n.Id == root.Id);
Assert.Contains(subgraph.Nodes, n => n.Id == related.Id);
Assert.DoesNotContain(subgraph.Nodes, n => n.Id == unrelated.Id);
}
}

View File

@@ -0,0 +1,85 @@
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.Attestor.ProofChain.Predicates;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.Statements;
public sealed class DeltaVerdictPredicateCategorizationTests
{
private static readonly DateTimeOffset FixedTime = new(2026, 02, 11, 12, 0, 0, TimeSpan.Zero);
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CategorizeChanges_AssignsExpectedCategories()
{
var predicate = CreatePredicate(
CreateChange("new finding", direction: "added", before: null, after: "0.5"),
CreateChange("resolved finding", direction: "resolved", before: "0.5", after: null),
CreateChange("confidence up", direction: "changed", before: "0.4", after: "0.7"),
CreateChange("confidence down", direction: "changed", before: "0.9", after: "0.2"),
CreateChange("policy gate impact", direction: "changed", before: "1.0", after: "1.0", rule: "policy-gate"));
var categorized = predicate.CategorizeChanges();
categorized.Changes.Select(change => change.ChangeType).Should().Equal(
DeltaVerdictPredicate.ChangeTypeNew,
DeltaVerdictPredicate.ChangeTypeResolved,
DeltaVerdictPredicate.ChangeTypeConfidenceUp,
DeltaVerdictPredicate.ChangeTypeConfidenceDown,
DeltaVerdictPredicate.ChangeTypePolicyImpact);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CategorizeChanges_PreservesKnownExplicitCategory()
{
var predicate = CreatePredicate(
CreateChange(
"already categorized",
direction: "changed",
before: "0.2",
after: "0.3",
changeType: "ConfidenceDown"));
var categorized = predicate.CategorizeChanges();
categorized.Changes[0].ChangeType.Should().Be(DeltaVerdictPredicate.ChangeTypeConfidenceDown);
}
private static DeltaVerdictPredicate CreatePredicate(params DeltaVerdictChange[] changes)
{
return new DeltaVerdictPredicate
{
BeforeRevisionId = "before",
AfterRevisionId = "after",
HasMaterialChange = true,
PriorityScore = 100,
Changes = changes.ToImmutableArray(),
ComparedAt = FixedTime
};
}
private static DeltaVerdictChange CreateChange(
string reason,
string direction,
string? before,
string? after,
string rule = "R1",
string? changeType = null)
{
return new DeltaVerdictChange
{
Rule = rule,
FindingKey = new DeltaFindingKey
{
VulnId = "CVE-2026-0001",
Purl = "pkg:npm/sample@1.0.0"
},
Direction = direction,
ChangeType = changeType,
Reason = reason,
PreviousValue = before,
CurrentValue = after
};
}
}

View File

@@ -0,0 +1,247 @@
using System.Text.Json;
using Org.BouncyCastle.Crypto.Parameters;
using StellaOps.Attestor.Envelope;
using StellaOps.Attestor.ProofChain.Json;
using StellaOps.Attestor.ProofChain.Predicates;
using StellaOps.Attestor.ProofChain.Signing;
using StellaOps.Attestor.ProofChain.Statements;
using StellaOps.TestKit;
namespace StellaOps.Attestor.ProofChain.Tests.Statements;
public sealed class ReachabilityWitnessAttestationBehaviorTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReachabilityWitnessStatement_WithThreeNodePath_HasExpectedStructure()
{
var statement = CreateWitnessStatement();
Assert.Equal("stellaops.dev/predicates/reachability-witness@v1", statement.PredicateType);
Assert.Equal("https://in-toto.io/Statement/v1", statement.Type);
Assert.Equal(3, statement.Predicate.CallPath.Length);
Assert.Equal("api-entrypoint", statement.Predicate.CallPath[0].NodeId);
Assert.Equal("domain-service", statement.Predicate.CallPath[1].NodeId);
Assert.Equal("vuln-sink", statement.Predicate.CallPath[2].NodeId);
Assert.Equal("static-analysis", statement.Predicate.Evidence.AnalysisMethod);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProofChainSigner_SignAndVerify_ReachabilityWitnessStatement_Passes()
{
var (signer, keyId) = CreateSigner();
var statement = CreateWitnessStatement();
var envelope = await signer.SignStatementAsync(statement, SigningKeyProfile.Evidence);
var verify = await signer.VerifyEnvelopeAsync(envelope, [keyId]);
Assert.Equal(ProofChainSigner.InTotoPayloadType, envelope.PayloadType);
Assert.NotEmpty(envelope.Payload);
Assert.Single(envelope.Signatures);
Assert.True(verify.IsValid);
Assert.Equal(keyId, verify.KeyId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProofChainSigner_TamperedReachabilityPayload_FailsVerification()
{
var (signer, keyId) = CreateSigner();
var statement = CreateWitnessStatement();
var envelope = await signer.SignStatementAsync(statement, SigningKeyProfile.Evidence);
var payloadBytes = Convert.FromBase64String(envelope.Payload);
payloadBytes[^1] ^= 0xFF;
var tampered = envelope with
{
Payload = Convert.ToBase64String(payloadBytes)
};
var verify = await signer.VerifyEnvelopeAsync(tampered, [keyId]);
Assert.False(verify.IsValid);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReachabilitySubgraphStatement_SerializesWithExpectedPredicateType()
{
var statement = new ReachabilitySubgraphStatement
{
Subject =
[
new Subject
{
Name = "pkg:oci/stellaops/app@sha256:1234",
Digest = new Dictionary<string, string>
{
["sha256"] = "1234"
}
}
],
Predicate = new ReachabilitySubgraphPredicate
{
GraphDigest = "blake3:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
FindingKeys = ["CVE-2026-0001@pkg:oci/stellaops/app"],
Analysis = new ReachabilitySubgraphAnalysis
{
Analyzer = "reachgraph",
AnalyzerVersion = "1.2.3",
Confidence = 0.94,
Completeness = "full",
GeneratedAt = new DateTimeOffset(2026, 2, 11, 13, 0, 0, TimeSpan.Zero)
}
}
};
var json = JsonSerializer.Serialize(statement);
using var doc = JsonDocument.Parse(json);
Assert.Equal(ReachabilitySubgraphPredicate.PredicateType, doc.RootElement.GetProperty("predicateType").GetString());
Assert.Equal("blake3:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", doc.RootElement.GetProperty("predicate").GetProperty("graphDigest").GetString());
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReachabilityWitnessEvidenceMetadata_PreservedAcrossSignedPayloads()
{
var (signer, _) = CreateSigner();
var staticStatement = CreateWitnessStatement("static-analysis", "reachgraph-1.0");
var symbolicStatement = CreateWitnessStatement("symbolic-execution", "symexec-2.1");
var staticEnvelope = await signer.SignStatementAsync(staticStatement, SigningKeyProfile.Evidence);
var symbolicEnvelope = await signer.SignStatementAsync(symbolicStatement, SigningKeyProfile.Evidence);
var staticDecoded = JsonSerializer.Deserialize<ReachabilityWitnessStatement>(Convert.FromBase64String(staticEnvelope.Payload));
var symbolicDecoded = JsonSerializer.Deserialize<ReachabilityWitnessStatement>(Convert.FromBase64String(symbolicEnvelope.Payload));
Assert.NotNull(staticDecoded);
Assert.NotNull(symbolicDecoded);
Assert.Equal("static-analysis", staticDecoded!.Predicate.Evidence.AnalysisMethod);
Assert.Equal("reachgraph-1.0", staticDecoded.Predicate.Evidence.ToolVersion);
Assert.Equal("symbolic-execution", symbolicDecoded!.Predicate.Evidence.AnalysisMethod);
Assert.Equal("symexec-2.1", symbolicDecoded.Predicate.Evidence.ToolVersion);
}
private static ReachabilityWitnessStatement CreateWitnessStatement(
string analysisMethod = "static-analysis",
string toolVersion = "reachgraph-1.0")
{
return new ReachabilityWitnessStatement
{
Subject =
[
new Subject
{
Name = "pkg:oci/stellaops/app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Digest = new Dictionary<string, string>
{
["sha256"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
],
Predicate = new ReachabilityWitnessPayload
{
WitnessId = "witness-001",
ScanId = "scan-001",
VulnId = "vuln-001",
CveId = "CVE-2026-0001",
PackageName = "stellaops.app",
PackageVersion = "1.0.0",
Purl = "pkg:oci/stellaops/app@1.0.0",
ConfidenceTier = "high",
ConfidenceScore = 0.92,
IsReachable = true,
CallPath =
[
new WitnessCallPathNode
{
NodeId = "api-entrypoint",
Symbol = "Controller.Handle",
File = "Controller.cs",
Line = 27
},
new WitnessCallPathNode
{
NodeId = "domain-service",
Symbol = "Service.Process",
File = "Service.cs",
Line = 88
},
new WitnessCallPathNode
{
NodeId = "vuln-sink",
Symbol = "LegacyParser.ParseUnsafe",
File = "LegacyParser.cs",
Line = 141
}
],
Entrypoint = new WitnessPathNode
{
NodeId = "api-entrypoint",
Symbol = "Controller.Handle",
HttpRoute = "/api/process",
HttpMethod = "POST"
},
Sink = new WitnessPathNode
{
NodeId = "vuln-sink",
Symbol = "LegacyParser.ParseUnsafe",
Method = "ParseUnsafe"
},
Gates =
[
new WitnessGateInfo
{
GateType = "input-validation",
Symbol = "Service.Validate",
Confidence = 0.83
}
],
Evidence = new WitnessEvidenceMetadata
{
CallGraphHash = "blake3:1111111111111111111111111111111111111111111111111111111111111111",
SurfaceHash = "blake3:2222222222222222222222222222222222222222222222222222222222222222",
AnalysisMethod = analysisMethod,
ToolVersion = toolVersion
},
ObservedAt = new DateTimeOffset(2026, 2, 11, 13, 10, 0, TimeSpan.Zero),
VexRecommendation = "affected"
}
};
}
private static (IProofChainSigner Signer, string KeyId) CreateSigner()
{
var seed = Enumerable.Range(0, 32).Select(static i => (byte)i).ToArray();
var privateKey = new Ed25519PrivateKeyParameters(seed, 0);
var publicKey = privateKey.GeneratePublicKey().GetEncoded();
var key = EnvelopeKey.CreateEd25519Signer(seed, publicKey, "reachability-test-key");
var keyStore = new StaticKeyStore(new Dictionary<SigningKeyProfile, EnvelopeKey>
{
[SigningKeyProfile.Evidence] = key
});
return (new ProofChainSigner(keyStore, new Rfc8785JsonCanonicalizer()), key.KeyId);
}
private sealed class StaticKeyStore : IProofChainKeyStore
{
private readonly IReadOnlyDictionary<SigningKeyProfile, EnvelopeKey> signingKeys;
private readonly IReadOnlyDictionary<string, EnvelopeKey> verificationKeys;
public StaticKeyStore(IReadOnlyDictionary<SigningKeyProfile, EnvelopeKey> signingKeys)
{
this.signingKeys = signingKeys;
verificationKeys = signingKeys.Values.ToDictionary(static key => key.KeyId, static key => key, StringComparer.Ordinal);
}
public bool TryGetSigningKey(SigningKeyProfile profile, out EnvelopeKey key)
=> signingKeys.TryGetValue(profile, out key!);
public bool TryGetVerificationKey(string keyId, out EnvelopeKey key)
=> verificationKeys.TryGetValue(keyId, out key!);
}
}

View File

@@ -1,16 +1,22 @@
# Attestor ProofChain Tests Task Board
# Attestor ProofChain Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| QA-ATTESTOR-VERIFY-006 | DONE | `ai-remediation-plan-attestation` verified with run-001 Tier 0/1/2 evidence and remediation threshold-fixture retest (`17/17`). |
| QA-ATTESTOR-VERIFY-007 | DONE | `asn-1-native-rfc-3161-timestamp-token-parsing` run-001 verification completed; placeholder RFC-3161 logic forced terminal `not_implemented`. |
| QA-ATTESTOR-VERIFY-008 | DONE | `attestable-exception-objects-with-expiries-and-audit-trails` run-001 reached terminal `not_implemented` after claim-parity review. |
| QA-ATTESTOR-VERIFY-009 | DONE | Added `ReachabilityWitnessAttestationBehaviorTests` and verified reachability-slice behavior (`5/5`) in run-001. |
| QA-ATTESTOR-VERIFY-001 | DONE | Implemented/verified behavior tests for adaptive noise gating and captured run-002 evidence. |
| QA-ATTESTOR-VERIFY-002 | DONE | Verified AI explanation/classification behavior with run-001 scoped evidence (`7/7`). |
| QA-ATTESTOR-VERIFY-003 | DONE | Added policy-threshold and VEX unresolvable-evidence assertions; revalidated authority classifier with run-002 (`11/11`). |
| QA-ATTESTOR-VERIFY-004 | DONE | Explanation attestation type contracts and schema alignment verified with run-001 targeted behavior tests (`7/7`). |
| QA-ATTESTOR-VERIFY-004 | DONE | Added `AIExplanationAttestationTypesTests`; verified serialization/replay/statement contracts and completed run-001 targeted behavior coverage (`13/13`). |
| AUDIT-0063-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0063-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0063-A | DONE | Waived after revalidation 2026-01-06. |
| VAL-SMOKE-001 | DONE | Fixed detached payload reference expectations; unit tests pass. |