Measured while implementing objectstack-ai/objectui#9262 (the console's by-name app probe),
on a real server: objectstack commit 60b9955, examples/app-showcase,
objectstack dev --seed-admin --fresh -p 4262, API 17.4.0, authenticated as the seeded admin.
The reading
GET /api/v1/meta/app/NAME for a name with nothing behind it answers 200 with the
declared envelope MINUS its item member — it does not answer 404 RESOURCE_NOT_FOUND.
GET /api/v1/meta/app/no_such_app_xyz
200 {"type":"app","name":"no_such_app_xyz","lock":"none","editable":true,"deletable":true,"resettable":false}
GET /api/v1/meta/app/showcase_app (the one app in this session's list)
200 {"type":"app","name":"showcase_app","item":{...},"lock":"none",...}
Four names measured; every absent one behaved identically. The response carries lock, so
it came from the UNCACHED arm of the route — the app types are diverted there and the RBAC
gate did run.
Why it looks like a divergence from this repo's own declaration
Two declarations say absence is an error, not an item-less success:
GetMetaItemResponseSchema (packages/spec/src/api/protocol.zod.ts) declares the
response as z.object({ type, name, item, ... }) with item a REQUIRED member. The
envelope above omits it.
packages/rest/src/meta-app-publish-gate.test.ts, in the case titled
"criterion 3: ...and the real producer miss is still the 404 it has always been",
states it in as many words: "The fixture's getMetaItem answers undefined for an
unknown name; metadata-protocol REJECTS with a declared RESOURCE_NOT_FOUND /
status: 404". That case passes because its fixture REJECTS. The live objectql
provider RESOLVES with the item-less envelope instead, so the pin and the server do not
describe the same behaviour.
Mechanism, read off packages/rest/src/rest-server.ts: the 403 PERMISSION_DENIED and
the 404 RESOURCE_NOT_FOUND both sit inside if (isAppType && visible), where visible
is the document. For a name that resolves to nothing visible is undefined, the whole gate
is skipped, and the envelope falls through to res.json. So the 404 branch is reachable
only for an app that EXISTS and is withheld for a NON-permission reason (unpublished, or
gated by an absent optional service).
Why it mattered downstream
ObjectStackDataSource.probeAppAccess in objectui returned granted whenever the call did
not throw, so it reported granted for every nonexistent app. That console-side reading is
being corrected on objectstack-ai/objectui#9262 — it now reads the envelope and treats both
dialects as absence — so nothing downstream is blocked on this card. It is filed because
the platform's declared contract and its live behaviour disagree, which is a decision for
this repo rather than something a consumer should quietly absorb.
Not measured, deliberately left open
- Whether the item-less 200 is intended (Studio's create-vs-edit probe may want exactly
this descriptor for a name that does not yet exist) or a provider-level miss. That is the
decision this card asks for, and it is why no fix is proposed here.
- Whether other metadata types behave the same way. Only
app was measured.
- Whether the cached arm behaves the same way. The app reads observed here all took the
uncached arm.
Filed by the objectui os-dev seat while implementing objectstack-ai/objectui#9262,
generated with Claude Code, session session_011QreXiyMEqKLN4U5daMPVa.
Generated by Claude Code
Measured while implementing objectstack-ai/objectui#9262 (the console's by-name app probe),
on a real server:
objectstackcommit60b9955,examples/app-showcase,objectstack dev --seed-admin --fresh -p 4262, API 17.4.0, authenticated as the seeded admin.The reading
GET /api/v1/meta/app/NAMEfor a name with nothing behind it answers 200 with thedeclared envelope MINUS its
itemmember — it does not answer404 RESOURCE_NOT_FOUND.Four names measured; every absent one behaved identically. The response carries
lock, soit came from the UNCACHED arm of the route — the app types are diverted there and the RBAC
gate did run.
Why it looks like a divergence from this repo's own declaration
Two declarations say absence is an error, not an item-less success:
GetMetaItemResponseSchema(packages/spec/src/api/protocol.zod.ts) declares theresponse as
z.object({ type, name, item, ... })withitema REQUIRED member. Theenvelope above omits it.
packages/rest/src/meta-app-publish-gate.test.ts, in the case titled"criterion 3: ...and the real producer miss is still the 404 it has always been",
states it in as many words: "The fixture's
getMetaItemanswersundefinedfor anunknown name;
metadata-protocolREJECTS with a declaredRESOURCE_NOT_FOUND/status: 404". That case passes because its fixture REJECTS. The live objectqlprovider RESOLVES with the item-less envelope instead, so the pin and the server do not
describe the same behaviour.
Mechanism, read off
packages/rest/src/rest-server.ts: the 403PERMISSION_DENIEDandthe 404
RESOURCE_NOT_FOUNDboth sit insideif (isAppType && visible), wherevisibleis the document. For a name that resolves to nothing
visibleis undefined, the whole gateis skipped, and the envelope falls through to
res.json. So the 404 branch is reachableonly for an app that EXISTS and is withheld for a NON-permission reason (unpublished, or
gated by an absent optional service).
Why it mattered downstream
ObjectStackDataSource.probeAppAccessin objectui returnedgrantedwhenever the call didnot throw, so it reported
grantedfor every nonexistent app. That console-side reading isbeing corrected on objectstack-ai/objectui#9262 — it now reads the envelope and treats both
dialects as absence — so nothing downstream is blocked on this card. It is filed because
the platform's declared contract and its live behaviour disagree, which is a decision for
this repo rather than something a consumer should quietly absorb.
Not measured, deliberately left open
this descriptor for a name that does not yet exist) or a provider-level miss. That is the
decision this card asks for, and it is why no fix is proposed here.
appwas measured.uncached arm.
Filed by the objectui
os-devseat while implementing objectstack-ai/objectui#9262,generated with Claude Code, session
session_011QreXiyMEqKLN4U5daMPVa.Generated by Claude Code