test(sdk): enforce runtime bridge seam and explicit no-fallback behavior

This commit is contained in:
Tom Boucher
2026-05-05 19:31:03 -04:00
parent 54b06e653e
commit 00ba404b60
3 changed files with 57 additions and 1 deletions

View File

@@ -234,6 +234,32 @@ describe('GSDTransport', () => {
expect(adapters.execSubprocessJson).toHaveBeenCalledOnce();
});
it('fails when command is unregistered and subprocess fallback is disabled', async () => {
const registry = new QueryRegistry();
const adapters = {
dispatchNative: vi.fn(async () => ({ data: { ok: true } })),
execSubprocessJson: vi.fn(async () => ({ ok: 'fallback' })),
execSubprocessRaw: vi.fn(async () => 'fallback-raw'),
};
const transport = new GSDTransport(registry, adapters);
await expect(transport.run({
legacyCommand: 'unknown',
legacyArgs: [],
registryCommand: 'unknown',
registryArgs: [],
mode: 'json',
projectDir: '/tmp',
}, {
preferNative: true,
allowFallbackToSubprocess: false,
})).rejects.toThrow("Subprocess fallback disabled");
expect(adapters.execSubprocessJson).not.toHaveBeenCalled();
});
it('forces raw subprocess path when workstream present and mode is raw', async () => {
const registry = new QueryRegistry();
registry.register('commit', async () => ({ data: { hash: 'abc' } }));

View File

@@ -2,6 +2,7 @@ import type { QueryResult } from './query/utils.js';
import type { QueryRegistry } from './query/registry.js';
import type { TransportMode } from './gsd-transport-policy.js';
import { toFailureSignal } from './query-failure-classification.js';
import { GSDToolsError } from './gsd-tools-error.js';
export interface TransportRequest {
legacyCommand: string;
@@ -52,7 +53,16 @@ export class GSDTransport {
onDecision?.({ dispatchMode: 'subprocess', reason: 'native_failure_fallback' });
}
} else {
onDecision?.({ dispatchMode: 'subprocess', reason: this.subprocessReason(request, policy) });
const reason = this.subprocessReason(request, policy);
onDecision?.({ dispatchMode: 'subprocess', reason });
if (!policy.allowFallbackToSubprocess) {
throw GSDToolsError.failure(
`Subprocess fallback disabled: command '${request.registryCommand}' cannot run without native dispatch`,
request.legacyCommand,
request.legacyArgs,
null,
);
}
}
return this.dispatchSubprocess(request);

View File

@@ -0,0 +1,20 @@
import { describe, it, expect, vi } from 'vitest';
import { createGSDToolsRuntime } from './query-gsd-tools-runtime.js';
describe('SDK Runtime Bridge seam coverage', () => {
it('exposes bridge as the single runtime seam', () => {
const runtime = createGSDToolsRuntime({
projectDir: '/tmp/project',
gsdToolsPath: '/tmp/gsd-tools.cjs',
timeoutMs: 1_000,
shouldUseNativeQuery: () => true,
execJsonFallback: vi.fn(async () => ({})),
execRawFallback: vi.fn(async () => ''),
});
expect(Object.keys(runtime)).toEqual(['bridge']);
expect(typeof runtime.bridge.resolve).toBe('function');
expect(typeof runtime.bridge.execute).toBe('function');
expect(typeof runtime.bridge.dispatchHotpath).toBe('function');
});
});