359 lines
9.6 KiB
C#
359 lines
9.6 KiB
C#
// <copyright file="IActionPolicyGate.cs" company="StellaOps">
|
|
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
|
|
// </copyright>
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
namespace StellaOps.AdvisoryAI.Actions;
|
|
|
|
/// <summary>
|
|
/// Evaluates whether AI-proposed actions are allowed by policy.
|
|
/// Integrates with K4 lattice for VEX-aware decisions and approval workflows.
|
|
/// Sprint: SPRINT_20260109_011_004_BE Task PACT-001
|
|
/// </summary>
|
|
public interface IActionPolicyGate
|
|
{
|
|
/// <summary>
|
|
/// Evaluates whether an action is allowed by policy.
|
|
/// </summary>
|
|
/// <param name="proposal">The action proposal from the AI.</param>
|
|
/// <param name="context">The execution context including tenant, user, roles, environment.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>The policy decision with any required approvals.</returns>
|
|
Task<ActionPolicyDecision> EvaluateAsync(
|
|
ActionProposal proposal,
|
|
ActionContext context,
|
|
CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Gets a human-readable explanation for a policy decision.
|
|
/// </summary>
|
|
/// <param name="decision">The decision to explain.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Human-readable explanation with policy references.</returns>
|
|
Task<PolicyExplanation> ExplainAsync(
|
|
ActionPolicyDecision decision,
|
|
CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Checks if an action has already been executed (idempotency check).
|
|
/// </summary>
|
|
/// <param name="proposal">The action proposal.</param>
|
|
/// <param name="context">The execution context.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>True if the action was already executed with the same parameters.</returns>
|
|
Task<IdempotencyCheckResult> CheckIdempotencyAsync(
|
|
ActionProposal proposal,
|
|
ActionContext context,
|
|
CancellationToken cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Context for action policy evaluation.
|
|
/// </summary>
|
|
public sealed record ActionContext
|
|
{
|
|
/// <summary>
|
|
/// Tenant identifier for multi-tenancy.
|
|
/// </summary>
|
|
public required string TenantId { get; init; }
|
|
|
|
/// <summary>
|
|
/// User identifier who initiated the action.
|
|
/// </summary>
|
|
public required string UserId { get; init; }
|
|
|
|
/// <summary>
|
|
/// User's roles/permissions.
|
|
/// </summary>
|
|
public required ImmutableArray<string> UserRoles { get; init; }
|
|
|
|
/// <summary>
|
|
/// Target environment (production, staging, development, etc.).
|
|
/// </summary>
|
|
public required string Environment { get; init; }
|
|
|
|
/// <summary>
|
|
/// Associated AI run ID, if any.
|
|
/// </summary>
|
|
public string? RunId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Associated finding ID for remediation actions.
|
|
/// </summary>
|
|
public string? FindingId { get; init; }
|
|
|
|
/// <summary>
|
|
/// CVE ID if this is a vulnerability-related action.
|
|
/// </summary>
|
|
public string? CveId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Image digest if this is a container-related action.
|
|
/// </summary>
|
|
public string? ImageDigest { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional correlation ID for tracing.
|
|
/// </summary>
|
|
public string? CorrelationId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Additional metadata for policy evaluation.
|
|
/// </summary>
|
|
public ImmutableDictionary<string, string> Metadata { get; init; } =
|
|
ImmutableDictionary<string, string>.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An action proposed by the AI system.
|
|
/// </summary>
|
|
public sealed record ActionProposal
|
|
{
|
|
/// <summary>
|
|
/// Unique identifier for this proposal.
|
|
/// </summary>
|
|
public required string ProposalId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Type of action (e.g., "approve", "quarantine", "create_vex").
|
|
/// </summary>
|
|
public required string ActionType { get; init; }
|
|
|
|
/// <summary>
|
|
/// Human-readable label for the action.
|
|
/// </summary>
|
|
public required string Label { get; init; }
|
|
|
|
/// <summary>
|
|
/// Action parameters.
|
|
/// </summary>
|
|
public required ImmutableDictionary<string, string> Parameters { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the proposal was created.
|
|
/// </summary>
|
|
public required DateTimeOffset CreatedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the proposal expires (null = never).
|
|
/// </summary>
|
|
public DateTimeOffset? ExpiresAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Idempotency key for deduplication.
|
|
/// </summary>
|
|
public string? IdempotencyKey { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of policy gate evaluation.
|
|
/// </summary>
|
|
public sealed record ActionPolicyDecision
|
|
{
|
|
/// <summary>
|
|
/// The decision outcome.
|
|
/// </summary>
|
|
public required PolicyDecisionKind Decision { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reference to the policy that made this decision.
|
|
/// </summary>
|
|
public string? PolicyId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Brief reason for the decision.
|
|
/// </summary>
|
|
public string? Reason { get; init; }
|
|
|
|
/// <summary>
|
|
/// Required approvers if decision is AllowWithApproval.
|
|
/// </summary>
|
|
public ImmutableArray<RequiredApprover> RequiredApprovers { get; init; } =
|
|
ImmutableArray<RequiredApprover>.Empty;
|
|
|
|
/// <summary>
|
|
/// Approval workflow ID if approval is required.
|
|
/// </summary>
|
|
public string? ApprovalWorkflowId { get; init; }
|
|
|
|
/// <summary>
|
|
/// K4 lattice position used in the decision.
|
|
/// </summary>
|
|
public string? K4Position { get; init; }
|
|
|
|
/// <summary>
|
|
/// VEX status that influenced the decision, if any.
|
|
/// </summary>
|
|
public string? VexStatus { get; init; }
|
|
|
|
/// <summary>
|
|
/// Severity level assigned by policy.
|
|
/// </summary>
|
|
public int? SeverityLevel { get; init; }
|
|
|
|
/// <summary>
|
|
/// When this decision expires.
|
|
/// </summary>
|
|
public DateTimeOffset? ExpiresAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Additional decision metadata.
|
|
/// </summary>
|
|
public ImmutableDictionary<string, string> Metadata { get; init; } =
|
|
ImmutableDictionary<string, string>.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kinds of policy decisions.
|
|
/// </summary>
|
|
public enum PolicyDecisionKind
|
|
{
|
|
/// <summary>
|
|
/// Action is allowed and can execute immediately.
|
|
/// </summary>
|
|
Allow,
|
|
|
|
/// <summary>
|
|
/// Action is allowed but requires approval workflow.
|
|
/// </summary>
|
|
AllowWithApproval,
|
|
|
|
/// <summary>
|
|
/// Action is denied by policy.
|
|
/// </summary>
|
|
Deny,
|
|
|
|
/// <summary>
|
|
/// Action is denied but admin can override.
|
|
/// </summary>
|
|
DenyWithOverride,
|
|
|
|
/// <summary>
|
|
/// Decision could not be made (missing context).
|
|
/// </summary>
|
|
Indeterminate
|
|
}
|
|
|
|
/// <summary>
|
|
/// Describes a required approver for AllowWithApproval decisions.
|
|
/// </summary>
|
|
public sealed record RequiredApprover
|
|
{
|
|
/// <summary>
|
|
/// Type of approver requirement.
|
|
/// </summary>
|
|
public required ApproverType Type { get; init; }
|
|
|
|
/// <summary>
|
|
/// Identifier (user ID, role name, or group name).
|
|
/// </summary>
|
|
public required string Identifier { get; init; }
|
|
|
|
/// <summary>
|
|
/// Human-readable description.
|
|
/// </summary>
|
|
public string? Description { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Types of approval requirements.
|
|
/// </summary>
|
|
public enum ApproverType
|
|
{
|
|
/// <summary>
|
|
/// Specific user must approve.
|
|
/// </summary>
|
|
User,
|
|
|
|
/// <summary>
|
|
/// Any user with this role can approve.
|
|
/// </summary>
|
|
Role,
|
|
|
|
/// <summary>
|
|
/// Any member of this group can approve.
|
|
/// </summary>
|
|
Group
|
|
}
|
|
|
|
/// <summary>
|
|
/// Human-readable explanation of a policy decision.
|
|
/// </summary>
|
|
public sealed record PolicyExplanation
|
|
{
|
|
/// <summary>
|
|
/// Natural language summary of the decision.
|
|
/// </summary>
|
|
public required string Summary { get; init; }
|
|
|
|
/// <summary>
|
|
/// Detailed explanation points.
|
|
/// </summary>
|
|
public required ImmutableArray<string> Details { get; init; }
|
|
|
|
/// <summary>
|
|
/// References to policies that were evaluated.
|
|
/// </summary>
|
|
public ImmutableArray<PolicyReference> PolicyReferences { get; init; } =
|
|
ImmutableArray<PolicyReference>.Empty;
|
|
|
|
/// <summary>
|
|
/// Suggested next steps for the user.
|
|
/// </summary>
|
|
public ImmutableArray<string> SuggestedActions { get; init; } =
|
|
ImmutableArray<string>.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reference to a specific policy.
|
|
/// </summary>
|
|
public sealed record PolicyReference
|
|
{
|
|
/// <summary>
|
|
/// Policy identifier.
|
|
/// </summary>
|
|
public required string PolicyId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Policy name.
|
|
/// </summary>
|
|
public required string Name { get; init; }
|
|
|
|
/// <summary>
|
|
/// Rule within the policy that matched.
|
|
/// </summary>
|
|
public string? RuleId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Link to policy documentation.
|
|
/// </summary>
|
|
public string? DocumentationUrl { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of idempotency check.
|
|
/// </summary>
|
|
public sealed record IdempotencyCheckResult
|
|
{
|
|
/// <summary>
|
|
/// Whether the action was previously executed.
|
|
/// </summary>
|
|
public required bool WasExecuted { get; init; }
|
|
|
|
/// <summary>
|
|
/// Previous execution ID if executed.
|
|
/// </summary>
|
|
public string? PreviousExecutionId { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the action was previously executed.
|
|
/// </summary>
|
|
public DateTimeOffset? ExecutedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Result of the previous execution.
|
|
/// </summary>
|
|
public string? PreviousResult { get; init; }
|
|
}
|