Skip to main content

agent_evaluate_v2_contract/
binding.rs

1//! Bindings: which snapshots an agent or runtime is expected to pass.
2
3use serde::{Deserialize, Serialize};
4
5use crate::catalog::SnapshotRef;
6use crate::report::GatePolicy;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum TargetKind {
11    Agent,
12    Runtime,
13}
14
15impl TargetKind {
16    pub const fn as_str(self) -> &'static str {
17        match self {
18            Self::Agent => "agent",
19            Self::Runtime => "runtime",
20        }
21    }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase", deny_unknown_fields)]
26pub struct BindingTarget {
27    pub kind: TargetKind,
28    pub id: String,
29}
30
31impl BindingTarget {
32    pub fn agent(id: impl Into<String>) -> Self {
33        Self {
34            kind: TargetKind::Agent,
35            id: id.into(),
36        }
37    }
38
39    pub fn runtime(id: impl Into<String>) -> Self {
40        Self {
41            kind: TargetKind::Runtime,
42            id: id.into(),
43        }
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase", deny_unknown_fields)]
49pub struct BoundSnapshot {
50    #[serde(rename = "ref")]
51    pub reference: SnapshotRef,
52    /// Non-required snapshots are observed but never gate.
53    #[serde(default = "default_required")]
54    pub required: bool,
55}
56
57fn default_required() -> bool {
58    true
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum BindingTrigger {
64    Manual,
65    OnDeploy,
66    Scheduled,
67}
68
69/// A binding is mutable, unlike everything in the catalogue. Runs freeze the
70/// resolved snapshot list at creation, so editing a binding never rewrites
71/// history.
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73#[serde(rename_all = "camelCase", deny_unknown_fields)]
74pub struct EvalBinding {
75    pub tenant_id: String,
76    pub project_id: String,
77    pub target: BindingTarget,
78    pub snapshots: Vec<BoundSnapshot>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub gate_policy: Option<GatePolicy>,
81    #[serde(default)]
82    pub triggers: Vec<BindingTrigger>,
83    /// Optimistic concurrency token.
84    pub version: i64,
85    pub created_at_ms: i64,
86    pub updated_at_ms: i64,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase", deny_unknown_fields)]
91pub struct PutBindingRequest {
92    pub target: BindingTarget,
93    pub snapshots: Vec<BoundSnapshot>,
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub gate_policy: Option<GatePolicy>,
96    #[serde(default)]
97    pub triggers: Vec<BindingTrigger>,
98    /// Expected current version. Absent means create-only.
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub if_version: Option<i64>,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase", deny_unknown_fields)]
105pub struct GetBindingRequest {
106    pub target: BindingTarget,
107}
108
109#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase", deny_unknown_fields)]
111pub struct ListBindingsRequest {
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub kind: Option<TargetKind>,
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub cursor: Option<String>,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub limit: Option<usize>,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct BindingPage {
123    pub items: Vec<EvalBinding>,
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub next_cursor: Option<String>,
126}