Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
19
src/Attestor/__Libraries/AGENTS.md
Normal file
19
src/Attestor/__Libraries/AGENTS.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Attestor __Libraries AGENTS
|
||||
|
||||
## Purpose & Scope
|
||||
- Working directory: `src/Attestor/__Libraries/` (shared attestation libraries).
|
||||
- Roles: backend engineer, QA automation.
|
||||
|
||||
## Required Reading (treat as read before DOING)
|
||||
- `docs/README.md`
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/modules/attestor/architecture.md`
|
||||
- Relevant sprint files.
|
||||
|
||||
## Working Agreements
|
||||
- Preserve DSSE/in-toto compatibility and deterministic serialization.
|
||||
- Avoid network dependencies in libraries and tests.
|
||||
- Record schema changes in attestor docs and sprint Decisions & Risks.
|
||||
|
||||
## Testing
|
||||
- Add tests under the corresponding attestor test projects or `src/Attestor/__Tests`.
|
||||
@@ -155,6 +155,12 @@ public sealed class PredicateSchemaValidator : IJsonSchemaValidator
|
||||
case "verdict.stella/v1":
|
||||
errors.AddRange(ValidateVerdictPredicate(root));
|
||||
break;
|
||||
case "delta-verdict.stella/v1":
|
||||
errors.AddRange(ValidateDeltaVerdictPredicate(root));
|
||||
break;
|
||||
case "reachability-subgraph.stella/v1":
|
||||
errors.AddRange(ValidateReachabilitySubgraphPredicate(root));
|
||||
break;
|
||||
}
|
||||
|
||||
return errors.Count > 0
|
||||
@@ -192,6 +198,8 @@ public sealed class PredicateSchemaValidator : IJsonSchemaValidator
|
||||
"proofspine.stella/v1" => true,
|
||||
"verdict.stella/v1" => true,
|
||||
"https://stella-ops.org/predicates/sbom-linkage/v1" => true,
|
||||
"delta-verdict.stella/v1" => true,
|
||||
"reachability-subgraph.stella/v1" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
@@ -248,4 +256,30 @@ public sealed class PredicateSchemaValidator : IJsonSchemaValidator
|
||||
if (!root.TryGetProperty("verifiedAt", out _))
|
||||
yield return new() { Path = "/verifiedAt", Message = "Required property missing", Keyword = "required" };
|
||||
}
|
||||
|
||||
private static IEnumerable<SchemaValidationError> ValidateDeltaVerdictPredicate(JsonElement root)
|
||||
{
|
||||
// Required: beforeRevisionId, afterRevisionId, hasMaterialChange, priorityScore, changes, comparedAt
|
||||
if (!root.TryGetProperty("beforeRevisionId", out _))
|
||||
yield return new() { Path = "/beforeRevisionId", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("afterRevisionId", out _))
|
||||
yield return new() { Path = "/afterRevisionId", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("hasMaterialChange", out _))
|
||||
yield return new() { Path = "/hasMaterialChange", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("priorityScore", out _))
|
||||
yield return new() { Path = "/priorityScore", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("changes", out _))
|
||||
yield return new() { Path = "/changes", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("comparedAt", out _))
|
||||
yield return new() { Path = "/comparedAt", Message = "Required property missing", Keyword = "required" };
|
||||
}
|
||||
|
||||
private static IEnumerable<SchemaValidationError> ValidateReachabilitySubgraphPredicate(JsonElement root)
|
||||
{
|
||||
// Required: graphDigest, analysis
|
||||
if (!root.TryGetProperty("graphDigest", out _))
|
||||
yield return new() { Path = "/graphDigest", Message = "Required property missing", Keyword = "required" };
|
||||
if (!root.TryGetProperty("analysis", out _))
|
||||
yield return new() { Path = "/analysis", Message = "Required property missing", Keyword = "required" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace StellaOps.Attestor.ProofChain.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated summary of unknowns for inclusion in attestations.
|
||||
/// Provides verifiable data about unknown risk handled during evaluation.
|
||||
/// </summary>
|
||||
public sealed record UnknownsSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// Total count of unknowns encountered.
|
||||
/// </summary>
|
||||
public int Total { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of unknowns by reason code.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, int> ByReasonCode { get; init; }
|
||||
= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Count of unknowns that would block if not excepted.
|
||||
/// </summary>
|
||||
public int BlockingCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of unknowns that are covered by approved exceptions.
|
||||
/// </summary>
|
||||
public int ExceptedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy thresholds that were evaluated.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> PolicyThresholdsApplied { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Exception IDs that were applied to cover unknowns.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ExceptionsApplied { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the unknowns list for integrity verification.
|
||||
/// </summary>
|
||||
public string? UnknownsDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty summary for cases with no unknowns.
|
||||
/// </summary>
|
||||
public static UnknownsSummary Empty { get; } = new()
|
||||
{
|
||||
Total = 0,
|
||||
ByReasonCode = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
|
||||
BlockingCount = 0,
|
||||
ExceptedCount = 0
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// DeltaVerdictPredicate.cs
|
||||
// Sprint: SPRINT_4400_0001_0001_signed_delta_verdict
|
||||
// Description: DSSE predicate for Smart-Diff delta verdict attestations.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Predicates;
|
||||
|
||||
/// <summary>
|
||||
/// DSSE predicate for Smart-Diff delta verdict attestation.
|
||||
/// predicateType: delta-verdict.stella/v1
|
||||
/// </summary>
|
||||
public sealed record DeltaVerdictPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// The predicate type URI for delta verdict attestations.
|
||||
/// </summary>
|
||||
public const string PredicateType = "delta-verdict.stella/v1";
|
||||
|
||||
/// <summary>
|
||||
/// Revision identifier for the baseline scan.
|
||||
/// </summary>
|
||||
[JsonPropertyName("beforeRevisionId")]
|
||||
public required string BeforeRevisionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Revision identifier for the current scan.
|
||||
/// </summary>
|
||||
[JsonPropertyName("afterRevisionId")]
|
||||
public required string AfterRevisionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether any material change was detected.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hasMaterialChange")]
|
||||
public required bool HasMaterialChange { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate priority score for the delta.
|
||||
/// </summary>
|
||||
[JsonPropertyName("priorityScore")]
|
||||
public required double PriorityScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Change details captured by Smart-Diff rules.
|
||||
/// </summary>
|
||||
[JsonPropertyName("changes")]
|
||||
public ImmutableArray<DeltaVerdictChange> Changes { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Digest of the baseline verdict attestation (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("beforeVerdictDigest")]
|
||||
public string? BeforeVerdictDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Digest of the current verdict attestation (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("afterVerdictDigest")]
|
||||
public string? AfterVerdictDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the baseline proof spine (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("beforeProofSpine")]
|
||||
public AttestationReference? BeforeProofSpine { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the current proof spine (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("afterProofSpine")]
|
||||
public AttestationReference? AfterProofSpine { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Graph revision identifier for the baseline analysis (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("beforeGraphRevisionId")]
|
||||
public string? BeforeGraphRevisionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Graph revision identifier for the current analysis (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("afterGraphRevisionId")]
|
||||
public string? AfterGraphRevisionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the comparison was performed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("comparedAt")]
|
||||
public required DateTimeOffset ComparedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual change captured in delta verdict.
|
||||
/// </summary>
|
||||
public sealed record DeltaVerdictChange
|
||||
{
|
||||
/// <summary>
|
||||
/// Detection rule identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("rule")]
|
||||
public required string Rule { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Finding key (vulnerability and component).
|
||||
/// </summary>
|
||||
[JsonPropertyName("findingKey")]
|
||||
public required DeltaFindingKey FindingKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Direction of risk change.
|
||||
/// </summary>
|
||||
[JsonPropertyName("direction")]
|
||||
public required string Direction { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Change category (optional).
|
||||
/// </summary>
|
||||
[JsonPropertyName("changeType")]
|
||||
public string? ChangeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable reason for the change.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reason")]
|
||||
public required string Reason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Previous value observed (optional).
|
||||
/// </summary>
|
||||
[JsonPropertyName("previousValue")]
|
||||
public string? PreviousValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Current value observed (optional).
|
||||
/// </summary>
|
||||
[JsonPropertyName("currentValue")]
|
||||
public string? CurrentValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Weight contribution for this change (optional).
|
||||
/// </summary>
|
||||
[JsonPropertyName("weight")]
|
||||
public double? Weight { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finding key for delta verdict changes.
|
||||
/// </summary>
|
||||
public sealed record DeltaFindingKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Vulnerability identifier (CVE, GHSA, etc.).
|
||||
/// </summary>
|
||||
[JsonPropertyName("vulnId")]
|
||||
public required string VulnId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Component package URL.
|
||||
/// </summary>
|
||||
[JsonPropertyName("purl")]
|
||||
public required string Purl { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to an attestation or proof spine.
|
||||
/// </summary>
|
||||
public sealed record AttestationReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Digest of the attestation (sha256:... or blake3:...).
|
||||
/// </summary>
|
||||
[JsonPropertyName("digest")]
|
||||
public required string Digest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional URI where the attestation can be retrieved.
|
||||
/// </summary>
|
||||
[JsonPropertyName("uri")]
|
||||
public string? Uri { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.ProofChain.Models;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Predicates;
|
||||
|
||||
/// <summary>
|
||||
/// Predicate type for policy decision attestations.
|
||||
/// Predicate type: https://stella.ops/predicates/policy-decision@v2
|
||||
/// </summary>
|
||||
public sealed record PolicyDecisionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// The predicate type URI for policy decisions.
|
||||
/// </summary>
|
||||
public const string PredicateType = "https://stella.ops/predicates/policy-decision@v2";
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the policy that was evaluated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("policyRef")]
|
||||
public required string PolicyRef { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Final policy decision outcome.
|
||||
/// </summary>
|
||||
[JsonPropertyName("decision")]
|
||||
public required PolicyDecision Decision { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when the policy was evaluated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("evaluatedAt")]
|
||||
public required DateTimeOffset EvaluatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary of findings from the evaluation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("findings")]
|
||||
public IReadOnlyList<FindingSummary> Findings { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Summary of unknowns and how they were handled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("unknowns")]
|
||||
public UnknownsSummary? Unknowns { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether unknowns were a factor in the decision.
|
||||
/// </summary>
|
||||
[JsonPropertyName("unknownsAffectedDecision")]
|
||||
public bool UnknownsAffectedDecision { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason codes that caused blocking (if any).
|
||||
/// </summary>
|
||||
[JsonPropertyName("blockingReasonCodes")]
|
||||
public IReadOnlyList<string> BlockingReasonCodes { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Content-addressed ID of the knowledge snapshot used.
|
||||
/// </summary>
|
||||
[JsonPropertyName("knowledgeSnapshotId")]
|
||||
public string? KnowledgeSnapshotId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy decision outcome.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum PolicyDecision
|
||||
{
|
||||
/// <summary>
|
||||
/// Policy evaluation passed.
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// Policy evaluation failed.
|
||||
/// </summary>
|
||||
Fail,
|
||||
|
||||
/// <summary>
|
||||
/// Policy passed with approved exceptions.
|
||||
/// </summary>
|
||||
PassWithExceptions,
|
||||
|
||||
/// <summary>
|
||||
/// Policy evaluation could not be completed.
|
||||
/// </summary>
|
||||
Indeterminate
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of a finding from policy evaluation.
|
||||
/// </summary>
|
||||
public sealed record FindingSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// The finding identifier.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the finding.
|
||||
/// </summary>
|
||||
[JsonPropertyName("severity")]
|
||||
public required string Severity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Description of the finding.
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// ReachabilitySubgraphPredicate.cs
|
||||
// Sprint: SPRINT_4400_0001_0002_reachability_subgraph_attestation
|
||||
// Description: DSSE predicate for reachability subgraph attestations.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Predicates;
|
||||
|
||||
/// <summary>
|
||||
/// DSSE predicate for reachability subgraph attestation.
|
||||
/// predicateType: reachability-subgraph.stella/v1
|
||||
/// </summary>
|
||||
public sealed record ReachabilitySubgraphPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// The predicate type URI for reachability subgraph attestations.
|
||||
/// </summary>
|
||||
public const string PredicateType = "reachability-subgraph.stella/v1";
|
||||
|
||||
/// <summary>
|
||||
/// Schema version for the predicate payload.
|
||||
/// </summary>
|
||||
[JsonPropertyName("schemaVersion")]
|
||||
public string SchemaVersion { get; init; } = "1.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// Content-addressed digest of the serialized subgraph.
|
||||
/// </summary>
|
||||
[JsonPropertyName("graphDigest")]
|
||||
public required string GraphDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional CAS URI for the subgraph content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("graphCasUri")]
|
||||
public string? GraphCasUri { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Finding keys covered by this subgraph (e.g., "CVE-2024-1234@pkg:...").
|
||||
/// </summary>
|
||||
[JsonPropertyName("findingKeys")]
|
||||
public ImmutableArray<string> FindingKeys { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Analysis metadata for the subgraph extraction.
|
||||
/// </summary>
|
||||
[JsonPropertyName("analysis")]
|
||||
public required ReachabilitySubgraphAnalysis Analysis { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata about subgraph extraction and analysis.
|
||||
/// </summary>
|
||||
public sealed record ReachabilitySubgraphAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzer name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("analyzer")]
|
||||
public required string Analyzer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Analyzer version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("analyzerVersion")]
|
||||
public required string AnalyzerVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence score (0.0-1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("confidence")]
|
||||
public required double Confidence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Completeness indicator (full, partial, unknown).
|
||||
/// </summary>
|
||||
[JsonPropertyName("completeness")]
|
||||
public required string Completeness { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the subgraph was generated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash algorithm used for graph digest.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hashAlgorithm")]
|
||||
public string HashAlgorithm { get; init; } = "blake3";
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Attestor.ProofChain.Models;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates unknowns data into summary format for attestations.
|
||||
/// </summary>
|
||||
public sealed class UnknownsAggregator : IUnknownsAggregator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an unknowns summary from evaluation results.
|
||||
/// </summary>
|
||||
public UnknownsSummary Aggregate(
|
||||
IReadOnlyList<UnknownItem> unknowns,
|
||||
BudgetCheckResult? budgetResult = null,
|
||||
IReadOnlyList<ExceptionRef>? exceptions = null)
|
||||
{
|
||||
if (unknowns.Count == 0)
|
||||
return UnknownsSummary.Empty;
|
||||
|
||||
// Count by reason code
|
||||
var byReasonCode = unknowns
|
||||
.GroupBy(u => u.ReasonCode)
|
||||
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Calculate blocking count (would block without exceptions)
|
||||
var blockingCount = budgetResult?.Violations.Values.Sum(v => v.Count) ?? 0;
|
||||
|
||||
// Calculate excepted count
|
||||
var exceptedCount = exceptions?.Count ?? 0;
|
||||
|
||||
// Compute digest of unknowns list for integrity
|
||||
var unknownsDigest = ComputeUnknownsDigest(unknowns);
|
||||
|
||||
// Extract policy thresholds that were checked
|
||||
var thresholds = budgetResult?.Violations.Keys
|
||||
.Select(k => $"{k}:{budgetResult.Violations[k].Limit}")
|
||||
.ToList() ?? new List<string>();
|
||||
|
||||
// Extract applied exception IDs
|
||||
var exceptionIds = exceptions?
|
||||
.Select(e => e.ExceptionId)
|
||||
.ToList() ?? new List<string>();
|
||||
|
||||
return new UnknownsSummary
|
||||
{
|
||||
Total = unknowns.Count,
|
||||
ByReasonCode = byReasonCode,
|
||||
BlockingCount = blockingCount,
|
||||
ExceptedCount = exceptedCount,
|
||||
PolicyThresholdsApplied = thresholds,
|
||||
ExceptionsApplied = exceptionIds,
|
||||
UnknownsDigest = unknownsDigest
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a deterministic digest of the unknowns list.
|
||||
/// </summary>
|
||||
private static string ComputeUnknownsDigest(IReadOnlyList<UnknownItem> unknowns)
|
||||
{
|
||||
// Sort for determinism
|
||||
var sorted = unknowns
|
||||
.OrderBy(u => u.PackageUrl)
|
||||
.ThenBy(u => u.CveId)
|
||||
.ThenBy(u => u.ReasonCode)
|
||||
.ToList();
|
||||
|
||||
// Serialize to canonical JSON
|
||||
var json = JsonSerializer.Serialize(sorted, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
});
|
||||
|
||||
// Hash the serialized data using SHA256
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(json));
|
||||
return Convert.ToHexString(hashBytes).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for unknowns aggregation service.
|
||||
/// </summary>
|
||||
public interface IUnknownsAggregator
|
||||
{
|
||||
/// <summary>
|
||||
/// Aggregates unknowns into a summary.
|
||||
/// </summary>
|
||||
UnknownsSummary Aggregate(
|
||||
IReadOnlyList<UnknownItem> unknowns,
|
||||
BudgetCheckResult? budgetResult = null,
|
||||
IReadOnlyList<ExceptionRef>? exceptions = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input item for unknowns aggregation.
|
||||
/// </summary>
|
||||
public sealed record UnknownItem(
|
||||
string PackageUrl,
|
||||
string? CveId,
|
||||
string ReasonCode,
|
||||
string? RemediationHint);
|
||||
|
||||
/// <summary>
|
||||
/// Reference to an applied exception.
|
||||
/// </summary>
|
||||
public sealed record ExceptionRef(
|
||||
string ExceptionId,
|
||||
string Status,
|
||||
IReadOnlyList<string> CoveredReasonCodes);
|
||||
|
||||
/// <summary>
|
||||
/// Result of a budget check operation.
|
||||
/// </summary>
|
||||
public sealed record BudgetCheckResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Budget violations by reason code.
|
||||
/// </summary>
|
||||
public required IReadOnlyDictionary<string, BudgetViolation> Violations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a budget violation for a specific reason code.
|
||||
/// </summary>
|
||||
public sealed record BudgetViolation(
|
||||
int Count,
|
||||
int Limit);
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.ProofChain.Predicates;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Statements;
|
||||
|
||||
/// <summary>
|
||||
/// In-toto statement for Smart-Diff delta verdicts.
|
||||
/// Predicate type: delta-verdict.stella/v1
|
||||
/// </summary>
|
||||
public sealed record DeltaVerdictStatement : InTotoStatement
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonPropertyName("predicateType")]
|
||||
public override string PredicateType => DeltaVerdictPredicate.PredicateType;
|
||||
|
||||
/// <summary>
|
||||
/// The delta verdict predicate payload.
|
||||
/// </summary>
|
||||
[JsonPropertyName("predicate")]
|
||||
public required DeltaVerdictPredicate Predicate { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.ProofChain.Predicates;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Statements;
|
||||
|
||||
/// <summary>
|
||||
/// In-toto statement for reachability subgraph attestations.
|
||||
/// Predicate type: reachability-subgraph.stella/v1
|
||||
/// </summary>
|
||||
public sealed record ReachabilitySubgraphStatement : InTotoStatement
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonPropertyName("predicateType")]
|
||||
public override string PredicateType => ReachabilitySubgraphPredicate.PredicateType;
|
||||
|
||||
/// <summary>
|
||||
/// The reachability subgraph predicate payload.
|
||||
/// </summary>
|
||||
[JsonPropertyName("predicate")]
|
||||
public required ReachabilitySubgraphPredicate Predicate { get; init; }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.ProofChain.Models;
|
||||
|
||||
namespace StellaOps.Attestor.ProofChain.Statements;
|
||||
|
||||
@@ -66,6 +67,20 @@ public sealed record VerdictReceiptPayload
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdAt")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary of unknowns encountered during evaluation.
|
||||
/// Included for transparency about uncertainty in the verdict.
|
||||
/// </summary>
|
||||
[JsonPropertyName("unknowns")]
|
||||
public UnknownsSummary? Unknowns { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the knowledge snapshot used for evaluation.
|
||||
/// Enables replay and verification of inputs.
|
||||
/// </summary>
|
||||
[JsonPropertyName("knowledgeSnapshotId")]
|
||||
public string? KnowledgeSnapshotId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user