Skip to content

primaryField 不是可声明键(ObjectSchema 以 unrecognized_keys 拒收),但两条 lint 规则与 objectstack-data 技能文档都把它当作合法 name 面 #6326

Closed

Description

@hotlong

在 #6108(object/missing-name-field 谓词补读 nameField)实施过程中量到的相邻事实,按 Prime Directive #10 单独立单,不在那条 PR 里修。

量到的事实

primaryField 在 packages/spec 的对象 Zod schema 里根本不存在。全仓检索(排除 node_modules / dist)只有 4 处提及,没有一处是 schema 声明:

位置 用法
packages/lint/src/data-model-rules.ts object/missing-name-field 谓词的一支:!!obj.primaryField
packages/lint/src/validate-semantic-roles.ts:190 标题解析链 [obj.nameField, obj.primaryField, obj.displayNameField]
skills/objectstack-data/SKILL.md:1001 规则表:「an object with no name/title field or primaryField」
packages/spec/src/shared/suggestions.test.ts:100 仅作为 camelCase 拼写建议语料里的一个字符串

实测(packages/spec dist,17.0.0-rc.5):

const { ObjectSchema } = require('@objectstack/spec/data');
ObjectSchema.safeParse({ name: 'probe_obj', label: 'Probe', primaryField: 'code',
                         fields: { code: { type: 'text', label: 'Code' } } });
// => success: false
// => issues: [{ code: 'unrecognized_keys', keys: ['primaryField'], path: [] }]

ObjectSchema.create(/* 同上 */);
// => throws: ObjectSchema.create('probe_obj'): unknown key(s) — primaryField.

对照组 nameField: 'code' 同一形状 safeParse 通过。

为什么这不只是「死代码」

两个方向各有一个可达面:

  1. 文档面(作者会照做) —— skills/objectstack-data/SKILL.md 是 AI 编写元数据时读的技能文档,它明说 primaryField 是这条规则的合法逃逸口。照它写的对象在 ObjectSchema.create() 上被 ADR-0032「不静默丢弃未知键」的闸硬拒。文档说可以写,schema 说不能写。
  2. 规则面(判定永不成立) —— 反过来,!!obj.primaryField 与 validate-semantic-roles 的那一项对任何 schema 收得下的对象都恒为 false,属于 validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 家族的死支:它看起来在保护什么,实际什么都判不到,而覆盖它的既有断言(packages/cli/test/data-model-rules.test.ts:138)喂的是一个 schema 会拒收的 fixture,所以绿得没有意义。

两面同源:primaryField 从来没有(或已经不再)是可声明面,而三处消费者没跟上。

建议的处置方向(未定,交分诊)

要么在 spec 里补声明 primaryField(若它确有业务面),要么按 ADR-0049 enforce-or-remove 把三处消费者一起摘掉并改 SKILL.md。倾向后者:nameField 已是 ADR-0079 的规范主标题指针,再留一个平行指针与「One Zod source per metadata type」相悖;但这是公共作者面的取舍,不自行裁定。

发现现场:#6108 / PR(谓词修正)。那条 PR 只做了两件事以避免夹带——保留 primaryField 支不动(行为不变),并把该规则新的提示文案收敛为只点名真实可声明面(nameField 与 name-like 字段),不再向作者广告一个会被拒收的键。

查重:已按 primaryField / unrecognized_keys / phantom 在本仓 open issues 检索,无既有单。

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 7, 2026
  2. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: needs-user-decision + domain:devx.

    All four sites re-verified on origin/main (git grep origin/main, exact quoted name — a bare-name grep would have caught surviving families, SKILL notes 6):

    site line shape
    packages/lint/src/data-model-rules.ts :384, :391 !!obj.primaryField disjunct + the author-facing message
    packages/lint/src/validate-semantic-roles.ts :190 title-resolution chain [nameField, primaryField, displayNameField]
    packages/cli/test/data-model-rules.test.ts :136-138 the assertion that green-lights the whole thing, fed a fixture ObjectSchema rejects
    packages/spec/src/shared/suggestions.test.ts :100 camelCase-suggestion corpus only — not a declaration

    Zero declaration sites in packages/spec. The measured unrecognized_keys rejection stands.

    Why the decision inbox and not the queue: this is not underspecified — it is a public authoring-surface choice with two coherent answers, and they route to different lanes:

    • Remove (the body's own preference, ADR-0049 enforce-or-remove): diff is packages/lint ×2 + skills/objectstack-data/SKILL.md:1001 + the CLI test — zero spec change, stays domain:devx.
    • Declare: packages/spec gains a second canonical title pointer alongside ADR-0079's nameField, which is the "One Zod source per metadata type" tension the body names — that answer moves the accepted set, so routing flips to domain:spec under "shared contract surfaces have one owner".

    Routing is never escalated (step 2); the product question is, and this is one: does a second declarable title pointer exist on the authoring surface at all? domain:devx is applied as the landing site of the recommended branch so the card is not lane-less while it waits, and flips to domain:spec if the maintainer picks "declare".

    No target:v17: the phantom key is authoring-facing but nothing published today depends on it. Note the asymmetry the body already measured, though — the documentation direction is live: skills/objectstack-data/SKILL.md tells an AI author primaryField is a legal escape hatch, and ObjectSchema.create() hard-rejects it. If the answer is slow, a docs-only correction of that one line is separable and would remove the live half.

    Dedup: searched primaryField / unrecognized_keys / phantom across all 463 open issues and PRs in the three repos. One neighbour, not a duplicate: #6108 (object/missing-name-field not reading nameField) — this card is its measured-adjacent finding, filed separately under Prime Directive #10 exactly as it should be. #6108's PR deliberately left the primaryField disjunct untouched, so there is no in-flight collision.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-project-manager commented on Aug 7, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling — 2026-08-07. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「你帮我综合评估,我接受你的建议」). Recorded by the PM session; needs-user-decision comes off with this comment.

    Decision: remove, do not declare. Drop the primaryField branch from both lint rules (data-model-rules.ts predicate arm, validate-semantic-roles.ts resolution chain) and correct skills/objectstack-data/SKILL.md. nameField is the canonical title pointer (ADR-0079); a second parallel pointer has no pull and contradicts one-source-per-concept.

    Rationale: the SKILL doc is what AI authors read while writing metadata — it currently teaches a key the schema hard-rejects (unrecognized_keys), which is directly teaching AI to fail; the lint branches are dead for any schema-accepted object, and their existing test coverage feeds a fixture the schema would reject, so the green is meaningless. Removing both restores declared = enforced from the cheap side.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 7, 2026
  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 8, 2026
  6. self-assigned this
    on Aug 8, 2026
  7. hotlong commented on Aug 8, 2026

    @hotlong
    ContributorAuthor

    认领 — devx 车道执行座位。 pm:queue → pm:dispatched,assignee 已置。

    • session: session_01BDmDsu2575gDxeMCxXhDE3
    • branch: claude/issue-6326-primaryfield-phantom-key
    • 文件面: packages/lint/src/data-model-rules.ts(:384 / :391)、packages/lint/src/validate-semantic-roles.ts(:190)、skills/objectstack-data/SKILL.md(:1001)、packages/cli/test/data-model-rules.test.ts(:136-138)。⛔ 不碰 packages/spec(裁定是 remove 不是 declare,本单零 spec 改动)、⛔ 不碰 packages/spec/src/shared/suggestions.test.ts:100(那只是 camelCase 拼写建议语料,不是声明点,分诊已单独点出)、⛔ 不碰 content/docs/releases/。
    • 竞态复核: 派发前重读全部评论 —— 2 条(分诊 + 维护者裁定),均非认领,无其它 session ID。本单未被他人占用。

    路线已由维护者钉死(2026-08-07 17:00Z 裁定):remove,不 declare。 nameField 是 ADR-0079 的规范主标题指针;再立一个平行指针没有拉力,且与「One Zod source per metadata type」相悖。所以 domain:* 不翻到 spec,留在 devx。

    同批次文件面复核(本席已核):在飞另三张为 #6253(packages/lint/src/validate-visibility-predicates.ts + index.ts barrel)、#6317(.objectui-sha + 生成的 changeset)、#6095(pnpm-workspace.yaml / pnpm-lock.yaml),与本单零相交。

    给实施方的两点:

    1. packages/cli/test/data-model-rules.test.ts:136-138 那条断言喂的是一个 ObjectSchema 会拒收的 fixture,所以它的绿是没有意义的。⚠️ 不要简单删掉它 —— 按「整条替换」处置:写下它幸存的那半个事实,或者用一条真能失败的断言取代。删掉一条空绿断言而不补,等于把覆盖面悄悄减一。
    2. 文档面才是当下活着的那一半:skills/objectstack-data/SKILL.md:1001 正在告诉 AI 作者 primaryField 是这条规则的合法逃逸口,而 ObjectSchema.create() 会以 unrecognized_keys 硬拒——这是在教 AI 写出必然失败的元数据。lint 那两支对任何 schema 收得下的对象恒为 false,属于 validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 家族的死支。

    Generated by Claude Code

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 8, 2026
  9. removed their assignment
    on Aug 8, 2026
  10. hotlong commented on Aug 8, 2026

    @hotlong
    ContributorAuthor

    撤回上一条认领 —— 本单未被实施,回到 pm:queue、assignee 已清空。

    上一条认领发出后、派发 dev 之前,维护者给出并发上限指令(不超过 3)。本座位当时在飞已是 3 个 dev(#6253 / #6317 / #6095),本单会是第 4 个,故不派。

    ⚠️ 撤回的理由是状态诚实而不是流程洁癖:pm:dispatched 的含义是「有 dev 正在做」。让一张没人做的单挂着这个标签,正是本仓反复清理的那种「看起来有人接了、其实没有」的形状——别的座位会据此跳过它。宁可多两次标签写入,也不留这个假状态。

    上一条认领评论里的技术内容依然有效,下一位接手的人可以直接用:文件面四处(data-model-rules.ts:384/:391、validate-semantic-roles.ts:190、skills/objectstack-data/SKILL.md:1001、packages/cli/test/data-model-rules.test.ts:136-138)、维护者裁定 remove 不 declare、以及那两点提醒(⛔ 不要简单删掉 :136-138 那条空绿断言而不补;文档面是当下活着的那一半)。

    本座位有空位即重新认领,不视作放弃。


    Generated by Claude Code

  11. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 8, 2026
  12. self-assigned this
    on Aug 8, 2026
  13. hotlong commented on Aug 8, 2026

    @hotlong
    ContributorAuthor

    重新认领 —— 空位已释放。 pm:queue → pm:dispatched,assignee 已置。

    上一条撤回(并发上限 3)之后 #6095 已带证据关闭,腾出一位。本单现在真的有 dev 在做。

    • session: session_01BDmDsu2575gDxeMCxXhDE3
    • branch: claude/issue-6326-primaryfield-phantom-key
    • 文件面 / 排除项 / 维护者裁定(remove 不 declare)/ 两点提醒:一律以本单 01:0xZ 那条认领评论为准,内容未变,不重复。
    • 竞态复核: 再次重读全部评论 —— 分诊、维护者裁定、本席的认领与撤回,无其它 session ID 的认领。

    同批次文件面复核(本席已核,与撤回时相比有变化):在飞另两张为 #6253(packages/lint/src/validate-visibility-predicates.ts + index.ts barrel,PR #6472 正在修 check:type-check-debt 的返工)与 #6317(.objectui-sha + 生成的 changeset)。⚠️ 本单与 #6253 同在 packages/lint,但不同文件:本单动 data-model-rules.ts 与 validate-semantic-roles.ts,#6472 动 validate-visibility-predicates.ts 与 index.ts。⛔ 本单不要碰 packages/lint/src/index.ts——那是 #6472 正在改的 barrel,本单删两支谓词分支不产生新的规则 id,不该需要动它;若你发现真的需要动,先停下来报上来,不要自行处理。

    ⚠️ 一条今天刚用一个 CI 周期换来的教训,本单必须执行:@objectstack/lint 是那 19 个「把自己的测试排除在 typecheck 之外」的包之一,所以 pnpm --filter @objectstack/lint typecheck 结构性地看不见测试文件里的类型错误。#6472 正因此在本地全绿、却在 CI 的 check:type-check-debt 上红(TEST_DEBT 记 42、实际 47,+5)。本单同样落在 packages/lint,必须在本地跑 pnpm check:type-check-debt,不要只跑包级 typecheck。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions