docs(plan): Plan 2 修复评审三 Issue 与建议——CLI机制/跨包接线前置/media清单哈希定义/probe路径策略/内存采样法/报告路径/责任链续传
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -41,11 +41,12 @@ cms-one/
|
||||
|
||||
---
|
||||
|
||||
### Task 1: adapter-wordpress 包骨架 + 限额类型
|
||||
### Task 1: adapter-wordpress 包骨架 + 限额类型 + 跨包接线决策
|
||||
|
||||
**Files:** Create `packages/adapter-wordpress/package.json`(@sf/adapter-wp,无 runtime 依赖本任务)、`tsconfig.json`/`tsconfig.build.json`(照 contracts 先例)、`src/limits.ts`;Test `test/limits.test.ts`
|
||||
|
||||
- [ ] Step 1 失败测试:默认限额值冻结(maxWxrBytes 64MB、maxItems 200k、maxDepth 64、maxCdataLength 1MB、maxTermCount 50k、maxMetaPerItem 200、timeBudgetMs 120s、phpMaxDepth 8、phpMaxElements 1000)+ 类型字段齐全
|
||||
- [ ] **Step 0: 跨包接线决策落地(评审 Issue 2 前置)**:`@sf/adapter-wp` 加 workspace 依赖 `@sf/contracts`;**消费方式钉死为 vitest alias 指 src**——根 `vitest.config.ts` 加 `resolve.alias: { '@sf/contracts': path.resolve('packages/contracts/src/index.ts') }`;typecheck 由既有包级 tsc 覆盖(adapter-wp tsconfig 直接相对路径引用 contracts 源码的编译由包级 tsc 保证;CLI 侧走 tsx 同样吃 src)。结论记入 Task 9 的 ADR-016(不阻塞 Task 6)。
|
||||
- [ ] Step 1 失败测试:默认限额值冻结(maxWxrBytes 64MB、maxItems 200k、maxDepth 64、maxCdataLength 1MB、**maxFieldLength 1MB**、maxTermCount 50k、maxMetaPerItem 200、timeBudgetMs 120s、phpMaxDepth 8、phpMaxElements 1000)+ 类型字段齐全。注:timeBudgetMs 在 Task 3 以可注入时钟测试(构造函数接受 `now()`),Task 8 实测覆盖
|
||||
- [ ] Step 2 实现 `WxrLimits` 接口 + `DEFAULT_WXR_LIMITS` 常量(as const)+ 类型锁(照 contracts 单源派生先例锁值域)
|
||||
- [ ] Step 3 绿 → commit `feat(adapter-wp): 包骨架与限额契约`
|
||||
|
||||
@@ -88,6 +89,7 @@ cms-one/
|
||||
- nav_menu_item / wpcf7_contact_form / elementor_library / wp_navigation → **UnsupportedResult**(带类型与原因),不产 UniversalRow
|
||||
- **canonical status 值域**:publish/draft/future/pending/private/trash/inherit → 原样;未知值 → 'unknown' + issue(测试钉死映射表)
|
||||
- **property 断言**:每条产出 row 的 sourceId/sourceType 非空字符串、taxonomyRefs/attachmentRefs 元素形状合法、rawLocation 存在
|
||||
- 注:publishedAt/modifiedAt/title 本阶段留在 knownFields(UniversalRow 顶层提升是阶段 3 导入映射的事,有意不提前)
|
||||
- [ ] Step 2 实现 `mapItem(item, config): MappedResult`(config: 类型路由表 {post:'wp:post', page:'wp:page', product:'wp:product', attachment:'wp:attachment'} + site locale)——纯函数
|
||||
- [ ] Step 3 绿 → commit `feat(adapter-wp): WXR→UniversalRow 映射器(status 值域/悬空保留/unsupported 分流)`
|
||||
|
||||
@@ -107,7 +109,7 @@ cms-one/
|
||||
|
||||
**Files:** Create `src/adapter.ts`(parse→map→sanitize 串联为 `AsyncIterable<RowResult>`,RowResult = {kind:'row', row}|{kind:'unsupported',…}|{kind:'error',…});`src/report.ts`(计数汇总);Test `test/adapter.test.ts`
|
||||
|
||||
- [ ] Step 1 失败测试:mini.xml 全链 → 3 row(post/product/page)+ 2 attachment row + 0 unsupported + 差异计数(wppop 解析 1 成功);报告结构与 spec §7.7 字段对齐(按类型 Created/Skipped/Failed/Unsupported、清洗统计、行级错误带 rawLocation)
|
||||
- [ ] Step 1 失败测试:mini.xml 全链 → 3 row(post/product/page)+ 2 attachment row + 0 unsupported + 差异计数(wppop 解析 1 成功);报告结构与 spec §7.7 字段对齐(**首条版本块:Adapter/Mapping/Sanitizer 版本常量**、按类型 Created/Skipped/Failed/Unsupported、清洗统计、行级错误带 rawLocation)
|
||||
- [ ] Step 2 实现(含 validateUniversalRow **加法式**检查:复用 isUniversalRow + 返回 issue[] 的浅扩展,不动 contracts 包)
|
||||
- [ ] Step 3 绿 → commit `feat(adapter-wp): 全链组装与导入前报告`
|
||||
|
||||
@@ -116,25 +118,27 @@ cms-one/
|
||||
**Files:** Create `apps/api/src/modules/source/{register.service.ts,source.module.ts}`;`scripts/sf.ts`(commander,命令 `source:register --site <key> --wxr <path> [--sitemap <path>] [--media <dir>]`、`wxr:probe --wxr <path>` 只解析出报告不入库);Test `tests/integration/source-register.test.ts`
|
||||
|
||||
- [ ] Step 1 失败测试(临时库):
|
||||
- register WXR+sitemap+media 目录 → 三条 source_snapshots 落库(kind 各异、sha256 与文件一致、storage_path 指向 content-addressed 副本)、source_connections upsert 幂等
|
||||
- **重复 register 同文件** → 复用既有 snapshot(UNIQUE(site,kind,sha256) 生效,不重复行)
|
||||
- register WXR+sitemap+media 目录 → 三条 source_snapshots 落库(kind 各异、sha256 与文件一致、storage_path 指向 content-addressed 副本)、source_connections upsert 幂等(**判重键 (site_id, name),应用层 select-then-insert**——表无此库级唯一,注释注明)
|
||||
- **media_bundle 的 sha256 定义(钉死)**:规范化清单哈希 = 目录内全部文件的排序相对路径(分隔符统一 `/`)+ 每文件 sha256 → canonical JSON(键排序、无空白)→ 对该 JSON 串再 sha256。**不含 mtime、不依赖遍历顺序**。清单本体(路径+大小+每文件哈希)存入 snapshot 的 metadata jsonb(兼满足 spec §7.1 媒体清单)
|
||||
- **重复 register 同文件/同目录** → 复用既有 snapshot(UNIQUE(site,kind,sha256) 生效,不重复行)——媒体目录重跑哈希必须一致(分隔符归一化保证)
|
||||
- 副本落 `storage/snapshots/<kind>/<sha256前2位>/<sha256>`;原输入不再被后续任务读取
|
||||
- 路径安全:输入路径必须在允许目录(storage/incoming 或绝对路径白名单校验——拒绝越界读取)
|
||||
- [ ] Step 2 实现服务(copy 流式+sha256 双算、DB 写入、报告输出 artifacts/jobs/<jobId>/reports/)+ CLI 接线
|
||||
- 路径安全:register 的输入路径必须在允许目录(storage/incoming 或显式绝对路径白名单校验——拒绝越界读取);**wxr:probe 例外:只读诊断命令,接受任意可读路径(Task 8 直读 cms 即用此通道),不写任何东西**
|
||||
- [ ] Step 2 实现服务(copy 流式+sha256 双算、媒体清单哈希、DB 写入、报告输出 **artifacts/register/<runId>/reports/**——阶段 2 无 import_jobs,runId 为本次 register 运行生成)+ CLI 接线
|
||||
- [ ] **CLI 执行机制(钉死)**:根 devDependency 加 `tsx`;根 package.json scripts 加 `"sf": "tsx scripts/sf.ts"`;`commander` 作为 adapter-wp 或根依赖;CLI 经 tsx 直吃 TS 源码(**无需预构建 dist**;apps/api 的 Nest 装饰器源码同样由 tsx 运行)。Task 8 命令相应为 `pnpm sf wxr:probe --wxr ...`
|
||||
- [ ] Step 3 绿(75+新增)→ commit `feat(source): 输入登记服务与 sf CLI(content-addressed 快照)`
|
||||
|
||||
### Task 8: 真实 WXR 冒烟(阶段 2 验收)
|
||||
|
||||
**Files:** Create `scripts/smoke-real-wxr.mjs`(wxr:probe 真实文件 → 计数 JSON);`docs/evidence/phase2-smoke.md`
|
||||
|
||||
- [ ] Step 1: `node scripts/sf.ts wxr:probe --wxr "D:/project/cms/storage/wxr/fd04d6ba8a1316b6.xml"`(**只读 cms**)→ 计数对照 real-wxr-baseline.md:855/129/48/15 attachment/post/product/page + 75 unsupported(nav_menu_item 68 等)+ 451 term,逐项一致才算过
|
||||
- [ ] Step 2: 峰值内存记录(process.memoryUsage heapUsed)——流式断言:解析 5.5MB 文件峰值 < 100MB(不整读)
|
||||
- [ ] Step 1: `pnpm sf wxr:probe --wxr "D:/project/cms/storage/wxr/fd04d6ba8a1316b6.xml"`(**只读 cms**;probe 为诊断命令接受任意可读路径)→ 计数对照 real-wxr-baseline.md:855/129/48/15 attachment/post/product/page + 75 unsupported(nav_menu_item 68 等)+ 451 term,逐项一致才算过
|
||||
- [ ] Step 2: 峰值内存记录——smoke 脚本内 `setInterval(100ms)` 采样 `heapUsed` 与 `rss` 取 max(瞬时值无意义);流式断言:解析 5.5MB 文件峰值 heapUsed < 100MB(不整读)
|
||||
- [ ] Step 3: 耗时记录;结果写 evidence 文档;commit `test(evidence): 阶段2真实WXR冒烟通过`
|
||||
|
||||
### Task 9: 阶段 2 收尾
|
||||
|
||||
- [ ] ADR-016(saxes/commander/sanitize-html 选型与许可证);根 typecheck 链加 @sf/adapter-wp;lint 全绿;contracts 首个消费者接线验证(adapter-wp workspace 依赖 contracts——**Plan 1 终审前瞻项落实**:import 路径用 dist 还是 src 的决策一并入 ADR-016)
|
||||
- [ ] 全量三绿 + push;plan 勾选;**移交阶段 3 清单**(内容/分类/媒体入库:media 魔数校验、source_records 落库、稳定身份 upsert、checksum skip、route_registry 生成)
|
||||
- [ ] 全量三绿 + push;plan 勾选;**移交阶段 3 清单**(内容/分类/媒体入库:media 魔数校验、source_records 落库、稳定身份 upsert、checksum skip、route_registry 生成;**续传携带:Task 0.2 cms 数据备份(用户暂缓,阶段 6 L3 验收前必须补做**——责任链不断)
|
||||
|
||||
## 验收(对照 spec §15 阶段 2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user