Files
git.stella-ops.org/src/AdvisoryAI/StellaOps.AdvisoryAI/Actions/IActionPolicyGate.cs

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; }
}