Skip to content

Commit 8ada725

Browse files
ayushozhaAyush Ojha
authored andcommitted
feat: add structured Risk Payload schema for Section 7.4 risk signals
Define TripConditionType, FCBState, and RiskPayload types for runtime risk governance. Add optional risk_payload field to IntentMandate, CartMandate, and PaymentMandateContents to enable structured risk signal exchange between agents. Fixes #163
1 parent 61f5de4 commit 8ada725

3 files changed

Lines changed: 182 additions & 0 deletions

File tree

src/ap2/types/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,17 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
from ap2.types.risk import FCBState
16+
from ap2.types.risk import RiskPayload
17+
from ap2.types.risk import TripCondition
18+
from ap2.types.risk import TripConditionStatus
19+
from ap2.types.risk import TripConditionType
20+
21+
__all__ = [
22+
"FCBState",
23+
"RiskPayload",
24+
"TripCondition",
25+
"TripConditionStatus",
26+
"TripConditionType",
27+
]

src/ap2/types/mandate.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ap2.types.payment_request import PaymentItem
2222
from ap2.types.payment_request import PaymentRequest
2323
from ap2.types.payment_request import PaymentResponse
24+
from ap2.types.risk import RiskPayload
2425
from pydantic import BaseModel
2526
from pydantic import Field
2627

@@ -74,6 +75,13 @@ class IntentMandate(BaseModel):
7475
...,
7576
description="When the intent mandate expires, in ISO 8601 format.",
7677
)
78+
risk_payload: Optional[RiskPayload] = Field(
79+
None,
80+
description=(
81+
"Optional structured risk payload containing the current risk"
82+
" assessment state for this intent, as defined in Section 7.4."
83+
),
84+
)
7785

7886

7987
class CartContents(BaseModel):
@@ -132,6 +140,13 @@ class CartMandate(BaseModel):
132140
"""),
133141
example="eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjQwOTA...", # Example JWT
134142
)
143+
risk_payload: Optional[RiskPayload] = Field(
144+
None,
145+
description=(
146+
"Optional structured risk payload containing the current risk"
147+
" assessment state for this cart, as defined in Section 7.4."
148+
),
149+
)
135150

136151

137152
class PaymentMandateContents(BaseModel):
@@ -160,6 +175,13 @@ class PaymentMandateContents(BaseModel):
160175
),
161176
default_factory=lambda: datetime.now(timezone.utc).isoformat(),
162177
)
178+
risk_payload: Optional[RiskPayload] = Field(
179+
None,
180+
description=(
181+
"Optional structured risk payload containing the current risk"
182+
" assessment state for this payment, as defined in Section 7.4."
183+
),
184+
)
163185

164186

165187
class PaymentMandate(BaseModel):

src/ap2/types/risk.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Structured Risk Payload schema for Section 7.4 Risk Signals.
16+
17+
Defines the types used for runtime risk governance between agents,
18+
including trip conditions, circuit breaker (FCB) state, and the
19+
overall risk payload that can be attached to mandates.
20+
"""
21+
22+
from datetime import datetime
23+
from datetime import timezone
24+
from enum import Enum
25+
from typing import Optional
26+
27+
from pydantic import BaseModel
28+
from pydantic import Field
29+
30+
31+
class TripConditionType(str, Enum):
32+
"""The type of trip condition evaluated during risk assessment."""
33+
34+
VALUE_THRESHOLD = "VALUE_THRESHOLD"
35+
CUMULATIVE_THRESHOLD = "CUMULATIVE_THRESHOLD"
36+
VELOCITY = "VELOCITY"
37+
AUTHORITY_SCOPE = "AUTHORITY_SCOPE"
38+
ANOMALY = "ANOMALY"
39+
TIME_BASED = "TIME_BASED"
40+
DEVIATION = "DEVIATION"
41+
42+
43+
class TripConditionStatus(str, Enum):
44+
"""The result status of a trip condition evaluation."""
45+
46+
PASS = "PASS"
47+
FAIL = "FAIL"
48+
WARNING = "WARNING"
49+
50+
51+
class FCBState(str, Enum):
52+
"""The state of the Fiduciary Circuit Breaker (FCB).
53+
54+
The FCB acts as a runtime safety mechanism that can halt or throttle
55+
agent actions when risk thresholds are exceeded.
56+
"""
57+
58+
CLOSED = "CLOSED"
59+
OPEN = "OPEN"
60+
HALF_OPEN = "HALF_OPEN"
61+
TERMINATED = "TERMINATED"
62+
63+
64+
class TripCondition(BaseModel):
65+
"""A single trip condition evaluated as part of risk assessment.
66+
67+
Each trip condition represents one risk check performed against the
68+
current transaction or agent action.
69+
"""
70+
71+
type: TripConditionType = Field(
72+
...,
73+
description="The type of trip condition being evaluated.",
74+
)
75+
status: TripConditionStatus = Field(
76+
...,
77+
description="The result status of this trip condition evaluation.",
78+
)
79+
threshold: Optional[float] = Field(
80+
None,
81+
description=(
82+
"The threshold value for this condition. For example, a maximum"
83+
" transaction amount or rate limit."
84+
),
85+
)
86+
actual_value: Optional[float] = Field(
87+
None,
88+
description=(
89+
"The actual observed value that was compared against the threshold."
90+
),
91+
)
92+
description: Optional[str] = Field(
93+
None,
94+
description=(
95+
"A human-readable description of the trip condition and its result."
96+
),
97+
)
98+
99+
100+
class RiskPayload(BaseModel):
101+
"""Structured risk payload for Section 7.4 risk signal exchange.
102+
103+
This payload captures the current risk assessment state, including
104+
the fiduciary circuit breaker state, evaluated trip conditions, an
105+
overall risk score, and the identity of the agent that produced the
106+
assessment.
107+
"""
108+
109+
fcb_state: FCBState = Field(
110+
...,
111+
description=(
112+
"The current state of the Fiduciary Circuit Breaker. CLOSED means"
113+
" normal operation, OPEN means the circuit has tripped and actions"
114+
" are blocked, HALF_OPEN means the system is testing whether it is"
115+
" safe to resume, and TERMINATED means the circuit is permanently"
116+
" open."
117+
),
118+
)
119+
trip_conditions: list[TripCondition] = Field(
120+
default_factory=list,
121+
description=(
122+
"The list of trip conditions that were evaluated as part of the"
123+
" risk assessment."
124+
),
125+
)
126+
risk_score: Optional[float] = Field(
127+
None,
128+
description=(
129+
"An overall risk score for the transaction, typically in the range"
130+
" [0.0, 1.0] where 0.0 indicates no risk and 1.0 indicates maximum"
131+
" risk."
132+
),
133+
)
134+
agent_identity: Optional[str] = Field(
135+
None,
136+
description=(
137+
"The identity of the agent that produced this risk assessment."
138+
),
139+
)
140+
timestamp: str = Field(
141+
description=(
142+
"The date and time the risk assessment was produced, in ISO 8601"
143+
" format."
144+
),
145+
default_factory=lambda: datetime.now(timezone.utc).isoformat(),
146+
)

0 commit comments

Comments
 (0)