# 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`。 - 代码注释/交付文档简体中文,标识符英文。