diff --git a/extensions/discord/src/doctor.test.ts b/extensions/discord/src/doctor.test.ts index a46bbeacc994..7467904c20ad 100644 --- a/extensions/discord/src/doctor.test.ts +++ b/extensions/discord/src/doctor.test.ts @@ -21,6 +21,37 @@ function getDiscordCompatibilityNormalizer(): NonNullable< } describe("discord doctor", () => { + it("promotes shipped nested DM access at root and account scope", () => { + const normalize = getDiscordCompatibilityNormalizer(); + const result = normalize({ + cfg: { + channels: { + discord: { + dm: { enabled: false, policy: "allowlist", allowFrom: ["123"] }, + accounts: { + work: { + dm: { groupEnabled: true, policy: "open", allowFrom: ["*"] }, + }, + }, + }, + }, + } as never, + }); + + expect(result.config.channels?.discord).toEqual({ + dm: { enabled: false }, + dmPolicy: "allowlist", + allowFrom: ["123"], + accounts: { + work: { + dm: { groupEnabled: true }, + dmPolicy: "open", + allowFrom: ["*"], + }, + }, + }); + }); + it("strips retired gateway, queue, and retry tuning at root and account scope", () => { const normalize = getDiscordCompatibilityNormalizer(); const result = normalize({ diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index ae4041b9c4fb..3ea48c92920a 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -1984,6 +1984,14 @@ describe("update-cli", () => { ), ).toBe(true); expect(defaultRuntime.exit).toHaveBeenCalledWith(0); + expect(doctorCommand).toHaveBeenCalledWith(defaultRuntime, { + nonInteractive: true, + repair: true, + yes: false, + }); + expect(vi.mocked(doctorCommand).mock.invocationCallOrder[0] ?? 0).toBeLessThan( + syncPluginsForUpdateChannel.mock.invocationCallOrder[0] ?? 0, + ); expect(syncPluginsForUpdateChannel).toHaveBeenCalledTimes(1); expect(updateNpmInstalledPlugins).toHaveBeenCalledTimes(1); expect(spawn).not.toHaveBeenCalled(); diff --git a/src/cli/update-cli/update-command-resume.ts b/src/cli/update-cli/update-command-resume.ts index f91cb0055876..03927d5bb439 100644 --- a/src/cli/update-cli/update-command-resume.ts +++ b/src/cli/update-cli/update-command-resume.ts @@ -1,3 +1,4 @@ +import { doctorCommand } from "../../commands/doctor.js"; import { readConfigFileSnapshot } from "../../config/config.js"; import { normalizeUpdateChannel } from "../../infra/update-channels.js"; import { POST_CORE_UPDATE_SOURCE_CONFIG_PATH_ENV } from "../../infra/update-post-core-context.js"; @@ -9,11 +10,15 @@ import { defaultRuntime } from "../../runtime.js"; import { VERSION } from "../../version.js"; import { readPackageVersion, type UpdateCommandOptions } from "./shared.js"; import { + createUpdateConfigSnapshot, persistRequestedUpdateChannel, readPostCorePreUpdateSourceConfig, restoreDroppedPreUpdateChannels, } from "./update-command-config.js"; -import { completePostCorePluginUpdate } from "./update-command-fresh-doctor.js"; +import { + completePostCorePluginUpdate, + withUpdateFinalizationEnv, +} from "./update-command-fresh-doctor.js"; import { updatePluginsAfterCoreUpdate } from "./update-command-plugins.js"; import { POST_CORE_UPDATE_INSTALL_RECORDS_PATH_ENV, @@ -71,6 +76,21 @@ async function resumePostCoreUpdateUnlocked(params: ResumePostCoreUpdateParams): currentSnapshot: configSnapshot, updateStartedAtMs, }); + await withUpdateFinalizationEnv(async () => { + await createUpdateConfigSnapshot(); + await doctorCommand(defaultRuntime, { + nonInteractive: true, + repair: true, + yes: params.opts.yes === true, + }); + }); + // The fresh process owns the updated migration contracts. Repair before + // plugin convergence writes config, or newly retired plugin keys can block + // the update before doctor gets a chance to migrate them. + configSnapshot = await readConfigFileSnapshot({ + skipPluginValidation: true, + suppressFutureVersionWarning: true, + }); configSnapshot = await persistRequestedUpdateChannel({ configSnapshot, requestedChannel,