@hasna/recordings 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +350 -35
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1922 -77
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1990 -29
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
import Darwin
|
|
2
|
+
import Foundation
|
|
3
|
+
import RecordingsUpdateProtocol
|
|
4
|
+
|
|
5
|
+
enum BrokerStartupRecovery {
|
|
6
|
+
static func recoverInterruptedTransactions() throws {
|
|
7
|
+
guard geteuid() == 0 else { throw InstallRecoveryError.notRoot }
|
|
8
|
+
let rootState = try RootUpdateState()
|
|
9
|
+
try rootState.withExclusiveTransactionLock {
|
|
10
|
+
try recoverLocked(rootState: rootState)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private static func recoverLocked(rootState: RootUpdateState) throws {
|
|
15
|
+
let names = try FileManager.default.contentsOfDirectory(
|
|
16
|
+
atPath: RecordingsUpdateConstants.stateRoot
|
|
17
|
+
).sorted()
|
|
18
|
+
let stateStore = try MonotonicReleaseStateStore()
|
|
19
|
+
for name in names where name.hasPrefix("transaction-") {
|
|
20
|
+
guard let identifier = UUID(uuidString: String(name.dropFirst("transaction-".count))),
|
|
21
|
+
"transaction-" + identifier.uuidString.lowercased() == name
|
|
22
|
+
else {
|
|
23
|
+
throw InstallRecoveryError.invalidTransactionDirectory
|
|
24
|
+
}
|
|
25
|
+
let directory = RecordingsUpdateConstants.stateRoot + "/" + name
|
|
26
|
+
try validateTransactionDirectory(directory)
|
|
27
|
+
let durableJournal = DurableInstallJournal(transactionDirectory: directory)
|
|
28
|
+
let current = try stateStore.currentState()
|
|
29
|
+
guard var journal = try durableJournal.read() else {
|
|
30
|
+
// An unjournaled transaction is inert only when there is no unresolved
|
|
31
|
+
// highest-seen barrier. Never silently prune the only possible evidence
|
|
32
|
+
// for a `seen` state created by an older or interrupted broker.
|
|
33
|
+
guard current?.phase != "seen" else {
|
|
34
|
+
throw InstallRecoveryError.missingJournalForSeenState
|
|
35
|
+
}
|
|
36
|
+
try rootState.removeTransactionDirectory(id: identifier)
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
let terminalPhases: Set<String> = [
|
|
40
|
+
"rolled-back", "committed", "bootstrap-aborted", "bootstrap-committed",
|
|
41
|
+
]
|
|
42
|
+
if !terminalPhases.contains(journal.phase) {
|
|
43
|
+
// A pre-hardening journal without signed minimum-OS evidence may
|
|
44
|
+
// be decoded for terminal cleanup, but it can never resume mutation.
|
|
45
|
+
guard let minimumOSVersion = journal.minimumOSVersion else {
|
|
46
|
+
throw InstallRecoveryError.invalidJournalPhase
|
|
47
|
+
}
|
|
48
|
+
try HostOSVersionPolicy.validate(
|
|
49
|
+
candidateMinimumOSVersion: minimumOSVersion,
|
|
50
|
+
hostProductVersion: try HostOSProductVersionReader.read()
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
if journal.resolvedOperation == .bootstrapCommit {
|
|
54
|
+
try recoverBootstrapCommit(
|
|
55
|
+
journal: &journal,
|
|
56
|
+
current: current,
|
|
57
|
+
durableJournal: durableJournal,
|
|
58
|
+
stateStore: stateStore
|
|
59
|
+
)
|
|
60
|
+
try rootState.removeTransactionDirectory(id: identifier)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if journal.phase == "rolled-back" {
|
|
64
|
+
try requireRollbackTerminalState(journal)
|
|
65
|
+
switch try rolledBackBarrierAction(journal: journal, current: current) {
|
|
66
|
+
case .finalizeAbortThenPrune:
|
|
67
|
+
try stateStore.finalizeRecoveredAbort(journal: journal)
|
|
68
|
+
case .prune:
|
|
69
|
+
break
|
|
70
|
+
case .invalid:
|
|
71
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
72
|
+
}
|
|
73
|
+
try rootState.removeTransactionDirectory(id: identifier)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if journal.phase == "committed" {
|
|
77
|
+
try recoverCommitted(
|
|
78
|
+
journal: journal,
|
|
79
|
+
current: current,
|
|
80
|
+
stateStore: stateStore
|
|
81
|
+
)
|
|
82
|
+
try rootState.removeTransactionDirectory(id: identifier)
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if journal.phase == "prepared" {
|
|
87
|
+
switch try preparedBarrierAction(journal: journal, current: current) {
|
|
88
|
+
case .recover:
|
|
89
|
+
try recoverPreparedWithSeenBarrier(
|
|
90
|
+
journal: &journal,
|
|
91
|
+
durableJournal: durableJournal,
|
|
92
|
+
stateStore: stateStore
|
|
93
|
+
)
|
|
94
|
+
case .discard:
|
|
95
|
+
try discardPreparedBeforeSeen(
|
|
96
|
+
journal: &journal,
|
|
97
|
+
current: current,
|
|
98
|
+
durableJournal: durableJournal
|
|
99
|
+
)
|
|
100
|
+
case .invalid:
|
|
101
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
try requireExactHighestSeen(journal: journal, current: current)
|
|
105
|
+
switch journal.phase {
|
|
106
|
+
case "launch-barrier-pending", "launch-barrier-held":
|
|
107
|
+
try recoverLaunchBarrierAndCommit(
|
|
108
|
+
journal: &journal,
|
|
109
|
+
durableJournal: durableJournal,
|
|
110
|
+
stateStore: stateStore
|
|
111
|
+
)
|
|
112
|
+
case "swap-pending":
|
|
113
|
+
try recoverSwapPending(journal: &journal, durableJournal: durableJournal)
|
|
114
|
+
try releaseCommittedLaunchBarrier(
|
|
115
|
+
journal: &journal,
|
|
116
|
+
durableJournal: durableJournal
|
|
117
|
+
)
|
|
118
|
+
try finishCommitted(
|
|
119
|
+
journal: &journal,
|
|
120
|
+
durableJournal: durableJournal,
|
|
121
|
+
stateStore: stateStore
|
|
122
|
+
)
|
|
123
|
+
case "swapped", "previous-retaining":
|
|
124
|
+
try recoverPreviousRetention(
|
|
125
|
+
journal: &journal,
|
|
126
|
+
durableJournal: durableJournal
|
|
127
|
+
)
|
|
128
|
+
try releaseCommittedLaunchBarrier(
|
|
129
|
+
journal: &journal,
|
|
130
|
+
durableJournal: durableJournal
|
|
131
|
+
)
|
|
132
|
+
try finishCommitted(
|
|
133
|
+
journal: &journal,
|
|
134
|
+
durableJournal: durableJournal,
|
|
135
|
+
stateStore: stateStore
|
|
136
|
+
)
|
|
137
|
+
case "previous-retained", "first-installed", "launch-barrier-releasing":
|
|
138
|
+
try releaseCommittedLaunchBarrier(
|
|
139
|
+
journal: &journal,
|
|
140
|
+
durableJournal: durableJournal
|
|
141
|
+
)
|
|
142
|
+
try finishCommitted(
|
|
143
|
+
journal: &journal,
|
|
144
|
+
durableJournal: durableJournal,
|
|
145
|
+
stateStore: stateStore
|
|
146
|
+
)
|
|
147
|
+
case "launch-barrier-released", "activated":
|
|
148
|
+
try finishCommitted(
|
|
149
|
+
journal: &journal,
|
|
150
|
+
durableJournal: durableJournal,
|
|
151
|
+
stateStore: stateStore
|
|
152
|
+
)
|
|
153
|
+
case "first-install-pending":
|
|
154
|
+
try recoverFirstInstallPending(
|
|
155
|
+
journal: &journal,
|
|
156
|
+
durableJournal: durableJournal
|
|
157
|
+
)
|
|
158
|
+
try releaseCommittedLaunchBarrier(
|
|
159
|
+
journal: &journal,
|
|
160
|
+
durableJournal: durableJournal
|
|
161
|
+
)
|
|
162
|
+
try finishCommitted(
|
|
163
|
+
journal: &journal,
|
|
164
|
+
durableJournal: durableJournal,
|
|
165
|
+
stateStore: stateStore
|
|
166
|
+
)
|
|
167
|
+
case "rollback-started":
|
|
168
|
+
try finishRollback(journal: journal)
|
|
169
|
+
journal.phase = "rolled-back"
|
|
170
|
+
try durableJournal.write(journal)
|
|
171
|
+
try stateStore.finalizeRecoveredAbort(journal: journal)
|
|
172
|
+
default:
|
|
173
|
+
throw InstallRecoveryError.invalidJournalPhase
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
guard journal.phase == "rolled-back" || journal.phase == "committed" else {
|
|
177
|
+
throw InstallRecoveryError.invalidJournalPhase
|
|
178
|
+
}
|
|
179
|
+
try rootState.removeTransactionDirectory(id: identifier)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static func recoverBootstrapCommit(
|
|
184
|
+
journal: inout BrokerInstallJournal,
|
|
185
|
+
current: PersistedReleaseState?,
|
|
186
|
+
durableJournal: DurableInstallJournal,
|
|
187
|
+
stateStore: MonotonicReleaseStateStore
|
|
188
|
+
) throws {
|
|
189
|
+
let journalPhase: BootstrapJournalPhase
|
|
190
|
+
switch journal.phase {
|
|
191
|
+
case "bootstrap-prepared": journalPhase = .prepared
|
|
192
|
+
case "bootstrap-commit-pending": journalPhase = .commitPending
|
|
193
|
+
case "bootstrap-aborted": journalPhase = .aborted
|
|
194
|
+
case "bootstrap-committed": journalPhase = .committed
|
|
195
|
+
default: throw InstallRecoveryError.invalidJournalPhase
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let candidateMatches: Bool
|
|
199
|
+
if current == nil {
|
|
200
|
+
// Before `seen`, the broker has not performed a bootstrap state mutation.
|
|
201
|
+
// The exact root-owned journal may therefore be closed without trusting
|
|
202
|
+
// or re-reading a candidate that an administrator changed meanwhile.
|
|
203
|
+
candidateMatches = true
|
|
204
|
+
} else {
|
|
205
|
+
do {
|
|
206
|
+
candidateMatches = try CanonicalTree.digest(at: journal.applicationPath) ==
|
|
207
|
+
journal.candidateTreeSHA256
|
|
208
|
+
} catch {
|
|
209
|
+
candidateMatches = false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
switch BootstrapRecoveryPolicy.action(
|
|
214
|
+
journalPhase: journalPhase,
|
|
215
|
+
currentPhase: try monotonicBarrierPhase(current),
|
|
216
|
+
exactBinding: exactReleaseBinding(journal: journal, current: current),
|
|
217
|
+
priorMonotonicStateWasAbsent: journal.bootstrapPriorMonotonicState == "absent",
|
|
218
|
+
candidateMatches: candidateMatches
|
|
219
|
+
) {
|
|
220
|
+
case .discardBeforeSeen:
|
|
221
|
+
journal.phase = "bootstrap-aborted"
|
|
222
|
+
try durableJournal.write(journal)
|
|
223
|
+
case .finalizeCommit:
|
|
224
|
+
if journal.phase == "bootstrap-prepared" {
|
|
225
|
+
journal.phase = "bootstrap-commit-pending"
|
|
226
|
+
try durableJournal.write(journal)
|
|
227
|
+
}
|
|
228
|
+
try stateStore.finalizeRecoveredCommit(journal: journal)
|
|
229
|
+
journal.phase = "bootstrap-committed"
|
|
230
|
+
try durableJournal.write(journal)
|
|
231
|
+
case .validateCommitted:
|
|
232
|
+
guard current?.phase == "committed" else {
|
|
233
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
234
|
+
}
|
|
235
|
+
journal.phase = "bootstrap-committed"
|
|
236
|
+
try durableJournal.write(journal)
|
|
237
|
+
case .prune:
|
|
238
|
+
break
|
|
239
|
+
case .invalid:
|
|
240
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private static func recoverPreparedWithSeenBarrier(
|
|
245
|
+
journal: inout BrokerInstallJournal,
|
|
246
|
+
durableJournal: DurableInstallJournal,
|
|
247
|
+
stateStore: MonotonicReleaseStateStore
|
|
248
|
+
) throws {
|
|
249
|
+
journal.phase = "launch-barrier-pending"
|
|
250
|
+
try durableJournal.write(journal)
|
|
251
|
+
try recoverLaunchBarrierAndCommit(
|
|
252
|
+
journal: &journal,
|
|
253
|
+
durableJournal: durableJournal,
|
|
254
|
+
stateStore: stateStore
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private static func recoverLaunchBarrierAndCommit(
|
|
259
|
+
journal: inout BrokerInstallJournal,
|
|
260
|
+
durableJournal: DurableInstallJournal,
|
|
261
|
+
stateStore: MonotonicReleaseStateStore
|
|
262
|
+
) throws {
|
|
263
|
+
let namespace = try applicationNamespace(journal)
|
|
264
|
+
try mapNamespaceMutation { try namespace.engageLaunchBarrier(journal: journal) }
|
|
265
|
+
journal.phase = "launch-barrier-held"
|
|
266
|
+
try durableJournal.write(journal)
|
|
267
|
+
try requireQuiescence()
|
|
268
|
+
if journal.previousTreeSHA256 == nil {
|
|
269
|
+
journal.phase = "first-install-pending"
|
|
270
|
+
try durableJournal.write(journal)
|
|
271
|
+
try recoverFirstInstallPending(journal: &journal, durableJournal: durableJournal)
|
|
272
|
+
} else {
|
|
273
|
+
journal.phase = "swap-pending"
|
|
274
|
+
try durableJournal.write(journal)
|
|
275
|
+
try recoverSwapPending(journal: &journal, durableJournal: durableJournal)
|
|
276
|
+
}
|
|
277
|
+
try releaseCommittedLaunchBarrier(
|
|
278
|
+
journal: &journal,
|
|
279
|
+
durableJournal: durableJournal
|
|
280
|
+
)
|
|
281
|
+
try finishCommitted(
|
|
282
|
+
journal: &journal,
|
|
283
|
+
durableJournal: durableJournal,
|
|
284
|
+
stateStore: stateStore
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private static func recoverSwapPending(
|
|
289
|
+
journal: inout BrokerInstallJournal,
|
|
290
|
+
durableJournal: DurableInstallJournal
|
|
291
|
+
) throws {
|
|
292
|
+
guard let previousDigest = journal.previousTreeSHA256 else {
|
|
293
|
+
throw InstallRecoveryError.invalidJournalPhase
|
|
294
|
+
}
|
|
295
|
+
let namespace = try applicationNamespace(journal)
|
|
296
|
+
switch ActivationRecoveryPolicy.commitAction(
|
|
297
|
+
snapshot: try namespaceSnapshot(journal),
|
|
298
|
+
previousDigest: previousDigest,
|
|
299
|
+
candidateDigest: journal.candidateTreeSHA256
|
|
300
|
+
) {
|
|
301
|
+
case .exchangeCandidateAndLive:
|
|
302
|
+
try requireQuiescence()
|
|
303
|
+
try mapNamespaceMutation { try namespace.exchangeCandidateAndLive() }
|
|
304
|
+
case .retainPrevious, .ready:
|
|
305
|
+
// The exchange or subsequent retain completed before its phase write.
|
|
306
|
+
break
|
|
307
|
+
case .installCandidateExclusively, .invalid:
|
|
308
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
309
|
+
}
|
|
310
|
+
journal.phase = "swapped"
|
|
311
|
+
try durableJournal.write(journal)
|
|
312
|
+
try recoverPreviousRetention(journal: &journal, durableJournal: durableJournal)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private static func recoverPreviousRetention(
|
|
316
|
+
journal: inout BrokerInstallJournal,
|
|
317
|
+
durableJournal: DurableInstallJournal
|
|
318
|
+
) throws {
|
|
319
|
+
guard let previousDigest = journal.previousTreeSHA256 else {
|
|
320
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
321
|
+
}
|
|
322
|
+
let namespace = try applicationNamespace(journal)
|
|
323
|
+
switch ActivationRecoveryPolicy.commitAction(
|
|
324
|
+
snapshot: try namespaceSnapshot(journal),
|
|
325
|
+
previousDigest: previousDigest,
|
|
326
|
+
candidateDigest: journal.candidateTreeSHA256
|
|
327
|
+
) {
|
|
328
|
+
case .retainPrevious:
|
|
329
|
+
journal.phase = "previous-retaining"
|
|
330
|
+
try durableJournal.write(journal)
|
|
331
|
+
try mapNamespaceMutation { try namespace.retainSwappedPreviousExclusively() }
|
|
332
|
+
case .ready:
|
|
333
|
+
// Retain completed before the durable phase update.
|
|
334
|
+
break
|
|
335
|
+
case .exchangeCandidateAndLive, .installCandidateExclusively, .invalid:
|
|
336
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
337
|
+
}
|
|
338
|
+
try requireBarrierHeldCommittedNamespaceState(journal)
|
|
339
|
+
journal.phase = "previous-retained"
|
|
340
|
+
try durableJournal.write(journal)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private static func recoverFirstInstallPending(
|
|
344
|
+
journal: inout BrokerInstallJournal,
|
|
345
|
+
durableJournal: DurableInstallJournal
|
|
346
|
+
) throws {
|
|
347
|
+
guard journal.previousTreeSHA256 == nil else {
|
|
348
|
+
throw InstallRecoveryError.invalidJournalPhase
|
|
349
|
+
}
|
|
350
|
+
let namespace = try applicationNamespace(journal)
|
|
351
|
+
switch ActivationRecoveryPolicy.commitAction(
|
|
352
|
+
snapshot: try namespaceSnapshot(journal),
|
|
353
|
+
previousDigest: nil,
|
|
354
|
+
candidateDigest: journal.candidateTreeSHA256
|
|
355
|
+
) {
|
|
356
|
+
case .installCandidateExclusively:
|
|
357
|
+
try requireQuiescence()
|
|
358
|
+
try mapNamespaceMutation { try namespace.installCandidateExclusively() }
|
|
359
|
+
case .ready:
|
|
360
|
+
// Exclusive install completed before its phase write.
|
|
361
|
+
break
|
|
362
|
+
case .exchangeCandidateAndLive, .retainPrevious, .invalid:
|
|
363
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
364
|
+
}
|
|
365
|
+
try requireBarrierHeldCommittedNamespaceState(journal)
|
|
366
|
+
journal.phase = "first-installed"
|
|
367
|
+
try durableJournal.write(journal)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private static func discardPreparedBeforeSeen(
|
|
371
|
+
journal: inout BrokerInstallJournal,
|
|
372
|
+
current: PersistedReleaseState?,
|
|
373
|
+
durableJournal: DurableInstallJournal
|
|
374
|
+
) throws {
|
|
375
|
+
if let current {
|
|
376
|
+
let exactAborted = current.phase == "aborted" &&
|
|
377
|
+
current.keyEpoch == journal.keyEpoch &&
|
|
378
|
+
current.releaseSequence == journal.releaseSequence &&
|
|
379
|
+
current.releaseID == journal.releaseID &&
|
|
380
|
+
current.envelopePayloadSHA256 == journal.envelopePayloadSHA256
|
|
381
|
+
let olderTerminal = (current.phase == "committed" || current.phase == "aborted") &&
|
|
382
|
+
current.keyEpoch == journal.keyEpoch &&
|
|
383
|
+
current.cohortPackageSHA256 == journal.cohortPackageSHA256 &&
|
|
384
|
+
current.releaseSequence < journal.releaseSequence
|
|
385
|
+
guard exactAborted || olderTerminal else {
|
|
386
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
guard try digestIfPresent(journal.applicationPath) == journal.previousTreeSHA256,
|
|
390
|
+
try digestIfPresent(journal.candidateApplicationPath) == journal.candidateTreeSHA256,
|
|
391
|
+
try digestIfPresent(journal.previousApplicationPath) == nil,
|
|
392
|
+
try digestIfPresent(failedCandidatePath(journal)) == nil
|
|
393
|
+
else {
|
|
394
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
395
|
+
}
|
|
396
|
+
journal.phase = "rolled-back"
|
|
397
|
+
try durableJournal.write(journal)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private static func finishRollback(journal: BrokerInstallJournal) throws {
|
|
401
|
+
let namespace = try applicationNamespace(journal)
|
|
402
|
+
do {
|
|
403
|
+
try AtomicActivator.restoreRollbackTerminal(journal: journal, namespace: namespace)
|
|
404
|
+
try namespace.releaseRolledBackLaunchBarrier(journal: journal)
|
|
405
|
+
try AtomicActivator.requireRollbackTerminalState(journal: journal)
|
|
406
|
+
} catch {
|
|
407
|
+
throw InstallRecoveryError.rollbackFailed
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private static func requireRollbackTerminalState(_ journal: BrokerInstallJournal) throws {
|
|
412
|
+
do {
|
|
413
|
+
try AtomicActivator.requireRollbackTerminalState(journal: journal)
|
|
414
|
+
} catch {
|
|
415
|
+
throw InstallRecoveryError.rollbackFailed
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private static func requireCommittedNamespaceState(_ journal: BrokerInstallJournal) throws {
|
|
420
|
+
do { try AtomicActivator.requireCommittedNamespaceState(journal: journal) }
|
|
421
|
+
catch { throw InstallRecoveryError.ambiguousApplicationState }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private static func requireBarrierHeldCommittedNamespaceState(
|
|
425
|
+
_ journal: BrokerInstallJournal
|
|
426
|
+
) throws {
|
|
427
|
+
let action = ActivationRecoveryPolicy.commitAction(
|
|
428
|
+
snapshot: try namespaceSnapshot(journal),
|
|
429
|
+
previousDigest: journal.previousTreeSHA256,
|
|
430
|
+
candidateDigest: journal.candidateTreeSHA256
|
|
431
|
+
)
|
|
432
|
+
guard action == .ready else {
|
|
433
|
+
throw InstallRecoveryError.ambiguousApplicationState
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static func releaseCommittedLaunchBarrier(
|
|
438
|
+
journal: inout BrokerInstallJournal,
|
|
439
|
+
durableJournal: DurableInstallJournal
|
|
440
|
+
) throws {
|
|
441
|
+
journal.phase = "launch-barrier-releasing"
|
|
442
|
+
try durableJournal.write(journal)
|
|
443
|
+
let namespace = try applicationNamespace(journal)
|
|
444
|
+
try mapNamespaceMutation {
|
|
445
|
+
try namespace.releaseCommittedLaunchBarrier(journal: journal)
|
|
446
|
+
}
|
|
447
|
+
try requireCommittedNamespaceState(journal)
|
|
448
|
+
journal.phase = "launch-barrier-released"
|
|
449
|
+
try durableJournal.write(journal)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private static func validateTransactionDirectory(_ path: String) throws {
|
|
453
|
+
guard DarwinACLValidator.rootOwnedDirectoryAncestryHasNoExtendedACL(to: path) else {
|
|
454
|
+
throw InstallRecoveryError.invalidTransactionDirectory
|
|
455
|
+
}
|
|
456
|
+
var metadata = stat()
|
|
457
|
+
guard lstat(path, &metadata) == 0,
|
|
458
|
+
(metadata.st_mode & S_IFMT) == S_IFDIR,
|
|
459
|
+
metadata.st_uid == 0,
|
|
460
|
+
(metadata.st_mode & 0o077) == 0
|
|
461
|
+
else {
|
|
462
|
+
throw InstallRecoveryError.invalidTransactionDirectory
|
|
463
|
+
}
|
|
464
|
+
let descriptor = Darwin.open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)
|
|
465
|
+
guard descriptor >= 0 else { throw InstallRecoveryError.invalidTransactionDirectory }
|
|
466
|
+
defer { Darwin.close(descriptor) }
|
|
467
|
+
var opened = stat()
|
|
468
|
+
guard fstat(descriptor, &opened) == 0,
|
|
469
|
+
opened.st_dev == metadata.st_dev,
|
|
470
|
+
opened.st_ino == metadata.st_ino,
|
|
471
|
+
DarwinACLValidator.descriptorHasNoExtendedACL(descriptor)
|
|
472
|
+
else {
|
|
473
|
+
throw InstallRecoveryError.invalidTransactionDirectory
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private static func preparedBarrierAction(
|
|
478
|
+
journal: BrokerInstallJournal,
|
|
479
|
+
current: PersistedReleaseState?
|
|
480
|
+
) throws -> PreparedBarrierRecoveryAction {
|
|
481
|
+
ActivationRecoveryPolicy.preparedBarrierAction(
|
|
482
|
+
currentPhase: try monotonicBarrierPhase(current),
|
|
483
|
+
currentSequence: current?.releaseSequence,
|
|
484
|
+
journalSequence: journal.releaseSequence,
|
|
485
|
+
exactBinding: exactReleaseBinding(journal: journal, current: current),
|
|
486
|
+
sameCohort: sameCohort(journal: journal, current: current)
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private static func rolledBackBarrierAction(
|
|
491
|
+
journal: BrokerInstallJournal,
|
|
492
|
+
current: PersistedReleaseState?
|
|
493
|
+
) throws -> RolledBackBarrierRecoveryAction {
|
|
494
|
+
ActivationRecoveryPolicy.rolledBackBarrierAction(
|
|
495
|
+
currentPhase: try monotonicBarrierPhase(current),
|
|
496
|
+
currentSequence: current?.releaseSequence,
|
|
497
|
+
journalSequence: journal.releaseSequence,
|
|
498
|
+
exactBinding: exactReleaseBinding(journal: journal, current: current),
|
|
499
|
+
sameCohort: sameCohort(journal: journal, current: current)
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private static func monotonicBarrierPhase(
|
|
504
|
+
_ current: PersistedReleaseState?
|
|
505
|
+
) throws -> MonotonicBarrierPhase? {
|
|
506
|
+
guard let current else { return nil }
|
|
507
|
+
switch current.phase {
|
|
508
|
+
case "seen": return .seen
|
|
509
|
+
case "aborted": return .aborted
|
|
510
|
+
case "committed": return .committed
|
|
511
|
+
default: throw InstallRecoveryError.monotonicStateMismatch
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private static func exactReleaseBinding(
|
|
516
|
+
journal: BrokerInstallJournal,
|
|
517
|
+
current: PersistedReleaseState?
|
|
518
|
+
) -> Bool {
|
|
519
|
+
guard let current else { return false }
|
|
520
|
+
return current.purpose == journal.expectedPurpose &&
|
|
521
|
+
current.keyEpoch == journal.keyEpoch &&
|
|
522
|
+
current.releaseSequence == journal.releaseSequence &&
|
|
523
|
+
current.releaseID == journal.releaseID &&
|
|
524
|
+
current.cohortPackageSHA256 == journal.cohortPackageSHA256 &&
|
|
525
|
+
current.envelopePayloadSHA256 == journal.envelopePayloadSHA256
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private static func sameCohort(
|
|
529
|
+
journal: BrokerInstallJournal,
|
|
530
|
+
current: PersistedReleaseState?
|
|
531
|
+
) -> Bool {
|
|
532
|
+
guard let current else { return false }
|
|
533
|
+
return current.keyEpoch == journal.keyEpoch &&
|
|
534
|
+
current.cohortPackageSHA256 == journal.cohortPackageSHA256
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private static func recoverCommitted(
|
|
538
|
+
journal: BrokerInstallJournal,
|
|
539
|
+
current: PersistedReleaseState?,
|
|
540
|
+
stateStore: MonotonicReleaseStateStore
|
|
541
|
+
) throws {
|
|
542
|
+
guard let current,
|
|
543
|
+
current.releaseSequence >= journal.releaseSequence
|
|
544
|
+
else {
|
|
545
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
546
|
+
}
|
|
547
|
+
if current.releaseSequence > journal.releaseSequence { return }
|
|
548
|
+
try requireExactReleaseBinding(journal: journal, current: current)
|
|
549
|
+
try requireCommittedNamespaceState(journal)
|
|
550
|
+
try stateStore.finalizeRecoveredCommit(journal: journal)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private static func exactHighestSeen(
|
|
554
|
+
journal: BrokerInstallJournal,
|
|
555
|
+
current: PersistedReleaseState?
|
|
556
|
+
) -> Bool {
|
|
557
|
+
guard let current, current.phase == "seen" else { return false }
|
|
558
|
+
return current.purpose == journal.expectedPurpose &&
|
|
559
|
+
current.keyEpoch == journal.keyEpoch &&
|
|
560
|
+
current.releaseSequence == journal.releaseSequence &&
|
|
561
|
+
current.releaseID == journal.releaseID &&
|
|
562
|
+
current.cohortPackageSHA256 == journal.cohortPackageSHA256 &&
|
|
563
|
+
current.envelopePayloadSHA256 == journal.envelopePayloadSHA256
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private static func requireExactHighestSeen(
|
|
567
|
+
journal: BrokerInstallJournal,
|
|
568
|
+
current: PersistedReleaseState?
|
|
569
|
+
) throws {
|
|
570
|
+
guard exactHighestSeen(journal: journal, current: current) else {
|
|
571
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private static func requireExactReleaseBinding(
|
|
576
|
+
journal: BrokerInstallJournal,
|
|
577
|
+
current: PersistedReleaseState
|
|
578
|
+
) throws {
|
|
579
|
+
guard current.purpose == journal.expectedPurpose,
|
|
580
|
+
current.keyEpoch == journal.keyEpoch,
|
|
581
|
+
current.releaseSequence == journal.releaseSequence,
|
|
582
|
+
current.releaseID == journal.releaseID,
|
|
583
|
+
current.cohortPackageSHA256 == journal.cohortPackageSHA256,
|
|
584
|
+
current.envelopePayloadSHA256 == journal.envelopePayloadSHA256
|
|
585
|
+
else {
|
|
586
|
+
throw InstallRecoveryError.monotonicStateMismatch
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private static func finishCommitted(
|
|
591
|
+
journal: inout BrokerInstallJournal,
|
|
592
|
+
durableJournal: DurableInstallJournal,
|
|
593
|
+
stateStore: MonotonicReleaseStateStore
|
|
594
|
+
) throws {
|
|
595
|
+
try requireCommittedNamespaceState(journal)
|
|
596
|
+
journal.phase = "committed"
|
|
597
|
+
try durableJournal.write(journal)
|
|
598
|
+
try stateStore.finalizeRecoveredCommit(journal: journal)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private static func applicationNamespace(_ journal: BrokerInstallJournal) throws -> ApplicationNamespace {
|
|
602
|
+
do { return try ApplicationNamespace(journal: journal) }
|
|
603
|
+
catch { throw InstallRecoveryError.invalidTransactionDirectory }
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private static func mapNamespaceMutation(_ operation: () throws -> Void) throws {
|
|
607
|
+
do { try operation() }
|
|
608
|
+
catch { throw InstallRecoveryError.namespaceMutationFailed }
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private static func requireQuiescence() throws {
|
|
612
|
+
do {
|
|
613
|
+
try ApplicationProcessQuiescence.requireQuiescence(
|
|
614
|
+
excludingAuthenticatedClientPID: nil
|
|
615
|
+
)
|
|
616
|
+
} catch {
|
|
617
|
+
throw InstallRecoveryError.activationDeferred
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private static func digestIfPresent(_ path: String?) throws -> String? {
|
|
622
|
+
do { return try AtomicActivator.digestIfPresent(path) }
|
|
623
|
+
catch { throw InstallRecoveryError.ambiguousApplicationState }
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private static func namespaceSnapshot(
|
|
627
|
+
_ journal: BrokerInstallJournal
|
|
628
|
+
) throws -> ApplicationNamespaceSnapshot {
|
|
629
|
+
do { return try AtomicActivator.namespaceSnapshot(journal) }
|
|
630
|
+
catch { throw InstallRecoveryError.ambiguousApplicationState }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private static func failedCandidatePath(_ journal: BrokerInstallJournal) -> String {
|
|
634
|
+
AtomicActivator.failedCandidatePath(journal)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
enum InstallRecoveryError: Error, CustomStringConvertible {
|
|
639
|
+
case notRoot
|
|
640
|
+
case invalidTransactionDirectory
|
|
641
|
+
case invalidJournalPhase
|
|
642
|
+
case missingJournalForSeenState
|
|
643
|
+
case monotonicStateMismatch
|
|
644
|
+
case ambiguousApplicationState
|
|
645
|
+
case activationDeferred
|
|
646
|
+
case namespaceMutationFailed
|
|
647
|
+
case rollbackFailed
|
|
648
|
+
|
|
649
|
+
var description: String {
|
|
650
|
+
switch self {
|
|
651
|
+
case .notRoot: "The update broker is not running as root during recovery"
|
|
652
|
+
case .invalidTransactionDirectory: "A protected update transaction directory is unsafe"
|
|
653
|
+
case .invalidJournalPhase: "An install journal has an unsupported recovery phase"
|
|
654
|
+
case .missingJournalForSeenState: "Highest-seen state has no recoverable transaction journal"
|
|
655
|
+
case .monotonicStateMismatch: "An interrupted transaction does not match highest-seen state"
|
|
656
|
+
case .ambiguousApplicationState: "Interrupted recovery found an ambiguous application namespace"
|
|
657
|
+
case .activationDeferred: "Recovery is deferred until live-bundle processes exit"
|
|
658
|
+
case .namespaceMutationFailed: "Recovery could not mutate the application namespace atomically"
|
|
659
|
+
case .rollbackFailed: "Interrupted recovery could not restore the exact previous tree"
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|