refactor(hive-c0re): shared additive-migration helper in db

db::apply_migrations runs ALTER lists and ignores duplicate-column
errors (turn_stats' pattern); approvals, operator_questions, broker
reminders, and scheduled_prompts drop their hand-rolled
pragma_table_info guards. broker's acked_at migration stays bespoke —
its backfill must only run when the column was just created
This commit is contained in:
müde 2026-07-06 21:53:48 +02:00
commit d190420946
7 changed files with 71 additions and 132 deletions

View file

@ -24,66 +24,26 @@ CREATE INDEX IF NOT EXISTS idx_approvals_pending
ON approvals (id) WHERE status = 'pending';
";
/// Add the `description` column to pre-description databases. Manager-supplied
/// note shown on the dashboard approval card at submission time (distinct from
/// `note` which is set on denial/failure).
fn ensure_description_column(conn: &Connection) -> Result<()> {
let has: bool = conn
.prepare("SELECT 1 FROM pragma_table_info('approvals') WHERE name = 'description'")?
.exists([])?;
if !has {
conn.execute_batch("ALTER TABLE approvals ADD COLUMN description TEXT;")
.context("add approvals.description column")?;
}
Ok(())
}
/// Add the `kind` column to pre-Phase-8 databases. ALTER TABLE ADD COLUMN is
/// idempotent here only via a column-existence check (sqlite doesn't support
/// IF NOT EXISTS on ADD COLUMN). Defaults legacy rows to `apply_commit`,
/// which matches their actual semantics.
fn ensure_kind_column(conn: &Connection) -> Result<()> {
let has_kind: bool = conn
.prepare("SELECT 1 FROM pragma_table_info('approvals') WHERE name = 'kind'")?
.exists([])?;
if !has_kind {
conn.execute_batch(
"ALTER TABLE approvals ADD COLUMN kind TEXT NOT NULL DEFAULT 'apply_commit';",
)
.context("add approvals.kind column")?;
}
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(())
}
/// Same shape as `ensure_fetched_sha_column` but for `submitter` — the
/// agent that submitted the approval (the authenticated socket caller).
/// Approval-scoped helper events route to this agent. Legacy rows have
/// NULL; callers fall back to the root agent for those.
fn ensure_submitter_column(conn: &Connection) -> Result<()> {
let has: bool = conn
.prepare("SELECT 1 FROM pragma_table_info('approvals') WHERE name = 'submitter'")?
.exists([])?;
if !has {
conn.execute_batch("ALTER TABLE approvals ADD COLUMN submitter TEXT;")
.context("add approvals.submitter column")?;
}
Ok(())
}
/// Additive column migrations for pre-existing databases, applied via
/// `db::apply_migrations` (try-and-ignore-duplicate-column).
const MIGRATIONS: &[&str] = &[
// `kind` (pre-Phase-8 dbs): legacy rows default to `apply_commit`,
// which matches their actual semantics.
"ALTER TABLE approvals ADD COLUMN kind TEXT NOT NULL DEFAULT 'apply_commit'",
// `description`: manager-supplied note shown on the dashboard
// approval card at submission time (distinct from `note`, set on
// denial/failure).
"ALTER TABLE approvals ADD COLUMN description TEXT",
// `fetched_sha`: the canonical sha hive-c0re vouched for at
// `request_apply_commit` time. Distinct from `commit_ref`
// (manager-supplied, may not even resolve by approve time).
"ALTER TABLE approvals ADD COLUMN fetched_sha TEXT",
// `submitter`: the agent that submitted the approval (the
// authenticated socket caller); approval-scoped helper events
// route to it. Legacy rows are NULL → callers fall back to the
// root agent.
"ALTER TABLE approvals ADD COLUMN submitter TEXT",
];
pub struct Approvals {
conn: Mutex<Connection>,
@ -94,10 +54,7 @@ impl Approvals {
let conn = crate::db::open(path, "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")?;
ensure_description_column(&conn).context("migrate approvals.description")?;
ensure_submitter_column(&conn).context("migrate approvals.submitter")?;
crate::db::apply_migrations(&conn, "approvals", MIGRATIONS)?;
Ok(Self {
conn: Mutex::new(conn),
})