Add 103 H5 rich page rollout runbook

This commit is contained in:
john
2026-07-08 10:33:23 +08:00
parent 81967eaf8c
commit 31742f914e
@@ -0,0 +1,243 @@
# 103 H5 页面生成灰度与发布复盘(2026-07-07)
> 适用场景:H5 / 服务号聊天中,长内容、结构化内容、适合读者阅读或转发的内容没有自动生成页面,或 103 开关/发布后需要做受控验证。
## 结论摘要
本次问题不是 `static-page-publish``sandbox-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` 当前启用:
```bash
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
```
启用前备份:
```bash
/Users/john/Project/Memind/.env.backup-h5-session-flags-20260707-151846
```
启动日志应看到:
```text
[Portal] Session Broker enabled (MEMIND_SESSION_BROKER_ENABLED=1)
[Portal] H5 session flags: MEMIND_SESSION_BROKER_ENABLED, ...
```
## 标准验证流程
### 1. 基础健康
103 本机:
```bash
ssh john@58.38.22.103 'curl -fsS http://127.0.0.1:8081/api/status'
```
外部入口:
```bash
curl -ksS https://m.tkmind.cn/api/status
```
Manifest
```bash
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_skill``sandbox-fs__write_file`
- 最终 Markdown 链接能 `HTTP 200`
示例验收 URL
```text
https://m.tkmind.cn/MindSpace/a6fb1e97-2b0f-447b-b138-4561d8e5c53e/public/world-news-20260707.html
```
### 4. 长内容/服务号场景 smoke
复测用户原始形态:
```text
以下是今天(2026年7月7日,星期二)国际上的主要热点新闻:
...
请整理给服务号用户阅读。
```
期望 run events
```json
{
"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 判断,否则容易把“整理给服务号用户阅读”判为纯文字摘要。
本地验证:
```bash
node --test chat-intent-router.test.mjs direct-chat-service.test.mjs chat-skills.test.mjs
npm run verify:h5-session-patches
```
发包脚本也会跑:
```bash
npm test -- --test-name-pattern='publish|space|billing'
npm run verify:mindspace-publish-guards
npm run verify:mindspace-publish-guards:full
```
## 发版路径
本次没有从原始脏工作区发包,而是使用干净 worktree:
```bash
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
bash scripts/check-release-ready.sh --skip-fetch
```
正式发布:
```bash
bash scripts/release-portal-runtime-prod.sh --yes
```
发布完成后推送 `main`,保证 manifest commit 可追溯:
```bash
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。
确认命令:
```bash
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
'
```
修复:
```bash
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
'
```
健康检查:
```bash
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_ENABLED``MEMIND_TOOL_GATEWAY_ENABLED`
- 页面发布主要应走 goosed + `sandbox-fs`,不是通用 code executor。
- 不要只看 run `succeeded`,必须看是否有 `write_file` 和公网 URL 200。
- 不要把 guard 拦截当作失败本身;guard 是保护用户不收到假链接,真正问题在路由或落盘。