save progress

This commit is contained in:
StellaOps Bot
2026-01-02 21:06:27 +02:00
parent f46bde5575
commit 3f197814c5
441 changed files with 21545 additions and 4306 deletions

View File

@@ -50,7 +50,7 @@ public class AuditLogEntity
/// Additional details about the operation.
/// </summary>
[Column("details", TypeName = "jsonb")]
public JsonDocument? Details { get; set; }
public JsonElement? Details { get; set; }
/// <summary>
/// When this log entry was created.

View File

@@ -53,7 +53,7 @@ public class RekorEntryEntity
/// </summary>
[Required]
[Column("inclusion_proof", TypeName = "jsonb")]
public JsonDocument InclusionProof { get; set; } = null!;
public JsonElement InclusionProof { get; set; }
/// <summary>
/// When this record was created.

View File

@@ -16,7 +16,7 @@ function Resolve-RepoRoot {
$repoRoot = Resolve-RepoRoot
$perfDir = Join-Path $repoRoot "src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Perf"
$migrationFile = Join-Path $repoRoot "src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/20251214000001_AddProofChainSchema.sql"
$migrationFile = Join-Path $repoRoot "src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/001_initial_schema.sql"
$seedFile = Join-Path $perfDir "seed.sql"
$queriesFile = Join-Path $perfDir "queries.sql"
$reportFile = Join-Path $repoRoot "docs/db/reports/proofchain-schema-perf-2025-12-17.md"

View File

@@ -57,6 +57,9 @@ public class ProofChainDbContext : DbContext
entity.HasIndex(e => e.Purl).HasDatabaseName("idx_sbom_entries_purl");
entity.HasIndex(e => e.ArtifactDigest).HasDatabaseName("idx_sbom_entries_artifact");
entity.HasIndex(e => e.TrustAnchorId).HasDatabaseName("idx_sbom_entries_anchor");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
// Unique constraint
entity.HasIndex(e => new { e.BomDigest, e.Purl, e.Version })
@@ -87,6 +90,9 @@ public class ProofChainDbContext : DbContext
.HasDatabaseName("idx_dsse_entry_predicate");
entity.HasIndex(e => e.SignerKeyId).HasDatabaseName("idx_dsse_signer");
entity.HasIndex(e => e.BodyHash).HasDatabaseName("idx_dsse_body_hash");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
// Unique constraint
entity.HasIndex(e => new { e.EntryId, e.PredicateType, e.BodyHash })
@@ -100,6 +106,9 @@ public class ProofChainDbContext : DbContext
entity.HasIndex(e => e.BundleId).HasDatabaseName("idx_spines_bundle").IsUnique();
entity.HasIndex(e => e.AnchorId).HasDatabaseName("idx_spines_anchor");
entity.HasIndex(e => e.PolicyVersion).HasDatabaseName("idx_spines_policy");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
entity.HasOne(e => e.Anchor)
.WithMany()
@@ -114,6 +123,12 @@ public class ProofChainDbContext : DbContext
entity.HasIndex(e => e.IsActive)
.HasDatabaseName("idx_trust_anchors_active")
.HasFilter("is_active = TRUE");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
entity.Property(e => e.UpdatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAddOrUpdate();
});
// RekorEntryEntity configuration
@@ -123,6 +138,9 @@ public class ProofChainDbContext : DbContext
entity.HasIndex(e => e.LogId).HasDatabaseName("idx_rekor_log_id");
entity.HasIndex(e => e.Uuid).HasDatabaseName("idx_rekor_uuid");
entity.HasIndex(e => e.EnvId).HasDatabaseName("idx_rekor_env");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
entity.HasOne(e => e.Envelope)
.WithOne(e => e.RekorEntry)
@@ -138,6 +156,70 @@ public class ProofChainDbContext : DbContext
entity.HasIndex(e => e.CreatedAt)
.HasDatabaseName("idx_audit_created")
.IsDescending();
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("NOW()")
.ValueGeneratedOnAdd();
});
}
public override int SaveChanges()
{
NormalizeTrackedArrays();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
NormalizeTrackedArrays();
return base.SaveChangesAsync(cancellationToken);
}
private void NormalizeTrackedArrays()
{
foreach (var entry in ChangeTracker.Entries<SpineEntity>())
{
if (entry.State is EntityState.Added or EntityState.Modified)
{
entry.Entity.EvidenceIds = NormalizeEvidenceIds(entry.Entity.EvidenceIds);
}
}
foreach (var entry in ChangeTracker.Entries<TrustAnchorEntity>())
{
if (entry.State is EntityState.Added or EntityState.Modified)
{
entry.Entity.AllowedKeyIds = NormalizeKeyIds(entry.Entity.AllowedKeyIds);
}
}
}
private static string[] NormalizeEvidenceIds(string[] evidenceIds)
{
if (evidenceIds.Length == 0)
{
return evidenceIds;
}
return evidenceIds
.Select(id => id.Trim())
.Where(id => !string.IsNullOrEmpty(id))
.Distinct(StringComparer.Ordinal)
.OrderBy(id => id, StringComparer.Ordinal)
.ToArray();
}
private static string[] NormalizeKeyIds(string[] keyIds)
{
if (keyIds.Length == 0)
{
return keyIds;
}
return keyIds
.Select(id => id.Trim())
.Where(id => !string.IsNullOrEmpty(id))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(id => id, StringComparer.OrdinalIgnoreCase)
.ToArray();
}
}

View File

@@ -59,7 +59,8 @@ public sealed class TrustAnchorMatcher : ITrustAnchorMatcher
private readonly ILogger<TrustAnchorMatcher> _logger;
// Cache compiled regex patterns
private readonly Dictionary<string, Regex> _patternCache = new();
private const int MaxRegexCacheSize = 1024;
private readonly Dictionary<string, Regex> _patternCache = new(StringComparer.OrdinalIgnoreCase);
private readonly Lock _cacheLock = new();
public TrustAnchorMatcher(
@@ -92,7 +93,7 @@ public sealed class TrustAnchorMatcher : ITrustAnchorMatcher
{
var specificity = CalculateSpecificity(anchor.PurlPattern);
if (bestMatch == null || specificity > bestMatch.Specificity)
if (IsBetterMatch(anchor, specificity, bestMatch))
{
bestMatch = new TrustAnchorMatchResult
{
@@ -190,6 +191,11 @@ public sealed class TrustAnchorMatcher : ITrustAnchorMatcher
var regexPattern = ConvertGlobToRegex(pattern);
var regex = new Regex(regexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (_patternCache.Count >= MaxRegexCacheSize)
{
_patternCache.Clear();
}
_patternCache[pattern] = regex;
return regex;
}
@@ -284,4 +290,36 @@ public sealed class TrustAnchorMatcher : ITrustAnchorMatcher
}
return true;
}
private static bool IsBetterMatch(
TrustAnchorEntity candidate,
int specificity,
TrustAnchorMatchResult? bestMatch)
{
if (bestMatch == null)
{
return true;
}
if (specificity != bestMatch.Specificity)
{
return specificity > bestMatch.Specificity;
}
var candidatePattern = candidate.PurlPattern ?? string.Empty;
var bestPattern = bestMatch.MatchedPattern ?? string.Empty;
if (candidatePattern.Length != bestPattern.Length)
{
return candidatePattern.Length > bestPattern.Length;
}
var patternCompare = string.Compare(candidatePattern, bestPattern, StringComparison.OrdinalIgnoreCase);
if (patternCompare != 0)
{
return patternCompare < 0;
}
return candidate.AnchorId.CompareTo(bestMatch.Anchor.AnchorId) < 0;
}
}

View File

@@ -7,6 +7,7 @@
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Attestor.Persistence</RootNamespace>
<Description>Proof chain persistence layer with Entity Framework Core and PostgreSQL support.</Description>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

View File

@@ -7,4 +7,4 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| --- | --- | --- |
| AUDIT-0060-M | DONE | Maintainability audit for StellaOps.Attestor.Persistence. |
| AUDIT-0060-T | DONE | Test coverage audit for StellaOps.Attestor.Persistence. |
| AUDIT-0060-A | TODO | Pending approval for changes. |
| AUDIT-0060-A | DONE | Applied defaults, normalization, deterministic matching, perf script, tests. |