codex-overleaf-link 2.5.0 → 2.5.1

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.
Files changed (168) hide show
  1. package/README.md +38 -26
  2. package/README.zh-CN.md +39 -27
  3. package/extension/bootstrap/background.js +52 -9
  4. package/extension/bootstrap/manifest.template.json +1 -1
  5. package/extension/bootstrap/popup.html +1 -0
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/updateStatus.js +1 -0
  8. package/extension/src/background.js +206 -53
  9. package/extension/src/backgroundBootstrapMigration.js +181 -0
  10. package/extension/src/backgroundSharedSessions.js +74 -14
  11. package/extension/src/backgroundUpdateCoordinator.js +118 -21
  12. package/extension/src/backgroundUpdateSafety.js +173 -0
  13. package/extension/src/content/activeTurnControl.js +8 -2
  14. package/extension/src/content/applyResultFormatters.js +46 -73
  15. package/extension/src/content/assetTransferBroker.js +72 -14
  16. package/extension/src/content/composerAttachments.js +93 -45
  17. package/extension/src/content/composerPanel.js +18 -0
  18. package/extension/src/content/contentRuntime.js +661 -676
  19. package/extension/src/content/diagnosticsController.js +122 -96
  20. package/extension/src/content/diagnosticsExport.js +19 -5
  21. package/extension/src/content/diagnosticsPanel.js +30 -0
  22. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  23. package/extension/src/content/generated/content.bundle.js +6879 -2786
  24. package/extension/src/content/generated/content.bundle.meta.json +11 -3
  25. package/extension/src/content/globalPreferencesController.js +1 -1
  26. package/extension/src/content/legacyGlobalRegistry.js +6 -0
  27. package/extension/src/content/localSkillsPanel.js +102 -12
  28. package/extension/src/content/markdownText.js +5 -6
  29. package/extension/src/content/modelPicker.js +7 -4
  30. package/extension/src/content/nativeCompatibilityController.js +45 -20
  31. package/extension/src/content/otWarmMirror.js +4 -5
  32. package/extension/src/content/otWarmMirrorController.js +17 -20
  33. package/extension/src/content/pageBridgeClient.js +15 -2
  34. package/extension/src/content/panelMaintenance.js +40 -12
  35. package/extension/src/content/panelRenderer.js +6 -0
  36. package/extension/src/content/projectProviderSelection.js +13 -4
  37. package/extension/src/content/projectSessionCleanup.js +109 -62
  38. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  39. package/extension/src/content/providerSettingsCoordinator.js +46 -8
  40. package/extension/src/content/providerSettingsDialog.js +71 -13
  41. package/extension/src/content/recentProjects.js +11 -5
  42. package/extension/src/content/runActivitySummary.js +40 -6
  43. package/extension/src/content/runCompletionMeta.js +344 -0
  44. package/extension/src/content/runController.js +37 -14
  45. package/extension/src/content/runFailureNotice.js +21 -12
  46. package/extension/src/content/runGuidanceController.js +72 -27
  47. package/extension/src/content/runGuidanceView.js +59 -10
  48. package/extension/src/content/runQueueScheduler.js +148 -42
  49. package/extension/src/content/runRecoveryActions.js +146 -0
  50. package/extension/src/content/runResultActions.js +8 -125
  51. package/extension/src/content/runReviewController.js +574 -0
  52. package/extension/src/content/runScrollLayout.js +21 -2
  53. package/extension/src/content/runTimelineView.js +146 -153
  54. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  55. package/extension/src/content/selectionContextView.js +1 -1
  56. package/extension/src/content/sensitivePreflight.js +140 -0
  57. package/extension/src/content/sessionForkController.js +80 -17
  58. package/extension/src/content/sessionManager.js +1 -1
  59. package/extension/src/content/settingsPanel.js +7 -3
  60. package/extension/src/content/settingsWorkbench.js +5 -0
  61. package/extension/src/content/sharedSessionViewSync.js +45 -8
  62. package/extension/src/content/updateIdle.js +4 -1
  63. package/extension/src/content/updateNotice.js +60 -8
  64. package/extension/src/content/updateSafetyReceiver.js +58 -0
  65. package/extension/src/content/writebackController.js +53 -4
  66. package/extension/src/content/writebackOrchestrator.js +82 -73
  67. package/extension/src/content/writingStyleSettings.js +35 -11
  68. package/extension/src/page/binaryAssetUploader.js +136 -14
  69. package/extension/src/page/fileMover.js +256 -0
  70. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  71. package/extension/src/page/referenceProjects.js +1 -0
  72. package/extension/src/page/reviewContext.js +113 -0
  73. package/extension/src/page/saveState.js +6 -4
  74. package/extension/src/page/snapshotRouter.js +84 -39
  75. package/extension/src/page/textFileCreator.js +105 -25
  76. package/extension/src/page/trackedChangeCapture.js +12 -2
  77. package/extension/src/page/trackedChangeOwnership.js +19 -1
  78. package/extension/src/page/trackedChangeReplay.js +41 -21
  79. package/extension/src/page/trackedChangesLifecycle.js +217 -199
  80. package/extension/src/page/treeOperations.js +7 -10
  81. package/extension/src/page/updateSafetyProbe.js +41 -0
  82. package/extension/src/page/writebackReceiptJournal.js +2 -0
  83. package/extension/src/page/writebackRouter.js +80 -79
  84. package/extension/src/pageBridge.js +19 -3
  85. package/extension/src/popup.js +62 -10
  86. package/extension/src/shared/agentTranscript.js +23 -0
  87. package/extension/src/shared/auditRecords.js +2 -0
  88. package/extension/src/shared/compatibility.js +22 -6
  89. package/extension/src/shared/compileAdapter.js +79 -57
  90. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  91. package/extension/src/shared/directoryCleanupProof.js +43 -0
  92. package/extension/src/shared/failureReasons.js +309 -3
  93. package/extension/src/shared/globalPreferences.js +30 -13
  94. package/extension/src/shared/governanceRules.js +4 -37
  95. package/extension/src/shared/i18n.js +298 -2
  96. package/extension/src/shared/managedUpdateProjection.js +2 -2
  97. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  98. package/extension/src/shared/pageRpcContract.js +5 -4
  99. package/extension/src/shared/projectFiles.js +21 -44
  100. package/extension/src/shared/providerProfiles.js +5 -3
  101. package/extension/src/shared/runInputQueue.js +48 -5
  102. package/extension/src/shared/selectionContext.js +12 -3
  103. package/extension/src/shared/sessionState.js +72 -27
  104. package/extension/src/shared/settlementFacts.js +10 -2
  105. package/extension/src/shared/sharedSessionBridge.js +37 -14
  106. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  107. package/extension/src/shared/staleGuard.js +118 -13
  108. package/extension/src/shared/storageDb.js +58 -26
  109. package/extension/src/shared/storageMigration.js +71 -111
  110. package/extension/src/shared/storageRunActions.js +209 -26
  111. package/extension/src/shared/subagentActivityModel.js +1 -1
  112. package/extension/src/shared/undoOperations.js +136 -13
  113. package/extension/src/shared/updateConsent.js +6 -4
  114. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  115. package/extension/src/shared/writebackIntent.js +16 -0
  116. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  117. package/extension/src/shared/writebackSettlement.js +38 -38
  118. package/extension/styles/panel.css +19 -5
  119. package/native-host/src/anthropicBridgeRequest.js +4 -2
  120. package/native-host/src/anthropicBridgeResponse.js +6 -2
  121. package/native-host/src/anthropicMessagesBridge.js +24 -16
  122. package/native-host/src/bootstrapMigration.js +296 -0
  123. package/native-host/src/bootstrapMigrationContract.js +101 -0
  124. package/native-host/src/bootstrapUpdate.js +233 -0
  125. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  126. package/native-host/src/chatBridgeResponse.js +10 -0
  127. package/native-host/src/chatCompletionsBridge.js +21 -15
  128. package/native-host/src/codexConfigPaths.js +106 -0
  129. package/native-host/src/codexHome.js +66 -73
  130. package/native-host/src/codexHomeScope.js +124 -0
  131. package/native-host/src/codexInheritedAuth.js +90 -0
  132. package/native-host/src/codexLocalSkills.js +301 -0
  133. package/native-host/src/codexPromptAssembly.js +31 -12
  134. package/native-host/src/codexProviderTest.js +24 -26
  135. package/native-host/src/codexSessionRunner.js +110 -183
  136. package/native-host/src/codexSessionTiming.js +18 -8
  137. package/native-host/src/codexSkillIsolation.js +170 -0
  138. package/native-host/src/codexThreadFork.js +19 -3
  139. package/native-host/src/index.js +19 -7
  140. package/native-host/src/managedInstall.js +110 -35
  141. package/native-host/src/managedInstallRecovery.js +140 -0
  142. package/native-host/src/managedRuntimeEvidence.js +57 -0
  143. package/native-host/src/mirrorWorkspace.js +92 -22
  144. package/native-host/src/nativeDoctor.js +15 -0
  145. package/native-host/src/nativeHelperLifecycle.js +21 -0
  146. package/native-host/src/nativeHostRegistration.js +33 -0
  147. package/native-host/src/nativeRunLifecycle.js +71 -0
  148. package/native-host/src/processStopBarrier.js +42 -7
  149. package/native-host/src/projectPathPlatform.js +30 -0
  150. package/native-host/src/providerHttp.js +80 -0
  151. package/native-host/src/providerProfile.js +4 -2
  152. package/native-host/src/providerReasoning.js +2 -7
  153. package/native-host/src/providerRuntime.js +1 -0
  154. package/native-host/src/providerStore.js +72 -14
  155. package/native-host/src/responsesPassthroughBridge.js +266 -52
  156. package/native-host/src/runtimeInstaller.js +21 -1
  157. package/native-host/src/selectionScope.js +36 -8
  158. package/native-host/src/subagentAdmission.js +26 -0
  159. package/native-host/src/subagentBroker.js +79 -66
  160. package/native-host/src/subagentTelemetry.js +19 -10
  161. package/native-host/src/subagentWorkspacePath.js +5 -2
  162. package/native-host/src/taskRunnerRuntime.js +77 -54
  163. package/native-host/src/tomlStatements.js +140 -0
  164. package/native-host/src/updateManager.js +297 -39
  165. package/native-host/src/writingStyleRuntime.js +57 -29
  166. package/native-host/src/writingStyleStore.js +73 -0
  167. package/package.json +1 -1
  168. package/scripts/install-managed.mjs +1 -0
@@ -14,6 +14,8 @@ const {
14
14
  const { extractVerifiedUpdateBundle } = require('./updateArchive');
15
15
  const { requestRelease } = require('./releaseTransport');
16
16
  const updateNetwork = require('./updateNetworkControl');
17
+ const { readManagedRuntimeEvidence, sameRuntimeEvidence } = require('./managedRuntimeEvidence');
18
+ const BootstrapUpdate = require('./bootstrapUpdate');
17
19
  const RUNNING_VERSION = require('../../package.json').version;
18
20
 
19
21
  const GITHUB_LATEST_URL = 'https://github.com/Ghqqqq/codex-overleaf-link/releases/latest';
@@ -30,9 +32,12 @@ const AUTHORIZATION_TTL_MS = 2 * 60 * 60 * 1000;
30
32
  const MUTATION_LOCK_WAIT_MS = 10 * 1000;
31
33
  const MUTATION_LOCK_STALE_MS = 15 * 60 * 1000;
32
34
  const ACTIVATION_DELAY_MS = 125;
35
+ const APPLYING_TRANSACTION_STATES = new Set(['activation_pending', 'applying', 'awaiting_health',
36
+ ...BootstrapUpdate.BOOTSTRAP_STATES]);
33
37
  const CONSENT_MIGRATION_TARGET_VERSION = '1.9.4';
34
38
  const UPDATE_METHODS = new Set([
35
39
  'update.status',
40
+ 'update.bootstrap',
36
41
  'update.check',
37
42
  'update.authorize',
38
43
  'update.stage',
@@ -77,7 +82,14 @@ async function handleUpdateRequest(request, options = {}) {
77
82
  }
78
83
  switch (request.method) {
79
84
  case 'update.status':
80
- return okResponse(request.id, readStatusWithoutWaiting(context));
85
+ return okResponse(request.id, readStatusWithoutWaiting(context, request.params || {}));
86
+ case 'update.bootstrap':
87
+ return okResponse(request.id, await withUpdateMutex(context, () => {
88
+ const gate = getApplyGate(options);
89
+ if (!gate.idle) throw updateError('update_native_busy', 'Native host is still processing a Codex task.');
90
+ return BootstrapUpdate.advanceBootstrapMigration(context, readJournal(context),
91
+ request.params || {}, bootstrapDependencies(context), options.bootstrapMigration || {});
92
+ }));
81
93
  case 'update.check':
82
94
  return okResponse(request.id, await runNetworkUpdate(context, request, options,
83
95
  networkOptions => checkForUpdate(context, request.params || {}, networkOptions)));
@@ -112,7 +124,9 @@ async function handleUpdateRequest(request, options = {}) {
112
124
  throw updateError('unknown_update_method', 'Unknown managed update method.');
113
125
  }
114
126
  } catch (error) {
115
- return errorResponse(request.id, safeErrorCode(error), safeErrorMessage(error));
127
+ const code = safeErrorCode(error);
128
+ return errorResponse(request.id, request.method === 'update.bootstrap' && code === 'update_internal_error'
129
+ ? 'update_bootstrap_failed' : code, safeErrorMessage(error));
116
130
  }
117
131
  }
118
132
 
@@ -127,7 +141,7 @@ async function runNetworkUpdate(context, request, options, action) {
127
141
 
128
142
  async function cancelNetworkUpdate(context, params) {
129
143
  const journal = readJournal(context);
130
- if (journal && ['activation_pending', 'applying', 'awaiting_health'].includes(journal.state)) {
144
+ if (journal && (APPLYING_TRANSACTION_STATES.has(journal.state) || journal.sourceActivationPending)) {
131
145
  throw updateError('update_revoke_too_late', 'The update is already being installed.');
132
146
  }
133
147
  return updateNetwork.cancel(context, params.operationId);
@@ -185,9 +199,16 @@ async function acquireUpdateFileLock(context) {
185
199
 
186
200
  function removeStaleUpdateFileLock(lockPath) {
187
201
  const owner = readJsonSafe(lockPath, null);
188
- const expired = !owner || Number(owner.expiresAt || 0) <= Date.now();
189
- const abandoned = Number.isInteger(Number(owner?.pid)) && !isProcessAlive(Number(owner.pid));
190
- if (!expired && !abandoned) return false;
202
+ const pid = Number(owner?.pid);
203
+ if (Number.isInteger(pid) && pid > 0) {
204
+ // Expiry is not exit evidence. A slow filesystem must not let another
205
+ // process steal a live install/apply lock.
206
+ if (isProcessAlive(pid)) return false;
207
+ } else {
208
+ // The creator may be between exclusive open and writing its owner record.
209
+ try { if (Date.now() - fs.statSync(lockPath).mtimeMs < MUTATION_LOCK_STALE_MS) return false; }
210
+ catch (_error) { return false; }
211
+ }
191
212
  try {
192
213
  fs.rmSync(lockPath, { force: true });
193
214
  return true;
@@ -212,6 +233,46 @@ function isProcessAlive(pid) {
212
233
  }
213
234
  }
214
235
 
236
+ function bootstrapDependencies(context) {
237
+ return {
238
+ runningVersion: RUNNING_VERSION,
239
+ verifyManifest: verifySignedReleaseManifest,
240
+ verifyArchive: verifyStagedArchive,
241
+ writeJournal: value => writeJournal(context, value),
242
+ assertAuthorization(journal) {
243
+ const authorization = requireAuthorization(context, journal.targetVersion, ['bound'], { activeVersion: journal.sourceVersion });
244
+ if (authorization.id !== journal.authorizationId || authorization.transactionId !== journal.id) {
245
+ throw updateError('update_consent_mismatch', 'Bootstrap update does not match the authorized transaction.');
246
+ }
247
+ }
248
+ };
249
+ }
250
+
251
+ async function recoverBootstrapTransaction(options, transactionId, token) {
252
+ const context = getManagedContext(options);
253
+ if (!context.managed) throw updateError('update_not_managed', 'Managed installation markers are unavailable.');
254
+ return withUpdateMutex(context, () => {
255
+ const journal = readJournal(context);
256
+ if (journal?.id !== transactionId || journal.bootstrapMigration?.watchdogToken !== token) return { state: 'obsolete' };
257
+ if (journal.sourceActivationPending && journal.state === 'rolled_back') {
258
+ finishSourceActivation(context, journal);
259
+ return { state: 'rolled_back', version: journal.sourceVersion };
260
+ }
261
+ if (!BootstrapUpdate.bootstrapExpired(journal)) return { state: journal.state };
262
+ return rollbackUpdate(context, { transactionId, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
263
+ });
264
+ }
265
+
266
+ function assertBootstrapBusinessAvailable(request, options = {}) {
267
+ if (isUpdateMethod(request?.method) || ['bridge.ping', 'codex.cancel'].includes(request?.method)) return;
268
+ const context = getManagedContext(options);
269
+ if (!context.managed) return;
270
+ const journal = readJournal(context);
271
+ if (BootstrapUpdate.migrationHint(context, journal) || journal?.sourceActivationPending) {
272
+ throw updateError('update_applying', 'The managed update is still restoring or loading its startup files.');
273
+ }
274
+ }
275
+
215
276
  function delay(milliseconds) {
216
277
  return new Promise(resolve => setTimeout(resolve, milliseconds));
217
278
  }
@@ -238,7 +299,8 @@ async function checkForUpdate(context, params = {}, options = {}) {
238
299
  });
239
300
  if (releaseResponse.status === 304) {
240
301
  const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
241
- if (candidate?.manifestBase64 && candidate?.signatureBase64) {
302
+ if (candidate?.manifestBase64 && candidate?.signatureBase64
303
+ && params.etag && candidate.etag === params.etag && candidate.currentVersion === currentVersion) {
242
304
  try {
243
305
  const manifest = verifySignedReleaseManifest(
244
306
  Buffer.from(candidate.manifestBase64, 'base64'),
@@ -258,7 +320,7 @@ async function checkForUpdate(context, params = {}, options = {}) {
258
320
  // Invalid cached candidates are treated as cache misses.
259
321
  }
260
322
  }
261
- const manual = readManualCandidate(context, currentVersion);
323
+ const manual = readManualCandidate(context, currentVersion, params.etag);
262
324
  if (manual) return { ...manual, etag: params.etag || manual.etag || '', cached: true };
263
325
  return { managed: true, available: false, reason: 'not_modified', currentVersion, etag: params.etag || '' };
264
326
  }
@@ -270,6 +332,10 @@ async function checkForUpdate(context, params = {}, options = {}) {
270
332
  throw updateError('update_github_response_invalid', 'GitHub latest release did not resolve to a stable semantic-version tag.');
271
333
  }
272
334
  const latestVersion = releaseTag.slice(1);
335
+ // A successful latest response replaces the previous discovery result,
336
+ // including when a release was withdrawn or is now in cooldown.
337
+ fs.rmSync(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), { force: true });
338
+ fs.rmSync(path.join(context.updatesRoot, CANDIDATE_FILE), { force: true });
273
339
  if (!isNewerStableVersion(latestVersion, currentVersion)) {
274
340
  const activeMarker = readJsonSafe(path.join(context.extensionRoot, EXTENSION_MARKER), null);
275
341
  const equalVersionPrerelease = compareSemver(latestVersion, currentVersion) === 0
@@ -287,7 +353,8 @@ async function checkForUpdate(context, params = {}, options = {}) {
287
353
  }
288
354
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
289
355
  if (Number(cooldowns[latestVersion]?.until || 0) > Date.now()) {
290
- return { managed: true, available: false, reason: 'cooldown', currentVersion, latestVersion, etag: releaseResponse.headers.get('etag') || '' };
356
+ return { managed: true, available: false, reason: 'cooldown', currentVersion, latestVersion,
357
+ cooldownUntil: Number(cooldowns[latestVersion].until), etag: releaseResponse.headers.get('etag') || '' };
291
358
  }
292
359
 
293
360
  const manifestBytes = await fetchReleaseAsset(
@@ -318,7 +385,12 @@ async function checkForUpdate(context, params = {}, options = {}) {
318
385
  manualInstall: manifest.manualInstall?.reason ? { reason: manifest.manualInstall.reason } : {},
319
386
  etag: releaseResponse.headers.get('etag') || ''
320
387
  };
321
- atomicWriteJson(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), { ...manual, checkedAt: new Date().toISOString() });
388
+ atomicWriteJson(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), {
389
+ ...manual,
390
+ checkedAt: new Date().toISOString(),
391
+ manifestBase64: manifestBytes.toString('base64'),
392
+ signatureBase64: signatureBytes.toString('base64')
393
+ });
322
394
  return manual;
323
395
  }
324
396
  const candidate = {
@@ -334,13 +406,29 @@ async function checkForUpdate(context, params = {}, options = {}) {
334
406
  return { managed: true, available: true, currentVersion, latestVersion, etag: candidate.etag };
335
407
  }
336
408
 
337
- function readManualCandidate(context, currentVersion) {
409
+ function readManualCandidate(context, currentVersion, etag) {
338
410
  const manual = readJsonSafe(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), null);
339
- if (manual?.reason !== 'manual_install_required' || !parseSemver(manual.latestVersion)
340
- || !isNewerStableVersion(manual.latestVersion, currentVersion)
341
- || manual.requiredBootstrapProtocol === BOOTSTRAP_PROTOCOL) return null;
342
- const { checkedAt: _checkedAt, ...result } = manual;
343
- return { ...result, currentVersion };
411
+ if (!etag || manual?.etag !== etag || manual.currentVersion !== currentVersion
412
+ || !manual.manifestBase64 || !manual.signatureBase64) return null;
413
+ try {
414
+ const manifest = verifySignedReleaseManifest(
415
+ Buffer.from(manual.manifestBase64, 'base64'),
416
+ Buffer.from(manual.signatureBase64, 'base64'),
417
+ { allowBootstrapMismatch: true }
418
+ );
419
+ if (!isNewerStableVersion(manifest.version, currentVersion)
420
+ || manifest.bootstrapProtocol === BOOTSTRAP_PROTOCOL) return null;
421
+ return {
422
+ managed: true, available: false, reason: 'manual_install_required',
423
+ currentVersion, latestVersion: manifest.version,
424
+ installedBootstrapProtocol: BOOTSTRAP_PROTOCOL,
425
+ requiredBootstrapProtocol: manifest.bootstrapProtocol,
426
+ manualInstall: manifest.manualInstall?.reason ? { reason: manifest.manualInstall.reason } : {},
427
+ etag
428
+ };
429
+ } catch (_error) {
430
+ return null;
431
+ }
344
432
  }
345
433
 
346
434
  function readAuthorizedCandidate(context, currentVersion) {
@@ -381,6 +469,10 @@ function buildReleaseAssetUrl(tag, assetName) {
381
469
  }
382
470
 
383
471
  function authorizeUpdate(context, params = {}) {
472
+ const activeJournal = readJournal(context);
473
+ if (BootstrapUpdate.migrationHint(context, activeJournal) || activeJournal?.sourceActivationPending) {
474
+ throw updateError('update_transaction_in_progress', 'The current Bootstrap update must finish before authorizing another update.');
475
+ }
384
476
  const authorizationId = String(params.authorizationId || '');
385
477
  const targetVersion = String(params.targetVersion || '');
386
478
  const requestedCurrentVersion = String(params.currentVersion || '');
@@ -428,6 +520,10 @@ function authorizeUpdate(context, params = {}) {
428
520
  }
429
521
 
430
522
  async function stageCandidate(context, options = {}) {
523
+ const activeJournal = readJournal(context);
524
+ if (BootstrapUpdate.migrationHint(context, activeJournal) || activeJournal?.sourceActivationPending) {
525
+ throw updateError('update_transaction_in_progress', 'The current Bootstrap update must finish before staging another update.');
526
+ }
431
527
  const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
432
528
  if (!candidate) {
433
529
  throw updateError('update_candidate_missing', 'Check for an update before staging it.');
@@ -467,7 +563,7 @@ async function stageCandidate(context, options = {}) {
467
563
  );
468
564
  }
469
565
  }
470
- if (existingJournal && ['activation_pending', 'applying', 'awaiting_health'].includes(existingJournal.state)) {
566
+ if (existingJournal && APPLYING_TRANSACTION_STATES.has(existingJournal.state)) {
471
567
  throw updateError('update_transaction_in_progress', 'A managed update transaction is already being applied.');
472
568
  }
473
569
  if (authorization.state === 'bound') {
@@ -533,6 +629,7 @@ function getApplyGate(options = {}) {
533
629
  const blockers = [];
534
630
  if (Number(state.projectLocks || 0) > 0) blockers.push('native_project_locked');
535
631
  if (Number(state.runControllers || 0) > 0) blockers.push('native_run_active');
632
+ if (Number(state.processStopBarriers || 0) > 0) blockers.push('native_process_stop_unconfirmed');
536
633
  return { idle: blockers.length === 0, blockers, workState: state };
537
634
  }
538
635
 
@@ -577,7 +674,8 @@ function scheduleStagedActivation(context, params = {}, options = {}) {
577
674
 
578
675
  function recordScheduledActivationFailure(context, pending, error) {
579
676
  const current = readJournal(context);
580
- if (!current || current.id !== pending.id || ['awaiting_health', 'rolled_back'].includes(current.state)) return;
677
+ if (!current || current.id !== pending.id || ['awaiting_health', 'rolled_back', 'committed',
678
+ ...BootstrapUpdate.BOOTSTRAP_STATES].includes(current.state)) return;
581
679
  writeJournal(context, {
582
680
  ...current,
583
681
  state: 'rolled_back',
@@ -687,6 +785,7 @@ function confirmUpdate(context, params = {}) {
687
785
  if (params.transactionId !== journal.id || params.extensionVersion !== journal.targetVersion || params.nativeVersion !== journal.targetVersion) {
688
786
  throw updateError('update_health_version_mismatch', 'Extension and native host did not confirm the same target version.');
689
787
  }
788
+ BootstrapUpdate.assertBootstrapConfirmation(context, journal, params);
690
789
  if (!isLegacyInboundConsentMigration(journal)) {
691
790
  const authorization = requireAuthorization(context, journal.targetVersion, ['bound'], {
692
791
  activeVersion: journal.sourceVersion
@@ -699,9 +798,15 @@ function confirmUpdate(context, params = {}) {
699
798
  if (readVersionPointer(context.nativeRoot, 'active-version') !== journal.targetVersion || manifest?.version !== journal.targetVersion) {
700
799
  throw updateError('update_health_version_mismatch', 'Managed files do not match the confirmed target version.');
701
800
  }
801
+ let committedEvidence;
802
+ try { committedEvidence = readManagedRuntimeEvidence(context, journal.targetVersion); }
803
+ catch (cause) {
804
+ throw updateError('update_health_failed', 'The installed runtime files did not pass the update health check.', { cause });
805
+ }
702
806
  writeJournal(context, {
703
807
  ...journal,
704
808
  state: 'committed',
809
+ committedEvidence,
705
810
  confirmedAt: new Date().toISOString(),
706
811
  updatedAt: new Date().toISOString()
707
812
  });
@@ -713,35 +818,53 @@ function confirmUpdate(context, params = {}) {
713
818
  }
714
819
 
715
820
  function rollbackUpdate(context, params = {}) {
716
- const journal = readJournal(context);
821
+ let journal = readJournal(context);
717
822
  if (params.transactionId && journal?.id !== params.transactionId) {
718
823
  throw updateError('update_transaction_mismatch', 'Rollback transaction identity changed.');
719
824
  }
720
825
  if (params.expectedState && journal?.state !== params.expectedState) {
721
826
  throw updateError('update_transaction_state_changed', 'The update state changed before rollback.');
722
827
  }
723
- if (!journal || !['applying', 'awaiting_health', 'committed'].includes(journal.state)) {
828
+ journal = BootstrapUpdate.bindBootstrapMigration(context, journal, bootstrapDependencies(context));
829
+ if (!journal || !['applying', 'awaiting_health', 'committed', 'bootstrap_pending', 'bootstrap_rolling_back'].includes(journal.state)) {
724
830
  throw updateError('update_rollback_unavailable', 'No applied update is available for rollback.');
725
831
  }
726
- rollbackFiles(context, journal);
832
+ if (journal.bootstrapMigration && !params.transactionId && journal.bootstrapMigration.phase !== 'pending') {
833
+ throw updateError('update_transaction_mismatch', 'Bootstrap rollback must identify its update transaction.');
834
+ }
835
+ const migration = Boolean(journal.bootstrapMigration);
836
+ if (migration) writeJournal(context, { ...journal, state: 'bootstrap_rolling_back' });
837
+ const rollbackEvidence = rollbackFiles(context, journal, { deferSourceActivation: migration });
727
838
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
728
839
  cooldowns[journal.targetVersion] = {
729
840
  until: Date.now() + 24 * 60 * 60 * 1000,
730
841
  reasonCode: normalizeReasonCode(params.reasonCode)
731
842
  };
732
843
  atomicWriteJson(path.join(context.updatesRoot, COOLDOWN_FILE), cooldowns);
733
- writeJournal(context, {
844
+ const restoredJournal = {
734
845
  ...journal,
735
846
  state: 'rolled_back',
736
847
  reasonCode: normalizeReasonCode(params.reasonCode),
737
848
  rolledBackAt: new Date().toISOString(),
738
849
  updatedAt: new Date().toISOString(),
739
- previousManifest: undefined
740
- });
850
+ previousManifest: undefined,
851
+ rollbackEvidence,
852
+ ...(migration ? { bootstrapMigration: { ...journal.bootstrapMigration, phase: 'restored',
853
+ sourceUnchanged: rollbackEvidence.bootstrapSourceUnchanged === true },
854
+ sourceActivationPending: true } : {})
855
+ };
856
+ writeJournal(context, restoredJournal);
857
+ // The old launcher must not select a source process that cannot understand
858
+ // our recovery record until every file and the durable terminal record exist.
859
+ if (migration) {
860
+ settleAuthorization(context, journal.authorizationId, 'revoked');
861
+ atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
862
+ writeJournal(context, { ...restoredJournal, sourceActivationPending: false });
863
+ }
741
864
  settleAuthorization(context, journal.authorizationId, 'revoked');
742
865
  cleanupStage(journal.stageRoot);
743
866
  cleanupOrphanStageRoots(context.updatesRoot);
744
- return { version: journal.sourceVersion, state: 'rolled_back' };
867
+ return { transactionId: journal.id, version: journal.sourceVersion, state: 'rolled_back', restoredPair: rollbackEvidence };
745
868
  }
746
869
 
747
870
  function revokeUpdate(context, params = {}) {
@@ -753,7 +876,7 @@ function revokeUpdate(context, params = {}) {
753
876
  }
754
877
  const journal = readJournal(context);
755
878
  if (journal?.targetVersion === targetVersion &&
756
- ['activation_pending', 'applying', 'awaiting_health', 'committed'].includes(journal.state)) {
879
+ (APPLYING_TRANSACTION_STATES.has(journal.state) || journal.state === 'committed' || journal.sourceActivationPending)) {
757
880
  throw updateError('update_revoke_too_late', 'The update is already being applied.');
758
881
  }
759
882
  const authorization = readAuthorization(context);
@@ -782,9 +905,15 @@ function revokeUpdate(context, params = {}) {
782
905
  return { state: 'revoked', targetVersion };
783
906
  }
784
907
 
785
- function rollbackFiles(context, journal) {
908
+ function rollbackFiles(context, journal, options = {}) {
786
909
  const runtime = path.join(context.extensionRoot, 'runtime');
787
910
  const previousRuntime = path.join(context.extensionRoot, 'slots', 'previous', 'runtime');
911
+ // An early interrupted apply may still have the source in the current slot.
912
+ // A missing previous slot is recoverable only when these actual bytes match
913
+ // the source pair; the source manifest and markers alone are insufficient.
914
+ const source = readManagedRuntimeEvidence(context, journal.sourceVersion,
915
+ fs.existsSync(previousRuntime) ? previousRuntime : runtime);
916
+ if (journal.bootstrapMigration) BootstrapUpdate.restoreBootstrap(context, journal);
788
917
  if (fs.existsSync(previousRuntime)) {
789
918
  fs.rmSync(runtime, { recursive: true, force: true });
790
919
  fs.renameSync(previousRuntime, runtime);
@@ -793,7 +922,7 @@ function rollbackFiles(context, journal) {
793
922
  if (journal.previousManifest) {
794
923
  atomicWriteText(path.join(context.extensionRoot, 'manifest.json'), journal.previousManifest);
795
924
  }
796
- if (parseSemver(journal.sourceVersion)) {
925
+ if (parseSemver(journal.sourceVersion) && !options.deferSourceActivation) {
797
926
  atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
798
927
  }
799
928
  const sourcePrevious = parseSemver(journal.sourcePreviousVersion) ? journal.sourcePreviousVersion : '';
@@ -805,6 +934,18 @@ function rollbackFiles(context, journal) {
805
934
  if (parseSemver(journal.sourceVersion)) {
806
935
  syncManagedMarkerVersions(context, journal.sourceVersion);
807
936
  }
937
+ const restored = readManagedRuntimeEvidence(context, journal.sourceVersion);
938
+ if (!sameRuntimeEvidence(source, restored)) {
939
+ throw updateError('update_rollback_unconfirmed', 'The source runtime readback did not match the restored files.');
940
+ }
941
+ if (journal.bootstrapMigration) {
942
+ const bootstrap = BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
943
+ if (bootstrap) {
944
+ restored.bootstrapSha256 = bootstrap.digest;
945
+ if (bootstrap.unmodified) restored.bootstrapSourceUnchanged = true;
946
+ }
947
+ }
948
+ return restored;
808
949
  }
809
950
 
810
951
  function syncManagedMarkerVersions(context, version) {
@@ -843,8 +984,11 @@ function cleanupOrphanStageRoots(updatesRoot, retainedRoots = []) {
843
984
  }
844
985
  }
845
986
 
846
- function recoverAndReadStatus(context) {
987
+ function recoverAndReadStatus(context, params = {}) {
847
988
  let journal = readJournal(context);
989
+ if (journal?.sourceActivationPending && journal.state === 'rolled_back') {
990
+ journal = finishSourceActivation(context, journal);
991
+ }
848
992
  const alignedVersion = readAlignedManagedVersion(context);
849
993
  journal = supersedeObsoleteJournal(context, journal, alignedVersion);
850
994
  if (journal?.state === 'activation_pending') {
@@ -852,33 +996,120 @@ function recoverAndReadStatus(context) {
852
996
  writeJournal(context, journal);
853
997
  }
854
998
  if (journal?.state === 'applying') {
855
- rollbackFiles(context, journal);
856
- journal = { ...journal, state: 'rolled_back', reasonCode: 'update_interrupted', updatedAt: new Date().toISOString() };
999
+ const rollbackEvidence = rollbackFiles(context, journal);
1000
+ journal = { ...journal, rollbackEvidence, state: 'rolled_back', reasonCode: 'update_interrupted', updatedAt: new Date().toISOString() };
857
1001
  writeJournal(context, journal);
858
1002
  settleAuthorization(context, journal.authorizationId, 'revoked');
859
1003
  }
1004
+ journal = BootstrapUpdate.bindBootstrapMigration(context, journal, bootstrapDependencies(context));
1005
+ if (BootstrapUpdate.bootstrapExpired(journal, { recoveryRequested: params.bootstrapRecovery === true, context })) {
1006
+ rollbackUpdate(context, { transactionId: journal.id, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
1007
+ journal = readJournal(context);
1008
+ }
860
1009
  if (journal?.state === 'rolled_back' || journal?.state === 'superseded') {
1010
+ if (journal.state === 'superseded') cleanupStage(journal.stageRoot);
861
1011
  pruneNativeVersions(context.nativeRoot, new Set([
862
1012
  readVersionPointer(context.nativeRoot, 'active-version'),
863
1013
  readVersionPointer(context.nativeRoot, 'previous-version')
864
1014
  ]));
865
1015
  }
1016
+ if (journal?.state === 'committed' && journal.committedEvidence && alignedVersion === journal.targetVersion) {
1017
+ const observed = readManagedRuntimeEvidence(context, journal.targetVersion);
1018
+ if (sameRuntimeEvidence(journal.committedEvidence, observed)) {
1019
+ if (journal.bootstrapMigration) BootstrapUpdate.assertBootstrapConfirmation(context, journal,
1020
+ { bootstrapRevision: journal.bootstrapMigration.targetRevision });
1021
+ const authorization = readAuthorization(context);
1022
+ if (['bound', 'consumed'].includes(authorization?.state) && authorization.id === journal.authorizationId
1023
+ && authorization.transactionId === journal.id && authorization.sourceVersion === journal.sourceVersion
1024
+ && authorization.targetVersion === journal.targetVersion) {
1025
+ settleAuthorization(context, journal.authorizationId, 'consumed');
1026
+ }
1027
+ cleanupStage(journal.stageRoot);
1028
+ }
1029
+ }
866
1030
  return currentStatus(context, journal);
867
1031
  }
868
1032
 
1033
+ function finishSourceActivation(context, journal) {
1034
+ const actual = readManagedRuntimeEvidence(context, journal.sourceVersion);
1035
+ if (!sameRuntimeEvidence(actual, journal.rollbackEvidence)) {
1036
+ throw updateError('update_rollback_unconfirmed', 'The recovered source files changed before activation.');
1037
+ }
1038
+ BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1039
+ settleAuthorization(context, journal.authorizationId, 'revoked');
1040
+ atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
1041
+ const finished = { ...journal, sourceActivationPending: false };
1042
+ writeJournal(context, finished);
1043
+ cleanupStage(journal.stageRoot);
1044
+ return finished;
1045
+ }
1046
+
1047
+ // Called by the full installer while holding the same mutation lock, only
1048
+ // after both runtime trees and browser registration have succeeded.
1049
+ function settleReinstalledUpdate(context, version, expected) {
1050
+ if (!expected?.id || !parseSemver(expected.sourceVersion) || !parseSemver(expected.targetVersion)
1051
+ || compareSemver(version, expected.targetVersion) < 0
1052
+ || compareSemver(version, expected.sourceVersion) <= 0) return;
1053
+ const journal = readJournal(context);
1054
+ if (!journal || ['id', 'state', 'sourceVersion', 'targetVersion'].some(key => journal[key] !== expected[key])) {
1055
+ throw updateError('update_transaction_mismatch', 'The update transaction changed during full installation.');
1056
+ }
1057
+ if (assertManagedLayout(context) !== version) {
1058
+ throw updateError('update_managed_layout_invalid', 'Full installation did not produce the requested pair.');
1059
+ }
1060
+ const reinstalledPair = readManagedRuntimeEvidence(context, version);
1061
+ const timestamp = new Date().toISOString();
1062
+ writeJournal(context, {
1063
+ ...journal, state: 'superseded', supersededFromState: journal.state,
1064
+ supersededByVersion: version, supersededAt: timestamp,
1065
+ updatedAt: timestamp, reasonCode: 'update_reinstalled', reinstalledPair
1066
+ });
1067
+ const authorization = readAuthorization(context);
1068
+ if (authorization?.state === 'bound' && authorization.id === journal.authorizationId
1069
+ && authorization.transactionId === journal.id && authorization.sourceVersion === journal.sourceVersion
1070
+ && authorization.targetVersion === journal.targetVersion) {
1071
+ settleAuthorization(context, authorization.id, 'revoked');
1072
+ }
1073
+ }
1074
+
869
1075
  function currentStatus(context, journal = readJournal(context)) {
1076
+ let restoredPair = null;
1077
+ let reinstalledPair = null;
1078
+ if (journal?.state === 'rolled_back') {
1079
+ try {
1080
+ const observed = readManagedRuntimeEvidence(context, journal.sourceVersion);
1081
+ if (!journal.rollbackEvidence || sameRuntimeEvidence(journal.rollbackEvidence, observed)) {
1082
+ if (journal.bootstrapMigration) {
1083
+ const bootstrap = BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1084
+ if (bootstrap) observed.bootstrapSha256 = bootstrap.digest;
1085
+ if (journal.rollbackEvidence?.bootstrapSha256 !== observed.bootstrapSha256) throw new Error('Bootstrap restoration changed.');
1086
+ }
1087
+ restoredPair = observed;
1088
+ }
1089
+ } catch (_) { /* Missing or changed bytes cannot confirm a recovered pair. */ }
1090
+ }
1091
+ if (journal?.state === 'superseded' && journal.reasonCode === 'update_reinstalled' && journal.reinstalledPair) {
1092
+ try {
1093
+ const observed = readManagedRuntimeEvidence(context, journal.supersededByVersion);
1094
+ if (sameRuntimeEvidence(journal.reinstalledPair, observed)) reinstalledPair = observed;
1095
+ } catch (_) { /* A reinstall receipt cannot authenticate later changed files. */ }
1096
+ }
870
1097
  return {
871
1098
  managed: true,
872
1099
  activeVersion: readVersionPointer(context.nativeRoot, 'active-version'),
873
1100
  runtimeVersion: context.env?.CODEX_OVERLEAF_ACTIVE_VERSION || RUNNING_VERSION,
874
1101
  installedAligned: Boolean(readAlignedManagedVersion(context)),
875
1102
  previousVersion: readVersionPointer(context.nativeRoot, 'previous-version'),
876
- transaction: journal ? publicTransaction(journal) : null,
877
- authorization: publicAuthorization(readAuthorization(context))
1103
+ transaction: journal ? { ...publicTransaction(journal),
1104
+ ...(journal.state === 'awaiting_health' && BootstrapUpdate.migrationHint(context, journal)
1105
+ && journal.bootstrapMigration?.phase !== 'loaded' ? { state: 'bootstrap_pending' } : {}) } : null,
1106
+ authorization: publicAuthorization(readAuthorization(context)),
1107
+ restoredPair,
1108
+ reinstalledPair
878
1109
  };
879
1110
  }
880
1111
 
881
- function readStatusWithoutWaiting(context) {
1112
+ function readStatusWithoutWaiting(context, params = {}) {
882
1113
  const lockPath = path.join(context.updatesRoot, MUTATION_LOCK_FILE);
883
1114
  const token = crypto.randomUUID();
884
1115
  let descriptor;
@@ -888,7 +1119,7 @@ function readStatusWithoutWaiting(context) {
888
1119
  if (error.code !== 'EEXIST') throw error;
889
1120
  const owner = readJsonSafe(lockPath, null);
890
1121
  if (Number.isInteger(owner?.pid) && !isProcessAlive(owner.pid) && removeStaleUpdateFileLock(lockPath)) {
891
- return readStatusWithoutWaiting(context);
1122
+ return readStatusWithoutWaiting(context, params);
892
1123
  }
893
1124
  // A live mutation owns recovery; report an atomic-file snapshot without
894
1125
  // queueing behind its network transfer or changing its journal.
@@ -897,7 +1128,7 @@ function readStatusWithoutWaiting(context) {
897
1128
  try {
898
1129
  fs.writeFileSync(descriptor, JSON.stringify({ token, pid: process.pid,
899
1130
  createdAt: Date.now(), expiresAt: Date.now() + MUTATION_LOCK_STALE_MS }));
900
- return recoverAndReadStatus(context);
1131
+ return recoverAndReadStatus(context, params);
901
1132
  } finally {
902
1133
  fs.closeSync(descriptor);
903
1134
  releaseUpdateFileLock({ lockPath, token });
@@ -913,12 +1144,21 @@ function readAlignedManagedVersion(context) {
913
1144
  }
914
1145
 
915
1146
  function supersedeObsoleteJournal(context, journal, activeVersion) {
916
- if (!journal || journal.state === 'superseded' || !parseSemver(activeVersion) ||
1147
+ if (!journal || !parseSemver(activeVersion) ||
917
1148
  !parseSemver(journal.sourceVersion) || !parseSemver(journal.targetVersion) ||
918
1149
  compareSemver(activeVersion, journal.sourceVersion) <= 0 ||
919
1150
  compareSemver(activeVersion, journal.targetVersion) <= 0) {
920
1151
  return journal;
921
1152
  }
1153
+ // A previous runtime may already have retired the journal without settling
1154
+ // its bound authorization. Never consume an authorization owned elsewhere.
1155
+ const authorization = readAuthorization(context);
1156
+ if (authorization?.state === 'bound' && journal.authorizationId
1157
+ && authorization.id === journal.authorizationId && authorization.transactionId === journal.id
1158
+ && authorization.sourceVersion === journal.sourceVersion && authorization.targetVersion === journal.targetVersion) {
1159
+ settleAuthorization(context, authorization.id, 'revoked');
1160
+ }
1161
+ if (journal.state === 'superseded') return journal;
922
1162
  const timestamp = new Date().toISOString();
923
1163
  const superseded = {
924
1164
  ...journal,
@@ -1098,6 +1338,7 @@ function publicTransaction(journal) {
1098
1338
  state: journal.state,
1099
1339
  sourceVersion: journal.sourceVersion,
1100
1340
  targetVersion: journal.targetVersion,
1341
+ ...(journal.bootstrapMigration ? { bootstrapRevision: journal.bootstrapMigration.targetRevision } : {}),
1101
1342
  createdAt: journal.createdAt,
1102
1343
  activationRequestedAt: journal.activationRequestedAt,
1103
1344
  appliedAt: journal.appliedAt,
@@ -1189,10 +1430,22 @@ function atomicWriteJson(target, value) {
1189
1430
  }
1190
1431
 
1191
1432
  function atomicWriteText(target, value) {
1192
- const temp = target + '.tmp-' + process.pid + '-' + Date.now();
1433
+ const temp = target + '.tmp-' + process.pid + '-' + crypto.randomBytes(6).toString('hex');
1193
1434
  fs.mkdirSync(path.dirname(target), { recursive: true });
1194
- fs.writeFileSync(temp, value, { mode: 0o600 });
1435
+ let descriptor = fs.openSync(temp, 'wx', 0o600);
1436
+ try {
1437
+ // Windows requires a writable handle for FlushFileBuffers (Node fsync).
1438
+ fs.writeFileSync(descriptor, value);
1439
+ fs.fsyncSync(descriptor);
1440
+ } finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
1195
1441
  fs.renameSync(temp, target);
1442
+ descriptor = undefined;
1443
+ try {
1444
+ descriptor = fs.openSync(path.dirname(target), 'r');
1445
+ fs.fsyncSync(descriptor);
1446
+ } catch (error) {
1447
+ if (process.platform !== 'win32' || !['EPERM', 'EISDIR', 'EINVAL', 'EBADF', 'EACCES'].includes(error.code)) throw error;
1448
+ } finally { if (descriptor !== undefined) fs.closeSync(descriptor); }
1196
1449
  }
1197
1450
 
1198
1451
  function readJsonSafe(target, fallback) {
@@ -1246,8 +1499,10 @@ function errorResponse(id, code, message) {
1246
1499
 
1247
1500
  module.exports = {
1248
1501
  GITHUB_LATEST_URL,
1502
+ MUTATION_LOCK_STALE_MS,
1249
1503
  applyStagedUpdate,
1250
1504
  assertManagedLayout,
1505
+ assertBootstrapBusinessAvailable,
1251
1506
  authorizeUpdate,
1252
1507
  checkForUpdate,
1253
1508
  cleanupOrphanStageRoots,
@@ -1257,8 +1512,11 @@ module.exports = {
1257
1512
  handleUpdateRequest,
1258
1513
  isUpdateMethod,
1259
1514
  revokeUpdate,
1515
+ removeStaleUpdateFileLock,
1516
+ recoverBootstrapTransaction,
1260
1517
  rollbackUpdate,
1261
1518
  scheduleStagedActivation,
1519
+ settleReinstalledUpdate,
1262
1520
  stageCandidate,
1263
1521
  syncManagedMarkerVersions
1264
1522
  };