fix: normalize uncharacterized read failures in the mock Studio gateway
`idempotent()` was fixed to wrap anything `work()` throws, with a documented rationale: this port's contract is `StudioGatewayError` only, so nothing else may cross it. Its `read()` sibling was left unwrapped, so an uncharacterized internal failure on any of the seven read operations escaped as a raw `Error` and reached UI code written to catch `StudioGatewayError`. `read()` now applies the same `failureOf` classification. It has no idempotency ledger, so only the problem half is used. `boundary()` stays outside the wrap so an aborted request still surfaces as `AbortError`, exactly as `idempotent()` arranges it -- asserted by the new test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e4f9f81a3f
commit
e2a0e695dd
@@ -104,7 +104,19 @@ export function createMockStudioGateway(supplied: Partial<MockStudioDependencies
|
||||
async function boundary(options?: RequestOptions) {
|
||||
options?.signal?.throwIfAborted(); await Promise.resolve(); options?.signal?.throwIfAborted();
|
||||
}
|
||||
async function read<T>(options: RequestOptions | undefined, work: () => T) { await boundary(options); return clone(work()); }
|
||||
async function read<T>(options: RequestOptions | undefined, work: () => T) {
|
||||
// `boundary` stays outside the wrap so an abort still surfaces as
|
||||
// `AbortError`, exactly as `idempotent()` arranges it.
|
||||
await boundary(options);
|
||||
try { return clone(work()); }
|
||||
catch (error) {
|
||||
// Same reason as `idempotent()`'s catch below: this port's contract is
|
||||
// `StudioGatewayError` only. A read has no idempotency ledger, so the
|
||||
// `replayable` half of the classification has nothing to record here --
|
||||
// only the problem is used.
|
||||
throw new StudioGatewayError(clone(failureOf(error).problem));
|
||||
}
|
||||
}
|
||||
async function idempotent<T>(operation: string, target: string, request: () => unknown, options: IdempotentOptions, work: () => T): Promise<T> {
|
||||
await boundary(options);
|
||||
if (typeof options.idempotencyKey !== "string" || cp(options.idempotencyKey) < 1 || cp(options.idempotencyKey) > 200) throw requestError([{ path: "/idempotencyKey", message: "Idempotency key must be 1-200 characters." }]);
|
||||
|
||||
Reference in New Issue
Block a user