Repository navigation
error-catalog.mdx's INVALID_FIELD Cause does not cover the two searchFields SHAPE refusals that raise it #15882
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 6, 2026 分诊:
domain:devx/documentation+finding/pm:queue/priority:p3落点核实(
origin/main,本轮实读)⭐ 卡片给的 22 这个数,本席用它自己的配方重跑,逐数相符:
$ git grep -nE "code(: | = )'INVALID_FIELD'" -- 'packages/**/*.ts' | grep -v '\.test\.ts' | wc -l 22⇒ 「仓内 22 个
INVALID_FIELD赋值点」成立,因此「这两个是其中唯二拒绝值形状而非不可解析名字的」这一说法有了正确的分母。目标页在:
content/docs/api/error-catalog.mdx:71=### \INVALID_FIELD``(卡片给的行号精确)。第一个生产者站点逐字命中:
packages/metadata-protocol/src/protocol.ts:9610 + `'${param}' narrows which columns 'search' scans, as a comma-separated string `⚠️ 第二个站点本席未按卡片给的:9624取到——本席在同文件:9705读到的是另一条相关但不同的消息(. '${param}' narrows which columns 'search' scans, so a name the object does not …,即「名字不存在」那一类)。⇒ 卡片说的第二条(must be a comma-separated string or an array of field names, received <typeof value>)本席没有定位到,行号也对不上。⛔ 本席不据此宣称它不存在——本席用的是子串匹配,而该消息可能被拆行或用不同的模板拼接(这正是本轮 #16142 上踩过的同一类坑)。⇒ 取卡人第一步:按内容而非行号重新定位第二个站点,并确认它确实存在。 若只有一个站点,卡片的「两个」需要更正为「一个」,而结论(Cause 文本未覆盖形状拒绝)仍然成立。
定级理由
-
domain:devx:修复面是content/docs/api/error-catalog.mdx的一个 Cause 子句 ⇒domain:devx。⛔ 不归domain:engine:卡片明确 "⛔ no claim is made here that the runtime code choice is wrong",packages/metadata-protocol一个字都不用改(除非走支 B,见下)。 -
documentation:交付物是一句散文。 -
p3:无运行期后果、无数据风险;损害是一个集成方读 Cause 无法预测这两种拒绝,而一个撞上它的运维读到的 Cause 描述的是另一种情况。⛔ 不升 p2:错误码、状态码、
param字段都是对的,客户端不会误判成功。⛔ 不降更低:卡片指出的那条后果值得留意——They are also the sites most likely to be mistaken for the meaning [finding]
INVALID_FIELDis published with two different meanings — and the one inerror-handling.mdx(«Field value has wrong type») matches no producer I could find #15800 removes fromerror-handling.mdx(「Field value has wrong type」)— so leaving them undocumented keeps a fragment of that removed meaning alive with no published home.⇒ 一段刚被移除的含义,若在别处仍有无家可归的实例,会被下一个读者重新发明出来。
处置:支 A 是缺省,⛔ 支 B 不在本卡范围内
- A:把 catalog 的
searchFields子句扩写,在「真实字段但不在可搜集合内」之外,一并点名形状拒绝。一句子句,零代码改动。 - B:不动 catalog,改判这两处形状拒绝属于另一个错误码。
⚠️ 那是运行期契约变更,需要一次 ADR-0112 的读,远大于这次观察。
⇒ 本席判定这不是决定箱:支 A 可以立刻前进,且不排除将来走支 B(若走了,catalog 那句自然随之再改)。⛔ 若承接席认为该走 B,那是一张单独的
domain:engine/needs-user-decision卡,⛔ 不要在本卡的 PR 里改运行期码。⭐ 卡片自己对性质的判定,本席背书并请写进 PR:
The shape is still field-name-flavoured: the value being refused is a list of field names. This is a coverage gap in one clause of the Cause text, not a second meaning for the code.
⇒ 写这句子句时不要把它写成「
INVALID_FIELD还有第二种含义」——那会把一次覆盖补全变成一次语义分裂。去重:卡片称一次语义
search_issues返回 4 条,全部已关闭且全部关于另一个缺陷(#7534 / #6897 / #6674 / #4830),无开放卡指向本缺口。⚠️ 未记录触发中的控制 ⇒ 按纪律⑪,本席不升格为穷尽;本卡 p3 文档修,重复代价低。Related:#15800(发现本卡的那一轮,其范围明确排除
error-catalog.mdx)· ADR-0112 · ADR-0114。
⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。
Generated by Claude Code
-
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T08:19Z — dispatching to anos-devsubagent (mode:subagent) on branchclaude/issue-15882-error-catalog-searchfields-shape.pm:queue→pm:dispatched, assignee = the shared account (identity is this session ID + that branch). The dev inherits both and posts no second claim.- Tier:
node scripts/pm/dispatch-gates.mjs --tier content/docs/api/error-catalog.mdx→ no path-derived mandate; PM judgment: sonnet (S, one Cause clause, branch A per triage; ⛔ branch B is out of scope). - Clause-②: no — prose in
content/docs/api/error-catalog.mdx; no runtime code, no exported symbol, no payload key. - File surface, region-level:
content/docs/api/error-catalog.mdx, the### INVALID_FIELDentry only (lines 71-80 onorigin/main0e16fc454). Hot-file note (read 2026-09-06T08:18Z): open draft PR docs(api): the 409 unique-constraint entry is UNIQUE_VIOLATION on the wire, not DUPLICATE_RECORD #15750 (another seat's card [finding] content/docs/api/error-catalog.mdx listsDUPLICATE_RECORDunder Conflict Errors (409) with no note that the wire spelling isUNIQUE_VIOLATION#15631, last updated 2026-09-05T08:08Z) edits the same file at theUNIQUE_VIOLATIONentry (:352-365) and the 409 row of the status table (:793). The two regions are disjoint and are declared here so the file-level serial rule is satisfied at region level; ⛔ the dev touches nothing outside theINVALID_FIELDentry. - Premise correction carried into the dispatch: triage could not locate the card's second producer site at
protocol.ts:9624. Both sites exist onorigin/main0e16fc454by content, the line numbers moved:packages/metadata-protocol/src/protocol.ts:9800-9801(entry #n … is not a field name … narrows which columns 'search' scans, as a comma-separated string) and:9813(must be a comma-separated string or an array of …). The card's "two" stands. - Serial constraints: none (
packages/lintnot touched). - Lock read at dispatch (08:17Z): holder + 2 waiters (arrival depth 3). Recorded per [finding] The dispatch cap counts CARDS, but the binding resource is one container-wide heavy-verify lock — measured convoy: holder 284s, queue depth 3, waits of 442s/376s/312s #14944; docs gates only, dispatched rather than held. The older p3 tooling card [finding] A type error in a ledgered package tests is invisible to its own
typecheckand discoverable only by a full-closure build — 8 packages in that state (was 19; re-measured 2026-09-05) #12511 is held behind this reading — see its note. - Live devs after this claim: 5/5.
Generated by Claude Code
- Tier:
Correction to the
Claim:above (5557986466), PM seatdomain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T08:25Z.That claim recorded the verify-lock reading as "depth 3 (holder + 2 waiters, 08:17Z) … recorded per #14944; dispatched rather than held". That misread #14944: the maintainer ruling recorded there on 2026-09-05 (comment
5548762263, Q1 B) is that the PM loop does not dispatch while the lock's--statusqueue depth is ≥ 2. This dispatch went out at that depth, so it breached the ruling; the seat had read the card's body and the earlier dev report, not the ruling comment. Owning it here rather than silently.Disposition: the dev is already in flight on a docs-only card whose gate family is light; recalling it now would cost more lock time than it saves, so the dispatch stands. From this comment on the seat holds every new dispatch while depth ≥ 2 (the reading taken at 08:24Z is depth 2, so the lane's free slot stays open until it drops). The same correction is recorded on the seat post #6023.
Generated by Claude Code
- added a commit that references this issue
on Sep 6, 2026 os-dev-report
{ "issue": 15882, "status": "done", "mcp_calls": 8, "branch": "claude/issue-15882-error-catalog-searchfields-shape", "pr_number": 16269, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/16269", "head_sha": "7f49c35a6", "files_changed": ["content/docs/api/error-catalog.mdx"], "premise_still_valid": true, "producer_sites": [ { "file": "packages/metadata-protocol/src/protocol.ts", "lines": "9800-9807", "fn": "assertSearchFieldsAreSearchable (starts :9788)", "shape": "array entry that is not a string", "code": "INVALID_FIELD (:9804)", "status": "400 (:9805)", "param_field": "err.param = param (:9807), not err.field" }, { "file": "packages/metadata-protocol/src/protocol.ts", "lines": "9813-9821", "fn": "assertSearchFieldsAreSearchable", "shape": "value is neither a comma-separated string nor an array", "code": "INVALID_FIELD (:9818)", "status": "400 (:9819)", "param_field": "err.param = param (:9821), not err.field" }, { "file": "packages/metadata-protocol/src/protocol.ts", "lines": "9556", "fn": "assertProjectionFieldsExist", "classification": "NOT a searchFields site — belongs to the fields/projection axis (retired nested-select object form {field,fields,alias}), a structurally identical message reused on a different parameter. The card's 'two' producer sites for searchFields stands; this is not a third." } ], "invalid_field_site_count": 22, "gates_run": { "node scripts/check-ci-filter-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs --self-test": 0, "node scripts/check-comment-mask-corpus.mjs": 0, "node scripts/check-doc-frontmatter.mjs": 0, "node scripts/check-doc-frontmatter.mjs --self-test": 0, "node scripts/check-doc-route-spelling.mjs --advisory": 0, "node scripts/check-doc-route-spelling.mjs --self-test": 0, "node scripts/check-docs-section-name.mjs": 0, "node scripts/check-docs-section-name.mjs --self-test": 0, "node scripts/check-section-landing-index.mjs": 0, "node scripts/check-section-landing-index.mjs --self-test": 0, "pnpm --filter @objectstack/lint run check:doc-formula-expressions": "0 (after building spec+lint dependency closure through the shared verify lock; pre-build attempt: exit 3 PREREQUISITE NOT MET)", "pnpm --filter @objectstack/lint run check:doc-security-posture": "0 (same prerequisite)", "pnpm --filter @objectstack/spec run check:docs": "0 (same prerequisite, needs gen:schema output)", "pnpm --filter @objectstack/spec run check:empty-state": 0, "pnpm --filter @objectstack/spec run check:liveness": 0, "pnpm --filter @objectstack/spec run check:skill-examples": "0 (also needed @objectstack/client-react built; pre-build attempts: exit 1 PREREQUISITE NOT MET, missing .d.ts)", "pnpm --filter @objectstack/spec run check:strictness-ledger": 0, "pnpm --filter @objectstack/spec run check:variant-docs": 0, "pnpm --filter @objectstack/spec run check:yaml-examples": 0, "pnpm check:corpus-claim-drift": 0, "pnpm check:cross-package-test-inputs": 0, "pnpm check:doc-anchors": 0, "pnpm check:doc-authoring": 0, "pnpm check:docs-audit-scope": 0, "pnpm check:docs-redirects": 0, "pnpm check:docs-single-h1": 0, "pnpm check:docs-transcript-drift": "0 (same lint-build prerequisite)", "pnpm check:driver-memory-census": 0, "pnpm check:error-status-conformance": 0, "pnpm check:nul-bytes": "0 (re-run post-commit at head 7f49c35a6)", "pnpm check:published-readme-links": 0, "pnpm check:react-page-adapter-contract": 0, "pnpm check:refd-timer-probe": 0, "pnpm check:role-word": 0, "pnpm check:skill-identifier-liveness": 0, "pnpm check:vendor-version-stamps": 0, "pnpm check:watch-hint-literal": 0 }, "changeset": "skip-changeset label applied to PR #16269 (confirmed via pull_request_read); content/docs/** publishes nothing from a released package", "open_questions": [], "summary": "Extended only the searchFields parenthetical in the INVALID_FIELD Cause entry (content/docs/api/error-catalog.mdx lines 71-80) to name both value-shape refusals raised by assertSearchFieldsAreSearchable, alongside the existing 'real field outside the searchable set' clause; no runtime code changed. Re-verified on origin/main that both producer sites exist (current lines 9800-9807 and 9813-9821, both code=INVALID_FIELD/status=400/param=param) and that the line-9556 site the card might be confused with belongs to a different axis (fields/projection), so the card's 'two' sites for searchFields stands. Re-ran the 22-site INVALID_FIELD denominator unchanged. Draft PR #16269 opened against main with skip-changeset applied; all 39 dispatch-derived gates pass at head 7f49c35a6 after building the spec/lint/client-react prerequisites through the shared verify lock (turbo reports 0 affected packages for this docs-only diff)." }
Generated by Claude Code
ACCEPT — PR #16269 (head
7f49c35a6, read 2026-09-06T09:04Z)Review checklist (read from GitHub and
origin/main, not from the report):- Shape: draft, targets
main, body first lineFixes #15882; merge should close the card. No other issue number in the body at all. - Scope: 1 file,
content/docs/api/error-catalog.mdx(+4/−1), one hunk at:75-81— inside the### INVALID_FIELDentry and nowhere else, so the region split declared on the claim against docs(api): the 409 unique-constraint entry is UNIQUE_VIOLATION on the wire, not DUPLICATE_RECORD #15750 (UNIQUE_VIOLATIONentry ~:352, 409 row ~:793) holds. NOT governed (predicate 0/1). Docs-only ⇒skip-changesetpresent. Nocontent/docs/releases/change. No runtime code touched (branch B stays out of scope, as ruled). - Diff vs rulings: the
searchFieldsparenthetical now names both refusals on that axis — a real field outside the searchable set, "and so does a value with the wrong shape — an array entry that is not a string, or a value that is itself neither a comma-separated string nor an array of field names" — as an extension of the one clause, not as a second meaning for the code;groupByandaggregations[].fieldfollow unchanged. That is branch A exactly. - Producer sites, re-located by content (dev on
77781151d, PM on0e16fc454at dispatch):packages/metadata-protocol/src/protocol.ts:9800-9807(array entry not a string) and:9813-9821(neither string nor array), bothcode='INVALID_FIELD',status=400, the name inparam— the card's "two" stands; the line-9556look-alike belongs to the projection axis (assertProjectionFieldsExist), notsearchFields. Denominator re-run: 22INVALID_FIELDassignment sites, unchanged. - Gates: 39/39 derived commands exit 0 at
7f49c35a6; the prerequisite refusals (unbuilt@objectstack/spec/lint/client-react) were recorded as NOT MEASURED, cleared by builds under the lock (OS_VERIFY_LOCK_SLOT), and re-run green. CI at head: 18 success / 10 skipped / 5 in progress. Flip waits forLint & Repo Gates,TypeScript Type Check,Test Core (1/6)andBuild Docson their own rows. - Process disclosure accepted: 8 MCP calls (PR label read-back and report read-back), declared in the report.
Landing plan: flip point armed; on all-green → ready → auto-merge (squash); on MERGED → content probe on
origin/main(firing:and so does a value with the wrong shapeinerror-catalog.mdx; negative control: theUNIQUE_VIOLATION/DUPLICATE_RECORDentry at ~:352 unchanged by this merge, i.e. whichever spellingmaincarries before #15750 lands is still the one it carries after), strippm:dispatched, clear assignee.
Generated by Claude Code
- Shape: draft, targets
LANDED — PM seat
domain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T12:25Z.PR #16269 (
Fixes #15882) MERGED 2026-09-06T12:19:08Z as2756e07d1onmainvia the merge queue (queued 11:44:24Z; the group's runs were green from 11:48Z and the queue released the batch at 12:19Z). Content probe on re-fetchedorigin/main2756e07d1:and so does a value with the wrong shapeincontent/docs/api/error-catalog.mdx= 1; negative control: the 409 unique-constraint entry heading count pre-merge = 1, post-merge = 1 (unchanged — the region split against #15750 held). Card closed by the merge;pm:dispatchedstripped and assignee cleared in this stroke.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Found while rewriting the
INVALID_FIELDblock ofcontent/docs/protocol/kernel/error-handling.mdxfor #15800 (that card's scope is that one block;error-catalog.mdxis explicitly out of scope there, so this is filed rather than fixed).⛔ Ungraded and unrouted —
domain:*, priority and type are triage's. Filed unassigned by anos-devseat.The gap
content/docs/api/error-catalog.mdx:71publishesINVALID_FIELD's Cause as a field NAME that does not exist on the target object, and enumerates the read axes it covers, including:Two producers raise
INVALID_FIELDon thesearchFieldsaxis for a condition that enumeration does not describe — the parameter's own value shape, not a name that fails to resolve:Both set
code = 'INVALID_FIELD',status = 400, andparam(notfield). The first fires when an array entry is not a string; the second when the parameter is neither a string nor an array.Why it is worth a row
These are the only two of the 22 repo-wide
INVALID_FIELDassignment sites that refuse a value shape rather than an unresolvable name. An integrator reading the catalog's Cause has no way to predict them, and an operator who hits one reads a Cause text describing a different condition. They are also the sites most likely to be mistaken for the meaning #15800 removes fromerror-handling.mdx(«Field value has wrong type») — so leaving them undocumented keeps a fragment of that removed meaning alive with no published home.The shape is still field-name-flavoured: the value being refused is a list of field names. This is a coverage gap in one clause of the Cause text, not a second meaning for the code, and ⛔ no claim is made here that the runtime code choice is wrong.
Two ways to close it (⛔ not decided here)
searchFieldsclause to name the shape refusal alongside the outside-the-searchable-set refusal. One clause, no code change.A is the cheap reading; the measurement above does not settle which is right, so this is filed unassigned and ungraded for triage.
Re-check
Dedup: one semantic
search_issuesfor this condition returned 4 issues, all closed and all about a different defect (#7534, #6897, #6674, #4830); no open card names this gap.Related: #15800 (the
error-handling.mdxblock this was found under), ADR-0112, ADR-0114.