onesvm-browser-server/.dsh/contracts/contract-20260901-browser-impl.md
chii eb972dfa93 feat: 落地 browser-server 控制面并打通 mgr1 海外订阅
单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 15:05:12 +08:00

137 lines
No EOL
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Contract: onesvm-browser-server 控制面首版实现(到「部署 mgr1 测试」就绪)
- **contract_id**: contract-20260901-browser-impl
- **run_id**: run-20260901-browser-impl
- **创建**: 2026-09-01(Asia/Shanghai)
- **状态**: approved
- **复杂度**: core_framework(触发 Architecture Review)
- **用户授权**: 2026-09-01 用户原话确认「按预设(D7=Go、D8=进程内适配器、D9=灰度、D10=附加 casa-net、D1=L0→L2 渐进、D3=:8640//bs-api)开实现 Contract」——D1–D10 全部按预设锁定,不再询问。
## 1. Goal
实现 docs 全部设计所定义的**控制面三角色 Go 单仓**(gateway / scheduler / proxymanager,`cmd/browser-server` 单二进制 role 分流),**到部署 mgr1 测试的就绪态**:代码 + 单测全绿 + 本机 docker compose 冒烟通过 + amd64 镜像构建 + stack.yml + 传输/部署脚本。**本 Contract 不执行任何对 mgr1 的实际部署/SSH 操作**(部署轮另走 🟡 确认)。
## 2. 权威输入(Read,全部必读)
| 输入 | 路径 | 用途 |
|---|---|---|
| 设计权威 | `docs/design-arch-20260901.md` | 机制细节(§2 认证/配额、§3 Dock 五方法/信封、§4 队列/背压、§5 ProxyManager/合规、§7 可观测) |
| 决策权威 | `docs/plan-final-20260901.md` | 结论与档位(§2 排队/内存、§3 Dock/输出、§5 评分) |
| 部署预设 | `docs/deploy-prod-preset-20260901.md` | 服务清单/端口/资源/灰度步骤(D7/D8/D9/D10 已锁) |
| MCP 契约 | `docs/mcp-usage-20260901.md` | 工具参数/响应形状/错误码对消费者承诺(T1 契约面) |
| 组织规范 | `onesvm-dev-md/base/development-standards.md` | §5 urlapi、§6 镜像 tag、§7 东八区 |
| 组织规范 | `onesvm-dev-md/base/deployment-rules.md` | stack 必备字段/secret/命名 |
| 实测凭据 | `bench/searxng-{cn,global}/settings.yml`、`bench/{lightpanda,chrome-headless-shell}/compose.yml`、`bench/trafilatura-http/app.py`、`bench/site-matrix/cdp_fetch.mjs`、`bench/proxy/up.sh` | 引擎配置/CDP 最小调用面/适配器接口形状(复用声明见 §6) |
## 3. 决策锁定(不再开放)
| # | 决策 | 值 |
|---|---|---|
| D3 | 端口 | gateway host `:8640`;反代前缀 `/bs-api`;scheduler `:8641`、proxymanager `:8642`、mihomo mixed `:17890` 仅 overlay |
| D7 | 技术栈 | Go 单仓单二进制多角色(`go run ./cmd/browser-server -role=…`) |
| D8 | 适配器形态 | scheduler 进程内 Go 模块(引擎经 overlay HTTP/CDP 调用;Dock 五方法 = 内部 interface) |
| D9 | 上线 | mgr1 单节点起步,灰度扩展三节点另议 |
| D10 | 联调网络 | stack 附加 `vlepontas-casa-net`(external,alias `browser-server`) |
| D2 | 国外搜索 | Bing-only 姿态 |
| O9/O10 | MCP 2025 腿 / 管理面 | 首版仅 2026 无状态 POST /mcp;管理面 CLI + SQL 先行(首版:CLI 子命令写 SQLite) |
| 依赖 | 第三方库 | 禁重型依赖;准许 `modernc.org/sqlite`(纯 Go 无 CGO)、`gopkg.in/yaml.v3`;其余 stdlib + 手写(MCP JSON-RPC/CDP WebSocket 自实现,面窄);网络依赖须经 Go proxy 镜像拉取 |
## 4. scope(写边界)
**in-scope(唯一写区)**:`server/`(新建,全部 Go 源码)、`stacks/browser-server.yml`、`scripts/deploy-mgr1.sh`、`scripts/smoke-local.sh`、`docs/impl-20260901.md`(交付记录)、`.dsh/contracts/**`、`.dsh/artifacts/run-20260901-browser-impl/**`。`docs/deploy-prod-preset-20260901.md` 仅允许追加「镜像 tag 约定(development-standards §6)」小节(O13 落地)。
**out-of-scope**:`docs/` 其余文档、`bench/`、`onesvm-dev-md/`(submodule)、`casa-commander/`(submodule)、`.dsh/casa-runtime/`、`README.md`、`AGENTS.md`、任何 remote/生产操作。**本仓不做 git commit**(除非用户另行指示)。
## 5. 逐模块 Acceptance(工程级硬门槛)
### A1 公共包(`server/internal/`)
- `contract/`:统一信封/JobEnvelope/RawResult/五方法接口/错误码——与 design-arch §3.2/§3.3、mcp-usage §2/§3 字段**一致**;时间字段一律 `+08:00`。
- `store/`:SQLite(modernc 纯 Go)open(WAL/busy_timeout=2s/foreign_keys=on)+ migration(schema v1:consumers/api_keys/audit/quota/jobs/dead_letters/rules)+ repository 方法面。SQL 源码内 `?` 占位符、行级错误处理、无 `SELECT *` 糊装。
- `auth/`:静态 key 校验 `hmac.Equal`(constant-time);密钥注入 `${VAR:?required}` fail-closed;**禁 `os.Getenv` 默认值兜底**。
- `policy/`:SSRF fail-closed(协议 80/443、私网/元数据 CIDR、CNAME 银行卡号段不重定向重验——**重定向重验按 design §5.4 每跳重验**)、域名规则 trie(direct/pool/deny)、robots(普通 key,TTL 缓存 SQLite)。
- `safetyscan/`:词表文件 + 编译正则(NFKC 归一)+ PII redact + 注入包裹 delimiter;命中高危 block;`wordlist_version` 字段。
- `httpx/`:共享 HTTP client(大小/类型守卫、超时、重定向重验 hook)。
### A2 gateway
- MCP JSON-RPC 2.0 单 handler(`tools/list`、`tools/call`,MCP 头校验/协议版本),HTTP `/v1/search` `/v1/read`,`/healthz` `/readyz`。
- 认证 → scope → 429/402/403 → **overlay `POST scheduler:8641/enqueue`**(落盘成功才 200,scheduler 不可达 → 503+Retry-After,gateway 零落盘)。
- 认证头仅 `X-Service-Token`(401 时响应头提示禁 Bearer);admin 面:`X-Service-Token` = admin token,`POST /admin/keys`(签发)、`DELETE /admin/keys/{id}`(吊销)。
- 搜索响应走 gateway 进程内短 TTL 缓存(query+region 键,默认 300s,≤50 条 LRU)——消化上游 CAPTCHA(plan-final §2.4 设计结论)。
### A3 scheduler
- 队列(SQLite 单写者 + 进程内 bounded queue):`enqueue` 落 WAL 才 ACK;`ADMIT_MAX=60`(running+queued,env 可调);429/503 携带 `Retry-After` + `running/queued` 现状。
- 派发:`UPDATE … WHERE id=(SELECT …) RETURNING`;租约 `lease_until` + reaper 收割 + `attempts≤2` 仅瞬时错误重试 + 死信表。
- Dock 适配器进程内注册表:searxng-cn / searxng-global / trafilatura / lightpanda / headless-shell 五个,各自实现 `contract.DockAdapter` 五方法;capabilities 如 §4.4 标签。
- 能力路由:region/render 标签匹配(`region=overseas` 强制走 mihomo 出口;`shell_active ⇒ panda 停新` 互斥,入队侧执行)。
- 模版层(模版层整合器,S7 归属):RawResult → Size/Type guard → fit markdown(trafilatura 引擎侧已产 markdown,CDP 引擎给 text→go 差分算法转 markdown 或直接 bodyText 作正文)→ safetyscan → 信封封装;score 归一 0–1。
- 降级链:C 满试 B、B 失败回 A(HTTP+fit),全败 `blocked/upstream` + warnings,不静默降质。
- `/pressure` + `/metrics`(文本格式,design §7.2 指标面)。
- headless-shell 按需槽:Swarm API(`DOCKER_HOST=tcp://…` 或 unix socket 挂载)scale 0→1,空闲 10min 回收(D8 预设;不可用时适配器返回 unhealthy,不阻塞其它通道)。
### A4 proxymanager
- 订阅解析:Clash YAML(复用 bench `lib.py` 逻辑的 Go 版)→ 剔除占位节点 → vless/hysteria2 分池(hy2 不调度)→ 写 mihomo provider 配置 + mihomo 热载。
- 探活:`https://www.google.com/generate_204`(禁 cp.cloudflare.com HEAD);活跃 30s / 全池 5min;连续 2 失败摘除、1 成功回候选;EWMA 延迟按域名分组。
- 轮换 P2C + sticky session(TTL map);域名路由表(direct/deny/pool)与 gateway policy 联动(共享 SQLite rules 表 + 热载)。
- HTTP API(仅 overlay):`/healthz`、`/api/proxies`(探活状态)、`/api/exit?domain=`(sticky 选出口)。
- `PROXY_SUB_URLS` 多订阅容灾;订阅 URL 不落盘不入 git(内存传递给 mihomo config 生成)。
### A5 部署产物
- `stacks/browser-server.yml`:9 服务(gateway/scheduler/proxymanager/mihomo + searxng-cn/global + trafilatura + lightpanda + backup sidecar)+ shell 按需(`replicas=0`,Swarm 拉起);deployment-rules §1.3 必备字段全(TZ/placement/healthcheck/limits);镜像 digest/tag 锁定;`vlepontas-casa-net` external 附加 alias `browser-server`;SQLite named volume 钉 mgr1。
- Dockerfile 多阶段(builder golang:1.27-alpine → distroless/base 或 alpine);Makefile(build/test/lint/fmt/cross-amd64/image);`scripts/deploy-mgr1.sh`(save|load + stack deploy --resolve-image never + prune dangling 尾步骤)与 `scripts/smoke-local.sh`(compose 冒烟:起本地 compose,签发 key,curl 全链路断言)。
- 镜像 tag 约定落地:`onesvm/browser-server:dev` 日常复用(O13)。
### A6 质量门槛(verify/reverify 共用)
1. `go build ./...`、`go vet ./...`、`gofmt -l` 空、`go test ./...` **全绿**;关键路径单测覆盖:auth(比对常数时间/过期/吊销)、SSRF 私网判定、队列抢单原子性、词表命中、信封字段与 mcp-usage 一致性(golden test)、路由标签匹配、探活摘挂状态机。
2. 行数:`server/` 与脚本每文件 **≤600 行**硬上限(组织 600 行纪律);>600 须拆分后再验收。
3. T 系复核:T1 契约(mock 形状=真实响应形状:searxng json/trafilatura/CDP 均以 bench 实测样本为准)、T2 fail-closed(SSRF/密钥/订阅缺省)、T3 密钥(`${VAR:?}` fail-closed + `hmac` 比对 + 无默认值)、T6(租约 reaper 退避)、T7 入参边界(Pydantic→Go 等价物:入参长度/范围校验)。
4. 本机冒烟(smoke-local.sh,可选依赖 Docker 在线):gateway/scheduler 起 + stub 引擎 → 签发 key → `/v1/search` `/v1/read` MCP+HTTP 全链路 200 + 错误路径(401/403/429/503)断言。
5. amd64 交叉构建成功(`GOOS=linux GOARCH=amd64 go build`),镜像 ≤60MB 目标(distroless)。
## 6. 复用声明(组织纪律:能复用须标明)
| 复用物 | 来源 | 去向 |
|---|---|--- A2 |
| searxng 引擎配置(cn 四引擎 / global Bing-only 姿态) | `bench/searxng-{cn,global}/settings.yml` | stack 内嵌同参数 settings(按 Swarm 语法适配) |
| trafilatura 服务(`POST /v1/read`、SSRF 守卫、semaphore=8) | `bench/trafilatura-http/app.py`(镜像已构建 `bench-s3b-trafilatura:local`) | stack 的 trafilatura 服务 = **同一代码构建**,镜像 tag `onesvm/trafilatura-http:dev` |
| CDP 最小调用面(json/version → ws → Target.create/attach → Page/Runtime/Network enable → navigate → evaluate 提取) | `bench/site-matrix/cdp_fetch.mjs` | lightpanda/headless-shell 适配器 CDP 客户端(Go 重写,面窄) |
| Clash 订阅解析/占位节点剔除/区域归类逻辑 | `bench/proxy/lib.py` | proxymanager 订阅解析(Go 移植) |
| 反爬特征检测正则(cloudflare/waf/captcha 判定) | `bench/site-matrix/cdp_fetch.mjs detectVendor` | 模版层 blocked 判定 |
| compose 参数(镜像 digest pin、shm、proxy env) | `bench/*/compose.yml` | stack yml 引擎服务 |
## 7. 工件流(artifact_io)
Worker 交付物一律落 `.dsh/artifacts/run-20260901-browser-impl/`:
| 产物 | 路径 |
|---|---|
| 实现回执(builder 自验收) | `impl-receipt.md` |
| verify(首轮独立验收) | `verify.md` |
| reverify(复核 + 迭代修复记录) | `reverify.md` |
| 架构审查(core_framework 强制) | `arch-review.md` |
| 冒烟原始输出 | `smoke/` |
| 行数检查输出 | `wc.txt` |
## 7bis. 通信纪律(CASA 铁律)
- Worker 之间/Worker→指挥官:只传**产物路径 + diff 摘要**,禁「如上所述」传话。
- Worker 不得最终自评 PASS——**builder ≠ judge**;verify/reverify 由独立只读 Worker 执行。
- 指挥官主会话禁写业务文件(`server/`、`stacks/`、`scripts/`);仅契约/工件/收口文档。
## 8. 失败与迭代
任一 acceptance 不满足 → verify Worker 出 `fail-*.md`(列证据 file:line)→ 指挥官派迭代 Worker **只注入 fail artifact + 本 Contract** 修复;最多 3 轮(policy.max_iterations=3),仍败则升级用户。
## 9. 部署边界(红线)
本轮**不 SSH mgr1、不 stack deploy、不 push remote**。`scripts/deploy-mgr1.sh` 只写脚本不执行。部署轮另走 🟡 前置确认(mgr1 free -g / ss -tln / 影响面报告)。
## 10. 时间与语言
- 全部时间字段 Asia/Shanghai `+08:00`(development-standards §7);容器 `TZ=Asia/Shanghai`。
- 代码注释/交付文档简体中文,标识符英文。