Revert "merge: integrate extension auto-update"

This reverts commit 322475860a, reversing
changes made to f52d9d7413.
This commit is contained in:
inman committed 2026-09-03 16:45:14 +08:00
1 parent b2e33e2e5d
commit 81a0cdac8e
47 files changed
+169 -3108

No files matched your search

@@ -71,37 +71,6 @@ test('AgentBus account-worker migration adds fail-closed channel, task, browser,
assert.match(sql, /owner_user_id IS NULL[\s\S]+enabled = true/);
});
test('extension host migration binds accounts to ECS and persists release/update state without a host daemon', async () => {
const [sql, server, tasks, extensionUpdates] = await Promise.all([
source('../migrations/019_extension_host_updates.sql'),
source('../src/server.ts'),
source('../src/task-service.ts'),
source('../src/extension-updates.ts')
]);
assert.match(sql, /extension_ecs_region_id/);
assert.match(sql, /extension_ecs_instance_id/);
assert.match(sql, /CREATE TABLE IF NOT EXISTS extension_releases/);
assert.match(sql, /CREATE TABLE IF NOT EXISTS extension_host_updates/);
assert.match(sql, /waiting_for_idle/);
assert.doesNotMatch(sql, /DELETE\s+FROM/i);
assert.match(server, /\/api\/extension-updates\/releases/);
assert.match(server, /extension_update_safe/);
assert.match(tasks, /requireExtensionHostAvailable/);
assert.match(tasks, /extension_update_required/);
assert.match(tasks, /compareExtensionVersions/);
assert.match(tasks, /attempt\.status IN \('accepted', 'running', 'reconciliation_pending'\)/);
assert.match(extensionUpdates, /class AliyunEcsCloudAssistantClient/);
assert.match(extensionUpdates, /type: 'RunPowerShellScript'/);
assert.match(extensionUpdates, /Get-FileHash/);
assert.match(extensionUpdates, /EXTENSION_WINDOWS_INSTALL_PATH/);
assert.match(extensionUpdates, /安全完成已启动的上一版本部署/);
assert.match(extensionUpdates, /id = \$2 AND is_active = true/);
assert.match(extensionUpdates, /\$\{inspected\.sha256\}/);
assert.match(extensionUpdates, /clientToken: createHash\('sha256'\)/);
assert.doesNotMatch(extensionUpdates, /clientToken: randomUUID\(\)/);
assert.doesNotMatch(extensionUpdates, /setInterval\(/);
});
test('AgentBus channel keys and owners are unique so one inbound identity cannot fan out to multiple employees', async () => {
const channels = await source('../src/agentbus-channels.ts');
assert.match(channels, /requireAssignableOwner/);
+3 -5
View File
@@ -317,7 +317,7 @@ test('control plane requires the latest durable task-outcome migration before re
const { readFile } = await import('node:fs/promises');
const db = await readFile(new URL('../src/db.ts', import.meta.url), 'utf8');
const server = await readFile(new URL('../src/server.ts', import.meta.url), 'utf8');
assert.equal(REQUIRED_SCHEMA_VERSION, '019_extension_host_updates');
assert.equal(REQUIRED_SCHEMA_VERSION, '018_agentbus_account_workers');
assert.match(db, /schema_migrations/);
assert.match(db, /databaseReadiness/);
assert.match(db, /assertDatabaseSchema/);
@@ -1365,10 +1365,8 @@ test('operator page has a login gate and uses the durable task API', async () =>
assert.match(app, /window\.addEventListener\('focus',[\s\S]+refreshBackgroundState\(\)/);
assert.match(app, /async function autoDispatchReadyTasks\(\{ force = false \} = \{\}\)/);
assert.match(app, /if \(!force && retryAt > Date\.now\(\)\) continue/);
assert.match(app, /extensionUpdateBlocksExecution = extensionUpdateIsBlocking\(extensionUpdate\)/);
assert.match(app, /function extensionUpdateIsBlocking\(update = \{\}\) \{[\s\S]+!\['current', 'disabled', 'no_release'\]\.includes/);
assert.match(app, /if \(!authUser \|\| autoHandoffInProgress \|\| extensionUpdateBlocksExecution\) return/);
assert.match(app, /autoDispatchReadyTasks\(\{ force: true \}\)/);
assert.match(app, /const bridgeReadyForDispatch = !wasBridgeConnected/);
assert.match(app, /autoDispatchReadyTasks\(\{ force: bridgeReadyForDispatch \}\)/);
const autoDispatchSource = app.slice(
app.indexOf('async function autoDispatchReadyTasks'),
app.indexOf('function taskStateClass')
-4
View File
@@ -65,10 +65,6 @@ test('diagnostic request identifiers and paths are stable and query-safe', () =>
assert.match(generated, /^[a-f0-9-]{36}$/u);
assert.doesNotMatch(generated, /token/u);
assert.equal(diagnosticRequestPath('/api/tasks/TASK-1?token=secret#fragment'), '/api/tasks/TASK-1');
assert.equal(
diagnosticRequestPath('/api/extension-updates/package/secret-bearer-token'),
'/api/extension-updates/package/:token'
);
const startedAt = process.hrtime.bigint() - 2_000_000n;
assert.ok(diagnosticDurationMs(startedAt) >= 1);
});
@@ -1,170 +0,0 @@
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import test from 'node:test';
import JSZip from 'jszip';
import { loadConfig } from '../src/config.js';
import {
ExtensionUpdateError,
buildExtensionUpdatePowerShell,
compareExtensionVersions,
createExtensionDownloadToken,
inspectExtensionPackage,
interpretCloudAssistantInvocation,
verifyExtensionDownloadToken
} from '../src/extension-updates.js';
async function extensionZip(version = '0.5.167'): Promise<Buffer> {
const zip = new JSZip();
zip.file('manifest.json', JSON.stringify({
manifest_version: 3,
name: '联泰下单助手',
version,
background: { service_worker: 'background.js' },
content_scripts: [{ matches: ['https://business.example.test/*'], js: ['business-bridge.js'] }]
}));
zip.file('background.js', 'chrome.runtime.onMessage.addListener(() => {});');
zip.file('business-bridge.js', 'window.postMessage({ ok: true });');
return zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
}
test('extension versions compare numerically rather than lexically', () => {
assert.equal(compareExtensionVersions('0.5.167', '0.5.166'), 1);
assert.equal(compareExtensionVersions('0.10.0', '0.9.99'), 1);
assert.equal(compareExtensionVersions('1.0.0', '1.0.0.0'), 0);
assert.equal(compareExtensionVersions('0.5.166', '0.5.167'), -1);
assert.throws(
() => compareExtensionVersions('latest', '0.5.167'),
(error: unknown) => error instanceof ExtensionUpdateError && error.code === 'extension_version_invalid'
);
});
test('extension release inspection binds identity, version, required files, and SHA-256', async () => {
const content = await extensionZip();
const inspected = await inspectExtensionPackage(content);
assert.equal(inspected.version, '0.5.167');
assert.equal(inspected.manifest.name, '联泰下单助手');
assert.equal(inspected.entryCount, 3);
assert.equal(inspected.sha256, createHash('sha256').update(content).digest('hex'));
const invalid = new JSZip();
invalid.file('manifest.json', JSON.stringify({
manifest_version: 3,
name: '其他插件',
version: '0.5.167',
background: { service_worker: 'background.js' },
content_scripts: [{ matches: ['https://business.example.test/*'], js: ['business-bridge.js'] }]
}));
invalid.file('background.js', '');
invalid.file('business-bridge.js', '');
await assert.rejects(
inspectExtensionPackage(await invalid.generateAsync({ type: 'nodebuffer' })),
(error: unknown) => error instanceof ExtensionUpdateError && error.code === 'extension_manifest_identity_mismatch'
);
const unsafePath = await extensionZip();
const unsafeZip = await JSZip.loadAsync(unsafePath);
unsafeZip.file('asset.js:alternate-stream', 'forbidden');
await assert.rejects(
inspectExtensionPackage(await unsafeZip.generateAsync({ type: 'nodebuffer' })),
(error: unknown) => error instanceof ExtensionUpdateError && error.code === 'extension_package_path_invalid'
);
});
test('short-lived host-scoped package tokens reject tampering and expiry', () => {
const key = Buffer.alloc(32, 7);
const payload = {
releaseId: 'release-a',
organizationId: 'organization-a',
regionId: 'cn-hangzhou',
instanceId: 'i-12345678',
expiresAt: 2_000
};
const token = createExtensionDownloadToken(payload, key);
assert.deepEqual(verifyExtensionDownloadToken(token, key, 1_999), payload);
assert.throws(
() => verifyExtensionDownloadToken(`${token}x`, key, 1_999),
(error: unknown) => error instanceof ExtensionUpdateError && error.code === 'extension_download_token_invalid'
);
assert.throws(
() => verifyExtensionDownloadToken(token, key, 2_001),
(error: unknown) => error instanceof ExtensionUpdateError && error.code === 'extension_download_token_expired'
);
});
test('PowerShell updater carries encoded values, enforces ProgramData, hash, staging, and rollback', () => {
const script = buildExtensionUpdatePowerShell({
downloadUrl: 'https://business.example.test/api/extension-updates/package/token',
sha256: 'a'.repeat(64),
version: '0.5.167',
installPath: 'C:\\ProgramData\\LTJT\\chrome-extension\\ltjt-order-assistant',
releaseId: 'release-a'
});
assert.match(script, /Get-FileHash/);
assert.match(script, /PackageHashMismatch/);
assert.match(script, /InstallPathOutsideAllowedRoot/);
assert.match(script, /LTJT_EXTENSION_NEWER_PRESENT/);
assert.match(script, /\.previous/);
assert.match(script, /Move-Item -LiteralPath \$backupPath -Destination \$installPath/);
assert.ok(Buffer.byteLength(Buffer.from(script, 'utf8').toString('base64')) < 24 * 1024);
assert.doesNotMatch(script, /business\.example\.test/);
});
test('Cloud Assistant success requires exit zero and the updater completion marker', () => {
assert.deepEqual(
interpretCloudAssistantInvocation({
invocationStatus: 'Success',
exitCode: 0,
output: 'LTJT_EXTENSION_UPDATED 0.5.167\n'
}),
{
status: 'success',
exitCode: 0,
output: 'LTJT_EXTENSION_UPDATED 0.5.167\n'
}
);
assert.equal(
interpretCloudAssistantInvocation({ invocationStatus: 'Success', exitCode: 0, output: '' }).errorCode,
'extension_update_marker_missing'
);
assert.equal(
interpretCloudAssistantInvocation({ invocationStatus: 'Running', output: '' }).status,
'running'
);
assert.equal(
interpretCloudAssistantInvocation({
invocationStatus: 'Success',
exitCode: 0,
output: 'LTJT_EXTENSION_NEWER_PRESENT 0.5.168\r\n'
}).status,
'success'
);
assert.equal(
interpretCloudAssistantInvocation({ invocationStatus: 'Aborted', errorCode: 'ClientNotRunning' }).status,
'failed'
);
});
test('extension updater is off by default and enabled production config requires HTTPS plus OSS and ECS credentials', () => {
const disabled = loadConfig({ NODE_ENV: 'test' });
assert.equal(disabled.EXTENSION_AUTO_UPDATE_ENABLED, false);
assert.equal(disabled.EXTENSION_WINDOWS_INSTALL_PATH, 'C:\\ProgramData\\LTJT\\chrome-extension\\ltjt-order-assistant');
assert.throws(() => loadConfig({
NODE_ENV: 'production',
FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 1).toString('base64'),
APP_ORIGIN: 'https://business.example.test',
EXTENSION_AUTO_UPDATE_ENABLED: 'true'
}), /missing configuration/);
assert.throws(() => loadConfig({
NODE_ENV: 'production',
FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 1).toString('base64'),
APP_ORIGIN: 'http://business.example.test',
EXTENSION_AUTO_UPDATE_ENABLED: 'true',
OSS_ACCESS_KEY_ID: 'oss-id',
OSS_ACCESS_KEY_SECRET: 'oss-secret',
OSS_ENDPOINT: 'oss-cn-hangzhou.aliyuncs.com',
OSS_BUCKET_NAME: 'bucket',
OSS_REGION: 'cn-hangzhou',
ALIBABA_CLOUD_ACCESS_KEY_ID: 'ecs-id',
ALIBABA_CLOUD_ACCESS_KEY_SECRET: 'ecs-secret'
}), /must use HTTPS/);
});