agentmux_srv/migrations/
m0029_drop_legacy_agent_tables.rs1use rusqlite::Connection;
27
28use crate::backend::storage::store::Store;
29
30use super::{Migration, MigrationContext, MigrationError, MigrationScope, VerifyOutcome};
31
32pub struct M0029DropLegacyAgentTables;
33
34fn table_exists(conn: &Connection, table: &str) -> Result<bool, String> {
35 conn.query_row(
36 "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1)",
37 [table],
38 |row| row.get(0),
39 )
40 .map_err(|e| format!("probe {table}: {e}"))
41}
42
43pub(super) fn drop_legacy_tables(conn: &Connection) -> Result<(), String> {
44 conn.execute_batch(
45 "DROP TABLE IF EXISTS db_agent_definitions;
46 DROP TABLE IF EXISTS db_agent_instances;",
47 )
48 .map_err(|e| format!("drop legacy agent tables: {e}"))
49}
50
51impl Migration for M0029DropLegacyAgentTables {
52 fn id(&self) -> &'static str { "0029_drop_legacy_agent_tables" }
53 fn scope(&self) -> MigrationScope { MigrationScope::Channel }
54 fn description(&self) -> &'static str {
55 "Drop db_agent_definitions and db_agent_instances — retired by the agent-concept consolidation"
56 }
57
58 fn up(&self, ctx: &MigrationContext) -> Result<(), MigrationError> {
59 if !ctx.channel_store_path.exists() {
60 return Ok(());
61 }
62 drop(
69 Store::open(&ctx.channel_store_path)
70 .map_err(|e| MigrationError(format!("drop_legacy_agent_tables: open mstore: {e}")))?,
71 );
72 let conn = Connection::open(&ctx.channel_store_path)
73 .map_err(|e| MigrationError(format!("drop_legacy_agent_tables: open: {e}")))?;
74 drop_legacy_tables(&conn).map_err(|e| MigrationError(format!("drop_legacy_agent_tables: {e}")))?;
75 tracing::info!("m0029_drop_legacy_agent_tables: complete");
76 Ok(())
77 }
78
79 fn verify(&self, ctx: &MigrationContext) -> VerifyOutcome {
80 let conn = match super::runner::open_readonly(&ctx.channel_store_path) {
81 Ok(Some(c)) => c,
82 Ok(None) => return VerifyOutcome::Ok("no channel store on this data dir".into()),
83 Err(e) => return VerifyOutcome::Error(e),
84 };
85 for table in ["db_agent_definitions", "db_agent_instances"] {
86 match table_exists(&conn, table) {
87 Ok(false) => {}
88 Ok(true) => return VerifyOutcome::Mismatch(format!("{table} still exists")),
89 Err(e) => return VerifyOutcome::Error(e),
90 }
91 }
92 VerifyOutcome::Ok("both legacy agent tables are gone".into())
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn drops_both_tables_and_is_idempotent() {
102 let dir = tempfile::tempdir().unwrap();
103 let path = dir.path().join("objects.db");
104 drop(Store::open(&path).unwrap());
105 let conn = Connection::open(&path).unwrap();
106 conn.execute_batch(
110 "CREATE TABLE db_agent_definitions (id TEXT PRIMARY KEY);
111 CREATE TABLE db_agent_instances (id TEXT PRIMARY KEY);
112 INSERT INTO db_agent_definitions (id) VALUES ('leftover');
113 INSERT INTO db_agent_instances (id) VALUES ('leftover');",
114 )
115 .unwrap();
116
117 drop_legacy_tables(&conn).unwrap();
118 assert!(!table_exists(&conn, "db_agent_definitions").unwrap());
119 assert!(!table_exists(&conn, "db_agent_instances").unwrap());
120
121 drop_legacy_tables(&conn).unwrap();
124 }
125
126 #[test]
127 fn a_fresh_store_with_neither_table_is_a_no_op() {
128 let conn = Connection::open_in_memory().unwrap();
129 drop_legacy_tables(&conn).unwrap();
130 }
131
132 #[test]
133 fn verify_confirms_absence_and_flags_a_survivor() {
134 let dir = tempfile::tempdir().unwrap();
135 let path = dir.path().join("objects.db");
136 let ctx = MigrationContext {
137 home: dir.path().to_path_buf(),
138 data_dir: dir.path().join("data"),
139 shared_store_path: dir.path().join("shared").join("store.db"),
140 channel_store_path: path.clone(),
141 };
142 assert!(matches!(M0029DropLegacyAgentTables.verify(&ctx), VerifyOutcome::Ok(_)));
144
145 drop(Store::open(&path).unwrap());
146 assert!(matches!(M0029DropLegacyAgentTables.verify(&ctx), VerifyOutcome::Ok(_)));
148
149 let conn = Connection::open(&path).unwrap();
151 conn.execute_batch("CREATE TABLE db_agent_definitions (id TEXT PRIMARY KEY);").unwrap();
152 match M0029DropLegacyAgentTables.verify(&ctx) {
153 VerifyOutcome::Mismatch(detail) => assert!(detail.contains("db_agent_definitions")),
154 other => panic!("expected Mismatch, got {other:?}"),
155 }
156 }
157}