fix: align DeepSeek canary tool rounds with gate
Memind CI / Test, build, and release guards (pull_request) Successful in 1m32s

This commit is contained in:
john
2026-07-26 23:49:32 +08:00
parent 046704816f
commit fb6865638a
19 changed files with 519 additions and 20 deletions
+2
View File
@@ -6,6 +6,7 @@ import path from 'node:path';
export const REQUIRED_PORTAL_RUNTIME_PATHS = Object.freeze([
'server.mjs',
'memind-canary-proxy.mjs',
'deepseek-no-think-proxy.mjs',
'wechat-mp.bundle.mjs',
'mindspace-sandbox-mcp.mjs',
'tkmind-search-mcp.mjs',
@@ -16,6 +17,7 @@ export const REQUIRED_PORTAL_RUNTIME_PATHS = Object.freeze([
'scripts/run-memind-portal-prod.sh',
'scripts/run-memind-portal-candidate.sh',
'scripts/run-memind-canary-proxy-prod.sh',
'scripts/run-deepseek-compat-proxy-candidate.sh',
'scripts/goosed-canary.compose.yml',
'scripts/check-mindspace-public-links.mjs',
'scripts/load-env.mjs',
@@ -0,0 +1,89 @@
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { startDeepseekNoThinkProxy } from '../deepseek-no-think-proxy.mjs';
const ROOT = path.resolve(new URL('..', import.meta.url).pathname);
test('sanitized production tool-round replay reaches DeepSeek with thinking disabled', async (t) => {
const forwarded = [];
const proxy = await startDeepseekNoThinkProxy({
host: '127.0.0.1',
port: 0,
logger: { info() {}, warn() {}, error() {} },
fetchImpl: async (_url, init) => {
forwarded.push(JSON.parse(String(init.body)));
return new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'synthetic result' } }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
},
});
t.after(() => new Promise((resolve) => proxy.close(resolve)));
const address = proxy.address();
const response = await fetch(
`http://127.0.0.1:${address.port}/v1/chat/completions`,
{
method: 'POST',
headers: {
authorization: 'Bearer synthetic-key',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'deepseek-v4-pro',
thinking: { type: 'enabled' },
messages: [
{ role: 'user', content: 'Use the synthetic filesystem tool.' },
{ role: 'assistant', tool_calls: [{ id: 'call-1', type: 'function' }] },
{ role: 'tool', tool_call_id: 'call-1', content: 'synthetic file list' },
],
}),
},
);
assert.equal(response.status, 200);
assert.equal(forwarded.length, 1);
assert.deepEqual(forwarded[0].thinking, { type: 'disabled' });
assert.equal(forwarded[0].messages[2].role, 'tool');
});
test('Gate, artifact, candidate routing and rollback share one compatibility contract', async () => {
const [
builder,
localStack,
candidateRunner,
compatRunner,
canaryRelease,
canaryRollback,
] = await Promise.all([
fs.readFile(path.join(ROOT, 'scripts', 'build-portal-runtime.mjs'), 'utf8'),
fs.readFile(path.join(ROOT, 'release-gate', 'local-stack.mjs'), 'utf8'),
fs.readFile(path.join(ROOT, 'scripts', 'run-memind-portal-candidate.sh'), 'utf8'),
fs.readFile(
path.join(ROOT, 'scripts', 'run-deepseek-compat-proxy-candidate.sh'),
'utf8',
),
fs.readFile(path.join(ROOT, 'scripts', 'release-portal-canary-prod.sh'), 'utf8'),
fs.readFile(path.join(ROOT, 'scripts', 'rollback-portal-canary-prod.sh'), 'utf8'),
]);
assert.match(builder, /--outfile=\.runtime\/portal\/deepseek-no-think-proxy\.mjs/);
assert.match(
localStack,
/path\.join\(resolvedPortalRoot, 'deepseek-no-think-proxy\.mjs'\)/,
);
assert.match(candidateRunner, /export MEMIND_DEEPSEEK_DISABLE_THINKING=1/);
assert.match(candidateRunner, /export MEMIND_GOOSED_HOST_GATEWAY=host\.docker\.internal/);
assert.match(compatRunner, /source "\$\{STABLE_ROOT\}\/\.env"/);
assert.match(compatRunner, /export MEMIND_DEEPSEEK_PROXY_ENTRYPOINT=1/);
assert.match(canaryRelease, /run-deepseek-compat-proxy-candidate\.sh/);
assert.match(canaryRelease, /MEMIND_CANARY_CANDIDATE_HEALTH_URLS/);
assert.match(canaryRelease, /host\.docker\.internal:\$\{DEEPSEEK_COMPAT_PORT\}\/health/);
assert.match(canaryRelease, /bootout.*DEEPSEEK_COMPAT_LABEL/);
assert.match(canaryRollback, /bootout.*DEEPSEEK_COMPAT_LABEL/);
});
+1 -1
View File
@@ -380,7 +380,7 @@ export async function createLocalGateStack({
deepseekProxyLogFd = fs.openSync(deepseekProxyLogPath, 'a');
deepseekProxyChild = spawn(
process.execPath,
[path.join(root, 'deepseek-no-think-proxy.mjs')],
[path.join(resolvedPortalRoot, 'deepseek-no-think-proxy.mjs')],
{
cwd: root,
env: {
+104 -2
View File
@@ -63,6 +63,16 @@ test('runtime builder materializes and declares required Linux ARM64 native pack
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'),
@@ -70,7 +80,10 @@ test('packaged runtime gate isolates persistent roots and rejects artifact mutat
);
assert.match(localStackSource, /MEMIND_PORTAL_H5_ROOT: sandboxRoot/);
assert.match(localStackSource, /MEMIND_DEEPSEEK_DISABLE_THINKING: '1'/);
assert.match(localStackSource, /deepseek-no-think-proxy\.mjs/);
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/);
@@ -119,6 +132,9 @@ test('production canary keeps stable 8081 live and switches only after verified
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(
@@ -129,7 +145,8 @@ test('production canary keeps stable 8081 live and switches only after verified
assert.ok(persistBackup > fullBackup);
assert.ok(edgeBackup > persistBackup);
assert.ok(goosedStart > edgeBackup);
assert.ok(candidateStart > goosedStart);
assert.ok(deepseekCompatStart > goosedStart);
assert.ok(candidateStart > deepseekCompatStart);
assert.ok(proxyStart > candidateStart);
assert.ok(tunnelStart > proxyStart);
assert.ok(edgeSwitch > tunnelStart);
@@ -140,6 +157,12 @@ test('production canary keeps stable 8081 live and switches only after verified
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/,
@@ -176,6 +199,11 @@ test('candidate runner overrides stable host MCP paths with container-visible pa
[
'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'),
);
@@ -186,6 +214,11 @@ test('candidate runner overrides stable host MCP paths with container-visible pa
'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'),
@@ -215,9 +248,78 @@ test('candidate runner overrides stable host MCP paths with container-visible pa
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');