feat(spec,objectql): IObjectQLEngine —— objectql 槽的契约落地,类 implements 之,七个本地 stand-in 退场 (#4251 B3) - #4404
Merged
Merged
Conversation
…exists, the class implements it, the seven local stand-ins are deleted (#4251 B3) The ledger can finally say what each of ObjectQL's two registration names means: `data` stays IDataEngine (the data plane); `objectql` resolves to IObjectQLEngine -- schema access (getSchema/getObject/registry), actions (registerAction/removeActionsByPackage/executeAction), hook & middleware seams (registerHook/unregisterHooksByPackage/registerFunction/ registerMiddleware/bindHooks), the first-wins default runners and hook metrics, boot wiring (registerDriver/setDatasourceMapping/registerApp), and ops probes (checkDriversHealth/wasDatastoreCreatedFromEmpty/ invalidateDataMigrationFlags). The ledger test pins the relation: objectql strictly WIDENS data, deliberately no longer equal. Why implements is the point: the honest state for two batches -- recorded on DomainHandlerContext.getObjectQL -- was that the wider surface had no contract, so seven consumer-local stand-ins accumulated (AppEngineSurface, EngineRegistrySurface, EngineExtensionSurface, SecurityEngineSurface, FreshDatastoreEngine, the dispatcher's inline checkDriversHealth slice, getObjectQL: any). Each was honest and each was an UNCHECKED claim: getService<Surface>() is an assertion, so an engine rename would break every consumer at runtime with zero compile errors. `ObjectQL implements IObjectQLEngine` turns all of them into one compiler-verified claim; all seven are deleted, and getObjectQL is typed Promise<IObjectQLEngine | null> end to end. Evidence bar unchanged: every member has a cross-package consumer through the slot; triggerHooks (cross-package only in tests) stays off. _registry never leaves the engine package now: plugin-security's five declared-metadata readers reached the private field through `any` -- the same reach /me/apps had in B2 -- all migrated to the public getter the contract declares, test doubles included. IMetadataService gains subscribe?/loadMany? -- implemented by MetadataManager beside watch all along, reached only via `any` by ObjectQLPlugin's metadata bridge. With them declared, that bridge's six lookups and metadata-protocol's objectql lookup carry contract types and both files leave the grandfather list: baseline 167 -> 159 sites, 36 -> 34 files. Verified: spec build + 7198/282; objectql build (the implements check) + 1464/89; runtime 1015/70; plugin-security 677/32; plugin-hono-server 135/12; plugin-auth 579/26; platform-objects 254/8; metadata 281/13; metadata-protocol 136/21; all eight dts builds; ratchet holds 159/34 none new; eslint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 9 package(s): 125 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…plements check caught a second spec-internal double (#4251 B3) Three fixes, all caught by gates: 1. security-plugin:880 -- the contract's getSchema returns `unknown`; narrow to EngineOwnedSchemaLike at the guard call (CI Build Core; my local batch had been aborted by an earlier failure and I re-ran only the failed package -- the DEBT-package lesson, second verse). 2. objectql plugin.ts:1583 -- optional-member narrowing does not survive into a closure (TS2722); take a bound reference under the guard. 3. IMetadataService.subscribe -- the first draft reused `watch`'s callback type, and `MetadataManager implements IMetadataService` REJECTED it: subscribe relays the persistence-side MetadataWatchEvent (add/changed/deleted + path/stats), not watch's registration-level events. The check working exactly as intended, on its first day. Fixing it exposed that spec carries TWO types named MetadataWatchEvent with different shapes (system/metadata-persistence.zod vs kernel/metadata-loader.zod) -- the same double-source shape as the http-server shadow, both alive this time. Reported on #4251; merging them is its own change. The contract now imports the persistence one the implementation uses, with the trap documented at the import. Verified: all nine touched packages' dts builds in ONE uninterrupted batch; spec 7198/282, objectql 1464/89, metadata 281/13, plugin-security 677/32; eslint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ract exports (#4251 B3) `check:api-surface` is spec's public-API snapshot gate, and it reported exactly what this PR does: "0 breaking (removed/narrowed), 2 added". The two are IObjectQLEngine and EngineSchemaRegistryView; the regenerated snapshot diff is +2 lines, -0 -- purely additive, matching the gate's own count. Missed because I ran spec's build and tests but not its check:* gates. #4361 ran all of them; this PR did not. All 16 now pass locally (check:exported-any included -- the contract's deliberate `any`s at the framework-handle and engine-local-shape edges are within its allowance). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 31, 2026
Closed
os-zhuang
added a commit
that referenced
this pull request
Aug 1, 2026
…11 names declared twice with different shapes (#4411) (#4458) * refactor(spec)!: remove the kernel metadata-loader envelope family — 11 names declared twice with different shapes (#4411) `@objectstack/spec` exported eleven names TWICE, with a different shape each time, on two subpath entries — so which type a consumer got depended on nothing but the import path: import type { MetadataWatchEvent } from '@objectstack/spec/kernel'; // one shape import type { MetadataWatchEvent } from '@objectstack/spec/system'; // another `MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataSaveOptions`, `MetadataExportOptions`, `MetadataImportOptions`, `MetadataLoadResult`, `MetadataSaveResult`, `MetadataWatchEvent`, `MetadataCollectionInfo` and `MetadataLoaderContract` are removed from `kernel/metadata-loader.zod`. The `system/metadata-persistence.zod` copies stay as the single source. Why the kernel side goes, and why this was worth removing rather than living with: - Zero consumers. Import-statement scans across this repo, `cloud` and `objectui` find every consumer on `./system` (or `./contracts`' own interface); only `kernel/metadata-loader.test.ts` ever parsed the kernel copies. ADR-0049 enforce-or-remove. - The naming intuition pointed the wrong way, which is what made this sharper than an ordinary duplicate. The kernel copies were the ones that LOOKED canonical — normalized enums, required fields, a `.describe()` per property — and they were the dead ones; the live copy is the loose superset its own consumer calls "legacy". Picking by name, or by which reads as more rigorous, picked the dead one, and because the shapes overlap heavily that choice compiled and failed later, at an edge value (`add` vs `added`) or on a field one copy made required. No tombstone and no ADR-0087 conversion, deliberately: these are runtime envelope types, not authorable metadata, so no authored source can carry them and there is nothing for `os migrate meta` to rewrite (the plugin-runtime / dev-plugin precedents). `MetadataManagerConfig` and `MetadataFallbackStrategy` are untouched — they were never duplicated (kernel owns them, system re-exports them), and that is the split that survives: manager wiring is kernel's, the loader/watch envelope is system's, nothing is declared twice. `MetadataManagerConfig.formats` now reads the `shared` format enum (same four members, leaf module, no cycle) rather than a fourth local copy. Also: - `contracts/metadata-service.ts` drops the "spec carries TWO types named MetadataWatchEvent" warning added in #4404 — it no longer does. - `expression-conformance.ledger.ts` drops the now-absent `kernel/metadata-loader.zod.ts:filter` CEL surface (the surviving system options never declared a `filter`, so no loader predicate was ever evaluated through it). - Baselines dropped deliberately: `json-schema.manifest.json` −11 entries, `authorable-surface.json` −65 lines (nothing can author these, so no `[RETIRED]` markers). `api-surface.json` regenerated: 22 exports leave `./kernel`. `references/kernel/metadata-persistence.mdx` removed by `gen:docs`. v17 release notes + upgrade checklist extended. No runtime behaviour changes — nothing read the removed copies. The system shapes are NOT tightened here; narrowing them would be a separate change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL * chore(spec): write the hand-edited baselines the way the generator does The two baselines this branch edits by hand — `json-schema.manifest.json` and `authorable-surface.json` — came out with `—` escaped as `—`, because the edit went through Python's `json.dump`, whose `ensure_ascii` defaults to true. `build-schemas.ts` writes them with `JSON.stringify`, which emits the character literally. No gate catches this: the manifest is only rewritten when the SCHEMA KEY SET changes, so the escape would have sat in the file until the next PR that adds a schema, where the generator would silently rewrite it back and hand that author an unrelated one-line diff to explain. Re-serialised with `ensure_ascii=False`. Both files now differ from main by exactly the intended removals and nothing else: 11 manifest keys, 65 authorable-surface lines, zero incidental churn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#4251 的 B3,按 issue 上评估过的方案:objectql 一族不机械类型化,而是把契约写出来。
IObjectQLEngine:objectql槽的契约ObjectQL 一个实例注册两个名字,ledger 现在能说清各自含义:
dataIDataEngine(不变)objectqlIObjectQLEngine extends IDataEngine新契约覆盖:schema 访问(
getSchema/getObject/registry)、actions(registerAction/removeActionsByPackage/executeAction)、hook/middleware seams(registerHook/unregisterHooksByPackage/registerFunction/registerMiddleware/bindHooks)、first-wins 默认 runner 与 hook metrics、启动接线(registerDriver/setDatasourceMapping/registerApp)、运维探针(checkDriversHealth/wasDatastoreCreatedFromEmpty/invalidateDataMigrationFlags)。ledger 测试钉住新关系:objectql严格加宽data,刻意不再相等。implements是本 PR 的要点两个批次以来的诚实状态记录在
DomainHandlerContext.getObjectQL上:更宽的表面没有契约,把它标成IDataEngine会是"看着更舒服的谎"。过渡纪律 —— 每个消费方声明自己用的窄切片 —— 累积出七个本地 stand-in(AppEngineSurface、EngineRegistrySurface、EngineExtensionSurface、SecurityEngineSurface、FreshDatastoreEngine、dispatcher 的 inlinecheckDriversHealth切片、getObjectQL: any本身)。每一个都诚实,每一个都是未经检查的 claim:
getService<Surface>('objectql')是断言,引擎改个名,七处消费方运行时全坏、编译期零报错。ObjectQL implements IObjectQLEngine把七份断言并成一份编译器验证的 claim。 七个 stand-in 全部删除;getObjectQL端到端标成Promise<IObjectQLEngine | null>,关闭 dispatcher 里最老的一处记录在案的any。证据门槛不变:每个声明成员都有经槽到达的跨包调用方;没有的(如
triggerHooks,跨包只有测试在用)不入契约,等调用方出现。注册表视图EngineSchemaRegistryView恰好声明消费方在用的 8 个成员。_registry从此不出引擎包plugin-security 的五处 declared-metadata 读取(
readDeclared、permission-set projection、suggested audience bindings)一直在any下伸进 ObjectQL 的私有_registry字段 —— 与 B2 抓到的/me/apps同款,×5。全部迁到契约声明的公开registrygetter,测试替身同步。顺带:
IMetadataService补subscribe?/loadMany?MetadataManager在watch旁边一直实现着它们,唯一跨包调用方(ObjectQLPlugin 的 metadata bridge —— 保持 runtime-authored hooks/actions 活性的 re-sync)只能靠any够到。声明后,该 bridge 的 6 处metadata查找与 metadata-protocol 的objectql查找全部携带契约类型,两个文件整体退出祖父名单。基线:167 → 159 站点,36 → 34 文件。
验证
pnpm check:slot-lookup强制模式:✓ 159 unswept site(s) in 34 file(s), none new;no files added(对 d25a0ec)implements检查)+ 1464 / 89🤖 Generated with Claude Code