Commit Graph

116 Commits

Author SHA1 Message Date
浅念 6573b8b326 feat(adapter-wp): 全链组装与导入前报告
- src/versions.ts:ADAPTER/MAPPING/SANITIZER_VERSION 三常量(bump 语义 JSDoc 冻结:
  映射表/清洗白名单实质变更才 bump 对应项,三者独立演进)+ PARSER_VERSION(saxes 硬编码)
- src/adapter.ts:runWxrAdapter 串联 parseWxrStream→mapItem→sanitizeBodyHtml 为
  AsyncGenerator<AdapterEvent>;term 旁路直通;bodyHtml 为空跳过清洗零 diff;
  changed 用 html!==input(Task 5 移交:diffs.length===0 ≠ 未变更);validateUniversalRow
  加法式契约复检(不动 contracts);解析/清洗分段计时(parser.next() await 窗口归属解析)
- src/report.ts:runWxrAdapterReport 聚合(首条版本块/按 sourceType Created-Skipped(无
  变化)-Failed-Unsupported/清洗统计按 diff.type+changedRows/行级错误 rawLocation+message
  +excerpt/致命 WxrError 记 fatalError 不上抛——wxr:probe 消费通道);renderReportHtml
  所有动态文本经 escapeHtml(<>&"',Task 5 移交的标记注入面防线)
- test/adapter.test.ts:19 用例(mini 全链/changed 语义/报告结构/恶意样本转义/
  unsupported-error 汇聚/wppop 失败贯通/JSON 可序列化/致命解析错误交付确定性)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 11:44:55 +08:00
浅念 15e1e51a14 docs(plan): P2 Task 5 勾选 + Task 6 对接清单
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 11:27:18 +08:00
浅念 7506fef875 fix(adapter-wp): srcset 审计精确化(parse-srcset);false 形态防御;复用 launder.naughtyHref;等价测试参数化
审查修复四项:srcset 逐候选审计改用 parse-srcset 同库实现(data: 逗号内嵌
URL 不再误切;全候选被拒/解析异常/零候选→整属性删除记账,含空 srcset);
deriveMirrorView 对 false/缺省形态 fail fast(allowedTags/allowedAttributes
的 false=库全放行语义与镜像空集解释相反,修正原注释);URL 判定复用
launder.naughtyHref(sanitize-html 2.18 同一实现路径,删手写镜像);
仪表化等价测试参数化为 15 个对抗样本(实体/控制字符/NULL/URL 注释走私、
未引号/重复属性、深嵌套、srcset 形态)。测试 204→228。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 11:18:39 +08:00
浅念 8dd6eaf1b6 feat(adapter-wp): HTML 白名单清洗与差异收集
sanitize-html@^2(MIT)白名单清洗 + onOpenTag 观察钩子按文档序收集 SanitizeDiff[]
(镜像判定从 SANITIZE_CONFIG 单源推导;仪表化与清洗输出字节等价,测试钉死)。
白名单决策冻结:h1/布局标签/内联样式/iframe 拒绝(diff 留痕),外链 img 含
1x1 追踪像素本切片保留(远程依赖报告归 Task 6,路径重写归阶段 3)。
测试 184→204。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 10:39:37 +08:00
浅念 8796da03f1 docs(plan): P2 Task 4 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 10:14:47 +08:00
浅念 4c4c43f7d9 fix(adapter-wp): 原型键防御与对抗测试;meta 冲突策略统一(单值先者+issue/phpRaw 清理);extras 原型污染防御;config 校验;prebuild 依赖固化
- Fix 1:routes/STATUS_MAP 查键一律 Object.hasOwn 守卫('constructor'/
  'valueOf' 等继承键裸索引命中函数值绕过 undefined 检查)+ 对抗测试
- Fix 2:prebuild 脚本先建 contracts dist(fresh-clone build 不再 TS2307,
  亦防 stale dist 静默编译;已实测 rm -rf dist 后全链成功)
- Fix 3:单值 meta(thumbnail/seo×2)重复→先者胜出+issue(WP 单值语义,
  首个占位即便值非法不被顶替);wppop 重复→最后一次决定终态(成功清
  phpRaw/失败清 wppop_sections,两键互斥);三方策略 JSDoc 冻结
- Fix 4:extras 改 null 原型字典——'__proto__' meta 不再被 setter 静默
  吞掉(测试钉死自有键+JSON 往返);gallery 累积语义明确
- Fix 5:typeRoutes 空字符串路由值 → TypeError fail fast
- 173→184 测试全绿;typecheck/lint/build 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 10:10:03 +08:00
浅念 c99bbbb29e feat(adapter-wp): WXR→UniversalRow 映射器(status 值域/悬空保留/unsupported 分流)
- mapItem 纯函数:WxrItem → MappedResult 三分支(row/unsupported/error),
  DEFAULT_TYPE_ROUTES/STATUS_MAP/META_KEYS 三冻结表导出
- Plan 1 移交落实:status 值域表驱动(未知→unknown+issue);property 断言
  (isUniversalRow+refs 形状非空)测试钉死
- Task 3 审查移交:postId null→error(不产 row 不猜);-7 原样;-0 归一 '0'
- taxonomyRefs 锚点=category @nicename(=term slug,Task 8 实数据核对后冻结)
- 悬空 gallery 203/204 原样保留;wppop 解析失败→extras.phpRaw+issue(不崩不丢)
- Task 2 审查移交补测四例:d: 键拒绝/NUL 往返/s:1:"0"→Map/1MB<50ms 烟雾
- tsconfig.build.json:src 首次引 @sf/contracts 后 build 经 contracts dist
  声明解析(rootDir TS6059;typecheck/vitest/tsx 仍 src 直连,ADR-016 归口)
- 142→173 测试全绿;typecheck/lint 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 09:45:34 +08:00
浅念 f4971a943b docs(plan): P2 Task 3 勾选 + 两批审查移交件并入 Task 4
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 09:26:49 +08:00
浅念 8edbbad45b fix(adapter-wp): 内存上界注释纠正;错误前事件排空确定化;post_id 严格解析;嵌套畸形 fail-fast;类型收紧与测试钉
审查修复六项(wxr-stream):
- 内存上界表述纠正:saxes 按节点内部缓冲整个 text/CDATA 节点,对抗性
  单字段文件峰值 ≤ maxWxrBytes 的 UTF-16 表示(约 2×),并非与文件
  大小解耦;maxFieldLength/maxCdataLength 封顶的是本模块缓冲与产出
  字段长度
- parser.write/close 抛错前先排空待 yield 队列再重抛:错误前已完成
  事件的交付不依赖 chunk 划分(测试钉死单 chunk 与 7 字节切块一致)
- wp:post_id / wp:term_id 严格解析:整串十进制 + Number.isSafeInteger,
  否则 null(表驱动测试:''/'0x10'/'12abc'/超安全域 → null)
- 嵌套 item/wp:term 立即 WxrParseError(fail-fast,防外层截断产出)
- Target.field 经 Exclude 收窄为纯文本字段,消除 as string 断言
- 测试钉死:提前 break 销毁上游流、text+CDATA 混合累计、限额错误
  message 含行列位置

142 测试全绿(新增 12)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 09:21:41 +08:00
浅念 25e8b9cb5c feat(adapter-wp): saxes 流式 WXR 解析器
parseWxrStream(readable, {limits, now}) → AsyncGenerator<WxrEvent>:
逐 chunk 喂入 saxes(^6.0.0,ISC;计划所写 ^0.9 不存在于版本历史),
item/term 按文档序交错产出;任一时刻只持有当前聚合状态 + 单 chunk 内
待 yield 队列,绝不收集已产出 item 列表(峰值内存与文件大小解耦)。

- 字段:postId(数值解析)/postType/status/postName/link/title/
  contentEncoded/excerptEncoded/postDate/postModified/attachmentUrl/
  creator + metas{key,value}[] + categories{domain,nicename,value}[] +
  rawLocation(wxr://item/<n>、wxr://term/<n>)
- 错误体系照 PhpError 先例:WxrError 基类;WxrParseError 带 saxes
  行列位置(error 事件同步抛出时捕获);WxrLimitError 按 kind 区分
- 限额:bytes/items/terms/depth/cdata/fieldLength(先检查后追加,
  超限内容永不入缓冲)/metaPerItem/time(可注入时钟)——超限即抛,
  不静默截断
- 实体防线(测试钉死):saxes 实体表固定为五预定义 + 数字引用,
  DOCTYPE 内部/SYSTEM 实体不注册——引用即 WxrParseError,替换文本
  不进产出;纯字符串解析,全程无网络 I/O
- 跨 chunk 喂入(7 字节/块)与单 chunk 产出一致(StringDecoder 处理
  多字节边界);畸形 XML(提前闭合/截断)→ WxrParseError 带行列

130 测试全绿(新增 22)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:55:11 +08:00
浅念 6eb5fa6dda docs(plan): P2 Task 2 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:41:17 +08:00
浅念 4f90b63c61 fix(adapter-wp): phpValueToJson 辅助与 JSON 陷阱警示;错误上下文摘录;PhpError 公共基类
复审修复:新增 phpValueToJson(Map→字符串键普通对象,递归;bigint 分支
冻结为抛 TypeError 不静默转换),PhpValue JSDoc 警示 Map 直接
JSON.stringify 会静默变 {};两类错误 message 附 offset ±20 码元原文
摘录(控制字符转义、120 字符封顶);PhpError 公共抽象基类,两子类
互不 instanceof 的既有断言保持通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:37:08 +08:00
浅念 5550952fdb feat(adapter-wp): PHP serialize 受限解析器
只支持 i/b/d/s/a 四类型的单指针递归下降解析器:深度与元素数双限额
(默认 DEFAULT_WXR_LIMITS phpMaxDepth=8/phpMaxElements=1000,超限抛
PhpLimitError 按 kind 区分),长度前缀/结构错误一律抛 PhpParseError
(含 offset),尾随垃圾抛错。a: 形状推断:连续 0 起整数键→密集数组,
否则 Map。s: 按 UTF-8 字节宽度取串(含代理对),中文 postmeta 准确。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:23:32 +08:00
浅念 ec898f6e0a docs(plan): P2 Task 1 勾选;tsconfig 双链型注释(ADR-016 待记)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:16:18 +08:00
浅念 dc4557cccf feat(adapter-wp): 包骨架与限额契约
Plan 2 Task 1:@sf/adapter-wp 包骨架(照 contracts 先例:build extends +
rootDir "." + exclude test)+ WxrLimits 十字段限额契约与 DEFAULT_WXR_LIMITS
值域冻结(设计基线 §7.2)+ 跨包接线。

跨包接线(一份 paths 三方消费):
- 根 tsconfig compilerOptions.paths 把 @sf/contracts、@sf/adapter-wp 映射到
  各自 src/index.ts;adapter tsconfig extends 根 tsconfig 使包级 tsc 读同一
  份 paths(extends 相对路径按声明处解析,映射始终落在仓库根)
- 根 vitest.config.ts resolve.alias 指向同一映射(vitest 不读 tsconfig paths)
- adapter 加 @sf/contracts workspace:* 依赖;三方均直连 src,不经 dist
  (已实测:移走 contracts dist 后 vitest 与包级 tsc 仍全绿)
- 根 devDependencies 加 @types/node@^22(对齐 apps/api 钉 22):alias 的
  fileURLToPath 辅助需 node 类型,此前根程序靠 apps/api 传递 @types 才有
  node 全局,属偶合

限额类型锁(照 contracts 单源派生先例):DEFAULT_WXR_LIMITS as const
satisfies WxrLimits + _LIMIT_KEYS 键集与 keyof WxrLimits 双向咬合断言;
timeBudgetMs 以可注入时钟(构造函数 now())测试于 Task 3、实测覆盖于 Task 8。

验证:pnpm test 78 绿(75+3);pnpm --filter @sf/adapter-wp exec tsc
-p tsconfig.json --noEmit exit 0(--listFiles 证实 contracts 经 src 解析);
根 typecheck 链与 pnpm lint 绿;build 产物形状核验后清理。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:05:08 +08:00
浅念 2d33dd17dd docs(plan): Plan 2 采纳复审建议——paths 统一三方消费、commander 定根依赖
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 22:53:40 +08:00
浅念 5cc98c584f docs(plan): Plan 2 修复评审三 Issue 与建议——CLI机制/跨包接线前置/media清单哈希定义/probe路径策略/内存采样法/报告路径/责任链续传
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 22:51:53 +08:00
浅念 ecff5a34af docs(plan): Plan 2 实施计划——阶段2 WXR 流式适配器(9任务)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 22:46:37 +08:00
浅念 494beadf94 chore: 终审 Minor 清理——@types/node 钉 22、contracts 构建排除测试、helpers 盲区注记、计划收口注记
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 21:08:34 +08:00
浅念 c128ebf5a0 docs(plan): Task 14 勾选——Plan 1 全部任务完成;修注释笔误
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:59:58 +08:00
浅念 07483b5ae2 docs: ADR 001–015 落档 + README
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:56:26 +08:00
浅念 3c32becba6 chore: 收尾清账——type:module/gitattributes/engines/lint/CI/根typecheck覆盖
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:56:26 +08:00
浅念 622c2b09d8 docs(plan): Task 13 勾选——阶段 1 验收:globalSetup 临时库 + 空库一键迁移
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:43:24 +08:00
浅念 f1c181de02 test(db): 空库一键迁移验收 + globalSetup 临时库 + 公共夹具收敛(阶段1验收)
- vitest globalSetup:每次运行 CREATE sf_test_<ts> 临时库 → drizzle-orm 程序化迁移器
  全量迁移(apps/api/src/db/migrate.ts,drizzle-kit 无法指向运行时建库)→ 设
  DATABASE_URL_TEST 让 getDb() 自动回退到临时库;teardown DROP(WITH FORCE)。
  有 default 导出时 vitest 不捕获具名 teardown,故用双具名导出。
- db.full-migrate.test.ts:21 表齐 + 四张不可变触发器在库 + sf_test_* 护栏断言
  (current_database 不符即红,杜绝静默连开发库)。
- helpers.ts 收敛:rand/rejectionMessage/createSiteFixture/withImmutableCleanup/
  withTestDb,Task 6–12 七文件内联版替换为 import(断言、对照组、清场顺序不变)。
- scripts/migrate-test.sh:docker compose up --wait + pnpm test 一键验收。
- 实证:75 测试全绿(72+3);开发库 21 表行数总和 0(零写入);无 sf_test_* 残留。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:43:13 +08:00
浅念 9e0b49885b docs(plan): Task 12 勾选——七域迁移收官,21 表齐
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:28:07 +08:00
浅念 e905cba729 feat(db): 0006 构建/验收/审计域 + 追加式审计
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:24:18 +08:00
浅念 c09a9b31f6 docs(plan): Task 11 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:19:00 +08:00
浅念 2b017ed197 feat(db): 0005 路由注册表——exact/collision 双键 + NULLS NOT DISTINCT 唯一
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:14:46 +08:00
浅念 de415e7301 docs(plan): Task 10 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:08:24 +08:00
浅念 1cc0dcc57f feat(db): 0004 媒体域——assets/usages 双唯一
media_assets 双唯一 UNIQUE(site_id, source_url) / UNIQUE(site_id, public_path)
+ status 四值 enum + source_id 可空;media_usages 复合主键
(media_asset_id, content_revision_id, field_path),无独立 id/时间戳(spec §5.5)。

vitest fileParallelism: false——不可变表清场 DISABLE/ENABLE TRIGGER 是库级 DDL,
与并行文件的不可变断言竞态(实测 media 清场窗口内 content 断言偶发放行);
串行执行消除竞态,套件仅数秒。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:02:57 +08:00
浅念 cb959c905d docs(plan): Task 9 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:52:01 +08:00
浅念 bc1dcc2ef9 feat(db): 0003 分类域——taxonomy/term/content_terms
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:48:04 +08:00
浅念 60581047ef docs(plan): Task 8 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:42:46 +08:00
浅念 3b45219bbd feat(db): 0002 内容域——entry/locale/revision + 修订不可变 + 回填 content_entry FK
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:36:19 +08:00
浅念 7636a81bd2 docs(plan): Task 7 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:31:25 +08:00
浅念 32ceba58f0 feat(db): 0001 来源域——connection/snapshot/job/record + 快照不可变触发器
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:27:31 +08:00
浅念 5da883cd40 docs(plan): Task 6 勾选;schema 注释编号对齐
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:19:54 +08:00
浅念 63830f249e feat(db): 0000 站点域——sites/site_config_revisions + 唯一约束
drizzle-kit 首个迁移从 0000 起编号(计划文案写 0001,以实际产出为准)。
迁移文件 + meta/_journal.json + snapshot 一并入库。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:17:16 +08:00
浅念 7720d66630 docs(plan): Task 5 勾选 + Step3 验证命令对齐现实
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:14:17 +08:00
浅念 39734a7467 fix(api): requireDatabaseUrl 早失败守卫;typecheck 门禁;build 排除 test;池生命周期注释
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 19:10:19 +08:00
浅念 514f37301e feat(api): NestJS standalone 骨架 + Drizzle/postgres.js 接入
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:56:50 +08:00
浅念 6725bf0024 docs(plan): Task 4 勾选;Task 13 沉淀 KINDS 复用注记
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:50:18 +08:00
浅念 d650490ed4 fix(contracts): Kind/Policy 单源派生冻结锁;status 整数+域校验;key 放宽注释
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:47:42 +08:00
浅念 c8c4336da7 feat(contracts): Route/Render/Theme Manifest 契约 v1 冻结
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:36:45 +08:00
浅念 33517065f6 docs(plan): Task 3 勾选 + 阶段2责任清单
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:30:43 +08:00
浅念 31cd7ed7f4 fix(contracts): 守卫收紧必填字段类型与对象形状;修复包级 test 脚本;补齐守卫测试与 REQUIRED 类型锁
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:26:42 +08:00
浅念 857389e68b feat(contracts): UniversalRow 契约 v1 冻结 + 守卫 + 测试
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:15:47 +08:00
浅念 5e4524e2fb docs(plan): Task 2 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:12:37 +08:00
浅念 cf9a34959c feat(infra): PostgreSQL16 compose(宿主5434)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:10:26 +08:00
浅念 b538c21e78 docs(plan): Task 1 勾选;Task 14 沉淀质量审查建议(type:module/.gitattributes/engines)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 17:43:25 +08:00