Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/api/router/snapshot_content_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ mod durable_sessions;
#[path = "snapshot_generation_upgrade_tests.rs"]
mod generation_upgrade;

#[path = "snapshot_generation_history_fixture.rs"]
mod generation_history_fixture;

#[derive(Default)]
struct ReadCounts {
whole: AtomicUsize,
Expand Down
31 changes: 31 additions & 0 deletions src/api/router/snapshot_generation_history_fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use sea_orm::{ConnectionTrait, DatabaseConnection, DbBackend, Statement};

pub(super) async fn restore_empty_g1_schema(db: &DatabaseConnection) {
let count = db
.query_one_raw(Statement::from_string(
DbBackend::Postgres,
"SELECT count(*)::bigint AS n FROM mst2_metadata_current",
))
.await
.unwrap()
.unwrap()
.try_get::<i64>("", "n")
.unwrap();
assert_eq!(
count, 0,
"upgrade fixture has no generation-bound incarnation to erase"
);
db.execute_unprepared(
"DROP TABLE mst2_metadata_current;
DROP FUNCTION mst2_metadata_current_guard();
DROP INDEX idx_mst2_metadata_lifetime_node_generation;
ALTER TABLE mst2_metadata_lifetime DROP CONSTRAINT mst2_metadata_lifetime_pkey,
ADD PRIMARY KEY(page_id),ADD UNIQUE(node_id);
ALTER TABLE mst2_metadata_prepare DROP CONSTRAINT mst2_metadata_prepare_state_check,
DROP CONSTRAINT mst2_metadata_prepare_terminal_check,
ADD CONSTRAINT mst2_metadata_prepare_state_check CHECK (state IN ('PREPARING','COMMITTED')),
ADD CONSTRAINT mst2_metadata_prepare_check CHECK ((state='COMMITTED')=(committed_at IS NOT NULL)),
DROP COLUMN graph_domain,DROP COLUMN aborted_at,DROP COLUMN coverage_retired_at;
DELETE FROM seaql_migrations WHERE version='m20261007_000300_add_mst2_metadata_lifetime_history'"
).await.unwrap();
}
1 change: 1 addition & 0 deletions src/api/router/snapshot_generation_upgrade_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ async fn mst2_generation_additive_upgrade_preserves_legacy_v3_sid_lease_and_new_
);
let pages = scalar(db, "SELECT count(*) FROM mst2_metadata_payload").await;
let original = success_json(fixture.send("GET", "descriptor", Body::empty()).await).await;
super::generation_history_fixture::restore_empty_g1_schema(db).await;
// The unchanged v3 installer created these durable rows. Remove only the
// empty additive schema in this isolated fixture to reproduce the previous
// deployed schema; the SID, lease, CAS bytes and all original guards survive.
Expand Down
1 change: 1 addition & 0 deletions src/callisto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub mod mega_view_root_chain_scan;
pub mod mega_webhook;
pub mod mega_webhook_delivery;
pub mod mega_webhook_event_type;
pub mod mst2_metadata_current;
pub mod mst2_metadata_lifetime;
pub mod mst2_metadata_payload;
pub mod mst2_metadata_prepare;
Expand Down
13 changes: 13 additions & 0 deletions src/callisto/mst2_metadata_current.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "mst2_metadata_current")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub page_id: Vec<u8>,
pub generation: i64,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
1 change: 1 addition & 0 deletions src/callisto/mst2_metadata_lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub page_id: Vec<u8>,
pub node_id: String,
#[sea_orm(primary_key, auto_increment = false)]
pub generation: i64,
pub state: String,
pub metadata_codec: i16,
Expand Down
3 changes: 3 additions & 0 deletions src/callisto/mst2_metadata_prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Model {
pub bindings_digest: Option<Vec<u8>>,
pub primary_scope: Option<Vec<u8>>,
pub storage_seal: Option<Vec<u8>>,
pub graph_domain: Option<String>,
pub source_domain: String,
pub tagged_root_tree_oid: String,
pub scope: String,
Expand All @@ -29,6 +30,8 @@ pub struct Model {
pub state: String,
pub created_at: DateTimeWithTimeZone,
pub committed_at: Option<DateTimeWithTimeZone>,
pub aborted_at: Option<DateTimeWithTimeZone>,
pub coverage_retired_at: Option<DateTimeWithTimeZone>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Preserve historical incarnations and explicit preparation terminal states.

use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.get_connection().execute_unprepared(
"ALTER TABLE mst2_metadata_lifetime
DROP CONSTRAINT mst2_metadata_lifetime_pkey,
DROP CONSTRAINT mst2_metadata_lifetime_node_id_key,
ADD PRIMARY KEY(page_id,generation);
CREATE TABLE mst2_metadata_current (
page_id bytea PRIMARY KEY CHECK (octet_length(page_id)=32),
generation bigint NOT NULL CHECK (generation>0),
FOREIGN KEY(page_id,generation) REFERENCES mst2_metadata_lifetime(page_id,generation)
);
INSERT INTO mst2_metadata_current(page_id,generation)
SELECT page_id,generation FROM mst2_metadata_lifetime;
CREATE FUNCTION mst2_metadata_current_guard() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN RAISE EXCEPTION 'metadata current watermark requires generation-fenced collection'; END $$;
CREATE TRIGGER mst2_metadata_current_guard BEFORE UPDATE OR DELETE
ON mst2_metadata_current FOR EACH ROW EXECUTE FUNCTION mst2_metadata_current_guard();
ALTER TABLE mst2_metadata_prepare
ADD COLUMN graph_domain text CHECK (graph_domain IN ('generic-v1','qualified-v1')),
ADD COLUMN aborted_at timestamptz,
ADD COLUMN coverage_retired_at timestamptz,
DROP CONSTRAINT mst2_metadata_prepare_state_check,
DROP CONSTRAINT mst2_metadata_prepare_check,
ADD CONSTRAINT mst2_metadata_prepare_state_check CHECK (state IN ('PREPARING','COMMITTED','ABORTED')),
ADD CONSTRAINT mst2_metadata_prepare_terminal_check CHECK (
(state='COMMITTED')=(committed_at IS NOT NULL)
AND (state='ABORTED')=(aborted_at IS NOT NULL)
AND (coverage_retired_at IS NULL OR state='COMMITTED')
AND (state<>'ABORTED' OR storage_seal IS NOT NULL)
AND (graph_domain IS NULL OR storage_seal IS NOT NULL)
);
CREATE OR REPLACE FUNCTION mst2_metadata_generation_seal_guard() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.canonical_bindings IS DISTINCT FROM OLD.canonical_bindings
OR NEW.bindings_digest IS DISTINCT FROM OLD.bindings_digest
OR NEW.primary_scope IS DISTINCT FROM OLD.primary_scope
OR NEW.storage_seal IS DISTINCT FROM OLD.storage_seal
OR NEW.graph_domain IS DISTINCT FROM OLD.graph_domain THEN
RAISE EXCEPTION 'metadata generation seal cannot be rebound';
END IF;
IF OLD.state IN ('COMMITTED','ABORTED') AND NEW.state IS DISTINCT FROM OLD.state THEN
RAISE EXCEPTION 'metadata preparation terminal state cannot be revived';
END IF;
IF OLD.committed_at IS NOT NULL AND NEW.committed_at IS DISTINCT FROM OLD.committed_at
OR OLD.aborted_at IS NOT NULL AND NEW.aborted_at IS DISTINCT FROM OLD.aborted_at
OR OLD.coverage_retired_at IS NOT NULL AND NEW.coverage_retired_at IS DISTINCT FROM OLD.coverage_retired_at THEN
RAISE EXCEPTION 'metadata preparation terminal receipt cannot be rewritten';
END IF;
RETURN NEW;
END $$;
CREATE INDEX idx_mst2_metadata_lifetime_node_generation ON mst2_metadata_lifetime(node_id,generation)"
).await.map(|_| ())
}

async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}
10 changes: 8 additions & 2 deletions src/jupiter/migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ mod m20261005_000300_add_mst2_metadata_install;
mod m20261006_000100_add_view_tables;
mod m20261007_000100_add_mst2_snapshot_sessions;
mod m20261007_000200_add_mst2_metadata_generations;
mod m20261007_000300_add_mst2_metadata_lifetime_history;
mod runner;
pub use m20260905_000100_add_push_queue::ensure_queue_control_seed;
pub use runner::apply_migrations;
Expand Down Expand Up @@ -282,6 +283,7 @@ impl MigratorTrait for Migrator {
Box::new(m20261006_000100_add_view_tables::Migration),
Box::new(m20261007_000100_add_mst2_snapshot_sessions::Migration),
Box::new(m20261007_000200_add_mst2_metadata_generations::Migration),
Box::new(m20261007_000300_add_mst2_metadata_lifetime_history::Migration),
]
}
}
Expand Down Expand Up @@ -1192,7 +1194,7 @@ mod tests {
async fn import_repo_alias_rows_canonicalized() {
let names = migration_names();
assert_eq!(
&names[names.len() - 10..names.len() - 1],
&names[names.len() - 11..names.len() - 2],
&[
"m20260923_000200_canonicalize_import_repo_paths".to_string(),
"m20260925_000100_media_paging".to_string(),
Expand All @@ -1207,9 +1209,13 @@ mod tests {
"native retention, metadata installation and view tables follow media paging"
);
assert_eq!(
names.last().unwrap(),
&names[names.len() - 2],
"m20261007_000200_add_mst2_metadata_generations"
);
assert_eq!(
names.last().unwrap(),
"m20261007_000300_add_mst2_metadata_lifetime_history"
);

let db = alias_db().await;
insert_repo(&db, 1, "/third-party//a").await;
Expand Down
Loading
Loading