import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; const ROOT = path.resolve(new URL('..', import.meta.url).pathname); const CANARY_RELEASE = path.join(ROOT, 'scripts', 'release-portal-canary-prod.sh'); const CANARY_ROLLBACK = path.join(ROOT, 'scripts', 'rollback-portal-canary-prod.sh'); function heredocBody(source, opener, closer) { const start = source.indexOf(opener); assert.ok(start >= 0, `missing heredoc opener: ${opener}`); const bodyStart = source.indexOf('\n', start) + 1; const end = source.indexOf(`\n${closer}\n`, bodyStart); assert.ok(bodyStart > 0 && end >= bodyStart, `missing heredoc closer: ${closer}`); return source.slice(bodyStart, end); } function assertShellParses(source, label) { const result = spawnSync('bash', ['-n'], { cwd: ROOT, encoding: 'utf8', input: source, }); assert.equal(result.status, 0, `${label} failed bash -n:\n${result.stderr}`); } test('production release verifies gate report before 103 preflight and upload', async () => { const source = await fs.readFile( path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'), 'utf8', ); const gateIndex = source.indexOf('verify-release-gate-report.mjs'); const preflightIndex = source.indexOf('执行 103 只读预检'); const uploadIndex = source.indexOf('上传 Portal runtime 到 103'); assert.ok(gateIndex > 0, 'missing gate verifier'); assert.ok(preflightIndex > gateIndex, '103 preflight must run after gate verification'); assert.ok(uploadIndex > preflightIndex, 'upload must run after preflight'); assert.match(source, /生产发布守门员禁止 --skip-tests/); assert.match(source, /禁止 ALLOW_PORTAL_RELEASE_SCOPE_BYPASS/); }); test('production stable release verifies canary promotion evidence before gate checks', async () => { const source = await fs.readFile( path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'), 'utf8', ); const promotionIndex = source.indexOf('verify-canary-promotion-evidence.mjs'); const gateIndex = source.indexOf('verify-release-gate-report.mjs'); const impactIndex = source.indexOf('run-release-gate-impact.mjs'); assert.ok(promotionIndex > 0, 'missing canary promotion evidence verifier'); assert.ok(gateIndex > promotionIndex, 'gate verification must follow promotion evidence'); assert.ok(impactIndex > 0, 'missing risk-based impact gate fallback'); assert.doesNotMatch(source, /在同一候选完成 103 灰度验收且晋升证据校验落地前,禁止非 dry-run/); assert.match(source, /read_agent_run_status_json/); assert.match(source, /sed -n '\/\^\{/); assert.match(source, /for _ in \$\(seq 1 120\)/); }); test('runtime builder removes every persisted path forbidden by artifact policy', async () => { const source = await fs.readFile( path.join(ROOT, 'scripts', 'build-portal-runtime.mjs'), 'utf8', ); assert.match(source, /removeForbiddenPortalRuntimePaths/); assert.match(source, /removeForbiddenPortalRuntimePaths\(runtimeRoot\)/); }); test('runtime builder materializes and declares required Linux ARM64 native packages', async () => { const source = await fs.readFile( path.join(ROOT, 'scripts', 'build-portal-runtime.mjs'), 'utf8', ); assert.match(source, /@resvg\/resvg-js-linux-arm64-gnu/); assert.match(source, /@node-rs\/argon2-linux-arm64-gnu/); assert.match(source, /installLinuxArm64NativePackages\(\)/); assert.match(source, /optionalDependencies: runtimeOptionalDependencies/); assert.match(source, /缺少原生 \.node 文件/); }); test('runtime builder bundles the DeepSeek compatibility dependency into the artifact', async () => { const source = await fs.readFile( path.join(ROOT, 'scripts', 'build-portal-runtime.mjs'), 'utf8', ); assert.match(source, /bundleDeepseekCompatProxy/); assert.match(source, /--outfile=\.runtime\/portal\/deepseek-no-think-proxy\.mjs/); assert.match(source, /await bundleDeepseekCompatProxy\(\)/); }); test('packaged runtime gate isolates persistent roots and rejects artifact mutation', async () => { const localStackSource = await fs.readFile( path.join(ROOT, 'release-gate', 'local-stack.mjs'), 'utf8', ); assert.match(localStackSource, /MEMIND_PORTAL_H5_ROOT: sandboxRoot/); assert.match(localStackSource, /MEMIND_DEEPSEEK_DISABLE_THINKING: '1'/); assert.match( localStackSource, /path\.join\(resolvedPortalRoot, 'deepseek-no-think-proxy\.mjs'\)/, ); assert.match(localStackSource, /MEMIND_ORCHESTRATOR_MODE: 'shadow'/); assert.match(localStackSource, /MEMIND_ORCHESTRATOR_PAGE_DATA_VALIDATION_GATE_ENABLED: '1'/); assert.match(localStackSource, /H5_USERS_ROOT: usersRoot/); assert.match(localStackSource, /MINDSPACE_STORAGE_ROOT: storageRoot/); assert.match(localStackSource, /MEMIND_SHARED_PUBLISH_ROOT: publishRoot/); for (const scriptName of [ 'run-release-gate-runtime-cold-start.mjs', 'run-release-gate-runtime-upgrade.mjs', ]) { const scriptSource = await fs.readFile(path.join(ROOT, 'scripts', scriptName), 'utf8'); assert.match(scriptSource, /const artifactBefore = await hashArtifact\(runtimeRoot\)/); assert.match(scriptSource, /const artifactAfter = await hashArtifact\(runtimeRoot\)/); assert.match(scriptSource, /artifactAfter\.sha256 !== artifactBefore\.sha256/); } }); test('production release rejects --skip-tests before repository or network preflight', () => { const result = spawnSync( 'bash', [path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'), '--skip-tests'], { cwd: ROOT, encoding: 'utf8' }, ); assert.notEqual(result.status, 0); assert.match(result.stderr, /禁止 --skip-tests/); assert.doesNotMatch(`${result.stdout}\n${result.stderr}`, /103 只读预检/); }); test('production canary verifies the exact Gate artifact before any 103 preflight or upload', async () => { const source = await fs.readFile(CANARY_RELEASE, 'utf8'); const gateIndex = source.indexOf('verify-release-gate-report.mjs'); const preflightIndex = source.indexOf('Run 103 read-only preflight'); const uploadIndex = source.indexOf('Upload the verified candidate bundle'); assert.ok(gateIndex > 0, 'missing canary Gate verifier'); assert.ok(preflightIndex > gateIndex, '103 preflight must follow Gate verification'); assert.ok(uploadIndex > preflightIndex, 'upload must follow read-only preflight'); assert.match(source, /branch.*!= "main"/); assert.match(source, /rev-parse origin\/main/); assert.match(source, /Production canary release forbids/); assert.match(source, /run-release-gate-impact\.mjs/); assert.match(source, /MEMIND_RELEASE_BASE_COMMIT/); assert.match(source, /-z "\$\{DEPLOYED_SHA\}".*"\$\{DRY_RUN\}" -ne 1/); }); test('production canary keeps stable 8081 live and switches only after verified backups and fallback', async () => { const source = await fs.readFile(CANARY_RELEASE, 'utf8'); const fullBackup = source.indexOf('Create and verify the full stable backup'); const persistBackup = source.indexOf('Create and verify the persisted-data backup'); const edgeBackup = source.indexOf('Create and verify the active 105 nginx routing backup'); const goosedStart = source.indexOf('Start an isolated goosed candidate on 18015'); const candidateStart = source.indexOf('Start the passive candidate Portal on 18081'); const deepseekCompatStart = source.indexOf( 'Start the DeepSeek tool-round compatibility proxy on 18036', ); const proxyStart = source.indexOf('Start the fail-closed identity router on 18082'); const tunnelStart = source.indexOf('Start the isolated 105 reverse tunnel on 19082'); const edgeSwitch = source.indexOf( 'Switch the committed 105 nginx upstreams to the isolated canary tunnel', ); const fallbackProbe = source.indexOf('route-probe?username=john'); assert.ok(fullBackup > 0); assert.ok(persistBackup > fullBackup); assert.ok(edgeBackup > persistBackup); assert.ok(goosedStart > edgeBackup); assert.ok(deepseekCompatStart > goosedStart); assert.ok(candidateStart > deepseekCompatStart); assert.ok(proxyStart > candidateStart); assert.ok(tunnelStart > proxyStart); assert.ok(edgeSwitch > tunnelStart); assert.ok(fallbackProbe > edgeSwitch); assert.match(source, /restore_edge_to_stable/); assert.match(source, /proxy_pass http:\/\/58\.38\.22\.103:8081;/); assert.match(source, /proxy_pass http:\/\/127\.0\.0\.1:\$\{CANARY_TUNNEL_REMOTE_PORT\};/); assert.match(source, /nginx -t/); assert.match(source, /CANARY_PROXY_PORT=18082/); assert.match(source, /CANARY_TUNNEL_REMOTE_PORT=19082/); assert.match(source, /DEEPSEEK_COMPAT_PORT=18036/); assert.match(source, /deepseek-no-think-proxy\.mjs/); assert.match(source, /run-deepseek-compat-proxy-candidate\.sh/); assert.match(source, /MEMIND_CANARY_CANDIDATE_HEALTH_URLS/); assert.match(source, /deepseek_compat_healthy/); assert.match(source, /bootout.*DEEPSEEK_COMPAT_LABEL/); assert.match( source, /edge_ssh\(\) \{\s*#.*\n\s*#.*\n\s*ssh -n -o BatchMode=yes/, ); assert.match( source, /ssh -o BatchMode=yes -o ConnectTimeout=10 "\$\{EDGE_HOST\}" \/bin\/bash <\/dev\/null 2>&1; then\s+stop_candidate_services/, ); assert.match(source, /keeping canary services running/); assert.match(source, /--resolve wechat\.m\.tkmind\.cn:443:127\.0\.0\.1/); assert.doesNotMatch(source, /lsof -tiTCP:8081/); assert.doesNotMatch(source, /bootout.*cn\.tkmind\.memind-portal/); }); test('candidate runner overrides stable host MCP paths with container-visible paths', async (t) => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-canary-runner-')); t.after(() => fs.rm(tempRoot, { recursive: true, force: true })); const stableRoot = path.join(tempRoot, 'stable'); const fakeNode = path.join(tempRoot, 'node'); await fs.mkdir(stableRoot); await fs.writeFile( path.join(stableRoot, '.env'), [ 'GOOSED_MCP_NODE_PATH=/opt/homebrew/opt/node@24/bin/node', 'GOOSED_MCP_SERVER_PATH=/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs', 'MEMIND_DEEPSEEK_DISABLE_THINKING=0', 'MEMIND_DEEPSEEK_NO_THINK_PORT=9999', 'MEMIND_DEEPSEEK_NO_THINK_BASE_URL=http://wrong.invalid/v1', 'MEMIND_DEEPSEEK_NO_THINK_HOST=wrong.invalid', 'MEMIND_GOOSED_HOST_GATEWAY=wrong.invalid', '', ].join('\n'), ); await fs.writeFile( fakeNode, [ '#!/usr/bin/env bash', 'printf "cwd=%s\\n" "$PWD"', 'printf "mcp_node=%s\\n" "$GOOSED_MCP_NODE_PATH"', 'printf "mcp_server=%s\\n" "$GOOSED_MCP_SERVER_PATH"', 'printf "deepseek_disable=%s\\n" "$MEMIND_DEEPSEEK_DISABLE_THINKING"', 'printf "deepseek_port=%s\\n" "$MEMIND_DEEPSEEK_NO_THINK_PORT"', 'printf "deepseek_gateway=%s\\n" "$MEMIND_GOOSED_HOST_GATEWAY"', 'printf "deepseek_base=%s\\n" "${MEMIND_DEEPSEEK_NO_THINK_BASE_URL-unset}"', 'printf "deepseek_host=%s\\n" "${MEMIND_DEEPSEEK_NO_THINK_HOST-unset}"', 'printf "entrypoint=%s\\n" "$1"', '', ].join('\n'), { mode: 0o755 }, ); const result = spawnSync( 'bash', [path.join(ROOT, 'scripts', 'run-memind-portal-candidate.sh')], { cwd: '/', encoding: 'utf8', env: { ...process.env, MEMIND_CANARY_STABLE_ROOT: stableRoot, MEMIND_CANARY_RELEASE_ID: 'test-release', MEMIND_CANARY_CANDIDATE_PORT: '65534', NODE_BIN: fakeNode, }, }, ); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, new RegExp(`^cwd=${ROOT}$`, 'm')); assert.match(result.stdout, /^mcp_node=\/usr\/local\/bin\/node$/m); assert.match( result.stdout, /^mcp_server=\/opt\/portal\/mindspace-sandbox-mcp\.mjs$/m, ); assert.match(result.stdout, /^deepseek_disable=1$/m); assert.match(result.stdout, /^deepseek_port=18036$/m); assert.match(result.stdout, /^deepseek_gateway=host\.docker\.internal$/m); assert.match(result.stdout, /^deepseek_base=unset$/m); assert.match(result.stdout, /^deepseek_host=unset$/m); assert.match(result.stdout, new RegExp(`^entrypoint=${ROOT}/server\\.mjs$`, 'm')); }); test('canary rollback stops the DeepSeek compatibility process with the other canary services', async () => { const source = await fs.readFile(CANARY_ROLLBACK, 'utf8'); assert.match( source, /DEEPSEEK_COMPAT_LABEL="cn\.tkmind\.memind-deepseek-compat-candidate"/, ); assert.match(source, /bootout.*DEEPSEEK_COMPAT_LABEL/); assert.match(source, /docker rm -f goosed-prod-canary/); }); test('DeepSeek compatibility runner preserves stable upstream config but enforces candidate controls', async (t) => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-deepseek-runner-')); t.after(() => fs.rm(tempRoot, { recursive: true, force: true })); const stableRoot = path.join(tempRoot, 'stable'); const fakeNode = path.join(tempRoot, 'node'); await fs.mkdir(stableRoot); await fs.writeFile( path.join(stableRoot, '.env'), [ 'DEEPSEEK_API_BASE_URL=https://synthetic-upstream.invalid/v1', 'MEMIND_DEEPSEEK_PROXY_ENTRYPOINT=0', 'MEMIND_DEEPSEEK_NO_THINK_PORT=9999', '', ].join('\n'), ); await fs.writeFile( fakeNode, [ '#!/usr/bin/env bash', 'printf "proxy_entrypoint=%s\\n" "$MEMIND_DEEPSEEK_PROXY_ENTRYPOINT"', 'printf "proxy_port=%s\\n" "$MEMIND_DEEPSEEK_NO_THINK_PORT"', 'printf "upstream=%s\\n" "$DEEPSEEK_API_BASE_URL"', 'printf "entrypoint=%s\\n" "$1"', '', ].join('\n'), { mode: 0o755 }, ); const result = spawnSync( 'bash', [path.join(ROOT, 'scripts', 'run-deepseek-compat-proxy-candidate.sh')], { cwd: '/', encoding: 'utf8', env: { ...process.env, MEMIND_CANARY_STABLE_ROOT: stableRoot, MEMIND_CANARY_DEEPSEEK_PROXY_PORT: '18036', NODE_BIN: fakeNode, }, }, ); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /^proxy_entrypoint=1$/m); assert.match(result.stdout, /^proxy_port=18036$/m); assert.match(result.stdout, /^upstream=https:\/\/synthetic-upstream\.invalid\/v1$/m); assert.match( result.stdout, new RegExp(`^entrypoint=${ROOT}/deepseek-no-think-proxy\\.mjs$`, 'm'), ); }); test('canary release and rollback remote shells remain syntactically valid', async () => { const releaseSource = await fs.readFile(CANARY_RELEASE, 'utf8'); const rollbackSource = await fs.readFile(CANARY_ROLLBACK, 'utf8'); const releaseRemote = heredocBody(releaseSource, "<<'REMOTE_SCRIPT'", 'REMOTE_SCRIPT'); const rollbackRemote = heredocBody(rollbackSource, "<<'REMOTE'", 'REMOTE'); const edgeSwitch = heredocBody(releaseRemote, '< { const source = await fs.readFile( path.join(ROOT, 'scripts', 'check-release-ready.sh'), 'utf8', ); assert.doesNotMatch(source, /:\(exclude\)\.runtime\//); });