来源:notes/documents/渠道开关实施记录:ticket_01_重跑与事件线闸门切换.md

渠道开关实施记录:ticket 01 重跑与事件线闸门切换

结论:渠道开关这条线正式开工。ticket 01 重跑(收回被 revert 的 schema 与回填),ticket 02 把事件线四处闸门从 sensitivity 换成 feishu_base_enabled。两个验收项都实测通过:logs/daily_log.md 逐字节不变、三目的地 dry-run 全绿。偏离 ticket 文本一处(网页日线是删闸门、不是换开关),已记录待确认。

来源:调研 note_10e5f94acdbd45c5、字段梳理 note_41fb5cfa22d34bed、方案与改造清单 note_908871bab2cc4fb0。

一、为什么先做完渠道开关,再动 SQLite

用户 2026-09-26 的决定是:SQLite 不再出现在代码里,历史数据挪进归档目录(仍在个人助理仓库内)。这等于选定了调研里的方案 B 全搬,而不是只搬台账。

但顺序上先做完 sensitivity 更省:

  • 开关集就是要搬过去的目标形状。先搬存储,等于按一个马上要删的字段设计新库,紧接着再改一次 schema、再定一次回填。
  • 回填的基线量在现在这层地基上,而且 ticket 01 的实测回填已经落在库里,只有地基不动它才成立。
  • 184 个 Python 单测、calendar_parity_report.py、Base 与日历的行数对照全都读 SQLite。先搬存储,这些对照要全部重造,ticket 08「不许差不多」就落不了地。
  • 回滚代价不对称:sensitivity 可 git revert 加重跑迁移;去 SQLite 动的是事实源本身。

二、ticket 01 重跑(commit eafd243)

背景:9c3db02 曾按当时的用户决策把 ticket 01 的代码整体回滚,只保留库里的列与回填,并把库的 schema 标记改回 3——形成「库比代码新」的状态。

做法:

  • 收回 58ed7f2 的 scripts/event_store.py,与那一版逐字节一致。
  • 不手工改标记,让真库走它自己的升级路径:note_registry_schema_version 3 → 4,_backfill_channel_switches() 重跑一次。
  • 重跑的实际效果只有一条:revert 期间新增的 note_908871bab2cc4fb0 的 publish_enabled 0 → 1。符合回填口径——博客两条管道都没有闸门,它已经在博客上。
  • 其余数字不变:notes 130 条,events 无 publish_enabled=0。

实测:单测全过;跑测试不改真库(前后 md5 一致);sync_daily_log_outputs --dry-run 三目的地 {succeeded: 3, failed: 0}。迁移前备份在 /tmp/dbcompare/pre-migration-20260926-174847.sqlite3。

三、ticket 02:事件线读点换掉(commit 46befea)

  • Base 三处闸门(sync_to_feishu_base.py 的行构建、V2 投影、旧版投影)与 note_base_projection.py::_event_rows:sensitivity == "private" 改成 not event.feishu_base_enabled;台账 error 文案从 private event 改成 feishu_base_enabled=0。
  • daily_log_view.py 删掉 [敏感记录已隐藏] 分支,本地日视图永远渲染全文。
  • capture_event.py 不再从 publish_enabled 推导 sensitivity,改为接收 feishu_base_enabled(默认 1,CLI 加 --no-feishu-base)。
  • project-calendar-entries.mjs 的 kept at home 与台账 error 两处文案不再提 private / sensitivity。

偏离 ticket 文本一处:web/scripts/lib/daily-projection.mjs 的日线没有改成读 feishu_base_enabled,而是把那道闸门删掉(只留 status === 'active')。理由:取合规则只该有一份、写在 calendar_entries 视图里(calendar_entries.py 的注释本身就写着飞书日历与网页是这份定义的兄弟投影),网页日程的开关是 calendar_enabled,视图已经实现;换成 Base 开关会让「关掉飞书 Base」经 planDeletions 静默删掉网页上的日历条目。两种改法今天的结果完全一致(视图 406 条里,active 的 private 日线是 0 条)。已记进 ticket 02 的 Comments 与 Answer,不认可可在那里回滚。

四、code-review 抓到的两件事

  • sensitivity 不能落默认值。 原本只删推导,新事件会落到 create_event 的默认 'private';而 migrate_daily_log_to_sqlite.py:67 拿 existing.sensitivity == "internal" 当幂等判据,被打破会让老 Markdown 迁移把已有事件当「变了」重写。现在 capture_event 写常量 internal,并加了断言钉住。
  • 文档措辞夸大。 新注释说这个文件不做筛选,但它仍筛 status === 'active'——那是这一行一直有的闸门。已改准。

五、验收实测

  • 验收项「logs/daily_log.md 改动前后逐字节一致」:同一份真库分别跑 HEAD 版与改动版的 render_daily_log_markdown,两版都是 38647 字符且相等;磁盘文件等于新渲染,且不含 [敏感记录已隐藏]。
  • Python 187 用例、web 62 用例全绿,eslint 干净。
  • sync_daily_log_outputs --dry-run:三目的地 {succeeded: 3, failed: 0};Base 事件线 299 条全部 skipped,网页投影 406 条 up to date、kept at home: 0。

六、遗留与下一步

  • 未做:ticket 03 笔记线开关、04 Base 列改造与退出语义、05 博客闸门、06 Web 与 Neon 列切换、07 剥离 sensitivity、08 观测对照。
  • ticket 06 的阻塞说明已过时:它写「本机没有 web/.env.local」,实际现在有且 DATABASE_URL 已配(本地预览用 4199 端口实测成功)。做 06 时直接跑迁移即可。
  • 去 SQLite 的四个根问题待答:事件事实源换成什么、断网是否必须能记、禁令是否覆盖测试与一次性脚本、归档目录里只放二进制还是也导出可读格式。(「两台机器要不要继续靠 commit 同步 SQLite」已由用户决策为不要。)
  • 一个观察:/note 在 17:51 那条事件只提交了 logs/daily_log.md,没带上 data/daily_log.sqlite3。这条缺口正是去 SQLite 要解决的问题;本次实施记录提交时把它一并带上。