Repository navigation
Commit 761fef4
fix(metadata-protocol): deleteMany names the id entry it refuses (#29198)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9PYTuLPa8oWyz3qTsijRhsG)_
Fixes #29137
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:批量删除(deleteMany)的 ids 里有空字符串、null 或对象时,仍然 400 拒收,但 fields
现在点名出错的那一项(ids.N),空字符串报 required,其余报 invalid_type。
- 为什么改:原来不论哪种都只回一句「ids 必须是记录 id 数组」且 fields 为空,ids 明明是数组,调用方找不到是哪一项出错。
- 风险与回滚:只改拒收时的句子和 fields,拒收与放行的范围不变;回滚即 revert 本 PR。
- 先看:packages/metadata-protocol/src/protocol-batch-data.ts 的
deleteManyData 拒收分支和 deleteManyIdEntryField。
- 两分钟验证:pnpm --filter @objectstack/metadata-protocol exec vitest run
src/protocol.delete-many.test.ts,应全部通过。
- 需要你做的:无,席位落地。
## What changes
`deleteManyData` (the bulk delete door of
`@objectstack/metadata-protocol`) used one check, with one sentence and
no `fields`, for two different faults: `ids` is not an array, and `ids`
is an array with an entry that is not a record id. It now splits them:
- `ids` not an array: unchanged, the sentence "requires 'ids' to be an
array of record ids" and no `fields`.
- `ids` an array with bad entries: still `400 VALIDATION_FAILED`, now
with one `fields[]` row per offending entry, `{ field: 'ids.N', code,
message }`, the shape the REST ingress already gives `[null]`
(`zodIssuesToFields`). An empty string answers `required` (the same
fault the batch door's `rowRequiredIdError` calls "Record id is
required"), anything else that is not a string or number (null, an
operator object, a boolean) answers `invalid_type`. The sentence names
the first offending entry, and says how many were refused when more than
one.
What is refused does not move: the fail-closed rule for non-scalar
entries is unchanged, and a whitespace-only id still reaches the row
loop and reports `RECORD_NOT_FOUND` for its row. No new error code (the
codes are existing members of the ADR-0114 field-level catalog), no
`packages/spec` edit.
## Tests
- `packages/metadata-protocol/src/protocol.delete-many.test.ts`: new
cases for `['']`, `['a', '']`, `[{"$ne":null}]` (still refused, now
named), `[null]` (same pair as the ingress), several bad entries named
at once, a non-array (old sentence, no `fields`), and `[' ']` (still
reaches the row loop).
- `packages/rest/src/rest-delete-many-ingress.test.ts`: a new block
through the real protocol at the REST door: `{"ids":["a",""]}` answers
400 with `fields` naming `ids.1` as `required`, and `[null]` keeps the
ingress answer `ids.0` / `invalid_type`.
- No existing test was edited.
## Verification
Base `fea2dbb7`, head `9e074bf5`.
<details><summary>Base reading (REST door in-process, real protocol,
fake engine)</summary>
```
{"ids":[""]} 400 "requires 'ids' to be an array of record ids" fields: []
{"ids":["a",""]} 400 "requires 'ids' to be an array of record ids" fields: []
{"ids":[{"$ne":null}]} 400 "Invalid deleteMany request" fields: [ids.0 invalid_type] (ingress)
{"ids":"a"} 400 "Invalid deleteMany request" fields: [ids invalid_type] (ingress)
{"ids":[null]} 400 "Invalid deleteMany request" fields: [ids.0 invalid_type] (ingress)
{"ids":[" "]} 200 row RECORD_NOT_FOUND
```
In-process (protocol called directly) every one of the first five got
the array sentence and no `fields`. Premise holds.
</details>
<details><summary>Suites</summary>
- `pnpm --filter @objectstack/metadata-protocol test`: Test Files 297
passed, 4 skipped; Tests 22069 passed, 26 skipped. `typecheck`: exit 0.
- REST deleteMany data-door tests (the 17 `packages/rest/src/*.test.ts`
files that mention deleteMany): Test Files 17 passed; Tests 1049 passed.
- `pnpm exec eslint --no-inline-config` on the three touched source/test
files: exit 0.
</details>
<details><summary>Ablation (two legs, each rebuilt, restored, git diff
HEAD empty)</summary>
- Leg 1, drop `err.fields = idFields`: 5 protocol cases red (`expected
undefined to deeply equal [ { field: 'ids.0', ... } ]`) and the REST
case red (`expected [] to deeply equal [ [ 'ids.1', 'required' ] ]`).
- Leg 2, accept an empty string as an id: 3 protocol cases red and the
REST case red (`expected 200 to be 400`).
- Restored through `ablation-replace.mjs --restore` (blob equals HEAD),
rebuilt, `ablation-dist-preflight --absent` passed.
</details>
<details><summary>Gates</summary>
`dispatch-gates --commands` derived 60 commands; 58 exited 0.
`check-plugin-teardown-shape --self-test` exited 3 on a shallow clone,
then 0 after fetching its pinned fixture commit.
`check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: needs the
whole workspace built): NOT MEASURED locally, CI runs it. `--ran`
reconciliation: 60 derived, 60 run, 0 UNRUN.
</details>
## Acceptance notes
- Choice of `required` for an empty string (rather than `min_length`,
which a `z.string().min(1)` ingress would spell) follows the batch
door's "Record id is required" for the same fault. Flagged to the seat
as an open question.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQJyGvWVm7dbChPMZ7)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 47ee57f commit 761fef4
4 files changed
Lines changed: 145 additions & 1 deletion
File tree
- .changeset
- packages
- metadata-protocol/src
- rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
920 | 920 | | |
921 | 921 | | |
922 | 922 | | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
923 | 942 | | |
924 | 943 | | |
925 | 944 | | |
| |||
934 | 953 | | |
935 | 954 | | |
936 | 955 | | |
937 | | - | |
| 956 | + | |
938 | 957 | | |
939 | 958 | | |
940 | 959 | | |
941 | 960 | | |
942 | 961 | | |
943 | 962 | | |
944 | 963 | | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
945 | 980 | | |
946 | 981 | | |
947 | 982 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
111 | 166 | | |
112 | 167 | | |
113 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
0 commit comments