docs: ADR 001–015 落档 + README
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# ADR-001:ORM 选择 Drizzle(+ PostgreSQL 16)
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:ORM 用 Drizzle,数据库 PostgreSQL 16;所有 Schema 通过迁移文件管理,关键约束(不可变触发器、NULLS NOT DISTINCT 唯一、CHECK)直接落数据库。
|
||||
- **理由**:SQL 完全可控——库级 DDL(触发器/部分唯一索引)在手写迁移里可精确表达;cms 项目实战已验证该组合。备选 Prisma 被否决:复杂约束的表达要绕开生成器,失控感强。
|
||||
- **权衡**:无成熟 migration 可视化工具,靠 drizzle-kit + 快照;换来的是约束即规格、无 ORM 魔法。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-002:服务形态 = NestJS 模块化单体 + Standalone App Context + CLI
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:NestJS 模块化单体,本切片以 Standalone App Context 供 CLI 使用,不监听公网端口;服务层未来由 HTTP Controller 复用。
|
||||
- **理由**:导入/构建管道是长任务,先以 CLI 形态交付价值;NestJS 的模块/DI 体系保证后续加 HTTP 入口时服务层零改动。
|
||||
- **权衡**:起步比裸脚本重(装饰器/依赖注入样板);换来明确的模块边界与可测性。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-003:Job 执行 = 进程内 Runner + 数据库 Job 状态
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:导入/构建 Job 由进程内 Runner 执行,状态持久化在数据库(import_jobs/build_jobs 表),不引入 BullMQ 等外部队列。
|
||||
- **理由**:单机单进程已满足切片吞吐;状态落库使进程中断后可识别 stale Job 并恢复/标记失败,不依赖外部组件。
|
||||
- **权衡**:无分布式并行能力;若未来需要横向扩展须引入真正的队列——但 Job 表结构已按状态机设计,迁移成本低。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-004:原始输入 = Source Connection 与 Source Snapshot 分离
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:来源连接(source_connections)与输入快照(source_snapshots)分为两张表;每个 WXR/Sitemap/已解包媒体目录形成一个不可变 Snapshot 并记录 Hash。
|
||||
- **理由**:连接是可变的凭据/位置信息,快照是不可变的证据基线——后续所有解析、清洗、审计都锚定快照哈希,可追溯可复现。
|
||||
- **权衡**:每次导入多一次快照落库;换取原文永远可回查(HTML 清洗差异报告、导入争议仲裁的依据)。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-005:WXR 解析 = 流式 SAX 解析
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:WXR 用流式 SAX 解析;禁止整文件正则解析;限制字节、节点、深度、字段长度和处理时间。
|
||||
- **理由**:真实 WXR 可达数百 MB,整文件 DOM/正则会内存爆炸或静默错切(CDATA、实体、命名空间变体);流式 + 硬限制保证对畸形输入可预测地失败。
|
||||
- **权衡**:实现比正则贵得多、状态机复杂;换取对任意大小/畸形文件的健壮性与确定性资源上界。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-006:内容身份 = Source Record 稳定键
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:WordPress 导入内容的身份锚定 source_records 的稳定键(来源类型 + WordPress ID);Permalink 只作为 URL 证据保存,不作为稳定主键。
|
||||
- **理由**:WP 的 permalink 可随站点设置/ slug 修改而变,重导入时按链接匹配合错内容;post ID 在单个来源内天然唯一且不可变。
|
||||
- **权衡**:跨来源同名 ID 须靠 source 维度区分(复合身份);换取重导幂等与身份稳定。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-007:URL 模型 = Exact Route Key 与 Collision Key 分离
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:路由注册表存 Exact Route Key(原文、不大小写折叠、不去尾斜杠)与 Collision Key(折叠+规范化,仅用于风险检查)两个独立键。
|
||||
- **理由**:实际路由必须按静态站点服务器的真实行为匹配(大小写/尾斜杠敏感);而大小写碰撞、尾斜杠歧义是迁移质量风险,需要单独检测报警而非混入路由匹配。
|
||||
- **权衡**:route_registry 多一列维护;换取"路由正确性"与"碰撞风险提示"互不污染。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-008:媒体 = 本地 Content-Addressed Storage
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:媒体文件存本地 content-addressed 存储(按内容哈希寻址);本切片不用 MinIO/S3;未来对象存储通过更换 Storage Adapter 接入。
|
||||
- **理由**:哈希寻址天然去重且完整性可校验;本地 FS 起步零运维。cms 现状评估(docs/evidence)也表明对象存储在现阶段是非必要复杂度。
|
||||
- **权衡**:单机存储无冗余、迁移到对象存储需搬运数据;接口已按 Adapter 抽象,替换是受控变更。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-009:HTML 安全 = 白名单清洗 + 原文保留在 Source Snapshot
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:WordPress 内容按白名单清洗后才进入 content_revisions;原始 HTML 永远保留在 Source Snapshot;清洗差异写入报告。
|
||||
- **理由**:WP 内容可能携带脚本/内联样式/可疑 iframe,白名单是唯一安全默认;原文保留使清洗决策可审计、可回溯、可重跑(白名单升级后可重新清洗)。
|
||||
- **权衡**:存储翻倍(原文+清洗后);换取安全边界清晰且清洗永不丢信息。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-010:发布模型 = current/published 双指针
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:content_entries 维护 current(编辑态)与 published(线上态)双指针;导入只更新 current,发布只移动 published 指针,从不修改 Revision 内容。
|
||||
- **理由**:导入是批量实验性操作,绝不能直接改线上内容;Revision 不可变 + 指针移动让"发布了什么""何时回滚"完全可追溯。
|
||||
- **权衡**:查询线上内容须 join 指针而非直取最新;换取导入安全与发布原子性。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-011:构建 = 冻结 Render Snapshot
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:Astro 构建输入是冻结的 Render Snapshot(build_input_snapshots),禁止查询数据库、禁止在线拉内容、禁止 Seed fallback。
|
||||
- **理由**:构建可复现是静态站点的核心承诺——同一次 Build 产物必须确定性一致;禁止运行时数据访问从机制上杜绝"构建结果依赖当时的库状态"。
|
||||
- **权衡**:构建前多一步快照物化;换来 Build 可重放、可缓存、可审计。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-012:主题 = 最小 Theme Manifest + Render Contract
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:主题由最小 Theme Manifest(入口/资产/能力声明)+ Render Contract(UniversalRow → 页面)定义;完整 Block 协议后置到后续阶段。
|
||||
- **理由**:先冻结渲染契约让主题与管道并行开发;第一主题不得依赖站点专属数据导入代码,保证主题可移植。
|
||||
- **权衡**:首版表现力有限(非全功能 Block 系统);换取契约早冻结、主题生态不被早期实现绑死。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-013:预览 = Artifact 自包含
|
||||
|
||||
- **状态**:已采纳(spec §3,阶段 0)
|
||||
- **决策**:预览服务读取与生产构建同一 Build 的 Route Manifest 与 Artifact,不读取实时数据库。
|
||||
- **理由**:预览必须所见即所得——与线上完全同源的产物;读实时库会让预览与实际发布内容出现不可解释的差异。
|
||||
- **权衡**:内容更新须走完整 Build 才能预览(无实时草稿预览);换取预览的绝对可信。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-014:vitest fileParallelism = false(集成测试文件串行)
|
||||
|
||||
- **状态**:已采纳(Task 10 实施发现,阶段 1)
|
||||
- **决策**:vitest 配置 `fileParallelism: false`,测试文件串行执行。
|
||||
- **理由**:集成测试共库,清场会做库级 DDL(不可变表 `DISABLE/ENABLE TRIGGER` 对**所有会话**可见)。文件级并行时 A 文件清场窗口会与 B 文件的不可变断言竞态——实测 db.media 清场 disable content_revisions_immutable 期间,db.content 恰好断言该触发器,UPDATE 意外放行。串行执行彻底消除该竞态。
|
||||
- **权衡**:套件失去文件级并行;但全套件仅数秒,并行无实际收益,确定性优先。
|
||||
@@ -0,0 +1,6 @@
|
||||
# ADR-015:globalSetup 临时测试库 + vitest 2 具名导出陷阱
|
||||
|
||||
- **状态**:已采纳(Task 13 实施发现,阶段 1)
|
||||
- **决策**:集成测试库由 vitest globalSetup 供给:setup 用开发库凭据连维护库 postgres,`CREATE DATABASE sf_test_<ts>` → 程序化跑全量迁移 → 写 `process.env.DATABASE_URL_TEST`;teardown `DROP DATABASE ... WITH (FORCE)`。彻底消除"测试碰开发库"与"清场残留"两类问题。
|
||||
- **理由**:每次运行拿到完好空库且全量迁移被真实验收;开发库零写入,无需预迁移。
|
||||
- **权衡/陷阱**:① vitest 2 的 globalSetup 加载器在有 default 导出时只取 default 作 setup、**不捕获具名 teardown**——必须用具名 `setup`/`teardown` 双导出且不写 default,teardown 才会注册执行。② worker 在 setup 之后派生,env 注入才对测试生效。
|
||||
Reference in New Issue
Block a user