feat(spec)!: EnhancedApiError.fieldErrors → fields, tombstoned (ADR-0114 D4, #3977) - #4055
Merged
Merged
Conversation
…0114 D4, #3977) Completes the one thing #4035 decided but did not execute. The wire has always carried `fields`; `fieldErrors` was declared and emitted by nobody, so a reader keying on it was reading a field no server sent — ADR-0078's silently-inert declaration, on the error envelope. Tombstoned rather than deleted, because the schema is not .strict(): a plain removal would let a producer still writing the old name parse clean and lose the per-field detail, answering a validation failure that mentions no field. retiredKey() turns that into a rejection carrying the rename. Registered as a SEMANTIC chain entry, not a conversion, and the distinction is the point. A conversion rewrites author metadata; this is a response envelope, and no stack, example or template carries the key (checked across packages/, examples/, templates/, apps/). A no-op conversion with an identity fixture would claim a rewrite that does not exist. The analytics-query-request-* entries set the precedent one step earlier in the same major: an HTTP-only surface with nothing stored to rewrite is a semantic entry with a reason and an acceptance criterion — which is what actually reaches spec-changes.json, the upgrade guide and the spec_changes MCP tool. Docs followed, and two examples were teaching the opposite of this ADR: the server guide hand-rolled a Zod mapping with code: 'invalid_field' — a top-level code in a field position — and the client guide's error class carried the dead name. The server example now calls zodIssuesToFields, which is the mapping the platform's own routes use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaSQn77TT5fUgHK9CesaDK
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ntries it was missing The v17 page documents each breaking change under its own heading, and the whole ADR-0112 line had none — the largest wire-visible change in it appeared only as a passing mention 600 lines down. That is the worst gap to leave, because a missed error-code branch fails SILENTLY: nothing throws, the affordance it guarded just disappears. Eleven console branches broke that way without one test failing. Two entries: ADR-0112's vocabulary unification (with the generic-condition collapse table, the four routes that stopped putting a code in the message slot, and the case-insensitive advice for consumers spanning the upgrade), and ADR-0114's field-level catalog plus this PR's fieldErrors -> fields rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MaSQn77TT5fUgHK9CesaDK
os-zhuang
marked this pull request as ready for review
July 30, 2026 07:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executes ADR-0114 D4, the one thing #4035 decided but deliberately left undone because retiring an authorable key needs its own sequence.
The rename
EnhancedApiError.fieldErrors→fields. The array and its element shape are unchanged — only the property name.The wire has always carried
fields: the validators, import coercion,validation-failure.ts,@objectstack/clientand the console's field-error extractor all say it.fieldErrorswas declared and emitted by nobody, so anyone readingerror.fieldErrorswas reading a field no server sent — ADR-0078's silently-inert declaration, sitting on the error envelope.Tombstoned, not deleted
EnhancedApiErrorSchemais not.strict(), so a plain removal would let a producer still writingfieldErrorsparse clean and lose the array — a validation failure that mentions no field.retiredKey()(ADR-0104) turns that into a rejection carrying the rename, so the failure is loud at the write side instead of silent at the read side. Two tests pin both halves: the old name throws withfieldsin the message, the new name parses.A semantic chain entry, not a conversion — and that's the point
The ADR-0104 guard requires a registered retirement surface so the change reaches the documentation channel. The obvious move is a D2 conversion, and it would have been wrong here.
A conversion's job is rewriting author metadata. This is a response envelope: no stack, example or template has ever carried the key (checked across
packages/,examples/,templates/,apps/), soos migrate metahas no source to rewrite. A conversion with an identity fixture andexpectedNotices: 0would claim a rewrite that does not exist.The precedent is one step earlier in the same major:
analytics-query-request-query/-formatare HTTP-only surfaces whose own migration step says "No stored metadata carries this shape (it was HTTP-only), so the change is two semantic TODOs for API callers rather than a stack conversion." Same situation, same treatment — asemanticentry with a reason and an acceptance criterion, which is what actually reachesspec-changes.json(ADR-0087 D4), the generated upgrade guide, and thespec_changesMCP tool. The guard accepts semantic surfaces for exactly this reason.The generated guide entry now reads:
Two doc examples were teaching the opposite of this ADR
Both fixed rather than left for the drift advisory to re-flag:
error-handling-server.mdxhand-rolled a Zod →fields[]mapping withcode: 'invalid_field' as const— a top-level code in a field position, and a private reimplementation of the translation ADR-0114 D3 centralised. It now callszodIssuesToFields, the mapping the platform's own routes use, with a comment naming what that buys (too_smallsplitting byorigin, a missing property becomingrequired).error-handling-client.mdx's error class carried the dead name through its constructor, field and iteration.Also gone:
error-catalog.mdx's "widen the cast until the two names converge" workaround, since they have now converged.Test evidence
pnpm turbo run test(CI's command)errors+conversions+migrationscheck:docs/check:spec-changes/check:upgrade-guidecheck:authorable-surface(the guard that demanded this)check:api-surface/check:skill-refs/check:skill-docscheck:skill-examplescheck:error-code-casing@objectstack/runtimefails one test,datasource-autoconnect.test.ts— the same pre-existing failure as #4035, which did not reproduce on CI there (17/17 green) and which I reproduced on that PR's parent commit. My diff touches spec and docs only.Refs #3977, #4035. Completes ADR-0114.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MaSQn77TT5fUgHK9CesaDK
Generated by Claude Code