bento: wire the agent protocol-version skew check (Run 3 wiring)
HealthResponse.version / PROTOCOL_VERSION were advertised and documented as the
mechanism to "detect a skewed agent at preflight" but no client ever compared
the value — preflight discarded it. preflight() now checks it: version 0 (a
legacy agent predating the field) is tolerated, a mismatched non-zero version is
refused with a clear upgrade message, so skew surfaces before a run instead of
mid-stream. e2e test added. ops-exec 41 tests; clippy -D clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed,
+31 insertions,
-2 deletions
| 172 |
172 |
|
}
|
| 173 |
173 |
|
|
| 174 |
174 |
|
async fn preflight(&self) -> Result<()> {
|
| 175 |
|
- |
self.health().await.map(|_| ()).with_context(|| {
|
|
175 |
+ |
let health = self.health().await.with_context(|| {
|
| 176 |
176 |
|
format!(
|
| 177 |
177 |
|
"agent /health failed for `{}` — is ops-agent running in the Aqua session on the build host?",
|
| 178 |
178 |
|
self.host_label,
|
| 179 |
179 |
|
)
|
| 180 |
|
- |
})
|
|
180 |
+ |
})?;
|
|
181 |
+ |
// Detect a skewed agent here rather than mid-stream. version 0 = a legacy
|
|
182 |
+ |
// agent that predates the field (HealthResponse.version defaults to 0);
|
|
183 |
+ |
// tolerate it, but refuse a future major we don't speak.
|
|
184 |
+ |
anyhow::ensure!(
|
|
185 |
+ |
health.version == 0 || health.version == crate::wire::PROTOCOL_VERSION,
|
|
186 |
+ |
"agent `{}` speaks wire protocol v{}, but this client expects v{} — upgrade the mismatched side",
|
|
187 |
+ |
self.host_label,
|
|
188 |
+ |
health.version,
|
|
189 |
+ |
crate::wire::PROTOCOL_VERSION,
|
|
190 |
+ |
);
|
|
191 |
+ |
Ok(())
|
| 181 |
192 |
|
}
|
| 182 |
193 |
|
|
| 183 |
194 |
|
fn capabilities(&self) -> &CapabilitySet {
|
| 82 |
82 |
|
}
|
| 83 |
83 |
|
|
| 84 |
84 |
|
#[tokio::test]
|
|
85 |
+ |
async fn preflight_accepts_a_matching_protocol_version() {
|
|
86 |
+ |
let base = spawn_agent(
|
|
87 |
+ |
vec![CallerGrant {
|
|
88 |
+ |
identity: "fw13".into(),
|
|
89 |
+ |
actuate: vec!["build".into()],
|
|
90 |
+ |
observe: vec![],
|
|
91 |
+ |
}],
|
|
92 |
+ |
builder_grant(),
|
|
93 |
+ |
)
|
|
94 |
+ |
.await;
|
|
95 |
+ |
let rpc = AgentRpc::new(base, "mbp", CapabilitySet::from_tokens(["build"], Vec::<&str>::new()));
|
|
96 |
+ |
// The agent advertises PROTOCOL_VERSION; preflight must accept it.
|
|
97 |
+ |
let health = rpc.health().await.unwrap();
|
|
98 |
+ |
assert_eq!(health.version, ops_exec::wire::PROTOCOL_VERSION);
|
|
99 |
+ |
rpc.preflight().await.expect("preflight accepts a same-version agent");
|
|
100 |
+ |
}
|
|
101 |
+ |
|
|
102 |
+ |
#[tokio::test]
|
| 85 |
103 |
|
async fn agent_denies_action_outside_its_grant() {
|
| 86 |
104 |
|
// The agent host grants build/sign only; the caller asks to deploy. Even
|
| 87 |
105 |
|
// though the client-side caps below include deploy, the agent must refuse.
|