来源:notes/documents/渠道开关实施记录:ticket_09_收尾与_Neon_投影回归修复.md

渠道开关实施记录:ticket 09 收尾与 Neon 投影回归修复

ticket 09 与 2026-09-27 的三件收尾都做完并推送(9096111、0d3a5d0)。ticket 09 是 ticket 06 的回归修复,不是新功能;三件收尾是 09-26 验收报告 §四 里挂着等用户拍板的事。至此 .scratch/channel-switches/ 的 9 张 ticket 全部收口,报告在 .scratch/channel-switches/report.md §八/§九。

一、回归:Neon 投影的 INSERT 列数错位

0919d14(ticket 06)从 UPSERT 里删 sensitivity 时,列清单删了、VALUES 里的 $11 没删——12 个表达式对 11 个列名。Postgres 只在 INSERT 分支报 more expressions than target columns,走 ON CONFLICT 更新分支不报。所以 ticket 06 的验收(projected 0 / deleted 0、「还是 409 行」)看不见它:那几天没有新条目要插。

直到给新笔记做投影才炸出来:failed: 1,sync_daily_log_outputs.py 的一个 stage 变 failed。修法不是补回 $11,而是把列名、占位符、ON CONFLICT 赋值三处从一个 UPSERT_COLUMNS 派生,让「加一列/删一列」不可能只改一半:

  • 新文件 web/scripts/lib/calendar-entry-upsert.mjs:UPSERT_COLUMNS / UPSERT_SQL / upsertValues()。
  • web/scripts/project-calendar-entries.mjs 的调用面收成 sql.query(UPSERT_SQL, upsertValues(entry, bodyOf(entry)))。
  • 新用例 web/tests/calendar-entry-upsert.test.mjs(5 个),其中一个直接断言 SQL 里不再出现 $11。

二、三件收尾(用户 2026-09-27 拍板)

① note_ffb6acf19cfe4547 用渠道开关表达冻结

渠道 处理 结果
飞书文档 feishu_docs_enabled=0 台账 skipped(feishu_docs_enabled=0),文档留在飞书不再更新
飞书日历 + 网页日程 calendar_enabled=0 飞书日历那条已删、Neon 那一行已删
博客 publish_enabled=1 不动 它 2026-09-23 起就在博客上,不新增曝光也不撤回
Base 「飞书文档」「日历同步」改成 false,状态 保持 active 与本地一致
生命周期 status=active 「冻结」不再由 status 兼职

必须记住的机制:笔记的操作字段(状态/主题/日历同步/日历时间/博客发布/飞书文档/Base 同步/用户备注)以 Base 为编辑面——note_base_projection._preserve_note_operations 在出站投影时用远端值覆盖本地值,note_inbound_reconciliation 再把远端真值回写本地。所以只改本地开关不会写进 Base,下一次反向回写还会把它翻回去。这次两边一起改,复核跑 reconcile_inbound 得 {updated: 0, unchanged: 133, ambiguous: 0, failed: 0},稳定。

由此暴露一个既存的不对称,见 §五。

② 两处口径偏差已追认

  • 网页日线是删掉闸门而不是改读 feishu_base_enabled:按 ADR 0011 第 1 条,网页日程的开关是 calendar_enabled(由本地视图的 eligible 表达)。spec 阶段一.3 那句已改写,ticket 02 追加了追认段。
  • 「飞书文档」列只在内容资产表:事件线没有文档投影。ADR 0011 第 4 条与 ticket 04 都已写明。

③ legacy「每日记录」的「敏感级别」列:复核不成立

工作区 Base 8 张表的字段列表里都没有这一列了(内容资产有「飞书文档」+「Base 同步」;日常事件 V2 有「Base 同步」;legacy 每日记录 13 个字段里一个都没有)。ticket 04 的那句遗留已标注为过期。顺带记一笔:legacy 每日记录表没有「Base 同步」列,而代码仍给它写这个字段,被 build_feishu_daily_log_fields 按 available fields 静默丢掉——不影响运行。

三、note_e3d7302eaa524b55 是另一台机器的行,保留

《地平线征程6(J6)系列公开量产车型梳理(截至2026年9月)》在 Base 里 status=active、修订号 6,本机 notes/ 与注册表本来都没有它。用户 2026-09-27 确认那是另一台机器做的事,保留。已写进 AGENTS.md 的 Storage Principle:审计与清理都不要把它当孤儿删掉。

四、09-27 的 rebase:两台机器各写一行,并集后 134 篇

推送被拒(另一台机器先推了 4 个提交,地平线笔记那串),按 AGENTS.md 的规矩 rebase 后并集合并 SQLite,不 force push。脚本按「base / 本机 / 远端」三方逐表逐主键比对,0 处真冲突(两边动的是不同主键):

表 base 本机 远端 合并后
notes 133 133 134 134
note_revisions 358 359 364 365
entity_projection_state 1744 1744 1747 1747
calendar_entries(视图) 410 409 411 410

合并后 integrity_check=ok、foreign_key_check 空。视图 410 行 = 本机那条 note_ffb6acf19cfe4547 退出、远端那条 note_e3d7302eaa524b55 进入,正好抵掉。地平线笔记缺的 Neon 那一路也补上:projected 1 / deleted 0 / failed 0 / remote rows 410(0d3a5d0)。

五、还挂着的一件:note_cli sync 的开关对已存在笔记是空操作

  • 现象:note_cli sync --no-feishu-docs(以及 --no-calendar / --no-publish-enabled)在已存在的笔记上不生效——出站投影被 _preserve_note_operations 用远端值覆盖,Base 不会变,下一次 reconcile_inbound 又把远端值回写本地。note_cli create 没这个问题(还没有远端行,没有 preserve 可覆盖)。
  • 为什么会这样:笔记的操作字段被设计成「Base 是编辑面」(ADR 0011 的验收要求 Base 里改这 8 个格子能回写),出站投影刻意让远端值优先,免得本地旧值盖掉刚在 Base 里改的东西。两条都合理,合起来就产生了这个不对称。
  • 三个选项:① 把 CLI 那几个开关在已存在笔记上的语义标成只读,文档指向「去 Base 改」;② 让「显式传参的字段」本地赢(preserve 只作用于没传的字段);③ 让 CLI 直接把改动写进 Base(复用投影的 upsert),把两个方向合成一条路。
  • 不阻塞本轮:这次是两边一起改的,状态已稳定。

六、验收命令

  • python3 -m unittest discover -s tests → 206 通过;cd web && npm test → 67 通过。
  • node web/scripts/project-calendar-entries.mjs --dry-run → local entries 410 (daily 277, note 133)、to project 0、up to date 410。