wip(#1838): add MergeConfigPr approvalkind + match-site arms

This commit is contained in:
damocles 2026-06-22 22:37:17 +02:00
commit 449e3fcb7b
4 changed files with 66 additions and 3 deletions

View file

@ -128,6 +128,28 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
}
result
}
ApprovalKind::MergeConfigPr => {
// Like ApplyCommit, the work ends in a container rebuild, so
// route it through the rebuild queue. The queue worker
// dispatches MergeConfigPr approvals to `run_merge_config_pr`
// (verify the reviewed PR head, ff the forge config repo's
// main to it, mark merged, then the shared deploy tail).
coord
.rebuild_queue
.enqueue_full(crate::rebuild_queue::FullEnqueue {
kind: crate::rebuild_queue::QueueKind::Rebuild,
agent: approval.agent.clone(),
source: crate::rebuild_queue::QueueSource::Approval,
reason: format!("approval #{id} merge config pr"),
parent_id: None,
inputs: Vec::new(),
approval_id: Some(id),
perm_payload: None,
depends_on: Vec::new(),
});
coord.emit_rebuild_queue_snapshot();
Ok(())
}
}
}
@ -381,6 +403,7 @@ fn finish_approval(
ApprovalKind::InitConfig => "init_config",
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
ApprovalKind::SchedulePrompt => "schedule_prompt",
ApprovalKind::MergeConfigPr => "merge_config_pr",
};
let sha_short = approval
.fetched_sha
@ -430,6 +453,15 @@ fn finish_approval(
sha: approval.fetched_sha.clone(),
tag: terminal_tag,
}),
// MergeConfigPr ends in a container rebuild like ApplyCommit, so
// surface the same Rebuilt lifecycle event.
ApprovalKind::MergeConfigPr => coord.notify_manager(&HelperEvent::Rebuilt {
agent: approval.agent.clone(),
ok,
note,
sha: approval.fetched_sha.clone(),
tag: terminal_tag,
}),
// UpdateMetaInputs / SchedulePrompt: ApprovalResolved already
// carries the result. No separate lifecycle event needed.
ApprovalKind::UpdateMetaInputs | ApprovalKind::SchedulePrompt => {}
@ -809,6 +841,7 @@ pub async fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()
ApprovalKind::InitConfig => "init_config",
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
ApprovalKind::SchedulePrompt => "schedule_prompt",
ApprovalKind::MergeConfigPr => "merge_config_pr",
};
let sha_short = sha.as_deref().map(|s| s[..s.len().min(12)].to_owned());
let description = a.description.clone();

View file

@ -335,6 +335,7 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result<Approval> {
"init_config" => ApprovalKind::InitConfig,
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
"schedule_prompt" => ApprovalKind::SchedulePrompt,
"merge_config_pr" => ApprovalKind::MergeConfigPr,
other => {
return Err(rusqlite::Error::FromSqlConversionFailure(
2,
@ -383,6 +384,7 @@ pub(crate) fn kind_to_str(kind: ApprovalKind) -> &'static str {
ApprovalKind::InitConfig => "init_config",
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
ApprovalKind::SchedulePrompt => "schedule_prompt",
ApprovalKind::MergeConfigPr => "merge_config_pr",
}
}
@ -393,6 +395,7 @@ fn kind_from_str(s: &str) -> Result<ApprovalKind> {
"init_config" => ApprovalKind::InitConfig,
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
"schedule_prompt" => ApprovalKind::SchedulePrompt,
"merge_config_pr" => ApprovalKind::MergeConfigPr,
other => bail!("unknown approval kind '{other}'"),
})
}

View file

@ -870,6 +870,7 @@ fn history_view(a: Approval) -> ApprovalHistoryView {
hive_sh4re::ApprovalKind::InitConfig => "init_config",
hive_sh4re::ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
hive_sh4re::ApprovalKind::SchedulePrompt => "schedule_prompt",
hive_sh4re::ApprovalKind::MergeConfigPr => "merge_config_pr",
};
ApprovalHistoryView {
id: a.id,
@ -939,6 +940,24 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
description: a.description,
requested_at: a.requested_at,
},
hive_sh4re::ApprovalKind::MergeConfigPr => {
// commit_ref = PR number; fetched_sha = the reviewed PR
// head. Show the head sha; the forge PR diff surface is
// a later phase (#1838 P4) — None for now.
let sha = a
.fetched_sha
.as_deref()
.map(|s| s[..s.len().min(12)].to_owned());
ApprovalView {
id: a.id,
agent: a.agent,
kind: "merge_config_pr",
sha_short: sha,
diff: None,
description: a.description,
requested_at: a.requested_at,
}
}
});
}
out

View file

@ -154,10 +154,12 @@ pub struct Approval {
/// Kind-specific payload (git sha / inputs array / schedule
/// payload / empty). See the Approval struct doc.
pub commit_ref: String,
/// `ApplyCommit` only: the canonical hive-c0re-vouched sha after
/// the proposal fetch, tagged `proposal/<id>`. Stable for the
/// The canonical hive-c0re-vouched sha. For `ApplyCommit`: the sha
/// after the proposal fetch, tagged `proposal/<id>` (stable for the
/// approval's lifetime — manager amends in proposed don't change
/// what gets built.
/// what gets built). For `MergeConfigPr`: the reviewed PR head
/// pinned at submit; if the PR head drifts off it before merge,
/// hive-c0re refreshes this + re-renders the card for re-review.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fetched_sha: Option<String>,
pub requested_at: i64,
@ -192,6 +194,12 @@ pub enum ApprovalKind {
UpdateMetaInputs,
/// Add a scheduled prompt to the broker queue.
SchedulePrompt,
/// Merge an operator-reviewed config PR: hive-c0re verifies the
/// reviewed PR head, fast-forwards the forge config repo's `main`
/// to it, marks the PR merged, then runs the same deploy tail as
/// `ApplyCommit`. `commit_ref` = PR number; `fetched_sha` = the
/// reviewed PR head pinned at submit. See `docs/approvals.md`.
MergeConfigPr,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]