Plan 4 Task 9 收尾(阶段 4 收官): - ADR-018(主题与构建决策八条):Astro 5.18 选型与 MIT/依赖只进主题包/ vitest 双版本(根 2.1.9 + 主题 3.2.7——astro vite6 兼容);渲染信任链 bodyHtml 不二次清洗(三段接力);分页 V1 不做——路由登记义务随迁移移交; P0 发布门禁偏离(issue 无 DB 源,Plan 5 SEO diff 重审);编码路径四层 字节精确 + 资产键 URL 线形归一(236f3dc);预览零依赖 + host 白名单 收紧(回环纪律,比 spec §12.2 更严);CSP style-src 'unsafe-inline' (Astro scoped 样式内联);报告 HTML 先写两域对齐 - README:快速开始补 publish→build→preview 全链命令链示例(含 artifacts/jobs/<jobId>/ 产物路径);目录结构补 themes/ 与 apps/preview-server/;当前状态阶段 4 完成(真实 hesupipe 190 页可 浏览器预览/408 测试/23 验收断言/6 截图);ADR-001~018 导航;下一步 指向 Plan 4 文末移交清单 - Plan 4 文档:Task 9 勾选 + 验收节 ✅ 全过;文末新增"移交 Plan 5 清单" 7 项(SEO 三方 diff 新输入源/完整门禁六件含可复现双构建与 footer 年份 固定化/sitemap-robots 预览可达/og 元数据/836 nginx conf 输出/Task 0.2 备份续传/度量基线 ~64s 首跑·~15s 复跑) 三绿全量:lint 过 / typecheck 五段过 / test 408(根 382 + 主题 26)全绿。 不 push——推送由用户执行。 Co-Authored-By: Claude Code <noreply@anthropic.com>
27 KiB
SiteForge 纵向切片 · Plan 4:发布、主题与静态预览(阶段 4 / 快线第 3 步)实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: hesupipe 真实数据第一次在浏览器亮起来:content:publish(显式发布)→ 冻结 Render Snapshot → pilot-minimal 主题渲染六类页面 → Astro 静态构建(媒体本地化)→ 最小门禁 → 本地 Preview Server 按真实路由响应。验收 = 快线"第 10 天演示标准"(六类页面可浏览、真实内容、图片本地化、SEO 基线、200/404、sitemap、移动端无明显破版)。
Architecture: 依据快线 §8-§9、V2 spec §8(发布)/§9(主题与 Render Snapshot)/§12(Route Manifest 与 Preview)/§13(门禁子集);消费 Plan 3 移交清单 9 项(必读——含五条既定事实)。表已冻结(build_jobs/build_input_snapshots/build_artifacts/validation_runs 均为 Plan 1 遗产);契约已冻结(ThemeManifest/RENDER_SNAPSHOT_FILES 六文件)。主题五禁:不查库/不调 API/不读源快照/无 Seed/构建时断网。
Tech Stack: Astro 5(新依赖,进 themes/pilot-minimal 包)+ 既有栈。预览服务器零新依赖(Node http + route-manifest 匹配)。
边界(不做):双构建 Hash 一致性(可复现性归 Plan 5/7)、SEO 三方 diff(Plan 5)、部署器/Release/发布锁(Plan 7)、第二主题、可视化编辑、构建沙箱容器化(本地进程纪律代替)。
前置事实(执行者必读)
- Plan 3 移交清单 9 项(Plan 3 文末)+ 五条既定事实:①
/products/被 basic_page 占据——产品列表演示用/product-category/*(10 个真实分类页)+/products/内容页走通用模板;② 19 条?p=无路由不进构建;③ is_primary first-import-wins;④ 五列锚点查询三份拷贝(本计划 publish 复用 content-writer 的或抽共享 helper——抽共享 helper,第 4 个消费方触发 Plan 3 审查既定条件);⑤ JSON/HTML 双写窄窗口——build 报告落盘改 HTML 先写或接受(顺手修)。 - 数据现状(临时库已 DROP——本计划验收时重建):192 entry(129 news/48 product/15 page)全为 current 未发布;2772 routes(html 190/redirect 1746/asset 836);836 media ready(public_path=旧 uploads 路径);媒体副本已在
storage/incoming/hesupipe-media/(2625 文件 164.5MiB)+ content-addressed 快照。bodyHtml 中图片是旧站绝对 URL(https://hesupipe.com/...)——渲染快照生成时映射为根相对 public_path(day-10"图片本地化"的兑现位)。 - 发布语义(V2 §8):publish = entry_locales.published_revision_id ← current_revision_id(首次批量发布 192 条);dry-run 展示差异清单 + confirm token;审计落 audit_logs。导入永不发布(已由 Plan 3 保证,本计划不碰导入代码)。
- Render Snapshot 六文件(contracts 冻结):site/content/taxonomies/routes/assets/render-manifest.json——从 published 修订 + route_registry + media ready 集合物化,canonical JSON + 总哈希(build_input_snapshots 表记录)。
- 既有纪律全套:TDD/临时库/四段 typecheck/身份断言/cms 只读/artifacts 与 storage 不入库。
- 版本纪律:新依赖 Astro 锁 minor;许可证 MIT 记 ADR-018。
- 路径形态事实(评审实测):真实 hesupipe 有 54 条百分号编码中文 permalink(如
/%e5%85%b3%e4%ba%8e%e6%88%91%e4%bb%ac/,多为 page/news)且 0 条 .html permalink(该规则仅夹具用)。编码路径贯穿规则钉死:routes.json→dist 文件路径→route-manifest→preview 匹配全程编码原样(字节精确),V1 不做解码归一(Windows/NTFS '%' 合法;解码归一属 SP3 验证器)。.html规则保留但标注"仅夹具防御"。 - 发布补充钉死:①
--actor参数(CLI 必传或缺省super_admin@cli,audit_logs.actor_label NOT NULL——本计划是全仓第一个审计写入方);② P0 门禁决策:V1 publish 不读报告文件做 P0 阻断(ADR-018 记偏离——import issue 无 DB 查询源,206 条真实现状下严格门禁会反堵发布;missing 媒体等已在 build/gate 层有真实防线),Plan 5 SEO diff 时重审。
仓库结构增量
themes/pilot-minimal/ # Astro 项目(独立 package.json:astro 依赖)
theme.manifest.ts # contracts.ThemeManifest 实现
src/layouts/BaseLayout.astro
src/components/ # SeoHead/SiteHeader/SiteFooter/Breadcrumbs/RichText/ResponsiveMedia/ProductCard/NewsCard/Pagination/NotFound
src/pages/[...slug].astro # 六类页面按 render data 分派(getStaticPaths 读输入目录)
src/lib/load-render-data.ts # 只读输入目录(SF_BUILD_INPUT 环境变量)
apps/api/src/modules/publish/ # publish.service(零装饰器)+ module
apps/api/src/modules/build/ # build.service 扩展(已有 spawn 构建骨架?无——Plan 1 只建了 build 触发占位?核实:无 build 模块,本计划新建)
apps/preview-server/ # 预览服务器(Node http,零依赖)
scripts/sf.ts # + content:publish / build:create / preview:start
Task 1: content:publish 命令(显式发布)✅ 完成(c35f108 + rider 6a1d9a3,双审通过;340 测试;dry-run/confirm 单事务/审计首写入方/config v1/anchors helper 三处合并逐字保真。审查移交:brand 容忍契约已入 Task 3;audit 第二写入方时抽共享 helper)
Files: apps/api/src/modules/publish/{publish.service.ts,publish.module.ts};sf.ts + content:publish;Test tests/integration/publish.test.ts
- 失败测试(临时库,mini 全链 register→import 后):① dry-run:列出 current 非空且 published≠current 的 192 条(mini 3 条)清单(typeKey/slug/修订对)+ 汇总计数,零写入;② confirm(token 从 dry-run 输出取):published_revision_id=current、audit_logs 行(action='content.publish' 批量摘要含条数)、重复发布幂等(已等则 skip);③ site_config_revisions 首版:首次 publish 时若站点无 config 修订 → 从 site 行生成 v1(name/origin/locale 空品牌占位——data 结构按后续主题消费定义:{name, origin, locale, brand:{}});④ 未导入条目不受影响
- 实现:
publishSiteContent(db,{siteKey, dryRun, confirmToken, actor})——查询起点entry_locales.current_revision_id IS NOT NULL AND (published_revision_id IS NULL OR published_revision_id != current_revision_id)(事务批量 UPDATE);dry-run token=随机;actor 按 --actor 传入、缺省 super_admin@cli(前置事实 8);P0 门禁按前置事实 8 决策不做(ADR-018 记偏离);五列锚点共享 helper 顺手抽apps/api/src/modules/import/anchors.ts(三写入器改 import——零行为变更由既有测试守护) - 绿 → commit
feat(publish): 显式发布命令(dry-run/confirm/审计/站点配置首版)
Task 2: Render Snapshot 物化 + build job ✅ 完成(8f42c83 + 修复 5434afe,双审通过;354 测试;审查战果:哈希链===文件字节互证、media 块 first-wins 确定性、job 复用只认 succeeded+#a 谱系崩溃回收、repeatable read 隔离探针实证。遗留 Task 5/6:stale 宣卫/幂等键事务外窗口/existsSync 出事务)
Files: apps/api/src/modules/build/{build.service.ts,render-snapshot.ts,build.module.ts};sf.ts + build:create;Test tests/integration/build-snapshot.test.ts
- 失败测试(临时库,publish 后):① build_jobs 行(queued→snapshotting→validating(空)→succeeded——dist 尚空;Task 6 接真实构建时新建 build job而非改行——build_input_snapshots 有不可变触发器)+ build_input_snapshots 行(三哈希/site_config_revision_id/theme_key='pilot-minimal');② 六文件物化:site.json(config v1)/content.json(published 修订——bodyHtml URL 重写钉死匹配键:URL 剥 host 后按 path 与 media ready 集合的 public_path/source_url 路径段比对(覆盖绝对/根相对/协议相对变体),命中→根相对 public_path;未命中保留原样+issue)/taxonomies.json(term+taxonomy)/routes.json(html+asset 行,redirect 并入)/assets.json(ready:public_path→storage 实体路径+sha256)/render-manifest.json(版本块+总数+源哈希);③ canonical JSON 确定性(键排序);④ 未 publish 站点 → NOT_PUBLISHED;⑤ ADR-010 双修订测试:发布 A→导入改内容 current=B→快照 content.json 仍为 A(published 冻结语义,spec §15 阶段 4 验收明文)
- 实现:一致性读取(单事务 repeatable read 或快照时点标记——简化:顺序读取+记录行数基线,差异检查);媒体实体路径解析用 register 快照 metadata 的 storageRoot+storage_path(
storage/snapshots/media_bundle/…已存在的 content-addressed 副本)+ incoming 副本兜底 - 绿 → commit
feat(build): 冻结 Render Snapshot 物化与 build job
Task 3: pilot-minimal 主题骨架 + 布局组件 ✅ 完成(1cd309d + 修复 eb81a57,双审通过;372 测试;审查战果:payload 契约上提 contracts 双端咬合、空路由明错(静默 0 页消除)、eslint astro ignore、routeHrefFor 预备。遗留:og:type/og:image、footer 年份可复现、origin 空守卫——均记 Plan 5/Task 5)
Files: themes/pilot-minimal/(package.json/tsconfig/theme.manifest.ts/src/{layouts,components,lib});根 workspace 已含 themes/*;Test themes/pilot-minimal/test/manifest.test.ts
- 失败测试:theme.manifest 满足 contracts 的 isThemeManifest(key='pilot-minimal'/version/renderContractVersion='1'/supportedContentTypes 三类/requiredSiteConfig/六模板条目:home/page/product-list/product-detail/news-list/news-detail+404);渲染数据 loader 对六文件夹具的加载形状
- 实现:Astro 5 项目(dependencies 仅 astro);BaseLayout(html 骨架/SeoHead:title/desc/canonical/og 从 page data)/SiteHeader(site.name+简导航:首页/Products/News)/SiteFooter(©+name)/基础样式(design tokens 极简 CSS 变量——快线 §8.5 视觉优先级:宽度/字体/颜色/间距先对,不动像素级还原);brand 恒可能为 {} 且 name 可能是 siteKey 派生占位(Task 1 审查移交)——主题必须对空 brand/占位 name 缺省容忍,禁止直接读 brand.xxx 落空;组件:RichText(set:html——输入已清洗+重写,信任链 JSDoc 记录,不二次清洗)/ResponsiveMedia(alt/尺寸/懒加载)/Breadcrumbs/ProductCard/NewsCard/Pagination/NotFound
- 绿 → commit
feat(theme): pilot-minimal 骨架与布局组件
Task 4: 六类页面路由与渲染 ✅ 完成(f916f72 + rider 0a7578b,双审通过;380 测试;审查战果:sf-literal-html-paths 折平集成(Astro 5.18 三态均无法产字面文件的实证解法)、真实数据字段探测三纠偏(title@knownFields/media键=fieldPath 全路径/产品无结构化参数表)、stub 清理+format 显式契约、195 页规模实测 5.3s/113MB。Task 8 分派观察项已记)
Files: themes/pilot-minimal/src/pages/[...slug].astro + 模板分派;Test themes/pilot-minimal/test/render-cases.test.ts(Astro 构建级测试:六类夹具数据 → 构建产物断言)
- 失败测试(六夹具页 = 真实 hesupipe 抽样数据的脱敏化形状——home(/)、generic page(/products/ 内容页 + 一条编码中文路径页如
/%e5%85%b3%e4%ba%8e%e6%88%91%e4%bb%ac/)、product-category 列表、product 详情、news 列表 /news/、news 详情 .html(仅夹具防御——真实 0 条)):构建产物存在且含关键内容(title/slug 渲染进 HTML、产品参数/新闻正文、图片 src=根相对;编码路径产物按编码原样落盘);404.html 存在 - 实现:getStaticPaths 读 SF_BUILD_INPUT 的 routes.json(html 行)+ 按内容类型/路径特征分派模板:
/→home;/product-category/*或/category/*→列表(taxonomy.json 查 term+content.json 过滤关联内容);内容页按 typeKey(product→详情含参数表+相册、news_article→正文+日期、basic_page→富文本);/news/ 特判 news-list。分页:V1 一律不做(/news/ 129 条单页全列 + taxonomy 列表同——分页路由不在 routes.json 会自破门禁①死链;触发条件与路由登记义务一并记 ADR-018 移交 Plan 5) - 绿 → commit
feat(theme): 六类页面渲染与路由分派
Task 5: 构建管线(astro 构建 + 媒体落盘 + sitemap/robots)✅ 完成(1ed3ac0 + 修复 41829e2 + nit 06ba069,双审通过;385 测试;审查战果:Windows taskkill 树杀(孤儿 astro 实证复现后封堵)、错误码分阶段包裹+PIPELINE_INTERNAL、dist 双位置纪律、五禁含无网 stub(telemetry 实联网发现)、/products/ 主列表分派真修。审查移交 Task 6/8 三项已记)
Files: build.service.ts 扩展(spawn astro build→媒体复制→sitemap→产物登记);Test tests/integration/build-pipeline.test.ts
- 失败测试(小夹具全链):① spawn
pnpm --filter @sf/theme-pilot-minimal build(SF_BUILD_INPUT 指输入目录;Task 3 备忘:包全名 filter;Windows astro 构建失败判据="非零退出+stderr 标记"勿匹配精确码——127+libuv 噪音)→ dist/ 页面按 routes.json 的 html 行路径落盘——映射规则钉死:尾斜杠→<path>index.html;.html后缀→字面文件(仅夹具);百分号编码路径→编码原样目录结构(Astro 产物若不符则构建后按 manifest 重命名/移动对齐——manifest 的 target_file 是唯一权威);② 媒体复制:assets.json 逐条复制到 dist 的 public_path(跳过 quarantined);sha256 抽检;③ sitemap.xml(origin+indexable html 行,编码 URL 原样进 sitemap)+ robots.txt(Allow / + Sitemap 行);④ route-manifest.json 生成与字段面断言(spec §12.1:manifest_version/site_id/build_job_id/origin/generated_from_route_snapshot_hash/routes[] 含 exact_key/query_policy/kind/target_file/status/location/content_type/indexable——含 redirect 行与 ?attachment_id= 查询形态)+ build_artifacts 行(dist/manifest 各 kind+哈希);⑤ 构建环境五禁断言:SF_BUILD_INPUT 之外零数据源(loader 单一入口 grep + DATABASE_URL 置空跑构建仍成功 + 无网证明:构建进程内 stub global fetch 抛错仍成功——spec §10.4 明文要求) - 实现:build.service 编排(快照→astro→媒体→sitemap→登记);产物落
artifacts/jobs/<buildJobId>/{input,dist,manifests} - 绿 → commit
feat(build): astro 构建管线与媒体落盘
Task 6: 最小门禁 + 完整 build 状态机 ✅ 完成(8f2b982 + 快修 fa27a3a,双审通过;393 测试;五门禁 htmlparser2 零新依赖/P0 阻断/issues 落库/报告 HTML 先写两域对齐/spawn 缩减 97→76s。审查战果:非文件路由兜底语义、1101 条分批插入防回归、注入面选侧(dist 侧走真实解析面)。遗留入 Task 9/Plan 5)
Files: apps/api/src/modules/build/gates.ts;sf.ts + build:validate;Test tests/integration/build-gates.test.ts
- 失败测试(夹具产物注入错误逐项触发):① 内部死链:dist 内 a[href]/img[src] 内部目标全命中(页面或 assets)——注入坏链 → fail;② sitemap 一致:sitemap 条目=实际 indexable html 集;③ SEO 基线:每 html 有 title/meta description/canonical(指向 origin+自身路径)/恰一 H1;④ 大小写冲突:dist 路径 lowercase 无重复;⑤ 产物完整性:route-manifest 的 target_file 全存在。全过 → validation_runs 行 PASS
- 实现:门禁引擎(纯函数读 dist+manifests → issues 按 P0/P2 分级——P0 阻断 P2 报告;HTML 解析用 parser 复用清洗管线,禁纯正则 spec §13.4);build 状态机补 validating→succeeded/failed;报告落盘 HTML 先写(Plan 3 窄窗口顺手修)
- Task 5 审查移交:① 测试套件 spawn 缩减——build-snapshot 7 处成功路径改 materializeRenderSnapshot 直调或复用单次 job(spawn 降到 1-2,五禁覆盖留 build-pipeline);Task 8 开工前完成 ② CLI 与测试禁止同时构建(themeDistDir 结构性共享暂存)③ Task 8 时间预算:全链分钟级(astro 5.3s+媒体 IO+门禁+1746 回环+Playwright),300s spawn 超时余量充足
- 绿 → commit
feat(build): 最小门禁与状态机闭环
Task 7: Preview Server ✅ 完成(7194d1b + 修复 5f78dde,双审通过;406 测试;审查战果:穿越预检先于 URL 解析(实测 new URL 规范化洞)、realpath+junction 逃逸双检、流式文件服务(64MB RSS 平坦实测)、quiet 开关、匹配优先序微探针。sitemap/robots 预览 404 记 Plan 5)
Files: apps/preview-server/{server.mjs,package.json};sf.ts + preview:start;Test tests/integration/preview-server.test.ts
- 失败测试(真实 http 请求小夹具构建产物):① html 行 200+Content-Type;② redirect 行 301+Location(?attachment_id= 查询形态匹配);③ asset 行 200+正确 MIME(png/jpg/webp/pdf);④ 未命中 404+自定义页;⑤ 全响应
X-Robots-Tag: noindex;⑥ 绑定 127.0.0.1;⑦ 路径穿越拒绝(../ 与编码形式);⑧ gone 行 410(夹具手构——真实数据暂无)+ 目录列表禁用 + HTML 响应基础 CSP 头(self-only) - 实现:Node http 服务器读 dist+route-manifest.json(exact_key 匹配+query_key 提取);MIME 表复用 magic-bytes 思路(按扩展名+魔数双确认可选——扩展名够用)
- 绿 → commit
feat(preview): 路由驱动的本地预览服务器
Task 8: 真实 hesupipe 端到端(验收——浏览器时刻)✅ 完成(c64f6ac + 修复 236f3dc,规格审查通过含产物级复现+截图亲验+浏览器复验;23 断言全 PASS:六类页/图片 10/SEO 5/404+301/sitemap+robots/双视口 6 截图/190 全 200+1746 全 301/耗时/门禁。验收内真修三件:Location 头 ASCII 化、CSP style-src、资产键编码归一(7 张全角名图预览 404——三层回归锁定)。截图在 docs/evidence/screenshots/)
Files: docs/evidence/phase4-preview.md;可能 scripts/e2e-real.mjs(编排复验)
- 临时库重建全链:register(副本复用)→ import → publish(192 条) → build → validate → preview:start
- 验收断言(快线 §8.7 第 10 天标准逐条):① 六类页面 HTTP 200 且内容来自真实数据(首页/一个内容页(含一条编码路径页)/产品分类列表/产品详情含参数/新闻列表/新闻详情);② 图片本地化抽检 10 张(dist 内存在+页面引用根相对);③ 每页 title/description/canonical 抽检 5 页;④ 404 与 301 行为(?attachment_id= 抽检);⑤ sitemap.xml 条目数=indexable html 数;⑥ 移动端 375px + 桌面 1440px 截图无明显破版(Playwright MCP 各 3 页存证据——快线 §8.7 原文双端);⑦ 全量 URL 冒烟:route-manifest html 行全 200(190 页)+ redirect 全量 1746 逐条 301+Location 校验(回环零成本)
- Task 4 审查移交的 Task 8 观察项:分派器对以下形态是有意明错(build 期 throw)——
/news/硬编码特判外的其他主列表路径、分类嵌套(/product-category/parent/child/)、分类索引路径(/product-category/ 裸前缀);真实 190 路由集若含这些形态构建会失败——届时按错误信息补分派规则即可,非缺陷 - 耗时记录(快照物化/astro 构建/媒体复制/门禁分解——对照 Plan 3 基线 51.8s);差异如实分析。证据文档 + commit
test(evidence): 阶段4端到端预览验收;临时库 DROP;storage 副本保留
Task 9: 收尾 ✅ 完成(5c2ee7d helper 收敛 + docs 收官 commit;ADR-018 八决策/README 全链命令链与阶段 4 状态/Plan 5 移交清单 7 项;三绿全量 408(根 382 + 主题 26)+ lint + typecheck 五段;commit 两个不 push——推送由用户执行)
- ADR-018(
docs/adr/ADR-018-主题与构建决策.md八决策:①Astro 5.18 选型 MIT/依赖只进主题包/vitest 双版本(根 2.1.9 + 主题 3.2.7——astro vite6 兼容,不升根)②渲染信任链——bodyHtml 不二次清洗(三段接力:Plan 2 清洗 + Task 2 重写 + load-data 唯一入口)③分页 V1 一律不做(未登记路由自破门禁①死链——路由登记义务随迁移移交)④P0 发布门禁偏离(issue 无 DB 查询源,206 条真实现状会反堵发布;Plan 5 SEO diff 时重审)⑤编码路径贯穿规则(四层字节精确 + 资产键 URL 线形归一 236f3dc)⑥预览零依赖 + host 白名单收紧(比 spec §12.2 更严——回环纪律)⑦CSP style-src 'unsafe-inline'(Astro scoped 样式内联实证)⑧报告 HTML 先写两域对齐);README(快速开始 publish→build→preview 全链命令链含产物路径/目录结构补 themes 与 preview-server/当前状态阶段 4 完成 + 下一步指向移交清单/ADR-001~018);typecheck 五段链(含 themes/pilot-minimal);lint;三绿全量 408 - Task 6 审查遗留:① cleanupSiteRows/makeWork/seedPublishedSite 三份测试 helper 迁 tests/integration/helpers.ts(5c2ee7d——Task 6 审查时三份,Task 7 preview-server 又落第四份一并收敛;seedPublishedSite 增 keyPrefix 参数保各文件站点 key 派生前缀不变;零行为变更由 408 测试守护)② build.html 尾换行与 import 域对齐——Task 6 快修
fa27a3a已覆盖(writeAtomically(reportHtmlPath,${render}\n),与 import 域${html}\n同款,核实无需再动)③ Plan 5 前置四项(绝对 origin 归一/redirect 环/OG/hreflang 完整门禁、build:validate CLI、sitemap 重复 loc multiset)全部入文末移交清单第 2 项 - 移交 Plan 5 清单(见文末新节定稿 7 项:SEO 三方 diff(source_rendered 抓取——旧站 HTML 抓取器是新输入源 + P0-P3 分级门禁)/完整门禁补齐六件(含可复现双构建与 footer 年份固定化)/sitemap-robots 预览可达/og:type=article·og:image·/category/ NewsCard/836 条 301 nginx conf 输出/续传 Task 0.2 cms 备份(阶段 6 L3 前必补)/度量基线(~64s 首跑·~15s 复跑·import 51s 占大头))
- commit 两个(refactor(test) + docs);push 由用户执行
组件清单省略注记:快线 §8.2 的 ProductGrid/NewsList 由模板内联网格实现、TaxonomyList V1 无消费场景(首页不做分类导航)——避免与 §8.2 对照误判遗漏。
验收(对照快线 §8.7 + V2 §15 阶段 4 子集)✅ 全过(Task 8 验收 + 23 断言/6 截图,证据 docs/evidence/phase4-preview.md)
- 显式发布可用且导入产物发布前不可见(published 指针语义——publish.test.ts + build-snapshot ADR-010 双修订测试锁定)
- Render Snapshot 冻结六文件 + 构建五禁(无库无网无 seed——build-pipeline 五禁断言:loader 单一入口/DATABASE_URL 置空/fetch+net stub/无 seed/不读源快照)
- 六类页面真实数据渲染 + 图片本地化(断言 ①a-①f + ② 10/10;媒体本地化 836 文件 sha256 全量校验)
- Preview 200/301/404/noindex/回环(断言 ④a/④b/④c + ⑦a 190 全 200 + ⑦b 1746 全 301;绑定 127.0.0.1 断言)
- 最小门禁五项全绿(断言 ⑨:min-gates-v1 PASS p0=0 p2=0——validation_runs 落库)
- 真实 hesupipe 端到端验收 + 移动端截图证据(23/23 PASS + 双视口 6 截图
docs/evidence/screenshots/phase4-{mobile-375,desktop-1440}-*.png)
移交 Plan 5 清单(Plan 4 收官定稿——阶段 5:SEO 三方差异 + 完整门禁 = M1 完整迁移预览,计划的必读输入)
- SEO 三方差异(source_rendered 抓取需求——新输入源):旧站 HTML 抓取器是新输入通道(按 route origin 爬旧站页面取 source_rendered,与 dist 渲染输出、导入期 source Record 三方对拍——title/description/canonical/H1/og 逐项 diff);这是 Plan 2 以来第一个新输入源,register 侧需扩 source_type 与快照形态。比较基准纪律:source_rendered 对拍要求 dist 逐字节可信——ADR-018 决策二的信任链(不二次清洗)是该 diff 的前提,任何主题层改写都会破坏对拍。P0-P3 分级门禁同此落地(issue DB 化 + 分级——ADR-018 决策四 publish 门禁重审的前置)。
- 完整门禁补齐(gates.ts 扩展,五门禁 → 完整集):① redirect 环/多跳(manifest 范围内沿 Location 链跟随,环 = P0);② OG/hreflang 面;③ 绝对 origin 死链归一(internal-links 吸收
https://<origin>/...形态——当前按外域豁免);④ sitemap 重复 loc multiset 语义(重复 loc = issue——当前集合语义测不出);⑤ build:validate CLI(对既有产物独立重跑门禁——runGates 运行面已有,缺 sf.ts 入口);⑥ 可复现双构建(冻结输入两次全构建 dist 哈希一致——footer 年份固定化也在此时:© 年份现取构建时刻年份,破坏可复现,双构建门禁前必须先固定化)。 - sitemap/robots 预览可达:dist 已含两文件但 manifest 无对应行 → 预览 404(Task 7 真实发现,记 Plan 5);方案二选一——manifest 补 asset 行或预览发布道(不进 manifest 的直通道),与第 2 项 sitemap 门禁一起定(避免两处 sitemap 语义漂移)。
- SEO 元数据补齐:og:type=article / og:image(SeoHead 扩展——Task 3 审查移交);/category/ 的 NewsCard 扩展(分类归档页新闻卡渲染——Task 4 时真实路由集未消费该形态,分派模式已预留)。origin 空守卫已在 load-render-data 落地(类型防御 + 空串缺省)——此项闭环。
- deploy 前置:836 条 301 的 nginx conf 输出(V2 §14 route compile——route_registry redirect 行含 location,数据已齐);输出时 Location 复用 pipeline 的 URL 线形归一(ADR-018 决策五同款——生产面 nginx 先解码,与预览键归一同理防裸非 ASCII)。
- 续传:Task 0.2 cms 数据备份(用户暂缓——阶段 6 L3 验收前必须补做,责任链自 Plan 2 清单第 6 项 → Plan 3 清单第 7 项延续)。
- 度量基线(Plan 5 优化对照):全链首跑 ~64s(import 51.1s + build 11.9s)/ spec 复审复跑 ~70s / 幂等复跑 ~15s(register 1.8s + import 复用 94ms + publish 164ms + build 复用读原报告);import 占大头——优化方向 = 批量锚点解析(逐事件串行分派的交互式往返口径,phase3-import.md §5 记录在案);astro 纯构建 4.9-7.5s + 媒体复制 836 文件 ~164.5MiB 占 building 相大头(量级自洽对账见 phase4-preview.md §4)。