外观
Perfowl 首页改版恢复与生产部署隔离方案
Lead AI 决策,2026-09-25。 本文是 Implementation AI 的唯一实施依据;先修部署边界,再恢复首页视觉,不以一次 HTTP 200 或一次 Cloudflare 上传代替页面内容验收。本批不改 MacApp 产品、指标公式、原始 Session、历史归档及其他 AI 未提交文件。Owner 已规定 Lead 完成方案后直接交 Implementation 执行,实施完成再由 Lead 独立验收。
0 · 已核实的单因链与证据等级
- 改版快照
18d818263f5a837a9b855d6341792bf2c903b98b只在wip/site-home-20260924,其父提交为 Build74 基线1ec9462;两远程该 WIP ref 均保存同一 SHA。18d8182不是当前 main 的祖先。其首页入口是<ImmersiveHome />,含 653 行沉浸组件、Three.js、品牌 SVG 与五章页面;当前 main 无该组件,首页仍为 VitePresslayout: home。因此素材没有丢失,代码没有从 main 被git revert;改版从未合入 main。 - 本机
.git/hooks/pre-push只检测推送范围含site/或docs/,不区分分支,并在实际 Git 推送完成之前从当前工作树运行npm run deploy。受版本控制的site/package.json将 deploy 固定为wrangler pages deploy ... --branch main --commit-dirty=true。因此 WIP 分支也能发布生产,后续 main 文档推送又能用旧首页覆盖生产;失败的 Git push 甚至可能留下已发布但远程 ref 未落地的站点。该机制由本地源码直接证明(E1)。 - 本地远程 reflog:WIP ref 于 2026-09-24 00:59 更新;main 于 07:04 推送
4c1e424,该提交触及site/devlog/index.md、其 main 首页无ImmersiveHome,满足上述 hook 的再次部署条件。07:04 是可定位的最早覆盖触发点,不把缺失的云端旧部署日志包装成精确首次回滚时刻。 当前 Cloudflare 生产列表来源为后续 main 提交,线上首页为旧布局。 - 共享 main 当前有四项他方 WIP,含一个已修改 devlog、两个未跟踪 devlog 与
MacApp/Tests/1.trace/;全部受保护。仓库外或独立干净 worktree 才能作为可复现的生产构建输入。
1 · Lead 决策与实施顺序
分为 G0 部署门禁 与 G1 视觉恢复 两个独立批次,每批各一个窄范围提交;Implementation 先只交付 G0,Lead 独立验收 G0 PASS 后才发 G1 执行指令。G0 未通过,不允许 G1 生产发布。禁止直接 git merge/cherry-pick 整个 WIP 分支、直接从 WIP 运行现有 npm run deploy、将其代码未经核验地覆盖当前 site/,也禁止 reset/clean/git add -A 清理共享工作区。
G0 · 将生产发布改为“推送后、双远程一致、干净提交、一次部署”
- 修改受版本控制的
site/package.jsondeploy 入口,接入仓库内的生产发布守卫(建议site/scripts/deploy-production.*)。任何 build、archive 同步或 Cloudflare 调用之前,守卫必须 fail-closed 检查:目标提交 SHA 是当前HEAD;git status --porcelain为空(忽略受 Git 忽略的依赖与构建物);实时git ls-remote origin refs/heads/main和git ls-remote github refs/heads/main均精确等于该 SHA;本次目标明确为 production/main。缺值、超时、网络失败、ref 不一致、工作树不干净一律不部署。不得仅信任本地 remote-tracking ref、commit message、Webhook 或--commit-dirty=true。 - 守卫通过后只构建一次、发布一次:
wrangler pages deploy显式带--branch main --commit-hash <完整 SHA> --commit-dirty=false。构建前后的 SHA 与干净状态再次核对;产物来自该提交的隔离干净 worktree,不是含四项 WIP 的共享目录。发布返回的 deployment id/URL、Cloudflare Source SHA、构建摘要和首页内容校验写入证据。 - 现有本机
pre-pushhook 是未入库的本机状态,不能作为跨环境唯一守卫;受控npm run deploy必须自身安全。旧 hook 在推送 G0/G1 新提交时即使自动调用 deploy,也应因远端 ref 尚未前进而在构建前拒绝生产发布;hook 对 Git 推送可继续非阻断。正式发布只能发生于双远程成功之后,由明确的交付步骤从隔离 worktree 触发一次。后续可另行替换本机 hook 为纯提示,但不得让其继续绕过受控入口。 - 为 WIP 预览设置与生产分离的命令/流程:非 main 来源只能部署 Cloudflare Preview,
--branch传明确的非main预览分支,且必须显示 Preview 环境;不能在预览路径上复用固定--branch main。不要把 WIP 预览成功记为生产验收。 - 守卫测试至少覆盖:非 main/共享脏树、仅一个远程前进、两个远程均未前进、
ls-remote错误/超时、两个远程同 SHA 且干净、预览分支误设 main。用桩验证“拒绝时构建/上传调用次数均为 0;放行时生产上传恰 1 次、SHA 参数精确”。G0 推送后核实 Cloudflare 生产 Source 未因 pre-push 再次变化。
G1 · 有选择地恢复首页,不回退当前知识库与文档
- 以当前 main 为起点,只从
18d8182选择首页必需的视觉资产:ImmersiveHome.vue、相关首页 CSS、site/index.md的组件入口、品牌 SVG、Three.js 锁定依赖,以及theme/index.ts中最小注册。保留当前 main 的导航、知识库、术语提示、归档路由及 devlog;config.ts只作确有必要的逐项合并。根目录.miora与生成图不是站点必需发布物,不整批复制。 - WIP 是 in-progress 视觉快照,不是获验收的能力文案。它只有五章,不能宣称已实现策略草案中的 S6/S7;是否补证据分级区应以本批可验证的静态内容为准,不扩成内容营销重构。逐条审查「精准定位」「直达根因」「干净、可对比」「一键导出结论」及能耗/温度等陈述,按当前知识库和 E2/E4 门禁改成有来源、有限定的表述。WIP 的 FPS/CPU/Network 动态数字与曲线必须明确标“示意数据”,不能让访客误认真机实测。当前
/download/明记公开下载blocked,首页 CTA 应如实写“查看发布状态/了解产品”,不得写成可立即下载。 - 保留 WIP 的沉浸视觉意图,同时提供无 WebGL、WebGL context loss、禁用 JS/加载失败、移动窄屏和
prefers-reduced-motion下可读可导航的静态回退;页面标题、章节、CTA 必须可被 SSR/键盘/读屏获得。进入/离开首页的深色主题、动画循环、监听器与 GPU 资源需成对回收;后台标签不持续渲染。不得引入外部 CDN、竞品资产或隐私标识。 - 效率预算:守卫本身除两次远程 ref 查询外为 O(1),每次发布只做一次 VitePress build/一次上传,不因双远程推送重复构建;远程查询分别设有界超时。首页与当前生产页、原 WIP 隔离预览按同环境交错至少 3 次测首屏文本可见、LCP、JS 转移体积、主线程长任务、动画帧耗时、CPU/内存及移动端最差场景;目标为首屏文本在 WebGL 前可见、桌面 LCP ≤2.5 s、模拟移动 Fast 4G LCP ≤4 s、可见动画 p95 帧间隔 ≤33 ms。若预算失败,优先关闭重型效果/降级而非放宽门槛;报告真实差值与设备/节流条件,不用构建通过冒充运行时性能。
2 · 验收矩阵与发布/回滚
| 门 | 必须交付的独立可核证证据 |
|---|---|
| G0 隔离 | 上述 fail-closed 测试、双远程实时 SHA、脏树拒绝、旧 hook 前推送调用不能部署、Preview 永不占 Production、Cloudflare Source 与提交一致 |
| G1 内容 | 当前 main 的 knowledge/devlog/archive 路由与旧归档保留;首页独有 SSR 文本/视觉标识、示意数据标签、真实 CTA、能力陈述逐项来源;0 死链与 66 项知识门禁 |
| G1 交互 | 桌面/移动截图及短录屏;滚动五章、导航点、键盘、禁动效、WebGL 不可用、页面离开后无动画/监听残留;真实浏览器检查,不仅比 HTML 字符串 |
| G1 性能 | 相同环境三轮交错前后表;LCP/资源字节/CPU/RSS/动画 p95 与最差档;预算内才允许生产 |
| 发布 | G0 先完成;G1 代码窄范围提交并双远程同 SHA;从该 SHA 的干净隔离 worktree 一次生产部署;线上 / 的新组件/文案/示意标签和非首页关键路由逐内容核验;记录 deployment URL、Source SHA 与可复用的旧生产快照 |
G0 与 G1 都要把实际修改、方案偏差、命令、证据等级、遗留项追加到本 devlog,并按 AGENTS.md C2/C3 新增完成批次归档 HTML/时间线及门禁联动;不得回改旧归档。站点构建与浏览器验收是 E2/E3,生产真实访问与交互另列 E4-Web,不与 iOS 真机 E4 混称。Lead 在收到交付后独立判定 PASS/FAIL;Implementation 不自行宣布网站恢复完成。
本方案提交时旧 pre-push 仍不安全:为避免只发布方案却再次从共享脏树覆盖生产,Lead 对本方案的双远程 Git 推送明确跳过该本机 hook,已在本地运行构建;方案的公开站点页待 G0 安全发布入口落地后由 Implementation 一并核验。该一次性 containment 不是今后可绕过 G0 的通用发布方式。
若新生产页面有严重错误,保留 Git 历史,从发布前记录的已验证旧生产 SHA 创建干净隔离构建并经同一受控入口重新发布旧页面;不得 reset --hard、强推或删除 WIP 分支。回滚后仍记录旧/新 deployment URL、触发条件和复核结果。
Implementation Update · 2026-09-25 · G0(生产发布受控入口)
关联方案:本文 §1 G0(Lead 决策提交 4ff2d2a,status: approved)。本批只实现 G0,不含 G1 视觉恢复,未部署生产、未改首页、未合并/移植 wip/site-home-20260924、未触碰共享区其他 AI 的 4 项未提交文件。
实际修改范围与关键文件(4 个入库路径,1 个新增归档):
| 文件 | 变更 |
|---|---|
site/scripts/deploy-production.py | 新增。fail-closed 生产发布守卫 + 唯一一次构建/上传;CLI:--sha --remotes --timeout --dry-run --preview [BRANCH] --project --json |
site/scripts/tests/test_deploy_production.py | 新增。26 项拒绝/放行测试(决策表 10 + 端到端 16),脚本化 runner 断言调用次数与参数 |
site/package.json | deploy 由 npm run build && npx wrangler pages deploy … --branch main --commit-dirty=true 改为 python3 scripts/deploy-production.py;新增 deploy:dry-run、deploy:preview |
site/devlog/index.md | C2 时间线顶部节点 + C3「八阶段总计划」「未关门门禁」两表联动 |
docs/architecture/perfowl-homepage-restoration-and-production-deploy-gate-2026-09-25.html | 本 devlog 的完成批次归档快照(C2) |
最终实现方式:
- fail-closed 前置校验(在任何 build / 归档同步 / Cloudflare 调用之前完成,纯函数
evaluate_gate()决策、零副作用): ① 目标 SHA 为完整 40-hex 且等于当前HEAD; ②git status --porcelain为空(Git 自身排除被忽略的依赖与构建产物,故含其他 AI 未提交文件的共享工作树必然拒绝); ③ 实时git ls-remote origin refs/heads/main与git ls-remote github refs/heads/main各自精确等于该 SHA(只解析恰好一行<sha>\trefs/heads/main;多行、非 40-hex、空输出一律计入不可解析); ④ 目标明确为 production/main。 缺值、超时(默认每远程 20 s 有界)、网络失败、ref 不一致、工作树不干净 一律拒绝,且拒绝路径不执行任何构建或上传。不使用本地 remote-tracking ref、commit message、Webhook 或--commit-dirty作为判据。 - 放行后只构建一次、只上传一次:
npm run build一次 → 复核HEAD未变且跟踪文件未被改动 →npx wrangler pages deploy .vitepress/dist --project-name perfowl --branch main --commit-hash <完整 SHA> --commit-dirty=false一次。产物来自隔离干净 worktree(由守卫的干净判据强制)。 - Preview 与生产分离:
--preview(可带分支名,缺省取当前分支)必须解析出非main分支,否则拒绝;Preview 路径不做双远程 SHA 等值检查(允许预览未合入的 WIP),但保留「目标 SHA ==HEAD」与「干净工作树」。输出显式标注environment=Preview,永不占 Production。 - 效率(AGENTS 效率门禁):守卫除两次远程 ref 查询外为 O(1)(1 次
rev-parse+ 1 次status+ 2 次ls-remote+ 1 次exists),无仓库规模相关的遍历;远程查询各有界超时;每次发布只 1 次 build、1 次 upload,不因双远程推送重复构建。守卫自报分阶段耗时(preflight/remote_refs/build/upload)。
相对原方案的偏差及原因:
- 方案建议
site/scripts/deploy-production.*;实现取.py+python3,与既有metric-knowledge-gate.py同栈,零新依赖(不引入测试框架)。 - 方案要求「构建前后的 SHA 与干净状态再次核对」。实现把构建前判据定为完整
git status --porcelain(含未跟踪),构建后判据定为--untracked-files=no(仅跟踪文件)。原因:site/.vitepress/.temp与site/.wrangler未被.gitignore覆盖,构建必然产生未跟踪物;若构建后仍要求未跟踪视图全空会在合法构建后误拒。此为收窄误拒面、未放宽发布前置的偏差——构建前的完整干净视图与双远程等值仍强制。 - 方案未提依赖就绪检查;实现增加「隔离 worktree 缺
node_modules即拒绝(build=0 / upload=0)」,避免在缺依赖的 worktree 里产出半成品或触发隐式网络安装。 - 方案提到后续可把本机 hook 替换为纯提示;本批不改本机
.git/hooks/pre-push(它是未入库的本机状态),只证明其调用受控入口后会在构建之前被拒绝。
验证命令与证据等级(E2 构建/测试 + E3 真实 Git 与网络环境;无 E4,也不与 iOS 真机 E4 混称):
bash
python3 site/scripts/tests/test_deploy_production.py # 26 tests → OK
cd site && python3 scripts/deploy-production.py --dry-run # 共享脏树 → REJECT(2),build=0 upload=0
cd <该提交的隔离干净 worktree>/site && python3 scripts/deploy-production.py --dry-run
# 干净 + 双远程同 SHA → GATE PASS(0),build=0 upload=0
python3 site/scripts/metric-knowledge-gate.py # 66 contracts PASS
env -u CODEBUDDY_SAFE_DELETE_BULK_STATE_DIR -u CODEBUDDY_TOOL_CALL_ID npm run build # exit 0旧 hook 的 containment 证明(E3):本机 pre-push 在推送完成之前执行 cd $REPO_ROOT/site && npm run deploy,此刻远端 refs/heads/main 尚未前进 ⇒ 守卫第③项必然不满足;在共享脏树上第②项也必然不满足。两者都使守卫在构建之前返回 2,因此旧 hook 即使触发也不产生构建与上传。本批双远程推送另行采取明确防误部署措施(推送时不运行本机 hook),并在推送后对照基线复校 Cloudflare 生产 Source 未变化。
当前状态:implemented · gate-open。G0 是否 PASS、是否进入 G1,由 Lead 独立验收后决定;Implementation 不自行宣布 G0 PASS。
遗留项 / 后续门禁:
- G1 首页视觉恢复未开始,需 Lead 验收 G0 后下发独立指令。
- 本批只到
--dry-run的放行验证;真实wrangler pages deploy的 deployment id / URL 记录留待 G1 的受控发布执行。 - 本机
pre-push仍存在且恒exit 0;它现在只调用受控入口,属安全但多余的副作用源,建议后续替换为纯提示(需单独批准)。 site/.vitepress/.temp、site/.wrangler是否补进.gitignore由 Lead 决定;守卫已按「构建后只查跟踪文件」适配,不影响 fail-closed 前置。
Lead Independent G0 Audit · 2026-09-25 · REPAIR REQUIRED
Lead 对 c7c54fa5c6bbe4d42b8eb41d3b2eb6579b6e77f4 只读复核:提交仅六个本批文件、双远程 main 同 SHA,26 项原测试全部通过,归档 HTML 严格标签嵌套 0/0;本机 Cloudflare Production 最新 Source 仍是 ea2a351,没有本批部署。这些交付与防误推送事实成立,但 G0 的“非 main 不占生产 / 两个远程不可绕过 / 构建后仍来源一致”门未通过,因此 G1 不放行。
- P0:双远程硬门可由公开 CLI 选项关闭。
deploy-production.py:397,413的--remotes被解析为任意长度元组;生产evaluate_gate()仅遍历该元组。Lead 直接执行纯函数对抗探针:remotes=()与仅('origin',)均返回空拒绝原因,即0 个或 1 个真实远程也能放行生产。26 项测试没有空/单远程反例。生产检查必须固定且精确为origin与github,不得由生产调用者缩减;测试注入只能在不可作为正式 CLI 配置的内部桩中进行。 - P0:旧 WIP 分支的真实推送入口仍会绕过新脚本。 两远程
wip/site-home-20260924仍指向18d8182,该提交的site/package.json保留旧命令npm run build && wrangler ... --branch main --commit-dirty=true。本机.git/hooks/pre-push仍不区分分支,遇该分支的site/变更会从WIP 工作树调用该旧命令;新守卫只存在于当前 main,不能覆盖旧 Git 快照。故「旧 hook 现在只调用受控入口」「Preview 永不占 Production」只对当前 main 工作树成立,不能推广到 WIP。必须先使本机旧 hook 不再部署任意分支(备份后改为纯提示/无发布副作用),并提供 WIP 分支推送的无生产调用反例;Preview 必须由受控流程显式选择非 main。其他主机旧 hook 的状态若未核实,保留为环境边界,不宣称全域保证。 - P0:构建完成到上传间存在可复现的过期来源竞态。 代码只在 build 之前调用两次
ls-remote;build 后仅重查 HEAD 与跟踪文件,未重查两个远程。Lead 用原测试FakeRunner的对抗变体在 build 返回时将两远程从目标 SHA 切到另一 SHA,实际结果仍是exit=0 / upload=1,可把旧页面重新发为 Production。上传前须再次实时核对两远程与目标 SHA;不一致时upload=0。上传后的最新 Production Source 与双远程 SHA 仍须核查,不能把一次上传成功当作最终上线正确。 - P1:构建后脏树检查缩到 tracked-only,且构建/上传进程无超时。
--untracked-files=no会忽略构建期间新出现的非生成型site/文件;报告中「未放宽发布前置」因此不够准确。应只豁免经证实的生成目录,保留其他未跟踪路径的拒绝,并补“build 时生成新site/rogue.md⇒ upload=0”fixture。npm run build/npx wrangler当前timeout=None,需按实测 95 秒构建与网络上传设置明确且有余量的有界预算,超限不得上传/不得自称成功。
唯一 G0-R1 最小返工合同
- 只修 G0 守卫、对应测试、必要的生成物忽略规则及本机未入库旧 hook 的无发布副作用处置;不得开始 G1 首页、不得对 Cloudflare 执行真实上传。保留原 26 项测试,再补空/单远程、WIP 旧 hook、构建中双远程前进、构建中新未跟踪源码、构建/上传超时、上传后 Source/远程不一致的正负 fixture。每个拒绝路径断言 build/upload 次数与原因,不只断言 exit code。
- 生产分支判定须读取实际 checkout 状态:仅 main 或确认为双远程同 SHA 的 detached 干净隔离工作树可生产;不能在代码中把生产
branch直接赋值为main当作已验证事实。Preview 指令须防止 WIP 旧脚本/旧 hook 误标main。 - 在本 devlog 追加日期化 G0-R1 Implementation Update,列代码差异、被取代的过度结论、最差时间/CPU/RSS/I/O 对照、仍不能证明的跨主机 hook 状态。一个窄范围提交;双远程推送时继续明确避开不安全旧 hook,Cloudflare Production Source 必须维持基线。Implementation 不自判 G0 PASS;Lead 独立复核后才考虑 G1。
Implementation Update · 2026-09-25 · G0-R1(双远程硬门、上传前重查、有界预算、旧 hook 处置)
关联合同:上文「Lead Independent G0 Audit · 2026-09-25 · REPAIR REQUIRED」→「唯一 G0-R1 最小返工合同」(Lead 基线 6ebcd30)。本批只修 G0:未恢复首页、未 merge/cherry-pick WIP、未执行任何真实 Cloudflare 上传、未触碰他方 4 项未提交文件、未进入 G1。G0 PASS 由 Lead 独立裁定。
实际修改范围与关键文件
| 路径 | 变更 |
|---|---|
site/scripts/deploy-production.py | 关键路径重写:删除 --remotes;生产远程集合固定为模块常量;checkout 状态派生生产分支;构建后(上传前)二次实时双远程核查;生成物白名单取代 tracked-only;build/upload/verify 三类有界超时;上传后 Production Source 核查 |
site/scripts/tests/test_deploy_production.py | 26 → 58 项(原 26 项语义全部保留,新增 32 项对抗 fixture) |
.gitignore | 新增 site/.vitepress/.temp/、site/.wrangler/ |
site/devlog/index.md | C2/C3 联动:时间线节点链接指向本归档快照 + 「八阶段总计划」P7 行 + 「未关门门禁」G0 行补 G0-R1 事实 |
.git/hooks/pre-push | 本机未入库状态:原始 1683 B / sha256 d5fdf780…774d 备份为 pre-push.legacy-autodeploy-2026-09-25.bak,替换为无发布副作用的纯提示 hook(971 B) |
docs/architecture/…-deploy-gate-2026-09-25-g0r1.html | 完成批次归档快照(C2,新文件;旧快照不回改) |
逐条回应 Lead 阻断项
- P0-1 双远程硬门不可由 CLI 缩减。
--remotes已从 CLI 删除。生产远程集合为模块常量PRODUCTION_REMOTES = ("origin", "github");evaluate_gate(gate)不再接受远程参数,并额外拒绝任何remote_heads键集不等于该常量的 gate(生产必查的集合是代码事实,不是调用者输入)。对抗探针(对真实模块):旧调用形状evaluate_gate(x, ())→TypeError;remote_heads={}/ 仅origin/ 仅github/ 多一个远程 → 全部allowed=False且理由含 "cannot be reduced by a caller";--remotes origin→ argparseSystemExit(2) unrecognized arguments。测试注入只经由内部Gate构造与脚本化 runner,不能作为正式 CLI 配置。 - P0-2 WIP 旧入口的自动触发点已消除,并给出无生产调用反例。
wip/site-home-20260924的18d8182仍带旧--branch main --commit-dirty=true(不回改他人分支/快照);其唯一自动触发点(本机pre-push)已去副作用。记录型 PATH shim 对照反例:对同一 WIP ref stdin,旧 hook(备份件)调用npm run deploy(即真实生产发布),新 hook 0 次调用且exit 0。守卫层独立反例:干净 detached worktree @ WIP 提交18d8182→ REJECT(2),理由为两个远程refs/heads/main均不等于该提交(WIP 提交不是 main,永不能作为生产来源);test_wip_branch_checkout_never_uploads另证「非main且非 detached 的 checkout,即使干净、双远程同 SHA,也拒绝且 upload=0」。Preview 路径必须解析出非main分支(空或等于main一律拒绝),确保 WIP 旧脚本/旧 hook 无法把 Preview 标成main。 - P0-3 构建→上传的过期来源竞态已封闭。 上传前新增第二轮实时双远程查询(
_query_remotes()),并以evaluate_pre_upload()判定:HEAD 变化、任一远程前进或不可解析、非生成型路径漂移 → upload=0。对抗探针复现 Lead 同款攻击(build 返回时把两远程切到另一 SHA):exit 2 / build=1 / upload=0,理由为 "a remote advanced while the site was building; refusing to publish a stale commit"。上传后新增wrangler pages deployment list核对最新 Production 行的 Source;不一致、不可解析或超时 → exit 4 且productionSourceVerified=false,显式打印 "reporting partial, not success"——不把一次上传成功当作上线正确。 - P1-4a 构建后脏树检查不再放宽。
--untracked-files=no已废弃,改回完整git status --porcelain,仅豁免经证实的生成目录GENERATED_SITE_PATHS(site/.vitepress/{.temp,cache,dist}、site/node_modules、site/public/archive、site/.wrangler);.gitignore同步补两条作为第二层。fixture:build 期间生成site/rogue.md→ upload=0 且理由点名该文件;只生成上述目录 → 仍正常上传。 - P1-4b 构建/上传改为有界预算。
--build-timeout默认 600 s(本机实测构建 95.06 s ⇒ ≈6.3× 余量)、--upload-timeout默认 900 s、--verify-timeout默认 90 s、--ls-remote-timeout默认 20 s(每远程)。超时一律按失败上报:build 超时 → upload=0;upload 超时 → 打印 "UPLOAD TIMED OUT … NOT reporting success" 且productionSourceVerified=false;verify 超时 → exit 4。 - 生产分支由实际 checkout 状态派生。 不再把
main当作已验证事实:读取git rev-parse --abbrev-ref HEAD,判main/detached/ 其他,并将checkoutKind、branchRef、deployBranch写入证据;只有main或 detached 干净隔离工作树可生产。
被取代的过度结论(明确撤回)
「旧 hook 现在只调用受控入口,因此安全」—— 只对当前 main 工作树成立。WIP 快照内仍是旧命令,因此真正的修复是「本机 hook 去副作用 + 守卫拒绝非 main checkout」,不是把守卫加进当前 main 就完事。「守卫除两次远程 ref 查询外为 O(1)」—— 生产路径现为构建前 2 次 + 上传前 2 次 = 4 次实时远程查询,另有可选 1 次 Source 核查。复杂度仍是 O(1)(不随仓库规模增长),但网络往返翻倍。「构建后只查跟踪文件,未放宽发布前置」—— 该表述不准确:--untracked-files=no确实放过了构建期间新出现的非生成型未跟踪文件。已改回完整状态检查 + 生成物白名单,承认前一版是放宽。「Preview 永不占 Production」作为全域结论—— 仅对本仓库的受控入口成立;已发布 Git 快照中的旧命令不受本批约束。
效率与资源对照(E2/E3,本机 macOS,time / /usr/bin/time -l;守卫 dry-run,真实 Git 与网络)
| 路径 | 墙钟 | user CPU | sys CPU | 最大 RSS | 块 I/O |
|---|---|---|---|---|---|
| 拒绝:共享脏树(2 次远程查询,构建前即拒) | 15.88 s | 1.00 s | 0.26 s | 33.5 MB | 0 / 0 |
| 放行:干净 detached worktree @ 远程 SHA(dry-run) | 15.61 s | 0.79 s | 0.25 s | 34.0 MB | 0 / 0 |
| 完整放行(本批未执行)上界 | ≥ build 95.06 s + 4 次远程查询 + 1 次上传 | — | — | — | — |
墙钟几乎全部是 git ls-remote 的 SSH 往返(GitLab 局域网 + GitHub 各约 7 s);守卫自身逻辑(rev-parse×2 + status×2 + exists)为常数级。每次发布仍严格 1 次 build + 1 次 upload。--help 级别的 CLI 解析、evaluate_* 纯函数调用均 <1 ms(58 项测试 0.015 s)。
跨主机 hook 边界(不能证明的部分):本批只处置了本机 .git/hooks/pre-push(未入库状态)。其他主机、其他克隆、CI runner 或任何本仓库管辖之外的机器上的同类 hook 未经核实,仍可能保留旧行为;已发布 Git 快照中的旧 deploy 命令也不会因本批而改变。本批不宣称全域保证,该边界保留为环境已知风险。
验证命令与证据等级(E2 测试/构建 + E3 真实 Git 与网络;无 E4,不与 iOS 真机 E4 混称)
bash
python3 site/scripts/tests/test_deploy_production.py # 58 tests → OK
python3 <对抗探针> # 空/单远程、CLI、构建中远程前进、非 main checkout
bash .git/hooks/pre-push <WIP refs> (记录型 PATH shim) # 新 hook 0 次发布调用;旧 hook 调用 npm run deploy
python3 scripts/deploy-production.py --repo-root <WIP worktree> --dry-run # REJECT(2)
python3 scripts/metric-knowledge-gate.py # 66 contracts PASS
env -u CODEBUDDY_SAFE_DELETE_BULK_STATE_DIR -u CODEBUDDY_TOOL_CALL_ID npm run build当前状态:implemented · gate-open。G0 PASS / 是否进入 G1 由 Lead 独立复核后决定;Implementation 不自行宣布 PASS。
遗留项
- 真实上传仍未执行(本批 0 次
wrangler pages deploy);Cloudflare Production Source 维持基线ea2a351。 --verify-timeout的 90 s 与上传路径的 900 s 预算只在脚本化桩上验证,未在真实 Cloudflare 调用上计时。- 上传后 Source 核查依赖
wrangler pages deployment list的表格格式;若上游改版需同步解析器。 - 其他主机 hook 状态属环境边界(见上)。
Lead Independent G0-R1 Audit · 2026-09-25 · REPAIR REQUIRED
Lead 对 c93dea617c2a4abaac788c3c79ecdbed9f6af9ec 作只读复核:提交仅六个约定路径,双远程跟踪 ref 同 SHA;独立复跑部署守卫测试 58/58 OK;本机 pre-push 已变为无发布调用的提示 hook,原件有备份;执行归档中的 Cloudflare 推送前后列表逐字节一致,最新 Production Source 仍为 ea2a351。本批没有真实上传,G1 没有开始。这些子门成立;G0 总门仍为 REPAIR REQUIRED,G1 不放行。
- P0 · 构建后 rename 可绕过源码漂移门。
is_generated()对 porcelain rename 只取->右侧目的路径;R site/index.md -> site/.vitepress/dist/evil.md因目的路径在生成白名单内被当作纯生成物。Lead 在真实模块和既有FakeRunner上复算:is_generated=True、execute exit=0 / build=1 / upload=1 / DEPLOY OK,尽管受版本控制的站点源文件已被移走。判定必须同时检查 rename/copy 的源与目的;任何非生成型路径参与变更均拒绝上传。不能仅检查目的路径。 - P0 · 上传期间远程前进仍可误报生产成功。 当前第二次
ls-remote在上传前,上传返回后只读 Cloudflare Production Source,不再读双远程。Lead 在上传动作后将两个远程从目标A切到B,同时令 Production Source 为A:实际exit=0 / build=1 / upload=1 / DEPLOY OK,上传后ls-remote次数为 0。这正违反上文 G0-R1 合同的「上传后的最新 Production Source 与双远程 SHA 仍须核查」。已上传时若任一远程前进/不可解析,必须报告partial、不得报告生产验收成功;不得试图用脚本回滚云端。 - P1 · Preview 误用 Production 行验收。
execute()无论 mode 均调用evaluate_post_upload()查最新 Production 行。Lead 的 preview 反例已上传一次 Preview,但 Production Source 为另一 SHA 时返回exit=4;若 Production 恰为相同前缀又会把 Preview 误称为source-verified。Preview 应只用同一受控入口验证对应 Preview 分支/部署,不借 Production 行证明 Preview 成功;拒绝mainPreview 的现有门保持。
唯一 G0-R2 最小返工合同
- 仅改
site/scripts/deploy-production.py、对应测试,以及本 devlog 的日期化 Implementation Update;C2/C3 在交付时按实际门禁状态最小联动并新增归档快照。不得改 MacApp、首页视觉、旧归档、WIP 分支或他方四项未提交路径;不得创建常驻新 worktree;本批仍不真实上传 Cloudflare、不进入 G1。 - 保留 58 项既有测试并补三族对抗测试:
rename/copy任一侧涉及源码 ⇒upload=0且理由含该源码;上传中双远程任一或全部前进、超时或不可解析 ⇒ 已上传但exit=4 / productionSourceVerified=false,并保留云端 Source 与远程的并列证据;Preview 上传后只检查对应 Preview 分支/Source,不从 Production 行取成功,且main仍拒绝。每例断言 build/upload/verify 次数、判定理由与证据字段,不能只断言退出码。 - 上传后校验应区分Cloudflare 表格只展示短 Source 前缀与 Git 双远程完整 40 位 SHA:短前缀只作列表同源线索,不得在报告中称作完整 SHA 精确证明。若本机工具能取得该 deployment 的完整 commit SHA 与 id,优先按完整值验证;否则显式记录证据等级与局限,不降低远程 40 位精确门。增加真实 Wrangler 输出样本解析 fixture,不只用人工构造表格。
- 资源边界保持一次 build、一次 upload;新增上传后两次
ls-remote均沿用每次 20 s 上界,不加轮询/无限等待。继续只在共享main上窄范围提交并双远程推送;不得因推送触发自动发布;Cloudflare Production Source 在本批前后须保持不变。Implementation 交回精确 commit、diff、反例结果、完整测试、真实远端/云端状态与遗留项,不自判 G0 PASS。Lead 验收 G0-R2 后才决定 G1。
Implementation Update · 2026-09-25 · G0-R2(rename/copy 双向门、上传后远程复检、环境感知验收)
关联合同:上文「Lead Independent G0-R1 Audit · 2026-09-25 · REPAIR REQUIRED」→「唯一 G0-R2 最小返工合同」(Lead 基线 afb00f2)。本批只改 site/scripts/deploy-production.py、其对应测试、本 devlog 与 C2/C3 联动文件;未改 MacApp、首页视觉、旧归档、WIP 分支或他方四项未提交路径;未创建常驻 worktree(仅在测量时临时 git worktree add --detach 后于同一条命令内 remove --force);未真实上传 Cloudflare;未进入 G1。G0 PASS 由 Lead 独立裁定。
精确改动范围
| 路径 | 变更 |
|---|---|
site/scripts/deploy-production.py | +315 / −25:is_generated() 改为逐路径判定(新增 status_paths() / _unquote(),rename/copy 同时检查源与目的);新增 parse_deployment_rows() / select_verification_row();evaluate_post_upload() 增加 mode / deploy_branch;新增 evaluate_post_upload_remotes();execute() 增加上传后远程复检与环境感知验收 + 7 个新证据字段 |
site/scripts/tests/test_deploy_production.py | +376 / −7:58 → 88 项(新增 30 项,保留原 58 项,其中 3 项的断言/fixture 被合同强制取代,见下) |
site/devlog/index.md | C2/C3 联动(时间线节点 + 「八阶段总计划」P7 行 + 「未关门门禁」G0 行) |
docs/architecture/…deploy-gate-2026-09-25-g0r2.html | 新归档快照(C2;旧快照 …-g0r1.html 与 …-2026-09-25.html 均不回改) |
逐条回应 Lead 阻断项
- P0-1 rename/copy 不能再把站点源码「洗成」生成物。 新增
status_paths():按 porcelain v1 解析,R/C行产出源与目的两个路径,切分" -> "时遵守引号(引号内的箭头不切分),并解开 git 的 C 风格引用(含八进制 UTF-8 字节)。is_generated()现在要求该行触及的每一个路径都在GENERATED_SITE_PATHS内,否则整行计入漂移,且拒绝理由显式写明「a rename/copy is judged on BOTH its source and its destination」并原样列出含源路径的 status 行。Lead 的原反例R site/index.md -> site/.vitepress/dist/evil.md:修复前is_generated=True、纯函数零拒绝理由、真实execute()走到exit=0 / build=1 / upload=1 / DEPLOY OK;修复后is_generated=False、理由点名site/index.md、exit=2 / build=1 / upload=0 / verify=0(探针 A1–A4,见证据包)。反向保护同样保留:R site/.vitepress/cache/a.json -> site/.vitepress/dist/a.json(两侧都在生成白名单内)仍可上传,避免把正常构建输出误判为漂移。 - P0-2 上传期间的远程前进不再被报成生产成功。 上传返回后新增第三次实时双远程查询(
_query_remotes(),每个远程沿用 20 s 上界,无轮询、无无限等待),由evaluate_post_upload_remotes()判定;任一远程前进、超时或不可解析 ⇒exit=4、verdict="partial"、productionSourceVerified=false,并把云端 Source 与远程 live head 并列打印(evidence <remote>: post-upload live head = … | cloudflare <env> Source short prefix = …)以及写入postUploadRemoteHeads/postUploadRemoteNotes/postUploadRemoteReasons。不做任何云端回滚,只如实报 partial。Lead 的反例(上传后两远程 A→B、同时 Production Source 仍为 A):修复前exit=0 / upload=1 / ls-remote 次数 4 / productionSourceVerified=true;修复后exit=4 / upload=1 / ls-remote 次数 6 / productionSourceVerified=false,且 B1 双前进、B2 单前进、B3 超时、B4 解析失败四条路径分别留证(探针 B1–B4)。 - P1-3 Preview 不再借 Production 行验收。
evaluate_post_upload()增加mode/deploy_branch:production 只认最新 Production 行(且该行必须在main,否则拒绝);preview 只认 Environment=Preview 且 Branch 等于本次发布分支的行,绝不使用 Production 行。Lead 的反例(Preview 已发布,但列表只有同短前缀的 Production 行):修复前exit=0且productionSourceVerified=true;修复后exit=4,理由含 "a preview publish is never proven by a Production row"。对照正向用例(列表含匹配的 Preview 行)仍为exit=0;--preview main仍exit=2且 build=0 / upload=0 / verify=0(探针 C1–C3)。Preview 路径按设计不查询生产远程(ls-remote次数 0,postUploadRemoteHeads={})。
被取代的既有断言(点名、旧值、接续覆盖)
ExecuteTests.test_each_remote_is_queried_before_and_after_the_build:真实放行路径的ls-remote期望值 4 → 6(构建前 2 + 上传前 2 + 上传后 2,三段各自覆盖固定的origin+github)。原因:合同第 2 条要求上传后复检,该断言不可能同时保持 4;旧值已不再成立。接续覆盖:同一测试扩展到三段逐一断言,另加test_post_upload_remote_recheck_uses_the_ls_remote_budget断言新增两次查询沿用ls_remote_timeout。ExecuteTests.test_preview_never_uses_main_branch与test_preview_branch_resolved_from_current_branch:两者的 fixture 原先只给默认的 Production 行,合同第 3 条生效后 preview 会正确判为未验证(exit=4)。断言目标不变(Preview 必须显式非main、--branch逐字一致),仅把verify_listing换成含匹配 Preview 行的列表。换言之:这两项从「借 Production 行就算成功」变为「必须有自己的 Preview 行」,正是 Lead 要求的行为。- 无其他既有测试被改动;原 58 项的名称、意图与覆盖全部保留,仅上述 3 项的期望值/fixture 因合同强制而更新。
证据等级:Cloudflare Source 是短前缀,不是完整 40 位证明
- 本机探测:
wrangler pages deployment list --project-name perfowl(4.140.0)与--json两种输出都只给出 7 位Source前缀(ea2a351),--json额外给出完整的 deploymentId(UUID),没有完整 40 位 commit SHA;本机~/.wrangler/config/default.toml只有 OAuthoauth_token/refresh_token(无 API token),控件刻意不去读取该凭据文件换取完整值。因此:短前缀只记为「同源列表线索」(sourceEvidenceLevel="cloudflare-list-short-source-prefix"、sourceShaFullPrecisionAvailable=false、sourceShaPrecisionNote写明),不降低远程 40 位精确门——40 位精确性继续由上传后那两次git ls-remote承担,两者语义在证据中分开记录。 - 解析器新增真实 Wrangler 输出样本 fixture(
REAL_WRANGLER_LISTING):含 ANSI 着色警告行、空行、版本横幅与真实框线表格的逐字节列宽(部署 id /*.perfowl.pages.dev子域 / 控制台账号 id 替换为等长占位符,不含任何基础设施标识);据此断言表头与分隔行被丢弃、4 条数据行被解析、最新 Production 行的Id/Branch/Source取值正确,并覆盖 stale / preview 两种判定。
效率与资源对照(E3,本机 macOS,/usr/bin/time -l,真实 Git 与网络;单次远程查询实测 origin ≈ 10.6–11.2 s、github ≈ 3.83 s)
| 路径 | 墙钟 | user CPU | sys CPU | 最大 RSS | 块 I/O | 远程查询次数 |
|---|---|---|---|---|---|---|
| 拒绝:共享脏树 dry-run(构建前即拒) | 14.43 s | 0.28 s | 0.13 s | 18.9 MiB | 0 / 0 | 2 |
| 放行:干净 detached worktree dry-run | 14.42 s | 0.26 s | 0.13 s | 17.5 MiB | 0 / 0 | 2 |
| 放行:完整路径(本批未执行) | ≥ build 95.58 s + 3 轮远程查询 ≈ 43 s + 1 次上传 + 1 次验收 | — | — | — | — | 6 |
本批新增成本:仅在放行路径增加一轮双远程查询(≈ 14.4 s,两次 SSH 往返之和),拒绝路径一分增加;仍严格 1 次 build + 1 次 upload,新增查询沿用每次 20 s 上界、无轮询、无无限等待。守卫自身逻辑(rev-parse×2 + status×2 + exists + 纯函数判定)为常数级:88 项测试合计 0.015 s。
验证命令与证据等级(E2 测试 + E3 真实 Git 与网络;无 E4,不与 iOS 真机 E4 混称)
bash
python3 site/scripts/tests/test_deploy_production.py # 88 tests → OK
python3 <A/B 对抗探针 against c93dea6 and the working tree> # 三族反例:修复前全部放行,修复后全部阻断
python3 site/scripts/metric-knowledge-gate.py # 66 contracts PASS
env -u CODEBUDDY_SAFE_DELETE_BULK_STATE_DIR -u CODEBUDDY_TOOL_CALL_ID npm run build当前状态:implemented · gate-open。G0 PASS / 是否进入 G1 由 Lead 独立复核后决定;Implementation 不自行宣布 PASS。
遗留项
- 真实上传仍未执行(本批 0 次
wrangler pages deploy);Cloudflare Production Source 维持基线。 - 上传后的完整 40 位 commit 证明仍依赖两个 Git 远程;Cloudflare 侧只有 7 位短前缀 + deployment
Id(见上)。若日后配置了可读的 API token,可把「完整值优先」作为独立增量补上,本批不引入凭据读取。 parse_deployment_rows()只解析框线表格(--json数组同样含Id/Environment/Branch/Source,字段名一致但当前调用未使用--json);若上游改版需同步解析器。- Preview 路径按设计不查询生产远程,因此「Preview 期间上游前进」仍不产生 partial;该语义与 G0-R1 合同一致,若 Lead 希望 Preview 也复检需另立合同。
Lead Independent G0-R2 Acceptance · 2026-09-27 · E2 PASS / Delivery partial
Lead 独立核验 372763c62d1f2d3d86946d079f53cb935243d7bc(parent afb00f2):范围恰五文件、旧归档零回改、差异检查干净,守卫测试 88/88 OK。独立对抗复算:源码 rename 到生成目录 ⇒ exit=2 / build=1 / upload=0;上传中任一远程前进 ⇒ exit=4 / upload=1 / verdict=partial / productionSourceVerified=false;仅 Production 行不能证明 Preview(exit 4),匹配 Preview 分支行则通过(exit 0)。固定双远程集合、超时边界、非 main 生产拒绝与本机无发布副作用 hook 均保持。新 HTML 严格嵌套 0 错位 / 0 未闭合,docs/dist SHA 同为 3e515b1b6df5ab8ddef8f3b73bc34207202557a48dd3d6451802672e987c053e。G0-R2 三项返工及 G0 代码/离线门 E2 PASS,不再安排同范围代码返工。
交付仍 partial,不能算生产发布完成。 Lead 本轮实时查询 GitHub main 为 372763c;GitLab origin 的 ls-remote 仍连接失败。执行证据中的云端推送前后表未变化,Production Source 保持 ea2a351;本轮不真实上传。原有主工作区 + 三个历史 worktree 均保留,未新建工作树;他方四项 WIP 未动。G1 首页恢复尚未开始,产品 A1 UI E4 仍另案 OPEN。
短 Source 的裁定:允许将 Cloudflare 七位 Source 前缀作为 G0 E2 的列表同源线索,前提是 sourceShaFullPrecisionAvailable=false 和证据等级被保留;Git 双远程的完整 SHA 只证明 Git 源码状态,不能替代 Cloudflare 完整 commit SHA 证明。本轮未取得云端完整 SHA,不将「没有 API token」推断成所有可用接口均不可查询。G1 真发布仍须记录上传 id/URL、完整出站 SHA、内容/静态产物校验及双远程实时值,不凭七位前缀或一个布尔字段宣告生产验收。当前 verdict=allow 可能只代表前置门,Preview 兼容字段 productionSourceVerified 也不能单独充当生产证明;必须结合最终 exit code、mode、verificationEnvironment 与来源精度读证据。这些字段命名不改变本次三项硬门的实际拒绝效果,不扩大本轮修改范围。
唯一下一动作 · 远程交付补齐(不新增开发批次)
Implementation 先读取当前 Git HEAD与本节,不沿用 372763c 为固定最新 HEAD。仅在 GitLab 恢复后核对双方远端分支祖先关系,对当前 main 执行普通快进推送;不得 force、reset、merge 或清理他方 WIP。复核 HEAD == origin/main == github/main 的实时 SHA,确认通知 hook 无发布调用、Cloudflare Production Source 未变,并交回同步结果;失败时仍报 partial,不使用本地 URL 重写/镜像替代真实双远程验收。不新建 worktree、不改守卫、不构建/上传、不进入 G1。Lead 收到真实同步证据后另发 G1 执行指令,无需 Owner 再批准一轮。