Skip to content

[finding] GET /meta/<unknown-type>/<name> says "item not found" — the single-item 404 cannot tell an unknown TYPE from an unknown ITEM #9744

Closed as not planned

Description

@os-zhuang

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:

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. unrecognisedMetaTypeRefusal returns a verdict for data, kind, package and policy — live SchemaRegistry keys an ordinary registerApp produces and GET /meta/types enumerates — and for plugin types, which enter the live set by registering items. Refusing on the static half alone would answer 400 for 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-level 404 on 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

No priority and no domain:* label — normal triage first-touch.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 18, 2026
  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    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:

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    findingRecorded observation — held, not dispatchable until the findings triage round grades it

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions