refactor(query): deepen dispatch policy seam with structured result contract (#3066)

* refactor(query): deepen dispatch policy seam with structured result contract

Closes #3065.

- unify query dispatch outcome as typed success/failure union
- include error kind/details + final exit_code in failure path
- align native and fallback paths under one dispatch policy seam
- make CLI query path consume seam result (thin adapter)
- add ADR + context term for Dispatch Policy Module

* refactor(query): strengthen dispatch seam with shared error mapper and typed details

- add query-dispatch-error-mapper module shared by native/fallback paths
- remove ad-hoc inline mapping in dispatch/fallback executors
- lock error-details schema in mapper + dispatch tests
- document structured dispatch contract in QUERY-HANDLERS.md

* fix(query): return structured fallback failure when path resolution throws

- guard resolveGsdToolsPath in cjs dispatch path
- map thrown resolution errors to fallback_failure result
- add regression test for structured failure contract
This commit is contained in:
Tom Boucher
2026-05-03 14:30:27 -04:00
committed by GitHub
parent 5975f06b6a
commit f104dab332
12 changed files with 297 additions and 51 deletions

View File

@@ -0,0 +1,51 @@
import type { QueryDispatchError, QueryDispatchErrorKind, QueryDispatchResult } from './query-dispatch-contract.js';
export function toDispatchFailure(
error: QueryDispatchError,
stderr: string[] = [],
): QueryDispatchResult {
return {
ok: false,
stderr,
exit_code: error.code,
error,
};
}
export function mapNativeDispatchError(error: unknown, command: string, args: string[]): QueryDispatchError {
const message = error instanceof Error ? error.message : String(error);
const kind: QueryDispatchErrorKind = message.includes('timed out after')
? 'native_timeout'
: 'native_failure';
return {
kind,
code: 1,
message: `Error: ${message}`,
details: {
command,
args,
...(kind === 'native_timeout' ? { timeout_ms: parseTimeoutMs(message) } : {}),
},
};
}
export function mapFallbackDispatchError(error: unknown, command: string, args: string[]): QueryDispatchError {
const message = error instanceof Error ? error.message : String(error);
return {
kind: 'fallback_failure',
code: 1,
message: `Error: gsd-tools.cjs fallback failed: ${message}`,
details: {
command,
args,
backend: 'cjs',
},
};
}
function parseTimeoutMs(message: string): number | undefined {
const m = message.match(/timed out after\s+(\d+)ms/i);
if (!m) return undefined;
const n = Number.parseInt(m[1], 10);
return Number.isFinite(n) ? n : undefined;
}