hive-c0re: fast-forward applied/<name>/main too, not just the one-shot relock

argus + mara (PR #4339 review): the previous commit's per-agent lock
relock is a one-shot effect on the single rebuild the cascade triggers
- applied/<name> never moves, so the next relock=true rebuild trigger
(the boot sweep, most notably) re-locks against applied/<name> and
reverts straight back to whatever it was stuck on. The fix didn't
outlive the transaction it ran in.

New forge::fast_forward_applied_main(name), sibling to the existing
reseed-only fetch_config_main_into_applied: for an applied repo that
already has a .git and just needs to catch up, force-set rather than
fast-forward-gated since there's no PR to review on this path either.
Called per cascade agent right after the relock, best-effort so one
unreachable agent repo doesn't block the others.

Once applied/<name>/main has actually moved, lock_update_for_rebuild's
override (always reads current applied/<name>/main, no ?rev pin)
naturally stays in sync on any later relock=true rebuild instead of
reverting.
This commit is contained in:
damocles 2026-09-13 16:24:34 +02:00 committed by mara
commit c45d679a32
3 changed files with 87 additions and 11 deletions

View file

@ -551,19 +551,35 @@ async fn run_meta_lock(
// Pull each cascade agent's own input too — an agent's config-repo main
// is trusted, so a meta-input bump is a reasonable place to also catch
// it up. Without this, a meta-input bump rebuilds every affected agent
// against whatever `applied/<name>`
// already happened to be locked to — normally current, but silently
// stale forever if a past deploy failed and nothing since retried it.
// Relocks against each agent's *declared* input (the forge URL, not the
// local `applied/<name>` mirror `prepare_deploy` uses for a reviewed
// deploy) — this path has no PR to review, so there's nothing to gate.
// One combined call, not per-agent: simpler, at the cost of one
// unreachable/broken agent repo failing the whole cascade relock rather
// than just that agent.
// against whatever `applied/<name>` already happened to be locked to —
// normally current, but silently stale forever if a past deploy failed
// and nothing since retried it. Relocks against each agent's *declared*
// input (the forge URL, not the local `applied/<name>` mirror
// `prepare_deploy` uses for a reviewed deploy) — this path has no PR to
// review, so there's nothing to gate. One combined call, not per-agent:
// simpler, at the cost of one unreachable/broken agent repo failing the
// whole cascade relock rather than just that agent.
if !cascade.is_empty() {
let agent_inputs: Vec<String> =
cascade.iter().map(|name| format!("agent-{name}")).collect();
crate::meta::lock_update(&agent_inputs).await?;
// The relock above is a one-shot effect on *this* rebuild only —
// `applied/<name>` itself hasn't moved, so the next `relock = true`
// rebuild trigger (the boot sweep, most notably) re-locks against
// `applied/<name>` and reverts straight back to whatever it was
// stuck on. Fast-forward each cascade agent's own applied mirror
// too so a later rebuild through a different trigger doesn't undo
// this one. Best-effort per agent (unlike the relock above): one
// agent's forge repo being briefly unreachable here shouldn't be
// fatal to the cascade that already relocked its input above.
for name in &cascade {
if !crate::forge::fast_forward_applied_main(name).await {
tracing::warn!(
%name,
"meta-update cascade: applied/main fast-forward skipped or failed"
);
}
}
}
// Grow one rebuild subgraph per affected agent into *this* meta-update
// DAG (rooted on this `MetaLock`, so they build against the post-bump