Skip to content

Commit 228a2f0

Browse files
committed
test(dogfood): type the federated-RLS lookups by their slot contracts (#7835)
CI runs `eslint . --no-inline-config`, so the two `eslint-disable` comments this file carried were inert on the runner and `slot-lookup/no-any-assignment` fired on both service lookups. The rule's own prescription is to stop erasing the lookup, which is what this does: ql -> IObjectQLEngine security -> ISecurityService Both are declared in `packages/spec/src/contracts/core-service-contracts.ts` as the contracts of the `objectql` and `security` slots, and `ObjectKernel .getService` is already generic over them — so the `stack.kernel as any` handle was not buying anything either and is gone with them. No assertion changed. One edge needed a narrowing rather than a type: `IObjectQLEngine.getSchema` declares `unknown` on purpose (the engine's own return is `ServiceObject | undefined`, but the contract keeps its edges loose so `spec` never depends on the engine package, and tells consumers to narrow at the call site). The PREMISE case reads `schema.external`, so it narrows to the `ServiceObject` the implementation already declares — a named spec type the package already depends on, not a re-erasure. Reverse-verified in both directions, since "the honest types compiled" is only worth something if tsc actually reads the file: dropping the `ServiceObject` narrowing turns typecheck red (TS2339 `Property 'external' does not exist`), and misspelling `getReadFilter` turns it red against `ISecurityService` (TS2551). Both confirm the contracts are live rather than inferred as `any`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6Qi9sYxhaRwj7TYiD5MWg
1 parent e0d3160 commit 228a2f0

1 file changed

Lines changed: 16 additions & 9 deletions

File tree

‎packages/qa/dogfood/test/federated-rls-injectors.dogfood.test.ts‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3737
import showcaseStack, { onEnable } from '@objectstack/example-showcase';
3838
import { bootStack, type VerifyStack } from '@objectstack/verify';
39+
import type { IObjectQLEngine, ISecurityService } from '@objectstack/spec/contracts';
40+
import type { ServiceObject } from '@objectstack/spec/data';
3941

4042
/** The federated object the showcase ships, bound to remote table `customers`. */
4143
const FEDERATED = 'showcase_ext_customer';
@@ -65,10 +67,8 @@ function mentionsOrgColumn(filter: unknown): boolean {
6567

6668
describe('[#7835] federated objects and the plugin-security tenant wall', () => {
6769
let stack: VerifyStack;
68-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
69-
let ql: any;
70-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
71-
let security: any;
70+
let ql: IObjectQLEngine;
71+
let security: ISecurityService;
7272

7373
beforeAll(async () => {
7474
// Stand up the "remote" database (the showcase's fixture provisioner), then
@@ -78,10 +78,12 @@ describe('[#7835] federated objects and the plugin-security tenant wall', () =>
7878
// PREDICATE plugin-security composes, not whether an org wall holds.
7979
await onEnable({ logger: { info() {}, warn() {} } } as never);
8080
stack = await bootStack(showcaseStack, { multiTenant: 'posture-only' });
81-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
82-
const kernel = stack.kernel as any;
83-
ql = kernel.getService('objectql');
84-
security = kernel.getService('security');
81+
// `ObjectKernel.getService` is already generic over the slot's contract, so
82+
// neither the kernel handle nor either result needs erasing: `objectql` is
83+
// `IObjectQLEngine` and `security` is `ISecurityService`, both declared in
84+
// `packages/spec/src/contracts/core-service-contracts.ts`.
85+
ql = stack.kernel.getService<IObjectQLEngine>('objectql');
86+
security = stack.kernel.getService<ISecurityService>('security');
8587
}, 120_000);
8688

8789
afterAll(async () => { await stack?.stop?.(); });
@@ -90,7 +92,12 @@ describe('[#7835] federated objects and the plugin-security tenant wall', () =>
9092
// Not an aspiration — the state of the tree this fix was written against.
9193
// `Engine.syncObjectSchema` returns early for `external != null` and issues
9294
// no DDL, so this column exists in the registry and in no table.
93-
const schema = ql.getSchema(FEDERATED);
95+
// `IObjectQLEngine.getSchema` declares `unknown` on purpose — the engine's
96+
// own return type (`ServiceObject | undefined`, `engine.ts:4766`) is a spec
97+
// type, but the contract keeps its edges loose so `spec` never depends on
98+
// the engine package, and tells consumers to narrow at the call site. This
99+
// is that narrowing, to the type the implementation already declares.
100+
const schema = ql.getSchema(FEDERATED) as ServiceObject | undefined;
94101
expect(schema?.external, `${FEDERATED} must be a federated object`).toBeTruthy();
95102
expect(fieldNames(schema)).toContain('organization_id');
96103
// The remote table's real columns, for contrast.

0 commit comments

Comments
 (0)