浅念
|
c64f6ac48a
|
test(evidence): 阶段4端到端预览验收
Plan 4 Task 8:真实 hesupipe 全链(register 副本复用 → import → publish
→ build → preview)临时库端到端验收,断言 21/21 PASS + 浏览器时刻 6 截图
无破版无横向溢出。新增 scripts/e2e-real.mjs(chain/serve 两子命令)与
docs/evidence/phase4-preview.md + phase4-assertions.json + screenshots/。
验收内修补(预览服务器,均有回归测试锁定):
- Location 头 ASCII 化:非 ASCII 码位按 UTF-8 百分号编码——原样 setHeader
触发 ERR_INVALID_CHAR 崩 500 或 latin1 mojibake(1746 redirect 全量
校验显影 2+3 例);manifest 原串保持权威,测试 2b 锁定。
- CSP 放行内联样式:Astro 5 scoped 样式内联 <style> 被 default-src 'self'
拦截(浏览器时刻显影,HTTP 级不可见);测试 6 断言同步。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 14:33:55 +08:00 |
|
浅念
|
5f78dde9ee
|
fix(preview): 流式文件服务与 HEAD 跳读;静默开关;redirect 转义;argv 缺值报错
复审四项:
- serveFile 改 createReadStream 直 pipe(Content-Length 取自先行 stat)——
64MB 文件 RSS 平坦(冒烟实测 -53MB 增长,此前整读驻留);HEAD 只发头
零读盘;流错误掐断连接不崩进程;头纪律抽 applyHeaders 唯一出口。
- createPreviewServer 增 quiet 选项(缺省 verbose);sf.ts preview:start
与 server.mjs 直跑入口各加 --quiet;CLI 透传经子进程冒烟验证。
- redirect 迁移页内嵌 location 时 HTML 转义(</&/"——Location 头原样)。
- parseCliArgs 末尾缺值 flag 显式报错(不再静默 undefined 漏成默认值)。
测试 12→13(新增 quiet 开关用例;用例 12 增 --quiet stdout 断言):
根套件 380 + 主题 26 = 406 全绿;typecheck/lint 干净。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 13:58:15 +08:00 |
|
浅念
|
7194d1bcef
|
feat(preview): 路由驱动的本地预览服务器
Plan 4 Task 7(V2 spec §12.2):apps/preview-server 零依赖 node:http 服务——
只读 succeeded job 的 dist + route-manifest(不查库),exact/ignore 双索引匹配
(query_key 行全串优先),html/asset 文件服务(manifest content_type 优先、
扩展名 MIME 表兜底、未知扩展 octet-stream)、redirect 301+Location、gone 410、
未命中 404 走 dist/404.html 自定义页;全响应 X-Robots-Tag noindex + HTML 响应
基础 CSP;回环绑定白名单(库级即拒);穿越预检先于 URL 解析(裸/编码点段、
反斜杠)+ target_file resolve+realpath 双检在 dist 内;目录请求一律 404(禁
列表);仅 GET/HEAD。sf.ts 增 preview:start(job 定位 + succeeded 校验 + 产物
行读取后即断池;--host 外值拒绝先于 DB 访问)。集成测试 12 例:真实构建产物
全链 + 手构 gone/MIME 探针行 + CLI 拒绝与子进程 happy path 冒烟。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 13:29:30 +08:00 |
|
浅念
|
1ed3ac062a
|
feat(build): astro 构建管线与媒体落盘
Plan 4 Task 5:build.service snapshotting 后接真实构建——building 态进
pipeline.ts(spawn `pnpm --filter @sf/theme-pilot-minimal exec astro build`
(SF_BUILD_INPUT env;非零退出+stderr 标记判据)→ dist 定位与 target_file
完整性 → 媒体 copyPath→dist/public_path(sha256 全量校验)→ sitemap.xml/
robots.txt(编码 URL 原样)→ route-manifest.json(§12.1 字段面、哈希链复用)
→ build_artifacts dist/manifest 登记)→ validating(空,Task 6) → 报告+report
行 → succeeded;失败任何一步 failed+error_code/error_summary(stderr 摘要)
+input/dist 清残。
dist 双位置纪律:主题包 dist 是暂存,产物汇齐后拷出 <artifactsRoot>/jobs/
<jobId>/dist(job 级权威),暂存随即清空。无网证明:ASTRO_TELEMETRY_DISABLED=1
(实测 @astrojs/telemetry 构建期真调 fetch)+ NODE_OPTIONS 透传(测试注入
fetch/net stub 构建仍成功)。主题集成补面:ProductList 模板 + /products/
主列表分派特判(DB 物化形状 targetContentId=null,Task 4 夹具曾被占位绕过)。
测试:tests/integration/build-pipeline.test.ts 五用例(全链/夹具直跑/五禁
断言/失败路径);build-snapshot 状态机补 building + 清场补 build_artifacts。
root 359 + theme 26 全绿。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 10:52:13 +08:00 |
|
浅念
|
8f42c838c1
|
feat(build): 冻结 Render Snapshot 物化与 build job
Plan 4 Task 2:build_jobs 生命周期(queued→snapshotting→validating(空)→succeeded)
+ build_input_snapshots 行(三哈希/主题/config 引用/真实版本列)+ 六文件物化
(site/content/taxonomies/routes/assets/render-manifest——canonicalJson 确定性落盘)。
- render-snapshot.ts:纯物化(db 只读)——published 修订(ADR-010 冻结语义)+
路由/媒体 ready 集合 → 六文件;bodyHtml URL 重写(剥 host 按 path 与 ready 集合
public_path/source_url 双源比对,命中根相对化、未命中保留+计数);媒体实体路径
三候选解析(快照 storageRoot→仓库存储根→incoming 兜底)
- build.service.ts:幂等键 = site+published 集合哈希+主题版本(复用不重跑);
repeatable read 单事务一致性读取+落盘+快照行;报告 artifacts/jobs/<id>/reports/
build.json 先于终态落行;解析期失败零 job 行(NOT_PUBLISHED/SITE_NOT_FOUND)
- sf.ts + build:create;build.module.ts Nest 薄壳
- tests/integration/build-snapshot.test.ts:10 测试(状态机/六文件面/URL 重写/
字节确定性/NOT_PUBLISHED/ADR-010 双修订/重写器纯函数四例)
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 07:46:23 +08:00 |
|
浅念
|
6a1d9a383b
|
chore(publish): locale 决胜键;confirm 清单输出;并发窗口注释
- 候选查询 orderBy 第三键 entry_locales.locale(同 typeKey+slug 多 locale 决胜,
清单呈现稳定);mini 单 locale,既有测试期望三元组已含 locale 列、无需改
- CLI confirm 输出发布清单(result.candidates,与 dry-run 同行格式
published→current;>20 行截断为 …and M more——CONFIRM_LIST_CAP)
- PublishSiteContentResult.candidates 注释钉死:confirm 期 candidates 是事务前
快照,权威数以 publishedCount 为准(并发窗口)
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 07:21:34 +08:00 |
|
浅念
|
c35f10835a
|
feat(publish): 显式发布命令(dry-run/confirm/审计/站点配置首版/锚点helper)
- publishSiteContent 零装饰器服务:dry-run 只读清单(current 非空且 published≠current)
+ 随机 confirm token;confirm 单事务批量 UPDATE published←current + audit_logs
(action=content.publish,全仓第一个审计写入方)+ site_config_revisions v1 首版
({name,origin,locale,brand:{}},UNIQUE(site_id,version) 幂等)
- 空清单 no-op 零写入;SITE_NOT_FOUND/CONFIRM_TOKEN_REQUIRED 均先于任何写入
- P0 门禁不做(ADR-018 记偏离,服务 JSDoc 注明);导入永不发布(ADR-010)
- sf CLI + content:publish(--dry-run/--confirm/--actor,缺省 dry-run 安全优先,
actor 缺省 super_admin@cli)
- anchors.ts:五列内容身份查询从 content/term/route 三写入器抽单源共享(零行为
变更,既有 333 测试守护)
- 集成测试 7 例(临时库 mini 全链):dry-run 零写入/confirm/幂等/config 首版与
既有跳过/actor/空清单/SITE_NOT_FOUND/缺 token
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-22 07:06:23 +08:00 |
|
浅念
|
8003de336b
|
feat(import): 全链编排与导入报告
Task 6(Plan 3):四家写入器汇流为默认编排管道。
- import.service:注入 events/pipeline 缺席时走真实事件流(runWxrAdapter
惰性 async generator,mapConfig.locale=site.default_locale 前置事实 8);
分派严格串行 content→term→media→route(禁 Promise.all);finish 硬序
term(sync)→content(sync)→media(await)→route(await);counts 由 ⊕ 覆盖
改为逐 key 求和聚合;新增 stale 回收(同站点 queued/running >24h 一条
SQL 置 stale);报告新增 paths/productsPath+newsPath、issueGroups 六组、
sanitize 清洗统计(事件流 changedRows/byDiffType 聚合)、adapterTiming。
- content-writer:NULL-entry 收编(快照行在而 content_entry 空,五列先验
三分支:无先验新建挂接/同 checksum 挂接 skipped/异 checksum 挂接新版本)。
- route-writer:attachment 降级汇总按去重 sourceId 计(重跑仍必现);
normalizeListPath 导出;writeAttachmentRoute 返回 written/resolved/blocked。
- 四家写入器 identity 收敛共享基类型 WriterContext =
Pick<ImportJobContext,'db'|'siteId'|'connectionId'>(type-only import,
无运行时环依赖)。
- CLI import 增 --products-path/--news-path(前置事实 9,缺省 /products/
/news/)记入 job 报告。
- 测试:tests/integration/import-full.test.ts 6 例(全链 zh-CN 接线证明
+published 全 NULL/报告四域计数+sanitize+分组/幂等重跑零写语义/
无 media 快照域缺失不失败/NULL-entry 收编/stale 回收/REPORT_WRITE_FAILED
遗留补测);import-job 契约测试钉死 counts 求和(100⊕3=103)。
全量 323 通过;typecheck/lint 干净。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 20:08:06 +08:00 |
|
浅念
|
eb188da5c2
|
fix(import): 复用口径忠实化;管道 Outcome 契约与 succeeded_with_issues;报告写入时序;stale 警告
评审修复(Plan 3 Task 1 复审):
- 复用路径只返回 job 锚定字段(jobId/status/reused/siteId/mapping_version/
report_path),快照/版本块仅在非复用时解析返回——复用结果不再回显编造数据
- ImportPipeline 契约钉死:返回 Promise<ImportPipelineOutcome>
({ counts, issues });服务按"累加器 ⊕ 返回值"合并(counts 同键返回值
覆盖、issues 累加器在前),issues 非空 → succeeded_with_issues;
ctx.report 即累加器(管道抛错时部分产出仍入报告)
- 报告时序:mkdir + tmp 写入 + rename 先于终态行更新——report_path 落库
即文件存在;写失败 → failed(REPORT_WRITE_FAILED),不挂悬空路径
- CLI 复用命中非终态(queued/running)→ 崩溃残留警告(重试需显式换 --key)
- 管道抛 ImportError 保留原 code 入 error_code,summary 冠 [pipeline]
- 确定性 23505 探针:未提交孪生 job 行事务内持唯一键触发真实 PG 唯一
冲突 → 败者复用既有 job
- 报告补 idempotencyKey / attempt 字段
- 计划文档 Task 6 增补 stale 回收决策项(ADR-017)
测试 282 通过(279 + 3:Outcome 合并/ImportError 前缀/23505 探针);
typecheck 四段链 + lint 全绿。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 15:56:00 +08:00 |
|
浅念
|
0e8b2b5356
|
feat(import): job 骨架与生命周期
Plan 3 Task 1:sf import 命令骨架 + import_jobs 生命周期(空管道,Task 6 接真实管道)。
- import.service.ts(零装饰器):createImportJob——站点/快照解析(缺省按 (connection,
kind='wxr') 选最新;三快照绑定按前置事实 13 记入报告,job 行不扩列)、幂等建行
(默认 key=site+wxr快照+三语义版本;重复提交复用既有 job 不重跑,并发撞唯一约束
重查收敛)、状态推进 queued→running→succeeded/failed(attempt/started_at/finished_at/
error_code/error_summary)、报告落 artifacts/import/<jobId>/reports/import.json
(全四版本块:adapter/mapping/sanitizer/parser;job 行落 mapping_version)
- 可预期错误带 code:SITE_NOT_FOUND / SNAPSHOT_NOT_FOUND / NO_WXR_SNAPSHOT /
PIPELINE_FAILED——解析期失败零 job 行
- adapter 事件加法式补 postId(前置事实 12):unsupported/error 事件可选携带
String(postId),缺失时键缺席不硬造 source_id;row 事件与报告聚合零影响、不触发版本 bump
- canonicalJson 提取至 apps/api/src/common/canonical.ts 共享导出(前置事实 6),
register 侧改 import 零行为变更(既有清单哈希测试守护)
- sf CLI + import 命令(--site/--snapshot/--key/--connection),输出 job 摘要与版本块
- 测试:tests/integration/import-job.test.ts(5 例:生命周期观察/最新快照解析/幂等复用
与并发收敛/可预期失败零落行/failed 路径)+ adapter postId 3 例 + canonical 单测 6 例
(265→279 全绿);typecheck 四段链/lint 绿;临时库 CLI 冒烟(register→import→复用)通过
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 15:30:42 +08:00 |
|
浅念
|
a185254e19
|
chore(scripts): 侧车导出加固——site-id UUID 校验与 audit 非 CDATA 匹配
评审两条非阻塞加固:--site-id 拼入 SQL 前过 UUID 形态白名单(非 UUID 拒绝并
明确报错);--audit 的 attachment_url 抽取正则同时匹配 CDATA 与裸文本两形态。
对真实数据重跑数字不变(859 条 / 855 对 836 命中,输出哈希一致)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 15:12:41 +08:00 |
|
浅念
|
a227cfac49
|
feat(source): 媒体映射侧车导出与登记
Plan 3 Task 0(评审 Issue 1):旧 cms 库 media_assets 的 source_url→file_path
映射只读导出为侧车 JSON(859 条,覆盖率对照 WXR 855 attachment_url:精确命中
836 / 归一命中 0 / 未命中 19,97.8%),register 新增 --media-map 输入通道
(kind=manual_url_list 快照,形状校验 INVALID_MEDIA_MAP,metadata 含条目数,
幂等复用)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 14:42:32 +08:00 |
|
浅念
|
21ef37232d
|
test(evidence): 阶段2真实WXR冒烟通过
真实 hesupipe WXR(5,490,399B,SHA-256 与 Plan 1 基线一致)跑 wxr:probe 全链:
计数 18 项对照全过(rows 1047 / terms 451 / unsupported 75 细分一致 / failed 0 /
item 总数 1122);heapUsed max 62.2MiB < 100MiB(setInterval(100ms) 双 max 口径,
全程 167ms 仅 2 样本如实注明);parseMs 含流 IO 等待口径注明。
nicename 锚点核对:在表 taxonomy 706/706 全命中(nicename≡slug,违例 0);
category/post_tag 共 807 引用因 wp:term 表缺该 taxonomy 不可解析——阶段 3 需按
(domain, nicename) 自建缺失 term,不能假设 term 表完备。
eslint.config.js:scripts/*.mjs 显式 node 全局(no-undef 误报)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 13:25:09 +08:00 |
|
浅念
|
c28c1655a4
|
fix(source): DB 事务化;storageRoot 入 metadata;siteCreated 与并发测试;CLI 体验与校验小修
评审修复(4 项 Important + 8 项 Minor):
- DB 事务化:site→connection→snapshots 全部落库包进 db.transaction 单事务
("任何失败零落库"字面成立);助手改收 Pick<select|insert|execute> 结构类型
(tx 与直连 db 通用)。并发撞唯一约束不在事务内补查(语句失败后续查 25P02),
改为 registerSource 层事务级重试(≤3 次,第二次 select 命中复用分支)
- connection 并发:select-then-insert 无约束可兜底,事务内 pg_advisory_xact_lock
(siteId:name) 串行化同键登记(kind 枚举扩展推迟 REST 通道,注释注明)
- storageRoot 绝对路径固化进每条 snapshot metadata 与报告 inputs(快照不可变,
事后无法补写——永久缺口封堵)
- ensureSite 返回 created;RegisterResult.siteCreated;CLI 站点已存在时提示
--name/--locale/--origin 未生效
- 媒体复制期逐文件 sha 复核(copyAndHash 对拍清单,源中途变更 → COPY_FAILED)
- 空媒体目录拒绝(EMPTY_MEDIA_DIR)
- CLI:PATH_NOT_ALLOWED 追加 --allow 提示;--origin 经 new URL 校验(http/s)、
--locale 语言标签形状校验,fail fast;开跑打印生效白名单(审计)
- makeRunId 服务导出、CLI 复用;.tmp 残留手工清理策略写入文件头注释
- 测试 +2:并发同输入恰好一组行/双方同 id/新建标志恰一(3 次重跑稳定);
isUniqueViolation 形状单测(drizzle cause.code / 裸 code / 其他)
全套 263 绿;typecheck / lint 绿;CLI 冒烟对临时库验证事务路径、siteCreated
输出、defaults 落库(en-US / https://qa.example)、白名单打印与校验报错
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 13:06:48 +08:00 |
|
浅念
|
586d715455
|
feat(source): 输入登记服务与 sf CLI(content-addressed 快照)
- registerSource(db, input):wxr/sitemap/媒体目录三类输入登记为 content-addressed
快照——流式 copy+sha256 单遍双算(64KB 块),副本落
storage/snapshots/<kind>/<sha256前2>/<sha256>(文件)或 <sha256>/(媒体整树),
后续任务只读副本;UNIQUE(site,kind,sha256) 幂等(重复登记复用既有行)
- media_bundle 哈希钉死:排序相对路径('/' 归一)+ 每文件 sha256 → canonical
JSON(键排序无空白)→ 再 sha256;不含 mtime、不依赖遍历顺序;完整清单
(path/bytes/sha256)入 metadata jsonb;符号链接(含 junction)跳过并记录
- site 按 key select-then-insert upsert(23505 竞态重查);connection 判重键
(site_id,name) 为应用层 select-then-insert(表无库级唯一,注释注明)
- 路径安全:输入 realpath 后须在 allowedRoots 白名单内(缺省 storage/incoming),
ENOENT/越界/类型不符抛带 code 的 SourceRegisterError;全部输入先校验后摄取,
任何失败零落库零复制
- 报告落 artifacts/register/<runId>/reports/register.json(runId=时间戳+随机;
输入清单/哈希/大小/耗时)
- scripts/sf.ts(commander):wxr:probe 只读诊断(任意可读路径,IO/损坏文件出
报告不崩——Task 6 fatalError 契约)出 JSON/HTML 报告;source:register 调服务
出摘要。CLI 机制钉死:根 devDep tsx + 根 script sf + commander 根依赖,TS 源码
直连(@sf/* 经 tsconfig paths,不经 dist)
- source.module.ts:Nest 注册 SourceRegisterService(standalone context 用);
register.service.ts 刻意零装饰器(CLI/测试直 import 不拉 reflect-metadata)
- launder 类型垫片改经 triple-slash 引用随 html.ts 进任意编译程序(apps/api 与
根程序直连适配器源码的新场景);apps/api 增 @sf/adapter-wp workspace 依赖与
paths 映射(register.service 消费 ADAPTER_VERSION 单源)
- 测试 +7(tests/integration/source-register.test.ts,临时库 + tmpdir storage/
报告根 finally 清理):三输入登记/清单哈希定义对拍/重复登记幂等/mtime 排除/
副本布局与内容一致/路径安全六分支/junction 跳过/报告契约;全套 260 绿
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
2026-10-21 12:31:15 +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 |
|
浅念
|
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 |
|