Skip to content

Commit 7ce02eb

Browse files
os-zhuangclaude
andauthored
feat(spec,objectql): IObjectQLEngine —— objectql 槽的契约落地,类 implements 之,七个本地 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>
1 parent 4bfd455 commit 7ce02eb

30 files changed

Lines changed: 382 additions & 232 deletions
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/objectql": patch
4+
"@objectstack/core": patch
5+
"@objectstack/runtime": patch
6+
"@objectstack/metadata-protocol": patch
7+
"@objectstack/platform-objects": patch
8+
"@objectstack/plugin-auth": patch
9+
"@objectstack/plugin-hono-server": patch
10+
"@objectstack/plugin-security": patch
11+
---
12+
13+
feat(spec,objectql): `IObjectQLEngine` — the `objectql` slot's contract exists, the class `implements` it, and the seven consumer-local stand-ins are deleted (#4251 B3)
14+
15+
ObjectQL registers one instance under two names, and the ledger can finally say
16+
what each name means: `data` stays `IDataEngine` (the data plane), `objectql`
17+
now resolves to **`IObjectQLEngine`** — the full engine: schema access
18+
(`getSchema` / `getObject` / `registry`), actions (`registerAction` /
19+
`removeActionsByPackage` / `executeAction`), the hook/middleware seams
20+
(`registerHook` / `unregisterHooksByPackage` / `registerFunction` /
21+
`registerMiddleware` / `bindHooks`), the first-wins default runners and hook
22+
metrics, boot wiring (`registerDriver` / `setDatasourceMapping` /
23+
`registerApp`), and the ops probes (`checkDriversHealth` /
24+
`wasDatastoreCreatedFromEmpty` / `invalidateDataMigrationFlags`). The ledger
25+
test pins the new relation: `objectql` strictly widens `data`, deliberately no
26+
longer equal.
27+
28+
**Why now, and why `implements` is the point.** The honest state for two
29+
batches was recorded on `DomainHandlerContext.getObjectQL`: ObjectQL is wider
30+
than `IDataEngine`, the wider part had no contract, and typing it `IDataEngine`
31+
would be "the more comfortable-looking lie". The interim discipline — each
32+
consumer declares the narrow slice it uses — produced seven local surfaces
33+
(`AppEngineSurface`, `EngineRegistrySurface`, `EngineExtensionSurface`,
34+
`SecurityEngineSurface`, `FreshDatastoreEngine`, the dispatcher's inline
35+
`checkDriversHealth` slice, the `getObjectQL: any` itself). Each was honest and
36+
each was an UNCHECKED claim: `getService<Surface>('objectql')` is an assertion,
37+
so an engine rename would have broken every consumer at runtime with zero
38+
compile errors. `ObjectQL implements IObjectQLEngine` converts all of them into
39+
one compiler-verified claim. All seven stand-ins are deleted; consumers import
40+
the one declaration. `getObjectQL` is typed `Promise<IObjectQLEngine | null>`
41+
end to end, closing the oldest documented `any` in the dispatcher.
42+
43+
**Evidence bar unchanged.** Every declared member has a cross-package consumer
44+
reaching it through the slot; engine members without one (e.g. `triggerHooks`,
45+
cross-package only in tests) stay off until a caller appears. The registry view
46+
(`EngineSchemaRegistryView`) declares exactly the eight members consumers use.
47+
48+
**`_registry` never leaves the engine package now.** plugin-security's
49+
declared-metadata readers (`readDeclared`, permission-set projection, suggested
50+
audience bindings) reached ObjectQL's private `_registry` field through `any`
51+
the same private reach `/me/apps` had in B2, five more times. All migrated to
52+
the public `registry` getter the contract declares, test doubles included.
53+
54+
**`IMetadataService` gains `subscribe?` / `loadMany?`** — implemented by
55+
`MetadataManager` beside `watch` all along, reached through the slot only via
56+
`any` by ObjectQLPlugin's metadata bridge (the re-sync keeping runtime-authored
57+
hooks/actions live). With them declared, the bridge's six `metadata` lookups
58+
and metadata-protocol's `objectql` lookup carry contract types, and both files
59+
leave the grandfather list entirely: baseline **167 → 159 sites, 36 → 34
60+
files**.

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,7 @@ export type {
5353
RouteHandler,
5454
Middleware,
5555
IDataEngine,
56+
IObjectQLEngine,
57+
EngineSchemaRegistryView,
5658
IDataDriver,
5759
} from '@objectstack/spec/contracts';

packages/metadata-protocol/src/plugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424

2525
import type { Plugin, PluginContext } from '@objectstack/core';
26+
import type { IObjectQLEngine } from '@objectstack/spec/contracts';
2627
import {
2728
SysMetadataObject,
2829
SysMetadataHistoryObject,
@@ -51,7 +52,7 @@ export function createMetadataProtocolPlugin(options: MetadataProtocolPluginOpti
5152
dependencies: ['com.objectstack.engine.objectql'],
5253

5354
init: async (ctx: PluginContext) => {
54-
const ql: any = ctx.getService('objectql');
55+
const ql = ctx.getService<IObjectQLEngine>('objectql');
5556

5657
// Assembly-conflict guard: the engine plugin's built-in assembly
5758
// (registerProtocol !== false) already registered `protocol`.

packages/objectql/src/engine.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ExecutionContext, ExecutionContextInput, ExecutionContextSchema } from
2828
import {
2929
IDataDriver,
3030
IDataEngine,
31+
type IObjectQLEngine,
3132
Logger,
3233
createLogger,
3334
withTransientRetry,
@@ -554,7 +555,12 @@ interface SummaryDescriptor {
554555
filter?: Record<string, unknown>;
555556
}
556557

557-
export class ObjectQL implements IDataEngine {
558+
// `implements IObjectQLEngine` is the verification step of #4251 B3: every
559+
// member the `objectql` slot's contract declares is checked against this class
560+
// on every build, so the seven consumer-local surface declarations the contract
561+
// replaced can never silently drift from the engine again. IObjectQLEngine
562+
// extends IDataEngine, so the old claim rides along.
563+
export class ObjectQL implements IObjectQLEngine {
558564
/**
559565
* Ambient transaction store (ADR-0034). While a `transaction()` callback
560566
* runs, the active transaction handle lives here so that EVERY data

packages/objectql/src/plugin.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { StorageNameMapping } from '@objectstack/spec/system';
88
import { LifecycleService } from './lifecycle/lifecycle-service.js';
99
import { lifecycleSettingsManifest } from './lifecycle/lifecycle-settings.js';
1010
import { runActionGovernanceInventory } from './action-governance.js';
11+
import type { IMetadataService } from '@objectstack/spec/contracts';
1112

1213
export type { Plugin, PluginContext };
1314

@@ -311,7 +312,7 @@ export class ObjectQLPlugin implements Plugin {
311312

312313
// Sync from external metadata service (e.g. MetadataPlugin) if available
313314
try {
314-
const metadataService = ctx.getService('metadata') as any;
315+
const metadataService = ctx.getService<IMetadataService>('metadata');
315316
if (metadataService && typeof metadataService.loadMany === 'function' && this.ql) {
316317
await this.loadMetadataFromService(metadataService, ctx);
317318
}
@@ -1095,7 +1096,7 @@ export class ObjectQLPlugin implements Plugin {
10951096
*/
10961097
private async bridgeObjectsToMetadataService(ctx: PluginContext): Promise<void> {
10971098
try {
1098-
const metadataService = ctx.getService<any>('metadata');
1099+
const metadataService = ctx.getService<IMetadataService>('metadata');
10991100
if (!metadataService || typeof metadataService.register !== 'function') {
11001101
ctx.logger.debug('Metadata service unavailable for bridging, skipping');
11011102
return;
@@ -1181,9 +1182,9 @@ export class ObjectQLPlugin implements Plugin {
11811182
if (!packageId || !this.ql?.registry) return;
11821183

11831184
try {
1184-
let metadataService: any;
1185+
let metadataService: IMetadataService | undefined;
11851186
try {
1186-
metadataService = ctx.getService<any>('metadata');
1187+
metadataService = ctx.getService<IMetadataService>('metadata');
11871188
} catch {
11881189
return; // no metadata service on this kernel — nothing to bridge into
11891190
}
@@ -1383,7 +1384,7 @@ export class ObjectQLPlugin implements Plugin {
13831384

13841385
let serviceHooks: any[] | null = null;
13851386
try {
1386-
const metadataService = ctx.getService('metadata') as any;
1387+
const metadataService = ctx.getService<IMetadataService>('metadata');
13871388
if (metadataService && typeof metadataService.loadMany === 'function') {
13881389
serviceHooks = (await metadataService.loadMany('hook')) ?? [];
13891390
}
@@ -1577,9 +1578,10 @@ export class ObjectQLPlugin implements Plugin {
15771578
if (!ql || typeof ql.listRegisteredActions !== 'function') return;
15781579
let loadStandaloneActions: (() => Promise<any[]>) | undefined;
15791580
try {
1580-
const meta: any = ctx.getService('metadata');
1581-
if (meta && typeof meta.loadMany === 'function') {
1582-
loadStandaloneActions = () => meta.loadMany('action');
1581+
const meta = ctx.getService<IMetadataService>('metadata');
1582+
const loadMany = meta?.loadMany;
1583+
if (meta && typeof loadMany === 'function') {
1584+
loadStandaloneActions = () => loadMany.call(meta, 'action');
15831585
}
15841586
} catch { /* no metadata service — registry objects still audit */ }
15851587
this.lastGovernanceFingerprint = await runActionGovernanceInventory({
@@ -1638,7 +1640,7 @@ export class ObjectQLPlugin implements Plugin {
16381640

16391641
let serviceActions: any[] | null = null;
16401642
try {
1641-
const metadataService = ctx.getService('metadata') as any;
1643+
const metadataService = ctx.getService<IMetadataService>('metadata');
16421644
if (metadataService && typeof metadataService.loadMany === 'function') {
16431645
serviceActions = (await metadataService.loadMany('action')) ?? [];
16441646
}

packages/platform-objects/src/plugin.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,9 @@ import { SetupAppTranslations } from './apps/translations/index.js';
44
import { MetadataFormsTranslations } from './metadata-translations/index.js';
55
import { SysMigration } from './system/sys-migration.object.js';
66
import { SysSecret } from './system/sys-secret.object.js';
7-
import { attestFreshDatastore, type MigrationFlagEngine } from './system/migration-flag.js';
8-
import type { II18nService } from '@objectstack/spec/contracts';
7+
import { attestFreshDatastore } from './system/migration-flag.js';
8+
import type { II18nService, IObjectQLEngine } from '@objectstack/spec/contracts';
99

10-
/**
11-
* The `objectql` slot's fresh-datastore attestation seam.
12-
*
13-
* [#4251] Not on `IDataEngine` — these are ObjectQL's own migration-flag
14-
* accessors, and the probe at the call site is what runs when the slot holds an
15-
* engine without them. Declared narrow and named instead of erased to `any`.
16-
*/
17-
interface FreshDatastoreEngine extends MigrationFlagEngine {
18-
wasDatastoreCreatedFromEmpty?(): boolean;
19-
invalidateDataMigrationFlags?(): void;
20-
}
2110

2211
/**
2312
* `PlatformObjectsPlugin`
@@ -108,9 +97,7 @@ export class PlatformObjectsPlugin {
10897
// service-storage). A store that was found rather than created attests
10998
// nothing and keeps producing evidence by scan.
11099
ctx?.hook?.('kernel:ready', async () => {
111-
// [#4251] The fresh-datastore attestation seam is ObjectQL's own, not
112-
// `IDataEngine`'s; declared here rather than erased to `any`.
113-
let engine: FreshDatastoreEngine | undefined;
100+
let engine: IObjectQLEngine | undefined;
114101
try {
115102
engine = ctx.getService?.('objectql');
116103
} catch {

packages/plugins/plugin-auth/src/auth-plugin.ts

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { SysOrganizationDetailPage, SysUserDetailPage } from '@objectstack/platform-objects/pages';
2020
import { resolveTenancyPosture } from '@objectstack/types';
2121
import { postureEnforcesWall, type OrgScopingEntitlement } from '@objectstack/spec/security';
22-
import type { IDataEngine, IEmailService, ISmsService } from '@objectstack/spec/contracts';
22+
import type { IDataEngine, IEmailService, IObjectQLEngine, ISmsService } from '@objectstack/spec/contracts';
2323
import {
2424
AuthManager,
2525
resolveOidcProviderEnabled,
@@ -44,36 +44,6 @@ import {
4444
authPluginManifestHeader,
4545
} from './manifest.js';
4646

47-
/**
48-
* The `objectql` slot BEYOND `IDataEngine` — the hook and middleware seams this
49-
* plugin installs on the engine.
50-
*
51-
* [#4251] The slot's ledger entry is `IDataEngine` and it covers every read and
52-
* write below; it does not cover `registerHook` / `registerMiddleware`, and no
53-
* contract has been written for the wider ObjectQL surface yet (the standing
54-
* record of why is on `getObjectQL` in `@objectstack/runtime`'s
55-
* `DomainHandlerContext`). Declared here, narrow and named, so the extension is
56-
* legible instead of hidden under `any` — and so it is deleted, not migrated,
57-
* when that contract lands.
58-
*
59-
* Both members are optional and every call site already guards with
60-
* `typeof … === 'function'`: the slot is satisfiable by engines that implement
61-
* neither (mock mode), and this plugin degrades rather than fails there.
62-
*/
63-
interface EngineExtensionSurface {
64-
registerHook?(
65-
event: string,
66-
handler: (context: any) => Promise<void> | void,
67-
options?: { object?: string | string[]; priority?: number; packageId?: string },
68-
): void;
69-
registerMiddleware?(
70-
fn: (opCtx: any, next: () => Promise<void>) => Promise<void>,
71-
options?: { object?: string },
72-
): void;
73-
}
74-
75-
/** The engine as this plugin uses it: the data contract plus those two seams. */
76-
type AuthEngine = IDataEngine & EngineExtensionSurface;
7747

7848
/**
7949
* The `settings` slot, as this plugin reads it.
@@ -764,7 +734,7 @@ export class AuthPlugin implements Plugin {
764734
// to platform admin" case where kernel:ready fired before any user
765735
// existed (same wiring the multi-org bootstrap uses).
766736
try {
767-
const ql = ctx.getService<AuthEngine>('objectql');
737+
const ql = ctx.getService<IObjectQLEngine>('objectql');
768738
if (ql && typeof ql.registerMiddleware === 'function') {
769739
ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {
770740
await next();
@@ -841,7 +811,7 @@ export class AuthPlugin implements Plugin {
841811
try {
842812
// Use the kernel's ObjectQL engine (available + hookable at kernel:ready);
843813
// the auth manager's getDataEngine() is not yet wired this early.
844-
const engine = ctx.getService<AuthEngine>('objectql');
814+
const engine = ctx.getService<IObjectQLEngine>('objectql');
845815
if (!engine || typeof engine.registerHook !== 'function') return;
846816
const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] };
847817
engine.registerHook('afterInsert', async (hookCtx: any) => {
@@ -883,7 +853,7 @@ export class AuthPlugin implements Plugin {
883853
// bypass — see identity-write-guard.ts for the full contract.
884854
ctx.hook('kernel:ready', async () => {
885855
try {
886-
const engine = ctx.getService<AuthEngine>('objectql');
856+
const engine = ctx.getService<IObjectQLEngine>('objectql');
887857
if (!engine || typeof engine.registerHook !== 'function') return;
888858
registerManagedUpdateWhitelist(SystemObjectName.USER, SYS_USER_PROFILE_EDIT_FIELDS);
889859
// [ADR-0105 D7] Extension fields ObjectStack adds to better-auth-managed
@@ -910,7 +880,7 @@ export class AuthPlugin implements Plugin {
910880

911881
// Register auth middleware on ObjectQL engine (if available)
912882
try {
913-
const ql = ctx.getService<AuthEngine>('objectql');
883+
const ql = ctx.getService<IObjectQLEngine>('objectql');
914884
if (ql && typeof ql.registerMiddleware === 'function') {
915885
ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {
916886
// If context already has userId or isSystem, skip auth resolution

packages/plugins/plugin-hono-server/src/current-user-endpoints.ts

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
type EnableLike,
4646
} from '@objectstack/spec/data';
4747
import type { ExecutionContext } from '@objectstack/spec/kernel';
48-
import type { IAuthService, IMetadataService, Logger } from '@objectstack/spec/contracts';
48+
import type { IAuthService, IMetadataService, IObjectQLEngine, Logger } from '@objectstack/spec/contracts';
4949
import { allowPerfDisclosure, isPerfDisclosurePrincipal } from '@objectstack/observability';
5050

5151
/** API prefix these endpoints mount under unless the host overrides it. */
@@ -261,37 +261,6 @@ export function foldWildcardSuperUser(objects: Record<string, any>): void {
261261
}
262262
}
263263

264-
/**
265-
* The `objectql` slot BEYOND `IDataEngine` — the schema registry these
266-
* endpoints read.
267-
*
268-
* [#4251] The slot's ledger entry is `IDataEngine` (ObjectQL registers the SAME
269-
* instance under `data` and `objectql`), and that covers the reads below. It
270-
* does NOT cover `registry` / `getSchema`, and the honest record of why lives
271-
* on `getObjectQL` in `@objectstack/runtime`'s `DomainHandlerContext`: ObjectQL
272-
* is genuinely wider than `IDataEngine`, nobody has written a contract for the
273-
* wider part, and typing the whole thing `IDataEngine` would be "the more
274-
* comfortable-looking lie". So the extra surface is declared here, named and
275-
* narrow, instead of the lookup being erased to `any` — the wider contract, when
276-
* someone writes it, absorbs this and the declaration is deleted.
277-
*
278-
* Every member is optional and every call site probes with `?.`: the slot is
279-
* satisfied by engines with no registry at all (test fakes, remote engines), and
280-
* these endpoints degrade rather than fail when it is absent.
281-
*/
282-
interface EngineRegistrySurface {
283-
/**
284-
* The engine's schema registry — the PUBLIC accessor. ObjectQL exposes it as
285-
* `get registry()` over the private `_registry` field; `_registry` is what
286-
* `/me/apps` used to reach through `as any`, two handlers away from the
287-
* `/auth/me/permissions` reach for the public one, for the same object.
288-
*/
289-
readonly registry?: {
290-
getAllObjects?(): ApiExposureSchemaLike[];
291-
getAllApps?(): unknown[];
292-
};
293-
getSchema?(objectName: string): unknown;
294-
}
295264

296265
/**
297266
* The `security.permissions` slot, as these two handlers use it.
@@ -769,7 +738,7 @@ export function registerCurrentUserEndpoints(
769738
// (created via the admin UI as `sys_permission_set`
770739
// rows) that aren't in metadata or bootstrap.
771740
const ql = (() => {
772-
try { return ctx.getService<IDataEngine & EngineRegistrySurface>('objectql') ?? null; }
741+
try { return ctx.getService<IObjectQLEngine>('objectql') ?? null; }
773742
catch { return null; }
774743
})();
775744
const dbLoader = ql
@@ -897,9 +866,12 @@ export function registerCurrentUserEndpoints(
897866
// can attach their effective apiOperations. Guarded — a failure
898867
// here must never drop the whole response.
899868
try {
900-
const allSchemas: ApiExposureSchemaLike[] = (() => {
901-
try { return ql?.registry?.getAllObjects?.() ?? []; }
902-
catch { return []; }
869+
// The contract's registry view returns `unknown[]` (schema
870+
// shape is engine-local); narrow to the slice this seeding
871+
// reads, as the callers of getSchema below already do.
872+
const allSchemas = (() => {
873+
try { return (ql?.registry?.getAllObjects?.() ?? []) as ApiExposureSchemaLike[]; }
874+
catch { return [] as ApiExposureSchemaLike[]; }
903875
})();
904876
seedSuperUserRestrictedObjects(objects, allSchemas);
905877
} catch (e: any) {
@@ -980,7 +952,7 @@ export function registerCurrentUserEndpoints(
980952
// private `_registry` while `/auth/me/permissions` read the
981953
// `registry` getter over the same field, on the same object,
982954
// in the same file — visible only once both were typed.
983-
const registry = ctx.getService<EngineRegistrySurface>('objectql')?.registry;
955+
const registry = ctx.getService<IObjectQLEngine>('objectql')?.registry;
984956
for (const app of registry?.getAllApps?.() ?? []) {
985957
if ((app as { name?: unknown })?.name) byName.set(String((app as { name: unknown }).name), app);
986958
}

0 commit comments

Comments
 (0)