From 87e1be4b586dfc1c498a8ac8d371a2145adaf73a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 18 Jul 2026 15:59:11 -0400 Subject: [PATCH] Add the R8 operator lever: V2Primary defaults key routes the app at the migrated v2 store The Swift wrapper constructs the backend's environment from a fixed map, so there was no way to select v2-primary on a real install without editing code (Foundation Process replaces the child environment wholesale, so a GUI-session launchctl setenv never reaches the daemon). `defaults write com.openmessage.app V2Primary -bool true` now adds OPENMESSAGES_V2_PRIMARY=1 to the backend launch environment; deleting the key rolls back to the legacy store on next launch. The backend refuses primary mode when /v2 is missing, so setting the key on an unmigrated install fails loudly rather than silently serving an empty store. The lever off leaves the environment byte-identical to the shipped contract, pinned by a test comparing the full map. --- .../Sources/BackendLauncherCore.swift | 16 ++++++++++--- .../OpenMessage/Sources/BackendManager.swift | 7 +++++- .../BackendLaunchConfigurationTests.swift | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/macos/OpenMessage/Sources/BackendLauncherCore.swift b/macos/OpenMessage/Sources/BackendLauncherCore.swift index 82782e6..6c04a48 100644 --- a/macos/OpenMessage/Sources/BackendLauncherCore.swift +++ b/macos/OpenMessage/Sources/BackendLauncherCore.swift @@ -54,10 +54,10 @@ struct BackendLaunchConfiguration: Equatable, Sendable { dataDirectory: String, port: Int, homeDirectory: String = NSHomeDirectory(), + v2Primary: Bool = false, additionalEnvironment: [String: String] = [:] ) -> BackendLaunchConfiguration { - var environment = additionalEnvironment - environment.merge([ + var canonical = [ "OPENMESSAGES_PORT": String(port), "OPENMESSAGES_DATA_DIR": dataDirectory, "OPENMESSAGES_LOG_LEVEL": "info", @@ -65,7 +65,17 @@ struct BackendLaunchConfiguration: Equatable, Sendable { "OPENMESSAGES_MACOS_NOTIFICATIONS": "0", "HOME": homeDirectory, "PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin", - ]) { _, canonical in canonical } + ] + if v2Primary { + // Operator lever for the R8 cutover: the backend selects the + // migrated v2 store only when this env var is set, and it refuses + // to start in primary mode if /v2 is missing, so setting + // it on an unmigrated install fails loudly rather than silently + // serving an empty store. Rollback = clear the defaults key. + canonical["OPENMESSAGES_V2_PRIMARY"] = "1" + } + var environment = additionalEnvironment + environment.merge(canonical) { _, canonical in canonical } let dataDirectoryURL = URL(fileURLWithPath: dataDirectory, isDirectory: true) return BackendLaunchConfiguration( diff --git a/macos/OpenMessage/Sources/BackendManager.swift b/macos/OpenMessage/Sources/BackendManager.swift index c78badf..4c18164 100644 --- a/macos/OpenMessage/Sources/BackendManager.swift +++ b/macos/OpenMessage/Sources/BackendManager.swift @@ -164,10 +164,15 @@ final class BackendManager: ObservableObject { } let path = binaryPath let dir = dataDir + // R8 operator lever: `defaults write com.openmessage.app V2Primary -bool true` + // routes the backend at the migrated v2 store; deleting the key rolls back + // to the legacy store on next launch (the legacy source is never modified). + let v2Primary = UserDefaults.standard.bool(forKey: "V2Primary") let configuration = BackendLaunchConfiguration.application( executablePath: path, dataDirectory: dir, - port: port + port: port, + v2Primary: v2Primary ) let launcher = BackendLauncherCore(configuration: configuration, processSpawner: processSpawner) self.launcher = launcher diff --git a/macos/OpenMessage/Tests/OpenMessageTests/BackendLaunchConfigurationTests.swift b/macos/OpenMessage/Tests/OpenMessageTests/BackendLaunchConfigurationTests.swift index 8554496..8fc8774 100644 --- a/macos/OpenMessage/Tests/OpenMessageTests/BackendLaunchConfigurationTests.swift +++ b/macos/OpenMessage/Tests/OpenMessageTests/BackendLaunchConfigurationTests.swift @@ -37,6 +37,30 @@ final class BackendLaunchConfigurationTests: XCTestCase { ) } + func testV2PrimaryLeverAddsExactlyOneEnvironmentVariable() { + let base = BackendLaunchConfiguration.application( + executablePath: "/Applications/OpenMessage.app/Contents/Resources/openmessage", + dataDirectory: "/Users/example/Library/Application Support/OpenMessage", + port: 8123, + homeDirectory: "/Users/example" + ) + let primary = BackendLaunchConfiguration.application( + executablePath: "/Applications/OpenMessage.app/Contents/Resources/openmessage", + dataDirectory: "/Users/example/Library/Application Support/OpenMessage", + port: 8123, + homeDirectory: "/Users/example", + v2Primary: true + ) + + // Default stays byte-identical to the shipped contract: the lever off + // must not perturb the environment in any way. + XCTAssertNil(base.environment["OPENMESSAGES_V2_PRIMARY"]) + XCTAssertEqual(primary.environment["OPENMESSAGES_V2_PRIMARY"], "1") + var expected = base.environment + expected["OPENMESSAGES_V2_PRIMARY"] = "1" + XCTAssertEqual(primary.environment, expected) + } + func testAdditionalEnvironmentIsExplicitWithoutOverridingCanonicalFields() { let configuration = BackendLaunchConfiguration.application( executablePath: "/tmp/openmessage",