Uh oh!
There was an error while loading. Please reload this page.
docs(api): catalog the unmintable /meta type refusal - #9287
Merged
Conversation
The `/meta` type boundary raises two refusals that share one envelope — `INVALID_REQUEST` / 400 — so code and status cannot tell them apart. Only the spelling refusal had a catalog entry; its sibling was named in one disambiguation line and otherwise undocumented. Adds the entry for `refuseUnmintableMetaType`, matching the sibling's Fix/Retry shape. The load-bearing part is the two exemptions, which a caller can otherwise only discover by hitting them: - compound arity — a `name` containing `/` puts an OBJECT name in the `:type` segment, so the verdict does not apply (and `/meta/fieldz/a/b` is therefore accepted); the exemption is the arity, not the name - pre-existing namespace — the write proceeds if `sys_metadata` already holds rows under that type key, decided by the store, never the caller Together these mean the same segment can be refused on one deployment and served on another. The entry says so plainly, records that the verdict is write-scoped (reads answer, residue rows stay deletable), and notes the 503 the namespace probe raises when the store is unreachable. Also updates the section intro, which described a single refusal, and rewrites the spelling entry's disambiguation line to point at the new entry instead of half-restating its envelope. No behaviour change: #8421 scoped this refusal deliberately and it is correct — only its documentation was absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
os-steve
marked this pull request as ready for review
August 17, 2026 11:45
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixes#9243
The
/metatype boundary raises two refusals that share one envelope —INVALID_REQUEST/400— so code and status cannot tell them apart; only the message can. PR #9245 (for #9193) documented the spelling refusal and named this one in a single disambiguation line. This adds the sibling entry it deliberately did not absorb.Docs-only. No behaviour change — #8421 scoped this refusal deliberately and it is correct; only its documentation was absent.
The load-bearing part: the two exemptions
A bare envelope row would have been worse than no entry, because it would assert a determinism the code does not have.
refuseUnmintableMetaTypeis not a pure function of the type segment — two shapes reach the door and are served:namecontaining/exempts the request, because at that arity the type segment carries an object name rather than a type claim (PUT /meta/lead/views/all_leadsistype='lead',name='views/all_leads'). Residue stated rather than hidden:PUT /meta/fieldz/a/bis therefore accepted. The exemption is the arity, not the name —PUT /meta/lead/all_leadsis still refused.sys_metadataalready holds rows under that type key, the write proceeds. The store decides this, never the caller.⇒ The same segment can be refused on one deployment and served on another, depending on stored state. The entry says this plainly, since it is the one thing a caller could otherwise only learn by collision.
Also documented: the refusal is write-scoped (one call site, in
saveMetaItem; reads still answer and residue rows stay deletable), and the namespace probe raises503when the store is unreachable — while an unprovisioned store is the one read failure that does not escalate, so the400stands.Verified against source, not restated from the card
All three properties were re-derived in the worktree (the card's line numbers had moved and are not repeated):
code = 'INVALID_REQUEST',status = 400—packages/metadata-protocol/src/protocol.tsif (request.name.includes('/')) return;if (await this.metaTypeNamespaceExists(unrecognised.type)) return;, which readssys_metadatabytypealonerefuseUnmintableMetaType, insidesaveMetaItemunrecognisedMetaTypeRefusalinpackages/spec/src/shared/metadata-url-spelling.tsEach is independently pinned by existing tests in
packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts(including the anti-vacuity control for arity and the positive control for the namespace exemption) andpackages/runtime/src/meta-compound-arity-mint-door.test.ts. The quoted JSON message is verbatim from the producer.Changes to the neighbouring entry, per the pair requirement
Two edits beyond the new entry, both because a second entry made the existing text inaccurate rather than merely redundant:
Scope
Per the card: one envelope, this one. ⛔ Not a sweep — the wider class of wire-visible refusals with no catalog entry stays unmeasured, as #9193 already recorded.
#8421is not addressed here beyond documenting its behaviour.Gates
Derived from the actual changed path with
node scripts/pm/dispatch-gates.mjs content/docs/api/error-catalog.mdx(8 families, matching the dispatch list exactly), and re-run as a union after the final commit, atae6c0624b:check:error-status-conformanceINVALID_REQUESTread as a ledger code the page publishes a status forcheck:docs-audit-scopecheck:docs-redirectscheck:role-wordcheck:empty-statecheck:livenesscheck:strictness-ledgercheck:variant-docsThe new heading uses the whitelisted
code + descriptive suffixshape from the conformance gate'sENTRY_HEADING_SHAPES, so it is read rather than landing in the unreadable-heading census. It claims400, the same status the sibling entry already claims for this code, so the reconciled set is unchanged.Docs-only, publishes nothing ⇒
skip-changeset.Generated by Claude Code
Generated by Claude Code