Skip to content

spec 双源清账 C6:EventSchema(./automation ≠ ./kernel)—— 1 条 #4658

Closed

Description

@os-zhuang

#4535 的 C6 簇。基线行:

EventSchema — [./automation (const)] ≠ [./kernel (const)]

可作者化 key:automation 2 / kernel 4(共 6)。

立单判断(开工请自行复核,不要采信)

主单把本簇标为「名字过泛,大概率该改名而非收敛」—— Event 是全仓最容易撞的裸名之一,两侧很可能根本是两个概念(自动化流程事件 vs 内核生命周期事件),而不是同一概念的两份声明。若确是两个概念,收敛是错的,改名才对。

参考 C5(#4653)刚证实的形态:四仓零 importer ≠ 可以死删 —— 两侧都只被各自父 schema 引用、两个父都在作者面上时,没有死侧。先把这一步做扎实。

⚠️ 适用纪律(#4535「v17 重切」§1–§3,开工前必读)

  1. 默认路线是收敛 + re-export(不删 key ⇒ 不需要 tombstone);仅在必须丢键时走 ADR-0087(retiredKey() + D2 conversion + D3 chain step + major changeset)。不要照抄 C1–C4 的死删路线。
  2. ⛔ 禁止手编 packages/spec/authorable-surface.json 绕过 vanish 检查(authorable-surface 的 tombstone 门禁可被手编基线绕过 —— 删掉基线行就删掉了证据(#4638 / #4643 已两次这样过绿) #4650:删基线行 = 删证据,门禁不拦)。gen:schema 只允许因新增 key 重写该文件。
  3. 改名不省 tombstone —— key 记作 ${defKey}:${name},改名 ⇒ 旧 defKey 下所有 key vanish ⇒ gen:schema exit 1。例外:若某侧可作者化 key 数为 0(如 C5 的 studio 侧),改那一侧零成本。先查两侧各自的 key 数再选改哪边。
  4. 回归 pin 用运行时模块命名空间断言,不要用编译期条件类型(packages/spec 的「编译期 pin」是失效的:tsconfig 排除了 *.test.ts,vitest 也不做类型检查 #4642:packages/spec 的 tsconfig exclude 掉 **/*.test.ts、vitest 未开 typecheck.enabled,编译期 pin 空转);且必须做 sabotage 验证并在 PR 里贴出结果。

判不出来就升级,不要猜

若「收敛 / 改名 / 退休」的选择取决于维护者对协议的意图(C5 就是这样:activationEvents 四仓零读取方,先要定去留才能定编码),停下来立单升级,把两轴分析写清,不要替维护者选。分析本身就是交付物。

验收

  • 基线删掉上面 1 行,22 → 21(若 C5 先落则相应顺延),只减不增。
  • 全绿:build、check:dual-source-exports、check:generated、test,加源码审计组 check:liveness / check:strictness-ledger / check:empty-state / check:variant-docs / check:exported-any / check:skill-examples,以及全仓 pnpm typecheck。
  • 删 zod 形状则同步 docs/audits/2026-07-unknown-key-strictness-ledger.md(C3 教训)。
  • changeset 一份,@objectstack/spec major,含 FROM → TO。
  • ⛔ 不要碰 content/docs/releases/。

关联:#4535(主单 + 手册)、#4650(基线手编漏洞)、#4642(pin 空转)、#4653 / #4657(C5 的升级先例)

Activity

  1. self-assigned this
    on Aug 2, 2026
  2. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    🤖 Claiming this issue.

    • Session ID: session_01M9uWvoEp9CoLzYjNExj9sL
    • Branch: claude/issue-4658-event-schema-dual-source
    • Worktree: ../objectstack-4658

    计划:先做四仓(objectstack + cloud + cloud-v1 + objectui)import 语句级扫描 + 两侧形状 diff,判定「同一概念 vs 两个概念」,再按 #4535「v17 重切」§1–§3 选路线(默认收敛 + re-export,非死删;两个概念则改名)。不会手编 authorable-surface.json(§2 / #4650),回归 pin 用运行时模块命名空间断言 + sabotage 验证(#4642)。判不出来会按 C5(#4653)的先例停下来升级而不是猜。

    若有更早的其他 session 已认领,请回复,我会立即退出。


    Generated by Claude Code

  3. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    🛑 停在裁决点:本簇存在机械死锁,未写任何代码

    session session_01M9uWvoEp9CoLzYjNExj9sL。四仓扫描 + 两侧形状 diff 已做完,并用两组实验把死锁跑出来了(下附真实输出)。结论:立单的判断(「两个概念,该改名而非收敛」)成立,但改名这条路在当前门禁下走不通,且不存在既关掉基线行、又不让可作者化 key 消失的路线 —— 唯一的例外是「收敛」,而收敛在本簇是事实错误。按军规不猜,交回裁决。


    1. 四仓 import 语句级扫描

    扫了 objectstack(worktree @ 21676eb5d)、cloud @ bce2bec、cloud-v1 @ e4c2040、objectui @ f572849。

    EventSchema 在 packages/spec 之外,四仓 import 为零。 三个兄弟仓连裸名 EventSchema 都是 0 命中(含多行 import 块的 multiline 扫描)。objectui 唯一的暴露是 packages/types/src/index.ts:937,940 的 export type * as Automation / export type * as Kernel —— 命名空间隔离,两侧不打架(与 C5 同形)。

    本仓消费方:

    侧 消费方
    automation 无。零引用。
    kernel kernel/events/dlq.zod.ts:24,71(嵌两次)、kernel/events.test.ts

    2. 两侧形状 diff:键集完全不相交

    automation/state-machine.zod.ts:55
      z.object({ type:   z.string()  // 'APPROVE' | 'REJECT' | 'Submit'
                 schema: z.record(z.string(), z.unknown()).optional() })
    
    kernel/events/core.zod.ts:111
      z.object({ id:       z.string().optional(),
                 name:     EventNameSchema,   // 点号命名空间 user.created / order.paid
                 payload:  z.unknown(),
                 metadata: EventMetadataSchema })
    

    不是宽窄之差,是两个概念:

    • automation 侧是事件定义(signal 声明) —— 「这台状态机接受哪些事件」。判别键 type,SCREAMING_CASE 词表。
    • kernel 侧是事件实例(信封) —— 一条已发出的事件。判别键 name,点号命名空间;还带 payload / metadata。

    零键重叠。而且 kernel 自己已经区分了这两件事:同文件 core.zod.ts:93 的 EventTypeDefinitionSchema(name / version / schema / deprecated / tags)才是 kernel 侧的「事件定义」。automation 的 EventSchema 语义上对应的是 kernel 的 EventTypeDefinition,而不是 kernel 的 Event。

    ⇒ 收敛是错的。立单判断成立。

    3. 但 automation 侧不是「活的另一侧」,是孤儿声明

    StateMachineSchema(同文件 :130)从不引用 EventSchema —— 状态机里事件类型是 on: 的记录键(z.record(z.string(), ...)),纯字符串:

    on: z.record(z.string(), z.union([z.string(), TransitionSchema, z.array(TransitionSchema)]))
    

    EventSchema 在 src/automation/ 里的出现次数:1 次,就是它自己的定义行。没有任何父 schema 嵌它,没有任何模块 import 它。它进 authorable-surface.json 的唯一原因是 automation/index.ts:26 的 export * from './state-machine.zod' 把它带进了 Automation 命名空间,而 build-schemas.ts 把每一个导出的 ZodType 都发成 def(不问是否被嵌入)。

    这一点与 C5 不同,不能照搬 C5 的「零 importer ≠ 可死删」:C5 是两侧都被各自父 schema 引用;本簇 automation 侧连父 schema 都没有。这里确实有死侧。

    补充一层证据 —— 两侧都够不到作者面:check:liveness 是 registry-rooted(从 BUILTIN_METADATA_TYPE_SCHEMAS 走)。automation/Event 零引用当然够不到;kernel/Event 只经 DLQ 形状,而 DLQ 只在测试里被 parse,不在任何 metadata type 下。两侧的 authorable-surface.json 条目都是过度收集的产物(#4535 §2 已承认该文件连 REST 信封字段都记)。

    可作者化 key 数复核 —— 与立单一致:automation 2 / kernel 4。

    automation/Event:schema     kernel/Event:id
    automation/Event:type       kernel/Event:metadata
                                kernel/Event:name
                                kernel/Event:payload
    

    4. 死锁:改名 / 删除 / 收敛,三条路互斥

    实验一 —— 删掉 automation 的孤儿(改名同理,defKey 一样整体位移):

    $ pnpm check:authorable-surface
    ❌ 2 authorable key(s) disappeared from the contract:
         - automation/Event:schema
         - automation/Event:type
    

    ⇒ 立单 §3 的「若某侧可作者化 key 数为 0 则改那侧零成本」的例外在本簇不成立 —— automation 侧是 2 不是 0,C5 的便宜路子这里没有。改 kernel 侧更贵(4 个 key,且改的是活的那侧)。

    唯一合法补救是 tombstone —— 但 tombstone 必须挂在 automation/Event 这个 defKey 上,即 ./automation 必须继续导出一个名字剥掉 Schema 后等于 Event 的符号。而 check-dual-source-exports.ts 判定的是符号同一性(「judged by symbol identity」),一个 tombstone 版的 automation/EventSchema 仍是与 kernel 不同的声明 ⇒ 基线行照报,22 → 22,本簇关不掉。

    实验二 —— 把 automation 两个 key 改成 retiredKey():

    $ pnpm check:authorable-surface
    ❌ 1 key(s) were tombstoned with no registered migration:
         - automation/Event:schema
    

    检查 (a) 确实放行(无 vanish),检查 (b) 只要求补一条 surface 以 .schema 结尾的 D2 conversion。注:automation/Event:type 静默通过是门禁的 bug(叶名匹配撞上无关的 flow.node.type),已按军规立案 #4659(unassigned,未在本单动手)。

    ⇒ 死锁成立:「关掉基线行」与「不让可作者化 key 消失」在本簇不可兼得,除非收敛 —— 而收敛把两个不相干的概念声明成一个。

    5. 三条路线,两轴分析

    A. 删掉 automation 的孤儿声明(工程上正确的那条)

    B. 收敛:./automation re-export kernel 的,把 type/schema 两块墓碑挂到 kernel 的 EventSchema 上

    • 关基线行:✅(re-export 不被报)。
    • 合规:✅ 零 vanish,配 D2 conversion + D3 chain step + major changeset,规则上完全走得通。这是唯一一条今天就能全绿收工的路。
    • 长期健全性:最差。它把「状态机信号定义」声明成「事件总线信封」—— 两者零键重叠,这是事实错误。代价还会外溢:kernel/Event:type / kernel/Event:schema 会作为新增基线行进 authorable-surface.json,即在 kernel 合同里凭空写下两段从未发生过的历史,让以后每个读到这两块墓碑的人被误导。这是典型的「为了门禁变绿而牺牲合同真实性」的 workaround。
    • 抗 AI 写错:有一点真实收益 —— type 是事件信封上最容易被 AI 写错的键(业界惯例就是 type 作判别符),一块指向 name 的墓碑确实挡得住。但这个收益可以单独、诚实地拿到(直接给 kernel 的 Event 加一条 type → name 的 UNKNOWN_KEY_GUIDANCE / 墓碑),不需要为此把两个概念合并。schema → payload 则纯属牵强(一个是声明的形状,一个是实际数据)。
    • 另有一个更差的变体(合并后 6 个 key 全部保留为 optional)—— 会产出一个同时有 type 和 name、同时有 schema 和 payload 的四不像形状,正是「消费侧宽容」的反面教材,建议直接排除。

    C. 原地 tombstone,基线行下轮再关

    • 现在给 automation 两个 key 挂墓碑 + D2/D3 + major changeset,保留碰撞;约两个 major 后墓碑老化,连导出一起删,基线行随之消失。
    • 关基线行:❌ 本轮 22 → 22,不满足本单验收。
    • 长期健全性:诚实,不预支任何裁决,不写假话。但债留着,而且要为没人能写的 key 花掉一条 ADR-0087 conversion 的预算。
    • 抗 AI 写错:中性。

    6. 我的建议

    推荐 A(删孤儿),前提是维护者愿意批一个窄例外;否则退 C。B 建议否决。

    两轴在此不冲突,但都不指向那条唯一能今天全绿的路:

    • 长期健全性:只有 A 让协议停在它该有的形状 —— 一个 Event,残留物删除。B 是用「在合同里写两条假历史 + 把两个不相干概念声明成一个」换一个绿灯,这正是纪律里点名的 workaround;它的长期成本会由以后每个读 kernel/Event 墓碑的人分期偿还。C 诚实但留债。
    • 抗 AI 写错:这一轴在本簇几乎不区分路线,因为两侧都不在作者面上 —— 今天没有任何 AI 生成的元数据会被这两个 schema 校验。也正因如此,A 的 vanish 是形式上的而非实质上的:门禁在保护一个空集。B 唯一的真实抗错收益(type → name)可以脱离 B 单独取得。

    所以请裁两件事:

    1. authorable-surface.json 的删行禁令,是否容许一个窄例外:「从 BUILTIN_METADATA_TYPE_SCHEMAS 不可达的 def(可机器判定),其 key 可随 def 一并出基线」?批 ⇒ 本单走 A,并把该判定固化成 authorable-surface 的 tombstone 门禁可被手编基线绕过 —— 删掉基线行就删掉了证据(#4638 / #4643 已两次这样过绿) #4650 的一部分,免得下一簇再撞。不批 ⇒ 本单走 C,基线行本轮不减,C6 从 v17 worklist 移出。
    2. automation 的 XState 式 EventSchema 是删还是救? 删 ⇒ A;想保留显式事件声明 ⇒ A′(改名 + 真的嵌进 StateMachineSchema),门禁代价相同。

    顺带一提:StateMachineSchema 本身在 ai/agent.zod.ts:197 上挂着 [EXPERIMENTAL — not enforced] ... Parsed but no runtime consumer yet (liveness #1878/#1893),而 ADR-0020 说记录状态机的真实作者面是 object.validations[] 里的 StateMachineValidationSchema(完全不同的扁平形状)。若这一族整体要按 ADR-0049 处置,本单更不该先替它挑编码 —— 与 C5/#4657 同形。

    裁决给到我就继续。分支 claude/issue-4658-event-schema-dual-source 无 commit,worktree 已清理,authorable-surface.json 与基线文件均未改动。


    Generated by Claude Code

  4. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁决:路线 A —— 但执行顺延 v18

    2026-08-02 裁决 A:在 #4650 里加一个可机检的窄例外(证明不从 BUILTIN_METADATA_TYPE_SCHEMAS 可达的 def,允许删其基线行),据此删掉 ./automation 的 EventSchema 孤儿,基线 22 → 21。

    采纳理由与升级分析一致:两侧键集完全不相交(XState 信号定义 {type, schema} vs 事件总线信封 {id, name, payload, metadata}),收敛等于往合同里写两句假话 —— 既宣称状态机信号就是事件信封,又向 kernel 合同凭空新增 type/schema 两行伪造历史。B 已否决。

    但本簇执行顺延 v18。 同日 v17 范围按可达性收口到 4 簇(C8 / C9 / C11 / C12),Event 两侧均不在元数据文档管道上,不占发布窗口。备选 A′(改名 StateMachineEventSchema 并真的嵌进 StateMachineSchema,救活孤儿而非删除)保留为实施时的选项。

    前置:#4650 的窄例外未落地前,本簇无法诚实收口 —— 现在删基线行仍然是「删证据」。

    你另外发现的 #4659(检查 (b) 按 leaf name 匹配 conversion surface,automation/Event:type 靠不相干的 flow.node.type 蒙混过关)已记入主单 §2,与 #4650 同族。两个发现都很扎实,谢谢。


    Generated by Claude Code

  5. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    🔒 认领(双源第二批首发 · 实施 B 线,与 #3207 并行——文件区零交集,入队串行按 #4535 新协议)

    • 会话:session_0176qgxgCXTJCUv4YFLtusP9
    • 分支:claude/issue-4658-event-dual-source
    • 路线:A(已裁决)——删 automation 侧孤儿 EventSchema,基线 13→12
    • 窗口:.changeset/pre.json 实测仍 mode: pre(2026-08-03 01:15Z)

    Generated by Claude Code

  6. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    📦 路线 A 已实施,draft PR:#4745(会话 session_0176qgxgCXTJCUv4YFLtusP9,分支 claude/issue-4658-event-dual-source)。


    Generated by Claude Code

  7. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    ✅ 验收通过(ACCEPT) — PR #4745 已转正并开 auto-merge。双源基线 13 → 12,第二批首单落地在望。

    对实际 diff(11 文件)逐项核对的结论:

    合并后 #4535 勾 C6;B 线按排程接 C13+C15(#4738)——同 automation 区,正好等本单落地后起飞。


    Generated by Claude Code

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