From 2c941dc6234d748b95928e0e9f032a5e9ed30667 Mon Sep 17 00:00:00 2001 From: Hammed Abass Date: Wed, 12 Aug 2026 17:00:28 +0200 Subject: [PATCH] refactor(core): remove experimental saved device contract --- .../Tests/SavedDeviceCoreContractTests.swift | 3 + crates/vnidrop/AGENTS.md | 2 +- crates/vnidrop/CORE_FLOW.md | 2 +- crates/vnidrop/src/api.rs | 21 ------ crates/vnidrop/src/device_relationship/mod.rs | 2 +- crates/vnidrop/src/lib.rs | 14 ++-- crates/vnidrop/src/runtime/facade.rs | 15 ---- crates/vnidrop/src/runtime/mod.rs | 2 +- crates/vnidrop/src/runtime/saved_devices.rs | 2 +- crates/vnidrop/src/secret.rs | 71 ------------------- crates/vnidrop/src/tests.rs | 2 - crates/vnidrop/src/tests/api_surface.rs | 4 +- .../src/tests/platform_contract_android.rs | 5 +- .../src/tests/platform_contract_apple.rs | 17 +++-- .../src/tests/platform_contract_linux.rs | 8 +-- .../src/tests/platform_contract_windows.rs | 6 +- crates/vnidrop/src/tests/secret.rs | 22 ------ ...ental_domain.rs => saved_device_domain.rs} | 4 +- ...vedDeviceCoreContractBindingHygieneTest.kt | 3 + ...vedDeviceCoreContractBindingHygieneTest.kt | 3 + 20 files changed, 46 insertions(+), 162 deletions(-) delete mode 100644 crates/vnidrop/src/secret.rs delete mode 100644 crates/vnidrop/src/tests/secret.rs rename crates/vnidrop/tests/{experimental_domain.rs => saved_device_domain.rs} (97%) diff --git a/apple/Tests/SavedDeviceCoreContractTests.swift b/apple/Tests/SavedDeviceCoreContractTests.swift index 5511b31..1f63745 100644 --- a/apple/Tests/SavedDeviceCoreContractTests.swift +++ b/apple/Tests/SavedDeviceCoreContractTests.swift @@ -161,6 +161,9 @@ final class SavedDeviceCoreContractTests: XCTestCase { "generated bindings must not expose \(needle)" ) } + XCTAssertFalse(source.contains("initializeWithExperimentalSavedDevices")) + XCTAssertFalse(source.contains("ExperimentalSavedDeviceCapabilities")) + XCTAssertFalse(source.contains("experimentalSavedDeviceCapabilities")) XCTAssertTrue(source.contains("initializeWithLimitsAndNetworkConfig")) XCTAssertTrue(source.contains("public struct SavedDeviceCapabilities")) XCTAssertTrue(source.contains("public func savedDeviceCapabilities()")) diff --git a/crates/vnidrop/AGENTS.md b/crates/vnidrop/AGENTS.md index fe47d15..c910128 100644 --- a/crates/vnidrop/AGENTS.md +++ b/crates/vnidrop/AGENTS.md @@ -54,7 +54,7 @@ src/ receive.rs # receive, download, export, OutputSinkFile lifecycle.rs # cancel share, delete, status, access mode, shutdown provider.rs # provider messages, per-peer transfer progress - saved_devices.rs # experimental saved-device pairing, forget, block + saved_devices.rs # saved-device pairing, forget, block targeted.rs # saved-device targeted transfers persistence.rs # AppDataStores / persistence open (domain stores) invitation/ # invitation-transfer domain store (type name: Repository) diff --git a/crates/vnidrop/CORE_FLOW.md b/crates/vnidrop/CORE_FLOW.md index 8911c1e..1f8be31 100644 --- a/crates/vnidrop/CORE_FLOW.md +++ b/crates/vnidrop/CORE_FLOW.md @@ -42,7 +42,7 @@ bytes through Kotlin memory. `stopped`. - Main event phases: `endpoint`, `import`, `ticket`, `handshake`, `approval`, `access`, `transfer`, `download`, `export`, `delivery`, `lifecycle`, `error`, - plus experimental `pairing` and `targeted_transfer` (see catalog below). + `pairing`, and `targeted_transfer` (see catalog below). - Events are sent to `CoreEventSink` immediately and persisted through the event hub. `list_events` flushes queued persistence before reading SQLite. - `shutdown()` is idempotent and flushes events before stopping the router. diff --git a/crates/vnidrop/src/api.rs b/crates/vnidrop/src/api.rs index c6eeb7e..92492ad 100644 --- a/crates/vnidrop/src/api.rs +++ b/crates/vnidrop/src/api.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - use anyhow::Context; use iroh::RelayUrl; use iroh_blobs::Hash; @@ -29,25 +27,6 @@ pub fn saved_device_capabilities() -> SavedDeviceCapabilities { } } -#[deprecated(note = "use SavedDeviceCapabilities")] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)] -pub struct ExperimentalSavedDeviceCapabilities { - pub domain_contract_version: u16, - pub relationship_protocol_version: u16, - pub targeted_transfer_protocol_version: u16, -} - -#[deprecated(note = "use saved_device_capabilities")] -#[uniffi::export] -pub fn experimental_saved_device_capabilities() -> ExperimentalSavedDeviceCapabilities { - let capabilities = saved_device_capabilities(); - ExperimentalSavedDeviceCapabilities { - domain_contract_version: capabilities.domain_contract_version, - relationship_protocol_version: capabilities.relationship_protocol_version, - targeted_transfer_protocol_version: capabilities.targeted_transfer_protocol_version, - } -} - /// Public view of a single-use pairing window after a completed transfer. /// /// The eligibility capability itself never crosses this boundary. diff --git a/crates/vnidrop/src/device_relationship/mod.rs b/crates/vnidrop/src/device_relationship/mod.rs index cd0a61b..47e5bc0 100644 --- a/crates/vnidrop/src/device_relationship/mod.rs +++ b/crates/vnidrop/src/device_relationship/mod.rs @@ -1,4 +1,4 @@ -//! Experimental saved-device mutual-consent relationships. +//! Saved-device mutual-consent relationships. //! //! Pending outgoing/incoming states, directional grants bound to relationship //! generation, and Saved only after mutual acknowledgement. diff --git a/crates/vnidrop/src/lib.rs b/crates/vnidrop/src/lib.rs index a1b37fd..4d2d8ca 100644 --- a/crates/vnidrop/src/lib.rs +++ b/crates/vnidrop/src/lib.rs @@ -14,8 +14,6 @@ mod logging; mod pairing_eligibility; mod persistence; mod runtime; -#[cfg(test)] -mod secret; #[allow( dead_code, reason = "the private custody seam is activated by platform credential adapters" @@ -26,15 +24,13 @@ mod ticket; mod transfer_state; mod util; -#[allow(deprecated)] pub use api::{ clear_inactive_transfer_cache, default_core_limits, default_core_network_config, - experimental_saved_device_capabilities, saved_device_capabilities, CoreEvent, CoreEventSink, - CoreLimits, CoreNetworkConfig, CoreRelayMode, CoreStorageUsage, DeviceRelationship, - DeviceRelationshipState, ExperimentalSavedDeviceCapabilities, PairingEligibilitySummary, - PendingTargetedOffer, PublishedOutput, ReceiveOutputSink, ReceiveOutputSinkV2, - ReceivedArtifact, ReceivedLocatorKind, ReceiverRequest, RuntimeStatus, SavedDevice, - SavedDeviceCapabilities, ShareMetadataInput, ShareResult, ShareSource, SourceKind, + saved_device_capabilities, CoreEvent, CoreEventSink, CoreLimits, CoreNetworkConfig, + CoreRelayMode, CoreStorageUsage, DeviceRelationship, DeviceRelationshipState, + PairingEligibilitySummary, PendingTargetedOffer, PublishedOutput, ReceiveOutputSink, + ReceiveOutputSinkV2, ReceivedArtifact, ReceivedLocatorKind, ReceiverRequest, RuntimeStatus, + SavedDevice, SavedDeviceCapabilities, ShareMetadataInput, ShareResult, ShareSource, SourceKind, StoredTransfer, TargetedOfferResponse, TargetedTransfer, TargetedTransferState, TicketInspection, TransferAccessMode, TransferMetadata, }; diff --git a/crates/vnidrop/src/runtime/facade.rs b/crates/vnidrop/src/runtime/facade.rs index fbff64d..e121bb5 100644 --- a/crates/vnidrop/src/runtime/facade.rs +++ b/crates/vnidrop/src/runtime/facade.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - use std::{future::Future, path::PathBuf, sync::Arc}; use anyhow::Context; @@ -317,7 +315,6 @@ impl VnidropCore { } } -#[allow(deprecated)] #[uniffi::export] impl VnidropCore { #[uniffi::constructor] @@ -371,18 +368,6 @@ impl VnidropCore { Self::initialize_protected(app_data_dir, event_sink, limits, network_config) } - /// Compatibility constructor retained during the protected-initialization expand phase. - #[deprecated(note = "use initialize_with_limits_and_network_config")] - #[uniffi::constructor] - pub fn initialize_with_experimental_saved_devices( - app_data_dir: String, - event_sink: Arc, - limits: CoreLimits, - network_config: CoreNetworkConfig, - ) -> Result, VnidropError> { - Self::initialize_protected(app_data_dir, event_sink, limits, network_config) - } - pub fn status(&self) -> RuntimeStatus { self.block_on(self.inner.status()) } diff --git a/crates/vnidrop/src/runtime/mod.rs b/crates/vnidrop/src/runtime/mod.rs index d452909..0a52727 100644 --- a/crates/vnidrop/src/runtime/mod.rs +++ b/crates/vnidrop/src/runtime/mod.rs @@ -6,7 +6,7 @@ //! - [`receive`] — ticket receive, download, export //! - [`lifecycle`] — cancel/delete/shutdown/status/access //! - [`provider`] — blob provider events and per-connection send progress -//! - [`saved_devices`] — experimental saved-device pairing, forget, block +//! - [`saved_devices`] — saved-device pairing, forget, block //! - [`targeted`] — saved-device targeted transfers mod delivery; diff --git a/crates/vnidrop/src/runtime/saved_devices.rs b/crates/vnidrop/src/runtime/saved_devices.rs index e4f0c5b..da457e2 100644 --- a/crates/vnidrop/src/runtime/saved_devices.rs +++ b/crates/vnidrop/src/runtime/saved_devices.rs @@ -1,4 +1,4 @@ -//! Runtime operations for experimental saved devices and device relationships. +//! Runtime operations for saved devices and device relationships. use std::sync::Arc; diff --git a/crates/vnidrop/src/secret.rs b/crates/vnidrop/src/secret.rs deleted file mode 100644 index 305f461..0000000 --- a/crates/vnidrop/src/secret.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{ - fs::OpenOptions, - io::{self, Write}, - path::Path, - str::FromStr, -}; - -#[cfg(unix)] -use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; - -use anyhow::{Context, Result}; -use data_encoding::HEXLOWER; -use iroh::SecretKey; - -pub(crate) async fn load_or_create_secret(app_data_dir: &Path) -> Result { - if let Ok(secret) = std::env::var("IROH_SECRET") { - return SecretKey::from_str(&secret).context("invalid IROH_SECRET"); - } - - let path = app_data_dir.join("iroh.secret"); - match tokio::fs::read_to_string(&path).await { - Ok(secret) => { - let bytes = HEXLOWER - .decode(secret.trim().as_bytes()) - .context("invalid persisted iroh secret encoding")?; - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| anyhow::anyhow!("invalid persisted iroh secret length"))?; - restrict_permissions(&path).await?; - Ok(SecretKey::from_bytes(&bytes)) - } - Err(error) if error.kind() == io::ErrorKind::NotFound => { - let secret = SecretKey::generate(); - let encoded = HEXLOWER.encode(&secret.to_bytes()); - // Create with owner-only mode on Unix so the key is never briefly 0644. - write_secret_file(&path, encoded.as_bytes()).await?; - restrict_permissions(&path).await?; - Ok(secret) - } - Err(error) => Err(error.into()), - } -} - -async fn write_secret_file(path: &Path, bytes: &[u8]) -> Result<()> { - let path = path.to_path_buf(); - let bytes = bytes.to_vec(); - tokio::task::spawn_blocking(move || { - let mut options = OpenOptions::new(); - options.write(true).create_new(true); - #[cfg(unix)] - options.mode(0o600); - let mut file = options - .open(&path) - .with_context(|| format!("failed to create {}", path.display()))?; - file.write_all(&bytes) - .with_context(|| format!("failed to write {}", path.display()))?; - file.sync_all() - .with_context(|| format!("failed to sync {}", path.display()))?; - Ok::<(), anyhow::Error>(()) - }) - .await? -} - -async fn restrict_permissions(path: &Path) -> Result<()> { - #[cfg(unix)] - tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)).await?; - // Windows: file lives under the user profile app-data dir with default ACLs - // limited to the current user. No portable owner-only API in std. - let _ = path; - Ok(()) -} diff --git a/crates/vnidrop/src/tests.rs b/crates/vnidrop/src/tests.rs index 971747b..bffee0e 100644 --- a/crates/vnidrop/src/tests.rs +++ b/crates/vnidrop/src/tests.rs @@ -37,8 +37,6 @@ mod platform_contract_windows_tests; mod repository_tests; #[path = "tests/runtime.rs"] mod runtime_tests; -#[path = "tests/secret.rs"] -mod secret_tests; #[path = "tests/secure_secret_android.rs"] mod secure_secret_android_tests; #[cfg(any(target_os = "macos", target_os = "ios"))] diff --git a/crates/vnidrop/src/tests/api_surface.rs b/crates/vnidrop/src/tests/api_surface.rs index 1867ba8..4f1e2c9 100644 --- a/crates/vnidrop/src/tests/api_surface.rs +++ b/crates/vnidrop/src/tests/api_surface.rs @@ -10,6 +10,9 @@ fn public_api_exposes_saved_device_surface_without_prototype_contact_entry_point let lib = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/lib.rs")); for forbidden in [ + "initialize_with_experimental_saved_devices", + "experimental_saved_device_capabilities", + "ExperimentalSavedDeviceCapabilities", "fn list_contacts(", "fn send_to_contact(", "fn poll_contacts_for_offers(", @@ -58,7 +61,6 @@ fn public_api_exposes_saved_device_surface_without_prototype_contact_entry_point "fn forget_saved_device(", "fn share_files(", "fn receive(", - "experimental_saved_device_capabilities", "saved_device_capabilities", ] { assert!( diff --git a/crates/vnidrop/src/tests/platform_contract_android.rs b/crates/vnidrop/src/tests/platform_contract_android.rs index 25e2afd..bc5c40c 100644 --- a/crates/vnidrop/src/tests/platform_contract_android.rs +++ b/crates/vnidrop/src/tests/platform_contract_android.rs @@ -1,4 +1,4 @@ -//! Android platform contract for the experimental saved-device foundation. +//! Android platform contract for the saved-device foundation. //! //! These tests drive the public UniFFI surface through an //! [`AndroidSecureSecretStore`] backed by an in-process Keystore fake so the @@ -929,6 +929,9 @@ fn android_public_surface_omits_raw_secrets_and_generic_mutation() { path.display() ); } + assert!(!kotlin.contains("initializeWithExperimentalSavedDevices")); + assert!(!kotlin.contains("ExperimentalSavedDeviceCapabilities")); + assert!(!kotlin.contains("experimentalSavedDeviceCapabilities")); assert!( kotlin.contains("initializeWithLimitsAndNetworkConfig"), "production protected Android init must remain on the public binding surface" diff --git a/crates/vnidrop/src/tests/platform_contract_apple.rs b/crates/vnidrop/src/tests/platform_contract_apple.rs index c9442a8..09935bf 100644 --- a/crates/vnidrop/src/tests/platform_contract_apple.rs +++ b/crates/vnidrop/src/tests/platform_contract_apple.rs @@ -53,7 +53,7 @@ impl RecordingSink { /// `AppleKeychainSecretStore` + profile scoping. CLI unit tests lack the app /// Keychain entitlement, so the system Keychain returns Unavailable; the /// injectable API exercises the identical adapter path. Swift XCTest covers -/// the real experimental constructor under the app entitlements. +/// the standard protected constructor under the app entitlements. struct KeychainNode { data_dir: tempfile::TempDir, api: RecordingKeychain, @@ -208,7 +208,7 @@ impl Drop for KeychainNode { } } -fn try_experimental_keychain_init(app_data_dir: &Path) -> Result, VnidropError> { +fn try_keychain_init(app_data_dir: &Path) -> Result, VnidropError> { VnidropCore::initialize_with_limits_and_network_config( app_data_dir.to_string_lossy().into_owned(), Arc::new(RecordingSink { @@ -593,7 +593,7 @@ fn recover_authoritative_state( } #[test] -fn experimental_keychain_identity_survives_core_restart() { +fn keychain_identity_survives_core_restart() { let node = KeychainNode::new(); let endpoint_id = node.core().status().endpoint_id.clone(); assert!(!endpoint_id.is_empty()); @@ -610,18 +610,18 @@ fn experimental_keychain_identity_survives_core_restart() { // constructor must also preserve identity. Headless cargo often lacks that // entitlement and maps it to Unavailable — that path is covered by Swift. let live = tempfile::tempdir().unwrap(); - match try_experimental_keychain_init(live.path()) { + match try_keychain_init(live.path()) { Ok(core) => { let id = core.status().endpoint_id.clone(); core.shutdown(); drop(core); - let restarted = try_experimental_keychain_init(live.path()).expect("restart"); + let restarted = try_keychain_init(live.path()).expect("restart"); assert_eq!(restarted.status().endpoint_id, id); restarted.shutdown(); cleanup_scoped_keychain(live.path()); } Err(VnidropError::SecureStorageUnavailable { .. }) => {} - Err(error) => panic!("unexpected experimental init failure: {error:?}"), + Err(error) => panic!("unexpected protected init failure: {error:?}"), } } @@ -840,6 +840,9 @@ fn apple_public_bindings_omit_raw_secrets_and_generic_mutation() { swift.display() ); } + assert!(!source.contains("initializeWithExperimentalSavedDevices")); + assert!(!source.contains("ExperimentalSavedDeviceCapabilities")); + assert!(!source.contains("experimentalSavedDeviceCapabilities")); assert!( source.contains("initializeWithLimitsAndNetworkConfig"), "Swift bindings must expose standard protected initialization" @@ -865,6 +868,8 @@ fn apple_public_bindings_omit_raw_secrets_and_generic_mutation() { ) .expect("api.rs"); for forbidden in [ + "ExperimentalSavedDeviceCapabilities", + "experimental_saved_device_capabilities", "SecretMaterial", "SecretHandle", "SecureSecretStore", diff --git a/crates/vnidrop/src/tests/platform_contract_linux.rs b/crates/vnidrop/src/tests/platform_contract_linux.rs index ed9c719..1b539fb 100644 --- a/crates/vnidrop/src/tests/platform_contract_linux.rs +++ b/crates/vnidrop/src/tests/platform_contract_linux.rs @@ -546,7 +546,7 @@ fn secret_service_identity_survives_core_restart() { #[cfg(target_os = "linux")] #[test] -fn experimental_secret_service_identity_survives_core_restart_on_linux() { +fn secret_service_identity_survives_core_restart_on_linux() { // Regression: protected init used to call blocking Secret Service on the // Tokio worker that drives `CoreInner::start`, which nested `block_on` and // aborted desktop startup with "Cannot start a runtime from within a runtime". @@ -560,7 +560,7 @@ fn experimental_secret_service_identity_survives_core_restart_on_linux() { CoreLimits::default(), CoreNetworkConfig::default(), ) - .expect("experimental Linux Secret Service core"); + .expect("Linux Secret Service core"); let endpoint_id = core.status().endpoint_id.clone(); assert!(!endpoint_id.is_empty()); assert!(!data_dir.path().join("iroh.secret").exists()); @@ -585,7 +585,7 @@ fn experimental_secret_service_identity_survives_core_restart_on_linux() { { std::thread::sleep(Duration::from_millis(25)); } - Err(error) => panic!("restart experimental Linux core: {error:?}"), + Err(error) => panic!("restart Linux core: {error:?}"), } }; assert_eq!(restarted.status().endpoint_id, endpoint_id); @@ -842,7 +842,7 @@ fn linux_public_bindings_omit_raw_secrets_and_generic_mutation() { } assert!( public_facade.contains("initialize_with_limits_and_network_config"), - "facade must expose experimental saved-device init" + "facade must expose standard protected initialization" ); assert!( !public_facade.contains("fn set_state(") && !public_facade.contains("fn mutate_state("), diff --git a/crates/vnidrop/src/tests/platform_contract_windows.rs b/crates/vnidrop/src/tests/platform_contract_windows.rs index d2a9b1a..5c39662 100644 --- a/crates/vnidrop/src/tests/platform_contract_windows.rs +++ b/crates/vnidrop/src/tests/platform_contract_windows.rs @@ -315,7 +315,7 @@ fn windows_dpapi_identity_survives_core_restart() { #[cfg(target_os = "windows")] #[test] -fn real_windows_dpapi_experimental_init_preserves_identity() { +fn real_windows_dpapi_init_preserves_identity() { let data_dir = tempfile::tempdir().unwrap(); let path = data_dir.path().to_string_lossy().into_owned(); let sink = Arc::new(RecordingSink { @@ -327,7 +327,7 @@ fn real_windows_dpapi_experimental_init_preserves_identity() { CoreLimits::default(), CoreNetworkConfig::default(), ) - .expect("experimental windows core"); + .expect("Windows core"); let first = core.status().endpoint_id.clone(); core.shutdown(); drop(core); @@ -341,7 +341,7 @@ fn real_windows_dpapi_experimental_init_preserves_identity() { CoreLimits::default(), CoreNetworkConfig::default(), ) - .expect("restarted experimental windows core"); + .expect("restarted Windows core"); assert_eq!(restarted.status().endpoint_id, first); restarted.shutdown(); } diff --git a/crates/vnidrop/src/tests/secret.rs b/crates/vnidrop/src/tests/secret.rs deleted file mode 100644 index 20b6bcd..0000000 --- a/crates/vnidrop/src/tests/secret.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; - -use crate::secret::load_or_create_secret; - -#[tokio::test] -async fn persists_with_restricted_permissions() { - let temp = tempfile::tempdir().unwrap(); - let first = load_or_create_secret(temp.path()).await.unwrap(); - let second = load_or_create_secret(temp.path()).await.unwrap(); - assert_eq!(first.to_bytes(), second.to_bytes()); - - #[cfg(unix)] - assert_eq!( - std::fs::metadata(temp.path().join("iroh.secret")) - .unwrap() - .permissions() - .mode() - & 0o777, - 0o600 - ); -} diff --git a/crates/vnidrop/tests/experimental_domain.rs b/crates/vnidrop/tests/saved_device_domain.rs similarity index 97% rename from crates/vnidrop/tests/experimental_domain.rs rename to crates/vnidrop/tests/saved_device_domain.rs index 058cfff..1d64698 100644 --- a/crates/vnidrop/tests/experimental_domain.rs +++ b/crates/vnidrop/tests/saved_device_domain.rs @@ -8,7 +8,7 @@ use vnidrop::{ }; #[test] -fn saved_device_protocols_are_explicitly_experimental_and_versioned() { +fn saved_device_protocols_are_explicitly_versioned() { assert_eq!( saved_device_capabilities(), SavedDeviceCapabilities { @@ -97,7 +97,7 @@ fn targeted_transfer_transitions_are_validated_by_the_domain() { } #[test] -fn experimental_domain_seam_does_not_change_multi_receiver_shares() { +fn saved_device_domain_seam_does_not_change_multi_receiver_shares() { let source_dir = tempfile::tempdir().unwrap(); let first_output = tempfile::tempdir().unwrap(); let second_output = tempfile::tempdir().unwrap(); diff --git a/shared/src/androidHostTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt b/shared/src/androidHostTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt index 68ca47e..377a7a6 100644 --- a/shared/src/androidHostTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt +++ b/shared/src/androidHostTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt @@ -32,6 +32,9 @@ class SavedDeviceCoreContractBindingHygieneTest { "generated binding ${binding.path} must not expose `$token`", ) } + assertFalse(source.contains("initializeWithExperimentalSavedDevices")) + assertFalse(source.contains("ExperimentalSavedDeviceCapabilities")) + assertFalse(source.contains("experimentalSavedDeviceCapabilities")) assertTrue( source.contains("initializeWithLimitsAndNetworkConfig"), diff --git a/shared/src/jvmTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt b/shared/src/jvmTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt index 68ca47e..377a7a6 100644 --- a/shared/src/jvmTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt +++ b/shared/src/jvmTest/kotlin/com/vnidrop/app/core/SavedDeviceCoreContractBindingHygieneTest.kt @@ -32,6 +32,9 @@ class SavedDeviceCoreContractBindingHygieneTest { "generated binding ${binding.path} must not expose `$token`", ) } + assertFalse(source.contains("initializeWithExperimentalSavedDevices")) + assertFalse(source.contains("ExperimentalSavedDeviceCapabilities")) + assertFalse(source.contains("experimentalSavedDeviceCapabilities")) assertTrue( source.contains("initializeWithLimitsAndNetworkConfig"),