Add unit tests for PackRunAttestation and SealedInstallEnforcer
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission. - Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic. - Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Severity level.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<Severity>))]
|
||||
public enum Severity
|
||||
{
|
||||
[JsonPropertyName("critical")]
|
||||
Critical,
|
||||
|
||||
[JsonPropertyName("high")]
|
||||
High,
|
||||
|
||||
[JsonPropertyName("medium")]
|
||||
Medium,
|
||||
|
||||
[JsonPropertyName("low")]
|
||||
Low,
|
||||
|
||||
[JsonPropertyName("info")]
|
||||
Info
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFC 7807 Problem Details for HTTP APIs.
|
||||
/// </summary>
|
||||
public sealed record ProblemDetails
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public required string Type { get; init; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public required string Title { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required int Status { get; init; }
|
||||
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; init; }
|
||||
|
||||
[JsonPropertyName("instance")]
|
||||
public string? Instance { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<ValidationError>? Errors { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation error.
|
||||
/// </summary>
|
||||
public sealed record ValidationError
|
||||
{
|
||||
[JsonPropertyName("field")]
|
||||
public string? Field { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common pagination parameters.
|
||||
/// </summary>
|
||||
public sealed record PaginationParams
|
||||
{
|
||||
public int PageSize { get; init; } = 20;
|
||||
public string? PageToken { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Policy override.
|
||||
/// </summary>
|
||||
public sealed record Override
|
||||
{
|
||||
[JsonPropertyName("override_id")]
|
||||
public required Guid OverrideId { get; init; }
|
||||
|
||||
[JsonPropertyName("profile_id")]
|
||||
public Guid? ProfileId { get; init; }
|
||||
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required OverrideStatus Status { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public OverrideScope? Scope { get; init; }
|
||||
|
||||
[JsonPropertyName("expires_at")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
[JsonPropertyName("approved_by")]
|
||||
public string? ApprovedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("approved_at")]
|
||||
public DateTimeOffset? ApprovedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("created_by")]
|
||||
public string? CreatedBy { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override status.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<OverrideStatus>))]
|
||||
public enum OverrideStatus
|
||||
{
|
||||
[JsonPropertyName("pending")]
|
||||
Pending,
|
||||
|
||||
[JsonPropertyName("approved")]
|
||||
Approved,
|
||||
|
||||
[JsonPropertyName("disabled")]
|
||||
Disabled,
|
||||
|
||||
[JsonPropertyName("expired")]
|
||||
Expired
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override scope.
|
||||
/// </summary>
|
||||
public sealed record OverrideScope
|
||||
{
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("cve_id")]
|
||||
public string? CveId { get; init; }
|
||||
|
||||
[JsonPropertyName("component")]
|
||||
public string? Component { get; init; }
|
||||
|
||||
[JsonPropertyName("environment")]
|
||||
public string? Environment { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create an override.
|
||||
/// </summary>
|
||||
public sealed record CreateOverrideRequest
|
||||
{
|
||||
[JsonPropertyName("profile_id")]
|
||||
public Guid? ProfileId { get; init; }
|
||||
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public required string Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public OverrideScope? Scope { get; init; }
|
||||
|
||||
[JsonPropertyName("expires_at")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to approve an override.
|
||||
/// </summary>
|
||||
public sealed record ApproveOverrideRequest
|
||||
{
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Policy pack workspace entity.
|
||||
/// </summary>
|
||||
public sealed record PolicyPack
|
||||
{
|
||||
[JsonPropertyName("pack_id")]
|
||||
public required Guid PackId { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required PolicyPackStatus Status { get; init; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public IReadOnlyList<PolicyRule>? Rules { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updated_at")]
|
||||
public required DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("published_at")]
|
||||
public DateTimeOffset? PublishedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy pack status.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PolicyPackStatus>))]
|
||||
public enum PolicyPackStatus
|
||||
{
|
||||
[JsonPropertyName("draft")]
|
||||
Draft,
|
||||
|
||||
[JsonPropertyName("pending_review")]
|
||||
PendingReview,
|
||||
|
||||
[JsonPropertyName("published")]
|
||||
Published,
|
||||
|
||||
[JsonPropertyName("archived")]
|
||||
Archived
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual policy rule within a pack.
|
||||
/// </summary>
|
||||
public sealed record PolicyRule
|
||||
{
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public required Severity Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("rego")]
|
||||
public string? Rego { get; init; }
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a policy pack.
|
||||
/// </summary>
|
||||
public sealed record CreatePolicyPackRequest
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public IReadOnlyList<PolicyRule>? Rules { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to update a policy pack.
|
||||
/// </summary>
|
||||
public sealed record UpdatePolicyPackRequest
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public IReadOnlyList<PolicyRule>? Rules { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paginated list of policy packs.
|
||||
/// </summary>
|
||||
public sealed record PolicyPackList
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public required IReadOnlyList<PolicyPack> Items { get; init; }
|
||||
|
||||
[JsonPropertyName("next_page_token")]
|
||||
public string? NextPageToken { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compilation result for a policy pack.
|
||||
/// </summary>
|
||||
public sealed record CompilationResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public required bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<CompilationError>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("warnings")]
|
||||
public IReadOnlyList<CompilationWarning>? Warnings { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public string? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compilation error.
|
||||
/// </summary>
|
||||
public sealed record CompilationError
|
||||
{
|
||||
[JsonPropertyName("rule_id")]
|
||||
public string? RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("line")]
|
||||
public int? Line { get; init; }
|
||||
|
||||
[JsonPropertyName("column")]
|
||||
public int? Column { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compilation warning.
|
||||
/// </summary>
|
||||
public sealed record CompilationWarning
|
||||
{
|
||||
[JsonPropertyName("rule_id")]
|
||||
public string? RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to simulate a policy pack.
|
||||
/// </summary>
|
||||
public sealed record SimulationRequest
|
||||
{
|
||||
[JsonPropertyName("input")]
|
||||
public required IReadOnlyDictionary<string, object> Input { get; init; }
|
||||
|
||||
[JsonPropertyName("options")]
|
||||
public SimulationOptions? Options { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulation options.
|
||||
/// </summary>
|
||||
public sealed record SimulationOptions
|
||||
{
|
||||
[JsonPropertyName("trace")]
|
||||
public bool Trace { get; init; }
|
||||
|
||||
[JsonPropertyName("explain")]
|
||||
public bool Explain { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulation result.
|
||||
/// </summary>
|
||||
public sealed record SimulationResult
|
||||
{
|
||||
[JsonPropertyName("result")]
|
||||
public required IReadOnlyDictionary<string, object> Result { get; init; }
|
||||
|
||||
[JsonPropertyName("violations")]
|
||||
public IReadOnlyList<SimulatedViolation>? Violations { get; init; }
|
||||
|
||||
[JsonPropertyName("trace")]
|
||||
public IReadOnlyList<string>? Trace { get; init; }
|
||||
|
||||
[JsonPropertyName("explain")]
|
||||
public PolicyExplainTrace? Explain { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulated violation.
|
||||
/// </summary>
|
||||
public sealed record SimulatedViolation
|
||||
{
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public required string Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
[JsonPropertyName("context")]
|
||||
public IReadOnlyDictionary<string, object>? Context { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Policy explain trace.
|
||||
/// </summary>
|
||||
public sealed record PolicyExplainTrace
|
||||
{
|
||||
[JsonPropertyName("steps")]
|
||||
public IReadOnlyList<object>? Steps { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to publish a policy pack.
|
||||
/// </summary>
|
||||
public sealed record PublishRequest
|
||||
{
|
||||
[JsonPropertyName("approval_id")]
|
||||
public string? ApprovalId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to promote a policy pack.
|
||||
/// </summary>
|
||||
public sealed record PromoteRequest
|
||||
{
|
||||
[JsonPropertyName("target_environment")]
|
||||
public TargetEnvironment? TargetEnvironment { get; init; }
|
||||
|
||||
[JsonPropertyName("approval_id")]
|
||||
public string? ApprovalId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Target environment for promotion.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<TargetEnvironment>))]
|
||||
public enum TargetEnvironment
|
||||
{
|
||||
[JsonPropertyName("staging")]
|
||||
Staging,
|
||||
|
||||
[JsonPropertyName("production")]
|
||||
Production
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Sealed mode status (air-gap operation).
|
||||
/// </summary>
|
||||
public sealed record SealedModeStatus
|
||||
{
|
||||
[JsonPropertyName("sealed")]
|
||||
public required bool Sealed { get; init; }
|
||||
|
||||
[JsonPropertyName("mode")]
|
||||
public required SealedMode Mode { get; init; }
|
||||
|
||||
[JsonPropertyName("sealed_at")]
|
||||
public DateTimeOffset? SealedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("sealed_by")]
|
||||
public string? SealedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("bundle_version")]
|
||||
public string? BundleVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("last_advisory_update")]
|
||||
public DateTimeOffset? LastAdvisoryUpdate { get; init; }
|
||||
|
||||
[JsonPropertyName("time_anchor")]
|
||||
public TimeAnchor? TimeAnchor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sealed mode state.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<SealedMode>))]
|
||||
public enum SealedMode
|
||||
{
|
||||
[JsonPropertyName("online")]
|
||||
Online,
|
||||
|
||||
[JsonPropertyName("sealed")]
|
||||
Sealed,
|
||||
|
||||
[JsonPropertyName("transitioning")]
|
||||
Transitioning
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time anchor for sealed mode operations.
|
||||
/// </summary>
|
||||
public sealed record TimeAnchor
|
||||
{
|
||||
[JsonPropertyName("timestamp")]
|
||||
public required DateTimeOffset Timestamp { get; init; }
|
||||
|
||||
[JsonPropertyName("signature")]
|
||||
public string? Signature { get; init; }
|
||||
|
||||
[JsonPropertyName("valid")]
|
||||
public required bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("expires_at")]
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to seal the environment.
|
||||
/// </summary>
|
||||
public sealed record SealRequest
|
||||
{
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("time_anchor")]
|
||||
public DateTimeOffset? TimeAnchor { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to unseal the environment.
|
||||
/// </summary>
|
||||
public sealed record UnsealRequest
|
||||
{
|
||||
[JsonPropertyName("reason")]
|
||||
public required string Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("audit_note")]
|
||||
public string? AuditNote { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to verify an air-gap bundle.
|
||||
/// </summary>
|
||||
public sealed record VerifyBundleRequest
|
||||
{
|
||||
[JsonPropertyName("bundle_digest")]
|
||||
public required string BundleDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("public_key")]
|
||||
public string? PublicKey { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of bundle verification.
|
||||
/// </summary>
|
||||
public sealed record BundleVerificationResult
|
||||
{
|
||||
[JsonPropertyName("valid")]
|
||||
public required bool Valid { get; init; }
|
||||
|
||||
[JsonPropertyName("bundle_digest")]
|
||||
public string? BundleDigest { get; init; }
|
||||
|
||||
[JsonPropertyName("signed_at")]
|
||||
public DateTimeOffset? SignedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("signer_fingerprint")]
|
||||
public string? SignerFingerprint { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<string>? Errors { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Policy snapshot.
|
||||
/// </summary>
|
||||
public sealed record Snapshot
|
||||
{
|
||||
[JsonPropertyName("snapshot_id")]
|
||||
public required Guid SnapshotId { get; init; }
|
||||
|
||||
[JsonPropertyName("digest")]
|
||||
public required string Digest { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("pack_ids")]
|
||||
public IReadOnlyList<Guid>? PackIds { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("created_by")]
|
||||
public string? CreatedBy { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a snapshot.
|
||||
/// </summary>
|
||||
public sealed record CreateSnapshotRequest
|
||||
{
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("pack_ids")]
|
||||
public required IReadOnlyList<Guid> PackIds { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paginated list of snapshots.
|
||||
/// </summary>
|
||||
public sealed record SnapshotList
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public required IReadOnlyList<Snapshot> Items { get; init; }
|
||||
|
||||
[JsonPropertyName("next_page_token")]
|
||||
public string? NextPageToken { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Overall staleness status.
|
||||
/// </summary>
|
||||
public sealed record StalenessStatus
|
||||
{
|
||||
[JsonPropertyName("overall_status")]
|
||||
public required StalenessLevel OverallStatus { get; init; }
|
||||
|
||||
[JsonPropertyName("sources")]
|
||||
public required IReadOnlyList<SourceStaleness> Sources { get; init; }
|
||||
|
||||
[JsonPropertyName("last_check")]
|
||||
public DateTimeOffset? LastCheck { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Staleness level.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<StalenessLevel>))]
|
||||
public enum StalenessLevel
|
||||
{
|
||||
[JsonPropertyName("fresh")]
|
||||
Fresh,
|
||||
|
||||
[JsonPropertyName("stale")]
|
||||
Stale,
|
||||
|
||||
[JsonPropertyName("critical")]
|
||||
Critical,
|
||||
|
||||
[JsonPropertyName("unknown")]
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Staleness status for an individual source.
|
||||
/// </summary>
|
||||
public sealed record SourceStaleness
|
||||
{
|
||||
[JsonPropertyName("source_id")]
|
||||
public required string SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("source_name")]
|
||||
public string? SourceName { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required StalenessLevel Status { get; init; }
|
||||
|
||||
[JsonPropertyName("last_update")]
|
||||
public required DateTimeOffset LastUpdate { get; init; }
|
||||
|
||||
[JsonPropertyName("max_age_hours")]
|
||||
public int? MaxAgeHours { get; init; }
|
||||
|
||||
[JsonPropertyName("age_hours")]
|
||||
public double? AgeHours { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to evaluate staleness.
|
||||
/// </summary>
|
||||
public sealed record EvaluateStalenessRequest
|
||||
{
|
||||
[JsonPropertyName("source_id")]
|
||||
public required string SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("threshold_hours")]
|
||||
public int? ThresholdHours { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of staleness evaluation.
|
||||
/// </summary>
|
||||
public sealed record StalenessEvaluation
|
||||
{
|
||||
[JsonPropertyName("source_id")]
|
||||
public required string SourceId { get; init; }
|
||||
|
||||
[JsonPropertyName("is_stale")]
|
||||
public required bool IsStale { get; init; }
|
||||
|
||||
[JsonPropertyName("age_hours")]
|
||||
public double? AgeHours { get; init; }
|
||||
|
||||
[JsonPropertyName("threshold_hours")]
|
||||
public int? ThresholdHours { get; init; }
|
||||
|
||||
[JsonPropertyName("recommendation")]
|
||||
public string? Recommendation { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Verification policy for attestation validation.
|
||||
/// Based on OpenAPI: docs/schemas/policy-registry-api.openapi.yaml
|
||||
/// </summary>
|
||||
public sealed record VerificationPolicy
|
||||
{
|
||||
[JsonPropertyName("policy_id")]
|
||||
public required string PolicyId { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant_scope")]
|
||||
public required string TenantScope { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate_types")]
|
||||
public required IReadOnlyList<string> PredicateTypes { get; init; }
|
||||
|
||||
[JsonPropertyName("signer_requirements")]
|
||||
public required SignerRequirements SignerRequirements { get; init; }
|
||||
|
||||
[JsonPropertyName("validity_window")]
|
||||
public ValidityWindow? ValidityWindow { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("updated_at")]
|
||||
public required DateTimeOffset UpdatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requirements for attestation signers.
|
||||
/// </summary>
|
||||
public sealed record SignerRequirements
|
||||
{
|
||||
[JsonPropertyName("minimum_signatures")]
|
||||
public int MinimumSignatures { get; init; } = 1;
|
||||
|
||||
[JsonPropertyName("trusted_key_fingerprints")]
|
||||
public required IReadOnlyList<string> TrustedKeyFingerprints { get; init; }
|
||||
|
||||
[JsonPropertyName("trusted_issuers")]
|
||||
public IReadOnlyList<string>? TrustedIssuers { get; init; }
|
||||
|
||||
[JsonPropertyName("require_rekor")]
|
||||
public bool RequireRekor { get; init; }
|
||||
|
||||
[JsonPropertyName("algorithms")]
|
||||
public IReadOnlyList<string>? Algorithms { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validity window for attestations.
|
||||
/// </summary>
|
||||
public sealed record ValidityWindow
|
||||
{
|
||||
[JsonPropertyName("not_before")]
|
||||
public DateTimeOffset? NotBefore { get; init; }
|
||||
|
||||
[JsonPropertyName("not_after")]
|
||||
public DateTimeOffset? NotAfter { get; init; }
|
||||
|
||||
[JsonPropertyName("max_attestation_age")]
|
||||
public int? MaxAttestationAge { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a verification policy.
|
||||
/// </summary>
|
||||
public sealed record CreateVerificationPolicyRequest
|
||||
{
|
||||
[JsonPropertyName("policy_id")]
|
||||
public required string PolicyId { get; init; }
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant_scope")]
|
||||
public string? TenantScope { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate_types")]
|
||||
public required IReadOnlyList<string> PredicateTypes { get; init; }
|
||||
|
||||
[JsonPropertyName("signer_requirements")]
|
||||
public SignerRequirements? SignerRequirements { get; init; }
|
||||
|
||||
[JsonPropertyName("validity_window")]
|
||||
public ValidityWindow? ValidityWindow { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to update a verification policy.
|
||||
/// </summary>
|
||||
public sealed record UpdateVerificationPolicyRequest
|
||||
{
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; init; }
|
||||
|
||||
[JsonPropertyName("predicate_types")]
|
||||
public IReadOnlyList<string>? PredicateTypes { get; init; }
|
||||
|
||||
[JsonPropertyName("signer_requirements")]
|
||||
public SignerRequirements? SignerRequirements { get; init; }
|
||||
|
||||
[JsonPropertyName("validity_window")]
|
||||
public ValidityWindow? ValidityWindow { get; init; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IReadOnlyDictionary<string, object>? Metadata { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paginated list of verification policies.
|
||||
/// </summary>
|
||||
public sealed record VerificationPolicyList
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public required IReadOnlyList<VerificationPolicy> Items { get; init; }
|
||||
|
||||
[JsonPropertyName("next_page_token")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("total_count")]
|
||||
public int? TotalCount { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Policy violation.
|
||||
/// </summary>
|
||||
public sealed record Violation
|
||||
{
|
||||
[JsonPropertyName("violation_id")]
|
||||
public required Guid ViolationId { get; init; }
|
||||
|
||||
[JsonPropertyName("policy_id")]
|
||||
public string? PolicyId { get; init; }
|
||||
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public required Severity Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("cve_id")]
|
||||
public string? CveId { get; init; }
|
||||
|
||||
[JsonPropertyName("context")]
|
||||
public IReadOnlyDictionary<string, object>? Context { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request to create a violation.
|
||||
/// </summary>
|
||||
public sealed record CreateViolationRequest
|
||||
{
|
||||
[JsonPropertyName("policy_id")]
|
||||
public string? PolicyId { get; init; }
|
||||
|
||||
[JsonPropertyName("rule_id")]
|
||||
public required string RuleId { get; init; }
|
||||
|
||||
[JsonPropertyName("severity")]
|
||||
public required Severity Severity { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public required string Message { get; init; }
|
||||
|
||||
[JsonPropertyName("purl")]
|
||||
public string? Purl { get; init; }
|
||||
|
||||
[JsonPropertyName("cve_id")]
|
||||
public string? CveId { get; init; }
|
||||
|
||||
[JsonPropertyName("context")]
|
||||
public IReadOnlyDictionary<string, object>? Context { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch request to create violations.
|
||||
/// </summary>
|
||||
public sealed record ViolationBatchRequest
|
||||
{
|
||||
[JsonPropertyName("violations")]
|
||||
public required IReadOnlyList<CreateViolationRequest> Violations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of batch violation creation.
|
||||
/// </summary>
|
||||
public sealed record ViolationBatchResult
|
||||
{
|
||||
[JsonPropertyName("created")]
|
||||
public required int Created { get; init; }
|
||||
|
||||
[JsonPropertyName("failed")]
|
||||
public required int Failed { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IReadOnlyList<BatchError>? Errors { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error from batch operation.
|
||||
/// </summary>
|
||||
public sealed record BatchError
|
||||
{
|
||||
[JsonPropertyName("index")]
|
||||
public int? Index { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paginated list of violations.
|
||||
/// </summary>
|
||||
public sealed record ViolationList
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public required IReadOnlyList<Violation> Items { get; init; }
|
||||
|
||||
[JsonPropertyName("next_page_token")]
|
||||
public string? NextPageToken { get; init; }
|
||||
|
||||
[JsonPropertyName("total_count")]
|
||||
public int? TotalCount { get; init; }
|
||||
}
|
||||
214
src/Policy/StellaOps.Policy.Registry/IPolicyRegistryClient.cs
Normal file
214
src/Policy/StellaOps.Policy.Registry/IPolicyRegistryClient.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
namespace StellaOps.Policy.Registry;
|
||||
|
||||
/// <summary>
|
||||
/// Typed HTTP client for Policy Registry API.
|
||||
/// Based on OpenAPI: docs/schemas/policy-registry-api.openapi.yaml
|
||||
/// </summary>
|
||||
public interface IPolicyRegistryClient
|
||||
{
|
||||
// ============================================================
|
||||
// VERIFICATION POLICY OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<VerificationPolicyList> ListVerificationPoliciesAsync(
|
||||
Guid tenantId,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<VerificationPolicy> CreateVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
CreateVerificationPolicyRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<VerificationPolicy> GetVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<VerificationPolicy> UpdateVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
UpdateVerificationPolicyRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeleteVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// POLICY PACK OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<PolicyPackList> ListPolicyPacksAsync(
|
||||
Guid tenantId,
|
||||
PolicyPackStatus? status = null,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PolicyPack> CreatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
CreatePolicyPackRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PolicyPack> GetPolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PolicyPack> UpdatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
UpdatePolicyPackRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeletePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CompilationResult> CompilePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<SimulationResult> SimulatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
SimulationRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PolicyPack> PublishPolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
PublishRequest? request = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PolicyPack> PromotePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
PromoteRequest? request = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// SNAPSHOT OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<SnapshotList> ListSnapshotsAsync(
|
||||
Guid tenantId,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Snapshot> CreateSnapshotAsync(
|
||||
Guid tenantId,
|
||||
CreateSnapshotRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Snapshot> GetSnapshotAsync(
|
||||
Guid tenantId,
|
||||
Guid snapshotId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeleteSnapshotAsync(
|
||||
Guid tenantId,
|
||||
Guid snapshotId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Snapshot> GetSnapshotByDigestAsync(
|
||||
Guid tenantId,
|
||||
string digest,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// VIOLATION OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<ViolationList> ListViolationsAsync(
|
||||
Guid tenantId,
|
||||
Severity? severity = null,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Violation> AppendViolationAsync(
|
||||
Guid tenantId,
|
||||
CreateViolationRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<ViolationBatchResult> AppendViolationBatchAsync(
|
||||
Guid tenantId,
|
||||
ViolationBatchRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Violation> GetViolationAsync(
|
||||
Guid tenantId,
|
||||
Guid violationId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// OVERRIDE OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<Override> CreateOverrideAsync(
|
||||
Guid tenantId,
|
||||
CreateOverrideRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Override> GetOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeleteOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Override> ApproveOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
ApproveOverrideRequest? request = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Override> DisableOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// SEALED MODE OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<SealedModeStatus> GetSealedModeStatusAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<SealedModeStatus> SealAsync(
|
||||
Guid tenantId,
|
||||
SealRequest? request = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<SealedModeStatus> UnsealAsync(
|
||||
Guid tenantId,
|
||||
UnsealRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<BundleVerificationResult> VerifyBundleAsync(
|
||||
Guid tenantId,
|
||||
VerifyBundleRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
// ============================================================
|
||||
// STALENESS OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
Task<StalenessStatus> GetStalenessStatusAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<StalenessEvaluation> EvaluateStalenessAsync(
|
||||
Guid tenantId,
|
||||
EvaluateStalenessRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
634
src/Policy/StellaOps.Policy.Registry/PolicyRegistryClient.cs
Normal file
634
src/Policy/StellaOps.Policy.Registry/PolicyRegistryClient.cs
Normal file
@@ -0,0 +1,634 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy.Registry.Contracts;
|
||||
|
||||
namespace StellaOps.Policy.Registry;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client implementation for Policy Registry API.
|
||||
/// </summary>
|
||||
public sealed class PolicyRegistryClient : IPolicyRegistryClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public PolicyRegistryClient(HttpClient httpClient, IOptions<PolicyRegistryClientOptions>? options = null)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
if (options?.Value?.BaseUrl is not null && _httpClient.BaseAddress is null)
|
||||
{
|
||||
_httpClient.BaseAddress = new Uri(options.Value.BaseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTenantHeader(HttpRequestMessage request, Guid tenantId)
|
||||
{
|
||||
request.Headers.Add("X-Tenant-Id", tenantId.ToString());
|
||||
}
|
||||
|
||||
private static string BuildQueryString(PaginationParams? pagination, params (string name, string? value)[] additional)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (pagination is not null)
|
||||
{
|
||||
if (pagination.PageSize != 20)
|
||||
{
|
||||
parts.Add($"page_size={pagination.PageSize}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(pagination.PageToken))
|
||||
{
|
||||
parts.Add($"page_token={Uri.EscapeDataString(pagination.PageToken)}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (name, value) in additional)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
parts.Add($"{name}={Uri.EscapeDataString(value)}");
|
||||
}
|
||||
}
|
||||
|
||||
return parts.Count > 0 ? "?" + string.Join("&", parts) : string.Empty;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// VERIFICATION POLICY OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<VerificationPolicyList> ListVerificationPoliciesAsync(
|
||||
Guid tenantId,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = BuildQueryString(pagination);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/verification-policies{query}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<VerificationPolicyList>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<VerificationPolicy> CreateVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
CreateVerificationPolicyRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/verification-policies");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<VerificationPolicy>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<VerificationPolicy> GetVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/verification-policies/{Uri.EscapeDataString(policyId)}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<VerificationPolicy>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<VerificationPolicy> UpdateVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
UpdateVerificationPolicyRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Put, $"/api/v1/policy/verification-policies/{Uri.EscapeDataString(policyId)}");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<VerificationPolicy>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task DeleteVerificationPolicyAsync(
|
||||
Guid tenantId,
|
||||
string policyId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/policy/verification-policies/{Uri.EscapeDataString(policyId)}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POLICY PACK OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<PolicyPackList> ListPolicyPacksAsync(
|
||||
Guid tenantId,
|
||||
PolicyPackStatus? status = null,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = BuildQueryString(pagination, ("status", status?.ToString().ToLowerInvariant()));
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/packs{query}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPackList>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<PolicyPack> CreatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
CreatePolicyPackRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/packs");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPack>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<PolicyPack> GetPolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/packs/{packId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPack>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<PolicyPack> UpdatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
UpdatePolicyPackRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Put, $"/api/v1/policy/packs/{packId}");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPack>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task DeletePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/policy/packs/{packId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<CompilationResult> CompilePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/packs/{packId}/compile");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
// Note: 422 also returns CompilationResult, so we read regardless of status
|
||||
return await response.Content.ReadFromJsonAsync<CompilationResult>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<SimulationResult> SimulatePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
SimulationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/packs/{packId}/simulate");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SimulationResult>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<PolicyPack> PublishPolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
PublishRequest? request = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/packs/{packId}/publish");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
if (request is not null)
|
||||
{
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPack>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<PolicyPack> PromotePolicyPackAsync(
|
||||
Guid tenantId,
|
||||
Guid packId,
|
||||
PromoteRequest? request = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/packs/{packId}/promote");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
if (request is not null)
|
||||
{
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PolicyPack>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SNAPSHOT OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<SnapshotList> ListSnapshotsAsync(
|
||||
Guid tenantId,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = BuildQueryString(pagination);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/snapshots{query}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SnapshotList>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Snapshot> CreateSnapshotAsync(
|
||||
Guid tenantId,
|
||||
CreateSnapshotRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/snapshots");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Snapshot>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Snapshot> GetSnapshotAsync(
|
||||
Guid tenantId,
|
||||
Guid snapshotId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/snapshots/{snapshotId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Snapshot>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task DeleteSnapshotAsync(
|
||||
Guid tenantId,
|
||||
Guid snapshotId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/policy/snapshots/{snapshotId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<Snapshot> GetSnapshotByDigestAsync(
|
||||
Guid tenantId,
|
||||
string digest,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/snapshots/by-digest/{Uri.EscapeDataString(digest)}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Snapshot>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// VIOLATION OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<ViolationList> ListViolationsAsync(
|
||||
Guid tenantId,
|
||||
Severity? severity = null,
|
||||
PaginationParams? pagination = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = BuildQueryString(pagination, ("severity", severity?.ToString().ToLowerInvariant()));
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/violations{query}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<ViolationList>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Violation> AppendViolationAsync(
|
||||
Guid tenantId,
|
||||
CreateViolationRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/violations");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Violation>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<ViolationBatchResult> AppendViolationBatchAsync(
|
||||
Guid tenantId,
|
||||
ViolationBatchRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/violations/batch");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<ViolationBatchResult>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Violation> GetViolationAsync(
|
||||
Guid tenantId,
|
||||
Guid violationId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/violations/{violationId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Violation>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OVERRIDE OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<Override> CreateOverrideAsync(
|
||||
Guid tenantId,
|
||||
CreateOverrideRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/overrides");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Override>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Override> GetOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/policy/overrides/{overrideId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Override>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task DeleteOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/policy/overrides/{overrideId}");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<Override> ApproveOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
ApproveOverrideRequest? request = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/overrides/{overrideId}:approve");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
if (request is not null)
|
||||
{
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Override>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<Override> DisableOverrideAsync(
|
||||
Guid tenantId,
|
||||
Guid overrideId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/policy/overrides/{overrideId}:disable");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<Override>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SEALED MODE OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<SealedModeStatus> GetSealedModeStatusAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/policy/sealed-mode/status");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SealedModeStatus>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<SealedModeStatus> SealAsync(
|
||||
Guid tenantId,
|
||||
SealRequest? request = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/sealed-mode/seal");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
if (request is not null)
|
||||
{
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SealedModeStatus>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<SealedModeStatus> UnsealAsync(
|
||||
Guid tenantId,
|
||||
UnsealRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/sealed-mode/unseal");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SealedModeStatus>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<BundleVerificationResult> VerifyBundleAsync(
|
||||
Guid tenantId,
|
||||
VerifyBundleRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/sealed-mode/verify");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<BundleVerificationResult>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STALENESS OPERATIONS
|
||||
// ============================================================
|
||||
|
||||
public async Task<StalenessStatus> GetStalenessStatusAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/policy/staleness/status");
|
||||
AddTenantHeader(request, tenantId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<StalenessStatus>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
|
||||
public async Task<StalenessEvaluation> EvaluateStalenessAsync(
|
||||
Guid tenantId,
|
||||
EvaluateStalenessRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/policy/staleness/evaluate");
|
||||
AddTenantHeader(httpRequest, tenantId);
|
||||
httpRequest.Content = JsonContent.Create(request, options: _jsonOptions);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<StalenessEvaluation>(_jsonOptions, cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("Failed to deserialize response");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for Policy Registry client.
|
||||
/// </summary>
|
||||
public sealed class PolicyRegistryClientOptions
|
||||
{
|
||||
public string? BaseUrl { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace StellaOps.Policy.Registry;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering Policy Registry services.
|
||||
/// </summary>
|
||||
public static class PolicyRegistryServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the Policy Registry typed HTTP client to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddPolicyRegistryClient(
|
||||
this IServiceCollection services,
|
||||
Action<PolicyRegistryClientOptions>? configureOptions = null)
|
||||
{
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
}
|
||||
|
||||
services.AddHttpClient<IPolicyRegistryClient, PolicyRegistryClient>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Policy Registry typed HTTP client with a custom base address.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddPolicyRegistryClient(
|
||||
this IServiceCollection services,
|
||||
string baseUrl)
|
||||
{
|
||||
services.Configure<PolicyRegistryClientOptions>(options =>
|
||||
{
|
||||
options.BaseUrl = baseUrl;
|
||||
});
|
||||
|
||||
services.AddHttpClient<IPolicyRegistryClient, PolicyRegistryClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(baseUrl);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>StellaOps.Policy.Registry</RootNamespace>
|
||||
<AssemblyName>StellaOps.Policy.Registry</AssemblyName>
|
||||
<Description>Policy Registry typed clients and contracts for StellaOps Policy Engine</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user