mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-23 15:29:50 +00:00
690 lines
24 KiB
JavaScript
690 lines
24 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Alibaba Group Holding Limited
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync } from 'node:child_process';
|
|
import {
|
|
chmod,
|
|
mkdtemp,
|
|
mkdir,
|
|
readFile,
|
|
rm,
|
|
stat,
|
|
symlink,
|
|
writeFile,
|
|
} from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import {
|
|
generateSecrets,
|
|
renderApplicationYml,
|
|
renderHarnessEnv,
|
|
renderServerEnv,
|
|
} from '../../packages/managed-agent-server/distribution/bin/qwen-managed-agent-bootstrap.mjs';
|
|
import { createReferenceDistribution } from '../package-managed-agent-reference.mjs';
|
|
|
|
async function fixture(root) {
|
|
const files = {
|
|
'package.json': JSON.stringify({
|
|
version: '1.2.3',
|
|
engines: { node: '>=22.0.0' },
|
|
}),
|
|
LICENSE: 'license\n',
|
|
'packages/managed-agent-server/pom.xml': `
|
|
<artifactId>qwen-managed-agent-server</artifactId>
|
|
<version>0.4.0-alpha</version>
|
|
<java.version>21</java.version>
|
|
`,
|
|
'packages/managed-agent-server/target/qwen-managed-agent-server-0.4.0-alpha.jar':
|
|
'server',
|
|
'packages/managed-agent-server/target/qwen-managed-agent-server-0.4.0-alpha-operator-recovery.jar':
|
|
'recovery',
|
|
'packages/managed-agent-server/src/main/resources/application.yml':
|
|
"workspace-shell-enabled: '${QWEN_MANAGED_AGENT_WORKSPACE_SHELL_ENABLED:false}'\n",
|
|
'packages/managed-agent-server/distribution/config/application.example.yml':
|
|
'template\n',
|
|
'packages/managed-agent-server/distribution/systemd/qwen-managed-agent-server.service':
|
|
'[Service]\nKillMode=process\n',
|
|
'packages/managed-agent-server/distribution/systemd/qwen-hosted-harness.service':
|
|
'ExecStart=qwen serve --profile hosted-harness --no-web\n',
|
|
'packages/managed-agent-server/distribution/systemd/harness.env.example':
|
|
'QWEN_SERVER_TOKEN=\n',
|
|
'packages/managed-agent-server/distribution/systemd/server.env.example':
|
|
'SPRING_DATASOURCE_URL=\n',
|
|
'packages/managed-agent-server/distribution/systemd/README.md': 'systemd\n',
|
|
'packages/managed-agent-server/distribution/systemd/README.zh-CN.md':
|
|
'systemd zh\n',
|
|
'packages/managed-agent-server/distribution/bin/qwen-managed-agent-bootstrap':
|
|
'#!/usr/bin/env sh\n',
|
|
'packages/managed-agent-server/distribution/bin/qwen-managed-agent-bootstrap.mjs':
|
|
'export {};\n',
|
|
'packages/managed-agent-server/src/main/resources/openapi/managed-agent-public-api.openapi.json':
|
|
JSON.stringify({ info: { version: '1.46.0' } }),
|
|
'packages/managed-agent-server/src/main/resources/db/managed-schema/V1__baseline.sql':
|
|
'select 1;',
|
|
'packages/managed-agent-server/src/main/java/db/migration/V9__latest.java':
|
|
'class Latest {}',
|
|
'packages/cli/src/serve/capabilities.ts':
|
|
"export const SERVE_PROTOCOL_VERSION = 'v1' as const;\n",
|
|
'packages/cli/src/serve/managed-runtime-broker-client.ts':
|
|
'export const MANAGED_RUNTIME_BROKER_PROTOCOL_VERSION = 2 as const;\n',
|
|
'packages/cli/src/serve/hosted-harness-contract.ts':
|
|
"const profiles = (\n process.env[HOSTED_LIFECYCLE_V2_PROFILES_ENV] ?? 'hosted-workspace-shell/1'\n).split(',');\n",
|
|
'docs/users/managed-agent-reference-deployment.md': 'deploy\n',
|
|
'docs/users/managed-agent-reference-deployment.zh-CN.md': '部署\n',
|
|
'docs/users/hosted-workspace-recovery.md': 'recover\n',
|
|
'docs/users/hosted-workspace-recovery.zh-CN.md': '恢复\n',
|
|
'packages/managed-agent-server/README.md': 'server reference\n',
|
|
};
|
|
for (const [relative, contents] of Object.entries(files)) {
|
|
const file = path.join(root, relative);
|
|
await mkdir(path.dirname(file), { recursive: true });
|
|
await writeFile(file, contents);
|
|
}
|
|
}
|
|
|
|
test('packages an independently verifiable reference distribution', async () => {
|
|
const temporary = await mkdtemp(path.join(os.tmpdir(), 'qwen-reference-'));
|
|
try {
|
|
const root = path.join(temporary, 'repo');
|
|
const output = path.join(temporary, 'release');
|
|
const secondOutput = path.join(temporary, 'release-second');
|
|
const extracted = path.join(temporary, 'extracted');
|
|
await fixture(root);
|
|
|
|
const result = await createReferenceDistribution({
|
|
repoRoot: root,
|
|
outputDirectory: output,
|
|
version: '0.1.0-alpha.1',
|
|
sourceRevision: 'a'.repeat(40),
|
|
});
|
|
|
|
assert.equal(result.manifest.source.revision, 'a'.repeat(40));
|
|
assert.equal(result.manifest.compatibility.qwenCodeVersion, '1.2.3');
|
|
assert.equal(result.manifest.compatibility.serveProtocolVersion, 'v1');
|
|
assert.equal(
|
|
result.manifest.compatibility.managedRuntimeBrokerProtocolVersion,
|
|
2,
|
|
);
|
|
assert.deepEqual(result.manifest.compatibility.databaseSchema, {
|
|
migrationTool: 'Flyway',
|
|
minimumMigrationVersion: '1',
|
|
latestMigrationVersion: '9',
|
|
});
|
|
assert.deepEqual(
|
|
result.manifest.components.map(({ name }) => name),
|
|
['qwen-managed-agent-server', 'qwen-managed-agent-operator-recovery'],
|
|
);
|
|
assert.deepEqual(result.manifest.defaults, {
|
|
publicShellEnabled: false,
|
|
v2CapabilityAdvertisementEnabled: true,
|
|
});
|
|
|
|
await mkdir(extracted);
|
|
execFileSync('tar', ['-xzf', result.archive, '-C', extracted]);
|
|
const bundle = path.join(
|
|
extracted,
|
|
'qwen-managed-agent-reference-0.1.0-alpha.1',
|
|
);
|
|
execFileSync('sha256sum', ['--check', 'SHA256SUMS'], {
|
|
cwd: bundle,
|
|
stdio: 'pipe',
|
|
});
|
|
const launcher = await readFile(
|
|
path.join(bundle, 'bin/qwen-managed-agent-server'),
|
|
'utf8',
|
|
);
|
|
assert.match(launcher, /lib\/qwen-managed-agent-server-0\.4\.0-alpha\.jar/);
|
|
assert.match(launcher, /config\/application\.yml/);
|
|
assert.match(launcher, /SPRING_CONFIG_ADDITIONAL_LOCATION/);
|
|
assert.doesNotMatch(launcher, /set --/);
|
|
assert.equal(
|
|
await readFile(
|
|
path.join(bundle, 'config/application.example.yml'),
|
|
'utf8',
|
|
),
|
|
'template\n',
|
|
);
|
|
assert.equal(
|
|
await readFile(
|
|
path.join(bundle, 'systemd/qwen-managed-agent-server.service'),
|
|
'utf8',
|
|
),
|
|
'[Service]\nKillMode=process\n',
|
|
);
|
|
assert.match(
|
|
await readFile(
|
|
path.join(bundle, 'systemd/qwen-hosted-harness.service'),
|
|
'utf8',
|
|
),
|
|
/--profile hosted-harness/,
|
|
);
|
|
assert.equal(
|
|
await readFile(
|
|
path.join(bundle, 'bin/qwen-managed-agent-bootstrap'),
|
|
'utf8',
|
|
),
|
|
'#!/usr/bin/env sh\n',
|
|
);
|
|
execFileSync('sha256sum', ['--check', 'SHA256SUMS.txt'], {
|
|
cwd: output,
|
|
stdio: 'pipe',
|
|
});
|
|
|
|
const second = await createReferenceDistribution({
|
|
repoRoot: root,
|
|
outputDirectory: secondOutput,
|
|
version: '0.1.0-alpha.1',
|
|
sourceRevision: 'a'.repeat(40),
|
|
});
|
|
assert.deepEqual(
|
|
await readFile(second.archive),
|
|
await readFile(result.archive),
|
|
);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('launchers load the distribution config only when it exists', async () => {
|
|
const temporary = await mkdtemp(path.join(os.tmpdir(), 'qwen-reference-'));
|
|
try {
|
|
const root = path.join(temporary, 'repo');
|
|
const output = path.join(temporary, 'release');
|
|
const extracted = path.join(temporary, 'extracted');
|
|
const fakeBin = path.join(temporary, 'bin');
|
|
await fixture(root);
|
|
const result = await createReferenceDistribution({
|
|
repoRoot: root,
|
|
outputDirectory: output,
|
|
version: '0.1.0-alpha.1',
|
|
sourceRevision: 'a'.repeat(40),
|
|
});
|
|
await mkdir(extracted);
|
|
execFileSync('tar', ['-xzf', result.archive, '-C', extracted]);
|
|
const bundle = path.join(
|
|
extracted,
|
|
'qwen-managed-agent-reference-0.1.0-alpha.1',
|
|
);
|
|
|
|
await mkdir(fakeBin);
|
|
const java = path.join(fakeBin, 'java');
|
|
await writeFile(
|
|
java,
|
|
'#!/bin/sh\nprintf "ENV=%s\\n" "${SPRING_CONFIG_ADDITIONAL_LOCATION:-}"\n' +
|
|
'printf "ARG=%s\\n" "$@"\n',
|
|
);
|
|
await chmod(java, 0o755);
|
|
const baseEnv = { ...process.env };
|
|
delete baseEnv.SPRING_CONFIG_ADDITIONAL_LOCATION;
|
|
const env = { ...baseEnv, PATH: `${fakeBin}:${process.env.PATH}` };
|
|
const launcherPath = path.join(bundle, 'bin/qwen-managed-agent-server');
|
|
const recoveryPath = path.join(
|
|
bundle,
|
|
'bin/qwen-managed-agent-operator-recovery',
|
|
);
|
|
const run = (script, args, overrides = {}) =>
|
|
execFileSync('sh', [script, ...args], {
|
|
env: { ...env, ...overrides },
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
const withoutConfig = run(launcherPath, ['--spring.profiles.active=test']);
|
|
assert.ok(withoutConfig.includes('ENV=\n'));
|
|
assert.ok(withoutConfig.includes('ARG=--spring.profiles.active=test'));
|
|
assert.ok(
|
|
!withoutConfig.includes('ARG=--spring.config.additional-location'),
|
|
);
|
|
|
|
const config = path.join(bundle, 'config/application.yml');
|
|
await writeFile(config, 'qwen: {}\n');
|
|
const withConfig = run(launcherPath, ['--spring.profiles.active=test']);
|
|
assert.ok(withConfig.includes(`ENV=file:${config}`));
|
|
assert.ok(withConfig.includes('ARG=--spring.profiles.active=test'));
|
|
assert.ok(!withConfig.includes('ARG=--spring.config.additional-location'));
|
|
|
|
const operator = run(launcherPath, [], {
|
|
SPRING_CONFIG_ADDITIONAL_LOCATION: '/tmp/operator.yml',
|
|
});
|
|
assert.ok(operator.includes('ENV=/tmp/operator.yml'));
|
|
|
|
const recovery = run(recoveryPath, ['inspect', 'binding-1', '7']);
|
|
assert.ok(recovery.includes(`ENV=file:${config}`));
|
|
assert.ok(recovery.includes('ARG=-jar'));
|
|
assert.ok(recovery.includes('ARG=inspect'));
|
|
assert.ok(recovery.includes('ARG=binding-1'));
|
|
assert.ok(recovery.includes('ARG=7'));
|
|
assert.ok(!recovery.includes('ARG=--spring.config.additional-location'));
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('ships systemd units with the documented kill modes', async () => {
|
|
const directory = new URL(
|
|
'../../packages/managed-agent-server/distribution/systemd/',
|
|
import.meta.url,
|
|
);
|
|
const server = await readFile(
|
|
new URL('qwen-managed-agent-server.service', directory),
|
|
'utf8',
|
|
);
|
|
const harness = await readFile(
|
|
new URL('qwen-hosted-harness.service', directory),
|
|
'utf8',
|
|
);
|
|
|
|
assert.match(server, /^KillMode=process$/m);
|
|
assert.match(server, /^StateDirectory=qwen-managed-agent$/m);
|
|
assert.match(
|
|
server,
|
|
/^EnvironmentFile=\/etc\/qwen-managed-agent\/server\.env$/m,
|
|
);
|
|
assert.match(
|
|
server,
|
|
/^ExecStart=\/opt\/qwen-managed-agent\/bin\/qwen-managed-agent-server$/m,
|
|
);
|
|
assert.match(harness, /^KillMode=mixed$/m);
|
|
assert.match(harness, /^StateDirectory=qwen-managed-agent$/m);
|
|
assert.match(harness, /--profile hosted-harness/);
|
|
assert.match(harness, /--no-web/);
|
|
assert.match(harness, /QWEN_HOSTED_HARNESS_BROKER_ARGS/);
|
|
assert.ok(server.endsWith('\n'));
|
|
assert.ok(harness.endsWith('\n'));
|
|
assert.doesNotMatch(server, /\t/);
|
|
assert.doesNotMatch(harness, /\t/);
|
|
});
|
|
|
|
test('bootstrap renders matching credentials and a 32-byte key', () => {
|
|
const secrets = generateSecrets('reference-test');
|
|
assert.match(secrets.digest, /^sha256:[0-9a-f]{64}$/);
|
|
assert.equal(Buffer.from(secrets.credentialKey, 'base64').length, 32);
|
|
const harness = renderHarnessEnv(secrets);
|
|
assert.ok(harness.includes(secrets.token));
|
|
// The control plane refuses a `;` comment, which a shell stops reading the file at.
|
|
assert.doesNotMatch(harness, /^\s*;/m);
|
|
const server = renderServerEnv(
|
|
secrets,
|
|
'/tmp/application.yml',
|
|
'/tmp/harness.env',
|
|
);
|
|
assertReadsHarnessCredentials(server, secrets, '/tmp/harness.env');
|
|
assertDerivesSessionStoreWiring(server);
|
|
assert.match(
|
|
renderApplicationYml(secrets),
|
|
/credential-key-id: 'reference-test'/,
|
|
);
|
|
});
|
|
|
|
test('bootstrap writes private files without printing secrets', async () => {
|
|
const temporary = await mkdtemp(path.join(os.tmpdir(), 'qwen-bootstrap-'));
|
|
try {
|
|
const directory = path.join(temporary, 'config');
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const output = execFileSync(
|
|
process.execPath,
|
|
[script, '--directory', directory],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
const harness = await readFile(path.join(directory, 'harness.env'), 'utf8');
|
|
const server = await readFile(path.join(directory, 'server.env'), 'utf8');
|
|
const application = await readFile(
|
|
path.join(directory, 'application.yml'),
|
|
'utf8',
|
|
);
|
|
const token = harness.match(/^QWEN_SERVER_TOKEN=([0-9a-f]{64})$/m)[1];
|
|
const digest = harness.match(
|
|
/^QWEN_HOSTED_HARNESS_CAPABILITY_DIGEST=(sha256:[0-9a-f]{64})$/m,
|
|
)[1];
|
|
assertReadsHarnessCredentials(
|
|
server,
|
|
{ token, digest },
|
|
path.join(directory, 'harness.env'),
|
|
);
|
|
assert.match(application, /credential-key: '[A-Za-z0-9+/=]{44}'/);
|
|
for (const name of ['harness.env', 'server.env', 'application.yml']) {
|
|
assert.equal(
|
|
(await stat(path.join(directory, name))).mode & 0o777,
|
|
0o600,
|
|
);
|
|
}
|
|
assert.ok(!output.includes(token));
|
|
assert.throws(
|
|
() =>
|
|
execFileSync(process.execPath, [script, '--directory', directory], {
|
|
stdio: 'pipe',
|
|
}),
|
|
(error) => error.status === 1,
|
|
);
|
|
execFileSync(
|
|
process.execPath,
|
|
[script, '--directory', directory, '--force'],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('bootstrap runs through a symlinked entry point', async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-bootstrap-link-'),
|
|
);
|
|
try {
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const link = path.join(temporary, 'bootstrap-link.mjs');
|
|
await symlink(script, link);
|
|
const directory = path.join(temporary, 'config');
|
|
execFileSync(process.execPath, [link, '--directory', directory], {
|
|
encoding: 'utf8',
|
|
});
|
|
const harness = await readFile(path.join(directory, 'harness.env'), 'utf8');
|
|
assert.match(harness, /QWEN_SERVER_TOKEN=[0-9a-f]{64}/);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('bootstrap quotes the configuration path for shell sourcing', async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-bootstrap-path-'),
|
|
);
|
|
try {
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const directory = path.join(temporary, 'config-$PR14634');
|
|
execFileSync(process.execPath, [script, '--directory', directory], {
|
|
encoding: 'utf8',
|
|
});
|
|
const value = execFileSync(
|
|
'sh',
|
|
[
|
|
'-c',
|
|
'set -a; . "$1"; printf %s "$SPRING_CONFIG_ADDITIONAL_LOCATION"',
|
|
'sh',
|
|
path.join(directory, 'server.env'),
|
|
],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
assert.equal(value, `file:${path.join(directory, 'application.yml')}`);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('bootstrap rejects a configuration path it cannot quote', async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-bootstrap-quote-'),
|
|
);
|
|
try {
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const directory = path.join(temporary, "config-'");
|
|
assert.throws(
|
|
() =>
|
|
execFileSync(process.execPath, [script, '--directory', directory], {
|
|
stdio: 'pipe',
|
|
}),
|
|
(error) => error.status === 1,
|
|
);
|
|
await assert.rejects(readFile(path.join(directory, 'harness.env')));
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('bootstrap rejects a configuration path the location list splits on', async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-bootstrap-comma-'),
|
|
);
|
|
try {
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const directory = path.join(temporary, 'config,one');
|
|
assert.throws(
|
|
() =>
|
|
execFileSync(process.execPath, [script, '--directory', directory], {
|
|
stdio: 'pipe',
|
|
}),
|
|
(error) => error.status === 1,
|
|
);
|
|
await assert.rejects(readFile(path.join(directory, 'harness.env')));
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('bootstrap refuses to run while another bootstrap holds the lock', async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-bootstrap-lock-'),
|
|
);
|
|
try {
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/bin/' +
|
|
'qwen-managed-agent-bootstrap.mjs',
|
|
import.meta.url,
|
|
),
|
|
);
|
|
const directory = path.join(temporary, 'config');
|
|
await mkdir(directory, { recursive: true });
|
|
await writeFile(path.join(directory, '.bootstrap.lock'), '', {
|
|
mode: 0o600,
|
|
});
|
|
assert.throws(
|
|
() =>
|
|
execFileSync(process.execPath, [script, '--directory', directory], {
|
|
stdio: 'pipe',
|
|
}),
|
|
(error) => error.status === 1,
|
|
);
|
|
await assert.rejects(readFile(path.join(directory, 'harness.env')));
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('leaves the Session Store wiring to the server defaults in the env template', async () => {
|
|
const template = await readFile(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/systemd/server.env.example',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
);
|
|
assert.match(template, /^QWEN_MANAGED_AGENT_HARNESS_ENABLED=true$/m);
|
|
assertDerivesSessionStoreWiring(template);
|
|
assert.doesNotMatch(
|
|
template,
|
|
/^QWEN_MANAGED_AGENT_(HARNESS_TOKEN|CAPABILITY_DIGEST)=/m,
|
|
);
|
|
assert.match(
|
|
template,
|
|
/^QWEN_MANAGED_AGENT_HARNESS_CREDENTIALS_FILE=\/etc\/qwen-managed-agent\/harness\.env$/m,
|
|
);
|
|
});
|
|
|
|
// The server derives the store switch, URL and workspace ID; an explicit
|
|
// workspace ID would conflict with the local-process Broker's derived one.
|
|
function assertDerivesSessionStoreWiring(env) {
|
|
assert.doesNotMatch(env, /^QWEN_MANAGED_AGENT_SESSION_STORE_/m);
|
|
assert.doesNotMatch(env, /^QWEN_MANAGED_AGENT_WORKSPACE_ID=/m);
|
|
assert.match(
|
|
env,
|
|
/^QWEN_MANAGED_AGENT_WORKSPACE_CWD=\/srv\/qwen-workspace$/m,
|
|
);
|
|
}
|
|
|
|
// harness.env is the one copy of the shared credentials; server.env names it.
|
|
function assertReadsHarnessCredentials(server, { token, digest }, harnessEnv) {
|
|
assert.ok(!server.includes(token));
|
|
assert.ok(!server.includes(digest));
|
|
assert.doesNotMatch(
|
|
server,
|
|
/^QWEN_MANAGED_AGENT_(HARNESS_TOKEN|CAPABILITY_DIGEST)=/m,
|
|
);
|
|
assert.ok(
|
|
server
|
|
.split('\n')
|
|
.includes(`QWEN_MANAGED_AGENT_HARNESS_CREDENTIALS_FILE='${harnessEnv}'`),
|
|
);
|
|
}
|
|
|
|
test('ships the Harness credential keys the control plane reads', async () => {
|
|
const template = await readFile(
|
|
new URL(
|
|
'../../packages/managed-agent-server/distribution/systemd/harness.env.example',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
);
|
|
// The control plane matches these keys exactly, from the start of a line,
|
|
// and accepts only these characters in their values.
|
|
assert.match(template, /^QWEN_SERVER_TOKEN=[A-Za-z0-9._:/+=-]+$/m);
|
|
assert.match(
|
|
template,
|
|
/^QWEN_HOSTED_HARNESS_CAPABILITY_DIGEST=sha256:[A-Za-z0-9._:/+=-]+$/m,
|
|
);
|
|
// It refuses a `;` comment, which a shell stops reading the file at.
|
|
assert.doesNotMatch(template, /^\s*;/m);
|
|
});
|
|
|
|
test('rejects a stable distribution version', async () => {
|
|
await assert.rejects(
|
|
createReferenceDistribution({
|
|
repoRoot: '.',
|
|
outputDirectory: '.',
|
|
version: '1.0.0',
|
|
sourceRevision: 'a'.repeat(40),
|
|
}),
|
|
/experimental SemVer/,
|
|
);
|
|
});
|
|
|
|
test('rejects a numeric prerelease identifier with a leading zero', async () => {
|
|
await assert.rejects(
|
|
createReferenceDistribution({
|
|
repoRoot: '.',
|
|
outputDirectory: '.',
|
|
version: '1.0.0-01',
|
|
sourceRevision: 'a'.repeat(40),
|
|
}),
|
|
/experimental SemVer/,
|
|
);
|
|
});
|
|
|
|
test('rejects stale or ambiguous safety defaults', async (context) => {
|
|
const cases = [
|
|
{
|
|
name: 'commented public Shell default',
|
|
file: 'packages/managed-agent-server/src/main/resources/application.yml',
|
|
contents:
|
|
"# workspace-shell-enabled: '${QWEN_MANAGED_AGENT_WORKSPACE_SHELL_ENABLED:false}'\nworkspace-shell-enabled: '${QWEN_MANAGED_AGENT_WORKSPACE_SHELL_ENABLED:true}'\n",
|
|
error: /Public Shell must default to false/,
|
|
},
|
|
{
|
|
name: 'commented lifecycle v2 default',
|
|
file: 'packages/cli/src/serve/hosted-harness-contract.ts',
|
|
contents:
|
|
"/*\nprocess.env[HOSTED_LIFECYCLE_V2_PROFILES_ENV] ?? 'hosted-workspace-shell/1'\n*/\nconst profiles = (\n process.env[HOSTED_LIFECYCLE_V2_PROFILES_ENV] ?? ''\n).split(',');\n",
|
|
error:
|
|
/Hosted lifecycle v2 profiles must default to canonical Shell only/,
|
|
},
|
|
{
|
|
name: 'duplicate public Shell default',
|
|
file: 'packages/managed-agent-server/src/main/resources/application.yml',
|
|
contents:
|
|
"workspace-shell-enabled: '${QWEN_MANAGED_AGENT_WORKSPACE_SHELL_ENABLED:false}'\nworkspace-shell-enabled: '${QWEN_MANAGED_AGENT_WORKSPACE_SHELL_ENABLED:false}'\n",
|
|
error: /Cannot uniquely read public Shell default/,
|
|
},
|
|
];
|
|
|
|
for (const candidate of cases) {
|
|
await context.test(candidate.name, async () => {
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), 'qwen-reference-'),
|
|
);
|
|
try {
|
|
const root = path.join(temporary, 'repo');
|
|
await fixture(root);
|
|
await writeFile(path.join(root, candidate.file), candidate.contents);
|
|
await assert.rejects(
|
|
createReferenceDistribution({
|
|
repoRoot: root,
|
|
outputDirectory: path.join(temporary, 'release'),
|
|
version: '0.1.0-alpha.1',
|
|
sourceRevision: 'a'.repeat(40),
|
|
}),
|
|
candidate.error,
|
|
);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test('publishes verified draft assets behind an exact tag', async () => {
|
|
const workflow = await readFile(
|
|
new URL(
|
|
'../../.github/workflows/release-managed-agent-reference.yml',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
);
|
|
const draft = workflow.indexOf('draft:true');
|
|
const upload = workflow.indexOf('gh release upload');
|
|
const assetVerification = workflow.indexOf('actual_names=');
|
|
const digestVerification = workflow.indexOf('remote_digest=');
|
|
const digestMismatch = workflow.indexOf('Uploaded digest mismatch');
|
|
const tag = workflow.indexOf('git/refs');
|
|
const publish = workflow.indexOf('-F draft=false');
|
|
|
|
assert.ok(draft >= 0);
|
|
assert.ok(draft < upload);
|
|
assert.ok(upload < assetVerification);
|
|
assert.ok(assetVerification < digestVerification);
|
|
assert.ok(digestVerification < digestMismatch);
|
|
assert.ok(digestMismatch < tag);
|
|
assert.ok(assetVerification < tag);
|
|
assert.ok(tag < publish);
|
|
assert.match(workflow, /Accept: application\/octet-stream/);
|
|
assert.match(workflow, /remote_digest.*!=.*local_digest/);
|
|
assert.match(workflow, /scripts\/contract-version-utils\.js/);
|
|
assert.match(workflow, /scripts\/flyway-migration-utils\.js/);
|
|
assert.match(workflow, /grep -x 'managed-agent-reference-v\.\*'/);
|
|
assert.match(workflow, /previous_tag_name:\$previous/);
|
|
});
|