You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 875d007
Browse filesBrowse the repository at this point in the historyBrowse files
fix(rest): GET /meta/diagnostics refuses an unknown type, severity or package, and keys an alias by its canonical type (#27514) (#27620)
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu5mq5spkb2GMJ5SN6jEWmDk)_Fixes#27514
Clause-②: no (narrowing — owes one contract-review-tier review before
the queue)
Metadata-protocol: none
Skills-docs: follow-up #27632
## 维护者速读
- **改了什么**:诊断接口 `GET /meta/diagnostics` 遇到不认识的类型、严重级别或包,不再回
200「全部正常」,而是回 400 并说明原因;用别名(如
`views`)查询时统计按标准类型名(`view`)给出;该接口所有拒绝统一成一种错误信封。
- **为什么改**:这个接口是作者、AI 和 CI 判断元数据是否健康的依据。拼错类型或包名时它回「扫描 1 个类型,0
个问题」,调用方会误以为一切正常。
-
**风险与回滚**:只收窄这一个只读接口的请求面,合法请求的响应形状和键集不变(别名请求的统计键从别名改为标准名,是本卡要求的变化)。回滚即还原本
PR。
- **先看**:`packages/rest/src/meta-collection-routes.ts`(诊断路由:severity
校验、类型拒绝、统一信封);`packages/metadata-protocol/src/protocol-meta-diagnostics.ts`(未知包拒绝、标准类型键);`packages/rest/src/rest-meta-diagnostics-unknown-params.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/rest exec vitest run
src/rest-meta-diagnostics-unknown-params.test.ts`,预期 11 个用例全部通过。
- **需要你做的**:无,席位落地(落地前需一次 contract-review-tier 评审)。
## What changes
Measured on `main` @ `556f3230` through the real route (a real
`ObjectStackProtocolImplementation` over a real `SchemaRegistry`), every
premise on the card held:
| query | before | after |
| :-- | :-- | :-- |
| `?type=zzz_unknown` | 200, `scannedTypes: 1`, `stats.zzz_unknown`
zeros | 400 `INVALID_REQUEST` |
| `?severity=bogus` | 200, read as the error sweep | 400
`INVALID_REQUEST` |
| `?package=com.nope.missing` | 200, zeros for all 30 swept types | 400
`INVALID_REQUEST` |
| `?type=views` | 200, `stats` and `entries` keyed `views` | 200, keyed
`view`, byte-equal to `?type=view` |
| `?type=viewes` (#8924) | 400 flat `{ error: '…', code }` | 400 nested
`{ error: { code, message } }`, same message |
| `?type=view&type=object` (#6877) | 400 nested `VALIDATION_ERROR` |
unchanged |
- **severity** (route): `error` or `warning`; an empty value keeps the
default, as an empty `type` keeps the full sweep.
- **type** (route): after the sweep, the route asks the list door's own
verdict, `refuseUnknownMetaListType` (the static spelling contract, then
the live type set from `getMetaTypes`), so `GET /meta/:type` and the
diagnostics door agree on which names are types, and a plugin type the
deployment carries live still answers. Asked after the sweep so the
#8924 near-miss refusal, which names the spellings the caller reached
for, keeps answering first.
- **package** (sweep): refused only when the sweep found no item bound
to it AND the installed-package listing (`getMetaItems` of type
`package`, the registry's records) does not carry it. An installed
package with no items still answers its zeros, and rows bound to an
uninstalled package are still swept. Fails open when the listing cannot
be read.
- **alias key** (sweep): a named type is swept and keyed under
`canonicalMetaUrlType(type)`, the fold `canonicalMetaType` applies at
every `/meta` entry point. A spelling the fold does not map (`viewes`,
`fieldz`) is unchanged, so the #8924 refusal still sees what the caller
wrote.
- **one envelope** (route): every `400 INVALID_REQUEST` the route throws
or receives now leaves in the nested `{ error: { code, message } }` its
repeated-parameter refusal already used (message bounded by
`truncateClientMessage`). Everything else keeps `handleRouteError`.
Every code is an existing ledgered one (`INVALID_REQUEST`,
`VALIDATION_ERROR`); no new code, nothing in `packages/spec`.
`metadata-protocol/src/protocol.ts` is untouched: the package check uses
`getMetaItems`, which `MetaDiagnosticsHost` already carries.
## Tests
- New `packages/rest/src/rest-meta-diagnostics-unknown-params.test.ts`
(11 cases, through the HTTP route): the three refusals, both envelope
cases, the alias case, and six preservation cases (`severity`
warning/error/empty/absent, an installed package, `package=all`, an
installed package with no items, items bound to an uninstalled package,
a live plugin type). On `main`: 5 failed, 6 passed. On this branch: 11
passed.
- Two existing pins moved, both the alias key this card retires:
`protocol.diagnostics-store-outage.test.ts` (`stats.fields` to
`stats.field`) and `rest-server-meta-read-org-scope.test.ts`
(`stats.views` to `stats.view`, plus `stats.views` absent).
- `pnpm --filter @objectstack/metadata-protocol test`: 279 files passed,
21781 tests passed. `pnpm --filter @objectstack/rest test`: 335 files
passed, 6589 tests passed. `typecheck` on both packages: exit 0.
- Ablation, one leg per pin, each through `scripts/ablation-replace.mjs`
(anchor hit 1 to 0, restore blob == HEAD, `git diff HEAD` empty):
severity check, type verdict, envelope writer (all three in `rest` src,
read by the test from source), and the package refusal and canonical key
(in `metadata-protocol`, which the test reads from `dist/`: rebuilt,
`ablation-dist-preflight` marker present exit 0, then rebuilt again and
`--absent` exit 0). Each leg turned exactly its own pins red: severity
1, type 1, envelope 4, package 1, alias 1.
## Acceptance notes
- `scripts/engine-double-contract.pinned.json` gains the new rig's
`findOne` double (written by `check-engine-double-contract.mjs
--write`); the rig opens `findOne` with `assertEngineFindOnePredicate`.
That ledger is outside the claimed file surface and is the gate's own
bookkeeping for the new test.
- With `?type=X&package=P`, the package refusal reads the swept types
only, so an uninstalled `P` whose orphan rows are all of other types is
refused with that type filter. Without the filter the full sweep finds
them.
- The branch name is the card thread's harness-pinned
`claude/project-thread-nx5qkr`, not `claude/issue-27514-…`; the claim
records it.
## Verification
All readings on head `59ef3dbe`.
- `node scripts/pm/dispatch-gates.mjs --commands`: 69 derived families,
each run with its exit code captured before any pipe, all exit 0.
`--ran`: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN.
- `pnpm lint` (repo-wide, `eslint . --no-inline-config`): exit 0. eslint
on `packages/rest` and `packages/metadata-protocol`: exit 0.
- Dogfood battery (`pnpm --filter @objectstack/dogfood test` under
`os-verify-lock.sh`, no shard, no path, closure built): the lock's
`VERDICT command-exit 0`; Test Files 350 passed, 3 skipped (353); Tests
2817 passed, 20 skipped (2837); no failure.
- Open PRs on the touched files: 36 open PR heads (every open PR but the
release and mailbox branches) fetched and diffed against their merge
bases; none touches the four source and test files. Two (#27535, #27474)
also add rows to `scripts/engine-double-contract.pinned.json`; `git
merge-tree --write-tree` of this head with each answers exit 0 (no
conflict).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(rest): `GET /meta/diagnostics` refuses an unknown type, severity or package instead of answering an all-clear, and keys an alias's stats by its canonical type (#27514)
7
+
8
+
Clause-②: no (narrowing — owes one contract-review-tier review before the queue)
9
+
Metadata-protocol: none
10
+
11
+
<!-- adr-0087: not-required (no-migration-prescription) The narrowed face is a REQUEST to one read door: query values that were never honoured (an unknown type, severity or package) are now refused instead of answered with an empty sweep, and an alias's stats move to the canonical key the sweep already listed it under. No stored metadata, package source or object definition holds a diagnostics request or response, no schema key or export changes, and the remedy is the caller's own query string, so there is nothing for `objectstack migrate meta` to rewrite. -->
12
+
13
+
`/meta/diagnostics` is the door an author, an agent or CI reads to learn whether metadata is healthy, and three query values it could not honour were answered `200` with zero issues.
14
+
15
+
-**`?type=`** naming no metadata type (`zzz_unknown`) is refused with `400 INVALID_REQUEST`, by the same verdict `GET /meta/:type` uses: the platform's static type spelling, then the types this deployment carries live. A near-miss spelling of a declared type (`viewes`) keeps its existing refusal, which names the spellings it was reaching for.
16
+
-**`?severity=`** other than `error` or `warning` is refused with `400 INVALID_REQUEST`. An empty value still means the default, `error`.
17
+
-**`?package=`** naming a package that is not installed, and that no swept item is bound to, is refused with `400 INVALID_REQUEST`. An installed package with no items still answers its zeros, items bound to an uninstalled package are still swept, and `package=all` still sweeps every package.
18
+
-**An alias** (`?type=views`) is swept and keyed in `stats` and `entries` under its canonical type (`view`), so it answers exactly what `?type=view` answers.
19
+
-**One envelope.** Every refusal of this route now answers the nested `{ error: { code, message } }` the repeated-parameter refusal already used. The near-miss type refusal used to answer `{ error: '…', code }`.
0 commit comments