fix(plugins): preserve install index state across failures (#119228)

* fix(plugins): preserve install index policy config

* fix(plugins): restore complete install index state

* fix(plugins): fence install index rollback

* fix(plugins): fence generic install index rollback

* fix(plugins): keep lifecycle lease context private

* test(cli): align plugin index rollback mocks

* test(plugins): enforce index rollback receipts

* fix(plugins): serialize install rollback with config commit

* test(plugins): keep legacy index writer mock private
This commit is contained in:
Vincent Koc
2026-08-05 01:04:17 +08:00
committed by GitHub
parent bfba83956f
commit ef11eae39b
17 changed files with 1314 additions and 242 deletions
+36 -2
View File
@@ -43,8 +43,9 @@ import type { UpdateRunResult } from "../../infra/update-runner.js";
import { getWindowsSystem32ExePath } from "../../infra/windows-install-roots.js";
import {
loadInstalledPluginIndexInstallRecords,
writePersistedInstalledPluginIndexInstallRecords,
writePersistedInstalledPluginIndexInstallRecordsWithLease,
} from "../../plugins/installed-plugin-index-records.js";
import { restorePersistedInstalledPluginIndexIfCurrent } from "../../plugins/installed-plugin-index-store.js";
import { withPluginLifecycleLease } from "../../plugins/plugin-lifecycle-lease.js";
import { runExec } from "../../process/exec.js";
import { defaultRuntime } from "../../runtime.js";
@@ -513,10 +514,33 @@ export async function continuePostCoreUpdateInFreshProcess(params: {
records: params.pluginInstallRecords,
targetVersion: postCoreHostVersion,
});
let tentativePluginIndex:
| Awaited<ReturnType<typeof writePersistedInstalledPluginIndexInstallRecordsWithLease>>
| undefined;
const restoreTentativePluginIndex = async () => {
const tentative = tentativePluginIndex;
if (!tentative) {
return;
}
await withPluginLifecycleLease({}, async (lease) => {
await restorePersistedInstalledPluginIndexIfCurrent(tentative.previous, tentative.revision, {
lease,
});
});
tentativePluginIndex = undefined;
};
try {
if (pluginInstallRecords && pluginInstallRecords !== params.pluginInstallRecords) {
await writePersistedInstalledPluginIndexInstallRecords(pluginInstallRecords);
await withPluginLifecycleLease({}, async (lease) => {
tentativePluginIndex = await writePersistedInstalledPluginIndexInstallRecordsWithLease(
pluginInstallRecords,
{
...(params.preUpdateConfig ? { config: params.preUpdateConfig.sourceConfig } : {}),
lease,
},
);
});
}
await writePostCorePluginInstallRecordsFile(installRecordsPath, pluginInstallRecords);
await writePostCoreSourceConfigFile(sourceConfigPath, params.preUpdateConfig);
@@ -608,9 +632,19 @@ export async function continuePostCoreUpdateInFreshProcess(params: {
if (pluginUpdate) {
return { resumed: true, pluginUpdate };
}
await restoreTentativePluginIndex();
return { resumed: false, exitCode };
}
return { resumed: true, ...(pluginUpdate ? { pluginUpdate } : {}) };
} catch (error) {
try {
await restoreTentativePluginIndex();
} catch (rollbackError) {
throw new Error("Post-core update failed and could not restore the previous plugin index", {
cause: rollbackError,
});
}
throw error;
} finally {
await fs.rm(resultDir, { recursive: true, force: true }).catch(() => undefined);
}