manager_server: fetch+tag at request_apply_commit submit
submit_apply_commit (1) queues the approval row, (2) git-fetches the manager-supplied sha from proposed into applied, pins it as refs/tags/proposal/<id>, (3) persists the resolved sha on the row via approvals.set_fetched_sha. from this point on the proposal is immutable from the manager's perspective: amends or force-pushes in proposed do not change what hive-c0re will build. fetch failures mark the row failed and surface the error to the manager so a phantom pending entry can't linger.
This commit is contained in:
parent
8cb8fcedad
commit
35b0edaf27
1 changed files with 63 additions and 3 deletions
|
|
@ -258,9 +258,9 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
||||||
}
|
}
|
||||||
ManagerRequest::RequestApplyCommit { agent, commit_ref } => {
|
ManagerRequest::RequestApplyCommit { agent, commit_ref } => {
|
||||||
tracing::info!(%agent, %commit_ref, "manager: request_apply_commit");
|
tracing::info!(%agent, %commit_ref, "manager: request_apply_commit");
|
||||||
match coord.approvals.submit(agent, commit_ref) {
|
match submit_apply_commit(coord, agent, commit_ref).await {
|
||||||
Ok(id) => {
|
Ok((id, sha)) => {
|
||||||
tracing::info!(%id, %agent, %commit_ref, "approval queued");
|
tracing::info!(%id, %agent, manager_ref = %commit_ref, %sha, "approval queued + proposal tag planted");
|
||||||
ManagerResponse::Ok
|
ManagerResponse::Ok
|
||||||
}
|
}
|
||||||
Err(e) => ManagerResponse::Err {
|
Err(e) => ManagerResponse::Err {
|
||||||
|
|
@ -271,6 +271,66 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Submit-time half of the apply flow: queue the approval row, then
|
||||||
|
/// fetch the manager's commit from the proposed repo into applied and
|
||||||
|
/// pin it as `refs/tags/proposal/<id>`. From this point on the manager
|
||||||
|
/// repo is irrelevant for this approval — even if the manager amends
|
||||||
|
/// or force-pushes, the canonical sha hive-c0re will eventually
|
||||||
|
/// approve/deny lives in applied's object DB.
|
||||||
|
///
|
||||||
|
/// If anything fails after the row is inserted (sha missing in
|
||||||
|
/// proposed, fs error, git plumbing crash) we mark the row failed and
|
||||||
|
/// surface the error to the manager. We don't try to roll the row
|
||||||
|
/// back — the failure is part of the audit trail.
|
||||||
|
async fn submit_apply_commit(
|
||||||
|
coord: &Arc<Coordinator>,
|
||||||
|
agent: &str,
|
||||||
|
commit_ref: &str,
|
||||||
|
) -> anyhow::Result<(i64, String)> {
|
||||||
|
let proposed_dir = crate::coordinator::Coordinator::agent_proposed_dir(agent);
|
||||||
|
let applied_dir = crate::coordinator::Coordinator::agent_applied_dir(agent);
|
||||||
|
if !proposed_dir.exists() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"proposed repo missing for agent '{agent}' (expected at {})",
|
||||||
|
proposed_dir.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !applied_dir.join(".git").exists() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"applied repo at {} is uninitialised — spawn the agent first",
|
||||||
|
applied_dir.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let id = coord
|
||||||
|
.approvals
|
||||||
|
.submit(agent, commit_ref)
|
||||||
|
.map_err(|e| anyhow::anyhow!("queue approval row: {e:#}"))?;
|
||||||
|
let tag = format!("proposal/{id}");
|
||||||
|
let sha = match crate::lifecycle::git_fetch_to_tag(
|
||||||
|
&applied_dir,
|
||||||
|
&proposed_dir,
|
||||||
|
commit_ref,
|
||||||
|
&tag,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
// Surface the failure on the approval row so the
|
||||||
|
// dashboard reflects it instead of leaving a phantom
|
||||||
|
// pending entry. The note doubles as the operator-visible
|
||||||
|
// explanation of why the approval can't be approved.
|
||||||
|
let _ = coord.approvals.mark_failed(id, &format!("{e:#}"));
|
||||||
|
return Err(anyhow::anyhow!("git_fetch_to_tag: {e:#}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
coord
|
||||||
|
.approvals
|
||||||
|
.set_fetched_sha(id, &sha)
|
||||||
|
.map_err(|e| anyhow::anyhow!("persist fetched_sha: {e:#}"))?;
|
||||||
|
Ok((id, sha))
|
||||||
|
}
|
||||||
|
|
||||||
/// On `AskOperator { ttl_seconds: Some(n) }`, sleep n seconds and then
|
/// On `AskOperator { ttl_seconds: Some(n) }`, sleep n seconds and then
|
||||||
/// try to resolve the question with `[expired]`. If the operator (or
|
/// try to resolve the question with `[expired]`. If the operator (or
|
||||||
/// any other path) already answered it, `answer()` returns Err and
|
/// any other path) already answered it, `answer()` returns Err and
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue