Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 046704816f |
@@ -16,6 +16,13 @@ Portal 的首个生产动作必须是用户级灰度,不能直接整包替换
|
|||||||
`MEMIND_RUNTIME_ROLE=candidate` 会禁止重复启动 MindSpace、Plaza、提醒、订阅续期和
|
`MEMIND_RUNTIME_ROLE=candidate` 会禁止重复启动 MindSpace、Plaza、提醒、订阅续期和
|
||||||
Agent 恢复扫描等单例后台任务。
|
Agent 恢复扫描等单例后台任务。
|
||||||
|
|
||||||
|
候选 Portal 必须从候选 runtime 目录启动,使相对模块只解析到本次候选 artifact。
|
||||||
|
候选 goosed 在 Docker 内执行扩展,MCP Node 和入口路径必须固定为
|
||||||
|
`/usr/local/bin/node` 与 `/opt/portal/mindspace-sandbox-mcp.mjs`;禁止继承稳定
|
||||||
|
`.env` 中仅宿主机可见的 `/Users/...` 路径。若日志出现
|
||||||
|
`Failed to add extension` 和宿主机绝对路径的 `MODULE_NOT_FOUND`,应先核对候选
|
||||||
|
进程环境与 `/opt/portal` 容器挂载,禁止把宿主机文件复制进容器临时修补。
|
||||||
|
|
||||||
## 身份和回落规则
|
## 身份和回落规则
|
||||||
|
|
||||||
- `john`:按数据库中的唯一用户名或用户 ID 匹配。
|
- `john`:按数据库中的唯一用户名或用户 ID 匹配。
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
@@ -163,6 +164,60 @@ test('production canary keeps stable 8081 live and switches only after verified
|
|||||||
assert.doesNotMatch(source, /bootout.*cn\.tkmind\.memind-portal/);
|
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',
|
||||||
|
'',
|
||||||
|
].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 "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, new RegExp(`^entrypoint=${ROOT}/server\\.mjs$`, 'm'));
|
||||||
|
});
|
||||||
|
|
||||||
test('canary release and rollback remote shells remain syntactically valid', async () => {
|
test('canary release and rollback remote shells remain syntactically valid', async () => {
|
||||||
const releaseSource = await fs.readFile(CANARY_RELEASE, 'utf8');
|
const releaseSource = await fs.readFile(CANARY_RELEASE, 'utf8');
|
||||||
const rollbackSource = await fs.readFile(CANARY_ROLLBACK, 'utf8');
|
const rollbackSource = await fs.readFile(CANARY_ROLLBACK, 'utf8');
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ if [[ ! -d "${STABLE_ROOT}" || ! -f "${STABLE_ROOT}/.env" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cd "${ROOT}"
|
||||||
|
|
||||||
set -a
|
set -a
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source "${STABLE_ROOT}/.env"
|
source "${STABLE_ROOT}/.env"
|
||||||
@@ -27,6 +29,10 @@ export H5_HOST=127.0.0.1
|
|||||||
export H5_PUBLIC_BASE_URL="${H5_PUBLIC_BASE_URL:-https://m.tkmind.cn}"
|
export H5_PUBLIC_BASE_URL="${H5_PUBLIC_BASE_URL:-https://m.tkmind.cn}"
|
||||||
export TKMIND_API_TARGETS="${MEMIND_CANARY_GOOSED_URL:-https://127.0.0.1:18015}"
|
export TKMIND_API_TARGETS="${MEMIND_CANARY_GOOSED_URL:-https://127.0.0.1:18015}"
|
||||||
export TKMIND_API_TARGET="${MEMIND_CANARY_GOOSED_URL:-https://127.0.0.1:18015}"
|
export TKMIND_API_TARGET="${MEMIND_CANARY_GOOSED_URL:-https://127.0.0.1:18015}"
|
||||||
|
# Extensions are spawned inside goosed-canary, where the candidate artifact is
|
||||||
|
# mounted at /opt/portal. Never inherit host-only MCP paths from the stable .env.
|
||||||
|
export GOOSED_MCP_NODE_PATH=/usr/local/bin/node
|
||||||
|
export GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs
|
||||||
unset TKMIND_API_TARGET_1
|
unset TKMIND_API_TARGET_1
|
||||||
|
|
||||||
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
|
NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"
|
||||||
|
|||||||
Reference in New Issue
Block a user