Files
cms-website/docs/evidence/phase3-import.md
T
浅念 1c4c163743 test(evidence): 阶段3真实全量导入验收
Plan 3 Task 7:真实 hesupipe 输入(WXR 5.5MB + 媒体 2625 文件/164.53MiB + 侧车 859 条)
全量导入验收证据——断言总表 21 项全过(content_entries=192 / source_records=1122 /
terms=451+476 自建 / media_assets=836 全侧车二级命中+19 missing 全列 / routes=2772
逐桶闭合 / published 指针全程未动);换 key 二导全幂等(五表行数不变);临时库验后
DROP;storage/incoming 副本保留(Plan 4 构建用)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 20:54:28 +08:00

16 KiB
Raw Blame History

阶段 3 真实 hesupipe 全量导入验收证据(Plan 3 · Task 7)

verdict:PASS(断言总表 21 项全过;两处"计划预估值 vs 实测"差异均有完整对账解释,见 §7;无计数硬凑)

项 值
日期 2026-10-21
环境 Windows 11 · Node v24.13.0 · pnpm 10.7.1 · tsx 直吃 TS 源码(sf CLI 同机制)
数据库 本项目 PG(docker sf-postgres,postgres:16,宿主 localhost:5434)——与执行进程同机(计划钉死"DB 须同机/近端");临时库 sf_phase3_hesupipe(验后已 DROP,见 §8)
输入 只读 cms 的三个真实输入,副本入本项目 storage/incoming/(WXR 副本 + robocopy 媒体副本 + Task 0 侧车原件)
首跑 job 45c71c5a-fc56-487a-91c2-38652da2d83b · status succeeded_with_issues · 51,768.0ms
二导 job 8cbc9eee-2811-41b3-a8ba-378c5081e445(--key phase3-rerun-2)· succeeded_with_issues · 20,271.1ms
版本块 adapter=1.0.0 / mapping=1.0.0 / sanitizer=1.0.0 / parser=saxes 6.0.0
站点锚点 site 77a20c60-5267-427a-b2ea-09abbcbfc8df(新建,origin=https://hesupipe.com,locale=en)· connection f6b3bd1a-aaad-424c-bf5e-62aeb12d459f · wxr 快照 0aa7d5db-c8e2-4d76-8a45-921a61b32862 · media_bundle d6d1282b-1340-469a-ad5e-7093e198a1a3 · manual_url_list 1f9d1495-55ec-4233-9e74-3d25d1c32634
报告产物 artifacts/import/45c71c5a…/reports/import.json(首跑)、artifacts/import/8cbc9eee…/reports/import.json(二导)、artifacts/register/20261021T204522-8vhybz/reports/register.json(登记;artifacts/ 不入库,仅存本地)

1. 副本与登记(耗时实测)

步骤 命令/方式 结果 耗时
WXR 副本 cp D:/project/cms/storage/wxr/fd04d6ba8a1316b6.xml storage/incoming/hesupipe-wxr.xml 5,490,399 B;SHA-256 f9bc5d44…b9f62 与 Plan 1 基线一致 0.12s
媒体目录副本 robocopy … /MIR → storage/incoming/hesupipe-media/ 2625 文件 / 172,532,000 B(164.53 MiB)全部复制,0 失败(exit=1=有文件复制);find 复核 2625 文件;抽查首文件 SHA-256 与源一致 10.59s(robocopy 自报 16,757,187 B/s)
临时库 docker exec sf-postgres psql -d postgres -c CREATE DATABASE sf_phase3_hesupipe + DATABASE_URL=… pnpm --filter @sf/api db:migrate 21 表全量迁移成功 8.06s(迁移)
登记 pnpm sf source:register --site hesupipe-real --wxr … --media … --media-map … --origin https://hesupipe.com --locale en(env DATABASE_URL_TEST 指临时库) 三快照新建:wxr 17.1ms / media_bundle 19,013.2ms(2625 文件逐个 SHA-256 + content-addressed 副本)/ manual_url_list 9.8ms(副本已存在——Task 0 冒烟已落同哈希副本,内容寻址复用);manifest fileCount=2625、totalBytes=172,532,000 19,323.9ms(服务内)

实际执行的命令原文(仓库根 D:\project\cms-one,Git Bash):

cp "D:/project/cms/storage/wxr/fd04d6ba8a1316b6.xml" storage/incoming/hesupipe-wxr.xml
robocopy "D:\project\cms\storage\media\54f91c49-fffb-4c58-87c8-6c2cf6aaef79" \
         "D:\project\cms-one\storage\incoming\hesupipe-media" /MIR /R:2 /W:1 /NFL /NDL /NP
docker exec sf-postgres psql -U siteforge -d postgres -c "CREATE DATABASE sf_phase3_hesupipe;"
DATABASE_URL="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase3_hesupipe" pnpm --filter @sf/api db:migrate
DATABASE_URL_TEST="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase3_hesupipe" \
  pnpm sf source:register --site hesupipe-real \
    --wxr storage/incoming/hesupipe-wxr.xml \
    --media storage/incoming/hesupipe-media \
    --media-map storage/incoming/media-map-hesupipe.json \
    --origin https://hesupipe.com --locale en
DATABASE_URL_TEST="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase3_hesupipe" \
  pnpm sf import --site hesupipe-real                       # 首跑(默认幂等键)
DATABASE_URL_TEST="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase3_hesupipe" \
  pnpm sf import --site hesupipe-real --key phase3-rerun-2   # 二导(换 key 重跑)

断言查询全部经 docker exec sf-postgres psql -U siteforge -d sf_phase3_hesupipe -tAc "<SQL>"(下称 SQL 对拍);报告数值取自两份 import.json(报告对拍)。

2. 首跑断言总表(预期 vs 实测 vs 判定)

预期来源:计划 Task 7 节 + Task 0/阶段 2 基线证据(real-wxr-baseline.md、media-map-export.md、phase2-smoke.md)。

# 断言项 预期 实测(报告 / SQL 双写) 判定
1 content_entries 总数 192 报告 contentEntries=192;SQL count(*)=192 PASS
2 typeKey 分布 129 news_article + 48 product + 15 basic_page SQL:news_article=129 / product=48 / basic_page=15 PASS
3 source_records 总数 1122 报告 sourceRecords=1122;SQL=1122 PASS
4 parse_status 分布 imported=1047 / unsupported=75 报告 unsupportedRecords=75;SQL:imported=1047 / unsupported=75 PASS
5 terms 总数 451 + 自建数 SQL=927 = 451 + 476 PASS
6 自建数(autoCreatedTerms) 476(category 5 nicename + post_tag 471 nicename,去重后建) 报告 autoCreatedTerms=476;SQL source_id LIKE 'auto:%'=476(category 5 / post_tag 471,逐 taxonomy 吻合) PASS
7 termsProcessed(wp:term 表侧) 451 报告 451;SQL 非 auto 行=451(product_tag 439 + product_cat 10 + nav_menu 1 + elementor_library_type 1,与阶段 2 基线分层一致) PASS
8 content_terms 报告口径 报告 contentTermsCreated=1444;SQL=1444 PASS
9 media_assets 总数 836 命中(Task 0 精确命中预估) 报告 mediaRegistered=836;SQL=836 PASS
10 mediaMissing 19(Task 0 未命中清单全列) 报告 19;issueGroups.media 逐条 19 条,URL 与 Task 0 清单逐字一致(§5.2) PASS
11 mediaQuarantined 实测 0(本数据集命中的 836 资产无 SVG);SQL status 全 ready PASS(实测口径)
12 mediaAmbiguous 0 0 PASS
13 mediaDanglingRefs 实测(gallery 悬空等) 22(明细 §5.3:外站热链 19 + 缺失资产 featured 引用 1 + WP 尺寸变体 2) PASS(实测口径)
14 routes 总数 实测(计划预估 ~2815,见 §7 差异分析) 报告 routesCreated=2772;SQL=2772 PASS(对账闭合,§4)
15 route kind 分布 html/redirect/asset 计数 SQL:html=190 / redirect=1746 / asset=836 PASS(§4 全量对账)
16 excludedQueries 实测 19(18 wp:post + 1 wp:product 的 ?p= 形态链接——不插行+计数+issue) PASS
17 published_revision_id 非空数 0(导入不发布) SQL=0(current 指针 192 非空、published 0) PASS
18 job 终态 succeeded_with_issues(attachment 降级汇总必在) succeeded_with_issues;issueGroups.route 末条即"attachment 页降级(前置事实 10):836 条 …"汇总 PASS
19 清洗统计(与阶段 2 probe 一致) changedRows=138 / removed-tag=3 / removed-attr=4667 报告 sanitize:changedRows=138 / removed-tag=3 / removed-attr=4667 PASS
20 revisionsCreated(首跑) 192(干净库首跑每 entry 一修订) 报告 192;SQL content_revisions=192(source_kind=import 全量) PASS
21 media_usages 实测 SQL=478(attachmentRefs 92 + data.bodyHtml.img 386) PASS(实测口径)

附:issues 总量 206 = content 0 + term 2 + media 41 + route 40 + adapter 123 + orchestrator 0(明细 §5);skipped=0(干净库首跑无可跳过修订)。

3. 二导幂等断言(换 key phase3-rerun-2)

断言项 预期 实测 判定
revisionsCreated 0 0 PASS
contentEntries 0 0(skipped=192——checksum 全等跳过,幂等路径正确显影) PASS
mediaRegistered 0 0(mediaFromSidecar 亦 0) PASS
routesCreated / routesUpdated 0 / 0 0 / 0 PASS
autoCreatedTerms / contentTermsCreated 0 0 / 0 PASS
sourceRecords 0 0(unsupportedRecords=75 重现——unsupported 行无身份键去重语义,重跑如实再计,非缺陷) PASS
routesCollisions 重跑如实再现(非缺陷) 首跑即 0,二导仍 0(本数据集无 collision_key 折叠碰撞) PASS
missing/dangling/excluded 重现 如实再现非缺陷 mediaMissing=19 / mediaDanglingRefs=22 / routesExcludedQueries=19 均原样再现;issues 总量仍 206 PASS
五表行数不变 不变 content_entries=192 / content_revisions=192 / terms=927 / media_assets=836 / route_registry=2772 全部不变(另 source_records=1122 / content_terms=1444 / media_usages=478 / entry_locales=192 / taxonomies=6 亦不变) PASS
published 指针 仍 0 SQL=0 PASS

4. 路由处置全量对账(2772 行逐桶闭合)

桶 行数 构成(SQL 分组实测)
html 190 内容 permalink 173(news_article 111 + product 47 + basic_page 15)+ 首页 / 1 + /news/ 列表 1 + category 归档 5 + product_cat 归档 10;/products/ 行存在但 target 是内容页(见冲突说明)
redirect 1746 attachment 页 301→媒体文件 836(路径形态 812 + ?attachment_id= 查询形态 24——query_policy=exact 的 24 行全在此桶)+ /tag/ 471→/news/ + /product-tag/ 439→/products/
asset 836 /wp-content/uploads/… → target_file(836 注册资产一一对应)
合计 2772 190+1746+836 ✓;query_policy:exact=24 / ignore=2748;host 单值 hesupipe.com(=production_origin);target_status:200×1026 / 301×1746

闭环核算:192 entry − 173 有路由 = 19 无 html 路由的 entry = 18 篇 ?p= 链接的 post + 1 个 ?post_type=product&p= 链接的 product(source_url 逐条 SQL 核对)= routesExcludedQueries 19——数目与身份双侧闭合。

冲突说明(1 条 issue,非计数桶):路由 exact_key 冲突(hesupipe.com/products/)——basic_page "products" 页(先到,保留)vs CLI 主列表参数 /products/(后到,跳过)。routesCollisions 计数桶只计 collision_key 折叠碰撞(ADR-007 双键语义,本数据集 0 条);exact_key 异 owner 冲突走 issue 通道不计该数——两通道语义在 route-writer 源码分离(writeRoute 的碰撞预警分支 vs owner 冲突分支),实测与设计一致。

5. 问题清单全量分层(206 条)

5.1 adapter 123 = 不支持对象 75 + 行级问题 48

  • unsupported 75:wp:nav_menu_item 68 + wp:wpcf7_contact_form 3 + wp:elementor_library 2 + wp:wp_navigation 2——与阶段 2 基线分布逐项一致。
  • 行级问题 48:全部为 wp:product 的 meta wppop_sections PHP 解析失败(WP 页面构建器序列化元数据,PhpParseError 摘录入 issue、原串保留于 extras.phpRaw)——降级不丢数据。

5.2 media 41 = missing 19 + 悬空 22

missing 19 条 URL 与 Task 0 media-map-export.md 未命中清单逐字一致(8×pdf/txt/zip + 5×mp4 + 5×、特殊字符 jpg + 1×全角括号 jpg 形态),全部"清单与侧车均未命中——不插 media_assets 行(sha256 无来源)"。

5.3 悬空引用 22 条明细特征(实测口径,如实记录)

特征 条数 说明
外站热链(hesucar.com 2 + www.cn-hdpe.com 17) 19 正文 img 指向姊妹/外站图床,WXR 无 attachment 行亦不在清单——真悬空,报告保留
featured 引用缺失资产 attachment 3803 1 即 missing 19 之一的 _thumbnail_id 引用(HDPE-carat-pipe-(2).jpg)
本站 WP 尺寸变体(-1024x677.png 等) 2 WP 生成的中间尺寸图无独立 attachment 行

quarantined/unsupported 资产的引用不落任何 count 桶(Task 4 口径)——本数据集 quarantined=0,无该形态;侧车 23 条悬空行(859−836)如 Task 0 预告不出现在 mediaDanglingRefs(只能经 issues 可见),实测一致。

5.4 term 2 = 未知 taxonomy 兜底

nav_menu、elementor_library_type 不在前置事实 9 常量表——object_type 兜底 'post'、/<key>/ 兜底路径,仅记录不消费(既定行为)。

5.5 route 40 = 查询排除 19 + exact 冲突 1 + attachment 301 目标缺席 19 + 降级汇总 1

attachment 301 目标缺席 19 = 19 个 missing attachment 无 media_assets 行故无路由(与 mediaMissing 同源闭合)。

6. 三级对拍命中分层(媒体核心验收)

级 结果 说明
级① 清单精确路径(uploads yyyy/mm 相对路径) 0 命中 媒体目录为扁平 GUID 命名(Task 0 实证),WXR attachment_url 的 yyyy/mm 路径与清单 path 零交集——符合预告
级② 侧车(manual_url_list 859 条) 836 命中(mediaFromSidecar=836) 侧车 file_path(GUID 扁平名)作为精确 path 命中清单实体;歧义 0;侧车二级的"文件名候选"子路径 0 次启用
级③ missing 19 全列报告(§5.2),不插行

mediaRegistered 首跑口径(Task 4 移交):836 全部来自干净库首跑插入;二导 mediaRegistered=0。836+19=855 attachment 全量闭合;魔数全检(836 全 ready、0 隔离、0 unknown、0 声明不符 issue)。抽查:DB 行 sha256 与 content-addressed 存储副本文件哈希一致(map-location.jpg 例:0fd30b3c…da87)。

7. 差异分析(如实记录,无硬凑)

  1. routes 总数 2772 vs 计划预估 ~2815:预估为规划期粗算(含首页/列表/归档/attachment 各桶上限假设)。实测 2772 的差额可完整解释:预估未扣除 19 条 ?p= 排除(不插行)与 /products/ 列表页 exact 冲突 1 条(不插行),且归档桶按 term 总数 925 上估而实际 tag 归档 910(category 5/product_cat 10 走 html 归档非 redirect)。2772 = 全部输入身份的逐桶闭合(§4),无数值缺陷。
  2. 首跑耗时 51.8s vs 估 7-10s(~9300 RT):估算口径是批量 DB 写往返;实际编排管道为逐事件串行分派 + 逐引用交互式查询(1122 事件 × 四写入器 + 每 taxonomyRefs 引用的 entry 锚点解析 + 每路由 payload 的 exact 预查),交互式往返数为事件数与引用数的乘积级,非 9300 常数。DB 确同机(localhost:5434);无超时/失败;二导全幂等路径仍 20.3s 同因。属口径差异非性能缺陷;若后续需要提速,优化方向是批量锚点解析(已具备结构条件),不在本任务范围。
  3. mediaFromSidecar=836(级① 0 命中):任务提示"836 精确命中预期 + 0 侧车二级?"——实测全命中经侧车(级②),级① 0。与 Task 0 实证(扁平 GUID 目录 × yyyy/mm URL 零精确交集)完全一致,非缺陷。
  4. unsupportedRecords 二导再现 75:unsupported 行不走身份键幂等(无 entry 语义),重跑如实重计——计划"missing/quarantined/dangling 重跑如实再现"同族语义,非缺陷。
  5. term issues(未知 taxonomy 兜底)与行级问题(wppop PHP 解析)二导原样再现——issue 通道本就无去重契约,非缺陷。

8. 清理与保留声明

  • 临时库已 DROP:docker exec sf-postgres psql -U siteforge -d postgres -c "DROP DATABASE sf_phase3_hesupipe WITH (FORCE);"(验后执行;本项目开发库 siteforge 全程零写入——所有 sf 命令均经 DATABASE_URL_TEST 指向临时库)。
  • storage/incoming/ 副本保留(WXR 副本 + 媒体副本 172,532,000 B + 侧车,共约 345MB 本地盘)——Plan 4 构建要用;storage/ 整体 gitignore 不入库。登记期的 content-addressed 快照(storage/snapshots/,另约 172MB)随 storage/ 一并保留。
  • cms 源侧(D:\project\cms)全程只读:cp/robocopy/sha256sum/find 读源目录,未写未挂载未改配置;旧 cms PostgreSQL(5433 容器)本任务零连接。

9. 内存与耗时汇总

指标 值
首跑 job durationMs 51,768.0ms(CLI 墙钟 53.6s)
首跑 adapterTiming parseMs=121.4 / sanitizeMs=235.0(parseMs 含流式 IO 等待口径,见阶段 2 证据)
二导 job durationMs 20,271.1ms(墙钟 22.0s)
登记 durationMs 19,323.9(media_bundle 快照 19,013.2 为主——2625 文件哈希+副本)
robocopy 媒体副本 10.59s(2625 文件 / 164.53 MiB)
内存 峰值 319,258,624 B ≈ 304.3 MiB(进程集 WorkingSet 之和,500ms 采样,node sf.ts 进程过滤——含 tsx 加载器与子进程,上界口径,仅记录不断言)