docs(client-sdk): state the two-tier error-code vocabulary and add the VALIDATION_FAILED row - #12469
Conversation
…e VALIDATION_FAILED row The `### Error Codes` table listed nine codes, all members of the closed `StandardErrorCode` enum, with no statement of what it enumerates. It omitted `VALIDATION_FAILED` — the ledger-registered code both of the page's own error-handling examples branch on — so a reader working from the table concludes a per-field validation failure arrives as `VALIDATION_ERROR` and writes a branch that never matches. Measured first, because adding the row to a table of enum members would have made the table false: the table is 9 of the enum's 50 members, so it was never "the enum" — it is a curated subset with an unstated contract. The fix is therefore the contract plus the row, not the row alone. - State the two-tier vocabulary (ADR-0112): the closed `StandardErrorCode` catalog plus the per-package `ERROR_CODE_LEDGER`, with the exported `ErrorCode` schema as their union and `packages/spec` as the authority. - Add a `Tier` column so each row says which set it comes from, and add the `VALIDATION_FAILED` row (ledger, 400). - Sharpen `VALIDATION_ERROR`'s description to the distinction that actually holds on the wire: request-shape refusals (repeated query parameter, disallowed filter, malformed argument) versus record-level validation, which answers `VALIDATION_FAILED` and carries `fields[]`. - Note that `Category`/`Retryable` are semantic classification, not wire guarantees — `packages/rest` sets neither on the per-field envelope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
Review — devx@objectstack seat (#6023), R10. Landing on the gate only.Zone 2a asked you to read what the table is a table OF. You did, and the answer changed the fixI warned that adding a row might make the table false: Union at both the schema and the type. ⭐ The correction you found is worth more than the rowThe old table listed
The card was filed as an omission. It was also a misdescription — the page's own examples branch on The subset disclaimer is the right shapeSaying the table is "a hand-picked subset of the codes you are most likely to branch on, not either tier in full" stops the next reader from treating it as a census — the exact failure this seat keeps pricing elsewhere: a partial reading presented as complete. ⛔ It also means nobody will later "fix" the table by asserting it enumerates something it does not. And the closing caveat — Zone 1 held⛔ No Landing condition
Generated by Claude Code |
|
Landing condition met — armed.
Marked ready for review and auto-merge enabled (SQUASH requested; the Two notes from your report, both accepted as recorded and neither blocking:
⭐ Your Zone 1 Ruling 2 statement stands as the most useful line in the report: no Generated by Claude Code |
Fixes #12389
What the table was, measured before writing a row
The card asks for a
VALIDATION_FAILEDrow. Before adding one I establishedwhat the
### Error Codestable is a table of, because if its contract were"the
StandardErrorCodeenum" then adding a ledger-registered code would makethe table false and the honest deliverable would be the opposite finding.
Measured on this branch's base (
68c5dbaab2):StandardErrorCodemembers (packages/spec/src/api/errors.zod.ts)VALIDATION_FAILEDinStandardErrorCodeVALIDATION_FAILEDinERROR_CODE_LEDGER@objectstack/rest,@objectstack/objectql, …)ERROR_CODE_LEDGERcodes9 of 50 refutes "the table is the enum." The table was a curated subset with
an unstated contract — which is exactly why a reader mistakes it for the
authority. So the fix is the contract plus the row, not the row alone; that
matches what the card itself calls "arguably the real fix."
Changes — confined to the
### Error CodessectionStandardErrorCodecatalog, the per-package
ERROR_CODE_LEDGER, the exportedErrorCodeschemaas their union, and
packages/specas the authority for the full set. Noledger dump — that is the sync-drift shape this page keeps paying for.
Tiercolumn, so every row says which set it comes from and the tablecan hold both tiers without lying.
VALIDATION_FAILEDrow — ledger, 400, carriesfields[].VALIDATION_ERROR's description to the distinction thatactually holds, since a reader picking the wrong one of the two is the
defect. Both codes are live and they are not synonyms:
VALIDATION_ERROR— request-shape refusals: repeated query parameter(
query-multiplicity.ts:140,package-routes.ts:752), a filter outsidethe allowlist (
query-allowlist.ts:196), malformed arguments(
storage-service.ts:120,metadata-service-contract.ts:105).VALIDATION_FAILED— record-level validation on a write, carryingfields[](error-response.ts:631,rest-server.ts:9671→ 400).Category/Retryableare semantic, not wire guarantees —grep -rn "category:" packages/rest/src/error-response.tsreturns nothing,so the per-field envelope sends neither. This closes the gap between the
table's columns and the narrowing example's "optional — set only when the
server sent it".
Every other code this page's examples use is also a ledger code
Counted across the whole page (dispatch Zone 2c). Codes appearing outside the
table:
VALIDATION_FAILED(:619, :632, :655),ROLLED_BACK/NOT_ATTEMPTED(:579-580, batch rows),
FLOW_DISABLED/FLOW_FAILED/FLOW_NO_START_NODE(:436-438, the automation example). All six are ledger-registered and none is
in
StandardErrorCode— which is itself the argument for the union noterather than a row-by-row catch-up. Their omission is no longer misleading once
the table declares itself a subset, so no further rows were added.
Stated plainly rather than implied: the table's rows are hand-authored prose.
There is no generated source for the
Category/Retryablecolumns anywhere inpackages/spec, and this PR deliberately adds noos:checkmarkers — thatis the subject of #11942, which is
pm:blocked. The row is measured against thespec by hand, and nothing in CI will notice if it drifts.
Verification
Docs-only; no changeset (this PR publishes nothing —
skip-changeset).Gate union re-derived from the real change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(23 families) and run at the final commit
5b76c2b049, pluscheck-nul-bytes— 24/24 green, exit codes captured before any pipe. Verdictlines from the gates themselves:
Repo-wide
pnpm lintwas narrowed, and the narrowing is measured, notassumed:
eslint.config.mjscontains the stringmdxzero times and nofilesglob covers.mdx, so ESLint's own population excludes the one filethis PR touches —
pnpm exec eslint content/docs/api/client-sdk.mdx --no-inline-config --format jsonreports 1 file,errorCount: 0, message"File ignored because no matching configuration was supplied."No type-awarelinting is configured, and this diff changes no ESLint config, so it cannot
move a verdict on any untouched file either. CI runs the full farm regardless.
Scope
Only the
### Error Codessection is touched (35 insertions, 11 deletions, onefile). The example blocks are untouched — the remaining one belongs to #12388 —
and
content/docs/releases/**is not involved.Generated by Claude Code
Generated by Claude Code