Skip to content

Commit ffa7038

Browse files
committed
Add AggregationMethod enum and DecisionSeverity to PolicyEvaluation for deterministic aggregation
1 parent 0c3288a commit ffa7038

6 files changed

Lines changed: 147 additions & 25 deletions

File tree

lib/go/cabincrew/protocol.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cabincrew
22

33
type CabinCrewProtocol struct {
4+
AggregationMethod *AggregationMethod `json:"AggregationMethod,omitempty"`
45
AnyMap map[string]interface{} `json:"AnyMap,omitempty"`
56
ApprovalReceivedData *ApprovalReceivedData `json:"ApprovalReceivedData,omitempty"`
67
ApprovalRecord *ApprovalRecord `json:"ApprovalRecord,omitempty"`
@@ -19,6 +20,7 @@ type CabinCrewProtocol struct {
1920
AuditPolicy *AuditPolicy `json:"AuditPolicy,omitempty"`
2021
AuditWorkflow *AuditWorkflow `json:"AuditWorkflow,omitempty"`
2122
Decision *Decision `json:"Decision,omitempty"`
23+
DecisionSeverity *float64 `json:"DecisionSeverity,omitempty"`
2224
EngineArtifact *EngineArtifact `json:"EngineArtifact,omitempty"`
2325
EngineInput *EngineInput `json:"EngineInput,omitempty"`
2426
EngineMeta *EngineMeta `json:"EngineMeta,omitempty"`
@@ -291,8 +293,9 @@ type PlanArtifactHash struct {
291293
// Extended to support chain-of-custody reconstruction.
292294
type AuditPolicy struct {
293295
// Aggregation method used to combine individual policy decisions.
294-
// Examples: 'most_restrictive', 'unanimous', 'majority'
295-
AggregationMethod *string `json:"aggregation_method,omitempty"`
296+
// REQUIRED if multiple policies were evaluated.
297+
// Ensures deterministic aggregation across orchestrators.
298+
AggregationMethod *AggregationMethod `json:"aggregation_method,omitempty"`
296299
// Final aggregated decision after all policy evaluations.
297300
// REQUIRED for chain-of-custody.
298301
Decision Decision `json:"decision"`
@@ -323,6 +326,10 @@ type PolicyEvaluation struct {
323326
PolicyID string `json:"policy_id"`
324327
// Reason for this decision.
325328
Reason *string `json:"reason,omitempty"`
329+
// Decision severity for aggregation ordering.
330+
// 0=allow, 1=warn, 2=require_approval, 3=deny
331+
// REQUIRED for deterministic "most restrictive" aggregation.
332+
Severity float64 `json:"severity"`
326333
// Policy source type.
327334
Source Source `json:"source"`
328335
}
@@ -593,6 +600,23 @@ type WorkflowStateRecord struct {
593600
WorkflowID string `json:"workflow_id"`
594601
}
595602

603+
// Policy aggregation strategy.
604+
// Defines how multiple policy decisions are combined into a final decision.
605+
//
606+
// Aggregation method used to combine individual policy decisions.
607+
// REQUIRED if multiple policies were evaluated.
608+
// Ensures deterministic aggregation across orchestrators.
609+
type AggregationMethod string
610+
611+
const (
612+
AggregationMethodCustom AggregationMethod = "custom"
613+
AllAllow AggregationMethod = "all_allow"
614+
AnyDeny AggregationMethod = "any_deny"
615+
Majority AggregationMethod = "majority"
616+
MostRestrictive AggregationMethod = "most_restrictive"
617+
Unanimous AggregationMethod = "unanimous"
618+
)
619+
596620
// Final aggregated decision after all policy evaluations.
597621
// REQUIRED for chain-of-custody.
598622
//
@@ -610,11 +634,11 @@ const (
610634
type Source string
611635

612636
const (
613-
Custom Source = "custom"
614-
LlmGateway Source = "llm_gateway"
615-
MCPGateway Source = "mcp_gateway"
616-
Onnx Source = "onnx"
617-
Opa Source = "opa"
637+
LlmGateway Source = "llm_gateway"
638+
MCPGateway Source = "mcp_gateway"
639+
Onnx Source = "onnx"
640+
Opa Source = "opa"
641+
SourceCustom Source = "custom"
618642
)
619643

620644
type Severity string

lib/nodejs/src/protocol.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface CabinCrewProtocol {
2+
AggregationMethod?: AggregationMethod;
23
AnyMap?: { [key: string]: any };
34
ApprovalReceivedData?: ApprovalReceivedData;
45
ApprovalRecord?: ApprovalRecord;
@@ -17,6 +18,7 @@ export interface CabinCrewProtocol {
1718
AuditPolicy?: AuditPolicy;
1819
AuditWorkflow?: AuditWorkflow;
1920
Decision?: Decision;
21+
DecisionSeverity?: number;
2022
EngineArtifact?: EngineArtifact;
2123
EngineInput?: EngineInput;
2224
EngineMeta?: EngineMeta;
@@ -58,6 +60,16 @@ export interface CabinCrewProtocol {
5860
[property: string]: any;
5961
}
6062

63+
/**
64+
* Policy aggregation strategy.
65+
* Defines how multiple policy decisions are combined into a final decision.
66+
*
67+
* Aggregation method used to combine individual policy decisions.
68+
* REQUIRED if multiple policies were evaluated.
69+
* Ensures deterministic aggregation across orchestrators.
70+
*/
71+
export type AggregationMethod = "all_allow" | "any_deny" | "custom" | "majority" | "most_restrictive" | "unanimous";
72+
6173
export interface ApprovalReceivedData {
6274
approval_id: string;
6375
approved: boolean;
@@ -385,9 +397,10 @@ export interface PlanArtifactHash {
385397
export interface AuditPolicy {
386398
/**
387399
* Aggregation method used to combine individual policy decisions.
388-
* Examples: 'most_restrictive', 'unanimous', 'majority'
400+
* REQUIRED if multiple policies were evaluated.
401+
* Ensures deterministic aggregation across orchestrators.
389402
*/
390-
aggregation_method?: string;
403+
aggregation_method?: AggregationMethod;
391404
/**
392405
* Final aggregated decision after all policy evaluations.
393406
* REQUIRED for chain-of-custody.
@@ -442,6 +455,12 @@ export interface PolicyEvaluation {
442455
* Reason for this decision.
443456
*/
444457
reason?: string;
458+
/**
459+
* Decision severity for aggregation ordering.
460+
* 0=allow, 1=warn, 2=require_approval, 3=deny
461+
* REQUIRED for deterministic "most restrictive" aggregation.
462+
*/
463+
severity: number;
445464
/**
446465
* Policy source type.
447466
*/

lib/python/src/cabincrew_protocol/protocol.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
from enum import Enum
12
from dataclasses import dataclass
23
from typing import List, Optional, Dict, Any, Union
3-
from enum import Enum
4+
5+
6+
class AggregationMethod(Enum):
7+
"""Policy aggregation strategy.
8+
Defines how multiple policy decisions are combined into a final decision.
9+
10+
Aggregation method used to combine individual policy decisions.
11+
REQUIRED if multiple policies were evaluated.
12+
Ensures deterministic aggregation across orchestrators.
13+
"""
14+
ALL_ALLOW = "all_allow"
15+
ANY_DENY = "any_deny"
16+
CUSTOM = "custom"
17+
MAJORITY = "majority"
18+
MOST_RESTRICTIVE = "most_restrictive"
19+
UNANIMOUS = "unanimous"
420

521

622
@dataclass
@@ -278,6 +294,11 @@ class PolicyEvaluation:
278294
policy_id: str = None
279295
"""Policy identifier (e.g., OPA policy name, ONNX model name)."""
280296

297+
severity: float = None
298+
"""Decision severity for aggregation ordering.
299+
0=allow, 1=warn, 2=require_approval, 3=deny
300+
REQUIRED for deterministic "most restrictive" aggregation.
301+
"""
281302
source: Source = None
282303
"""Policy source type."""
283304

@@ -301,9 +322,10 @@ class AuditPolicy:
301322
"""Workflow state when this policy evaluation occurred.
302323
REQUIRED for temporal chain-of-custody.
303324
"""
304-
aggregation_method: Optional[str] = None
325+
aggregation_method: Optional[AggregationMethod] = None
305326
"""Aggregation method used to combine individual policy decisions.
306-
Examples: 'most_restrictive', 'unanimous', 'majority' = None
327+
REQUIRED if multiple policies were evaluated.
328+
Ensures deterministic aggregation across orchestrators.
307329
"""
308330
engine: Optional[str] = None
309331
"""Legacy field for backward compatibility."""
@@ -721,6 +743,7 @@ class WorkflowStateRecord:
721743

722744
@dataclass
723745
class CabinCrewProtocol:
746+
aggregation_method: Optional[AggregationMethod] = None
724747
any_map: Optional[Dict[str, Any]] = None
725748
approval_received_data: Optional[ApprovalReceivedData] = None
726749
approval_record: Optional[ApprovalRecord] = None
@@ -739,6 +762,7 @@ class CabinCrewProtocol:
739762
audit_policy: Optional[AuditPolicy] = None
740763
audit_workflow: Optional[AuditWorkflow] = None
741764
decision: Optional[Decision] = None
765+
decision_severity: Optional[float] = None
742766
engine_artifact: Optional[EngineArtifact] = None
743767
engine_input: Optional[EngineInput] = None
744768
engine_meta: Optional[EngineMeta] = None

schemas/draft/schema.json

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,28 @@
836836
},
837837
"additionalProperties": false
838838
},
839+
"AggregationMethod": {
840+
"description": "Policy aggregation strategy.\nDefines how multiple policy decisions are combined into a final decision.",
841+
"enum": [
842+
"all_allow",
843+
"any_deny",
844+
"custom",
845+
"majority",
846+
"most_restrictive",
847+
"unanimous"
848+
],
849+
"type": "string"
850+
},
851+
"DecisionSeverity": {
852+
"description": "Decision severity for ordering.\nUsed to determine \"most restrictive\" in aggregation.",
853+
"enum": [
854+
0,
855+
1,
856+
2,
857+
3
858+
],
859+
"type": "number"
860+
},
839861
"AuditPolicy": {
840862
"description": "Policy evaluation audit record.\nExtended to support chain-of-custody reconstruction.",
841863
"type": "object",
@@ -852,7 +874,15 @@
852874
}
853875
},
854876
"aggregation_method": {
855-
"description": "Aggregation method used to combine individual policy decisions.\nExamples: 'most_restrictive', 'unanimous', 'majority'",
877+
"description": "Aggregation method used to combine individual policy decisions.\nREQUIRED if multiple policies were evaluated.\nEnsures deterministic aggregation across orchestrators.",
878+
"enum": [
879+
"all_allow",
880+
"any_deny",
881+
"custom",
882+
"majority",
883+
"most_restrictive",
884+
"unanimous"
885+
],
856886
"type": "string"
857887
},
858888
"workflow_state": {
@@ -904,14 +934,12 @@
904934
"type": "string"
905935
},
906936
"decision": {
907-
"description": "Decision from this specific policy.",
908-
"enum": [
909-
"allow",
910-
"deny",
911-
"require_approval",
912-
"warn"
913-
],
914-
"type": "string"
937+
"$ref": "#/definitions/Decision",
938+
"description": "Decision from this specific policy."
939+
},
940+
"severity": {
941+
"$ref": "#/definitions/DecisionSeverity",
942+
"description": "Decision severity for aggregation ordering.\n0=allow, 1=warn, 2=require_approval, 3=deny\nREQUIRED for deterministic \"most restrictive\" aggregation."
915943
},
916944
"reason": {
917945
"description": "Reason for this decision.",
@@ -931,6 +959,7 @@
931959
"decision",
932960
"evaluated_at",
933961
"policy_id",
962+
"severity",
934963
"source"
935964
]
936965
},

schemas/draft/schema.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Extended to support chain-of-custody reconstruction.
239239
|---|---|---|---|
240240
| `decision` | `[Decision]` | Yes | Final aggregated decision after all policy evaluations. REQUIRED for chain-of-custody. |
241241
| `policy_evaluations` | `array` | No | Individual policy evaluation results. Captures which specific policies (OPA/ONNX/gateway) produced which decisions. |
242-
| `aggregation_method` | `string` | No | Aggregation method used to combine individual policy decisions. Examples: 'most_restrictive', 'unanimous', 'majority' |
242+
| `aggregation_method` | `string` | No | Aggregation method used to combine individual policy decisions. REQUIRED if multiple policies were evaluated. Ensures deterministic aggregation across orchestrators. |
243243
| `workflow_state` | `string` | Yes | Workflow state when this policy evaluation occurred. REQUIRED for temporal chain-of-custody. |
244244
| `violations` | `array` | No | Policy violations detected. |
245245
| `warnings` | `array` | No | Policy warnings (non-blocking). |

src/audit.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ export interface AuditArtifact {
2222
size?: number;
2323
}
2424

25+
/**
26+
* Policy aggregation strategy.
27+
* Defines how multiple policy decisions are combined into a final decision.
28+
*/
29+
export type AggregationMethod =
30+
| 'most_restrictive' // deny > require_approval > warn > allow
31+
| 'unanimous' // all must agree, otherwise most restrictive
32+
| 'majority' // majority vote, ties go to most restrictive
33+
| 'any_deny' // single deny blocks all
34+
| 'all_allow' // all must allow, otherwise most restrictive
35+
| 'custom'; // implementation-defined (must document)
36+
37+
/**
38+
* Decision severity for ordering.
39+
* Used to determine "most restrictive" in aggregation.
40+
*/
41+
export type DecisionSeverity = 0 | 1 | 2 | 3; // allow=0, warn=1, require_approval=2, deny=3
42+
2543
/**
2644
* Policy evaluation audit record.
2745
* Extended to support chain-of-custody reconstruction.
@@ -41,9 +59,10 @@ export interface AuditPolicy {
4159

4260
/**
4361
* Aggregation method used to combine individual policy decisions.
44-
* Examples: 'most_restrictive', 'unanimous', 'majority'
62+
* REQUIRED if multiple policies were evaluated.
63+
* Ensures deterministic aggregation across orchestrators.
4564
*/
46-
aggregation_method?: string;
65+
aggregation_method?: AggregationMethod;
4766

4867
/**
4968
* Workflow state when this policy evaluation occurred.
@@ -86,7 +105,14 @@ export interface PolicyEvaluation {
86105
/**
87106
* Decision from this specific policy.
88107
*/
89-
decision: 'allow' | 'warn' | 'require_approval' | 'deny';
108+
decision: Decision;
109+
110+
/**
111+
* Decision severity for aggregation ordering.
112+
* 0=allow, 1=warn, 2=require_approval, 3=deny
113+
* REQUIRED for deterministic "most restrictive" aggregation.
114+
*/
115+
severity: DecisionSeverity;
90116

91117
/**
92118
* Reason for this decision.

0 commit comments

Comments
 (0)