Commit 4d2008c
fix(rest): GET /meta/:type/:name answers 404 RESOURCE_NOT_FOUND for a name with nothing behind it (#18395)
Fixes #18066
Clause-②: no — this PR puts no new key on a published payload. It makes
`GET /meta/app/NAME` answer the `RESOURCE_NOT_FOUND` / 404 that
`packages/rest/src/meta-app-publish-gate.test.ts` already declares and
that `GetMetaItemResponseSchema` already implies by making `item`
required — restoring a declared contract, which narrows rather than
widens. Declared by the dispatching seat in claim `5693775028`.
`RESOURCE_NOT_FOUND` is an already-published code and no new
`ERROR_CODE_LEDGER` entry is introduced.
## What it does
`GET /api/v1/meta/:type/:name` for a name with nothing behind it
answered **200** with the declared envelope MINUS its `item` member. It
now answers `404 RESOURCE_NOT_FOUND`.
The reported body, re-measured on this tree against the route's own
`responseSchema`:
```
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}
GetMetaItemResponseSchema.safeParse(that body)
success = false
issues = [{"code":"invalid_type","expected":"nonoptional","path":["item"]}]
```
## Why this is execution, not a design call
The card's "Not measured, deliberately left open" section reads like an
open question. It is not: three declarations in this repository already
agreed with each other and against the live route, and the loudest of
the three — the 200 — was the wrong one.
Governing text, maintainer ruling 2026-09-10, quoted rather than
translated:
> 我们的项目以objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。
> 声明而未兑现是实现缺口,补实现或退役,⛔ 不在消费端收窄。
1. **`GetMetaItemResponseSchema`**
(`packages/spec/src/api/protocol.zod.ts`, this route's declared
`responseSchema` per `rest-route-ledger.ts`) makes `item` a required
member, while every genuinely-optional key beside it is spelled
`.optional()`. Measured above. `packages/spec` was read only and is not
edited by this PR.
2. **The CACHED arm of this same route already answered 404.**
`getMetaItemCached` throws `metadataItemNotFoundError` on a falsy
`item`. So which arm a request took decided whether absence was an error
at all — `app`, `dashboard`, `doc`, `book`, `?state=draft`,
`?preview=draft`, `?package=` and every `enableCache: false` deployment
are diverted around the cache. That is the #5563 defect class, one
member over.
3. **`packages/runtime/src/domains/meta.ts`** refuses the same item-less
answer in its object branch — "only treat the lookup as a hit when
`item` is really there" — and 404s. Cited as corroboration, not touched
here; see Acceptance notes.
## Mechanism, re-verified on today's tree
The card's reading (dated 2026-09-13) still holds. Both the 403
`PERMISSION_DENIED` and the 404 `RESOURCE_NOT_FOUND` sit inside `if
(isAppType && visible)`, where `visible` is the document, so for a name
that resolves to nothing the whole gate was skipped and the envelope
fell through to `res.json`.
Two things the card left open, now measured:
- **It was never an `app` rule.** The fall-through sat in the shared
tail of the uncached arm, below the per-type gates, so every type
diverted around the cache reached it. `app` is what the report saw
because that type bypasses the cache structurally; a `?state=draft`,
`?preview=draft` or `?package=` read of any type met the same 200.
Pinned across `dashboard`, `view`, `object`, `flow`, `page` and all
three query-flag diversions.
- **The cached arm does NOT agree — it was already correct, so absence
was never the uncached arm's opinion alone.** Read at the source:
`packages/metadata-protocol/src/protocol.ts:12084` `getMetaItemCached`
delegates to `getMetaItem`, takes `result?.item`, and at `if (!item)`
throws `metadataItemNotFoundError(type, name)` — `RESOURCE_NOT_FOUND`,
`status: 404`. Exercised over the route as well, not only read: §3
drives the cached branch with a provider that answers that rejection and
asserts `getMetaItemCached` was the verb called, then drives the same
address through the uncached branch with `enableCache: false` and
asserts the two now agree on status and code. The one remaining
asymmetry between them is the envelope dialect, which this PR
deliberately does not touch (see Acceptance notes).
And one mechanism detail the card could not have seen:
- 1 parent 7358c1c commit 4d2008c
4 files changed
Lines changed: 576 additions & 10 deletions
File tree
- .changeset
- packages/rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
115 | 126 | | |
116 | 127 | | |
117 | | - | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
118 | 133 | | |
119 | 134 | | |
120 | 135 | | |
| |||
447 | 462 | | |
448 | 463 | | |
449 | 464 | | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
450 | 475 | | |
451 | 476 | | |
452 | 477 | | |
453 | | - | |
454 | | - | |
455 | | - | |
456 | | - | |
457 | | - | |
458 | | - | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
459 | 505 | | |
460 | 506 | | |
461 | 507 | | |
| |||
0 commit comments