Watch
0
0
Fork
You've already forked hyperhive
0

pin the declared closure once per rebuild and seed the marker from the running closure

This commit is contained in:
atlas 2026-11-02 00:42:45 +01:00 • committed by mara
commit 4356f3840c

View file

@ -45,6 +45,12 @@ pub(super) trait Host: Sync {
/// The record the swarm's builder wrote for the agent; `None` when it wrote
/// none.
async fn closure_record(&self, name: &str) -> Result<Option<AgentClosure>>;
/// Pin `store_path` as the closure this rebuild of the agent deploys.
fn pin_closure(&self, name: &str, store_path: &str);
/// The store path pinned for the agent's rebuild.
fn pinned_closure(&self, name: &str) -> Option<String>;
/// Drop the agent's pin.
fn unpin_closure(&self, name: &str);
/// Download `store_path` from the swarm's cache into the store, never
/// building it.
async fn realise_closure(&self, name: &str, store_path: &str) -> Result<()>;
@ -109,6 +115,18 @@ impl Host for RealHost {
crate::attic::closure::record(name).await
}
fn pin_closure(&self, name: &str, store_path: &str) {
crate::attic::closure::pin(name, store_path);
}
fn pinned_closure(&self, name: &str) -> Option<String> {
crate::attic::closure::pinned(name)
}
fn unpin_closure(&self, name: &str) {
crate::attic::closure::unpin(name);
}
async fn realise_closure(&self, name: &str, store_path: &str) -> Result<()> {
crate::priv_client::realise_closure(name, store_path).await
}
@ -471,8 +489,13 @@ fn run_set_wanted(coord: &Arc<Coordinator>, agent: &str, up: bool) -> Result<()>
}
/// The rebuild's preamble: runtime-dir prep, the archetype and agent-set
/// refresh. Reads nothing from the forge: what a rebuild deploys is the swarm's
/// declared closure, read by `Prebuild` and `Swap`.
/// refresh, and the closure the rebuild deploys. The swarm's record is read
/// once, here, and its store path pinned: `Prebuild` and `Swap` use the pin, so
/// a record that changes meanwhile cannot make `Swap` pull another closure than
/// `Prebuild` fetched, and the marker names what was applied. The next
/// auto-update pass picks up the newer record. Reads nothing from the forge.
///
/// Fails, before anything is stopped, when the swarm has no closure to deploy.
async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
// Runs while the agent is still up — the runtime dir and MCP listener
// already exist. Use the pure path accessor; no need to re-register the
@ -486,6 +509,10 @@ async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
}
host.prepare_rebuild_dirs(name, &paths).await?;
host.refresh_agent_set().await?;
let store_path = declared_closure(host, name)
.await
.map_err(|why| without_closure(name, "meta-sync", &why))?;
host.pin_closure(name, &store_path);
Ok(())
}
@ -496,7 +523,7 @@ async fn run_meta_sync(host: &impl Host, name: &str) -> Result<()> {
/// is already down: there is no uptime to protect, and `Swap` fails the same way.
async fn run_prebuild(host: &impl Host, name: &str) -> Result<()> {
if host.is_running(name).await {
let closure = closure_for(host, name, "prebuild").await?;
let closure = realise_pinned(host, name, "prebuild").await?;
tracing::info!(%name, "prebuild: substituted {closure}");
}
Ok(())
@ -512,18 +539,46 @@ enum NoClosure {
}
/// The store path of the closure the swarm declares for `name`, downloaded into
/// the store. Whatever `internal/meta` commit it was built from, it is the one
/// deployed.
/// the store, for a deploy that has no `MetaSync` to pin it first (a create).
/// Whatever `internal/meta` commit it was built from, it is the one deployed.
///
/// # Errors
/// [`without_closure`]'s refusal, after logging it.
async fn closure_for(host: &impl Host, name: &str, step: &str) -> Result<String> {
pull_closure(host, name)
.await
.map_err(|why| without_closure(name, step, &why))
async {
let store_path = declared_closure(host, name).await?;
realise(host, name, &store_path).await?;
Ok(store_path)
}
.await
.map_err(|why| without_closure(name, step, &why))
}
async fn pull_closure(host: &impl Host, name: &str) -> Result<String, NoClosure> {
/// Download the closure `MetaSync` pinned for the agent's rebuild.
///
/// # Errors
/// No closure was pinned, or the download failed; the latter is logged by
/// [`without_closure`].
async fn realise_pinned(host: &impl Host, name: &str, step: &str) -> Result<String> {
let Some(store_path) = host.pinned_closure(name) else {
let why = NoClosure::Unpullable("no closure was pinned for this rebuild".to_owned());
return Err(without_closure(name, step, &why));
};
realise(host, name, &store_path)
.await
.map_err(|why| without_closure(name, step, &why))?;
Ok(store_path)
}
async fn realise(host: &impl Host, name: &str, store_path: &str) -> Result<(), NoClosure> {
host.realise_closure(name, store_path)
.await
.map_err(|e| NoClosure::Unpullable(format!("{e:#}")))
}
/// The store path of the record the swarm's builder wrote for `name`, when the
/// builder pushed its closure.
async fn declared_closure(host: &impl Host, name: &str) -> Result<String, NoClosure> {
let record = host
.closure_record(name)
.await
@ -534,9 +589,6 @@ async fn pull_closure(host: &impl Host, name: &str) -> Result<String, NoClosure>
"the builder did not push the closure".to_owned(),
));
}
host.realise_closure(name, &record.store_path)
.await
.map_err(|e| NoClosure::Unpullable(format!("{e:#}")))?;
Ok(record.store_path)
}
@ -574,7 +626,7 @@ async fn run_swap(
let agent_dir = crate::paths::agent_runtime_dir(name);
let hive = coord.hive_env();
let paths = Coordinator::agent_paths(name, agent_dir)?;
let closure = closure_for(host, name, "swap").await?;
let closure = realise_pinned(host, name, "swap").await?;
tracing::info!(%name, "swap: applying {closure}");
let result = host
.swap_update(name, &hive, &paths, &closure, id.get())
@ -582,10 +634,11 @@ async fn run_swap(
// The marker names what was applied; a failed write is not a failed swap
// (the container runs the closure), it only means the next comparison
// sees the declaration as new.
if result.is_ok()
&& let Err(e) = host.mark_deployed(name, &closure)
{
tracing::warn!(%name, error = ?e, "write the deployed-closure marker failed");
if result.is_ok() {
if let Err(e) = host.mark_deployed(name, &closure) {
tracing::warn!(%name, error = ?e, "write the deployed-closure marker failed");
}
host.unpin_closure(name);
}
// On success the Ok-only bookkeeping tail (forge
// sync, kick, rescan, snapshot) runs in the sibling `RebuildBookkeeping` node,