1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! Machine payment DTOs and internal execution contract.
//!
//! Design decision: payment is an internal authority consumed by capabilities,
//! not a generic model-facing paid HTTP tool. Domain tools such as
//! `parallel_search` build typed requests and let the platform resolve wallets,
//! enforce policy, sign, settle, and record receipts.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::typed_id::{PaymentAccountId, PaymentAttemptId, PaymentPolicyId};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
/// Payment rail used to settle a machine payment.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentRail {
MppTempo,
X402Base,
}
impl PaymentRail {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentRail::MppTempo => "mpp_tempo",
PaymentRail::X402Base => "x402_base",
}
}
}
impl std::fmt::Display for PaymentRail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl std::str::FromStr for PaymentRail {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"mpp_tempo" => Ok(PaymentRail::MppTempo),
"x402_base" => Ok(PaymentRail::X402Base),
_ => Err(format!("Invalid payment rail: {value}")),
}
}
}
impl From<&str> for PaymentRail {
fn from(value: &str) -> Self {
match value {
"x402_base" => PaymentRail::X402Base,
_ => PaymentRail::MppTempo,
}
}
}
/// Principal class that owns a payment account.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentOwnerType {
User,
AgentIdentity,
Organization,
}
impl PaymentOwnerType {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentOwnerType::User => "user",
PaymentOwnerType::AgentIdentity => "agent_identity",
PaymentOwnerType::Organization => "organization",
}
}
}
impl std::fmt::Display for PaymentOwnerType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl From<&str> for PaymentOwnerType {
fn from(value: &str) -> Self {
match value {
"agent_identity" => PaymentOwnerType::AgentIdentity,
"organization" => PaymentOwnerType::Organization,
_ => PaymentOwnerType::User,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum PaymentStatus {
Active,
Disabled,
Pending,
Succeeded,
Failed,
Released,
}
impl PaymentStatus {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentStatus::Active => "active",
PaymentStatus::Disabled => "disabled",
PaymentStatus::Pending => "pending",
PaymentStatus::Succeeded => "succeeded",
PaymentStatus::Failed => "failed",
PaymentStatus::Released => "released",
}
}
}
impl std::fmt::Display for PaymentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl From<&str> for PaymentStatus {
fn from(value: &str) -> Self {
match value {
"disabled" => PaymentStatus::Disabled,
"pending" => PaymentStatus::Pending,
"succeeded" => PaymentStatus::Succeeded,
"failed" => PaymentStatus::Failed,
"released" => PaymentStatus::Released,
_ => PaymentStatus::Active,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentAccount {
/// Prefixed public identifier (see `specs/id-schema.md`).
pub id: PaymentAccountId,
/// Owning organization's prefixed public identifier.
pub organization_id: String,
/// Principal class that owns this account (user, agent identity, or organization).
pub owner_type: PaymentOwnerType,
/// Prefixed identifier of the owning principal (e.g. `user_…`, `agent_…`, `org_…`).
pub owner_id: String,
/// Settlement rail this account operates on.
pub rail: PaymentRail,
/// Human-readable label for this account. Safe to render in user-facing messages.
pub label: String,
/// Public address on the rail (chain address, account number, etc.). Optional; `None` until provisioning completes.
pub public_address: Option<String>,
/// Current lifecycle status of this account.
pub status: PaymentStatus,
/// Free-form metadata attached to this account (caller-defined; opaque to the platform).
pub metadata: serde_json::Value,
/// Timestamp when this account was created (RFC 3339).
pub created_at: DateTime<Utc>,
/// Timestamp when this account was last updated (RFC 3339).
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentPolicy {
/// Prefixed public identifier (see `specs/id-schema.md`).
pub id: PaymentPolicyId,
/// Owning organization's prefixed public identifier.
pub organization_id: String,
/// Payment account this policy authorizes spending from.
pub payment_account_id: PaymentAccountId,
/// Class of subject this policy binds to (e.g. `agent_identity`, `session`).
pub subject_type: String,
/// Prefixed identifier of the bound subject.
pub subject_id: String,
/// Capability IDs this policy permits paid calls for. Empty list means no capability gating.
pub allowed_capabilities: Vec<String>,
/// HTTP host allowlist for paid outbound calls. Empty list means no host gating.
pub allowed_hosts: Vec<String>,
/// Preferred settlement rails in priority order; the authority picks the first available.
pub rail_preference: Vec<PaymentRail>,
/// Maximum amount (USD) any single paid request may settle for. **Enforced** by the payment authority at policy selection. `None` means no per-request cap.
pub max_amount_usd_per_request: Option<f64>,
/// Maximum cumulative amount (USD) per agent turn. **Advisory only — not yet enforced.** Stored on the policy for forward compatibility; the payment authority currently checks only `max_amount_usd_per_request`. `None` means no per-turn cap.
pub max_amount_usd_per_turn: Option<f64>,
/// Maximum cumulative amount (USD) per UTC day. **Advisory only — not yet enforced.** Stored on the policy for forward compatibility; the payment authority currently checks only `max_amount_usd_per_request`. `None` means no per-day cap.
pub max_amount_usd_per_day: Option<f64>,
/// Threshold (USD) above which a request would require explicit human approval. **Advisory only — not yet enforced.** Stored on the policy for forward compatibility; no approval gate is wired up yet. `None` disables the (future) gate.
pub require_approval_above_usd: Option<f64>,
/// Current lifecycle status of this policy.
pub status: PaymentStatus,
/// Free-form metadata attached to this policy.
pub metadata: serde_json::Value,
/// Timestamp when this policy was created (RFC 3339).
pub created_at: DateTime<Utc>,
/// Timestamp when this policy was last updated (RFC 3339).
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct PaymentAttempt {
/// Prefixed public identifier (see `specs/id-schema.md`).
pub id: PaymentAttemptId,
/// Owning organization's prefixed public identifier.
pub organization_id: String,
/// Payment account that settled (or attempted to settle) this attempt. `None` if no account could be resolved.
pub payment_account_id: Option<PaymentAccountId>,
/// Session that initiated the paid call, if any.
pub session_id: Option<String>,
/// Capability ID that originated this paid call.
pub capability: String,
/// Capability-specific operation name that originated this paid call.
pub operation: String,
/// Settlement rail actually used. `None` if the attempt failed before rail selection.
pub rail: Option<PaymentRail>,
/// Amount actually charged (USD).
pub amount_usd: f64,
/// ISO 4217 currency code for the charge (typically `USD`).
pub currency: String,
/// Destination URL of the paid outbound call.
pub target_url: String,
/// Stable hash of the outbound request used to detect replays. `None` when not applicable.
pub request_hash: Option<String>,
/// Current lifecycle status of this attempt.
pub status: PaymentStatus,
/// Human-readable error message when `status` is `failed`; `None` otherwise.
pub error_message: Option<String>,
/// Rail-specific receipt payload (transaction id, block reference, signature, etc.).
pub receipt: serde_json::Value,
/// Timestamp when this attempt was created (RFC 3339).
pub created_at: DateTime<Utc>,
/// Timestamp when this attempt was last updated (RFC 3339).
pub updated_at: DateTime<Utc>,
}
/// HTTP method for an internal paid request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum PaymentMethod {
Get,
Post,
}
impl PaymentMethod {
pub fn as_wire(&self) -> &'static str {
match self {
PaymentMethod::Get => "GET",
PaymentMethod::Post => "POST",
}
}
}
impl std::fmt::Display for PaymentMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
impl std::str::FromStr for PaymentMethod {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"GET" => Ok(PaymentMethod::Get),
"POST" => Ok(PaymentMethod::Post),
_ => Err(format!("Invalid payment method: {value}")),
}
}
}
/// Internal request from a capability to the payment authority.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachinePaymentRequest {
pub capability: String,
pub operation: String,
pub method: PaymentMethod,
pub url: String,
pub body: Option<serde_json::Value>,
pub max_amount_usd: f64,
pub rail_preference: Vec<PaymentRail>,
pub metadata: serde_json::Value,
}
/// Response returned to the calling capability after payment and execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachinePaymentResponse {
pub attempt_id: Option<PaymentAttemptId>,
pub amount_usd: f64,
pub rail: Option<PaymentRail>,
pub response: serde_json::Value,
pub receipt: serde_json::Value,
}