diff --git a/hive-c0re/src/approvals.rs b/hive-c0re/src/approvals.rs index b770178..926aad5 100644 --- a/hive-c0re/src/approvals.rs +++ b/hive-c0re/src/approvals.rs @@ -41,6 +41,21 @@ fn ensure_kind_column(conn: &Connection) -> Result<()> { Ok(()) } +/// Same shape as `ensure_kind_column` but for `fetched_sha` — the +/// canonical sha hive-c0re vouched for at request_apply_commit time. +/// Distinct from `commit_ref` (manager-supplied, may not even resolve +/// in proposed by the time we approve). +fn ensure_fetched_sha_column(conn: &Connection) -> Result<()> { + let has: bool = conn + .prepare("SELECT 1 FROM pragma_table_info('approvals') WHERE name = 'fetched_sha'")? + .exists([])?; + if !has { + conn.execute_batch("ALTER TABLE approvals ADD COLUMN fetched_sha TEXT;") + .context("add approvals.fetched_sha column")?; + } + Ok(()) +} + pub struct Approvals { conn: Mutex, } @@ -56,6 +71,7 @@ impl Approvals { conn.execute_batch(SCHEMA) .context("apply approvals schema")?; ensure_kind_column(&conn).context("migrate approvals.kind")?; + ensure_fetched_sha_column(&conn).context("migrate approvals.fetched_sha")?; Ok(Self { conn: Mutex::new(conn), }) @@ -75,10 +91,22 @@ impl Approvals { Ok(conn.last_insert_rowid()) } + /// Record the canonical sha hive-c0re fetched from the proposed repo + /// into applied at submission time. Idempotent on identical values. + #[allow(dead_code)] // wired up by manager_server in the next commit + pub fn set_fetched_sha(&self, id: i64, sha: &str) -> Result<()> { + let conn = self.conn.lock().unwrap(); + conn.execute( + "UPDATE approvals SET fetched_sha = ?1 WHERE id = ?2", + params![sha, id], + )?; + Ok(()) + } + pub fn pending(&self) -> Result> { let conn = self.conn.lock().unwrap(); let mut stmt = conn.prepare( - "SELECT id, agent, kind, commit_ref, requested_at, status, resolved_at, note + "SELECT id, agent, kind, commit_ref, requested_at, status, resolved_at, note, fetched_sha FROM approvals WHERE status = 'pending' ORDER BY id ASC", @@ -91,7 +119,7 @@ impl Approvals { pub fn get(&self, id: i64) -> Result> { let conn = self.conn.lock().unwrap(); conn.query_row( - "SELECT id, agent, kind, commit_ref, requested_at, status, resolved_at, note + "SELECT id, agent, kind, commit_ref, requested_at, status, resolved_at, note, fetched_sha FROM approvals WHERE id = ?1", params![id], row_to_approval, @@ -104,9 +132,10 @@ impl Approvals { /// approval so the caller can run the action and pass the agent name. pub fn mark_approved(&self, id: i64) -> Result { let conn = self.conn.lock().unwrap(); - let current: Option<(String, String, String, i64, String)> = conn + let current: Option<(String, String, String, i64, String, Option)> = conn .query_row( - "SELECT agent, kind, commit_ref, requested_at, status FROM approvals WHERE id = ?1", + "SELECT agent, kind, commit_ref, requested_at, status, fetched_sha + FROM approvals WHERE id = ?1", params![id], |row| { Ok(( @@ -115,11 +144,12 @@ impl Approvals { row.get(2)?, row.get(3)?, row.get(4)?, + row.get(5)?, )) }, ) .optional()?; - let Some((agent, kind, commit_ref, requested_at, status)) = current else { + let Some((agent, kind, commit_ref, requested_at, status, fetched_sha)) = current else { bail!("approval {id} not found"); }; if status != "pending" { @@ -139,7 +169,7 @@ impl Approvals { status: ApprovalStatus::Approved, resolved_at: Some(resolved_at), note: None, - fetched_sha: None, + fetched_sha, }) } @@ -179,7 +209,7 @@ impl Approvals { } fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result { - // Column order: id, agent, kind, commit_ref, requested_at, status, resolved_at, note. + // Column order: id, agent, kind, commit_ref, requested_at, status, resolved_at, note, fetched_sha. let kind: String = row.get(2)?; let kind = match kind.as_str() { "apply_commit" => ApprovalKind::ApplyCommit, @@ -215,7 +245,7 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result { status, resolved_at: row.get(6)?, note: row.get(7)?, - fetched_sha: None, + fetched_sha: row.get(8)?, }) }