You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/09/10 14:15:18 UTC
(maka) 02/02: fix(desktop): fence legacy WSL handoff without status fingerprint
This is an automated email from the ASF dual-hosted git repository.
me2seeks pushed a commit to branch fix/5143-wsl-managed-handoff
in repository https://gitbox.apache.org/repos/asf/maka.git
commit e0bba4e6d1a851ebd6cc5f4e5d630200f5da69fc
Author: me2seeks <me...@gmail.com>
AuthorDate: Thu Sep 10 19:31:45 2026 +0800
fix(desktop): fence legacy WSL handoff without status fingerprint
Validate the observed source package version under the deployment lease alongside deployment and Host generation identities. Preserve the additional fingerprint fence where the source operator supports it.
Refs #5143
Generated-by: OpenAI Codex
---
.../__tests__/runtime-host-wsl-controller.test.ts | 1 +
.../main/__tests__/runtime-host-wsl-handoff.test.ts | 19 +++++++++++++++++++
.../desktop/src/main/runtime-host-wsl-controller.ts | 6 ++++--
apps/desktop/src/main/runtime-host-wsl-handoff.ts | 8 +++++---
.../__tests__/runtime-host-selected-update.test.ts | 1 +
packages/cli/src/cli-core.ts | 6 ++++++
packages/cli/src/runtime-host-cli.ts | 21 +++++++++++++++++++--
packages/cli/src/runtime-host-update-command.ts | 10 ++++++++++
8 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
index 5480dea490..07447f8a28 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
@@ -229,6 +229,7 @@ test('WSL update cancellation closes retirement input without killing the transa
expectedTarget: { serviceId: 'a'.repeat(64), rootId: 'a'.repeat(64), rootPath: '/state', deploymentId: '00000000-0000-4000-8000-000000000001' },
expectedConfigFingerprint: `sha256:${'b'.repeat(64)}`,
expectedHost: { hostEpoch: 'old-host', pid: 42 },
+ expectedSourceVersion: '0.2.0',
allowInterruptActiveTasks: false, signal: abort.signal,
}, () => {}, {
wslExecutable: 'wsl.exe',
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
index 9220742034..4c4697026b 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
@@ -90,3 +90,22 @@ test('WSL handoff carries exact source identity and separately forwards interrup
assert.deepEqual(await blocker.replacement!.execute('interrupt_active_work', () => {}, 'explicit'), { kind: 'changed' });
assert.deepEqual(policies, [false, true]);
});
+
+
+test('legacy operator without a configuration fingerprint remains fenced by source version and Host identity', async () => {
+ const { configurationFingerprint: _fingerprint, ...legacyService } = service;
+ const blocker = await resolveDesktopWslHostHandoff(profile, incompatible(), new AbortController().signal, {
+ resolveBinding: async () => binding,
+ resolvePackage: async () => ({ kind: 'development_archive', path: '/selected.tgz', integrity: 'sha512-selected' }),
+ status: async () => ({ schemaVersion: 1, kind: 'result', action: 'status', service: legacyService }),
+ update: async (input) => {
+ assert.equal(input.expectedConfigFingerprint, undefined);
+ assert.equal(input.expectedSourceVersion, legacyService.installedVersion);
+ assert.deepEqual(input.expectedHost, { hostEpoch: 'old-host', pid: 42 });
+ assert.equal(input.expectedTarget.deploymentId, binding.deployment.deploymentId);
+ return { schemaVersion: 1, kind: 'error', action: 'update', error: { code: 'target_mismatch', message: 'Source changed under deployment lock' } };
+ },
+ });
+ assert.ok(blocker.replacement);
+ assert.deepEqual(await blocker.replacement.execute('refuse_active_work', () => {}, 'explicit'), { kind: 'changed' });
+});
diff --git a/apps/desktop/src/main/runtime-host-wsl-controller.ts b/apps/desktop/src/main/runtime-host-wsl-controller.ts
index a582bb0ba2..ba04338317 100644
--- a/apps/desktop/src/main/runtime-host-wsl-controller.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-controller.ts
@@ -219,7 +219,8 @@ export async function runDesktopRuntimeHostWslUpdate(
readonly distribution: string;
readonly setupPackage: DesktopRuntimeHostSetupPackage;
readonly expectedTarget: DesktopRuntimeHostWslManagementInput['expectedTarget'];
- readonly expectedConfigFingerprint: string;
+ readonly expectedConfigFingerprint?: string;
+ readonly expectedSourceVersion: string;
readonly expectedHost: { readonly hostEpoch: string; readonly pid: number };
readonly allowInterruptActiveTasks: boolean;
readonly signal?: AbortSignal;
@@ -246,7 +247,8 @@ export async function runDesktopRuntimeHostWslUpdate(
'--expected-root-id', target.rootId,
'--expected-root-path', target.rootPath,
'--expected-deployment-id', target.deploymentId,
- '--expected-config-fingerprint', input.expectedConfigFingerprint,
+ '--expected-source-version', input.expectedSourceVersion,
+ ...(input.expectedConfigFingerprint ? ['--expected-config-fingerprint', input.expectedConfigFingerprint] : []),
'--expected-host-json', JSON.stringify(input.expectedHost),
...(input.allowInterruptActiveTasks ? ['--allow-interrupt-active-tasks'] : []),
];
diff --git a/apps/desktop/src/main/runtime-host-wsl-handoff.ts b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
index c4e1f12fa4..bd7921838a 100644
--- a/apps/desktop/src/main/runtime-host-wsl-handoff.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
@@ -63,7 +63,7 @@ export async function resolveDesktopWslHostHandoff(
action: 'status', expectedTarget, signal,
});
if (status.kind !== 'result' || status.action !== 'status' || status.service.lifecycle?.mode !== 'on_demand' ||
- !status.service.pid || !status.service.installedVersion || !status.service.configurationFingerprint) {
+ !status.service.pid || !status.service.installedVersion) {
return { ...base, operatorStep: guidance.source };
}
const setupPackage = await deps.resolvePackage(signal);
@@ -72,9 +72,11 @@ export async function resolveDesktopWslHostHandoff(
return { ...base, operatorStep: guidance.target };
}
const currentVersion = status.service.installedVersion;
+ // Legacy operators omit the fingerprint. The transaction still fences the source
+ // package version, deployment identity and exact Host generation under its lease.
const expectedConfigFingerprint = status.service.configurationFingerprint;
const expectedHost = { hostEpoch: error.hostEpoch, pid: status.service.pid };
- const identity = JSON.stringify([base.identity, binding.deployment, expectedHost, expectedConfigFingerprint, setupPackage]);
+ const identity = JSON.stringify([base.identity, binding.deployment, expectedHost, currentVersion, expectedConfigFingerprint, setupPackage]);
return {
...base, identity,
packageChange: { current: currentVersion, target: targetVersion ?? (setupPackage.kind === 'development_archive' ? setupPackage.integrity : setupPackage.specifier) },
@@ -85,7 +87,7 @@ export async function resolveDesktopWslHostHandoff(
if (JSON.stringify(fresh) !== JSON.stringify(binding)) return { kind: 'changed' };
const result = await (deps.update ?? runDesktopRuntimeHostWslUpdate)({
distribution: profile.provider.distribution, setupPackage, expectedTarget,
- expectedConfigFingerprint, expectedHost,
+ expectedConfigFingerprint, expectedHost, expectedSourceVersion: currentVersion,
allowInterruptActiveTasks: policy === 'interrupt_active_work', signal: retirementSignal,
}, (phase) => {
const phases: readonly string[] = ['checking', 'staging', 'retiring', 'replacing', 'verifying'];
diff --git a/packages/cli/src/__tests__/runtime-host-selected-update.test.ts b/packages/cli/src/__tests__/runtime-host-selected-update.test.ts
index 28ae71a1fb..56aa9bc41e 100644
--- a/packages/cli/src/__tests__/runtime-host-selected-update.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-selected-update.test.ts
@@ -478,6 +478,7 @@ describe('canonical WSL update fences', () => {
for (const selection of [
{ version: '1.0.0' },
+ { version: '3.0.0', expectedSourceVersion: '1.9.0' },
{ version: '3.0.0', expectedConfigFingerprint: `sha256:${'f'.repeat(64)}` },
])
it(`rejects a downgrade or stale consent before staging: ${selection.version}`, async () => {
diff --git a/packages/cli/src/cli-core.ts b/packages/cli/src/cli-core.ts
index e7774aba7c..faa829a351 100644
--- a/packages/cli/src/cli-core.ts
+++ b/packages/cli/src/cli-core.ts
@@ -575,6 +575,9 @@ export async function runMakaCli(
defaultRootPath: serviceDataRoots.workspaceRoot,
selector: command.selector,
expectedTarget: command.expectedTarget,
+ ...(command.expectedSourceVersion
+ ? { expectedSourceVersion: command.expectedSourceVersion }
+ : {}),
...(command.expectedHost ? { expectedHost: command.expectedHost } : {}),
...(command.expectedConfigFingerprint
? { expectedConfigFingerprint: command.expectedConfigFingerprint }
@@ -596,6 +599,9 @@ export async function runMakaCli(
...(sourcePackageIntegrity ? { sourcePackageIntegrity } : {}),
version,
expectedTarget: command.expectedTarget,
+ ...(command.expectedSourceVersion
+ ? { expectedSourceVersion: command.expectedSourceVersion }
+ : {}),
...(command.expectedHost ? { expectedHost: command.expectedHost } : {}),
...(command.expectedConfigFingerprint
? { expectedConfigFingerprint: command.expectedConfigFingerprint }
diff --git a/packages/cli/src/runtime-host-cli.ts b/packages/cli/src/runtime-host-cli.ts
index b4896ac109..05e90d2579 100644
--- a/packages/cli/src/runtime-host-cli.ts
+++ b/packages/cli/src/runtime-host-cli.ts
@@ -241,6 +241,7 @@ export type RuntimeHostCliCommand =
expectedTarget: RuntimeHostManagedServiceTarget;
expectedHost?: RuntimeHostExpectedHost;
expectedConfigFingerprint?: string;
+ expectedSourceVersion?: string;
selector?: RuntimeHostUpdateSelector;
allowManualUpdate?: true;
allowInterruptActiveTasks?: true;
@@ -881,6 +882,7 @@ function parseServiceManagementCommand(argv: string[]): RuntimeHostCliCommand {
let updateTarget: string | undefined;
let expectedHost: RuntimeHostExpectedHost | undefined;
let expectedConfigFingerprint: string | undefined;
+ let expectedSourceVersion: string | undefined;
const flagOptions: Readonly<Record<string, () => void | RuntimeHostCliError>> =
action === 'uninstall'
? {
@@ -935,6 +937,17 @@ function parseServiceManagementCommand(argv: string[]): RuntimeHostCliCommand {
},
}
: {}),
+ ...(action === 'update'
+ ? {
+ '--expected-source-version': (value: string) => {
+ if (expectedSourceVersion !== undefined)
+ return error('Duplicate --expected-source-version');
+ if (!isProductReleaseVersion(value))
+ return error('--expected-source-version must be an exact package version');
+ expectedSourceVersion = value;
+ },
+ }
+ : {}),
...(action === 'update' || action === 'restart'
? {
'--expected-host-json': (value: string) => {
@@ -1028,8 +1041,11 @@ function parseServiceManagementCommand(argv: string[]): RuntimeHostCliCommand {
};
}
if (action === 'update') {
- if (expectedHost && !options.managedRootId) {
- return error('--expected-host-json requires --managed-root-id');
+ if (
+ (expectedHost || expectedSourceVersion || expectedConfigFingerprint) &&
+ !options.managedRootId
+ ) {
+ return error('Observed Host and deployment fences require --managed-root-id');
}
const selector =
updateTarget === undefined ? undefined : parseUpdateSelector(updateTarget, 'update');
@@ -1056,6 +1072,7 @@ function parseServiceManagementCommand(argv: string[]): RuntimeHostCliCommand {
expectedTarget: options.expectedTarget!,
...(expectedHost ? { expectedHost } : {}),
...(expectedConfigFingerprint ? { expectedConfigFingerprint } : {}),
+ ...(expectedSourceVersion ? { expectedSourceVersion } : {}),
...(selector ? { selector } : {}),
...(allowManualUpdate ? { allowManualUpdate: true } : {}),
...(allowInterruptActiveTasks ? { allowInterruptActiveTasks: true } : {}),
diff --git a/packages/cli/src/runtime-host-update-command.ts b/packages/cli/src/runtime-host-update-command.ts
index d7db43d3c7..d55dc4dc54 100644
--- a/packages/cli/src/runtime-host-update-command.ts
+++ b/packages/cli/src/runtime-host-update-command.ts
@@ -107,6 +107,7 @@ export interface RuntimeHostUpdateCliOptions {
readonly expectedTarget: RuntimeHostManagedServiceTarget;
readonly expectedHost?: RuntimeHostExpectedHost;
readonly expectedConfigFingerprint?: string;
+ readonly expectedSourceVersion?: string;
readonly managedRootId?: string;
readonly operatorDeploymentId?: string;
readonly registrySelection?: {
@@ -660,6 +661,15 @@ async function runCanonicalRuntimeHostUpdate(
);
}
const current = recovered.config;
+ if (
+ options.expectedSourceVersion &&
+ current.launch.package.version !== options.expectedSourceVersion
+ ) {
+ throw new RuntimeHostServiceManagerError(
+ 'target_mismatch',
+ 'The installed Runtime Host package changed; check it again before updating',
+ );
+ }
await deps.canonical.convergeControlProjection(current, lifecycleDeps);
await deps.canonical.verifyProjection(current, lifecycleDeps);
deps.canonical.assertOperatorConfig(
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org