ticket: own typed schema migrations

This commit is contained in:
2026-08-11 05:00:40 +09:00
parent 1d498d13c3
commit 9dccf99c50
6 changed files with 1171 additions and 158 deletions
+3 -2
View File
@@ -131,7 +131,7 @@ impl SqliteWorkspaceAuthority {
Ok(Self {
workspace_id: workspace_id.clone(),
store: SqliteWorkspaceStore::open(&database_path)?,
ticket_backend: SqliteTicketBackend::new(database_path, workspace_id),
ticket_backend: SqliteTicketBackend::open_verified(database_path, workspace_id)?,
})
}
@@ -725,7 +725,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
write_ticket(dir.path(), "00000000001J2", "Read bridge", "ready");
let db_path = dir.path().join("workspace.db");
SqliteTicketBackend::new(&db_path, "workspace-test")
SqliteTicketBackend::open(&db_path, "workspace-test")
.unwrap()
.import_from_local_backend(&ticket::LocalTicketBackend::new(
dir.path().join(".yoi/tickets"),
))
+5 -4
View File
@@ -2383,10 +2383,10 @@ struct BrowserCloseTicketRequest {
fn browser_ticket_backend(api: &WorkspaceApi) -> Result<SqliteTicketBackend> {
let config = ticket::config::TicketConfig::load_workspace(&api.config.workspace_root)
.map_err(|error| Error::Config(format!("load Ticket workspace settings: {error}")))?;
Ok(SqliteTicketBackend::new(
Ok(SqliteTicketBackend::open_verified(
api.config.database_path.clone(),
api.config.workspace_id.clone(),
)
)?
.with_record_language(config.ticket_record_language()))
}
@@ -2553,10 +2553,11 @@ async fn execute_worker_ticket_rest_operation(
validate_workspace_scope(api, workspace_id)?;
let config = ticket::config::TicketConfig::load_workspace(&api.config.workspace_root)
.map_err(|error| Error::Config(format!("load Ticket workspace settings: {error}")))?;
let mut backend = SqliteTicketBackend::new(
let mut backend = SqliteTicketBackend::open_verified(
api.config.database_path.clone(),
api.config.workspace_id.clone(),
)
.map_err(Error::from)?
.with_record_language(config.ticket_record_language());
let operation_kind = ticket_mutation_operation_kind(&operation);
let is_mutation = operation_kind != "read";
@@ -15225,7 +15226,7 @@ mod tests {
) {
use ticket::TicketBackend as _;
let backend = ticket::SqliteTicketBackend::new(database_path, workspace_id);
let backend = ticket::SqliteTicketBackend::open(database_path, workspace_id).unwrap();
let mut input = ticket::NewTicket::new(title);
input.workflow_state = Some(state);
backend.create(input).unwrap();
+40
View File
@@ -754,6 +754,7 @@ impl SqliteWorkspaceStore {
pub fn from_connection(conn: Connection) -> Result<Self> {
configure_sqlite(&conn)?;
apply_migrations(&conn)?;
ticket::migrate_sqlite_ticket_schema(&conn)?;
Ok(Self {
conn: Arc::new(Mutex::new(conn)),
})
@@ -4526,6 +4527,45 @@ mod tests {
use super::*;
use std::collections::BTreeSet;
#[test]
fn startup_composes_ticket_migrations_when_control_plane_is_current() {
let conn = Connection::open_in_memory().unwrap();
configure_sqlite(&conn).unwrap();
apply_migrations(&conn).unwrap();
assert!(!table_exists(&conn, "ticket_schema_migrations").unwrap());
let store = SqliteWorkspaceStore::from_connection(conn).unwrap();
store
.with_conn(|conn| {
ticket::verify_sqlite_ticket_schema(conn)?;
let latest = conn.query_row(
"SELECT MAX(version) FROM ticket_schema_migrations",
[],
|row| row.get::<_, i64>(0),
)?;
assert_eq!(latest, ticket::LATEST_SQLITE_TICKET_SCHEMA_VERSION);
Ok(())
})
.unwrap();
}
#[test]
fn startup_fails_closed_when_current_ticket_schema_has_drifted() {
let conn = Connection::open_in_memory().unwrap();
configure_sqlite(&conn).unwrap();
apply_migrations(&conn).unwrap();
ticket::migrate_sqlite_ticket_schema(&conn).unwrap();
conn.execute_batch("DROP TABLE typed_ticket_artifacts")
.unwrap();
let result = SqliteWorkspaceStore::from_connection(conn);
let error = match result {
Ok(_) => panic!("schema drift unexpectedly passed startup verification"),
Err(error) => error,
};
assert!(error.to_string().contains("typed_ticket_artifacts"));
}
#[test]
fn removes_unused_control_plane_ticket_tables() {
let conn = Connection::open_in_memory().unwrap();