Found while verifying the 17.0.0-rc.1 checklist on #3909 (F3, the "silent drops became refusals" axis). Verified on main @ 1ee48bc60, showcase under os serve --dev, admin session.
The read path already answers honestly, but both single-record write paths report success for a record that does not exist:
GET /api/v1/data/showcase_task/definitely_not_a_row
→ 404 {"error":"Record … not found in showcase_task","code":"RECORD_NOT_FOUND",…} ✅
PATCH /api/v1/data/showcase_task/definitely_not_a_row {"progress":1}
→ 200 {"object":"showcase_task","id":"definitely_not_a_row","record":null} ❌
DELETE /api/v1/data/showcase_task/definitely_not_a_row
→ 200 {"object":"showcase_task","id":"definitely_not_a_row","success":true} ❌
The REST layer is a pass-through (rest-server.ts DELETE handler res.json(await p.deleteData(...))), so the 200s come from the metadata-protocol's update/delete answering success on a zero-row match.
Why this matters beyond symmetry:
Expected: PATCH/DELETE of a nonexistent id answer 404 RECORD_NOT_FOUND exactly as GET does (and a DELETE that does delete answers the declared deleted key). If idempotent-DELETE-200 is a deliberate posture, it should at least say deleted: false — but then PATCH, which has no idempotency story, still needs the 404.
Part of the #3909 rc.1 verification (section F3).
Found while verifying the 17.0.0-rc.1 checklist on #3909 (F3, the "silent drops became refusals" axis). Verified on
main@1ee48bc60, showcase underos serve --dev, admin session.The read path already answers honestly, but both single-record write paths report success for a record that does not exist:
The REST layer is a pass-through (
rest-server.tsDELETE handlerres.json(await p.deleteData(...))), so the 200s come from the metadata-protocol's update/delete answering success on a zero-row match.Why this matters beyond symmetry:
record: nulland believes the write landed. Nothing in the envelope says "not found" — the caller has to null-check a success payload to detect a failed write.DELETEreportssuccess: truefor any string you put in the path — a typo'd id, an already-deleted row and a real deletion are indistinguishable. (The response also carriessuccesswhere the spec'sDeleteDataResponsedeclaresdeleted, so even the "did anything happen" bit the spec provides for is absent.)sort/select/expand指向不存在的字段时被拒绝,而不是静默丢弃 (#4226) #4240/fix(data):sort的点号路径被拒绝(400 INVALID_SORT),不再静默降级为不排序 (#4256) #4303/fix(data):searchFields/groupBy/aggregations指向不存在的字段时被拒绝,而不是静默降级 (#4254) #4315 (sort/select/expand/searchFields refuse missing fields), fix(data): reject unknown list query params instead of reading them as zero-matching filters (#4134) #4169 (unknown params refused), fix(data): implicit field filters AND-merge with an explicit filter instead of being silently dropped (#4164) #4190 (dropped filters now merge)… a write that touched zero rows reporting 200 is the same silent no-op, one level up.Expected: PATCH/DELETE of a nonexistent id answer
404 RECORD_NOT_FOUNDexactly as GET does (and a DELETE that does delete answers the declareddeletedkey). If idempotent-DELETE-200 is a deliberate posture, it should at least saydeleted: false— but then PATCH, which has no idempotency story, still needs the 404.Part of the #3909 rc.1 verification (section F3).