Files
memind/docs/103-h5-rich-page-rollout-runbook-20260707.md
T
2026-07-08 10:33:23 +08:00

7.2 KiB
Raw Blame History

103 H5 页面生成灰度与发布复盘(2026-07-07)

适用场景:H5 / 服务号聊天中,长内容、结构化内容、适合读者阅读或转发的内容没有自动生成页面,或 103 开关/发布后需要做受控验证。

结论摘要

本次问题不是 static-page-publishsandbox-fs 完全不可用,而是三层问题叠加:

  1. 103 起初未开启 H5 session 灰度开关,生产仍停在旧路径。
  2. Router 将“长新闻 + 请整理给服务号用户阅读”判成 direct_chat,导致只回复摘要,不进入页面生成链路。
  3. Portal runtime 发布后,旧 goosed-prod-* 容器仍握着旧 live 目录 inode 的 bind mount,导致容器内写入成功但 host 当前 /Users/john/Project/Memind/MindSpace 看不到。

最终上线版本:

  • Portal release: 20260707-154245-ea4c6b7
  • Git commit: ea4c6b707c2a95c5c63eed4ed5dc02f8a6f786bc
  • Branch used for release: release/rich-page-router-0707
  • Production host: john@58.38.22.103

安全边界

  • 不从脏工作区发包。
  • 不从功能分支、局部文件或单 commit 手工拷贝到 103。
  • 不直接 rsync 到 103/105。
  • Portal 发包必须走 bash scripts/release-portal-runtime-prod.sh --yes
  • .env 灰度开关可以作为运行配置变更,但必须先备份、记录、重启、健康检查。
  • goosed 容器重建是运行态 remount 修复,不是源码变更;执行前后必须检查 18006-18014 健康。

本次启用的 103 H5 灰度开关

103 .env 当前启用:

MEMIND_SESSION_BROKER_ENABLED=1
MEMIND_ROUTER_NORMALIZED_DECISION=1
MEMIND_SSE_EVENT_TAXONOMY=1
MEMIND_RUN_STREAM_REPLAY=1
MEMIND_H5_HTML_FINISH_GUARD=1
MEMIND_SESSION_STREAM_REPLAY=1
MEMIND_SESSION_BROKER_METRICS=1

启用前备份:

/Users/john/Project/Memind/.env.backup-h5-session-flags-20260707-151846

启动日志应看到:

[Portal] Session Broker enabled (MEMIND_SESSION_BROKER_ENABLED=1)
[Portal] H5 session flags: MEMIND_SESSION_BROKER_ENABLED, ...

标准验证流程

1. 基础健康

103 本机:

ssh john@58.38.22.103 'curl -fsS http://127.0.0.1:8081/api/status'

外部入口:

curl -ksS https://m.tkmind.cn/api/status

Manifest

ssh john@58.38.22.103 'cat /Users/john/Project/Memind/.release-manifest.txt'

2. 账号级 smoke

使用生产账号登录后,创建 session 并发一条短消息。期望:

  • /api/agent/runs 返回 202
  • run 最终 succeeded
  • 会话里 message_count=2
  • 助手回复 OK

3. 页面链路 smoke

发明确页面生成请求。期望:

  • run events 有 intent_routed
  • route 为 agent_orchestration
  • suggestedSkill=static-page-publish
  • goosed 原始 session 有 load_skillsandbox-fs__write_file
  • 最终 Markdown 链接能 HTTP 200

示例验收 URL

https://m.tkmind.cn/MindSpace/a6fb1e97-2b0f-447b-b138-4561d8e5c53e/public/world-news-20260707.html

4. 长内容/服务号场景 smoke

复测用户原始形态:

以下是今天(2026年7月7日,星期二)国际上的主要热点新闻:
...
请整理给服务号用户阅读。

期望 run events

{
  "eventType": "intent_routed",
  "data": {
    "route": "agent_orchestration",
    "reason": "结构化长内容适合生成页面承载",
    "source": "rule",
    "suggestedSkill": "static-page-publish"
  }
}

本次代码调整

文件:

  • chat-intent-router.mjs
  • chat-intent-router.test.mjs

策略:

  • 保留短问答、普通摘要、故事/诗歌等 direct chat。
  • 对“服务号/公众号/H5/读者/用户/转发/分享/阅读”等场景中较长、结构化、多条目内容,优先走 static-page-publish
  • 避免完全依赖 LLM router 判断,否则容易把“整理给服务号用户阅读”判为纯文字摘要。

本地验证:

node --test chat-intent-router.test.mjs direct-chat-service.test.mjs chat-skills.test.mjs
npm run verify:h5-session-patches

发包脚本也会跑:

npm test -- --test-name-pattern='publish|space|billing'
npm run verify:mindspace-publish-guards
npm run verify:mindspace-publish-guards:full

发版路径

本次没有从原始脏工作区发包,而是使用干净 worktree:

git worktree add -b codex/rich-page-router /Users/john/Project/Memind-rich-page-router main
git worktree add -b release/rich-page-router-0707 /private/tmp/memind-release-rich-page main

发布前闸门:

bash scripts/check-release-ready.sh --skip-fetch

正式发布:

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

发布完成后推送 main,保证 manifest commit 可追溯:

git push origin main

关键踩坑:goosed bind mount 旧 inode

现象:

  • goosed 原始会话显示 sandbox-fs__write_file public/today-news-20260707.html 成功。
  • 容器内能看到文件。
  • host 当前 /Users/john/Project/Memind/MindSpace/.../public 看不到文件。
  • 公网 URL 404 或被 MEMIND_H5_HTML_FINISH_GUARD 拦截。

原因:

Portal runtime 发布会移动旧 live 目录到 archive,再展开新 runtime live 目录。已经运行的 goosed-prod-* 容器仍握着旧目录 inode 的 bind mount。

确认命令:

ssh john@58.38.22.103 '
  /opt/homebrew/bin/docker exec goosed-prod-1 sh -lc "ls -l /Users/john/Project/Memind/MindSpace/<user-id>/public/<file>.html"
  ls -l /Users/john/Project/Memind/MindSpace/<user-id>/public/<file>.html
'

修复:

ssh john@58.38.22.103 '
  cd /Users/john/Project/goosed-prod
  /opt/homebrew/bin/docker compose -f docker-compose.prod.yml up -d --force-recreate
'

健康检查:

ssh john@58.38.22.103 '
  /opt/homebrew/bin/docker ps --format "{{.Names}} {{.Status}}" | grep goosed-prod | sort -V
  for p in $(seq 18006 18014); do curl -kfsS "https://127.0.0.1:$p/status" >/dev/null || exit 1; done
  curl -fsS http://127.0.0.1:8081/api/status
'

经验:Portal runtime 发布后,如果本次涉及 MindSpace 页面写入,应该把 goosed remount 检查纳入发布后验收;必要时重建 goosed 容器。

下次 Checklist

  1. 确认目标:是本地、103、还是 105 入口问题。
  2. 先只读确认生产 manifest、flags、health。
  3. 用真实生产账号做短消息 smoke。
  4. 用明确页面生成请求做 write_file + URL 200 smoke。
  5. 用用户原始表达做路由 smoke,检查 intent_routed
  6. direct_chat:修 router 规则或 prompt,不要先开通用 code/tool gateway。
  7. 若有 write_file 但 URL 404:检查 host/container 同路径可见性,重点怀疑 goosed bind mount 旧 inode。
  8. 只从干净 release worktree 发包。
  9. 发包后验证 manifest、外部入口、真实业务页面链接。
  10. 推送 main,确保生产 manifest commit 可追溯。

不建议的做法

  • 不要因为页面没生成就直接打开 MEMIND_AGENT_CODE_RUNS_ENABLEDMEMIND_TOOL_GATEWAY_ENABLED
  • 页面发布主要应走 goosed + sandbox-fs,不是通用 code executor。
  • 不要只看 run succeeded,必须看是否有 write_file 和公网 URL 200。
  • 不要把 guard 拦截当作失败本身;guard 是保护用户不收到假链接,真正问题在路由或落盘。