mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
fix(desktop): integrate lifecycle with captured launch admission
Use producer preflight authority and shared captured Start/Restart admission. Capture the existing explicit Start Stop fence and runtime generation before await; keep probes and Restart without resume authority and preserve Failed after destructive Stop. Broker remains provisioning-unavailable. Native compilation remains gated on CI-only draft authorization; local formatting and integrated frontend checks pass. Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
@@ -339,7 +339,7 @@ fn execute(
|
||||
conn: &rusqlite::Connection,
|
||||
owner: &str,
|
||||
request: &Request,
|
||||
prepared: Result<&managed_agents::runtime_configurations::PreparedLaunch, &Outcome>,
|
||||
prepared: Result<&configurations::CapturedLaunch, &Outcome>,
|
||||
) -> Result<Outcome, String> {
|
||||
let target = &request.target;
|
||||
if request.configuration.is_none() {
|
||||
@@ -375,37 +375,40 @@ fn execute(
|
||||
community: &target.community,
|
||||
agent: &target.agent,
|
||||
},
|
||||
plan.record(),
|
||||
plan.plan.record(),
|
||||
) {
|
||||
Ok(b) => b,
|
||||
Err(outcome) => return Ok(outcome),
|
||||
};
|
||||
if request.action == Action::Restart
|
||||
&& managed_agents::stop_pair_locked(
|
||||
target.agent.clone(),
|
||||
target.community.clone(),
|
||||
app.clone(),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Ok(Outcome::Failed);
|
||||
}
|
||||
// Existing transition lock spans final intent check, ordinary launch and registration.
|
||||
// Shared admission validates the captured Stop fence and generation BEFORE
|
||||
// destructive Restart. The transition lock spans that Stop, launch and receipt.
|
||||
if placement::blocked(conn, &target.agent, &target.desktop)? {
|
||||
return Ok(Outcome::Unknown);
|
||||
}
|
||||
match managed_agents::start_pair_prepared_locked(
|
||||
match managed_agents::start_pair_captured_locked(
|
||||
target.agent.clone(),
|
||||
target.community.clone(),
|
||||
true,
|
||||
None,
|
||||
true,
|
||||
plan.resume.as_ref(),
|
||||
Some(&broker),
|
||||
Some(plan),
|
||||
&plan.plan,
|
||||
false, // Explicit wire reference, not the destination's next selection.
|
||||
request.action == Action::Restart,
|
||||
Some(&plan.generation),
|
||||
app.clone(),
|
||||
) {
|
||||
Ok(_) => Ok(Outcome::Running),
|
||||
Err(_) => Ok(Outcome::Failed),
|
||||
Ok(status)
|
||||
if status.running_configuration == request.configuration
|
||||
&& !matches!(
|
||||
status.lifecycle,
|
||||
managed_agents::ManagedAgentRuntimeLifecycle::Failed
|
||||
| managed_agents::ManagedAgentRuntimeLifecycle::Stopped
|
||||
) =>
|
||||
{
|
||||
Ok(Outcome::Running)
|
||||
}
|
||||
Ok(_) | Err(_) => Ok(Outcome::Failed),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,13 @@ use super::*;
|
||||
use buzz_core_pkg::desktop_lifecycle::{CatalogPage, Observation, RuntimeConfigurationSummary};
|
||||
use managed_agents::runtime_configurations::{self as configurations, PreparedLaunch};
|
||||
|
||||
/// The shared launch plan and its pre-await lifecycle fences; never wire data.
|
||||
pub(super) struct CapturedLaunch {
|
||||
pub(super) plan: PreparedLaunch,
|
||||
pub(super) resume: Option<managed_agents::remote_stop::ResumeTicket>,
|
||||
pub(super) generation: Option<String>,
|
||||
}
|
||||
|
||||
fn record(app: &AppHandle, agent: &str) -> Result<managed_agents::ManagedAgentRecord, String> {
|
||||
let state = app.state::<AppState>();
|
||||
let _store = state
|
||||
@@ -19,16 +26,51 @@ pub(super) async fn prepare(
|
||||
app: &AppHandle,
|
||||
owner: &str,
|
||||
request: &Request,
|
||||
) -> Result<PreparedLaunch, Outcome> {
|
||||
) -> Result<CapturedLaunch, Outcome> {
|
||||
let reference = request.configuration.as_ref().ok_or(Outcome::Ineligible)?;
|
||||
let record = record(app, &request.target.agent).map_err(|_| Outcome::Ineligible)?;
|
||||
let plan = configurations::prepare_for_app(
|
||||
app,
|
||||
&record,
|
||||
Some(reference),
|
||||
owner,
|
||||
&request.target.community,
|
||||
)
|
||||
let mut captured = (|| -> Result<CapturedLaunch, String> {
|
||||
let state = app.state::<AppState>();
|
||||
let _transition = state
|
||||
.managed_agent_runtime_transition
|
||||
.lock()
|
||||
.map_err(|e| e.to_string())?;
|
||||
scope(app, &state, owner, &request.target.community)?;
|
||||
let record = record(app, &request.target.agent)?;
|
||||
let plan = configurations::prepare_for_app(
|
||||
app,
|
||||
&record,
|
||||
Some(reference),
|
||||
owner,
|
||||
&request.target.community,
|
||||
)?;
|
||||
let key = managed_agents::ManagedAgentRuntimeKey::new(
|
||||
&request.target.agent,
|
||||
&request.target.community,
|
||||
)?;
|
||||
// Only explicit Start can supersede an older Stop. Capture its existing
|
||||
// shared fence before await; probes and Restart receive no resume authority.
|
||||
let resume = if request.action == Action::Start {
|
||||
Some(managed_agents::remote_stop::capture_resume(
|
||||
app,
|
||||
&key,
|
||||
&request.target.community,
|
||||
owner,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let generation = state
|
||||
.managed_agent_processes
|
||||
.lock()
|
||||
.map_err(|e| e.to_string())?
|
||||
.get(&key)
|
||||
.map(|runtime| runtime.start_nonce.clone());
|
||||
Ok(CapturedLaunch {
|
||||
plan,
|
||||
resume,
|
||||
generation,
|
||||
})
|
||||
})()
|
||||
.map_err(|_| Outcome::Ineligible)?;
|
||||
// Do not advertise an eligible Switch that is known to fail after source Stop.
|
||||
broker_launch::provision(
|
||||
@@ -37,20 +79,38 @@ pub(super) async fn prepare(
|
||||
community: &request.target.community,
|
||||
agent: &request.target.agent,
|
||||
},
|
||||
plan.record(),
|
||||
captured.plan.record(),
|
||||
)?;
|
||||
configurations::preflight_prepared(app, &plan, owner, &request.target.community)
|
||||
.await
|
||||
.map_err(|_| Outcome::Ineligible)?;
|
||||
Ok(plan)
|
||||
configurations::preflight_with(
|
||||
&mut captured.plan,
|
||||
owner,
|
||||
&request.target.community,
|
||||
false,
|
||||
|model, allow| async move {
|
||||
#[cfg(feature = "mesh-llm")]
|
||||
{
|
||||
crate::commands::ensure_relay_mesh_for_record(app, model.as_deref(), allow).await
|
||||
}
|
||||
#[cfg(not(feature = "mesh-llm"))]
|
||||
{
|
||||
let _ = (app, model, allow);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Outcome::Ineligible)?;
|
||||
Ok(captured)
|
||||
}
|
||||
|
||||
pub(super) fn revalidate(
|
||||
app: &AppHandle,
|
||||
owner: &str,
|
||||
request: &Request,
|
||||
plan: &PreparedLaunch,
|
||||
captured: &CapturedLaunch,
|
||||
) -> Result<(), String> {
|
||||
let plan = &captured.plan;
|
||||
plan.require_preflight()?;
|
||||
plan.check_scope(Some(owner), &request.target.community)?;
|
||||
if plan.configuration().as_ref() != request.configuration.as_ref() {
|
||||
return Err("Prepared configuration does not match the request".into());
|
||||
|
||||
@@ -463,8 +463,12 @@ confirms source Stop, then requests a fresh Start of that exact revision. Prefli
|
||||
and Catalog cannot write placement intent or stop a process. The native consumer
|
||||
uses ordinary asynchronous preflight outside the transition lock, revalidates the
|
||||
immutable plan inside it, and delegates to the shared prepared launcher under the
|
||||
existing admission/Stop fences. No second spawn path or launch authority is allowed.
|
||||
Missing/edited targets fail rather than substitute another configuration. Remote
|
||||
existing admission/Stop fences. Only explicit Start captures the shared Stop-fence
|
||||
resume ticket before preflight; probes and Restart never receive that authority.
|
||||
Shared admission checks the captured runtime generation before destructive Restart,
|
||||
and a post-Stop Failed status must never become a Running response. No second spawn
|
||||
path or launch authority is allowed. Missing/edited targets fail rather than
|
||||
substitute another configuration. Remote
|
||||
keyless provisioning remains its existing independent fail-closed gate; a known
|
||||
provisioning failure must exclude Switch before source Stop.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user