Skip to content

Commit 2a58532

Browse files
committed
chore(spec): 合并 main(#4831/#4657)后整体重生成 ADR-0087 生成物
os-regen 合并驱动对 spec-changes.json / protocol-upgrade-guide.md 不做文本合并, 本次合并零冲突标记地只保留了本分支一侧,静默吞掉 #4657 的条目(实测: origin/main 的 spec-changes.json 含 4 条 activationEvents 条目,合并后为 0)。 按协议 git checkout origin/main -- <生成物> 后从合并后的源整体重新生成: - spec-changes.json:现同时含 #4657 的 activationEvents 4 条与本单 batch-row-result-schema-shape 2 条 - protocol-upgrade-guide.md:同时含 plugin-activation-events-retired 与 batch-row-result-schema-shape - 四张 ratchet(api-surface / api-surface-signatures / authorable-surface / json-schema.manifest)与 origin/main 逐字节相同 —— 本单不增删任何导出 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
1 parent e6d64d1 commit 2a58532

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

docs/protocol-upgrade-guide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ The object capability block closes out the same ADR-0049 pass: `enable.trash` an
182182

183183
The same enforce-or-remove pass retires the `RestServerConfig.openApi31` block (#4579): `OpenApi31ExtensionsSchema` (`webhooks` / `callbacks` / `jsonSchemaDialect` / `pathItemReferences`) with `OpenApiWebhookEventSchema` and `CallbackSchema` under it. Declared-but-unenforced end to end: the REST server's `normalizeConfig` forwards only `api`/`crud`/`metadata`/`batch`/`routes`, the served /openapi.json is the pre-generated contract enriched with the live server URL and registered objects, and `gen:openapi` never read a webhook or callback — so a definition authored under `openApi31.webhooks` never appeared in any served document, and zero import-level consumers existed across objectstack / cloud / objectui. `RestServerConfig` is plugin TS configuration (the REST plugin constructor / `plugin-hono-server` `restConfig`), never a stored metadata shape: the stack tree's own `api` block declares only its four scoping/auth knobs, so no `sys_metadata` row can carry `openApi31` and there is no source for the chain to rewrite — one semantic TODO for config authors rather than a stack conversion, the `validateOnly` shape. The key itself is tombstoned (the schema is not `.strict()`; a plain delete would strip it silently), and a config-driven webhooks/callbacks synthesis, if ever wanted, returns via the enforce route of ADR-0049 through a new ADR.
184184

185+
The same pass closes `activationEvents` (#4657): both keys that carried it — `DynamicLoadRequest.activationEvents` on the kernel side and `StudioPluginManifest.activationEvents` on the studio side — declared lazy plugin activation ("plugins remain dormant until an activation event fires") that no runtime in any repo ever implemented: every plugin has always activated immediately on load/registration, and cloud-v1's own ROADMAP recorded the capability as unimplemented, planned for v0.4.0. #4653 had just converged the two `ActivationEventSchema` declarations onto one structured `{ type, pattern }` vocabulary in this same unreleased major; with the maintainer's enforce-or-remove ruling landing on REMOVE, that converged vocabulary retires before ever shipping — composed across the two changes, a v16 author simply deletes the key in whichever form they carried. Neither parent is stored metadata (`StudioPluginManifest` is TS configuration parsed by `defineStudioPlugin`; `DynamicLoadRequest` is a runtime request shape with no caller in any repo), so there is no source for the chain to rewrite — one semantic TODO, the `validateOnly` shape. The kernel key is tombstoned (its schema is not `.strict()`; a plain delete would strip it silently), the studio key is rejected by the strict manifest parse with its own guidance prescription, and the orphaned `ActivationEventSchema` def is removed with them. Behaviour is byte-identical: eager activation was always the only behaviour.
186+
185187
### Mechanical (applied for you)
186188

187189
| Conversion | Surface | Change | Load window |
@@ -279,6 +281,9 @@ The same enforce-or-remove pass retires the `RestServerConfig.openApi31` block (
279281
- **`driver-capabilities-inert-bits-removed`** — `data.DriverCapabilities.create / data.DriverCapabilities.read / data.DriverCapabilities.update / data.DriverCapabilities.delete / data.DriverCapabilities.bulkCreate / data.DriverCapabilities.bulkUpdate / data.DriverCapabilities.bulkDelete / data.DriverCapabilities.transactions / data.DriverCapabilities.savepoints / data.DriverCapabilities.isolationLevels / data.DriverCapabilities.queryFilters / data.DriverCapabilities.queryAggregations / data.DriverCapabilities.querySorting / data.DriverCapabilities.queryPagination / data.DriverCapabilities.queryWindowFunctions / data.DriverCapabilities.querySubqueries / data.DriverCapabilities.queryCTE / data.DriverCapabilities.joins / data.DriverCapabilities.fullTextSearch / data.DriverCapabilities.jsonQuery / data.DriverCapabilities.geospatialQuery / data.DriverCapabilities.streaming / data.DriverCapabilities.jsonFields / data.DriverCapabilities.arrayFields / data.DriverCapabilities.vectorSearch / data.DriverCapabilities.schemaSync / data.DriverCapabilities.migrations / data.DriverCapabilities.indexes / data.DriverCapabilities.connectionPooling / data.DriverCapabilities.preparedStatements / data.DriverCapabilities.queryCache` → (removed — delete the keys. A driver advertises a capability by implementing the corresponding IDataDriver method; the three bits that survive because method presence cannot carry the signal are `queryDateGranularity`, `autonumber` and `batchSchemaSync`)
280282
- Why not automatic: The #4484 findStream close-out found `DriverCapabilities.streaming` pointing at a capability the contract no longer declares, and the follow-up audit (#4634) checked every bit in the record the same way, across objectstack and cloud (objectui confirmed clean): of 34 declared bits, THREE have a decision-making reader — `queryDateGranularity` (engine aggregate dispatch + checkDateBucketParity), `autonumber` (engine defers generation to the driver), `batchSchemaSync` (engine ANDs it with method presence, because a subclass can inherit `syncSchemasBatch` from a base whose transport batches while its own cannot) — and THIRTY-ONE were written by every driver and read by nothing. Their `.describe()` strings promised engine adaptation ("if false, ObjectQL will filter/sort/paginate in memory") that was never built, and zero readers let the values go WRONG unnoticed: SqlDriver declared `streaming: false` while implementing `findStream`; InMemoryDriver declared `streaming: true` over a full-table read (ADR-0078 false affordance, on the capability record itself). The real mechanism everywhere else is METHOD presence: transactions gate on `driver.beginTransaction`, aggregate pushdown on `typeof driver.aggregate`, schema sync on `typeof driver.syncSchema`, and the REQUIRED CRUD/bulk methods are called unconditionally. A driver is CODE, never stack metadata — `supports` literals live in driver classes and `DriverConfig.capabilities` is plugin TS configuration, neither ever a `sys_metadata` shape (the stack-tree neighbour, `datasource.capabilities`, was retired separately in #4583) — so there is no source for the D2 chain to rewrite and this entry is the D3 record. The keys are tombstoned rather than deleted because `DriverCapabilitiesSchema` is not `.strict()` and IS parsed (DriverConfigSchema / SQLDriverConfigSchema / NoSQLDriverConfigSchema embed it): a plain delete would silently strip a vendor's authored bit, replacing one silent no-op with another. `batchSchemaSync` also drops its `.default(false)` for `.optional()` — absence already meant false at both readers, and the default forced every capability object to spell out 30+ bits. ADR-0049 / ADR-0078, #4634.
281283
- Done when: No `supports` literal or `DriverConfig.capabilities` object authors any of the 31 retired bits — a driver class that still writes one fails tsc against `IDataDriver.supports` (the bit is `never`), and a parsed config fails with the per-key prescription. The three in-repo drivers (memory / mongodb / sql) declare only live bits; cloud's TursoDriver keeps compiling via its `...super.supports` spread (its stale explicit overrides are cleanup, tracked cloud-side). Engine behaviour is byte-identical: every removed bit had zero readers, and the three live bits keep their readers (engine.ts autonumber defer / aggregate dispatch, plugin.ts + engine.ts batched schema sync, verify date-bucket parity).
284+
- **`plugin-activation-events-retired`**`kernel.dynamicLoadRequest.activationEvents / studio.studioPluginManifest.activationEvents` → (removed — delete the key. Every plugin activates immediately on load/registration, which is the only behaviour that has ever existed; `activate()` still runs at registration time. Lazy activation, if built, returns via the enforce route of ADR-0049 through a new ADR, with a vocabulary its executor actually honours)
285+
- Why not automatic: Both `activationEvents` keys — and the `ActivationEventSchema` trigger vocabulary they embedded (`onCommand` / `onRoute` / … / `onView` after the #4653 convergence) — promised lazy plugin activation ("plugins remain dormant until an activation event fires") that no runtime in objectstack, cloud, cloud-v1 or objectui ever implemented: nothing anywhere read the key, every plugin activates immediately, and cloud-v1's own ROADMAP recorded lazy activation as unimplemented (planned v0.4.0). That is the ADR-0049 false-compliance shape in the semantically-lying direction: an author writing `activationEvents: [{ type: 'onMetadataType', pattern: 'flow' }]` expected deferral and got eager activation with a clean parse. Neither parent shape is stored metadata — `StudioPluginManifest` is TS configuration parsed by `defineStudioPlugin` (a root schema, never part of a stack tree) and `DynamicLoadRequest` is a runtime request shape with no caller — so no `sys_metadata` row can carry the key and there is no source for the D2 chain to rewrite; this entry is the D3 record. The kernel key is tombstoned via `retiredKey()` (its schema is not `.strict()`; a plain delete would strip an authored value silently), the studio key is rejected by the strict manifest parse with a guidance prescription (as are its former VS Code-flavoured aliases `activation` / `events` / `onActivate`), and the orphaned `ActivationEventSchema` / `ActivationEvent` exports are removed from `./kernel` and `./studio` with the keys (#3950: an exported schema with no consumer is read as a capability). #4657.
286+
- Done when: No `DynamicLoadRequest` or `defineStudioPlugin` input authors `activationEvents` — authoring it is a tsc error (`never` on the kernel side; an unknown key on the strict studio manifest) and a parse error carrying the prescription on both. No code imports `ActivationEventSchema` / `ActivationEvent` from `@objectstack/spec/kernel` or `@objectstack/spec/studio` (TS2305 after upgrade). Runtime behaviour is byte-identical: plugins loaded eagerly before and after.
282287

283288
---
284289

packages/spec/spec-changes.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,13 @@
481481
"migrationId": "driver-capabilities-inert-bits-removed",
482482
"toMajor": 17,
483483
"rationale": "The #4484 findStream close-out found `DriverCapabilities.streaming` pointing at a capability the contract no longer declares, and the follow-up audit (#4634) checked every bit in the record the same way, across objectstack and cloud (objectui confirmed clean): of 34 declared bits, THREE have a decision-making reader — `queryDateGranularity` (engine aggregate dispatch + checkDateBucketParity), `autonumber` (engine defers generation to the driver), `batchSchemaSync` (engine ANDs it with method presence, because a subclass can inherit `syncSchemasBatch` from a base whose transport batches while its own cannot) — and THIRTY-ONE were written by every driver and read by nothing. Their `.describe()` strings promised engine adaptation (\"if false, ObjectQL will filter/sort/paginate in memory\") that was never built, and zero readers let the values go WRONG unnoticed: SqlDriver declared `streaming: false` while implementing `findStream`; InMemoryDriver declared `streaming: true` over a full-table read (ADR-0078 false affordance, on the capability record itself). The real mechanism everywhere else is METHOD presence: transactions gate on `driver.beginTransaction`, aggregate pushdown on `typeof driver.aggregate`, schema sync on `typeof driver.syncSchema`, and the REQUIRED CRUD/bulk methods are called unconditionally. A driver is CODE, never stack metadata — `supports` literals live in driver classes and `DriverConfig.capabilities` is plugin TS configuration, neither ever a `sys_metadata` shape (the stack-tree neighbour, `datasource.capabilities`, was retired separately in #4583) — so there is no source for the D2 chain to rewrite and this entry is the D3 record. The keys are tombstoned rather than deleted because `DriverCapabilitiesSchema` is not `.strict()` and IS parsed (DriverConfigSchema / SQLDriverConfigSchema / NoSQLDriverConfigSchema embed it): a plain delete would silently strip a vendor's authored bit, replacing one silent no-op with another. `batchSchemaSync` also drops its `.default(false)` for `.optional()` — absence already meant false at both readers, and the default forced every capability object to spell out 30+ bits. ADR-0049 / ADR-0078, #4634."
484+
},
485+
{
486+
"surface": "kernel.dynamicLoadRequest.activationEvents / studio.studioPluginManifest.activationEvents",
487+
"replacement": "(removed — delete the key. Every plugin activates immediately on load/registration, which is the only behaviour that has ever existed; `activate()` still runs at registration time. Lazy activation, if built, returns via the enforce route of ADR-0049 through a new ADR, with a vocabulary its executor actually honours)",
488+
"migrationId": "plugin-activation-events-retired",
489+
"toMajor": 17,
490+
"rationale": "Both `activationEvents` keys — and the `ActivationEventSchema` trigger vocabulary they embedded (`onCommand` / `onRoute` / … / `onView` after the #4653 convergence) — promised lazy plugin activation (\"plugins remain dormant until an activation event fires\") that no runtime in objectstack, cloud, cloud-v1 or objectui ever implemented: nothing anywhere read the key, every plugin activates immediately, and cloud-v1's own ROADMAP recorded lazy activation as unimplemented (planned v0.4.0). That is the ADR-0049 false-compliance shape in the semantically-lying direction: an author writing `activationEvents: [{ type: 'onMetadataType', pattern: 'flow' }]` expected deferral and got eager activation with a clean parse. Neither parent shape is stored metadata — `StudioPluginManifest` is TS configuration parsed by `defineStudioPlugin` (a root schema, never part of a stack tree) and `DynamicLoadRequest` is a runtime request shape with no caller — so no `sys_metadata` row can carry the key and there is no source for the D2 chain to rewrite; this entry is the D3 record. The kernel key is tombstoned via `retiredKey()` (its schema is not `.strict()`; a plain delete would strip an authored value silently), the studio key is rejected by the strict manifest parse with a guidance prescription (as are its former VS Code-flavoured aliases `activation` / `events` / `onActivate`), and the orphaned `ActivationEventSchema` / `ActivationEvent` exports are removed from `./kernel` and `./studio` with the keys (#3950: an exported schema with no consumer is read as a capability). #4657."
484491
}
485492
],
486493
"removed": []
@@ -1021,6 +1028,13 @@
10211028
"migrationId": "driver-capabilities-inert-bits-removed",
10221029
"toMajor": 17,
10231030
"rationale": "The #4484 findStream close-out found `DriverCapabilities.streaming` pointing at a capability the contract no longer declares, and the follow-up audit (#4634) checked every bit in the record the same way, across objectstack and cloud (objectui confirmed clean): of 34 declared bits, THREE have a decision-making reader — `queryDateGranularity` (engine aggregate dispatch + checkDateBucketParity), `autonumber` (engine defers generation to the driver), `batchSchemaSync` (engine ANDs it with method presence, because a subclass can inherit `syncSchemasBatch` from a base whose transport batches while its own cannot) — and THIRTY-ONE were written by every driver and read by nothing. Their `.describe()` strings promised engine adaptation (\"if false, ObjectQL will filter/sort/paginate in memory\") that was never built, and zero readers let the values go WRONG unnoticed: SqlDriver declared `streaming: false` while implementing `findStream`; InMemoryDriver declared `streaming: true` over a full-table read (ADR-0078 false affordance, on the capability record itself). The real mechanism everywhere else is METHOD presence: transactions gate on `driver.beginTransaction`, aggregate pushdown on `typeof driver.aggregate`, schema sync on `typeof driver.syncSchema`, and the REQUIRED CRUD/bulk methods are called unconditionally. A driver is CODE, never stack metadata — `supports` literals live in driver classes and `DriverConfig.capabilities` is plugin TS configuration, neither ever a `sys_metadata` shape (the stack-tree neighbour, `datasource.capabilities`, was retired separately in #4583) — so there is no source for the D2 chain to rewrite and this entry is the D3 record. The keys are tombstoned rather than deleted because `DriverCapabilitiesSchema` is not `.strict()` and IS parsed (DriverConfigSchema / SQLDriverConfigSchema / NoSQLDriverConfigSchema embed it): a plain delete would silently strip a vendor's authored bit, replacing one silent no-op with another. `batchSchemaSync` also drops its `.default(false)` for `.optional()` — absence already meant false at both readers, and the default forced every capability object to spell out 30+ bits. ADR-0049 / ADR-0078, #4634."
1031+
},
1032+
{
1033+
"surface": "kernel.dynamicLoadRequest.activationEvents / studio.studioPluginManifest.activationEvents",
1034+
"replacement": "(removed — delete the key. Every plugin activates immediately on load/registration, which is the only behaviour that has ever existed; `activate()` still runs at registration time. Lazy activation, if built, returns via the enforce route of ADR-0049 through a new ADR, with a vocabulary its executor actually honours)",
1035+
"migrationId": "plugin-activation-events-retired",
1036+
"toMajor": 17,
1037+
"rationale": "Both `activationEvents` keys — and the `ActivationEventSchema` trigger vocabulary they embedded (`onCommand` / `onRoute` / … / `onView` after the #4653 convergence) — promised lazy plugin activation (\"plugins remain dormant until an activation event fires\") that no runtime in objectstack, cloud, cloud-v1 or objectui ever implemented: nothing anywhere read the key, every plugin activates immediately, and cloud-v1's own ROADMAP recorded lazy activation as unimplemented (planned v0.4.0). That is the ADR-0049 false-compliance shape in the semantically-lying direction: an author writing `activationEvents: [{ type: 'onMetadataType', pattern: 'flow' }]` expected deferral and got eager activation with a clean parse. Neither parent shape is stored metadata — `StudioPluginManifest` is TS configuration parsed by `defineStudioPlugin` (a root schema, never part of a stack tree) and `DynamicLoadRequest` is a runtime request shape with no caller — so no `sys_metadata` row can carry the key and there is no source for the D2 chain to rewrite; this entry is the D3 record. The kernel key is tombstoned via `retiredKey()` (its schema is not `.strict()`; a plain delete would strip an authored value silently), the studio key is rejected by the strict manifest parse with a guidance prescription (as are its former VS Code-flavoured aliases `activation` / `events` / `onActivate`), and the orphaned `ActivationEventSchema` / `ActivationEvent` exports are removed from `./kernel` and `./studio` with the keys (#3950: an exported schema with no consumer is read as a capability). #4657."
10241038
}
10251039
],
10261040
"removed": []

0 commit comments

Comments
 (0)