Skip to content

Commit 52a5de3

Browse files
committed
feat(spec)!: 双源 C5 收敛 — ActivationEventSchema 归 ./kernel 结构化形状,./studio 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
1 parent 21676eb commit 52a5de3

17 files changed

Lines changed: 344 additions & 51 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: 双源 C5 收敛 — `ActivationEventSchema``./kernel` 结构化形状,`./studio` re-export (#4653)
6+
7+
`ActivationEventSchema` 这个名字过去在两个入口解析到**两份不同的声明**,插件作者拿到哪套校验取决于他从哪个子路径 import(#4411 陷阱):
8+
9+
| 入口 | 声明 | 作者写的样子 |
10+
|:--|:--|:--|
11+
| `@objectstack/spec/kernel` | `z.object({ type: z.enum([...]), pattern: z.string() })` | `{ type: 'onCommand', pattern: 'my.cmd' }` |
12+
| `@objectstack/spec/studio` | `z.string()` | `'onCommand:my.cmd'` |
13+
14+
两侧都在作者面上(kernel 侧嵌在 `DynamicLoadRequest.activationEvents`,studio 侧嵌在 `StudioPluginManifest.activationEvents`,后者正是 `defineStudioPlugin` 的入参),所以没有"死侧"可删。v17 统一到**结构化形状**:`./studio` 现在 re-export `./kernel` 的那一份声明,平台只剩一套激活词表。
15+
16+
**为什么是结构化的那一侧赢。** 字符串那一侧更眼熟(照搬 VS Code),但它什么都不校验:`z.string()` 接受 `''``'banana'`,以及真正要命的 `'onMetadatType:flow'` —— 这个文件文档里列的词表(`*``onMetadataType:``onCommand:``onView:`)只活在散文里,拼错永远静默通过。结构化形状用 enum 在**创作时**就把触发器类型钉死,这才是声明它的意义。
17+
18+
## FROM → TO
19+
20+
`activationEvents` 的每一项从字符串变成对象。冒号前的段成为 `type`,冒号后的段成为 `pattern`:
21+
22+
```ts
23+
// FROM (v16 及以前,@objectstack/spec/studio)
24+
defineStudioPlugin({
25+
id: 'objectstack.flow-designer',
26+
name: 'Flow Designer',
27+
activationEvents: ['onMetadataType:flow'],
28+
});
29+
30+
// TO (v17+)
31+
defineStudioPlugin({
32+
id: 'objectstack.flow-designer',
33+
name: 'Flow Designer',
34+
activationEvents: [{ type: 'onMetadataType', pattern: 'flow' }],
35+
});
36+
```
37+
38+
逐条对照:
39+
40+
| FROM | TO |
41+
|:--|:--|
42+
| `'*'` | `{ type: 'onStartup', pattern: '*' }` |
43+
| `'onMetadataType:flow'` | `{ type: 'onMetadataType', pattern: 'flow' }` |
44+
| `'onCommand:myPlugin.doSomething'` | `{ type: 'onCommand', pattern: 'myPlugin.doSomething' }` |
45+
| `'onView:myPlugin.myPanel'` | `{ type: 'onView', pattern: 'myPlugin.myPanel' }` |
46+
47+
`StudioPluginManifest.activationEvents` 的默认值随之从 `['*']` 变为 `[{ type: 'onStartup', pattern: '*' }]``'*'` 没有拿到独立的 `type`:它一直就是"立即激活",而 kernel 侧的 `onStartup` 本来就是这个意思,再加一个枚举值只会造出两个同义词。
48+
49+
## 词表 = 两侧并集,没有能力被静默拿掉
50+
51+
enum 取**两侧 v17 前词表的并集**,共 9 个值:
52+
53+
|| 来源 |
54+
|:--|:--|
55+
| `onCommand` | kernel enum + studio 文档 `onCommand:myPlugin.doSomething` |
56+
| `onRoute` | kernel enum |
57+
| `onObject` | kernel enum |
58+
| `onEvent` | kernel enum |
59+
| `onService` | kernel enum |
60+
| `onSchedule` | kernel enum |
61+
| `onStartup` | kernel enum;同时是 studio `'*'` 的落点 |
62+
| `onMetadataType` | studio 文档/测试 `onMetadataType:object` —— kernel 原本没有 |
63+
| `onView` | studio 文档/测试 `onView:myPlugin.myPanel` —— kernel 原本没有 |
64+
65+
**未采纳**:cloud-v1 未发布的 marketplace runtime 里的 `priority``onInstall``onWebhook`。四仓无人读它们,而新增一个 declared-but-unenforced 的键正是 ADR-0049 在清的债 —— 等真有执行点再单独提。
66+
67+
## 迁移是手工的,但失败是响亮的
68+
69+
**没有随附 ADR-0087 conversion,因为写不出能跑到的那一个。** conversion 层(`applyConversions`)接在 `normalizeStackInput` 上,只走 stack 树;而 `StudioPluginManifestSchema``DynamicLoadRequestSchema` 都是**根 schema**,没有任何父 schema 嵌入它们(前者由 `defineStudioPlugin` 直接 parse,后者是运行时请求载荷),都不在 stack 里。伪造一个永远不会命中的 conversion 只会制造"已自动迁移"的假象。
70+
71+
手工迁移步骤:按上表把每个字符串改写成 `{ type, pattern }`**漏改会在 parse 处响亮失败** —— `StudioPluginManifestSchema``strictObject`,字符串遇到对象 schema 直接抛错,不存在静默吞掉或强制转换。
72+
73+
## 其它影响
74+
75+
- `@objectstack/spec/studio` 现在**额外导出** `ActivationEvent` 类型(此前只有 schema),与 `./kernel` 指向同一份声明。
76+
- `ActivationEventSchema``dual-source-exports.baseline.json` 移除,基线 22 → 21。
77+
- 零可作者化 key 消失、零 tombstone:kernel`ActivationEvent:type` / `:pattern` 原样存活,`studio/ActivationEvent` 侧新增 2 个 key(字符串没有 key,对象有),属 `gen:schema` 允许的**新增**

content/docs/plugins/development.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ export const manifest = defineStudioPlugin({
384384
name: 'Flow Designer',
385385
version: '2.0.0',
386386
description: 'Visual flow builder for automation workflows',
387-
activationEvents: ['onMetadataType:flow'],
387+
activationEvents: [{ type: 'onMetadataType', pattern: 'flow' }],
388388

389389
contributes: {
390390
metadataViewers: [{

content/docs/references/kernel/plugin-runtime.mdx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,27 @@ This protocol enables:
3838
## TypeScript Usage
3939

4040
```typescript
41-
import { DynamicLoadRequestSchema, DynamicPluginOperationSchema, DynamicPluginResultSchema, DynamicUnloadRequestSchema, PluginSourceSchema } from '@objectstack/spec/kernel';
42-
import type { DynamicLoadRequest, DynamicPluginOperation, DynamicPluginResult, DynamicUnloadRequest, PluginSource } from '@objectstack/spec/kernel';
41+
import { ActivationEventSchema, DynamicLoadRequestSchema, DynamicPluginOperationSchema, DynamicPluginResultSchema, DynamicUnloadRequestSchema, PluginSourceSchema } from '@objectstack/spec/kernel';
42+
import type { ActivationEvent, DynamicLoadRequest, DynamicPluginOperation, DynamicPluginResult, DynamicUnloadRequest, PluginSource } from '@objectstack/spec/kernel';
4343

4444
// Validate data
45-
const result = DynamicLoadRequestSchema.parse(data);
45+
const result = ActivationEventSchema.parse(data);
4646
```
4747

48+
---
49+
50+
## ActivationEvent
51+
52+
Lazy activation trigger for a dynamic plugin
53+
54+
### Properties
55+
56+
| Property | Type | Required | Description |
57+
| :--- | :--- | :--- | :--- |
58+
| **type** | `Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup' \| 'onMetadataType' \| 'onView'>` || Trigger type for lazy activation |
59+
| **pattern** | `string` || Match pattern for the activation trigger |
60+
61+
4862
---
4963

5064
## DynamicLoadRequest
@@ -57,7 +71,7 @@ Request to dynamically load a plugin at runtime
5771
| :--- | :--- | :--- | :--- |
5872
| **pluginId** | `string` || Unique plugin identifier |
5973
| **source** | `{ type: Enum<'npm' \| 'local' \| 'url' \| 'registry' \| 'git'>; location: string; version?: string; integrity?: string }` || Plugin source location for dynamic resolution |
60-
| **activationEvents** | `{ type: Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup'>; pattern: string }[]` | optional | Lazy activation triggers; if omitted plugin starts immediately |
74+
| **activationEvents** | `{ type: Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup' \| 'onMetadataType' \| 'onView'>; pattern: string }[]` | optional | Lazy activation triggers; if omitted plugin starts immediately |
6175
| **config** | `Record<string, any>` | optional | Runtime configuration overrides |
6276
| **priority** | `integer` || Loading priority (lower is higher) |
6377
| **sandbox** | `boolean` || Run in an isolated sandbox |

content/docs/references/kernel/plugin.mdx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,12 @@ These are the specialized plugin types common between Manifest (Package) and Plu
1616
## TypeScript Usage
1717

1818
```typescript
19-
import { ActivationEventSchema, PluginSchema } from '@objectstack/spec/kernel';
20-
import type { ActivationEvent } from '@objectstack/spec/kernel';
19+
import { PluginSchema } from '@objectstack/spec/kernel';
2120

2221
// Validate data
23-
const result = ActivationEventSchema.parse(data);
22+
const result = PluginSchema.parse(data);
2423
```
2524

26-
---
27-
28-
## ActivationEvent
29-
30-
Lazy activation trigger for a dynamic plugin
31-
32-
### Properties
33-
34-
| Property | Type | Required | Description |
35-
| :--- | :--- | :--- | :--- |
36-
| **type** | `Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup'>` || Trigger type for lazy activation |
37-
| **pattern** | `string` || Match pattern for the activation trigger |
38-
39-
4025
---
4126

4227
## Plugin

content/docs/references/studio/meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"action",
55
"flow-builder",
66
"object-designer",
7-
"plugin"
7+
"plugin",
8+
"plugin-runtime"
89
]
910
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Plugin Runtime
3+
description: Plugin Runtime protocol schemas
4+
---
5+
6+
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
7+
8+
## TypeScript Usage
9+
10+
```typescript
11+
import { ActivationEventSchema } from '@objectstack/spec/studio';
12+
import type { ActivationEvent } from '@objectstack/spec/studio';
13+
14+
// Validate data
15+
const result = ActivationEventSchema.parse(data);
16+
```
17+
18+
---
19+
20+
## ActivationEvent
21+
22+
Lazy activation trigger for a dynamic plugin
23+
24+
### Properties
25+
26+
| Property | Type | Required | Description |
27+
| :--- | :--- | :--- | :--- |
28+
| **type** | `Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup' \| 'onMetadataType' \| 'onView'>` || Trigger type for lazy activation |
29+
| **pattern** | `string` || Match pattern for the activation trigger |
30+
31+
32+
---
33+

content/docs/references/studio/plugin.mdx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ modes: ['preview', 'design', 'data'],
100100
## TypeScript Usage
101101

102102
```typescript
103-
import { ActionContributionSchema, ActivationEventSchema, CommandContributionSchema, MetadataIconContributionSchema, MetadataViewerContributionSchema, PanelContributionSchema, PanelLocationSchema, SidebarGroupContributionSchema, StudioPluginContributionsSchema, StudioPluginManifestSchema, ViewModeSchema } from '@objectstack/spec/studio';
103+
import { ActionContributionSchema, CommandContributionSchema, MetadataIconContributionSchema, MetadataViewerContributionSchema, PanelContributionSchema, PanelLocationSchema, SidebarGroupContributionSchema, StudioPluginContributionsSchema, StudioPluginManifestSchema, ViewModeSchema } from '@objectstack/spec/studio';
104104
import type { ActionContribution, CommandContribution, MetadataIconContribution, MetadataViewerContribution, PanelContribution, SidebarGroupContribution, StudioPluginContributions, StudioPluginManifest, ViewMode } from '@objectstack/spec/studio';
105105

106106
// Validate data
@@ -122,9 +122,6 @@ const result = ActionContributionSchema.parse(data);
122122
| **metadataTypes** | `string[]` || Applicable metadata types |
123123

124124

125-
---
126-
127-
128125
---
129126

130127
## CommandContribution
@@ -237,7 +234,7 @@ const result = ActionContributionSchema.parse(data);
237234
| **description** | `string` | optional | Plugin description |
238235
| **author** | `string` | optional | Author |
239236
| **contributes** | `{ metadataViewers: { id: string; metadataTypes: string[]; label: string; priority: number; … }[]; sidebarGroups: { key: string; label: string; icon?: string; metadataTypes: string[]; … }[]; actions: { id: string; label: string; icon?: string; location: Enum<'toolbar' \| 'contextMenu' \| 'commandPalette'>; … }[]; metadataIcons: { metadataType: string; label: string; icon: string }[]; … }` || |
240-
| **activationEvents** | `string[]` || |
237+
| **activationEvents** | `{ type: Enum<'onCommand' \| 'onRoute' \| 'onObject' \| 'onEvent' \| 'onService' \| 'onSchedule' \| 'onStartup' \| 'onMetadataType' \| 'onView'>; pattern: string }[]` || |
241238

242239

243240
---

packages/spec/PLUGIN_STANDARDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ Plugins can be loaded and unloaded at runtime **without restarting the kernel**:
162162

163163
- **`DynamicLoadRequestSchema`** — Load a plugin from `npm`, `local`, `url`, `registry`, or `git` sources with optional integrity verification
164164
- **`DynamicUnloadRequestSchema`** — Graceful/forceful/drain unload with dependency awareness (`cascade`, `warn`, or `block` dependents)
165-
- **`ActivationEventSchema`** — Lazy activation triggers: `onCommand`, `onRoute`, `onObject`, `onEvent`, `onService`, `onSchedule`, `onStartup`
165+
- **`ActivationEventSchema`** — Lazy activation triggers, shaped `{ type, pattern }`. Types: `onCommand`, `onRoute`, `onObject`, `onEvent`, `onService`, `onSchedule`, `onStartup`, `onMetadataType`, `onView`. Since v17 this is the platform's **single** activation vocabulary — `@objectstack/spec/studio` re-exports this exact declaration rather than carrying its own `z.string()` (#4653)
166166
- **`PluginDiscoveryConfigSchema`** — Runtime discovery from registries and local directories with polling and trust filtering
167167
- **`DynamicLoadingConfigSchema`** — Subsystem configuration: max dynamic plugins, default sandbox policy, allowed sources, integrity requirements
168168

packages/spec/api-surface.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4045,6 +4045,7 @@
40454045
"ActionContribution (type)",
40464046
"ActionContributionSchema (const)",
40474047
"ActionLocationSchema (const)",
4048+
"ActivationEvent (type)",
40484049
"ActivationEventSchema (const)",
40494050
"BUILT_IN_NODE_DESCRIPTORS (const)",
40504051
"CommandContribution (type)",

packages/spec/authorable-surface.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"description": "Ratchet of every AUTHORABLE key in the spec \u2014 what a metadata author may write, which for this platform IS the third-party API. Auto-updated on additions (commit the change). A key that disappears without a tombstone fails gen:schema, because these schemas are not .strict() and Zod would silently strip it. \"[RETIRED]\" marks a tombstoned key that still rejects with an upgrade prescription. See #3855, ADR-0059 \u00a75.",
2+
"description": "Ratchet of every AUTHORABLE key in the spec what a metadata author may write, which for this platform IS the third-party API. Auto-updated on additions (commit the change). A key that disappears without a tombstone fails gen:schema, because these schemas are not .strict() and Zod would silently strip it. \"[RETIRED]\" marks a tombstoned key that still rejects with an upgrade prescription. See #3855, ADR-0059 §5.",
33
"keys": [
44
"ai/AIModelConfig:maxTokens",
55
"ai/AIModelConfig:model",
@@ -5422,6 +5422,8 @@
54225422
"studio/ActionContribution:label",
54235423
"studio/ActionContribution:location",
54245424
"studio/ActionContribution:metadataTypes",
5425+
"studio/ActivationEvent:pattern",
5426+
"studio/ActivationEvent:type",
54255427
"studio/CommandContribution:icon",
54265428
"studio/CommandContribution:id",
54275429
"studio/CommandContribution:label",

0 commit comments

Comments
 (0)