Repository navigation
Description
Found while implementing #9488 (the list door's unknown-type refusal). Filed unassigned; not fixed there, because #9488's scope is the LIST door and this door does not carry #9488's defect — it already refuses distinguishably.
Observed
Measured on packages/rest's GET /api/v1/meta/:type/:name with a type name that is in neither the static spelling contract nor any live registry:
GET /api/v1/meta/totally_invented_type/x
-> 404 {"error":"Metadata item totally_invented_type/x not found","code":"RESOURCE_NOT_FOUND"}
The producer is @objectstack/metadata-protocol's protocol.ts (Metadata item ${type}/${name} not found).
Why it is worth recording
This is a weaker form of what #9488 was filed about, not an instance of it — and the difference is why this is an observation rather than a bug:
GET /api/v1/meta/<unknown-type>answers 200 with an empty collection while the write door refuses the same type #9488's defect was indistinguishable from success:200with an empty collection reads exactly like "this type exists and holds nothing".- This answer is a refusal, so a caller cannot mistake it for a served result. Nothing is silently wrong.
What it does do is point the caller at the wrong half of the address. The write door for the same segment says "'totally_invented_type' is not a metadata type"; this door says the item was not found, for a request whose actual mistake was the type. A caller who typo'd the type re-checks the name — the one thing that cannot help them, which is the same shape recorded on the repeated-filter refusal (a message naming the wrong cause sends the reader to re-check the part that was already correct).
The three doors therefore give three different accounts of one segment:
| door | answer for an unknown type name |
|---|---|
PUT /meta/:type/:name |
400 INVALID_REQUEST — "'x' is not a metadata type" |
GET /meta/:type (after #9488) |
400 INVALID_REQUEST — same sentence, same code |
GET /meta/:type/:name |
404 RESOURCE_NOT_FOUND — "Metadata item x/y not found" |
What a fix would have to respect, if one is wanted
The same constraint #9488 measured, and it is the reason this is not a one-line change:
- the refusal cannot be raised from the static spelling contract alone.
unrecognisedMetaTypeRefusalreturns a verdict fordata,kind,packageandpolicy— liveSchemaRegistrykeys an ordinaryregisterAppproduces andGET /meta/typesenumerates — and for plugin types, which enter the live set by registering items. Refusing on the static half alone would answer400for types this same service advertises, which is the objection recorded when the write-side verdict landed and was deliberately not raised on the read entries then. - so it needs the same union
GET /api/v1/meta/<unknown-type>answers 200 with an empty collection while the write door refuses the same type #9488 built for the list door (static contract ∪ live listing), and a decision about whether a type-level refusal should outrank the item-level404on an address where both are true.
No consumer is known to depend on the current wording; the sweep behind #9488 found no caller addressing an off-contract type on this route outside refusal fixtures.
Not covered by
- [Ruling] The
/metatype segment is ALWAYS singular — retire every plural spelling, no exception, in three steps #9180 (/metatype segment is always singular) — that ruling is about plural spellings, a different axis. GET /api/v1/meta/<unknown-type>answers 200 with an empty collection while the write door refuses the same type #9488 — the list door only; the single-item doors were measured and explicitly left alone there.
No priority and no domain:* label — normal triage first-touch.
Activity
os-support-ai commentedon Aug 18, 2026
Triage first-touch: closing as not planned — the card is the record, reopening is free, and the maintainer can veto this closure.
Rationale on the three axes:
404 RESOURCE_NOT_FOUND); nothing is silently wrong, and the sweep behindGET /api/v1/meta/<unknown-type>answers 200 with an empty collection while the write door refuses the same type #9488 found no caller addressing an off-contract type on this route outside refusal fixtures.400outranks the item-level404on a public route — a refusal-shape contract question, not a mechanical alignment.GET /api/v1/meta/<unknown-type>answers 200 with an empty collection while the write door refuses the same type #9488's rationale (indistinguishable-from-success) is branch-specific and does not carry over, exactly as this card records.Opportunistic restart: if a caller measurably trips on the wrong-cause 404, or a card touching this route's refusal path is dispatched, fold the static ∪ live type check in there and reopen this for the precedence decision. Typed Task for the record.
Generated by Claude Code