Skip to content

feat(spec)!: 双源 C5 收敛 — ActivationEventSchema 归 ./kernel 结构化形状,./studio re-export (#4653) - #4662

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4653-activation-event-dual-source
Aug 2, 2026
Merged

feat(spec)!: 双源 C5 收敛 — ActivationEventSchema 归 ./kernel 结构化形状,./studio re-export (#4653)#4662
os-zhuang merged 1 commit into
mainfrom
claude/issue-4653-activation-event-dual-source

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#4653

按维护者在 #4653 的裁决走路线 A:保留 activationEvents,收敛到 ./kernel 的结构化 z.object({ type, pattern }),./studio 侧 re-export。基线 22 → 21


1. 三仓(实为四仓)import 语句级扫描结论

扫了 objectstack @ 21676eb5dcloud @ bce2bec99objectui @ f5728490c,外加 org code search 追出来的 cloud-v1 @ e4c2040b9

ActivationEventSchema / ActivationEvent 的 import 语句,packages/spec 之外四仓为零:

结果
cloud0 命中。该仓 @objectstack/spec/kernel 的 import 全是 ExecutionContext / PluginContext / PluginPermissions / ManifestSchema
objectui0 直接 import。只有 packages/types/src/index.ts:940,943 的整命名空间 export type * as Kernel / as Studio —— 命名空间隔离,两侧不打架
cloud-v10 import,但 apps/cloud/lib/marketplace/plugin-runtime.ts 自己另写了第三份形状
objectstack消费方只有两侧各自的父 schema、各自单测、生成文档

没有死侧可删,两个父 schema 都在作者面上(DynamicLoadRequest.activationEventsStudioPluginManifest.activationEvents,后者正是 defineStudioPlugin 的入参)。

2. 选定路线及为什么

裁决选 A。落地时确认了它成立的关键一点:结构化那一侧赢,不是因为它更常见,而是因为字符串那一侧什么都不校验。z.string() 接受 '''banana',以及真正要命的 'onMetadatType:flow' —— studio/plugin.zod.ts 文档里列的词表(*onMetadataType:onCommand:onView:)只活在散文里,拼错永远静默通过。enum 把触发器类型在创作时钉死。

词表 = 两侧并集(9 值),每个值的来源

来源
onCommandkernel enum + studio 文档 onCommand:myPlugin.doSomething
onRoutekernel enum
onObjectkernel enum
onEventkernel enum
onServicekernel enum
onSchedulekernel enum
onStartupkernel enum;同时是 studio '*' 的落点
onMetadataTypestudio 文档 plugin.zod.ts:281 + 测试 —— kernel 原本没有
onViewstudio 文档 plugin.zod.ts:283 + 测试 —— kernel 原本没有

未采纳 cloud-v1 的 priority / onInstall / onWebhook(裁决第 2 条):四仓无人读,新增一个 declared-but-unenforced 键正是 ADR-0049 在清的债。

['*'] 的去处

落到 { type: 'onStartup', pattern: '*' },即维护者的倾向,没有给 eager 独立 type。理由:'*' 一直就是「立即激活」,而 kernel 侧 onStartup 的原文就是 "Activate immediately on kernel startup" —— 再加一个枚举值只会造出两个同义词。StudioPluginManifest.activationEvents.default() 同步改成该值,并有测试钉住。

3. 可作者化 key:零消失,零 tombstone ✅

gen:schemaauthorable-surface.json 的实际改动只有新增:

+ "studio/ActivationEvent:pattern",
+ "studio/ActivationEvent:type",

kernel 的 ActivationEvent:type / :pattern 原样存活;studio 侧原本 0 key(字符串没有 key),收敛后有了 2 个 —— 属 gen:schema 允许的新增重写。check:authorable-surface 自己确认了零 vanish。

未手编 authorable-surface.json(#4650)。唯一另一处非新增的改动是该文件 description 行的 → 字面 ,那是生成器自身的规范化输出(顺带说明该文件此前曾被手工改过 —— 已按军规立案,见下)。

另有两条 ratchet 按各自门禁的明确指令删行:dual-source-exports.baseline.json 删掉 C5 那行(22 → 21),docs-import-surface.baseline.json 删掉 studio/ActivationEvent — no type export(144 → 143,./studio 现在确实导出该类型了)。两者都是门禁主动点名要求删的 stale 行,与 authorable 基线不是一回事。

4. conversion walker 可达性:已验证,不可达 —— 故不写 conversion

裁决第 4 条要求查实而非假装。证据:

  • applyConversions 接在 normalizeStackInput 上,只走 stack 树(conversions/apply.ts:6-11)。
  • StudioPluginManifestSchema没有任何父 schema 嵌入:全仓引用只有它自己的声明、studio/index.ts 的导出、defineStudioPlugin.parse() 和测试。它是根 schema,由作者直接 parse,从不进 stack。
  • DynamicLoadRequestSchema 同样零父嵌入 —— 是运行时请求载荷。
  • stack 的 plugins[] 装的是 ObjectStack 内核插件实例(metadata-collection.zod.ts:72 明写 plugins / devPlugins "not named metadata schemas"),不是 Studio 插件清单。

⇒ 任何 mapCollection / mapFlowNodes 都够不到这两处。不伪造跑不到的 conversion,改为在 major changeset 写清手工迁移步骤。字符串遇到对象 schema 在 parse 处响亮失败(StudioPluginManifestSchema 还是 strictObject),这是可接受的失败模式,并已单独钉测试。

5. Sabotage 验证(#4642:本包编译期 pin 空转)

新 pin 是运行时模块命名空间断言(src/studio/plugin.test.ts 末尾),import 两个真实入口 ../kernel/index / ../studio/index 并断言符号同一性 —— 与 check:dual-source-exports 的判据(alias 解析后的符号身份)一致。三组 sabotage 全部验证其真的会红:

Sabotage 1 —— studio 重新声明一份「形状完全相同」的 schema(shape 测试抓不到的那种):

 × both entry points export the very same declaration 1292ms
AssertionError: expected [Function] to be [Function] // Object.is equality
Tests 1 failed | 39 passed (40)

只有身份断言红,其余 39 条全绿 —— 证明这个 pin 不是在重复测形状。

Sabotage 2 —— studio 侧完整回退到 v17 前的 z.string():

 × should accept valid activation events
× should reject the pre-v17 bare-string form
× should accept minimal manifest with defaults
× should accept full manifest
× rejects a manifest still carrying the pre-v17 string activation events
× should return a parsed manifest
× both entry points export the very same declaration
× the shared declaration is the structured kernel form on BOTH entries
× the trigger vocabulary is the union of both pre-v17 vocabularies
Tests 9 failed | 31 passed (40)

Sabotage 3 —— 从 enum 里拿掉 onMetadataType(即静默删掉一个 studio 作者在用的能力):

 × the trigger vocabulary is the union of both pre-v17 vocabularies
AssertionError: 'onMetadataType' must stay in the vocabulary: expected [Function] to not throw
Tests 5 failed | 53 passed (58)

三次 sabotage 后均已还原,还原态 58/58 绿。

6. 全部门禁结果

packages/spec 下,前台跑、共享 flock 串行、--max-old-space-size=4096:

门禁结果
build
check:dual-source-exports4313 names across 16 entry points — 162 re-exported (single declaration), **21 accepted dual-source** (baseline)
check:generated(8 项)✅ 全绿,含 check:authorable-surface / check:api-surface / check:docs
testTest Files 291 passed (291) / Tests 7285 passed (7285)
check:liveness
check:strictness-ledger67 file(s) across 5 triaged director(ies) — site counts match
check:empty-state
check:variant-docs
check:exported-any
check:skill-examples202 prose examples type-check(content/docs/**os:check,即改过的 development.mdx 例子是真的过了编译)
全仓 pnpm typecheckTasks: 122 successful, 122 total

严格性台账未动:本 PR 删的是一个 z.string()lazySchema,不是 z.object( 站点,check:strictness-ledger 自证站点数不变。

7. 文档

  • content/docs/plugins/development.mdx:387os:check 例子改为结构化形式(由 check:skill-examples 实际编译验证)。
  • packages/spec/PLUGIN_STANDARDS.md:165 词表更新。
  • 生成侧(gen:docs)按 docs-drift 提示全量重生成,顺带修好一个既有缺陷:kernel/ActivationEvent 此前被生成到错误的 reference 页 kernel/plugin.mdx,现在回到 kernel/plugin-runtime.mdx(与 C4 修的同类问题);studio/ActivationEvent 则落到新的 studio/plugin-runtime.mdx
  • ⛔ 未碰 content/docs/releases/

8. Changeset

.changeset/converge-activation-event-schema.md,@objectstack/specmajor,含逐条 FROM → TO 对照表、词表来源表、以及「为什么没有 conversion + 手工迁移步骤」。

9. 范围外发现(已立案,未在本 PR 修)


🤖 Generated with Claude Code

https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL


Generated by Claude Code


Generated by Claude Code

…io re-export (#4653)
`ActivationEventSchema` 过去在两个入口解析到两份不同声明,插件作者拿到
哪套校验取决于 import 路径(#4411 陷阱):`./kernel` 是结构化的
`z.object({ type, pattern })`,`./studio` 是裸 `z.string()`。
四仓(objectstack / cloud / cloud-v1 / objectui)import 语句级扫描:
spec 之外零消费方,两侧都只被自己的父 schema 引用,且两个父 schema 都在
作者面上 —— 没有死侧可删。按维护者裁决走收敛:`./studio` 现在 re-export
`./kernel` 的那一份声明。
结构化的一侧赢,因为字符串那一侧什么都不校验:`z.string()` 接受
`'onMetadatType:flow'` 及一切拼写错误,文件里记的词表只活在散文里。
enum 取两侧词表并集(kernel 7 值 + studio 的 onMetadataType / onView),
没有能力被静默拿掉;`'*'` 落到 `{ type:'onStartup', pattern:'*' }`。
未采纳 cloud-v1 的 priority / onInstall / onWebhook —— 四仓无人读,
新增 declared-but-unenforced 键正是 ADR-0049 在清的债。
零可作者化 key 消失、零 tombstone:kernel 的 2 个 key 原样存活,
studio 侧新增 2 个(字符串无 key,对象有),属 gen:schema 允许的新增。
未手编 authorable-surface.json。基线 22 → 21。
无 ADR-0087 conversion:conversion 层接在 normalizeStackInput 上只走 stack 树,
而两个父 schema 都是根 schema,不在 stack 里 —— 伪造一个跑不到的 conversion
只会制造已自动迁移的假象。迁移手工进行,漏改在 parse 处响亮失败。
回归 pin 用运行时模块命名空间断言(#4642:本包编译期 pin 空转),
三组 sabotage 已验证其真的会红。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
@vercel

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 2, 2026 2:59pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Aug 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 16:12
@os-zhuang
os-zhuang added this pull request to the merge queueAug 2, 2026
Merged via the queue into main with commit 65ca83aAug 2, 2026
23 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4653-activation-event-dual-source branch August 2, 2026 16:24
os-zhuang pushed a commit that referenced this pull request Aug 2, 2026
生成物冲突以三路集合合并解决(等价于重新生成),手写登记表以
base→ours 的 hunk 打到 main 版上,双方条目均保留:
- dual-source-exports.baseline.json: 22 → 19(C8 去 RetryPolicy ×2,
main 的 #4662 去 ActivationEventSchema ×1)
- conversions/registry.ts: retryPolicyConverged 与 main 的
objectManagedBySystemToSystemData 同时注册
- migrations/registry.ts: job-retry-policy-constraints-tightened 保留
- protocol-upgrade-guide.md: 两侧表格行都保留
未跑本地全套门禁 —— 由 CI 的 check:generated 验证生成物确实等于
重新生成的结果,这是与维护者商定的快路径(main 上 spec PR 密度使
本地十几分钟的门禁跑完即过期)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 3, 2026
… 的区分说明 (objectstack-ai#4653) (objectstack-ai#4679)
PR objectstack-ai#4662 合并时,这段说明还在未提交状态(worktree 被清理时一并丢失),
故补一个 changeset-only 的跟进。无代码改动。
同窗口的 objectstack-ai#4664 退休了 `app.contextSelectors[].placement`,而它的退休说明
里写着「`location` 曾是 `placement` 的别名」。Studio 插件的面板贡献点恰好
也有一个 `location` 键(`studio/PanelContribution.location`),两者在不同
schema 上、取值域不同、互不相关 —— 但对着 v17 release notes 逐条读的作者
很容易把两件事连起来,以为 `contributes.panels[].location` 也要改。
changeset 里主动写清这个区分,挡掉误解;同时记下另外四个退休键与 objectstack-ai#4668
与 `activationEvents` 均无语义交叉。
Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 3, 2026
…elves (objectstack-ai#4650) (objectstack-ai#4726)
Check (a) reads authorable-surface.json from the commit under check, so
hand-deleting a baseline line deleted the evidence it runs on (objectstack-ai#4638,
objectstack-ai#4643 landed exactly that way; objectstack-ai#4662 proved the file was hand-edited).
gen:schema / check:authorable-surface now add check (c): every key
present at the merge base with origin/main but absent from this build
must carry one of three in-gate proofs —
1. aged-out tombstone: base entry [RETIRED] + an ADR-0087
conversion/migration registered >= 2 majors ago;
2. def not reachable from the metadata-type roots (2026-08-02 ruling):
BFS over the build's in-memory Zod graph from
BUILTIN_METADATA_TYPE_SCHEMAS + EXTRA_METADATA_TYPE_SCHEMAS, with
derived-clone bridging so .refine()/.extend() copies keep their
originals protected; waives ONLY this file's tombstone requirement;
3. whole def no longer emitted (manifest ratchet / api-surface
jurisdiction).
Anchoring on the merge base (not HEAD) keeps the check alive in CI,
where HEAD is the PR's own commit and a HEAD-relative diff is always
empty. --check further rejects any byte of the file that is not the
generator's output (objectstack-ai#4662 description drift class); write mode
regenerates it. Checks (a0)/(a)/(b) unchanged and pinned by tests.
Fixesobjectstack-ai#4650
Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 双源清账 C5:ActivationEventSchema(./kernel ≠ ./studio)—— 1 条

2 participants

@os-zhuang@claude