import { createServer } from 'node:http' import type { AddressInfo } from 'node:net' import { describe, expect, it } from 'vitest' import { Context, Service, symbols } from '@deepseek-ai/cordis' import { z } from 'zod' import { apply as applyConnection, inject as connectionInject } from '@deepseek-ai/dsh-client-connection' import type { HttpServerService, WebRoute } from '@deepseek-ai/dsh-host-webserver' import { bindTypeRTGateway, Remote, RemoteScope, TypeRTLookupFailure, type InvocationDescriptor, type TypeRTContext, type TypeRTLookup, type TypeRTLookupProvider, } from '@deepseek-ai/dsh-type-meta' import TypertRegistry, { type TypertContribution } from '@deepseek-ai/dsh-typert-registry' import TypertGatewayService, { TypertGatewayError } from '@deepseek-ai/dsh-api-gateway' interface FixtureAgent { readonly id: string } interface MarkedContext extends Context { readonly fixtureScope?: string } declare module '@deepseek-ai/dsh-type-meta' { interface TypeRTLookupMap { gatewayFixture: TypeRTLookup gatewayFixtureAlias: TypeRTLookup } interface TypeRTContextMap { gatewayFixture: TypeRTContext } } const emptyModel: TypertContribution['model'] = { services: [], events: [], objects: [], } class GoalService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'goals') readonly calls: string[] = [] lastSignal: AbortSignal | undefined nextResult: unknown = undefined businessError: Error | undefined constructor(ctx: Context) { super(ctx, 'goals') } @Remote create(agent: FixtureAgent, request: { readonly title: string }, signal: AbortSignal): unknown { this.calls.push('create') this.lastSignal = signal return { agentId: agent.id, title: request.title, scope: (this.ctx as MarkedContext).fixtureScope ?? 'root', } } @RemoteScope('gatewayFixture') rename(request: { readonly title: string }): unknown { this.calls.push('rename') return { title: request.title, scope: (this.ctx as MarkedContext).fixtureScope ?? 'root' } } @Remote passthrough(value: unknown): unknown { this.calls.push('passthrough') return this.nextResult === undefined ? value : this.nextResult } @Remote fail(request: unknown): never { void request this.calls.push('fail') throw this.businessError ?? new Error('fixture business failure') } strictOnly(request: { readonly title: string }): unknown { this.calls.push('strictOnly') return this.nextResult === undefined ? request : this.nextResult } } type FakeRpcResult = | { readonly ok: true; readonly value: unknown } | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: object } } type FakeRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise class FakeConnectionService extends Service { channel: string | undefined authority: string | undefined matches: ((endpoint: string) => boolean) | undefined handler: FakeRpcHandler | undefined constructor(ctx: Context) { super(ctx, 'connection') } get rpc() { const owner = this.ctx return { intercept: ( channel: string, matches: (endpoint: string) => boolean, handler: FakeRpcHandler, options: { readonly authority: string }, ) => owner.effect(() => { this.channel = channel this.authority = options.authority this.matches = matches this.handler = handler return () => { this.channel = undefined this.authority = undefined this.matches = undefined this.handler = undefined } }), } } } function fakeHttpServer(routes: WebRoute[]): Pick { return { register(route) { if (routes.some(candidate => candidate.kind === route.kind && candidate.path === route.path)) { throw new Error(`duplicate route ${route.path}`) } routes.push(route) return () => { routes.splice(routes.indexOf(route), 1) } }, tapIndex: () => () => {}, port: 0, } } async function serveRoute(route: WebRoute): Promise<{ readonly origin: string; close(): Promise }> { const server = createServer((request, response) => { void route.handler(request, response) }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) const address = server.address() as AddressInfo return { origin: `http://127.0.0.1:${String(address.port)}`, close: () => new Promise((resolve, reject) => { server.close((error) => { if (error === undefined || error === null) resolve() else reject(error) }) }), } } class FirstSharedService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'firstShared', { namespace: 'shared' }) constructor(ctx: Context) { super(ctx, 'firstShared') } @Remote run(value: string): string { return value } } class SecondSharedService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'secondShared', { namespace: 'shared' }) constructor(ctx: Context) { super(ctx, 'secondShared') } @Remote run(value: string): string { return value } } class DefaultParameterService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'defaultParameter', { namespace: 'invalid-default' }) constructor(ctx: Context) { super(ctx, 'defaultParameter') } @Remote run(value = 'fallback'): string { return value } } class DestructuredParameterService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'destructuredParameter', { namespace: 'invalid-destructure' }) constructor(ctx: Context) { super(ctx, 'destructuredParameter') } @Remote run({ value }: { readonly value: string }): string { return value } } class RestParameterService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'restParameter', { namespace: 'invalid-rest' }) constructor(ctx: Context) { super(ctx, 'restParameter') } @Remote run(...values: readonly unknown[]): string { return values.map(String).join(',') } } class NonFinalSignalService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'nonFinalSignal', { namespace: 'invalid-signal' }) constructor(ctx: Context) { super(ctx, 'nonFinalSignal') } @Remote run(signal: AbortSignal, value: string): string { return signal.aborted ? '' : value } } class WrongBindingService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'notWrongBinding', { namespace: 'wrong-binding' }) constructor(ctx: Context) { super(ctx, 'wrongBinding') } @Remote run(value: string): string { return value } } class ExportedMethodService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'exportedMethod', { namespace: 'exported' }) constructor(ctx: Context) { super(ctx, 'exportedMethod') } @Remote('execute') run(value: string): string { return value } } class EmptyMethodService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'emptyMethod', { namespace: 'empty' }) constructor(ctx: Context) { super(ctx, 'emptyMethod') } @Remote ping(): string { return 'pong' } } class CollidingWireService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'collidingWire', { namespace: 'colliding-wire' }) constructor(ctx: Context) { super(ctx, 'collidingWire') } @Remote run(agent: FixtureAgent, agentId: string): string { return `${agent.id}:${agentId}` } } class ContextWireService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'contextWire', { namespace: 'context-wire' }) constructor(ctx: Context) { super(ctx, 'contextWire') } @RemoteScope('gatewayFixture') run(agentId: string): string { return agentId } } class NoBindingService extends Service { constructor(ctx: Context) { super(ctx, 'noBinding') } run(value: string): string { return value } } class ObservedClaimService extends Service { private readonly binding = bindTypeRTGateway(this, 'observedClaim', { namespace: 'observed-claim' }) bindingReads = 0 constructor(ctx: Context) { super(ctx, 'observedClaim') } get typertGateway() { this.bindingReads += 1 return this.binding } @Remote run(value: string): string { return value } } class MissingMethodService extends Service { readonly typertGateway = bindTypeRTGateway(this, 'missingMethod', { namespace: 'missing-method' }) constructor(ctx: Context) { super(ctx, 'missingMethod') } @Remote run(value: string): string { return value } } class InheritedMethodBase extends Service { readonly typertGateway = bindTypeRTGateway(this, 'inheritedMethod', { namespace: 'inherited' }) constructor(ctx: Context) { super(ctx, 'inheritedMethod') } @Remote run(value: string): string { return value } } class InheritedMethodService extends InheritedMethodBase {} describe('TypertGatewayService', () => { it('invokes a strict direct method with schema decoding and a live lookup', async () => { const { ctx, service } = await setup() const agent = { id: 'agent-1' } registerAgentLookup(ctx, agent) registerStrict(ctx, [createDescriptor()]) const caller = ctx.extend({ fixtureScope: 'direct-caller' }) const abort = new AbortController() await expect(caller.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: ' ship ' } }, signal: abort.signal, })).resolves.toEqual({ agentId: 'agent-1', title: 'ship', scope: 'direct-caller' }) expect(service.calls).toEqual(['create']) expect(service.lastSignal).toBe(abort.signal) await expect(caller.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'again' } }, })).resolves.toEqual({ agentId: 'agent-1', title: 'again', scope: 'direct-caller' }) expect(service.lastSignal).toBeInstanceOf(AbortSignal) expect(service.lastSignal?.aborted).toBe(false) }) it('resolves strict Remote Scope identity without adding a business argument', async () => { const { ctx, service } = await setup() const scoped = ctx.extend({ fixtureScope: 'agent-scope' }) ctx.typert.contexts.registerHost('gatewayFixture', contextProvider(scoped)) registerStrict(ctx, [renameDescriptor()]) await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, })).resolves.toEqual({ title: 'land', scope: 'agent-scope' }) expect(service.calls).toEqual(['rename']) }) it('derives SRC direct lookup and JSON parameters from marker and parameter names', async () => { const { ctx, service } = await setup() const agent = { id: 'agent-1' } registerAgentLookup(ctx, agent) const caller = ctx.extend({ fixtureScope: 'direct-src' }) const abort = new AbortController() await expect(caller.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, signal: abort.signal, })).resolves.toEqual({ agentId: 'agent-1', title: 'ship', scope: 'direct-src' }) expect(service.lastSignal).toBe(abort.signal) }) it('does not downgrade an observed SRC lookup after its provider unloads', async () => { const { ctx, service } = await setup() const dispose = registerAgentLookup(ctx, { id: 'agent-1' }) await dispose() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'lookup-unavailable') expect(service.calls).toEqual([]) }) it('derives SRC Remote Scope identity and preserves the scoped Proxy receiver', async () => { const { ctx } = await setup() const scoped = ctx.extend({ fixtureScope: 'agent-src' }) ctx.typert.contexts.registerHost('gatewayFixture', contextProvider(scoped)) await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, })).resolves.toEqual({ title: 'land', scope: 'agent-src' }) }) it('derives exported, empty, inherited, and distinct-namespace SRC methods', async () => { const ctx = await setupGateway() await ctx.plugin(ExportedMethodService) await ctx.plugin(EmptyMethodService) await ctx.plugin(InheritedMethodService) await expect(ctx.typertGateway.invoke({ namespace: 'exported', method: 'execute', args: { value: 'ship' }, })).resolves.toBe('ship') await expect(ctx.typertGateway.invoke({ namespace: 'empty', method: 'ping', args: {}, })).resolves.toBe('pong') await expect(ctx.typertGateway.invoke({ namespace: 'inherited', method: 'run', args: { value: 'land' }, })).resolves.toBe('land') await expectCode(ctx.typertGateway.invoke({ namespace: 'other', method: 'absent', args: {}, }), 'invocation-unavailable') }) it('rejects SRC wire collisions and unavailable Context providers', async () => { const colliding = await setupGateway() await colliding.plugin(CollidingWireService) registerAgentLookup(colliding, { id: 'agent-1' }) await expectCode(colliding.typertGateway.invoke({ namespace: 'colliding-wire', method: 'run', args: { agentId: 'agent-1' }, }), 'signature-invalid') const missing = await setup() await expectCode(missing.ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, }), 'context-unavailable') const contextCollision = await setupGateway() await contextCollision.plugin(ContextWireService) contextCollision.typert.contexts.registerHost('gatewayFixture', contextProvider(contextCollision.extend())) await expectCode(contextCollision.typertGateway.invoke({ namespace: 'context-wire', method: 'run', args: { agentId: 'agent-1' }, }), 'signature-invalid') }) it('re-reads Service and providers on every strict invocation', async () => { const { ctx, serviceFiber } = await setup() const agent = { id: 'agent-1' } const disposeLookup = registerAgentLookup(ctx, agent) registerStrict(ctx, [createDescriptor()]) await disposeLookup() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'lookup-unavailable') registerAgentLookup(ctx, agent) await serviceFiber.dispose() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'service-unavailable') }) it('re-reads and contains Context providers', async () => { const { ctx } = await setup() const scoped = ctx.extend() const dispose = ctx.typert.contexts.registerHost('gatewayFixture', contextProvider(scoped)) registerStrict(ctx, [renameDescriptor()]) await dispose() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, }), 'context-unavailable') ctx.typert.contexts.registerHost('gatewayFixture', { ...contextProvider(scoped), resolve: () => { throw new Error('provider failed') }, }) const error = await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, }), 'context-failed') expect(error.cause).toEqual(new Error('provider failed')) }) it('preserves a Host Context policy rejection for the active RPC adapter', async () => { const { ctx } = await setup() const rejection = new TypeRTLookupFailure({ code: 'agent-busy', message: 'owned', details: { reason: 'subagent' } }) ctx.typert.contexts.registerHost('gatewayFixture', { ...contextProvider(ctx.extend()), resolve: async () => { throw rejection }, }) registerStrict(ctx, [renameDescriptor()]) await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, })).rejects.toBe(rejection) }) it('reports Context provider metadata mismatch and unresolved identities', async () => { const { ctx } = await setup() registerStrict(ctx, [renameDescriptor()]) const scoped = ctx.extend() const mismatch = ctx.typert.contexts.registerHost('gatewayFixture', { ...contextProvider(scoped), wire: 'differentAgentId', }) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, }), 'provider-mismatch') await mismatch() ctx.typert.contexts.registerHost('gatewayFixture', { ...contextProvider(scoped), resolve: () => undefined, }) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'rename', args: { agentId: 'agent-1', request: { title: 'land' } }, }), 'context-not-found') }) it('contains lookup provider failures and missing identities', async () => { const { ctx } = await setup() registerStrict(ctx, [createDescriptor()]) const throwing = ctx.typert.lookups.register('gatewayFixture', { ...agentLookup({ id: 'agent-1' }), resolve: async () => { throw new Error('lookup failed') }, }) const failure = await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'lookup-failed') expect(failure.cause).toEqual(new Error('lookup failed')) await throwing() const missing = ctx.typert.lookups.register('gatewayFixture', { ...agentLookup({ id: 'agent-1' }), resolve: () => Promise.resolve(undefined), }) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'lookup-not-found') await missing() ctx.typert.lookups.register('gatewayFixture', { ...agentLookup({ id: 'agent-1' }), resolve: async id => ({ id }), }) await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, })).resolves.toMatchObject({ agentId: 'agent-1', title: 'ship' }) }) it('never downgrades an observed strict endpoint after definition disposal', async () => { const { ctx } = await setup() const dispose = registerStrict(ctx, [passthroughDescriptor()]) await dispose() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: 'would pass through SRC' }, }), 'definition-unavailable') }) it('seeds the no-downgrade guard from definitions present before Gateway startup', async () => { const ctx = new Context() await ctx.plugin(TypertRegistry) const dispose = registerStrict(ctx, [passthroughDescriptor()]) await ctx.plugin(TypertGatewayService) await ctx.plugin(GoalService) await dispose() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: 'would pass through SRC' }, }), 'definition-unavailable') }) it('retains the no-downgrade guard across Gateway Service reloads', async () => { const ctx = new Context() await ctx.plugin(TypertRegistry) const gatewayFiber = ctx.plugin(TypertGatewayService) await gatewayFiber await ctx.plugin(GoalService) const dispose = registerStrict(ctx, [passthroughDescriptor()]) await dispose() await gatewayFiber.dispose() await ctx.plugin(TypertGatewayService) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: 'would pass through SRC' }, }), 'definition-unavailable') }) it('rejects ambiguous SRC endpoints independently of reflection order', async () => { const ctx = await setupGateway() await ctx.plugin(FirstSharedService) await ctx.plugin(SecondSharedService) const error = await expectCode(ctx.typertGateway.invoke({ namespace: 'shared', method: 'run', args: { value: 'ship' }, }), 'ambiguous-endpoint') expect(error.message).toContain('firstShared, secondShared') }) it('rejects SRC signatures that cannot map one wire field to each position', async () => { const cases = [ { plugin: DefaultParameterService, namespace: 'invalid-default', args: { value: 'x' } }, { plugin: DestructuredParameterService, namespace: 'invalid-destructure', args: { value: { value: 'x' } } }, { plugin: RestParameterService, namespace: 'invalid-rest', args: { values: ['x'] } }, { plugin: NonFinalSignalService, namespace: 'invalid-signal', args: { value: 'x' } }, ] as const for (const testCase of cases) { const ctx = await setupGateway() await ctx.plugin(testCase.plugin) await expectCode(ctx.typertGateway.invoke({ namespace: testCase.namespace, method: 'run', args: testCase.args, }), 'signature-invalid') } }) it('rejects a SRC parameter matching more than one lookup provider', async () => { const { ctx } = await setup() const provider = agentLookup({ id: 'agent-1' }) ctx.typert.lookups.register('gatewayFixture', provider) ctx.typert.lookups.register('gatewayFixtureAlias', provider) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'signature-invalid') }) it('requires exact wire fields before invoking business code', async () => { const { ctx, service } = await setup() registerAgentLookup(ctx, { id: 'agent-1' }) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { request: { title: 'ship' } }, }), 'arguments-invalid') await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' }, optional: true }, }), 'arguments-invalid') await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: [] as unknown as Record, }), 'arguments-invalid') expect(service.calls).toEqual([]) }) it('distinguishes strict input and result validation failures', async () => { const { ctx, service } = await setup() registerStrict(ctx, [strictOnlyDescriptor()]) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'strictOnly', args: { request: { title: 1 } }, }), 'input-invalid') service.nextResult = { title: 1 } await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'strictOnly', args: { request: { title: 'ship' } }, }), 'result-invalid') }) it('rejects non-JSON values after strict codec validation', async () => { const { ctx, service } = await setup() const descriptor = strictOnlyDescriptor() registerStrict(ctx, [{ ...descriptor, result: strictCodec('@fixture/gateway#UnknownResult', z.unknown()), }]) service.nextResult = 1n await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'strictOnly', args: { request: { title: 'ship' } }, }), 'result-invalid') }) it.each([ undefined, Number.NaN, Number.POSITIVE_INFINITY, 1n, Symbol('value'), () => 'value', new Date(0), new Map(), [, 'sparse'], ])('rejects non-JSON SRC input %#', async (value) => { const { ctx } = await setup() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value }, }), 'input-invalid') }) it('rejects cyclic SRC input and non-JSON SRC results', async () => { const { ctx, service } = await setup() const cyclic: { self?: unknown } = {} cyclic.self = cyclic await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: cyclic }, }), 'input-invalid') service.nextResult = new Date(0) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: null }, }), 'result-invalid') }) it('accepts dense JSON and rejects decorated arrays and object properties', async () => { const { ctx } = await setup() await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value: [1, { nested: true }] }, })).resolves.toEqual([1, { nested: true }]) const sparseWithExtra = Array(1) as unknown[] & { extra?: boolean } sparseWithExtra.extra = true const symbolArray = [1] Object.defineProperty(symbolArray, Symbol('extra'), { value: true }) const symbolObject = { value: true } Object.defineProperty(symbolObject, Symbol('extra'), { value: true }) const hidden = {} Object.defineProperty(hidden, 'value', { value: true, enumerable: false }) const accessor = {} Object.defineProperty(accessor, 'value', { get: () => true, enumerable: true }) for (const value of [sparseWithExtra, symbolArray, symbolObject, hidden, accessor]) { await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'passthrough', args: { value }, }), 'input-invalid') } }) it('validates strict provider identity against generated wire metadata', async () => { const { ctx } = await setup() ctx.typert.lookups.register('gatewayFixture', { ...agentLookup({ id: 'agent-1' }), wire: 'differentAgentId', }) registerStrict(ctx, [createDescriptor()]) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'create', args: { agentId: 'agent-1', request: { title: 'ship' } }, }), 'provider-mismatch') }) it('validates binding identity and active method availability', async () => { const ctx = await setupGateway() await ctx.plugin(WrongBindingService) await expectCode(ctx.typertGateway.invoke({ namespace: 'wrong-binding', method: 'run', args: { value: 'ship' }, }), 'binding-invalid') await ctx.plugin(GoalService) registerStrict(ctx, [{ ...passthroughDescriptor(), method: 'missing' }]) await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'missing', args: { value: 'ship' }, }), 'method-unavailable') }) it('requires a visible binding and supports explicitly provided plain Services', async () => { const ctx = await setupGateway() await ctx.plugin(NoBindingService) registerStrict(ctx, [{ ...passthroughDescriptor(), id: '@fixture/gateway#no-binding/run', service: 'noBinding', namespace: 'no-binding', method: 'run', }]) await expectCode(ctx.typertGateway.invoke({ namespace: 'no-binding', method: 'run', args: { value: 'ship' }, }), 'binding-invalid') const plain: { typertGateway?: ReturnType run(value: string): string } = { run: value => value } plain.typertGateway = bindTypeRTGateway(plain, 'plainRemote', { namespace: 'plain' }) ctx.provide('plainRemote', plain) ctx.typert.register({ package: '@fixture/plain', face: 'host', schemas: [], model: emptyModel, invocations: [{ ...passthroughDescriptor(), id: '@fixture/plain#plain/run', service: 'plainRemote', namespace: 'plain', method: 'run', }], }) await expect(ctx.typertGateway.invoke({ namespace: 'plain', method: 'run', args: { value: 'land' }, })).resolves.toBe('land') }) it('reports a SRC marker whose prototype implementation disappeared', async () => { const ctx = await setupGateway() await ctx.plugin(MissingMethodService) const descriptor = Object.getOwnPropertyDescriptor(MissingMethodService.prototype, 'run')! Object.defineProperty(MissingMethodService.prototype, 'run', { configurable: true, value: 42, }) try { await expectCode(ctx.typertGateway.invoke({ namespace: 'missing-method', method: 'run', args: { value: 'ship' }, }), 'method-unavailable') } finally { Object.defineProperty(MissingMethodService.prototype, 'run', descriptor) } }) it('preserves business exception identity after invocation begins', async () => { const { ctx, service } = await setup() const failure = new Error('business identity') service.businessError = failure await expect(ctx.typertGateway.invoke({ namespace: 'goals', method: 'fail', args: { request: { reason: 'fixture' } }, })).rejects.toBe(failure) }) it('reports an absent endpoint without retaining receiver state', async () => { const { ctx } = await setup() await expectCode(ctx.typertGateway.invoke({ namespace: 'goals', method: 'absent', args: {}, }), 'invocation-unavailable') }) it('mounts a shared /api interceptor through an optional Connection and returns existing RPC results', async () => { const ctx = new Context().extend({ fixtureScope: 'rpc-caller' }) await ctx.plugin(TypertRegistry) await ctx.plugin(FakeConnectionService) const gatewayFiber = ctx.plugin(TypertGatewayService) await gatewayFiber await ctx.plugin(GoalService) const connection = rawConnection(ctx) expect(connection).toMatchObject({ channel: '/api', authority: 'trusted-host' }) registerAgentLookup(ctx, { id: 'agent-1' }) registerStrict(ctx, [createDescriptor()]) expect(connection.matches?.('goals/create')).toBe(true) expect(connection.matches?.('goals/passthrough')).toBe(true) expect(connection.matches?.('goals')).toBe(false) expect(connection.matches?.('goals/missing')).toBe(false) expect(connection.matches?.('legacy/list')).toBe(false) const abort = new AbortController() const signal = abort.signal const handler = connection.handler if (handler === undefined) throw new Error('fixture Connection did not retain the /api interceptor') await expect(handler('goals/create', { args: { agentId: 'agent-1', request: { title: 'ship' } }, }, signal)).resolves.toEqual({ ok: true, value: { agentId: 'agent-1', title: 'ship', scope: 'rpc-caller' }, }) const service = rawGoalService(ctx) expect(service.lastSignal).toBe(signal) abort.abort(new Error('client disconnected')) expect(service.lastSignal?.aborted).toBe(true) const invalid = await handler('goals/create', { invalid: true }, signal) expect(invalid).toMatchObject({ ok: false, error: { code: 'internal' }, }) if (invalid.ok) throw new Error('invalid Remote payload unexpectedly succeeded') expect(invalid.error.message).toMatch(/exactly one plain-object args field/) for (const endpoint of ['goals', '/create', 'goals/', 'goals/create/extra']) { const result = await handler(endpoint, { args: {} }, signal) expect(result).toMatchObject({ ok: false, error: { code: 'internal' } }) if (result.ok) throw new Error('invalid Remote endpoint unexpectedly succeeded') expect(result.error.message).toContain('invalid Remote endpoint') } for (const payload of [null, [], { args: {}, extra: true }, { only: true }, { args: null }, { args: [] }]) { const result = await handler('goals/create', payload, signal) expect(result).toMatchObject({ ok: false, error: { code: 'internal' } }) if (result.ok) throw new Error('invalid Remote payload unexpectedly succeeded') expect(result.error.message).toContain('plain-object args field') } service.businessError = 'non-error failure' as unknown as Error await expect(handler('goals/fail', { args: { request: null } }, signal)).resolves.toEqual({ ok: false, error: { code: 'internal', message: 'non-error failure', details: {} }, }) await gatewayFiber.dispose() expect(connection.handler).toBeUndefined() }) it('preserves a lookup policy rejection through the Connection RPC result', async () => { const ctx = new Context() await ctx.plugin(TypertRegistry) await ctx.plugin(FakeConnectionService) await ctx.plugin(TypertGatewayService) await ctx.plugin(GoalService) registerStrict(ctx, [createDescriptor()]) const failure = { code: 'agent-busy', message: 'session is owned by subagent routing', details: { reason: 'use subagent delivery for this child session' }, } ctx.typert.lookups.register('gatewayFixture', { ...agentLookup({ id: 'agent-1' }), resolve: () => { throw new TypeRTLookupFailure(failure) }, }) const handler = rawConnection(ctx).handler if (handler === undefined) throw new Error('fixture Connection did not retain the /api interceptor') await expect(handler('goals/create', { args: { agentId: 'agent-1', request: { title: 'ship' } }, }, new AbortController().signal)).resolves.toEqual({ ok: false, error: failure }) }) it('caches SRC ownership until the Cordis Service set changes', async () => { const ctx = new Context() await ctx.plugin(TypertRegistry) await ctx.plugin(FakeConnectionService) await ctx.plugin(TypertGatewayService) const observedFiber = ctx.plugin(ObservedClaimService) await observedFiber const connection = rawConnection(ctx) const observed = ctx.get('observedClaim') as unknown as ObservedClaimService & { [symbols.original]?: ObservedClaimService } const service = observed[symbols.original] ?? observed expect(connection.matches?.('legacy/list')).toBe(false) expect(connection.matches?.('legacy/list')).toBe(false) expect(service.bindingReads).toBe(1) expect(connection.matches?.('observed-claim/run')).toBe(true) expect(connection.matches?.('observed-claim/run')).toBe(true) expect(service.bindingReads).toBe(1) const unrelatedFiber = ctx.plugin(NoBindingService) await unrelatedFiber expect(connection.matches?.('legacy/list')).toBe(false) expect(service.bindingReads).toBe(2) await observedFiber.dispose() expect(connection.matches?.('observed-claim/run')).toBe(false) await unrelatedFiber.dispose() }) it('dispatches claimed invocations through /api and leaves unclaimed endpoints to its fallback', async () => { const ctx = new Context().extend({ fixtureScope: 'http-caller' }) const routes: WebRoute[] = [] ctx.provide('httpServer', fakeHttpServer(routes) as HttpServerService) const connectionFiber = ctx.plugin({ inject: [...connectionInject], apply: applyConnection }) await connectionFiber await ctx.plugin(TypertRegistry) const gatewayFiber = ctx.plugin(TypertGatewayService) await gatewayFiber const goalFiber = ctx.plugin(GoalService) await goalFiber const removeLookup = registerAgentLookup(ctx, { id: 'agent-1' }) const removeStrict = registerStrict(ctx, [createDescriptor()]) let strictActive = true expect(routes).toHaveLength(1) const server = await serveRoute(routes[0]!) try { const response = await fetch(`${server.origin}/api/goals/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ type: 'client-request', rpcId: 'rpc-http', method: 'goals/create', payload: { args: { agentId: 'agent-1', request: { title: ' ship ' } } }, }), }) expect(response.status).toBe(200) await expect(response.json()).resolves.toEqual({ type: 'server-response', rpcId: 'rpc-http', result: { ok: true, value: { agentId: 'agent-1', title: 'ship', scope: 'http-caller' }, }, }) const invalid = await fetch(`${server.origin}/api/goals/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ type: 'client-request', rpcId: 'rpc-invalid', method: 'goals/create', payload: { invalid: true }, }), }) expect(invalid.status).toBe(200) const invalidBody = await invalid.json() as unknown expect(invalidBody).toMatchObject({ type: 'server-response', rpcId: 'rpc-invalid', result: { ok: false, error: { code: 'internal' }, }, }) expect(JSON.stringify(invalidBody)).toContain('plain-object args field') await removeStrict() strictActive = false const withdrawn = await fetch(`${server.origin}/api/goals/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ type: 'client-request', rpcId: 'rpc-withdrawn', method: 'goals/create', payload: { args: { agentId: 'agent-1', request: { title: 'ship' } } }, }), }) expect(withdrawn.status).toBe(200) const withdrawnBody = await withdrawn.json() as unknown expect(withdrawnBody).toMatchObject({ type: 'server-response', rpcId: 'rpc-withdrawn', result: { ok: false, error: { code: 'internal' }, }, }) expect(JSON.stringify(withdrawnBody)).toContain('strict definition was withdrawn') const unclaimed = await fetch(`${server.origin}/api/legacy/list`, { method: 'POST' }) expect(unclaimed.status).toBe(404) } finally { await server.close() if (strictActive) await removeStrict() await removeLookup() await goalFiber.dispose() await gatewayFiber.dispose() await connectionFiber.dispose() } expect(routes).toHaveLength(0) }) }) async function setup(): Promise<{ readonly ctx: Context readonly service: GoalService readonly serviceFiber: ReturnType }> { const ctx = await setupGateway() const serviceFiber = ctx.plugin(GoalService) await serviceFiber return { ctx, service: rawGoalService(ctx), serviceFiber } } async function setupGateway(): Promise { const ctx = new Context() await ctx.plugin(TypertRegistry) await ctx.plugin(TypertGatewayService) return ctx } function rawGoalService(ctx: Context): GoalService { const receiver = ctx.get('goals') as unknown as GoalService & { [symbols.original]?: GoalService } return receiver[symbols.original] ?? receiver } function rawConnection(ctx: Context): FakeConnectionService { const receiver = ctx.get('connection') as unknown as FakeConnectionService & { [symbols.original]?: FakeConnectionService } return receiver[symbols.original] ?? receiver } function registerStrict(ctx: Context, descriptors: readonly InvocationDescriptor[]): () => Promise { return ctx.typert.register({ package: '@fixture/gateway', face: 'host', schemas: [], model: emptyModel, invocations: descriptors, }) } function registerAgentLookup(ctx: Context, agent: FixtureAgent): () => Promise { return ctx.typert.lookups.register('gatewayFixture', agentLookup(agent)) } function agentLookup(agent: FixtureAgent): TypeRTLookupProvider { return { parameter: 'agent', wire: 'agentId', hostTypeSymbol: '@fixture/domain#Agent', wireTypeSymbol: '@fixture/domain#AgentId', resolve: id => id === agent.id ? agent : undefined, } } function contextProvider(context: Context) { return { wire: 'agentId', wireTypeSymbol: '@fixture/domain#AgentId', resolve: (id: string) => id === 'agent-1' ? context : undefined, } } function strictCodec(typeSymbol: string, schema: z.ZodType): InvocationDescriptor['result'] { return { mode: 'strict', typeSymbol, schema } } function createDescriptor(): InvocationDescriptor { return { id: '@fixture/gateway#goals/create', service: 'goals', namespace: 'goals', method: 'create', invocation: { kind: 'direct' }, parameters: [ { name: 'agent', wire: 'agentId', source: 'lookup', lookup: 'gatewayFixture', codec: strictCodec('@fixture/domain#AgentId', z.string()), }, { name: 'request', wire: 'request', source: 'json', codec: strictCodec('@fixture/gateway#CreateRequest', z.object({ title: z.string().transform(value => value.trim()), })), }, ], cancellation: { parameter: 'signal' }, result: strictCodec('@fixture/gateway#CreateResult', z.object({ agentId: z.string(), title: z.string(), scope: z.string(), })), } } function renameDescriptor(): InvocationDescriptor { return { id: '@fixture/gateway#goals/rename', service: 'goals', namespace: 'goals', method: 'rename', invocation: { kind: 'context', context: 'gatewayFixture', wire: 'agentId', codec: strictCodec('@fixture/domain#AgentId', z.string()), }, parameters: [{ name: 'request', wire: 'request', source: 'json', codec: strictCodec('@fixture/gateway#RenameRequest', z.object({ title: z.string() })), }], result: strictCodec('@fixture/gateway#RenameResult', z.object({ title: z.string(), scope: z.string(), })), } } function passthroughDescriptor(): InvocationDescriptor { return { id: '@fixture/gateway#goals/passthrough', service: 'goals', namespace: 'goals', method: 'passthrough', invocation: { kind: 'direct' }, parameters: [{ name: 'value', wire: 'value', source: 'json', codec: { mode: 'src-json' }, }], result: { mode: 'src-json' }, } } function strictOnlyDescriptor(): InvocationDescriptor { const value = strictCodec('@fixture/gateway#StrictValue', z.object({ title: z.string() })) return { id: '@fixture/gateway#goals/strictOnly', service: 'goals', namespace: 'goals', method: 'strictOnly', invocation: { kind: 'direct' }, parameters: [{ name: 'request', wire: 'request', source: 'json', codec: value }], result: value, } } async function expectCode( promise: Promise, code: TypertGatewayError['code'], ): Promise { try { await promise } catch (error) { expect(error).toBeInstanceOf(TypertGatewayError) expect(error).toMatchObject({ code }) return error as TypertGatewayError } throw new Error(`expected TypertGatewayError ${code}`) }