From 95f96d78edd77f2d3ca790c3d79f35c75f91aa7e Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 18 Jan 2026 13:40:07 +0100 Subject: [PATCH] feat(types): add workflow policy enforcement types (#1019, #1020, #1021) Add new types for MAP and WCP policy enforcement: - PolicyEvaluationResult: result of policy evaluation with allowed status, applied policies, risk score, required actions, and processing time - StepGateResponse: add policies_evaluated and policies_matched fields - PlanExecutionResponse: add policy_info field for plan execution results --- CHANGELOG.md | 6 ++++++ axonflow/__init__.py | 2 ++ axonflow/types.py | 28 ++++++++++++++++++++++++++++ axonflow/workflow.py | 8 ++++++++ 4 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd59ae4..1517512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Helper methods on `WorkflowStatus` and `WorkflowStatusResponse`: `is_terminal()` - LangGraph adapter: `axonflow.adapters.langgraph.AxonFlowLangGraphAdapter` +- **Workflow Policy Enforcement** (Issues #1019, #1020, #1021): Policy transparency for workflow operations + - `StepGateResponse` now includes `policies_evaluated` and `policies_matched` fields with `PolicyMatch` type + - `PolicyMatch` class with `policy_id`, `policy_name`, `action`, `reason` for policy transparency + - `PolicyEvaluationResult` class for MAP execution with `allowed`, `applied_policies`, `risk_score` + - Workflow operations (`workflow_created`, `workflow_step_gate`, `workflow_completed`) logged to audit trail + ### Fixed - Datetime parsing now handles variable-length fractional seconds (e.g., 5 digits) for Python 3.9 compatibility diff --git a/axonflow/__init__.py b/axonflow/__init__.py index 2eeb164..e540e23 100644 --- a/axonflow/__init__.py +++ b/axonflow/__init__.py @@ -114,6 +114,7 @@ PlanStep, PolicyApprovalResult, PolicyEvaluationInfo, + PolicyEvaluationResult, PricingInfo, PricingListResponse, RateLimitInfo, @@ -168,6 +169,7 @@ "PlanStep", "PlanResponse", "PlanExecutionResponse", + "PolicyEvaluationResult", # Gateway Mode types "RateLimitInfo", "PolicyApprovalResult", diff --git a/axonflow/types.py b/axonflow/types.py index 08749ab..a939c7d 100644 --- a/axonflow/types.py +++ b/axonflow/types.py @@ -285,6 +285,31 @@ class PlanResponse(BaseModel): metadata: dict[str, Any] = Field(default_factory=dict) +class PolicyEvaluationResult(BaseModel): + """Result of policy evaluation for workflow steps and plan executions. + + Used by MAP (Multi-Agent Planning) and WCP (Workflow Control Plane) to provide + detailed policy enforcement information (Issues #1019, #1020, #1021). + """ + + allowed: bool = Field(..., description="Whether the action is allowed by policy") + applied_policies: list[str] = Field( + default_factory=list, description="List of policy IDs that were applied" + ) + risk_score: float = Field( + default=0.0, ge=0.0, le=1.0, description="Calculated risk score (0.0-1.0)" + ) + required_actions: list[str] | None = Field( + default=None, description="Actions required before proceeding (if any)" + ) + processing_time_ms: int = Field( + default=0, ge=0, description="Time taken for policy evaluation in milliseconds" + ) + database_accessed: bool | None = Field( + default=None, description="Whether a database was accessed during the operation" + ) + + class PlanExecutionResponse(BaseModel): """Plan execution result.""" @@ -294,6 +319,9 @@ class PlanExecutionResponse(BaseModel): step_results: dict[str, Any] = Field(default_factory=dict) error: str | None = None duration: str | None = None + policy_info: PolicyEvaluationResult | None = Field( + default=None, description="Policy evaluation result for the plan execution" + ) # Gateway Mode Types diff --git a/axonflow/workflow.py b/axonflow/workflow.py index a8d6cac..9a3cdbd 100644 --- a/axonflow/workflow.py +++ b/axonflow/workflow.py @@ -123,6 +123,14 @@ class StepGateResponse(BaseModel): approval_url: str | None = Field( default=None, description="URL to the approval portal (if decision is require_approval)" ) + policies_evaluated: list[PolicyMatch] | None = Field( + default=None, + description="List of all policies that were evaluated during the gate check (Issue #1019)", + ) + policies_matched: list[PolicyMatch] | None = Field( + default=None, + description="List of policies that matched and influenced the decision (Issue #1019)", + ) def is_allowed(self) -> bool: """Check if the step is allowed to proceed."""