fix(spec,plugins): 清扫 auth/session 一族的槽查找 —— 31 处类型化,并揪出 user-import 的 metadata reader 指着一个没有该方法的服务 (#4251) - #4361
Merged
Conversation
Batch B2 of the #4251 sweep: every service-lookup erasure in the auth/session family. plugin-auth/auth-plugin.ts (20), plugin-hono-server/current-user-endpoints.ts (10) and plugin-security/security-plugin.ts (1) now pass the slot's contract type; the ratchet baseline drops 171 -> 140 sites, 40 -> 37 files, and all three files leave the grandfather list. The yield: POST /admin/import-users resolved the `metadata` slot and probed `metadataService?.getMetaItem` to decide whether to pass the import's field-coercion dependency. `getMetaItem` is a PROTOCOL method (ObjectStackProtocolImplementation, registered under `protocol`); MetadataManager, which occupies `metadata`, has never had it. The probe was false on every deployment and the dep was never passed -- imported rows reached sys_user uncoerced, with the branch claiming otherwise sitting right there. Same shape as #4127's dead automation.trigger. The route reads `protocol` now. /me/apps reached ObjectQL's PRIVATE `_registry` through `as any` while /auth/me/permissions, two handlers up in the same file, read the public `registry` getter over the same field of the same object. Both read the public accessor now; the one test that stubbed `_registry` was pinning that private reach and stubs `registry`. Contract, from evidence: IDataEngine's read methods (find / findOne / count / aggregate) declare the trailing `options?: BaseEngineOptions` argument they have always accepted. ObjectQL's own doc explains why it exists -- the same `{ context }` object was correct as insert's 3rd argument and SILENTLY DROPPED as find's, so an intended isSystem bypass just vanished. The contract exposed only query.context, so callers using the trailing channel could reach it only by erasing the lookup. Adding an optional trailing parameter breaks no implementor or caller; BaseEngineOptions was already exported but sat unused under the legacy/deprecated heading, and moves up with the rationale attached. One new spec test pins the argument at the call site. Where the contract does not reach, the escape hatch is named rather than `any`: PermissionEvaluatorSurface (security.permissions), SettingsReadSurface (settings), EngineRegistrySurface / EngineExtensionSurface (objectql beyond IDataEngine). That last one is deliberate scope -- the standing record on getObjectQL in @objectstack/ runtime says ObjectQL is genuinely wider than IDataEngine and typing the whole thing IDataEngine would be the more comfortable-looking lie. These declarations are what that contract gets written from, and what it deletes. Verified: full-repo typecheck 110/110; spec 7157/278, objectql 1394/87, runtime 951/66, plugin-auth 579/26, plugin-security 677/32, plugin-hono-server 135/12; eslint clean on every changed file; check:slot-lookup 140 sites in 37 files. 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 4 package(s): 114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 31, 2026
…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>
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…之,七个本地 stand-in 退场 (#4251 B3) (#4404) * feat(spec,objectql): IObjectQLEngine -- the objectql slot's contract 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> * fix(spec,objectql,plugin-security): CI round-trip fixes -- and the implements 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> * chore(spec): regenerate the api-surface snapshot for the two new contract 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> --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <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 清扫的 B2 批次 —— auth / session 一族,该族里的擦除一个不留。基线棘轮 171 → 140 站点、40 → 37 文件,三个文件全部退出祖父名单。
plugins/plugin-auth/src/auth-plugin.tsplugins/plugin-hono-server/src/current-user-endpoints.tsplugins/plugin-security/src/security-plugin.ts产出
POST /admin/import-users在探测一个该服务从来没有的方法。 这条路由解析metadata槽,然后用metadataService?.getMetaItem决定要不要把导入的字段强制转换依赖传下去:getMetaItem是 protocol 的方法 ——ObjectStackProtocolImplementation,由MetadataProtocolPlugin注册在protocol槽下。占着metadata槽的是MetadataManager,它没有、也从来没有过这个方法。所以这个三元判断在任何部署上都是假,依赖从未传入:导入行未经类型强制就写进了sys_user,而声称相反的分支就摆在那里。这和 #4127 那个死掉的
automation.trigger、#4321 的registerInMemory探测是同一个形状 —— 代码宣称、运行时给不出的能力,被any一直盖着。把这次查找类型化到IMetadataService,正是让这个死探测变成编译错误的那一步。 路由现在读protocol。/me/apps伸进了 ObjectQL 的私有状态。 它读的是(ctx.getService('objectql') as any)?._registry,而同一个文件里往上两个 handler 的/auth/me/permissions,读的是同一个对象同一个字段上的公开registrygetter。两处现在都走公开访问器。唯一那个 stub 了_registry的测试,钉住的就是这次私有伸手,改 stubregistry。契约:按证据补
IDataEngine的读方法(find/findOne/count/aggregate)现在声明了它们一直在接受的尾随options?: BaseEngineOptions参数。ObjectQL 自己给EngineReadOptions写的注释就是理由:引擎两条通道都收(
options.context优先)。但契约只暴露了 query 那条,于是每个用尾随通道的调用方 —— 包括 current-user endpoints 的权限集加载器 —— 只能靠把查找擦成any才能够到它。这正是本 issue 在扫的那种擦除,只不过是在它的成因那一层。加一个可选尾随参数不破坏任何实现方(
data-engine.test.ts里既有的"最小实现"用例本身就证明少写参数仍然满足接口),也不破坏任何调用方。BaseEngineOptions本来就已导出 —— 只是躺在 "legacy/deprecated" 标题下无人使用,这也正是契约当初去找、没找到的原因;现在把它挪到其余 QueryAST 对齐类型旁边,并附上理由。新增一个 spec 测试,把这个参数钉在调用点上 —— 旧契约当初拒绝它的那个位置。契约够不到的地方,逃生口是具名的
有三个槽今天还给不出 spec 类型。每一个都拿到一个窄的、有文档的本地接口,而不是
any—— 沿用 plugin-audit 的写法(MessagingEmitSurface/AuditI18nSurface):security.permissions→PermissionEvaluatorSurface。plugin-security 的PermissionEvaluator;plugin-hono-server 不能对一个可选插件产生运行时依赖(下面那些!evaluator分支就是它的缺席)。settings→SettingsReadSurface。service-settings 同理。objectql超出IDataEngine的部分 →EngineRegistrySurface(registry/getSchema)与EngineExtensionSurface(registerHook/registerMiddleware)。最后这条是刻意留的范围,不是遗漏。
@objectstack/runtime的DomainHandlerContext上getObjectQL那段现存记录写得很清楚:ObjectQL 确实比IDataEngine宽,更宽的那部分没人写过契约,把整个东西标成IDataEngine会是"看着更舒服的那个谎",把缺口埋在一堆 cast 底下。这些声明就是那份契约将来要据以书写的具体输入 —— 也是它落地时(B3)要删掉的东西。顺带一提:
SettingsReadSurface一开始漏了subscribe,编译器当场点出 6 个调用点(brand name / SMS locale / auth 命名空间的实时重绑)。any不会说一个字。SettingsChangeHandler/SettingsUnsubscribe在@objectstack/spec/system已有公开类型,所以直接复用而不是另起一套形状。范围外,只报告不修
规则仍然看不见的第三种形状,与已记录的"包装函数返回注解"残留不同,而且不需要类型信息就能抓:
全仓约 20 处(
runtime/app-plugin.ts×4、cloud-connection/*、core/fallbacks/authored-translation-sync.ts…)。细节回帖到 #4251。验证
pnpm check:slot-lookup(强制模式) →✓ ratchet holds: 140 unswept site(s) in 37 file(s), none new;baseline key set verified against 6a67d7a: no files addedeslint --no-inline-config全部改动文件 —— 干净pnpm typecheck—— 110/110 任务通过@objectstack/spec7157 tests / 278 files、objectql1394 / 87、runtime951 / 66、plugin-auth579 / 26、plugin-security677 / 32、plugin-hono-server135 / 12 —— 全绿🤖 Generated with Claude Code