Skip to content

✅ spec 双源清账主单:基线 52 → 0(2026-08-03 收官)—— #4446 gate 落地后的偿还 worklist #4535

Description

@os-zhuang

✅ 已完成 —— packages/spec/dual-source-exports.baseline.json 现为 entries: []

52 → 0,历时两天、17 簇、17 个 PR。最后一簇 C7(#4741 → PR #4789)于 2026-08-03 07:5xZ 合并,门禁实报 0 accepted dual-source (baseline)

#4446 的 gate(#4506)守住「不再新增」;本单偿清了存量。这份账目从此只应是 0——任何新增都会被门禁在 PR 上拦下,不需要再开清账单。


问题是什么

同一个名字在两个入口解析到不同声明,消费者拿到哪个类型只取决于 import 路径 —— #4411 陷阱。两侧都不 .strict() 时,把一侧的文档粘到另一侧不报错,只静默剥掉全部外来键(ADR-0104 silent-strip 类)。52 条这样的名字曾同时存在于发布契约里。

最终账目

批次 条数 时间 说明
第一批 52 → 13 2026-08-02 ~ 08-03 A1/A2/A3/B/C1–C5/C8/C9/C11/C12/C14,共 39 条
第二批 13 → 0 2026-08-03 C6/C10/C13/C15/C16/C17/C7,共 13 条,维护者裁决全部纳入 v17 窗口

第二批逐簇(维护者 2026-08-03 裁决:13 条全入 v17)

名字 子 issue → PR 基线 路线
C6 Event #4658#4745 13 → 12 死侧删除:automation 侧是零引用 XState 信号孤儿,从未接线
C16 TenantPlan #4739#4752 12 → 10 ./cloud 5 值词表为唯一真源;system/provisioning.zod.ts 六 def 全族 + 两个 contracts 整文件退役(21 文件 +271/−939)。词表 3→5 拓宽如实写进 changeset
C13+C15 DataSyncConfig / ConflictResolution #4738#4760 10 → 6 整删 automation/sync.zod.ts(L1「Simple Sync」叙事层,17 导出名 / 8 def / 39 key,三仓零消费者);integration 侧改名 ConnectorConflictResolution;./ui 一字未动
C10 EnvironmentArtifact #4740#4767 6 → 3 收敛到线上 wire 形状(唯一 runtime 消费者 metadata/plugin.ts 已在解析的形状),./cloud 纯 re-export;v0 家族 16 名 / 9 def 退役。checksum 对象→字符串是 #4666 盲区内的类型变更,显式承载 + parse pin 钉住
C17 ActionLocationSchema #4737#4768 3 → 2 改名 studio 侧 ActionContributionLocationSchema + 补类型导出(顺带消掉 docs-import-surface 例外行);ui 侧一字未动
C7 PackageDependency #4741#4789 2 → 0 两侧键集完全不相交(声明形 vs 解析器形),改名 kernel 侧 ResolvedPackageDependency;4 key 全承接

沉淀下来的东西(比账目本身更值钱)

1. RENAMED_DEFS 承接表 —— def 改名的正规通道

packages/spec/scripts/lib/renamed-defs.ts,C9(PR #4695)首创、C12(PR #4710)加固。五条不变式,任一违反即红:

  1. 旧 def 名下每个 key 都必须在新 def 名下存在(否则是「披着改名外衣的删除」);
  2. target 必须被本次 build 产出;
  3. source 必须不再被产出 —— 两个都在是复制而非改名,而复制正是这张表绝不能洗白的 dual-source 形状;
  4. 两个 source 不许指向同一个 target(合并两个 def 是真变更);
  5. 链式改名(A → B → C)按名报错。

表内现有 6 条(C9 / C12 ×2 / C15 / C17 / C7)。def 改名一律走这条通道;真退役一律不许走(那张表的不变式正是「没有 key 离开契约」)。

2. #4650 删行自证门禁(PR #4726)—— 基线不能再手编

被删基线行必须在门禁内自证,merge-base 锚定(CI 不空转),三条合法路径:aged-out tombstone(≥2 majors)/ 从 24 个元数据根真 Zod 图 BFS 不可达 / 整 def 不再发出(归 manifest ratchet + check:api-surface 裁决)。另加 --check 对整文件的规范形字节比对(#4663 加固法),手编即红。

实证补充:整文件/整族删除走的都是路径 3,per-key 可达性判定不适用 —— 以门禁实跑输出为准,不要按静态推断写 PR。

3. 类型级 pin 的唯一有效形态(#4642 的解药)

tsconfig exclude 掉 test、vitest 未开 typecheck.enabled ⇒ 编译期条件类型 pin 空转。有效形态是:TypeScript compiler API 在 src/ 上做符号身份解析,从 package.json exports map 枚举全部入口(新入口无法逃逸),带防空转守卫(module symbol 必解析、表面非平凡),holders 用精确相等断言。

为什么必须精确相等:C17 与 C7 各自实证了一条对 dual-source 门禁全绿、但语义说谎的路线 —— 在错误的入口 re-export 同一声明。C7 更把它量化:用含 sabotage 的源码重建 dist 后实跑门禁,读数从 173 re-exported175,门禁结构性看不见。拦住它的是 pin 的精确相等断言,以及 RENAMED_DEFS 不变式 3 —— 后者更早在 build 阶段即拒绝。

4. 定级要据实,五种形状五种论证(不许跨簇照抄)

形状 定级 实例
FROM ≡ TO patch C11
def 改名 major,零元数据迁移 C9 / C12 / C17 / C7
已发布导出名移除 major(TS2305),零元数据迁移 C14 / C6 / C16 / C10
收敛致词表拓宽 major + changeset 显式警示 C16(3→5)
收敛致字段类型变更 major + 警示 + parse pin(#4666 盲区) C10(checksum 对象→字符串)

「major 且零迁移」对真退役不成立(#4651 那类有迁移)。谎报破坏与漏报破坏同样污染升级指南。

5. ⚠️ 入队纪律:同步 main 的三个坑(2026-08-03 全部实证)

a. 用 git merge,不用 rebase + force-push。 AGENTS.md §3 禁止 --force/--force-with-lease —— 仓规不因 PM 指令而失效(PM 曾下达错误指令,实施 agent 拒绝执行并改走 merge 是正确处置)。merge=os-regen 驱动正为此而设。

b. 静默回退有三处,第三处最隐蔽:

文件 归属 合并时表现 暴露方式
dual-source-exports.baseline.json 手管 ratchet 产生冲突 冲突标记
authorable-surface.json / docs-import-surface.baseline.json 手管 ratchet 产生冲突 冲突标记
json-schema.manifest.json merge driver 托管 无冲突标记,静默合并 只有 gen:schema 实跑暴露

C17 与 #4634 各踩一次:manifest 无声复活了 C10 蓄意删除的 9 个键 / 文本 auto-merge 复活了 46 行 authorable-surface。唯一可靠处置:四张 ratchet 一律 git checkout origin/main -- <file>只重施自己的那一处改动,再全量重跑生成器,零文本合并(#4783 做法,重放后与 main 逐字节一致)。

c. renamed-defs.ts 的合并冲突必须让两条改名条目并存,不能二选一。 C7 实证:承接表是累积台账,冲突里选一条就等于把别人已合并的改名从台账抹掉 —— 而不变式 1 只在本次 build 产出时校验,被抹掉的那条不会报红。这类静默丢失只能靠人判断拦住。

6. 判不出来就升级,不要猜

分析本身就是交付物。#4653 / #4658 / #4661 都是这样处理的。四仓零 importer ≠ 有死侧可删(#4653 实证);C7 同理:两侧各自被真实 schema 嵌入、都活着,所以是改名而非删除。


派生工单(留维护者排期,均非窗口限时)

门禁洞

# 内容 优先级建议
#4666 门禁对默认值 / 约束变更完全不可见 —— 改一个字符的 .default(),check:authorable-surface 全绿 最高:元数据即 API,默认值就是 API 的一部分。C8/C10/#4634 三次实证
#4725 manifest 删键是 #4650 那个洞上移一层
#4659 检查 (b) 按 leaf name 匹配 conversion surface,无关 conversion 即可蒙混 中(与 #4726 检查 (c) 同词表,一起修)
#4642 编译期条件类型 pin 空转 中(本单已给出有效形态,可作修复参考)
#4675 / #4676 生成物冲突 —— #4676 才是对合并队列有效的那个 排期待定

文档管道(双源的第三类受害者)

# 内容
#4696 build-docs.ts 按裸 schema 名做全局索引,同名跨 category 互相覆盖产出幽灵页 —— 本单四次实证(幽灵页随改名/删除自愈消失)
#4759 references/index.mdx 根索引被生成器「保留不重生」,烂尾无 gate
#4781 runtime-capabilities.mdx 整页描述已于 #3605 删除的 schema

其他

#4686(两份 RateLimitConfig 全仓零 runtime reader,C9 落地不使其失效)、#4782(测试 mock 写满 off-spec 键)、#4723

跨仓下游

# 内容 紧迫性
objectui#3235 C14 下游:packages/types 的 type-only 一行迁移(HttpMethodType as HttpMethod) 下个 rc 前不处理会 TS2305 断链
cloud#1027 C16 下游:手拷 5 值 plan 并集
cloud#1028 #4634 下游:TursoDriver 死位 + off-spec fallback 清理 低(不破编译,纯死重)

协作方式记录

本单第二批由 PM 会话 session_0176qgxgCXTJCUv4YFLtusP9 协调,os-dev agent 并行实施。两条关键协议:

  • 不并行入队(2026-08-02 三次生成物冲突返工的教训 → 08-03 收窄):def 文件/入口区域不相交的簇可并行实施,但入队严格串行(merge main + 四张 ratchet 取 main 版重施 + 重生成 + 复验后逐个进队)。
  • 认领 = assign + claim 评论(含 session ID 与分支名):所有 agent 共用一个 GitHub 身份,assignee 字段无法区分是谁的认领,开工前必须重读评论。

关联:#4411(先例)、#4446 / #4506(gate 本体)、#4650 / PR #4726(删行自证门禁)、#4663#4711 / PR #4724、ADR-0049、ADR-0059 §5、ADR-0087、ADR-0104、ADR-0112

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions