Skip to main content

systemprompt_database/lifecycle/migrations/
repair.rs

1//! Migration checksum-drift repair.
2//!
3//! When an already-applied migration file is edited in place, its stored
4//! checksum stops matching the file and the runner refuses to proceed.
5//! [`MigrationService::repair_drift`] reconciles the `extension_migrations`
6//! tracking table by dropping the drifted rows and re-applying those
7//! migrations — every migration is idempotent (guarded seeds or
8//! `CREATE ... IF NOT EXISTS`), so re-running them re-records the current
9//! checksum without touching real data.
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14use super::{ChecksumDrift, MigrationService};
15use systemprompt_extension::{Extension, LoaderError};
16
17#[derive(Debug, Default, Clone)]
18pub struct RepairResult {
19    pub repaired: Vec<ChecksumDrift>,
20    pub migrations_run: usize,
21}
22
23impl MigrationService<'_> {
24    pub async fn repair_drift(
25        &self,
26        extension: &dyn Extension,
27    ) -> Result<RepairResult, LoaderError> {
28        let ext_id = extension.metadata().id;
29        let status = self.status(extension).await?;
30
31        if status.drift.is_empty() {
32            return Ok(RepairResult::default());
33        }
34
35        for drift in &status.drift {
36            self.db
37                .execute(
38                    &"DELETE FROM extension_migrations WHERE extension_id = $1 AND version = $2",
39                    &[&drift.extension_id, &drift.version],
40                )
41                .await
42                .map_err(|e| LoaderError::MigrationFailed {
43                    extension: ext_id.to_owned(),
44                    message: format!(
45                        "Failed to drop drifted migration record {} ('{}'): {e}",
46                        drift.version, drift.name
47                    ),
48                })?;
49        }
50
51        let result = self.run_pending_migrations(extension).await?;
52
53        Ok(RepairResult {
54            repaired: status.drift,
55            migrations_run: result.migrations_run,
56        })
57    }
58}