Files
memind/docs/发包必看.md
john fb6865638a
Memind CI / Test, build, and release guards (pull_request) Successful in 1m32s
fix: align DeepSeek canary tool rounds with gate
2026-07-26 23:49:32 +08:00

11 KiB
Raw Permalink Blame History

发包必看

适用范围:test-memind Portal runtime 发布、goosed-prod 镜像更新、103/105 生产链路排障。
这份文档是发布前强制阅读的事故经验清单。后续发包如果绕过这里,容易把 2026-06-27 的问题重新带回生产。

103 生产发布还必须完整通过 生产发布守门员。守门员自动执行器、机器报告和发布脚本硬阻断全部落地前,不允许将现有脚本视为满足新的生产发布条件。

0. 先确认边界

  • 生产 Portal 主机是 103 / Studiojohn@58.38.22.103,默认 SSH 命令不要再写旧端口 2222
  • 105 是云侧入口/历史链路,不是 Portal 源码真相;不要 SSH 到 105 直接改业务代码。
  • Portal 生产目录:/Users/john/Project/Memind
  • goosed 生产目录:/Users/john/Project/goosed-prod
  • Portal 生产必须是无源码 runtime 模式,只能用 scripts/release-portal-runtime-prod.sh 发包。
  • 禁止直接 rsync 到 103/105,禁止在 103 上 npm installnpm run build 后继续跑。

1. 发包前硬性检查

  1. 当前代码必须已经包含以下修复,或位于它们之后:
    • fc5b50c fix: make goosed mcp paths container safe
    • 0420c42 fix: make schema bootstrap mysql safe
  2. release worktree 必须干净:
git status --short
  1. 不允许把 .runtime/portal/node_modules、本地构建缓存提交进 Git。
  2. 如果 release worktree 需要临时复用主 worktree 的依赖,只能临时建 symlink,发布/测试后必须删除:
ln -s /Users/john/PycharmProjects/test/test-memind/node_modules /Users/john/PycharmProjects/test/test-memind-release-goosed-mcp/node_modules
# 测试或发布完成后:
rm -f /Users/john/PycharmProjects/test/test-memind-release-goosed-mcp/node_modules
  1. 发包前必须完成生产发布守门员;其中现有基础验证至少包括:
node --test db.test.mjs capabilities.test.mjs llm-providers.test.mjs wechat-mp.test.mjs
npm run check:mindspace-public-links
  1. public/*.html 里的下载/附件相对链接(如 report.docx)必须在同目录真实存在。发版脚本默认只查这类链接(--downloads-only);全量资源检查用 npm run check:mindspace-public-links:all。失败会阻断发版;确知要带着已知坏链上线时,才可临时 ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1

2. Portal runtime 发布唯一流程

生产发布守门员、完整 Gate report 和本次明确人工批准全部满足后,首次生产动作只使用:

bash scripts/release-portal-canary-prod.sh --yes

生产 103 禁止使用 --skip-tests 或任何范围绕过变量。整包替换脚本 release-portal-runtime-prod.sh 只用于同一候选完成灰度验收后的全量晋升,在晋升证据 校验完成前仍会硬阻断。本文历史记录中的旧绕过命令只用于事故复盘,不构成当前发布授权。

这个脚本必须完成这些动作:

  • 本机构建 .runtime/portal
  • 上传 artifact 到 103。
  • 103 备份当前 /Users/john/Project/Memind 全目录。
  • 103 单独备份持久目录:.envMindSpace/data/users/.tailscale/public/plaza-covers/logs/
  • 备份并校验 105 的 m.tkmind.cn.confwechat.m.tkmind.cn.conf
  • 保持稳定 Portal 103:8081 不变,启动候选 Portal 18081、候选 goosed 18015 和身份路由器 18082
  • 启动独立 105:19082 -> 103:18082 灰度隧道;禁止复用历史 19081 或占用 18080。
  • nginx -t 通过后才把 105 活动上游从 58.38.22.103:8081 切到 127.0.0.1:19082
  • 健康检查稳定、候选、回落和 105 HTTPS 入口均通过。
  • 校验 MindSpace/*/public/*.html 下载/附件相对链接均已落盘(check-mindspace-public-links.mjs --downloads-only)。

发布成功后,记录脚本输出里的:

  • release_id
  • candidate_dir
  • full_backup
  • persist_backup
  • 105 nginx 配置备份
  • git_head

3. Portal 发布后必须验收

在本机执行:

curl -k -i https://m.tkmind.cn/auth/login \
  -H 'content-type: application/json' \
  --data '{"username":"john","password":"wrong-password"}' | head -40

正确结果应该是 401,消息类似 用户名或密码错误
如果返回 503 且消息是 未配置用户数据库或访问密码,说明用户系统 bootstrap 失败,不是简单健康检查能发现的问题。

在 103 执行:

ssh john@58.38.22.103 '
  printf "portal="; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8081/api/status
  for p in $(seq 18006 18014); do
    printf "goosed_${p}="
    curl -k -s -o /dev/null -w "%{http_code}\n" https://127.0.0.1:${p}/status
  done
  cd /Users/john/Project/Memind && node scripts/check-mindspace-public-links.mjs --root MindSpace --downloads-only
  pid=$(pgrep -f "node .*server.mjs" | head -1)
  echo "portal_pid=${pid}"
  ps eww -p "$pid" | tr " " "\n" | awk -F= "/^(DATABASE_URL|H5_PORT|TKMIND_API_TARGETS|GOOSED_MCP_NODE_PATH|GOOSED_MCP_SERVER_PATH)=/ {print \$1\"=SET\"}"
'

必须看到:

  • portal=200
  • goosed_18006=200goosed_18014=200(共 9 个 target
  • DATABASE_URL=SET
  • TKMIND_API_TARGETS=SET
  • GOOSED_MCP_NODE_PATH=SET
  • GOOSED_MCP_SERVER_PATH=SET

日志检查:

ssh john@58.38.22.103 '
  tail -200 ~/Library/Logs/memind-portal.log |
    egrep "User auth bootstrap failed|ER_PARSE_ERROR|Cannot find package|Failed to add extension|No such file|Unknown extension|Provider not set|not configured" || true
'

新启动之后不能继续出现上述错误。

4. 2026-06-27 事故复盘形成的禁止项

4.1 不要只看 /api/status

/api/status 返回 200 只能说明 Express 进程活着,不能说明登录、数据库、Agent 会话策略都正常。每次 Portal 发包必须额外测 /auth/login

4.2 schema 必须兼容 MySQL

禁止在 schema.sql 里写 PostgreSQL partial index

KEY idx_xxx (...) WHERE ...

MySQL 不支持这种写法。生产曾因此触发:

User auth bootstrap failed
ER_PARSE_ERROR
未配置用户数据库或访问密码

db.mjs 现在使用 splitSqlStatements(),会跳过 SQL 注释里的分号;不要改回 sql.split(';')

release worktree 如果有 node_modules -> /Users/john/.../node_modules symlink,构建脚本必须 realpath 后复制真实依赖。发布后必须确认线上:

ssh john@58.38.22.103 'cd /Users/john/Project/Memind && test -d node_modules/http-proxy-middleware && echo ok'

否则可能出现:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'http-proxy-middleware'

4.4 Portal 下发给 goosed 的 MCP 路径必须是容器内路径

推荐并默认使用:

GOOSED_MCP_NODE_PATH=/usr/local/bin/node
GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs

不要让新会话继续依赖宿主机路径:

/opt/homebrew/Cellar/node@24/24.16.0/bin/node
/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs

否则会触发:

Failed to add extension: IO error: No such file or directory (os error 2)

4.5 code_execution 默认不能下发

当前 goosed 不认识 code_execution 扩展。除非确认 goosed 已支持,否则不要设置:

TKMIND_ENABLE_CODE_EXECUTION_EXTENSION=1

4.6 Provider 必须同步到全部 goosed target

Portal 现在使用:

TKMIND_API_TARGETS=https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009,https://127.0.0.1:18010,https://127.0.0.1:18011,https://127.0.0.1:18012,https://127.0.0.1:18013,https://127.0.0.1:18014

发布或重建 goosed 容器后,要确认 1800618014 全部 target 都有 provider 配置。否则 Agent 会出现:

Provider 'custom_deepseek' is not configured

注意:DeepSeek API key / provider secret 是运行配置,不应该烘焙进镜像。

4.7 DeepSeek 工具轮次兼容必须与生产拓扑一致

DeepSeek V4 思考模式在工具调用后的下一轮要求客户端回传上一轮 reasoning_content。当前 goosed 工具链不能保证完整回传,因此生产候选必须使用 artifact 内的 deepseek-no-think-proxy.mjs,由独立 LaunchAgent 在 18036 启动,并强制候选 Portal 设置:

MEMIND_DEEPSEEK_DISABLE_THINKING=1
MEMIND_DEEPSEEK_NO_THINK_PORT=18036
MEMIND_GOOSED_HOST_GATEWAY=host.docker.internal

发布不能只看 Portal /api/status。还必须验证:

  • http://127.0.0.1:18036/health 返回兼容契约版本和 deepseekThinking=disabled
  • goosed-prod-canary 能访问 http://host.docker.internal:18036/health
  • 灰度路由器把该地址纳入候选健康闭包;
  • 失败清理和回滚会停止对应 LaunchAgent。

Gate 本地栈和生产候选必须使用同一组开关与进程依赖。若出现:

The reasoning_content in the thinking mode must be passed back to the API.

说明实际生产请求绕过了兼容契约;即使场景 Gate 全部通过,也必须阻断发布并排查 环境漂移。

5. goosed-prod 镜像更新必看

当前已验证镜像:

tkmind/goosed:prod-20260627-2125-mcp

这个镜像必须保证以下路径在容器内存在:

/usr/local/bin/node
/opt/homebrew/bin/node
/opt/homebrew/opt/node@24/bin/node
/opt/homebrew/Cellar/node@24/24.16.0/bin/node
/opt/portal/mindspace-sandbox-mcp.mjs

重建镜像后必须验:

ssh john@58.38.22.103 '
  cd /Users/john/Project/goosed-prod
  set -a
  source .env
  source .paths.env
  export PORTAL_RUNTIME_DIR="$portal_runtime_dir" MINDSPACE_ROOT="$mindspace_root"
  set +a
  /opt/homebrew/bin/docker compose -f docker-compose.prod.yml ps
  for c in goosed-prod-{1..9}; do
    echo "--- $c"
    /opt/homebrew/bin/docker exec "$c" sh -lc "
      ls -l /usr/local/bin/node \
        /opt/homebrew/bin/node \
        /opt/homebrew/opt/node@24/bin/node \
        /opt/homebrew/Cellar/node@24/24.16.0/bin/node \
        /opt/portal/mindspace-sandbox-mcp.mjs
    "
  done
'

重建容器后必须重新确认:

  • 九个容器都是 healthy
  • 1800618014 /status 都是 200。
  • provider 已同步到全部 target。
  • sandbox-fs smoke 能成功 add extension 并完成一次 /reply

6. 出问题时先走这条判断链

  1. m.tkmind.cn/api/status 是否 200
  2. /auth/login 是否进入 400/401,而不是 503
  3. 103 Portal 进程是否拿到了 DATABASE_URL
  4. ~/Library/Logs/memind-portal.log 是否有 User auth bootstrap failed
  5. 1800618014 是否全 200
  6. goosed 容器内 /usr/local/bin/node/opt/portal/mindspace-sandbox-mcp.mjs 是否存在?
  7. provider 是否同步到全部 target
  8. 如果是微信服务号或专属 Agent 报错,必须测真实业务路径,不要只看健康接口。

7. 发布完成后给用户的最小回报格式

发布完成后至少说明:

  • release id
  • git head
  • full backup
  • persisted backup
  • Portal 健康结果
  • /auth/login 结果
  • 1800618014 健康结果(共 9 个 target
  • 是否发现并处理了日志里的错误关键词

不要只说“已发布成功”。