use crank_registry::{MigrationAuthority, MigrationPreflight, PostgresRegistry}; use sqlx::Row; mod artifact_metadata; mod rollback; static EVENT_TRIGGER_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); fn legacy_preservation_row_expr(table: &str) -> &'static str { match table { "invocation_logs" => { "to_jsonb(t) - array['trace_id','operation_version','execution_stage','execution_error_code','retryability','outcome_certainty','platform_api_key_id']" } "operation_versions" => { "to_jsonb(t) - array['name','display_name','category','protocol','security_level','snapshot_provenance','snapshot_observed_at','published_at','published_by']" } "approval_requests" => "to_jsonb(t) - array['request_id','trace_id']", "agents" => "to_jsonb(t) - array['catalog_revision']", _ => "to_jsonb(t)", } } #[tokio::test] async fn controlled_authority_is_versioned_and_safe_under_concurrent_apply() { let database_url = crank_test_support::postgres_schema_url("test_core_migration").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); let (first, second) = tokio::join!( MigrationAuthority::apply(&pool), MigrationAuthority::apply(&pool), ); first.expect("first controlled runner must apply the sequence"); second.expect("second controlled runner must observe the applied sequence"); let first = PostgresRegistry::connect(&database_url) .await .expect("service startup must verify the migrated schema"); let rows = sqlx::query("select version, name, checksum from __crank_migrations order by version") .fetch_all(first.pool()) .await .expect("migration ledger must be readable"); let expected = [ (1, "community-baseline-v1"), (2, "legacy-consolidation-v2"), (3, "request-trace-identity-v3"), (4, "operation-lifecycle-v4"), (5, "execution-outcome-v5"), (6, "platform-key-name-reuse-v6"), (7, "master-key-identity-v7"), (8, "admin-auth-lifecycle-v8"), (9, "agent-catalog-lifecycle-v9"), (10, "approval-side-effects-v10"), (11, "onboarding-product-events-v11"), (12, "artifact-metadata-v12"), ]; assert_eq!(rows.len(), expected.len()); for (row, (version, name)) in rows.iter().zip(expected) { assert_eq!(row.get::("version"), version); assert_eq!(row.get::("name"), name); let checksum = row.get::("checksum"); assert!(checksum == "crank-community-baseline-v1" || checksum.len() == 64); } let approval_columns = sqlx::query( "select column_name from information_schema.columns where table_schema = current_schema() and table_name = 'approval_requests' and column_name in ('execution_started_at', 'execution_attempts', 'request_fingerprint')", ) .fetch_all(first.pool()) .await .expect("approval schema must be readable"); assert_eq!(approval_columns.len(), 3); let onboarding_relations = sqlx::query( "select table_name from information_schema.tables where table_schema = current_schema() and table_name in ('product_events', 'product_event_daily_rollups', 'onboarding_selections') order by table_name", ) .fetch_all(first.pool()) .await .expect("V11 onboarding relations must be readable"); assert_eq!(onboarding_relations.len(), 3); let key_scope_column: bool = sqlx::query_scalar( "select exists ( select 1 from information_schema.columns where table_schema = current_schema() and table_name = 'invocation_logs' and column_name = 'platform_api_key_id' )", ) .fetch_one(first.pool()) .await .expect("V11 key provenance column must be readable"); assert!(key_scope_column); let artifact_relations = sqlx::query( "select table_name from information_schema.tables where table_schema = current_schema() and table_name in ('artifact_blobs', 'artifact_sources') order by table_name", ) .fetch_all(first.pool()) .await .expect("V12 artifact metadata relations must be readable"); assert_eq!(artifact_relations.len(), 2); assert_eq!( MigrationAuthority::preflight(first.pool()).await.unwrap(), MigrationPreflight::Current { version: 12 } ); } #[tokio::test] async fn service_connect_is_read_only_on_fresh_database() { let database_url = crank_test_support::postgres_schema_url("test_read_only_startup").await; let error = PostgresRegistry::connect(&database_url) .await .expect_err("fresh schema must require the controlled migration command"); assert!(error.to_string().contains("schema_missing")); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); let ledger = sqlx::query("select to_regclass('__crank_migrations')::text as name") .fetch_one(&pool) .await .unwrap() .try_get::, _>("name") .unwrap(); assert_eq!( ledger, None, "startup compatibility check must not create DDL" ); } #[tokio::test] async fn changed_checksum_fails_closed_without_repair() { let database_url = crank_test_support::postgres_schema_url("test_changed_checksum").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("update __crank_migrations set checksum = 'changed' where version = 1") .execute(&pool) .await .unwrap(); let error = MigrationAuthority::apply(&pool) .await .expect_err("published checksum mismatch must fail closed"); assert_eq!(error.code(), "checksum_mismatch"); let checksum = sqlx::query("select checksum from __crank_migrations where version = 1") .fetch_one(&pool) .await .unwrap() .get::("checksum"); assert_eq!( checksum, "changed", "authority must not rewrite corrupt history" ); } #[tokio::test] async fn legacy_core_baseline_is_consolidated_without_data_loss() { let database_url = crank_test_support::postgres_schema_url("test_legacy_core").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v4_schema(&pool).await; sqlx::query( "insert into workspaces (id, slug, display_name, status, settings_json, created_at, updated_at) values ('ws_preserved', 'preserved', 'Preserved', 'active', '{}'::jsonb, now(), now())", ) .execute(&pool) .await .unwrap(); sqlx::raw_sql( "insert into operations (id, workspace_id, name, display_name, protocol, status, created_at, updated_at) values ('op_preserved', 'ws_default', 'preserved', 'Preserved', 'rest', 'draft', now(), now()); insert into operation_versions (operation_id, version, status, target_json, input_schema_json, output_schema_json, input_mapping_json, output_mapping_json, execution_config_json, tool_description_json, created_at) values ('op_preserved', 1, 'draft', '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, now()); insert into agents (id, workspace_id, slug, display_name, description, status, created_at, updated_at) values ('agent_preserved', 'ws_default', 'preserved', 'Preserved', '', 'draft', now(), now()); insert into agent_versions (agent_id, version, status, instructions_json, tool_selection_policy_json, created_at) values ('agent_preserved', 1, 'draft', '{}'::jsonb, '{}'::jsonb, now()); insert into platform_api_keys (id, workspace_id, agent_id, name, prefix, secret_hash, scopes_json, status, created_at) values ('key_preserved', 'ws_default', 'agent_preserved', 'Preserved', 'cp_', 'hash', '[]'::jsonb, 'active', now()); insert into approval_requests (id, workspace_id, agent_id, operation_id, operation_version, status, risk_level, request_payload_json, created_at, expires_at) values ('approval_preserved', 'ws_default', 'agent_preserved', 'op_preserved', 1, 'pending', 'high', '{}'::jsonb, now(), now() + interval '1 hour'); insert into invocation_logs (id, workspace_id, agent_id, operation_id, source, level, status, tool_name, message, duration_ms, request_preview_json, response_preview_json, created_at) values ('log_preserved', 'ws_default', 'agent_preserved', 'op_preserved', 'mcp', 'info', 'success', 'preserved', 'safe', 1, '{}'::jsonb, '{}'::jsonb, now());", ) .execute(&pool) .await .unwrap(); remove_v3_schema(&pool).await; let tables = [ "operations", "operation_versions", "agents", "agent_versions", "platform_api_keys", "approval_requests", "invocation_logs", ]; let mut before = Vec::new(); for table in tables { let row = legacy_preservation_row_expr(table); let sql = format!("select jsonb_agg({row} order by {row}::text)::text from {table} t"); before.push( sqlx::query_scalar::<_, Option>(sqlx::AssertSqlSafe(sql)) .fetch_one(&pool) .await .unwrap(), ); } sqlx::query( "drop table __crank_migrations, __crank_migration_legacy_audit, __crank_mcp_migrations, mcp_transport_sessions, __crank_ext_migrations", ) .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::MigrationRequired { current: 1, target: 12, } ); MigrationAuthority::apply(&pool).await.unwrap(); let display_name = sqlx::query("select display_name from workspaces where id = 'ws_preserved'") .fetch_one(&pool) .await .unwrap() .get::("display_name"); assert_eq!(display_name, "Preserved"); let mut after = Vec::new(); for table in tables { let row = legacy_preservation_row_expr(table); let sql = format!("select jsonb_agg({row} order by {row}::text)::text from {table} t"); after.push( sqlx::query_scalar::<_, Option>(sqlx::AssertSqlSafe(sql)) .fetch_one(&pool) .await .unwrap(), ); } assert_eq!(before, after, "brownfield rows must remain byte-equivalent"); } #[tokio::test] async fn legacy_mcp_sessions_survive_consolidation() { let database_url = crank_test_support::postgres_schema_url("test_legacy_mcp").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query( "insert into mcp_transport_sessions ( id, protocol_version, initialized, supports_elicitation, workspace_slug, agent_slug, created_at, updated_at, expires_at ) values ('session_preserved', '2025-11-25', true, false, 'default', 'agent', now(), now(), null)", ) .execute(&pool) .await .unwrap(); remove_v4_schema(&pool).await; remove_v3_schema(&pool).await; sqlx::query("drop table __crank_migrations, __crank_migration_legacy_audit") .execute(&pool) .await .unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); let count = sqlx::query("select count(*)::bigint as count from mcp_transport_sessions where id = 'session_preserved'") .fetch_one(&pool) .await .unwrap() .get::("count"); assert_eq!(count, 1); } #[tokio::test] async fn repeated_apply_does_not_rewrite_audit_timestamps() { let database_url = crank_test_support::postgres_schema_url("test_repeat_apply").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); let before = sqlx::query("select applied_at from __crank_migrations order by version") .fetch_all(&pool) .await .unwrap() .into_iter() .map(|row| row.get::("applied_at")) .collect::>(); MigrationAuthority::apply(&pool).await.unwrap(); let after = sqlx::query("select applied_at from __crank_migrations order by version") .fetch_all(&pool) .await .unwrap() .into_iter() .map(|row| row.get::("applied_at")) .collect::>(); assert_eq!(before, after); } #[tokio::test] async fn future_sequence_fails_closed() { let database_url = crank_test_support::postgres_schema_url("test_future_sequence").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("update __crank_migrations set version = 13 where version = 12") .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool) .await .unwrap_err() .code(), "future_version" ); } #[tokio::test] async fn healthy_v2_is_reported_as_migration_required_and_upgrades_to_v3() { let database_url = crank_test_support::postgres_schema_url("test_v2_to_v3_identity").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v4_schema(&pool).await; sqlx::query("delete from __crank_migrations where version = 3") .execute(&pool) .await .unwrap(); remove_v3_schema(&pool).await; assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::MigrationRequired { current: 2, target: 12, } ); MigrationAuthority::apply(&pool).await.unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::Current { version: 12 } ); let trace_column: bool = sqlx::query_scalar( "select exists ( select 1 from information_schema.columns where table_schema = current_schema() and table_name = 'invocation_logs' and column_name = 'trace_id' )", ) .fetch_one(&pool) .await .unwrap(); assert!(trace_column); } #[tokio::test] async fn healthy_v3_upgrades_to_v4_with_honest_legacy_snapshot_provenance() { let database_url = crank_test_support::postgres_schema_url("test_v3_to_v4_lifecycle").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v4_schema(&pool).await; sqlx::raw_sql( "insert into operations (id, workspace_id, name, display_name, category, protocol, security_level, status, current_draft_version, latest_published_version, created_at, updated_at, published_at) values ('op_v3_cutover', 'ws_default', 'v3_cutover', 'V3 Cutover', 'general', 'rest', 'standard', 'published', 1, 1, now(), now(), now()); insert into operation_versions (operation_id, version, status, target_json, input_schema_json, output_schema_json, input_mapping_json, output_mapping_json, execution_config_json, tool_description_json, created_at) values ('op_v3_cutover', 1, 'published', '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '{}'::jsonb, now()); insert into published_operations(operation_id, version, published_at, published_by) values ('op_v3_cutover', 1, now(), 'legacy-owner');", ) .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::MigrationRequired { current: 3, target: 12, } ); MigrationAuthority::apply(&pool).await.unwrap(); let row = sqlx::query( "select name, display_name, snapshot_provenance, snapshot_observed_at, published_at, published_by from operation_versions where operation_id = 'op_v3_cutover' and version = 1", ) .fetch_one(&pool) .await .unwrap(); assert_eq!(row.get::("name"), "v3_cutover"); assert_eq!(row.get::("display_name"), "V3 Cutover"); assert_eq!( row.get::("snapshot_provenance"), "legacy_observed" ); assert!( row.try_get::("snapshot_observed_at") .is_ok() ); assert!( row.try_get::("published_at") .is_ok() ); assert_eq!( row.try_get::, _>("published_by").unwrap(), Some("legacy-owner".to_owned()) ); } #[tokio::test] async fn healthy_v4_upgrades_to_v5_without_fabricating_legacy_outcomes() { let database_url = crank_test_support::postgres_schema_url("test_v4_to_v5_outcome").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v5_schema(&pool).await; sqlx::query( "insert into operations (id, workspace_id, name, display_name, protocol, status, created_at, updated_at) values ('op_v4_history', 'ws_default', 'v4_history', 'V4 History', 'rest', 'draft', now(), now())", ) .execute(&pool) .await .unwrap(); sqlx::query( "insert into invocation_logs (id, workspace_id, operation_id, source, level, status, tool_name, message, duration_ms, request_preview_json, response_preview_json, created_at) values ('legacy_v4_log', 'ws_default', 'op_v4_history', 'admin', 'info', 'success', 'legacy', 'safe', 1, '{}'::jsonb, '{}'::jsonb, now())", ) .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::MigrationRequired { current: 4, target: 12, } ); MigrationAuthority::apply(&pool).await.unwrap(); let row = sqlx::query( "select operation_version, execution_stage, execution_error_code, retryability, outcome_certainty from invocation_logs where id = 'legacy_v4_log'", ) .fetch_one(&pool) .await .unwrap(); for column in [ "operation_version", "execution_stage", "execution_error_code", "retryability", "outcome_certainty", ] { assert!( row.try_get::, _>(column) .is_ok_and(|value| value.is_none()) || row .try_get::, _>(column) .is_ok_and(|value| value.is_none()) ); } } #[tokio::test] async fn failed_operation_lifecycle_migration_rolls_back_schema_and_ledger() { let _event_trigger_guard = EVENT_TRIGGER_TEST_LOCK.lock().await; let database_url = crank_test_support::postgres_schema_url("test_operation_lifecycle_rollback").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v4_schema(&pool).await; let schema: String = sqlx::query_scalar("select current_schema()") .fetch_one(&pool) .await .unwrap(); let failure_trigger = format!( "create function reject_story17_v4() returns event_trigger language plpgsql as $$ begin if current_schema() = '{schema}' and current_query() like '%operation_versions_immutable_guard%' then raise exception 'injected v4 ddl failure'; end if; end $$; create event trigger reject_story17_v4 on ddl_command_start execute function reject_story17_v4();" ); sqlx::raw_sql(sqlx::AssertSqlSafe(failure_trigger)) .execute(&pool) .await .unwrap(); let error = MigrationAuthority::apply(&pool).await.unwrap_err(); sqlx::raw_sql( "drop event trigger reject_story17_v4; drop function reject_story17_v4();", ) .execute(&pool) .await .unwrap(); assert_eq!(error.code(), "apply_failed"); assert_eq!(error.version(), Some(4)); let lifecycle_column: bool = sqlx::query_scalar( "select exists ( select 1 from information_schema.columns where table_schema = current_schema() and table_name = 'operation_versions' and column_name = 'snapshot_provenance' )", ) .fetch_one(&pool) .await .unwrap(); assert!(!lifecycle_column); let ledger_v4: i64 = sqlx::query_scalar("select count(*) from __crank_migrations where version = 4") .fetch_one(&pool) .await .unwrap(); assert_eq!(ledger_v4, 0); } #[tokio::test] async fn v2_with_partial_v3_objects_fails_before_apply() { let database_url = crank_test_support::postgres_schema_url("test_v2_partial_v3").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); remove_v4_schema(&pool).await; sqlx::query("delete from __crank_migrations where version = 3") .execute(&pool) .await .unwrap(); sqlx::raw_sql( "drop index invocation_logs_workspace_request_id_idx; drop index invocation_logs_workspace_trace_id_idx; alter table invocation_logs drop constraint invocation_logs_trace_id_format_check;", ) .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); assert_eq!(error.stage(), "preflight.schema"); } #[tokio::test] async fn v3_rejects_same_named_constraint_or_index_with_wrong_definition() { let database_url = crank_test_support::postgres_schema_url("test_v3_named_drift").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::raw_sql( "alter table invocation_logs drop constraint invocation_logs_trace_id_format_check; alter table invocation_logs add constraint invocation_logs_trace_id_format_check check (true) not valid;", ) .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool) .await .unwrap_err() .code(), "partial_sequence" ); sqlx::raw_sql( "alter table invocation_logs drop constraint invocation_logs_trace_id_format_check; alter table invocation_logs add constraint invocation_logs_trace_id_format_check check ( trace_id is null or ( trace_id ~ '^[0-9a-f]{32}$' and trace_id <> '00000000000000000000000000000000' ) ) not valid; drop index invocation_logs_workspace_trace_id_idx; create index invocation_logs_workspace_trace_id_idx on invocation_logs(trace_id);", ) .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool) .await .unwrap_err() .code(), "partial_sequence" ); } #[tokio::test] async fn v5_rejects_same_named_execution_code_constraint_with_wrong_definition() { let database_url = crank_test_support::postgres_schema_url("test_v5_execution_code_named_drift").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::raw_sql( "alter table invocation_logs drop constraint invocation_logs_execution_error_code_check; alter table invocation_logs add constraint invocation_logs_execution_error_code_check check (true) not valid;", ) .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); assert_eq!(error.stage(), "preflight.schema"); } #[tokio::test] async fn v11_rejects_same_named_product_event_contract_drift() { let database_url = crank_test_support::postgres_schema_url("test_v11_product_event_named_drift").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::raw_sql( "alter table product_events drop constraint product_events_name_check; alter table product_events add constraint product_events_name_check check (true) not valid;", ) .execute(&pool) .await .unwrap(); let constraint_error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(constraint_error.code(), "partial_sequence"); assert_eq!(constraint_error.stage(), "preflight.schema"); sqlx::raw_sql( "alter table product_events drop constraint product_events_name_check; alter table product_events add constraint product_events_name_check check (event_name in ( 'onboarding_eligible', 'onboarding_started', 'onboarding_resumed', 'onboarding_dismissed', 'onboarding_abandoned', 'onboarding_completed' )); drop index product_events_workspace_occurred_idx; create index product_events_workspace_occurred_idx on product_events(occurred_at, workspace_id, id);", ) .execute(&pool) .await .unwrap(); let index_error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(index_error.code(), "partial_sequence"); assert_eq!(index_error.stage(), "preflight.schema"); sqlx::raw_sql( "drop index product_events_workspace_occurred_idx; create index product_events_workspace_occurred_idx on product_events(workspace_id, occurred_at, id); create or replace function crank_reject_product_event_mutation() returns trigger language plpgsql as $$ begin raise exception 'ProductEvent is append-only' using errcode = '23514'; end; $$;", ) .execute(&pool) .await .unwrap(); let function_error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(function_error.code(), "partial_sequence"); assert_eq!(function_error.stage(), "preflight.schema"); } #[tokio::test] async fn v3_upgrade_ignores_oversized_legacy_request_ids_in_partial_index() { let database_url = crank_test_support::postgres_schema_url("test_v3_legacy_request_id").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::raw_sql( "insert into operations (id, workspace_id, name, display_name, protocol, status, created_at, updated_at) values ('op_legacy_request', 'ws_default', 'legacy-request', 'Legacy Request', 'rest', 'draft', now(), now());", ) .execute(&pool) .await .unwrap(); sqlx::query( "insert into invocation_logs (id, workspace_id, operation_id, source, level, status, tool_name, message, request_id, duration_ms, request_preview_json, response_preview_json, created_at) values ('legacy_request_log', 'ws_default', 'op_legacy_request', 'admin', 'info', 'success', 'legacy_request', 'safe', $1, 1, '{}'::jsonb, '{}'::jsonb, now())", ) .bind("x".repeat(10_000)) .execute(&pool) .await .unwrap(); remove_v4_schema(&pool).await; sqlx::query("delete from __crank_migrations where version = 3") .execute(&pool) .await .unwrap(); remove_v3_schema(&pool).await; MigrationAuthority::apply(&pool).await.unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::Current { version: 12 } ); } async fn remove_v3_schema(pool: &sqlx::PgPool) { sqlx::raw_sql( "drop index if exists invocation_logs_workspace_request_id_idx; alter table invocation_logs drop column if exists trace_id;", ) .execute(pool) .await .unwrap(); } async fn remove_v4_schema(pool: &sqlx::PgPool) { remove_v5_schema(pool).await; sqlx::raw_sql( "drop trigger if exists operation_versions_immutable_guard on operation_versions; drop function if exists crank_guard_operation_version_immutable(); drop trigger if exists published_operations_monotonic_guard on published_operations; drop function if exists crank_guard_published_operation_pointer(); drop trigger if exists operations_latest_pointer_monotonic_guard on operations; drop function if exists crank_guard_operation_latest_pointer(); alter table operation_versions drop constraint if exists operation_versions_snapshot_provenance_check, drop column if exists name, drop column if exists display_name, drop column if exists category, drop column if exists protocol, drop column if exists security_level, drop column if exists snapshot_provenance, drop column if exists snapshot_observed_at, drop column if exists published_at, drop column if exists published_by; delete from __crank_migrations where version = 4;", ) .execute(pool) .await .unwrap(); } async fn remove_v5_schema(pool: &sqlx::PgPool) { remove_v6_schema(pool).await; sqlx::raw_sql( "drop index if exists invocation_logs_workspace_operation_version_idx; alter table invocation_logs drop column if exists operation_version, drop column if exists execution_stage, drop column if exists execution_error_code, drop column if exists retryability, drop column if exists outcome_certainty; delete from __crank_migrations where version = 5;", ) .execute(pool) .await .unwrap(); } async fn remove_v6_schema(pool: &sqlx::PgPool) { remove_v7_schema(pool).await; sqlx::raw_sql( "drop index if exists platform_api_keys_workspace_name_active_idx; create unique index if not exists platform_api_keys_workspace_name_idx on platform_api_keys(workspace_id, name); delete from __crank_migrations where version = 6;", ) .execute(pool) .await .unwrap(); } async fn remove_v7_schema(pool: &sqlx::PgPool) { remove_v8_schema(pool).await; sqlx::raw_sql( "alter table secret_versions drop constraint if exists secret_versions_target_all_or_none_check, drop constraint if exists secret_versions_target_epoch_check, drop constraint if exists secret_versions_master_key_epoch_check, drop column if exists target_master_key_epoch, drop column if exists target_key_version, drop column if exists target_ciphertext, drop column if exists master_key_epoch; drop table if exists master_key_rotations; drop table if exists master_key_identities; delete from __crank_migrations where version = 7;", ) .execute(pool) .await .unwrap(); } async fn remove_v8_schema(pool: &sqlx::PgPool) { remove_v9_schema(pool).await; sqlx::raw_sql( "drop table if exists admin_security_audit_events; drop table if exists admin_login_backoff; drop table if exists admin_bootstrap_contracts; alter table user_sessions drop constraint if exists user_sessions_csrf_hash_check, drop column if exists csrf_hash, drop column if exists revoked_at; delete from __crank_migrations where version = 8;", ) .execute(pool) .await .unwrap(); } async fn remove_v9_schema(pool: &sqlx::PgPool) { remove_v10_schema(pool).await; sqlx::raw_sql( "drop trigger if exists published_agents_monotonic_guard on published_agents; drop function if exists crank_reject_published_agent_pointer_rewind(); drop trigger if exists agent_operation_bindings_immutable_guard on agent_operation_bindings; drop function if exists crank_reject_published_agent_binding_mutation(); drop trigger if exists agent_versions_immutable_guard on agent_versions; drop function if exists crank_reject_published_agent_version_mutation(); alter table published_agents drop constraint if exists published_agents_catalog_revision_check, drop column if exists catalog_revision; alter table agents drop constraint if exists agents_catalog_revision_check, drop column if exists catalog_revision; delete from __crank_migrations where version = 9;", ) .execute(pool) .await .unwrap(); } async fn remove_v10_schema(pool: &sqlx::PgPool) { remove_v11_schema(pool).await; sqlx::raw_sql( "drop index if exists approval_requests_pending_scope_fingerprint_idx; drop index if exists approval_requests_workspace_request_trace_idx; alter table approval_requests drop constraint if exists approval_requests_request_id_check; alter table approval_requests drop constraint if exists approval_requests_trace_id_check; alter table approval_requests drop column if exists request_id; alter table approval_requests drop column if exists trace_id; create unique index if not exists approval_requests_pending_fingerprint_idx on approval_requests(agent_id, operation_id, operation_version, request_fingerprint) where status = 'pending' and request_fingerprint is not null; delete from __crank_migrations where version = 10;", ) .execute(pool) .await .unwrap(); } async fn remove_v11_schema(pool: &sqlx::PgPool) { remove_v12_schema(pool).await; sqlx::raw_sql( "drop table if exists onboarding_selections; drop trigger if exists product_events_append_only_guard on product_events; drop function if exists crank_reject_product_event_mutation(); drop table if exists product_event_daily_rollups; drop table if exists product_events; drop index if exists invocation_logs_workspace_agent_key_success_idx; alter table invocation_logs drop constraint if exists invocation_logs_platform_key_scope_fk; alter table invocation_logs drop column if exists platform_api_key_id; alter table platform_api_keys drop constraint if exists platform_api_keys_workspace_agent_id_unique; delete from __crank_migrations where version = 11;", ) .execute(pool) .await .unwrap(); } async fn remove_v12_schema(pool: &sqlx::PgPool) { sqlx::raw_sql( "drop table if exists artifact_sources; drop table if exists artifact_blobs; delete from __crank_migrations where version = 12;", ) .execute(pool) .await .unwrap(); } #[tokio::test] async fn partial_sequence_fails_closed() { let database_url = crank_test_support::postgres_schema_url("test_partial_sequence").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("delete from __crank_migrations where version = 1") .execute(&pool) .await .unwrap(); assert_eq!( MigrationAuthority::preflight(&pool) .await .unwrap_err() .code(), "partial_sequence" ); } #[tokio::test] async fn missing_relation_with_current_ledger_fails_closed() { let database_url = crank_test_support::postgres_schema_url("test_missing_relation").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("drop table usage_rollups") .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); assert_eq!(error.stage(), "preflight.schema"); } #[tokio::test] async fn unregistered_legacy_extension_provenance_is_rejected() { let database_url = crank_test_support::postgres_schema_url("test_legacy_extension").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("drop table __crank_migrations, __crank_migration_legacy_audit") .execute(&pool) .await .unwrap(); let checksum = "a".repeat(64); sqlx::query( "insert into __crank_ext_migrations (extension_name, version, checksum) values ('known-extension', 1, $1)", ) .bind(&checksum) .execute(&pool) .await .unwrap(); let error = MigrationAuthority::apply(&pool).await.unwrap_err(); assert_eq!(error.code(), "legacy_conflict"); } #[tokio::test] async fn any_owned_relation_without_core_ledger_is_partial() { let database_url = crank_test_support::postgres_schema_url("test_owned_partial").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); sqlx::query("create table users (id text primary key)") .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); } #[tokio::test] async fn current_ledger_with_structural_drift_fails_closed() { let database_url = crank_test_support::postgres_schema_url("test_structural_drift").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query("alter table mcp_transport_sessions drop column supports_elicitation") .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); assert_eq!(error.stage(), "preflight.schema"); } #[tokio::test] async fn tampered_legacy_audit_fails_closed() { let database_url = crank_test_support::postgres_schema_url("test_tampered_audit").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); sqlx::query( "update __crank_migration_legacy_audit set source_checksum = 'tampered' where source = 'core'", ) .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "legacy_conflict"); }