Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -9,9 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace StellaOps.Policy.Persistence.EfCore.Context;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core DbContext for Policy module.
|
||||
/// This is a stub that will be scaffolded from the PostgreSQL database.
|
||||
/// </summary>
|
||||
public class PolicyDbContext : DbContext
|
||||
{
|
||||
public PolicyDbContext(DbContextOptions<PolicyDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("policy");
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
using StellaOps.Policy.Persistence.Postgres;
|
||||
using StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
using IAuditableExceptionRepository = StellaOps.Policy.Exceptions.Repositories.IExceptionRepository;
|
||||
// Use local repository interfaces (not the ones from StellaOps.Policy.Storage or StellaOps.Policy)
|
||||
using ILocalRiskProfileRepository = StellaOps.Policy.Persistence.Postgres.Repositories.IRiskProfileRepository;
|
||||
using ILocalPolicyAuditRepository = StellaOps.Policy.Persistence.Postgres.Repositories.IPolicyAuditRepository;
|
||||
|
||||
namespace StellaOps.Policy.Persistence.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring Policy persistence services.
|
||||
/// </summary>
|
||||
public static class PolicyPersistenceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Policy PostgreSQL persistence services.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="sectionName">Configuration section name for PostgreSQL options.</param>
|
||||
/// <returns>Service collection for chaining.</returns>
|
||||
public static IServiceCollection AddPolicyPersistence(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string sectionName = "Postgres:Policy")
|
||||
{
|
||||
services.Configure<PostgresOptions>(sectionName, configuration.GetSection(sectionName));
|
||||
services.AddSingleton<PolicyDataSource>();
|
||||
|
||||
// Register repositories
|
||||
services.AddScoped<IPackRepository, PackRepository>();
|
||||
services.AddScoped<IPackVersionRepository, PackVersionRepository>();
|
||||
services.AddScoped<IRuleRepository, RuleRepository>();
|
||||
services.AddScoped<ILocalRiskProfileRepository, RiskProfileRepository>();
|
||||
services.AddScoped<IEvaluationRunRepository, EvaluationRunRepository>();
|
||||
services.AddScoped<IExceptionRepository, ExceptionRepository>();
|
||||
services.AddScoped<IAuditableExceptionRepository, PostgresExceptionObjectRepository>();
|
||||
services.AddScoped<IReceiptRepository, PostgresReceiptRepository>();
|
||||
services.AddScoped<IExplanationRepository, ExplanationRepository>();
|
||||
services.AddScoped<ILocalPolicyAuditRepository, PolicyAuditRepository>();
|
||||
services.AddScoped<ISnapshotRepository, SnapshotRepository>();
|
||||
services.AddScoped<IViolationEventRepository, ViolationEventRepository>();
|
||||
services.AddScoped<IConflictRepository, ConflictRepository>();
|
||||
services.AddScoped<ILedgerExportRepository, LedgerExportRepository>();
|
||||
services.AddScoped<IWorkerResultRepository, WorkerResultRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Policy PostgreSQL persistence services with explicit options.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configureOptions">Options configuration action.</param>
|
||||
/// <returns>Service collection for chaining.</returns>
|
||||
public static IServiceCollection AddPolicyPersistence(
|
||||
this IServiceCollection services,
|
||||
Action<PostgresOptions> configureOptions)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
services.AddSingleton<PolicyDataSource>();
|
||||
|
||||
// Register repositories
|
||||
services.AddScoped<IPackRepository, PackRepository>();
|
||||
services.AddScoped<IPackVersionRepository, PackVersionRepository>();
|
||||
services.AddScoped<IRuleRepository, RuleRepository>();
|
||||
services.AddScoped<ILocalRiskProfileRepository, RiskProfileRepository>();
|
||||
services.AddScoped<IEvaluationRunRepository, EvaluationRunRepository>();
|
||||
services.AddScoped<IExceptionRepository, ExceptionRepository>();
|
||||
services.AddScoped<IAuditableExceptionRepository, PostgresExceptionObjectRepository>();
|
||||
services.AddScoped<IReceiptRepository, PostgresReceiptRepository>();
|
||||
services.AddScoped<IExplanationRepository, ExplanationRepository>();
|
||||
services.AddScoped<ILocalPolicyAuditRepository, PolicyAuditRepository>();
|
||||
services.AddScoped<ISnapshotRepository, SnapshotRepository>();
|
||||
services.AddScoped<IViolationEventRepository, ViolationEventRepository>();
|
||||
services.AddScoped<IConflictRepository, ConflictRepository>();
|
||||
services.AddScoped<ILedgerExportRepository, LedgerExportRepository>();
|
||||
services.AddScoped<IWorkerResultRepository, WorkerResultRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
# Archived Pre-1.0 Migrations
|
||||
|
||||
This directory contains the original migrations that were compacted into `001_initial_schema.sql`
|
||||
for the 1.0.0 release.
|
||||
|
||||
## Original Files
|
||||
- `001_initial_schema.sql` - Packs, rules, risk profiles, evaluation runs
|
||||
- `002_cvss_receipts.sql` - CVSS scoring receipts
|
||||
- `003_snapshots_violations.sql` - Policy snapshots, violation events
|
||||
- `004_epss_risk_scores.sql` - EPSS scores, combined risk scoring
|
||||
- `005_cvss_multiversion.sql` - CVSS v2/v3/v4 support
|
||||
- `006_enable_rls.sql` - Row-Level Security
|
||||
- `007_unknowns_registry.sql` - Unknowns tracking
|
||||
- `008_exception_objects.sql` - Exception enhancements
|
||||
- `009_exception_applications.sql` - Exception application records
|
||||
- `010_recheck_evidence.sql` - Recheck policies, evidence hooks
|
||||
- `010_unknowns_blast_radius_containment.sql` - Containment columns (duplicate prefix)
|
||||
- `011_unknowns_reason_codes.sql` - Reason codes and hints
|
||||
- `012_budget_ledger.sql` - Risk budget tracking
|
||||
- `013_exception_approval.sql` - Approval workflow
|
||||
|
||||
## Why Archived
|
||||
Pre-1.0, the schema evolved incrementally. For 1.0.0, migrations were compacted into a single
|
||||
initial schema to:
|
||||
- Simplify new deployments
|
||||
- Reduce startup time
|
||||
- Provide cleaner upgrade path
|
||||
|
||||
## For Existing Deployments
|
||||
If upgrading from pre-1.0, run the reset script directly with psql:
|
||||
```bash
|
||||
psql -h <host> -U <user> -d <db> -f devops/scripts/migrations-reset-pre-1.0.sql
|
||||
```
|
||||
This updates `schema_migrations` to recognize the compacted schema.
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Migration;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Migration;
|
||||
|
||||
/// <summary>
|
||||
/// Converts legacy policy documents (as JSON) to migration data transfer objects.
|
||||
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Migration;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Migration;
|
||||
|
||||
/// <summary>
|
||||
/// Handles migration of policy data from legacy storage to PostgreSQL.
|
||||
@@ -8,7 +8,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a risk budget for a service within a time window.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a policy conflict for resolution.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluation run status enumeration.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Approval request status enumeration.
|
||||
@@ -56,7 +56,7 @@ public enum ExceptionReasonCode
|
||||
/// <summary>
|
||||
/// Entity representing an exception approval request.
|
||||
/// </summary>
|
||||
public sealed class ExceptionApprovalRequestEntity
|
||||
public sealed record ExceptionApprovalRequestEntity
|
||||
{
|
||||
/// <summary>Unique identifier.</summary>
|
||||
public required Guid Id { get; init; }
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Exception status enumeration.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Rule evaluation result enumeration.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a ledger export operation.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a policy pack (container for rules).
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing an immutable policy pack version.
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing an audit log entry for the policy module.
|
||||
/// </summary>
|
||||
public sealed class PolicyAuditEntity
|
||||
{
|
||||
public long Id { get; init; }
|
||||
public required string TenantId { get; init; }
|
||||
public Guid? UserId { get; init; }
|
||||
public required string Action { get; init; }
|
||||
public required string ResourceType { get; init; }
|
||||
public string? ResourceId { get; init; }
|
||||
public string? OldValue { get; init; }
|
||||
public string? NewValue { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
// VEX Trust audit fields (SPRINT_1227_0004_0003)
|
||||
|
||||
/// <summary>
|
||||
/// VEX trust composite score (0.0 - 1.0) at decision time.
|
||||
/// </summary>
|
||||
public decimal? VexTrustScore { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// VEX trust tier at decision time (VeryHigh, High, Medium, Low, VeryLow).
|
||||
/// </summary>
|
||||
public string? VexTrustTier { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the VEX signature was cryptographically verified.
|
||||
/// </summary>
|
||||
public bool? VexSignatureVerified { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// VEX issuer identifier.
|
||||
/// </summary>
|
||||
public string? VexIssuerId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// VEX issuer display name.
|
||||
/// </summary>
|
||||
public string? VexIssuerName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// VEX trust gate result (Allow, Warn, Block).
|
||||
/// </summary>
|
||||
public string? VexTrustGateResult { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason code for the VEX trust gate decision.
|
||||
/// </summary>
|
||||
public string? VexTrustGateReason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Signature verification method used (dsse, cosign, pgp, x509).
|
||||
/// </summary>
|
||||
public string? VexSignatureMethod { get; init; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a risk scoring profile.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Rule type enumeration.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing an immutable policy configuration snapshot.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing an append-only violation event.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing a background worker job result.
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres;
|
||||
namespace StellaOps.Policy.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL data source for the Policy module.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for conflict detection and resolution operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy evaluation run operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for exception approval workflow operations.
|
||||
@@ -699,7 +699,7 @@ public sealed class ExceptionApprovalRepository : RepositoryBase<PolicyDataSourc
|
||||
return reader.GetFieldValue<string[]>(ordinal) ?? [];
|
||||
}
|
||||
|
||||
private static DateTimeOffset? GetNullableDateTimeOffset(NpgsqlDataReader reader, int ordinal)
|
||||
private new static DateTimeOffset? GetNullableDateTimeOffset(NpgsqlDataReader reader, int ordinal)
|
||||
{
|
||||
return reader.IsDBNull(ordinal) ? null : reader.GetFieldValue<DateTimeOffset>(ordinal);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy exception operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for explanation operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for conflict detection and resolution operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy evaluation run operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for exception approval workflow operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy exception operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for explanation operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for ledger export operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy pack operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy pack version operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy audit operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for risk profile operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy rule operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for policy snapshot operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for append-only violation event operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for worker result operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for ledger export operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy pack operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy pack version operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy audit operations.
|
||||
@@ -9,9 +9,9 @@ using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Gates;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL implementation of budget storage.
|
||||
@@ -36,7 +36,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
return await QuerySingleOrDefaultAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -61,7 +61,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
await ExecuteAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -92,7 +92,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
await ExecuteAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -119,7 +119,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
await ExecuteAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -148,7 +148,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
return await QueryAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -180,7 +180,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
sql += " ORDER BY updated_at DESC LIMIT @limit";
|
||||
|
||||
return await QueryAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -221,7 +221,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
sql += " ORDER BY window DESC, service_id";
|
||||
|
||||
return await QueryAsync(
|
||||
tenantId,
|
||||
tenantId ?? string.Empty,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
@@ -251,7 +251,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
return await QueryAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "status", status.ToString().ToLowerInvariant()),
|
||||
MapRiskBudget,
|
||||
@@ -286,7 +286,7 @@ public sealed class PostgresBudgetStore : RepositoryBase<PolicyDataSource>, IBud
|
||||
""";
|
||||
|
||||
return await ExecuteAsync(
|
||||
null,
|
||||
null!,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "new_window", newWindow),
|
||||
ct).ConfigureAwait(false);
|
||||
@@ -8,7 +8,7 @@ using StellaOps.Policy.Exceptions.Models;
|
||||
using StellaOps.Policy.Exceptions.Repositories;
|
||||
using IAuditableExceptionRepository = StellaOps.Policy.Exceptions.Repositories.IExceptionRepository;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository implementation for auditable exception objects.
|
||||
@@ -8,7 +8,7 @@ using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Scoring;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for CVSS score receipts.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for risk profile operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy rule operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for policy snapshot operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for append-only violation event operations.
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.Policy.Storage.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
namespace StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL repository for worker result operations.
|
||||
@@ -3,13 +3,13 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Infrastructure.Postgres;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
using StellaOps.Policy.Storage.Postgres.Repositories;
|
||||
using StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
using IAuditableExceptionRepository = StellaOps.Policy.Exceptions.Repositories.IExceptionRepository;
|
||||
// Use local repository interfaces (not the ones from StellaOps.Policy.Storage or StellaOps.Policy)
|
||||
using ILocalRiskProfileRepository = StellaOps.Policy.Storage.Postgres.Repositories.IRiskProfileRepository;
|
||||
using ILocalPolicyAuditRepository = StellaOps.Policy.Storage.Postgres.Repositories.IPolicyAuditRepository;
|
||||
using ILocalRiskProfileRepository = StellaOps.Policy.Persistence.Postgres.Repositories.IRiskProfileRepository;
|
||||
using ILocalPolicyAuditRepository = StellaOps.Policy.Persistence.Postgres.Repositories.IPolicyAuditRepository;
|
||||
|
||||
namespace StellaOps.Policy.Storage.Postgres;
|
||||
namespace StellaOps.Policy.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring Policy PostgreSQL storage services.
|
||||
@@ -7,18 +7,30 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<RootNamespace>StellaOps.Policy.Storage.Postgres</RootNamespace>
|
||||
<RootNamespace>StellaOps.Policy.Persistence</RootNamespace>
|
||||
<AssemblyName>StellaOps.Policy.Persistence</AssemblyName>
|
||||
<Description>Consolidated persistence layer for StellaOps Policy module</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Migrations\**\*.sql" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Policy.Scoring\StellaOps.Policy.Scoring.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Policy.Exceptions\StellaOps.Policy.Exceptions.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Policy\StellaOps.Policy.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.EfCore\StellaOps.Infrastructure.EfCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,34 +0,0 @@
|
||||
# StellaOps.Policy.Storage.Postgres - Agent Charter
|
||||
|
||||
## Mission
|
||||
- Provide deterministic PostgreSQL persistence for Policy module data (packs, risk profiles, exceptions, unknowns).
|
||||
- Keep migrations idempotent, RLS-safe, and replayable in air-gapped environments.
|
||||
|
||||
## Roles
|
||||
- Backend / database engineer (.NET 10, C# preview, PostgreSQL).
|
||||
- QA engineer (integration tests with Postgres fixtures).
|
||||
|
||||
## Required Reading (treat as read before DOING)
|
||||
- `docs/modules/policy/architecture.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- Current sprint file in `docs/implplan/SPRINT_*.md`
|
||||
|
||||
## Working Directory & Boundaries
|
||||
- Primary scope: `src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/**`.
|
||||
- Migrations: `src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/migrations/**`.
|
||||
- Tests: `src/Policy/__Tests/StellaOps.Policy.Storage.Postgres.Tests/**`.
|
||||
- Avoid cross-module edits unless the sprint explicitly allows.
|
||||
|
||||
## Determinism & Offline Rules
|
||||
- Use UTC timestamps and stable ordering.
|
||||
- Keep migrations deterministic (no volatile defaults or nondeterministic functions).
|
||||
- No external network calls in repositories or tests.
|
||||
|
||||
## Testing Expectations
|
||||
- Add/adjust integration tests for repository and migration changes.
|
||||
- Use `PolicyPostgresFixture` and truncate tables between tests.
|
||||
- Validate JSON serialization order and default values where applicable.
|
||||
|
||||
## Workflow
|
||||
- Update task status to `DOING`/`DONE` in the sprint file.
|
||||
- Record schema or contract changes in sprint `Decisions & Risks` and update docs when needed.
|
||||
@@ -1,220 +0,0 @@
|
||||
-- Policy Schema Migration 001: Initial Schema
|
||||
-- Creates the policy schema for packs, rules, and risk profiles
|
||||
|
||||
-- Create schema
|
||||
CREATE SCHEMA IF NOT EXISTS policy;
|
||||
|
||||
-- Packs table (policy pack containers)
|
||||
CREATE TABLE IF NOT EXISTS policy.packs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
active_version INT,
|
||||
is_builtin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_deprecated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_packs_tenant ON policy.packs(tenant_id);
|
||||
CREATE INDEX idx_packs_builtin ON policy.packs(is_builtin);
|
||||
|
||||
-- Pack versions table (immutable versions)
|
||||
CREATE TABLE IF NOT EXISTS policy.pack_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
pack_id UUID NOT NULL REFERENCES policy.packs(id) ON DELETE CASCADE,
|
||||
version INT NOT NULL,
|
||||
description TEXT,
|
||||
rules_hash TEXT NOT NULL,
|
||||
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
published_at TIMESTAMPTZ,
|
||||
published_by TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
UNIQUE(pack_id, version)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pack_versions_pack ON policy.pack_versions(pack_id);
|
||||
CREATE INDEX idx_pack_versions_published ON policy.pack_versions(pack_id, is_published);
|
||||
|
||||
-- Rules table (OPA/Rego rules)
|
||||
CREATE TABLE IF NOT EXISTS policy.rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
pack_version_id UUID NOT NULL REFERENCES policy.pack_versions(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
rule_type TEXT NOT NULL DEFAULT 'rego' CHECK (rule_type IN ('rego', 'json', 'yaml')),
|
||||
content TEXT NOT NULL,
|
||||
content_hash TEXT NOT NULL,
|
||||
severity TEXT NOT NULL DEFAULT 'medium' CHECK (severity IN ('critical', 'high', 'medium', 'low', 'info')),
|
||||
category TEXT,
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(pack_version_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rules_pack_version ON policy.rules(pack_version_id);
|
||||
CREATE INDEX idx_rules_severity ON policy.rules(severity);
|
||||
CREATE INDEX idx_rules_category ON policy.rules(category);
|
||||
CREATE INDEX idx_rules_tags ON policy.rules USING GIN(tags);
|
||||
|
||||
-- Risk profiles table
|
||||
CREATE TABLE IF NOT EXISTS policy.risk_profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
thresholds JSONB NOT NULL DEFAULT '{}',
|
||||
scoring_weights JSONB NOT NULL DEFAULT '{}',
|
||||
exemptions JSONB NOT NULL DEFAULT '[]',
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
UNIQUE(tenant_id, name, version)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_risk_profiles_tenant ON policy.risk_profiles(tenant_id);
|
||||
CREATE INDEX idx_risk_profiles_active ON policy.risk_profiles(tenant_id, name, is_active)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
-- Risk profile history (for audit trail)
|
||||
CREATE TABLE IF NOT EXISTS policy.risk_profile_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
risk_profile_id UUID NOT NULL REFERENCES policy.risk_profiles(id),
|
||||
version INT NOT NULL,
|
||||
thresholds JSONB NOT NULL,
|
||||
scoring_weights JSONB NOT NULL,
|
||||
exemptions JSONB NOT NULL,
|
||||
changed_by TEXT,
|
||||
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
change_reason TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_risk_profile_history_profile ON policy.risk_profile_history(risk_profile_id);
|
||||
|
||||
-- Evaluation runs table
|
||||
CREATE TABLE IF NOT EXISTS policy.evaluation_runs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
project_id TEXT,
|
||||
artifact_id TEXT,
|
||||
pack_id UUID REFERENCES policy.packs(id),
|
||||
pack_version INT,
|
||||
risk_profile_id UUID REFERENCES policy.risk_profiles(id),
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed')),
|
||||
result TEXT CHECK (result IN ('pass', 'fail', 'warn', 'error')),
|
||||
score NUMERIC(5,2),
|
||||
findings_count INT NOT NULL DEFAULT 0,
|
||||
critical_count INT NOT NULL DEFAULT 0,
|
||||
high_count INT NOT NULL DEFAULT 0,
|
||||
medium_count INT NOT NULL DEFAULT 0,
|
||||
low_count INT NOT NULL DEFAULT 0,
|
||||
input_hash TEXT,
|
||||
duration_ms INT,
|
||||
error_message TEXT,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_by TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_evaluation_runs_tenant ON policy.evaluation_runs(tenant_id);
|
||||
CREATE INDEX idx_evaluation_runs_project ON policy.evaluation_runs(tenant_id, project_id);
|
||||
CREATE INDEX idx_evaluation_runs_artifact ON policy.evaluation_runs(tenant_id, artifact_id);
|
||||
CREATE INDEX idx_evaluation_runs_created ON policy.evaluation_runs(tenant_id, created_at);
|
||||
CREATE INDEX idx_evaluation_runs_status ON policy.evaluation_runs(status);
|
||||
|
||||
-- Explanations table (rule evaluation details)
|
||||
CREATE TABLE IF NOT EXISTS policy.explanations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
evaluation_run_id UUID NOT NULL REFERENCES policy.evaluation_runs(id) ON DELETE CASCADE,
|
||||
rule_id UUID REFERENCES policy.rules(id),
|
||||
rule_name TEXT NOT NULL,
|
||||
result TEXT NOT NULL CHECK (result IN ('pass', 'fail', 'skip', 'error')),
|
||||
severity TEXT NOT NULL,
|
||||
message TEXT,
|
||||
details JSONB NOT NULL DEFAULT '{}',
|
||||
remediation TEXT,
|
||||
resource_path TEXT,
|
||||
line_number INT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_explanations_run ON policy.explanations(evaluation_run_id);
|
||||
CREATE INDEX idx_explanations_result ON policy.explanations(evaluation_run_id, result);
|
||||
|
||||
-- Exceptions table (policy exceptions/waivers)
|
||||
CREATE TABLE IF NOT EXISTS policy.exceptions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
rule_pattern TEXT,
|
||||
resource_pattern TEXT,
|
||||
artifact_pattern TEXT,
|
||||
project_id TEXT,
|
||||
reason TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'revoked')),
|
||||
expires_at TIMESTAMPTZ,
|
||||
approved_by TEXT,
|
||||
approved_at TIMESTAMPTZ,
|
||||
revoked_by TEXT,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
metadata JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by TEXT,
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_exceptions_tenant ON policy.exceptions(tenant_id);
|
||||
CREATE INDEX idx_exceptions_status ON policy.exceptions(tenant_id, status);
|
||||
CREATE INDEX idx_exceptions_expires ON policy.exceptions(expires_at)
|
||||
WHERE status = 'active';
|
||||
CREATE INDEX idx_exceptions_project ON policy.exceptions(tenant_id, project_id);
|
||||
|
||||
-- Audit log table
|
||||
CREATE TABLE IF NOT EXISTS policy.audit (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
user_id UUID,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT,
|
||||
old_value JSONB,
|
||||
new_value JSONB,
|
||||
correlation_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_tenant ON policy.audit(tenant_id);
|
||||
CREATE INDEX idx_audit_resource ON policy.audit(resource_type, resource_id);
|
||||
CREATE INDEX idx_audit_created ON policy.audit(tenant_id, created_at);
|
||||
|
||||
-- Update timestamp function
|
||||
CREATE OR REPLACE FUNCTION policy.update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Triggers
|
||||
CREATE TRIGGER trg_packs_updated_at
|
||||
BEFORE UPDATE ON policy.packs
|
||||
FOR EACH ROW EXECUTE FUNCTION policy.update_updated_at();
|
||||
|
||||
CREATE TRIGGER trg_risk_profiles_updated_at
|
||||
BEFORE UPDATE ON policy.risk_profiles
|
||||
FOR EACH ROW EXECUTE FUNCTION policy.update_updated_at();
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace StellaOps.Policy.Storage.Postgres.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entity representing an audit log entry for the policy module.
|
||||
/// </summary>
|
||||
public sealed class PolicyAuditEntity
|
||||
{
|
||||
public long Id { get; init; }
|
||||
public required string TenantId { get; init; }
|
||||
public Guid? UserId { get; init; }
|
||||
public required string Action { get; init; }
|
||||
public required string ResourceType { get; init; }
|
||||
public string? ResourceId { get; init; }
|
||||
public string? OldValue { get; init; }
|
||||
public string? NewValue { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Dapper" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// CryptoAsset - Local types for crypto risk evaluation
|
||||
// These types mirror Scanner.Emit.Cbom types to avoid circular dependency
|
||||
// Sprint: SPRINT_1227_0013_0001_LB_cyclonedx_cbom
|
||||
// Task 7: Policy Integration - Crypto Risk Rules
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Policy.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Extracted cryptographic asset for policy evaluation.
|
||||
/// This is a local type that mirrors the Scanner.Emit.Cbom.CryptoAsset to avoid circular dependencies.
|
||||
/// </summary>
|
||||
public sealed record CryptoAsset
|
||||
{
|
||||
/// <summary>Unique identifier for this crypto asset.</summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>Component key this asset belongs to.</summary>
|
||||
public required string ComponentKey { get; init; }
|
||||
|
||||
/// <summary>Algorithm name (e.g., "AES-256-GCM", "RSA-2048", "SHA-256").</summary>
|
||||
public string? AlgorithmName { get; init; }
|
||||
|
||||
/// <summary>OID if available (e.g., "2.16.840.1.101.3.4.1.46" for AES-256-GCM).</summary>
|
||||
public string? Oid { get; init; }
|
||||
|
||||
/// <summary>Key size in bits if applicable.</summary>
|
||||
public int? KeySizeBits { get; init; }
|
||||
|
||||
/// <summary>Confidence of detection (0.0 - 1.0).</summary>
|
||||
public double Confidence { get; init; } = 1.0;
|
||||
|
||||
/// <summary>Risk flags identified for this crypto asset.</summary>
|
||||
public ImmutableArray<CryptoRiskFlag> RiskFlags { get; init; } = ImmutableArray<CryptoRiskFlag>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Risk flag associated with a crypto asset.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskFlag
|
||||
{
|
||||
/// <summary>Risk identifier.</summary>
|
||||
public required string RiskId { get; init; }
|
||||
|
||||
/// <summary>Risk severity.</summary>
|
||||
public required CryptoRiskSeverity Severity { get; init; }
|
||||
|
||||
/// <summary>Human-readable description.</summary>
|
||||
public required string Description { get; init; }
|
||||
|
||||
/// <summary>Recommended action.</summary>
|
||||
public string? Recommendation { get; init; }
|
||||
}
|
||||
232
src/Policy/__Libraries/StellaOps.Policy/Crypto/CryptoAtoms.cs
Normal file
232
src/Policy/__Libraries/StellaOps.Policy/Crypto/CryptoAtoms.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
// CryptoAtoms - Security propositions for cryptographic risk assessment
|
||||
// Sprint: SPRINT_1227_0013_0001_LB_cyclonedx_cbom
|
||||
// Task 7: Policy Integration - Crypto Risk Rules
|
||||
|
||||
namespace StellaOps.Policy.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographic security propositions for risk assessment.
|
||||
/// These atoms represent crypto-specific risks that can be evaluated by the policy engine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used in conjunction with CBOM (Cryptographic Bill of Materials) to identify
|
||||
/// cryptographic weaknesses in software components.
|
||||
/// </remarks>
|
||||
public enum CryptoAtom
|
||||
{
|
||||
/// <summary>
|
||||
/// WEAK_CRYPTO: Component uses deprecated or weak cryptographic algorithms.
|
||||
/// True when algorithms like MD5, SHA-1, DES, 3DES, RC4, RC2, or Blowfish are detected.
|
||||
/// </summary>
|
||||
WeakCrypto = 100,
|
||||
|
||||
/// <summary>
|
||||
/// DEPRECATED_ALGO: Component uses a deprecated algorithm (subset of weak).
|
||||
/// True for MD5, SHA-1, DES, RC2, RC4 specifically.
|
||||
/// </summary>
|
||||
DeprecatedAlgorithm = 101,
|
||||
|
||||
/// <summary>
|
||||
/// QUANTUM_VULNERABLE: Component uses algorithms vulnerable to quantum attacks.
|
||||
/// True for RSA, DSA, ECDSA, ECDH, DH - any classical asymmetric crypto.
|
||||
/// </summary>
|
||||
QuantumVulnerable = 102,
|
||||
|
||||
/// <summary>
|
||||
/// SMALL_KEY: Component uses cryptographic keys below recommended sizes.
|
||||
/// True for RSA < 2048, ECDSA < 256, AES < 128, etc.
|
||||
/// </summary>
|
||||
SmallKey = 103,
|
||||
|
||||
/// <summary>
|
||||
/// ECB_MODE: Component uses ECB block cipher mode.
|
||||
/// True when AES-ECB or similar insecure modes are detected.
|
||||
/// </summary>
|
||||
EcbMode = 104,
|
||||
|
||||
/// <summary>
|
||||
/// NO_INTEGRITY: Component uses encryption without authentication (e.g., CBC without HMAC).
|
||||
/// True when unauthenticated encryption modes are used.
|
||||
/// </summary>
|
||||
NoIntegrity = 105,
|
||||
|
||||
/// <summary>
|
||||
/// POST_QUANTUM_READY: Component uses post-quantum safe algorithms.
|
||||
/// True for ML-KEM, ML-DSA, SLH-DSA, SPHINCS+, etc.
|
||||
/// </summary>
|
||||
PostQuantumReady = 106,
|
||||
|
||||
/// <summary>
|
||||
/// HARDWARE_BOUND: Crypto operations are hardware-bound (HSM, TPM, TEE).
|
||||
/// True when execution environment is hardware-protected.
|
||||
/// </summary>
|
||||
HardwareBound = 107,
|
||||
|
||||
/// <summary>
|
||||
/// FIPS_VALIDATED: Algorithm implementation is FIPS 140-2/3 validated.
|
||||
/// True when using certified crypto modules.
|
||||
/// </summary>
|
||||
FipsValidated = 108,
|
||||
|
||||
/// <summary>
|
||||
/// EXPIRED_CERT: Component contains or uses expired certificates.
|
||||
/// True when certificate notAfter is in the past.
|
||||
/// </summary>
|
||||
ExpiredCertificate = 109,
|
||||
|
||||
/// <summary>
|
||||
/// WEAK_HASH_FOR_SIGNING: Weak hash used for digital signatures.
|
||||
/// True when MD5 or SHA-1 is used for signature generation.
|
||||
/// </summary>
|
||||
WeakHashForSigning = 110,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for CryptoAtom.
|
||||
/// </summary>
|
||||
public static class CryptoAtomExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a human-readable display name for the atom.
|
||||
/// </summary>
|
||||
public static string ToDisplayName(this CryptoAtom atom) => atom switch
|
||||
{
|
||||
CryptoAtom.WeakCrypto => "Weak Cryptography",
|
||||
CryptoAtom.DeprecatedAlgorithm => "Deprecated Algorithm",
|
||||
CryptoAtom.QuantumVulnerable => "Quantum Vulnerable",
|
||||
CryptoAtom.SmallKey => "Insufficient Key Size",
|
||||
CryptoAtom.EcbMode => "ECB Mode Usage",
|
||||
CryptoAtom.NoIntegrity => "Missing Integrity Protection",
|
||||
CryptoAtom.PostQuantumReady => "Post-Quantum Ready",
|
||||
CryptoAtom.HardwareBound => "Hardware-Bound Crypto",
|
||||
CryptoAtom.FipsValidated => "FIPS Validated",
|
||||
CryptoAtom.ExpiredCertificate => "Expired Certificate",
|
||||
CryptoAtom.WeakHashForSigning => "Weak Hash for Signing",
|
||||
_ => atom.ToString(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns the canonical string representation for serialization.
|
||||
/// </summary>
|
||||
public static string ToCanonicalName(this CryptoAtom atom) => atom switch
|
||||
{
|
||||
CryptoAtom.WeakCrypto => "WEAK_CRYPTO",
|
||||
CryptoAtom.DeprecatedAlgorithm => "DEPRECATED_ALGO",
|
||||
CryptoAtom.QuantumVulnerable => "QUANTUM_VULNERABLE",
|
||||
CryptoAtom.SmallKey => "SMALL_KEY",
|
||||
CryptoAtom.EcbMode => "ECB_MODE",
|
||||
CryptoAtom.NoIntegrity => "NO_INTEGRITY",
|
||||
CryptoAtom.PostQuantumReady => "POST_QUANTUM_READY",
|
||||
CryptoAtom.HardwareBound => "HARDWARE_BOUND",
|
||||
CryptoAtom.FipsValidated => "FIPS_VALIDATED",
|
||||
CryptoAtom.ExpiredCertificate => "EXPIRED_CERT",
|
||||
CryptoAtom.WeakHashForSigning => "WEAK_HASH_FOR_SIGNING",
|
||||
_ => atom.ToString().ToUpperInvariant(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parses a canonical name to CryptoAtom.
|
||||
/// </summary>
|
||||
public static CryptoAtom? FromCanonicalName(string name)
|
||||
{
|
||||
return name?.ToUpperInvariant() switch
|
||||
{
|
||||
"WEAK_CRYPTO" => CryptoAtom.WeakCrypto,
|
||||
"DEPRECATED_ALGO" => CryptoAtom.DeprecatedAlgorithm,
|
||||
"QUANTUM_VULNERABLE" => CryptoAtom.QuantumVulnerable,
|
||||
"SMALL_KEY" => CryptoAtom.SmallKey,
|
||||
"ECB_MODE" => CryptoAtom.EcbMode,
|
||||
"NO_INTEGRITY" => CryptoAtom.NoIntegrity,
|
||||
"POST_QUANTUM_READY" => CryptoAtom.PostQuantumReady,
|
||||
"HARDWARE_BOUND" => CryptoAtom.HardwareBound,
|
||||
"FIPS_VALIDATED" => CryptoAtom.FipsValidated,
|
||||
"EXPIRED_CERT" => CryptoAtom.ExpiredCertificate,
|
||||
"WEAK_HASH_FOR_SIGNING" => CryptoAtom.WeakHashForSigning,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all defined crypto atoms.
|
||||
/// </summary>
|
||||
public static IEnumerable<CryptoAtom> All()
|
||||
{
|
||||
yield return CryptoAtom.WeakCrypto;
|
||||
yield return CryptoAtom.DeprecatedAlgorithm;
|
||||
yield return CryptoAtom.QuantumVulnerable;
|
||||
yield return CryptoAtom.SmallKey;
|
||||
yield return CryptoAtom.EcbMode;
|
||||
yield return CryptoAtom.NoIntegrity;
|
||||
yield return CryptoAtom.PostQuantumReady;
|
||||
yield return CryptoAtom.HardwareBound;
|
||||
yield return CryptoAtom.FipsValidated;
|
||||
yield return CryptoAtom.ExpiredCertificate;
|
||||
yield return CryptoAtom.WeakHashForSigning;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all risk atoms (those indicating security concerns).
|
||||
/// </summary>
|
||||
public static IEnumerable<CryptoAtom> RiskAtoms()
|
||||
{
|
||||
yield return CryptoAtom.WeakCrypto;
|
||||
yield return CryptoAtom.DeprecatedAlgorithm;
|
||||
yield return CryptoAtom.QuantumVulnerable;
|
||||
yield return CryptoAtom.SmallKey;
|
||||
yield return CryptoAtom.EcbMode;
|
||||
yield return CryptoAtom.NoIntegrity;
|
||||
yield return CryptoAtom.ExpiredCertificate;
|
||||
yield return CryptoAtom.WeakHashForSigning;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all positive indicator atoms (those indicating good security posture).
|
||||
/// </summary>
|
||||
public static IEnumerable<CryptoAtom> PositiveIndicators()
|
||||
{
|
||||
yield return CryptoAtom.PostQuantumReady;
|
||||
yield return CryptoAtom.HardwareBound;
|
||||
yield return CryptoAtom.FipsValidated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default severity for this atom when triggered.
|
||||
/// </summary>
|
||||
public static CryptoRiskSeverity GetDefaultSeverity(this CryptoAtom atom) => atom switch
|
||||
{
|
||||
CryptoAtom.WeakCrypto => CryptoRiskSeverity.High,
|
||||
CryptoAtom.DeprecatedAlgorithm => CryptoRiskSeverity.Critical,
|
||||
CryptoAtom.QuantumVulnerable => CryptoRiskSeverity.Medium,
|
||||
CryptoAtom.SmallKey => CryptoRiskSeverity.High,
|
||||
CryptoAtom.EcbMode => CryptoRiskSeverity.High,
|
||||
CryptoAtom.NoIntegrity => CryptoRiskSeverity.Medium,
|
||||
CryptoAtom.ExpiredCertificate => CryptoRiskSeverity.Critical,
|
||||
CryptoAtom.WeakHashForSigning => CryptoRiskSeverity.Critical,
|
||||
// Positive indicators are not risks
|
||||
CryptoAtom.PostQuantumReady => CryptoRiskSeverity.Info,
|
||||
CryptoAtom.HardwareBound => CryptoRiskSeverity.Info,
|
||||
CryptoAtom.FipsValidated => CryptoRiskSeverity.Info,
|
||||
_ => CryptoRiskSeverity.Low,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Risk severity levels for crypto findings.
|
||||
/// </summary>
|
||||
public enum CryptoRiskSeverity
|
||||
{
|
||||
/// <summary>Informational only (positive indicators).</summary>
|
||||
Info = 0,
|
||||
|
||||
/// <summary>Low risk, may need future attention.</summary>
|
||||
Low = 1,
|
||||
|
||||
/// <summary>Medium risk, should be planned for remediation.</summary>
|
||||
Medium = 2,
|
||||
|
||||
/// <summary>High risk, needs prompt attention.</summary>
|
||||
High = 3,
|
||||
|
||||
/// <summary>Critical risk, immediate action required.</summary>
|
||||
Critical = 4
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// CryptoRiskRules - Default crypto risk evaluation rules
|
||||
// Sprint: SPRINT_1227_0013_0001_LB_cyclonedx_cbom
|
||||
// Task 7: Policy Integration - Crypto Risk Rules
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Policy.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Service for evaluating crypto risk rules against CBOM data.
|
||||
/// </summary>
|
||||
public interface ICryptoRiskEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates crypto assets and returns triggered atoms with findings.
|
||||
/// </summary>
|
||||
CryptoRiskEvaluationResult Evaluate(ImmutableArray<CryptoAsset> assets, CryptoRiskRuleSet ruleSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of crypto risk evaluation.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskEvaluationResult
|
||||
{
|
||||
/// <summary>All triggered atoms.</summary>
|
||||
public required ImmutableArray<CryptoAtomFinding> Findings { get; init; }
|
||||
|
||||
/// <summary>Atoms grouped by severity.</summary>
|
||||
public required ImmutableDictionary<CryptoRiskSeverity, int> CountBySeverity { get; init; }
|
||||
|
||||
/// <summary>Whether any blocking findings exist (Critical severity with block action).</summary>
|
||||
public bool HasBlockingFindings { get; init; }
|
||||
|
||||
/// <summary>Overall crypto risk score (0-100, higher = worse).</summary>
|
||||
public double RiskScore { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A finding representing a triggered crypto atom.
|
||||
/// </summary>
|
||||
public sealed record CryptoAtomFinding
|
||||
{
|
||||
/// <summary>The triggered atom.</summary>
|
||||
public required CryptoAtom Atom { get; init; }
|
||||
|
||||
/// <summary>Severity of this finding.</summary>
|
||||
public required CryptoRiskSeverity Severity { get; init; }
|
||||
|
||||
/// <summary>Component key that triggered this finding.</summary>
|
||||
public required string ComponentKey { get; init; }
|
||||
|
||||
/// <summary>Algorithm or asset that caused the finding.</summary>
|
||||
public string? TriggeringAsset { get; init; }
|
||||
|
||||
/// <summary>Human-readable message.</summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>Recommended action.</summary>
|
||||
public string? Recommendation { get; init; }
|
||||
|
||||
/// <summary>Policy action (Warn, Block, Allow).</summary>
|
||||
public CryptoRuleAction Action { get; init; } = CryptoRuleAction.Warn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actions a crypto rule can take.
|
||||
/// </summary>
|
||||
public enum CryptoRuleAction
|
||||
{
|
||||
/// <summary>Allow the crypto asset.</summary>
|
||||
Allow,
|
||||
|
||||
/// <summary>Warn about the crypto asset but allow.</summary>
|
||||
Warn,
|
||||
|
||||
/// <summary>Block the artifact due to crypto risk.</summary>
|
||||
Block
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of crypto risk rules to evaluate.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskRuleSet
|
||||
{
|
||||
/// <summary>Rules to evaluate.</summary>
|
||||
public required ImmutableArray<CryptoRiskRule> Rules { get; init; }
|
||||
|
||||
/// <summary>Whether to apply default rules if none match.</summary>
|
||||
public bool ApplyDefaults { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the default rule set.
|
||||
/// </summary>
|
||||
public static CryptoRiskRuleSet Default => new()
|
||||
{
|
||||
Rules = CryptoRiskRuleDefaults.DefaultRules,
|
||||
ApplyDefaults = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single crypto risk rule.
|
||||
/// </summary>
|
||||
public sealed record CryptoRiskRule
|
||||
{
|
||||
/// <summary>Rule identifier.</summary>
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
/// <summary>Human-readable name.</summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>Description of what this rule checks.</summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>Atom this rule triggers.</summary>
|
||||
public required CryptoAtom Atom { get; init; }
|
||||
|
||||
/// <summary>Severity when triggered.</summary>
|
||||
public required CryptoRiskSeverity Severity { get; init; }
|
||||
|
||||
/// <summary>Action when triggered.</summary>
|
||||
public required CryptoRuleAction Action { get; init; }
|
||||
|
||||
/// <summary>Algorithm patterns to match (regex).</summary>
|
||||
public ImmutableArray<string> AlgorithmPatterns { get; init; } = ImmutableArray<string>.Empty;
|
||||
|
||||
/// <summary>Message template (use {algorithm} for substitution).</summary>
|
||||
public required string MessageTemplate { get; init; }
|
||||
|
||||
/// <summary>Recommendation template.</summary>
|
||||
public string? RecommendationTemplate { get; init; }
|
||||
|
||||
/// <summary>Whether this rule is enabled.</summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default crypto risk rules.
|
||||
/// </summary>
|
||||
public static class CryptoRiskRuleDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Default set of crypto risk rules.
|
||||
/// </summary>
|
||||
public static ImmutableArray<CryptoRiskRule> DefaultRules { get; } = ImmutableArray.Create(
|
||||
// DEPRECATED_ALGO - Block MD5, SHA-1, DES, RC2, RC4
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-001",
|
||||
Name = "Block MD5 Usage",
|
||||
Description = "MD5 is cryptographically broken and should never be used for security",
|
||||
Atom = CryptoAtom.DeprecatedAlgorithm,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^MD5$", "^HMACMD5$", ".*MD5.*"),
|
||||
MessageTemplate = "Deprecated algorithm {algorithm} detected - MD5 is broken",
|
||||
RecommendationTemplate = "Replace MD5 with SHA-256 or SHA-3"
|
||||
},
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-002",
|
||||
Name = "Block SHA-1 Usage",
|
||||
Description = "SHA-1 is deprecated and vulnerable to collision attacks",
|
||||
Atom = CryptoAtom.DeprecatedAlgorithm,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^SHA-?1$", "^HMACSHA1$", ".*SHA-?1(?!\\d).*"),
|
||||
MessageTemplate = "Deprecated algorithm {algorithm} detected - SHA-1 is vulnerable",
|
||||
RecommendationTemplate = "Replace SHA-1 with SHA-256 or SHA-3"
|
||||
},
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-003",
|
||||
Name = "Block DES Usage",
|
||||
Description = "DES has insufficient key size and is easily brute-forced",
|
||||
Atom = CryptoAtom.DeprecatedAlgorithm,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^DES$", "^DESEDE$", "^3DES$", "^TRIPLEDES$", "^TRIPLE-DES$"),
|
||||
MessageTemplate = "Deprecated algorithm {algorithm} detected - DES/3DES are obsolete",
|
||||
RecommendationTemplate = "Replace with AES-256-GCM"
|
||||
},
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-004",
|
||||
Name = "Block RC4 Usage",
|
||||
Description = "RC4 has known biases and is no longer secure",
|
||||
Atom = CryptoAtom.DeprecatedAlgorithm,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^RC4$", "^ARCFOUR$"),
|
||||
MessageTemplate = "Deprecated algorithm {algorithm} detected - RC4 is broken",
|
||||
RecommendationTemplate = "Replace with ChaCha20-Poly1305 or AES-GCM"
|
||||
},
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-005",
|
||||
Name = "Block RC2 Usage",
|
||||
Description = "RC2 is obsolete and has insufficient security",
|
||||
Atom = CryptoAtom.DeprecatedAlgorithm,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^RC2$"),
|
||||
MessageTemplate = "Deprecated algorithm {algorithm} detected - RC2 is obsolete",
|
||||
RecommendationTemplate = "Replace with AES-256-GCM"
|
||||
},
|
||||
|
||||
// WEAK_CRYPTO - Warn for Blowfish, RIPEMD, small RSA
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-010",
|
||||
Name = "Warn Blowfish Usage",
|
||||
Description = "Blowfish has a small block size vulnerable to Sweet32",
|
||||
Atom = CryptoAtom.WeakCrypto,
|
||||
Severity = CryptoRiskSeverity.High,
|
||||
Action = CryptoRuleAction.Warn,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^BLOWFISH$", "^BF$"),
|
||||
MessageTemplate = "Weak algorithm {algorithm} detected - vulnerable to Sweet32",
|
||||
RecommendationTemplate = "Replace with AES-256-GCM or ChaCha20-Poly1305"
|
||||
},
|
||||
|
||||
// ECB_MODE - Block ECB mode
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-020",
|
||||
Name = "Block ECB Mode",
|
||||
Description = "ECB mode leaks information about plaintext patterns",
|
||||
Atom = CryptoAtom.EcbMode,
|
||||
Severity = CryptoRiskSeverity.High,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create(".*ECB.*", ".*-ECB$"),
|
||||
MessageTemplate = "Insecure mode {algorithm} detected - ECB leaks patterns",
|
||||
RecommendationTemplate = "Use GCM, CTR, or CBC mode with authentication"
|
||||
},
|
||||
|
||||
// QUANTUM_VULNERABLE - Warn for RSA, ECDSA, ECDH
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-030",
|
||||
Name = "Quantum Vulnerability Warning",
|
||||
Description = "Classical asymmetric cryptography is vulnerable to quantum attacks",
|
||||
Atom = CryptoAtom.QuantumVulnerable,
|
||||
Severity = CryptoRiskSeverity.Medium,
|
||||
Action = CryptoRuleAction.Warn,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^RSA$", "^DSA$", "^ECDSA$", "^ECDH$", "^DH$", "^ED25519$", "^ED448$", "^X25519$", "^X448$"),
|
||||
MessageTemplate = "Quantum-vulnerable algorithm {algorithm} detected",
|
||||
RecommendationTemplate = "Plan migration to ML-KEM, ML-DSA, or hybrid schemes"
|
||||
},
|
||||
|
||||
// POST_QUANTUM_READY - Positive indicator
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-040",
|
||||
Name = "Post-Quantum Ready",
|
||||
Description = "Component uses NIST post-quantum approved algorithms",
|
||||
Atom = CryptoAtom.PostQuantumReady,
|
||||
Severity = CryptoRiskSeverity.Info,
|
||||
Action = CryptoRuleAction.Allow,
|
||||
AlgorithmPatterns = ImmutableArray.Create("^ML-KEM.*", "^ML-DSA.*", "^SLH-DSA.*", "^KYBER.*", "^DILITHIUM.*", "^SPHINCS.*", "^FALCON.*"),
|
||||
MessageTemplate = "Post-quantum ready algorithm {algorithm} detected",
|
||||
RecommendationTemplate = null
|
||||
},
|
||||
|
||||
// WEAK_HASH_FOR_SIGNING
|
||||
new CryptoRiskRule
|
||||
{
|
||||
RuleId = "CRYPTO-050",
|
||||
Name = "Weak Hash for Signing",
|
||||
Description = "Using weak hash algorithm for digital signatures",
|
||||
Atom = CryptoAtom.WeakHashForSigning,
|
||||
Severity = CryptoRiskSeverity.Critical,
|
||||
Action = CryptoRuleAction.Block,
|
||||
AlgorithmPatterns = ImmutableArray.Create(".*WithMD5$", ".*WithSHA1$", "^RSA-MD5$", "^RSA-SHA1$", "^ECDSA-SHA1$"),
|
||||
MessageTemplate = "Weak hash for signing: {algorithm}",
|
||||
RecommendationTemplate = "Use SHA-256 or stronger for signatures"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of crypto risk evaluator.
|
||||
/// </summary>
|
||||
public sealed class CryptoRiskEvaluator : ICryptoRiskEvaluator
|
||||
{
|
||||
public CryptoRiskEvaluationResult Evaluate(ImmutableArray<CryptoAsset> assets, CryptoRiskRuleSet ruleSet)
|
||||
{
|
||||
var findings = new List<CryptoAtomFinding>();
|
||||
var countBySeverity = new Dictionary<CryptoRiskSeverity, int>
|
||||
{
|
||||
[CryptoRiskSeverity.Info] = 0,
|
||||
[CryptoRiskSeverity.Low] = 0,
|
||||
[CryptoRiskSeverity.Medium] = 0,
|
||||
[CryptoRiskSeverity.High] = 0,
|
||||
[CryptoRiskSeverity.Critical] = 0
|
||||
};
|
||||
|
||||
bool hasBlocking = false;
|
||||
double riskScore = 0;
|
||||
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
if (string.IsNullOrEmpty(asset.AlgorithmName))
|
||||
continue;
|
||||
|
||||
var alg = asset.AlgorithmName.ToUpperInvariant();
|
||||
|
||||
foreach (var rule in ruleSet.Rules.Where(r => r.Enabled))
|
||||
{
|
||||
if (MatchesRule(alg, rule))
|
||||
{
|
||||
var finding = CreateFinding(asset, rule);
|
||||
findings.Add(finding);
|
||||
countBySeverity[finding.Severity]++;
|
||||
|
||||
if (finding.Action == CryptoRuleAction.Block)
|
||||
{
|
||||
hasBlocking = true;
|
||||
}
|
||||
|
||||
// Add to risk score
|
||||
riskScore += GetSeverityScore(finding.Severity);
|
||||
}
|
||||
}
|
||||
|
||||
// Also include risk flags from the asset itself
|
||||
foreach (var flag in asset.RiskFlags)
|
||||
{
|
||||
countBySeverity[flag.Severity]++;
|
||||
riskScore += GetSeverityScore(flag.Severity);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize risk score to 0-100
|
||||
riskScore = Math.Min(100, riskScore);
|
||||
|
||||
return new CryptoRiskEvaluationResult
|
||||
{
|
||||
Findings = findings.ToImmutableArray(),
|
||||
CountBySeverity = countBySeverity.ToImmutableDictionary(),
|
||||
HasBlockingFindings = hasBlocking,
|
||||
RiskScore = riskScore
|
||||
};
|
||||
}
|
||||
|
||||
private static bool MatchesRule(string algorithm, CryptoRiskRule rule)
|
||||
{
|
||||
if (rule.AlgorithmPatterns.IsDefaultOrEmpty)
|
||||
return false;
|
||||
|
||||
foreach (var pattern in rule.AlgorithmPatterns)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(algorithm, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid regex, skip
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static CryptoAtomFinding CreateFinding(CryptoAsset asset, CryptoRiskRule rule)
|
||||
{
|
||||
var message = rule.MessageTemplate.Replace("{algorithm}", asset.AlgorithmName ?? "unknown");
|
||||
var recommendation = rule.RecommendationTemplate?.Replace("{algorithm}", asset.AlgorithmName ?? "unknown");
|
||||
|
||||
return new CryptoAtomFinding
|
||||
{
|
||||
Atom = rule.Atom,
|
||||
Severity = rule.Severity,
|
||||
ComponentKey = asset.ComponentKey,
|
||||
TriggeringAsset = asset.AlgorithmName,
|
||||
Message = message,
|
||||
Recommendation = recommendation,
|
||||
Action = rule.Action
|
||||
};
|
||||
}
|
||||
|
||||
private static double GetSeverityScore(CryptoRiskSeverity severity) => severity switch
|
||||
{
|
||||
CryptoRiskSeverity.Critical => 25,
|
||||
CryptoRiskSeverity.High => 10,
|
||||
CryptoRiskSeverity.Medium => 5,
|
||||
CryptoRiskSeverity.Low => 1,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
@@ -12,6 +12,28 @@ public sealed record PolicyGateContext
|
||||
public bool HasReachabilityProof { get; init; }
|
||||
public string? Severity { get; init; }
|
||||
public IReadOnlyCollection<string> ReasonCodes { get; init; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Subgraph slice for reachability proof.
|
||||
/// Required for high-severity findings when RequireSubgraphProofForHighSeverity is enabled.
|
||||
/// </summary>
|
||||
public SubgraphSlice? SubgraphSlice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Subject key for Signals lookup.
|
||||
/// </summary>
|
||||
public string? SubjectKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CVE ID if applicable.
|
||||
/// </summary>
|
||||
public string? CveId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Mutable metadata for audit trail.
|
||||
/// Gates can add metadata here for later inspection.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GateResult
|
||||
|
||||
@@ -8,6 +8,12 @@ public sealed record ReachabilityRequirementGateOptions
|
||||
{
|
||||
public bool Enabled { get; init; } = true;
|
||||
public string SeverityThreshold { get; init; } = "CRITICAL";
|
||||
|
||||
/// <summary>
|
||||
/// When true, requires subgraph proof for high-severity findings (CRITICAL, HIGH).
|
||||
/// </summary>
|
||||
public bool RequireSubgraphProofForHighSeverity { get; init; } = true;
|
||||
|
||||
public IReadOnlyCollection<VexStatus> RequiredForStatuses { get; init; } = new[]
|
||||
{
|
||||
VexStatus.NotAffected,
|
||||
@@ -52,6 +58,12 @@ public sealed class ReachabilityRequirementGate : IPolicyGate
|
||||
return Task.FromResult(Pass("bypass_reason"));
|
||||
}
|
||||
|
||||
// Check for subgraph proof for high-severity findings
|
||||
if (_options.RequireSubgraphProofForHighSeverity && severityRank >= 3) // HIGH or CRITICAL
|
||||
{
|
||||
return EvaluateSubgraphProof(context, severityRank);
|
||||
}
|
||||
|
||||
var passed = context.HasReachabilityProof;
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("severity", context.Severity ?? string.Empty)
|
||||
@@ -67,6 +79,71 @@ public sealed class ReachabilityRequirementGate : IPolicyGate
|
||||
});
|
||||
}
|
||||
|
||||
private Task<GateResult> EvaluateSubgraphProof(PolicyGateContext context, int severityRank)
|
||||
{
|
||||
// Check if subgraph slice is available
|
||||
if (context.SubgraphSlice is null)
|
||||
{
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("severity", context.Severity ?? string.Empty)
|
||||
.Add("severityRank", severityRank)
|
||||
.Add("reason", "high_severity_requires_subgraph_proof");
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(ReachabilityRequirementGate),
|
||||
Passed = false,
|
||||
Reason = "subgraph_proof_required_for_high_severity",
|
||||
Details = details,
|
||||
});
|
||||
}
|
||||
|
||||
// Validate that subgraph shows actual reachable path
|
||||
if (!HasReachablePath(context.SubgraphSlice))
|
||||
{
|
||||
// No reachable path in subgraph = can pass with "not reachable" justification
|
||||
var passDetails = ImmutableDictionary<string, object>.Empty
|
||||
.Add("severity", context.Severity ?? string.Empty)
|
||||
.Add("subgraphDigest", context.SubgraphSlice.Digest ?? string.Empty)
|
||||
.Add("reason", "subgraph_shows_no_reachable_path");
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(ReachabilityRequirementGate),
|
||||
Passed = true,
|
||||
Reason = "not_reachable_per_subgraph",
|
||||
Details = passDetails,
|
||||
});
|
||||
}
|
||||
|
||||
// Subgraph shows reachable path, include digest in audit metadata
|
||||
var detailsWithDigest = ImmutableDictionary<string, object>.Empty
|
||||
.Add("severity", context.Severity ?? string.Empty)
|
||||
.Add("subgraphDigest", context.SubgraphSlice.Digest ?? string.Empty)
|
||||
.Add("reachablePathCount", context.SubgraphSlice.Paths?.Count ?? 0)
|
||||
.Add("hasReachabilityProof", true);
|
||||
|
||||
// Store digest in metadata for audit trail
|
||||
if (context.Metadata is not null && context.SubgraphSlice.Digest is not null)
|
||||
{
|
||||
context.Metadata["reachgraph_digest"] = context.SubgraphSlice.Digest;
|
||||
}
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(ReachabilityRequirementGate),
|
||||
Passed = true,
|
||||
Reason = "reachable_with_subgraph_proof",
|
||||
Details = detailsWithDigest,
|
||||
});
|
||||
}
|
||||
|
||||
private static bool HasReachablePath(SubgraphSlice slice)
|
||||
{
|
||||
return slice.Paths?.Count > 0 &&
|
||||
slice.Paths.Any(p => p.Hops is { Count: > 0 });
|
||||
}
|
||||
|
||||
private bool HasBypass(IReadOnlyCollection<string> reasons)
|
||||
=> reasons.Any(reason => _options.BypassReasons.Contains(reason, StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
@@ -95,3 +172,22 @@ public sealed class ReachabilityRequirementGate : IPolicyGate
|
||||
Details = ImmutableDictionary<string, object>.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subgraph slice data for policy evaluation.
|
||||
/// </summary>
|
||||
public sealed record SubgraphSlice
|
||||
{
|
||||
public string? Digest { get; init; }
|
||||
public List<SubgraphPath>? Paths { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path in a subgraph slice.
|
||||
/// </summary>
|
||||
public sealed record SubgraphPath
|
||||
{
|
||||
public string? Entrypoint { get; init; }
|
||||
public string? Sink { get; init; }
|
||||
public List<string>? Hops { get; init; }
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public static class PolicyScoringConfigBinder
|
||||
foreach (var pair in result.Errors)
|
||||
{
|
||||
var keyword = SanitizeKeyword(pair.Key);
|
||||
var path = ConvertPointerToPath(result.InstanceLocation?.ToString() ?? "#");
|
||||
var path = ConvertPointerToPath(result.InstanceLocation.ToString());
|
||||
var message = pair.Value ?? "Schema violation.";
|
||||
var key = $"{path}|{keyword}|{message}";
|
||||
if (seen.Add(key))
|
||||
|
||||
@@ -339,7 +339,7 @@ public static class RiskProfileDiagnostics
|
||||
yield return RiskProfileIssue.Error(
|
||||
"RISK003",
|
||||
error.Value ?? "Schema validation failed",
|
||||
detail.EvaluationPath?.ToString() ?? "/");
|
||||
detail.EvaluationPath.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,4 +275,15 @@ public sealed class InMemorySnapshotStore : ISnapshotStore
|
||||
// In-memory implementation doesn't support digest-based lookup
|
||||
return Task.FromResult<byte[]?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all snapshots from the store (for testing only).
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_snapshots.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="JsonSchema.Net" Version="7.3.2" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
<PackageReference Include="YamlDotNet" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="JsonSchema.Net" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="JsonSchema.Net" Version="7.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Schemas\policy-schema@1.json" />
|
||||
<EmbeddedResource Include="Schemas\policy-scoring-default.json" />
|
||||
<EmbeddedResource Include="Schemas\policy-scoring-schema@1.json" />
|
||||
<EmbeddedResource Include="Schemas\spl-schema@1.json" />
|
||||
<EmbeddedResource Include="Schemas\spl-sample@1.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Policy.RiskProfile/StellaOps.Policy.RiskProfile.csproj" />
|
||||
<ProjectReference Include="../../../Attestor/__Libraries/StellaOps.Attestor.ProofChain/StellaOps.Attestor.ProofChain.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user