Skip to content

fix(wire): round-trip the core error taxonomy over the wire#82

Merged
cuibonobo merged 2 commits into
mainfrom
claude/relaxed-cerf-487g2s
Jul 10, 2026
Merged

fix(wire): round-trip the core error taxonomy over the wire#82
cuibonobo merged 2 commits into
mainfrom
claude/relaxed-cerf-487g2s

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

Closes #53.

APIAdapter collapsed StackPermissionError / StackNotFoundError / StackConflictError / StackValidationError down to a generic APIAdapterError with just an HTTP status code. App code that does catch (e) { if (e instanceof StackPermissionError) ... } worked against adapter-local and silently fell through to throw against adapter-api — breaking the "swap backends without changing your app" promise, and specifically undermining #48's opt-in conflict handling and #51's 403-vs-404 anti-oracle distinction.

  • wire-types: new WireError body ({ error: { code, message, details? } }), WireErrorCode union, a code↔status table, and serializeError / deserializeError / errorForStatus helpers so servers and APIAdapter share one mapping instead of hand-maintained switches.
  • core: each Stack*Error class now exposes a static code. Added StackQueryError (code bad_request) for structurally malformed input — distinct from StackValidationError's content-schema failures.
  • adapter-api: request() / requestBinary() / uploadBinary() reconstruct the typed core error from the response body (code is authoritative), falling back to status-based reconstruction for foreign/legacy servers, then to a generic APIAdapterError. Status 500 is deliberately excluded from the status-only fallback so an unrelated server crash isn't misclassified as StackMigrationError.
  • record-adapter-sqljs: cursor decode now throws StackQueryError (was a bare Error) for an unknown sort field, non-numeric value, or malformed/undecodable blob, so a server can map it to 400 instead of a bare 500. Also fixes a latent bug where a cursor with no | separator left cursorId silently undefined instead of erroring.
  • conformance-fixtures: new fixtures pinning the wire error body for 403/404/409/422/400, driven end-to-end against APIAdapter in tests — this is the enforcement mechanism APIAdapter collapses the error taxonomy: Stack* errors never survive the wire round trip #53 asked for so the mapping can't drift silently again.
  • docs/spec.md: documents the wire error body contract, why 500/StackMigrationError is reserved-but-never-inferred, and the cursor 400 behavior.

Test plan

  • pnpm -r build — all packages compile
  • pnpm -r typecheck — clean
  • pnpm -r lint — clean
  • pnpm format:check — clean
  • pnpm -r test — all suites pass (core 264, record-adapter-sqljs 79, adapter-api 83, adapter-local 17, blob-adapter-disk 12)
  • New tests: adapter-api error-taxonomy reconstruction (per-code + status-fallback + 500-exclusion), conformance-fixture-driven error round-trips, sqljs malformed-cursor cases

https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx


Generated by Claude Code

claude added 2 commits July 10, 2026 13:52
APIAdapter collapsed StackPermissionError/StackNotFoundError/
StackConflictError/StackValidationError to a generic APIAdapterError with
just a status code, so app-level `instanceof StackPermissionError` handling
worked against adapter-local but silently broke against adapter-api.

- wire-types: define the WireError body ({ error: { code, message,
  details? } }), a code<->status table, and serializeError/deserializeError/
  errorForStatus helpers shared by servers and APIAdapter.
- core: give each Stack*Error class a static `code` so serialization is
  mechanical; add StackQueryError (code bad_request) for structurally
  malformed input like an undecodable pagination cursor, distinct from
  StackValidationError's content-schema failures.
- adapter-api: reconstruct the typed error from the wire body, falling back
  to status-based reconstruction for foreign/legacy servers, then to a
  generic APIAdapterError. 500 is deliberately excluded from status
  fallback so an unrelated server crash isn't misclassified as
  StackMigrationError.
- record-adapter-sqljs: cursor decode now throws StackQueryError (was a
  generic Error) for an unknown sort field, non-numeric value, or malformed
  blob, so a server can map it to 400 instead of a bare 500.
- conformance-fixtures: pin the wire error body for 403/404/409/422/400 so
  APIAdapter and any server implementation can't drift on the mapping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx
request()'s error-reconstruction now imports isWireError/deserializeError/
errorForStatus as values (not just types) from @haverstack/wire-types.
CI's test job runs before any build step, so wire-types' dist/ doesn't
exist yet and vite fails to resolve the package entry. @haverstack/core
and @haverstack/conformance-fixtures already had this alias for the same
reason; wire-types just needed the same treatment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx
@cuibonobo cuibonobo merged commit a86bb69 into main Jul 10, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/relaxed-cerf-487g2s branch July 10, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

APIAdapter collapses the error taxonomy: Stack* errors never survive the wire round trip

2 participants