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

169 lines
16 KiB
Markdown
Raw 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.
# 阶段 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):
```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 加载器与子进程,上界口径,仅记录不断言) |