diff --git a/crates/tracedecay-configuration/src/config/mod.rs b/crates/tracedecay-configuration/src/config/mod.rs index 77b2c79384..33771069b2 100644 --- a/crates/tracedecay-configuration/src/config/mod.rs +++ b/crates/tracedecay-configuration/src/config/mod.rs @@ -25,8 +25,7 @@ use tracedecay_domain::configuration::{ LcmSummarizerExecutablesV1, SYNC_AUTO_INIT_SETTING_KEY, SYNC_AUTO_TRACK_PR_BRANCHES_SETTING_KEY, SYNC_AUTO_TRACK_PR_POLL_SECS_SETTING_KEY, SYNC_AUTO_WATCH_SETTING_KEY, SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY, - SYNC_BRANCH_GC_DAYS_SETTING_KEY, SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, - SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY, SYNC_READ_COOLDOWN_SECS_SETTING_KEY, + SYNC_BRANCH_GC_DAYS_SETTING_KEY, SYNC_READ_COOLDOWN_SECS_SETTING_KEY, SYNC_READ_REFRESH_SETTING_KEY, SYNC_SESSION_START_STALE_THRESHOLD_SECS_SETTING_KEY, SYNC_SESSION_START_SYNC_SETTING_KEY, SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY, SYNC_WATCH_LINKED_WORKTREES_SETTING_KEY, SYNC_WATCH_MAX_DELAY_MS_SETTING_KEY, @@ -284,11 +283,6 @@ fn runtime_config_from_snapshot( snapshot, SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY, )?, - full_sync_escalation_files: required_usize( - snapshot, - SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, - )?, - max_concurrent_syncs: required_usize(snapshot, SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY)?, branch_gc_days: required_unsigned(snapshot, SYNC_BRANCH_GC_DAYS_SETTING_KEY)?, auto_init: required_bool(snapshot, SYNC_AUTO_INIT_SETTING_KEY)?, auto_track_pr_branches: required_bool( @@ -458,7 +452,8 @@ mod tests { use tracedecay_domain::configuration::{ ConfigurationLayerIdV1, ConfigurationRevisionId, ConfigurationSnapshotV1, ConfigurationValueV1, INDEX_EXCLUDE_SETTING_KEY, INDEX_MAX_FILE_SIZE_SETTING_KEY, - SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY, SettingKey, + SYNC_READ_COOLDOWN_SECS_SETTING_KEY, SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY, + SYNC_WATCH_LINKED_WORKTREES_SETTING_KEY, SYNC_WATCH_MAX_PROJECTS_SETTING_KEY, SettingKey, }; use tracedecay_domain::errors::TraceDecayError; @@ -546,6 +541,47 @@ mod tests { ); } + #[test] + fn pin_decodes_supported_sync_settings_without_retired_keys() { + let complete = resolved(BTreeMap::from([ + ( + SettingKey::new(SYNC_WATCH_LINKED_WORKTREES_SETTING_KEY).unwrap(), + ConfigurationValueV1::Boolean(true), + ), + ( + SettingKey::new(SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY).unwrap(), + ConfigurationValueV1::Unsigned(750), + ), + ( + SettingKey::new(SYNC_WATCH_MAX_PROJECTS_SETTING_KEY).unwrap(), + ConfigurationValueV1::Unsigned(12), + ), + ( + SettingKey::new(SYNC_READ_COOLDOWN_SECS_SETTING_KEY).unwrap(), + ConfigurationValueV1::Unsigned(45), + ), + ])); + let mut values = complete.effective_values; + let mut provenance = complete.provenance; + for raw_key in [ + "sync.full_sync_escalation_files.v1", + "sync.max_concurrent_syncs.v1", + ] { + let key = SettingKey::new(raw_key).unwrap(); + values.remove(&key); + provenance.remove(&key); + } + let snapshot = ConfigurationSnapshotV1::new(values, provenance).unwrap(); + + let pinned = PinnedRuntimeConfiguration::new(target(), revision(), snapshot).unwrap(); + + assert!(pinned.config().sync.watch_linked_worktrees); + assert_eq!(pinned.config().sync.watch_debounce_ms, 750); + assert_eq!(pinned.config().sync.watch_max_projects, 12); + assert_eq!(pinned.config().sync.read_cooldown_secs, 45); + assert!(pinned.config().sync.read_refresh); + } + #[test] fn pin_rejects_a_required_setting_with_the_wrong_type() { let complete = resolved(BTreeMap::new()); diff --git a/crates/tracedecay-configuration/src/config/model.rs b/crates/tracedecay-configuration/src/config/model.rs index 532c7b4dfe..5c06a25783 100644 --- a/crates/tracedecay-configuration/src/config/model.rs +++ b/crates/tracedecay-configuration/src/config/model.rs @@ -132,10 +132,6 @@ pub struct SyncConfig { pub session_start_stale_threshold_secs: u64, /// Daemon backstop scheduler interval (minutes); 0 disables it. pub backstop_interval_mins: u64, - /// Diff-scoped syncs above this many changed files escalate to a full sync. - pub full_sync_escalation_files: usize, - /// Daemon-wide cap on concurrent syncs. - pub max_concurrent_syncs: usize, /// Grace period before a dead tracked-branch store is GC'd (days). pub branch_gc_days: u64, /// Auto-initialise never-indexed repos on first contact. @@ -173,8 +169,6 @@ impl Default for SyncConfig { session_start_sync: true, session_start_stale_threshold_secs: 600, backstop_interval_mins: 15, - full_sync_escalation_files: 500, - max_concurrent_syncs: 2, branch_gc_days: 14, auto_init: true, auto_track_pr_branches: false, diff --git a/crates/tracedecay-configuration/src/configuration/operations.rs b/crates/tracedecay-configuration/src/configuration/operations.rs index 5f1295259e..8fd9f5c406 100644 --- a/crates/tracedecay-configuration/src/configuration/operations.rs +++ b/crates/tracedecay-configuration/src/configuration/operations.rs @@ -617,7 +617,7 @@ mod tests { use std::sync::Mutex; use super::*; - use crate::config::registry::ConfigurationRegistry; + use crate::config::registry::{ConfigurationRegistry, ConfigurationRegistryError}; use tracedecay_domain::configuration::{ AuthorityRef, ConfigurationGrantId, ConfigurationGrantReceiptId, ConfigurationLayerIdV1, ConfigurationMutationGrantReceiptV1, ConfigurationSnapshotV1, ConfigurationValueV1, @@ -825,6 +825,121 @@ mod tests { assert_eq!(result, Err(ConfigurationError::PolicyWideningForbidden)); } + #[test] + fn direct_mutation_rejects_retired_sync_settings() { + let registry = ConfigurationRegistry::core().unwrap(); + let layer = ConfigurationLayerIdV1::Project { + project_id: id::("project.configuration.sync"), + }; + let retained = DirectConfigurationMutation::Set { + layer: layer.clone(), + key: SettingKey::new("sync.watch_max_projects.v1").unwrap(), + value: Box::new(ConfigurationValueV1::Unsigned(7)), + }; + assert_eq!(validate_direct_mutation(®istry, &retained), Ok(())); + + for raw_key in [ + "sync.full_sync_escalation_files.v1", + "sync.max_concurrent_syncs.v1", + ] { + let key = SettingKey::new(raw_key).unwrap(); + let expected = ConfigurationError::validation( + ConfigurationRegistryError::UnknownSetting(key.clone()), + ); + let set = DirectConfigurationMutation::Set { + layer: layer.clone(), + key: key.clone(), + value: Box::new(ConfigurationValueV1::Unsigned(9)), + }; + for mutation in [ + set.clone(), + DirectConfigurationMutation::Unset { + layer: layer.clone(), + key, + }, + DirectConfigurationMutation::Batch { + mutations: vec![retained.clone(), set], + }, + ] { + assert_eq!( + validate_direct_mutation(®istry, &mutation), + Err(expected.clone()), + "retired settings must reject every direct write form: {mutation:?}" + ); + } + } + } + + #[asupersync::test] + async fn retired_sync_settings_are_absent_from_list_and_get() { + let registry = ConfigurationRegistry::core().unwrap(); + let store = Store { + current: ConfigurationCurrentStateV1 { + revision_id: id("configuration.revision.sync"), + snapshot: crate::config::resolver::resolve_configuration(®istry, &[]) + .unwrap() + .snapshot, + }, + saved: Mutex::new(None), + replay: Mutex::new(None), + }; + let authorization = Authorization { + current: CurrentConfigurationMutationAuthorizationV1 { + grant_revision: 1, + grant_digest: digest('c'), + scope_digest: digest('a'), + policy_epoch: 7, + policy_digest: policy_digest('b'), + }, + }; + let scope = Scope { + evidence: ScopeRevalidationEvidenceV1 { + resolved_scope_digest: digest('a'), + membership_digest: None, + authorization_policy_digest: policy_digest('b'), + policy_epoch: 7, + }, + }; + let operations = ConfigurationControlPlaneOperations::new( + ®istry, + &store, + &scope, + &authorization, + clock, + ); + let actor = AuthorizedActor { + actor_id: id::("actor.configuration.sync"), + }; + let listed = operations.list(actor.clone()).await.unwrap(); + let retained_key = SettingKey::new("sync.watch_max_projects.v1").unwrap(); + assert!(listed.iter().any(|setting| setting.key == retained_key)); + assert_eq!( + operations + .get(actor.clone(), retained_key) + .await + .unwrap() + .effective_value, + ConfigurationValueV1::Unsigned(32) + ); + + for raw_key in [ + "sync.full_sync_escalation_files.v1", + "sync.max_concurrent_syncs.v1", + ] { + let key = SettingKey::new(raw_key).unwrap(); + assert!( + !listed.iter().any(|setting| setting.key == key), + "{raw_key}" + ); + assert_eq!( + operations.get(actor.clone(), key.clone()).await, + Err(ConfigurationError::validation( + ConfigurationRegistryError::UnknownSetting(key) + )) + ); + } + } + #[test] fn current_authorization_must_match_scope_and_policy_evidence() { let authorization = CurrentConfigurationMutationAuthorizationV1 { diff --git a/crates/tracedecay-domain/src/configuration.rs b/crates/tracedecay-domain/src/configuration.rs index 304f2e80fd..1e708fe2df 100644 --- a/crates/tracedecay-domain/src/configuration.rs +++ b/crates/tracedecay-domain/src/configuration.rs @@ -56,10 +56,14 @@ pub const LCM_SUMMARIZER_EXECUTABLES_SETTING_KEY: &str = "lcm.summarizer_executa /// project's snapshot through v1.0.0-beta.61 and now live only in the profile /// store ([`PROFILE_SETTING_KEYS_V1`]). `diagnostics.prewarm.v1` shipped /// through v1.0.0-beta.83 and left with the compile-on-read cargo prewarm. +/// The full-sync escalation and concurrent-sync settings were retired because +/// sync execution does not read them. pub const RETIRED_CORE_SETTING_KEYS_V1: &[&str] = &[ "diagnostics.prewarm.v1", "index.git_ignore.v1", "semantic.runtime.v1", + "sync.full_sync_escalation_files.v1", + "sync.max_concurrent_syncs.v1", "sync.orphan_db_gc_days.v1", "user.upload_enabled.v1", USER_WATCHER_DEBOUNCE_MS_SETTING_KEY, @@ -112,8 +116,6 @@ pub const SYNC_SESSION_START_SYNC_SETTING_KEY: &str = "sync.session_start_sync.v pub const SYNC_SESSION_START_STALE_THRESHOLD_SECS_SETTING_KEY: &str = "sync.session_start_stale_threshold_secs.v1"; pub const SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY: &str = "sync.backstop_interval_mins.v1"; -pub const SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY: &str = "sync.full_sync_escalation_files.v1"; -pub const SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY: &str = "sync.max_concurrent_syncs.v1"; pub const SYNC_BRANCH_GC_DAYS_SETTING_KEY: &str = "sync.branch_gc_days.v1"; pub const SYNC_AUTO_INIT_SETTING_KEY: &str = "sync.auto_init.v1"; pub const SYNC_AUTO_TRACK_PR_BRANCHES_SETTING_KEY: &str = "sync.auto_track_pr_branches.v1"; @@ -154,8 +156,6 @@ pub const CONFIGURATION_SETTING_KEYS_V1: &[&str] = &[ SYNC_SESSION_START_SYNC_SETTING_KEY, SYNC_SESSION_START_STALE_THRESHOLD_SECS_SETTING_KEY, SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY, - SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, - SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY, SYNC_BRANCH_GC_DAYS_SETTING_KEY, SYNC_AUTO_INIT_SETTING_KEY, SYNC_AUTO_TRACK_PR_BRANCHES_SETTING_KEY, diff --git a/crates/tracedecay-domain/src/lib.rs b/crates/tracedecay-domain/src/lib.rs index 946657283e..bbaf2a6595 100644 --- a/crates/tracedecay-domain/src/lib.rs +++ b/crates/tracedecay-domain/src/lib.rs @@ -115,8 +115,7 @@ pub use configuration::{ SOURCE_BINDINGS_SETTING_KEY, SYNC_AUTO_INIT_SETTING_KEY, SYNC_AUTO_TRACK_PR_BRANCHES_SETTING_KEY, SYNC_AUTO_TRACK_PR_POLL_SECS_SETTING_KEY, SYNC_AUTO_WATCH_SETTING_KEY, SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY, - SYNC_BRANCH_GC_DAYS_SETTING_KEY, SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, - SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY, SYNC_READ_COOLDOWN_SECS_SETTING_KEY, + SYNC_BRANCH_GC_DAYS_SETTING_KEY, SYNC_READ_COOLDOWN_SECS_SETTING_KEY, SYNC_READ_REFRESH_SETTING_KEY, SYNC_SESSION_START_STALE_THRESHOLD_SECS_SETTING_KEY, SYNC_SESSION_START_SYNC_SETTING_KEY, SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY, SYNC_WATCH_LINKED_WORKTREES_SETTING_KEY, SYNC_WATCH_MAX_DELAY_MS_SETTING_KEY, diff --git a/crates/tracedecay-global-db/src/configuration/registry.rs b/crates/tracedecay-global-db/src/configuration/registry.rs index e31e1ece16..0b577c46de 100644 --- a/crates/tracedecay-global-db/src/configuration/registry.rs +++ b/crates/tracedecay-global-db/src/configuration/registry.rs @@ -16,7 +16,6 @@ use tracedecay_domain::configuration::{ SYNC_AUTO_INIT_SETTING_KEY, SYNC_AUTO_TRACK_PR_BRANCHES_SETTING_KEY, SYNC_AUTO_TRACK_PR_POLL_SECS_SETTING_KEY, SYNC_AUTO_WATCH_SETTING_KEY, SYNC_BACKSTOP_INTERVAL_MINS_SETTING_KEY, SYNC_BRANCH_GC_DAYS_SETTING_KEY, - SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY, SYNC_READ_COOLDOWN_SECS_SETTING_KEY, SYNC_READ_REFRESH_SETTING_KEY, SYNC_SESSION_START_STALE_THRESHOLD_SECS_SETTING_KEY, SYNC_SESSION_START_SYNC_SETTING_KEY, SYNC_WATCH_DEBOUNCE_MS_SETTING_KEY, SYNC_WATCH_LINKED_WORKTREES_SETTING_KEY, @@ -483,8 +482,6 @@ struct SyncDefaults { session_start_sync: bool, session_start_stale_threshold_secs: u64, backstop_interval_mins: u64, - full_sync_escalation_files: usize, - max_concurrent_syncs: usize, branch_gc_days: u64, auto_init: bool, auto_track_pr_branches: bool, @@ -504,8 +501,6 @@ impl Default for SyncDefaults { session_start_sync: true, session_start_stale_threshold_secs: 600, backstop_interval_mins: 15, - full_sync_escalation_files: 500, - max_concurrent_syncs: 2, branch_gc_days: 14, auto_init: true, auto_track_pr_branches: false, @@ -641,18 +636,6 @@ fn register_project_settings( SettingSensitivityV1::Public, RestartRequirementV1::DaemonRestart, ), - ( - SYNC_FULL_SYNC_ESCALATION_FILES_SETTING_KEY, - ConfigurationValueV1::Unsigned(sync.full_sync_escalation_files as u64), - SettingSensitivityV1::Public, - RestartRequirementV1::DaemonRestart, - ), - ( - SYNC_MAX_CONCURRENT_SYNCS_SETTING_KEY, - ConfigurationValueV1::Unsigned(sync.max_concurrent_syncs as u64), - SettingSensitivityV1::Public, - RestartRequirementV1::DaemonRestart, - ), ( SYNC_BRANCH_GC_DAYS_SETTING_KEY, ConfigurationValueV1::Unsigned(sync.branch_gc_days), diff --git a/crates/tracedecay-global-db/src/configuration/store/tests/mod.rs b/crates/tracedecay-global-db/src/configuration/store/tests/mod.rs index 873af7e58a..a04cf19eb8 100644 --- a/crates/tracedecay-global-db/src/configuration/store/tests/mod.rs +++ b/crates/tracedecay-global-db/src/configuration/store/tests/mod.rs @@ -586,3 +586,4 @@ fn evidence_for(plan: &ProtectedChangePlan) -> ScopeRevalidationEvidenceV1 { mod control; mod persistence; mod receipt_writer; +mod retired_sync; diff --git a/crates/tracedecay-global-db/src/configuration/store/tests/retired_sync.rs b/crates/tracedecay-global-db/src/configuration/store/tests/retired_sync.rs new file mode 100644 index 0000000000..09f59ae548 --- /dev/null +++ b/crates/tracedecay-global-db/src/configuration/store/tests/retired_sync.rs @@ -0,0 +1,221 @@ +//! Released sync settings retire without changing supported policy or history. + +use super::super::mutation::commit_direct_in_transaction_with_registry; +use super::super::{ + ConfigurationControlStore, ConfigurationError, ConfigurationRevisionStore, + GlobalDbConfigurationControlStore, +}; +use super::{ + HostAdmissionScope, HostAdmissionTestRuntimeV1, control_authority, count, direct_project_layer, + global_setup, id, +}; +use crate::configuration::contracts::DirectConfigurationMutation; +use crate::configuration::registry::{ConfigurationRegistry, ConfigurationRegistryError}; +use crate::configuration::resolver::resolve_configuration; +use tracedecay_domain::UtcMicros; +use tracedecay_domain::configuration::{ + ConfigurationMutationOperationV1, ConfigurationSnapshotV1, ConfigurationValueKindV1, + ConfigurationValueV1, DeprecationStateV1, RestartRequirementV1, + SYNC_READ_COOLDOWN_SECS_SETTING_KEY, SettingDefinitionV1, SettingKey, SettingScopeV1, + SettingSensitivityV1, +}; + +// These defaults and descriptors shipped before the controls were retired. +const RELEASED_SYNC_SETTINGS: [(&str, u64); 2] = [ + ("sync.full_sync_escalation_files.v1", 500), + ("sync.max_concurrent_syncs.v1", 2), +]; + +#[asupersync::test] +async fn retired_sync_defaults_converge_without_reset_or_history_rewrite() { + assert_sync_retirement_preserves_history(false).await; +} + +#[asupersync::test] +async fn retired_sync_overrides_converge_without_losing_supported_policy() { + assert_sync_retirement_preserves_history(true).await; +} + +async fn assert_sync_retirement_preserves_history(override_retired: bool) { + let mut released = ConfigurationRegistry::core().unwrap(); + for (raw_key, default) in RELEASED_SYNC_SETTINGS { + released + .register(SettingDefinitionV1 { + key: SettingKey::new(raw_key).unwrap(), + schema_revision: 7, + value_kind: ConfigurationValueKindV1::Unsigned, + default_value: ConfigurationValueV1::Unsigned(default), + sensitivity: SettingSensitivityV1::Public, + scope: SettingScopeV1::Project, + restart_requirement: RestartRequirementV1::DaemonRestart, + deprecation: DeprecationStateV1::Active, + }) + .unwrap(); + } + let directory = tempfile::tempdir().unwrap(); + let profile_root = directory.path().join("profile"); + let project_root = directory.path().join("project"); + std::fs::create_dir_all(&project_root).unwrap(); + let runtime = HostAdmissionTestRuntimeV1::project( + &profile_root, + &project_root, + id("project.configuration-store.fixture"), + ) + .await + .unwrap(); + let db = runtime + .registered_database(HostAdmissionScope::Project) + .unwrap(); + let store = GlobalDbConfigurationControlStore::new_registered(db); + let initial_revision = id("configuration.revision.released-sync"); + store + .initialize_canonical_with_registry( + &initial_revision, + &resolve_configuration(&released, &[]).unwrap(), + UtcMicros(1), + &released, + ) + .await + .unwrap(); + let initial = store.current_revision().await.unwrap(); + + let cooldown = SettingKey::new(SYNC_READ_COOLDOWN_SECS_SETTING_KEY).unwrap(); + let mut mutations = vec![DirectConfigurationMutation::Set { + layer: direct_project_layer(), + key: cooldown.clone(), + value: Box::new(ConfigurationValueV1::Unsigned(83)), + }]; + if override_retired { + mutations.extend(RELEASED_SYNC_SETTINGS.into_iter().zip([900, 7]).map( + |((raw_key, _), value)| DirectConfigurationMutation::Set { + layer: direct_project_layer(), + key: SettingKey::new(raw_key).unwrap(), + value: Box::new(ConfigurationValueV1::Unsigned(value)), + }, + )); + } + let authority = control_authority( + ConfigurationMutationOperationV1::DirectMutation, + &initial_revision, + ); + let transaction = db.begin_write_transaction().await.unwrap(); + commit_direct_in_transaction_with_registry( + &transaction, + &authority, + &DirectConfigurationMutation::Batch { mutations }, + &initial_revision, + &released, + ) + .await + .unwrap(); + transaction.commit().await.unwrap(); + let historical = store.current_revision().await.unwrap(); + + let mut expected_values = historical.snapshot.effective_values.clone(); + let mut expected_provenance = historical.snapshot.provenance.clone(); + for ((raw_key, default), overridden) in RELEASED_SYNC_SETTINGS.into_iter().zip([900, 7]) { + let key = SettingKey::new(raw_key).unwrap(); + assert_eq!( + expected_values.remove(&key), + Some(ConfigurationValueV1::Unsigned(if override_retired { + overridden + } else { + default + })) + ); + assert!(expected_provenance.remove(&key).is_some()); + } + let expected = ConfigurationSnapshotV1::new(expected_values, expected_provenance).unwrap(); + + let converged = store + .converge_registered_registry_shape(&historical.revision_id, UtcMicros(20)) + .await + .unwrap(); + assert_ne!(converged.revision_id, historical.revision_id); + assert_eq!(converged.snapshot, expected); + assert_eq!( + converged.snapshot.effective_values[&cooldown], + ConfigurationValueV1::Unsigned(83) + ); + assert_eq!( + converged.snapshot.provenance[&cooldown], + historical.snapshot.provenance[&cooldown] + ); + let current = store.current_revision().await.unwrap(); + assert_eq!(current.revision_id, converged.revision_id); + assert_eq!( + current.parent_revision_id, + Some(historical.revision_id.clone()) + ); + assert_eq!(current.operation_kind, "registry_shape_convergence"); + assert_eq!( + store.read_revision(&historical.revision_id).await.unwrap(), + Some(historical) + ); + assert_eq!( + store.read_revision(&initial_revision).await.unwrap(), + Some(initial) + ); + assert_eq!( + store + .converge_registered_registry_shape(&converged.revision_id, UtcMicros(21)) + .await + .unwrap(), + converged, + "reopening the current shape must not create another revision" + ); +} + +#[asupersync::test] +async fn retired_sync_settings_are_absent_and_writes_are_rejected() { + let registry = ConfigurationRegistry::core().unwrap(); + let (_directory, runtime, root) = Box::pin(global_setup()).await; + let db = runtime + .registered_database(HostAdmissionScope::Project) + .unwrap(); + let store = GlobalDbConfigurationControlStore::new_registered(db); + let authority = control_authority( + ConfigurationMutationOperationV1::DirectMutation, + &root.revision_id, + ); + + for (raw_key, default) in RELEASED_SYNC_SETTINGS { + let key = SettingKey::new(raw_key).unwrap(); + assert!( + registry + .definitions() + .all(|definition| definition.key != key) + ); + assert!(matches!( + registry.definition(&key), + Err(ConfigurationRegistryError::UnknownSetting(rejected)) if rejected == key + )); + assert!(!root.snapshot.effective_values.contains_key(&key)); + assert!(!root.snapshot.provenance.contains_key(&key)); + + for mutation in [ + DirectConfigurationMutation::Set { + layer: direct_project_layer(), + key: key.clone(), + value: Box::new(ConfigurationValueV1::Unsigned(default)), + }, + DirectConfigurationMutation::Unset { + layer: direct_project_layer(), + key: key.clone(), + }, + ] { + assert_eq!( + store + .commit_direct(&authority, &mutation, &root.revision_id) + .await, + Err(ConfigurationError::Validation(format!( + "setting key is not registered: {raw_key}" + ))) + ); + assert_eq!(store.current_revision().await.unwrap(), root); + } + } + let read = db.read_snapshot().await.unwrap(); + assert_eq!(count(&read, "configuration_mutation_receipts").await, 0); + assert_eq!(count(&read, "configuration_audit_events").await, 0); +}