From 71efbb313ea75d698dd23cc7f7955ea8b314abec Mon Sep 17 00:00:00 2001 From: Jonathan Haas Date: Tue, 4 Aug 2026 13:49:03 -0700 Subject: [PATCH] fix(db): preserve applied migration checksums --- crates/corpus-core/src/db.rs | 63 +++++++++++++++++++++- migrations/0002_agents.sql | 2 - migrations/0003_similarity.sql | 2 - migrations/0004_bootstrap.sql | 2 - migrations/0005_analyst.sql | 2 - migrations/0006_semantic.sql | 2 - migrations/0007_detonation.sql | 2 - migrations/0008_lsh_and_hunt_queue.sql | 2 - migrations/0009_continuous_investigate.sql | 2 - 9 files changed, 62 insertions(+), 17 deletions(-) diff --git a/crates/corpus-core/src/db.rs b/crates/corpus-core/src/db.rs index 734f5b1..dd99ffc 100644 --- a/crates/corpus-core/src/db.rs +++ b/crates/corpus-core/src/db.rs @@ -8,6 +8,9 @@ //! Each migration filename must have a unique numeric prefix because SQLx //! persists that prefix as the primary-keyed migration version. //! +//! SQLx also records a SHA-384 checksum for each applied migration, so +//! applied files are immutable; add a new migration instead of editing one. +//! //! Call [`migrate`] once at process start before serving traffic. use crate::error::Result; @@ -85,7 +88,12 @@ where #[cfg(test)] mod tests { use super::*; - use std::sync::atomic::{AtomicU32, Ordering}; + use sha2::{Digest, Sha384}; + use std::{ + fs, + path::Path, + sync::atomic::{AtomicU32, Ordering}, + }; #[tokio::test] async fn startup_retry_survives_a_slow_database_proxy() { @@ -110,6 +118,59 @@ mod tests { assert_eq!(observed_attempts.load(Ordering::Relaxed), 7); } + #[test] + fn applied_migration_checksums_are_immutable() { + const APPLIED_MIGRATIONS: &[(&str, &str)] = &[ + ( + "0001_init.sql", + "e2dd15450914e699dfc1523c96a94e4d63dffa1a4b9bc4dfb5f6d253cfdd6a0695c7f9466be74ca36f613570a765d2e9", + ), + ( + "0002_agents.sql", + "b63e38b1816f2279767f059e8b5bbcbe0b1e4aa34a08b07e00884626ea85492baf6e3ea3599e643930fdae2d30d68d7a", + ), + ( + "0003_similarity.sql", + "9afa81e8f30af8158e959a4d5d6bfdc9d67a0337826cd4f0651ed3fca250baf81573a00f9ce6d1d57f89a3a844dccebe", + ), + ( + "0004_bootstrap.sql", + "ccdb12d8a0e28d2e66f5a311760bb10d96c7f66ada4a71c62c9d5642802f5a10d57028e0da672e16ad49d7ae226104de", + ), + ( + "0005_analyst.sql", + "68c9f52ceed1cf6d93b287054b3ecd396da6a522499fb2def326462ade364bf300ed21a9af88aebebe038ccd695a4b1c", + ), + ( + "0006_semantic.sql", + "7020f4223c4d165881f738e4656855cb534360a86841540d8ae6f69dcba1f63160c4dfbac7c6ed489e4254695cc5295c", + ), + ( + "0007_detonation.sql", + "8b5f867b081243df98cce5b6f857d35d246cfdd4cbbe55614048cfabc507b8e922ef125b4a306ac34582ccb3110cf6e9", + ), + ( + "0008_lsh_and_hunt_queue.sql", + "261485c56c6eb617d70c57fab1c99341ecc16bb70b0fab364209d8fbaf890c16e0df1e6b10c425ac26993f2786fb4e2e", + ), + ( + "0009_continuous_investigate.sql", + "5b3861bc0eb81eadb3c9d7932e5e216365f151873f3b4fe6c96e18a62e741ccc1452b995d2cd962bfaf9130fa107f7f0", + ), + ( + "0010_receipts_and_cleanup.sql", + "bfb3ac668520b6893a5d0b49d73112c1fe8c705d59585ef9f518d250c6998686c108476438c9f8264552540e0e444573", + ), + ]; + + let migrations_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../migrations"); + for (name, expected) in APPLIED_MIGRATIONS { + let bytes = fs::read(migrations_dir.join(name)).expect("read applied migration"); + let actual = hex::encode(Sha384::digest(&bytes)); + assert_eq!(actual, *expected, "applied migration {name} was modified"); + } + } + #[test] fn migration_versions_are_unique() { use std::{collections::BTreeMap, fs, path::Path}; diff --git a/migrations/0002_agents.sql b/migrations/0002_agents.sql index 4a2c0e0..f826c21 100644 --- a/migrations/0002_agents.sql +++ b/migrations/0002_agents.sql @@ -1,7 +1,5 @@ -- Milestone 1: agent enrollment, identity, and health (spec 10.1, 10.11). -- Tenant-scoped like every other data table (0001 tenant registry). --- --- Agent enrollment tokens, agent rows, heartbeats, and coverage gaps. -- One-time enrollment tokens minted by operators via corpusctl. CREATE TABLE enrollment_token ( diff --git a/migrations/0003_similarity.sql b/migrations/0003_similarity.sql index 2336b03..c7b5832 100644 --- a/migrations/0003_similarity.sql +++ b/migrations/0003_similarity.sql @@ -1,6 +1,4 @@ -- Milestone 3a: similarity features, typed edges, variant groups (spec 16). --- --- Similarity features, typed edges, and variant groups (byte-level M3a). -- Versioned features per artifact. family: exact | normalized | byte | -- structural | semantic (plugin slot, unpopulated in M3a) | provenance. diff --git a/migrations/0004_bootstrap.sql b/migrations/0004_bootstrap.sql index ef772e3..4021dbb 100644 --- a/migrations/0004_bootstrap.sql +++ b/migrations/0004_bootstrap.sql @@ -1,7 +1,5 @@ -- Milestone 4 (vault bootstrap): snapshot backfill, OCI ingestion, -- intel-corpus connectors. --- --- Bootstrap helpers and seed data beyond the default tenant. -- Artifact scope separates endpoint-collected bytes from intel imports. -- Default queries (retro hunts, blast radius occurrence views) cover diff --git a/migrations/0005_analyst.sql b/migrations/0005_analyst.sql index 3966aec..d35e2eb 100644 --- a/migrations/0005_analyst.sql +++ b/migrations/0005_analyst.sql @@ -1,8 +1,6 @@ -- Milestone 5: analyst surface — opinions, triggers, audit. -- (Prevalence and dropper hunts are pure SQL over the occurrence ledger; -- proof-of-absence is computed, not stored.) --- --- Analyst surface tables: opinions, prevalence support, rarity indexes. -- Human verdicts, separate from analyzer scores (spec 5.5). Append-only; -- the current opinion for an artifact is the latest row. diff --git a/migrations/0006_semantic.sql b/migrations/0006_semantic.sql index b7dfa35..e57a379 100644 --- a/migrations/0006_semantic.sql +++ b/migrations/0006_semantic.sql @@ -1,6 +1,4 @@ -- Milestone M8: per-function semantic signatures (spec 16.2/16.5). --- --- Per-function signatures for semantic similarity (x86-64). CREATE TABLE similarity_function ( tenant_id uuid NOT NULL REFERENCES tenant (id), diff --git a/migrations/0007_detonation.sql b/migrations/0007_detonation.sql index 2ea43a0..890447c 100644 --- a/migrations/0007_detonation.sql +++ b/migrations/0007_detonation.sql @@ -1,6 +1,4 @@ -- Milestone M10: detonation findings (spec 13.4, 17.4). --- --- External sandbox job records and behavioral result storage. -- Analyzer runs (spec 12.2 sketch; the table was deferred from the M0 -- subset until the first producer — detonation — needed it). diff --git a/migrations/0008_lsh_and_hunt_queue.sql b/migrations/0008_lsh_and_hunt_queue.sql index 688ce6e..0e497eb 100644 --- a/migrations/0008_lsh_and_hunt_queue.sql +++ b/migrations/0008_lsh_and_hunt_queue.sql @@ -2,8 +2,6 @@ -- Each artifact contributes fixed band keys derived from its ssdeep digest; -- candidate queries join on (tenant_id, band_idx, band_key) instead of a -- full per-class table scan. --- --- LSH band index for byte-similar candidates; hunt worker queue. CREATE TABLE IF NOT EXISTS similarity_lsh_band ( tenant_id uuid NOT NULL REFERENCES tenant (id), diff --git a/migrations/0009_continuous_investigate.sql b/migrations/0009_continuous_investigate.sql index 345a6d9..5e09577 100644 --- a/migrations/0009_continuous_investigate.sql +++ b/migrations/0009_continuous_investigate.sql @@ -1,8 +1,6 @@ -- Continuous re-analysis tracking, autonomous detection events, and -- investigation scaffolding for the continuous re-analysis product loop: -- retain → detect → re-hunt history → blast radius → recommended actions. --- --- Continuous re-analysis work items and investigation snapshots. CREATE TABLE IF NOT EXISTS detection_event ( id uuid PRIMARY KEY,