Skip to content

serve 面保证 stored metadata 规范拼写:reference_to → reference 归一化 + spec 裁定 reference_field/referenceField 协议归属(ui#6837 半 1) #13700

Closed

Description

@zhuangjianguo

背景

ui#6837(objectui 删除 17 处 legacy 兼容臂)经维护者裁定按「两半」执行,本卡为 半 1(objectstack 侧,先行)。裁定评论:objectstack-ai/objectui#6837 (comment id 5475017957)。

维护者定调(逐字):「objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀」——协议归一化属于服务端;前端只按协议执行,不背兼容臂。

关键事实(为什么 serve 面保证是承重件,编译不是):stored metadata 是运行时 JSON,经 API 下发,不经过任何 TypeScript 编译;zod 拒绝面只在新文档 parse/publish 时生效,不回溯已存量数据;TS 编译错误只保护手写 BYO schema。因此若 serve 面不归一化,前端删臂后存量 reference_to 会静默降级——必须先由本卡兜底。

工作项

  1. 测量:ADR-0087 conversion 链是否已覆盖 reference_to → reference?若已覆盖,出具 serve 面证据(存量含 reference_to 的 metadata 经 serve 路径下发后只含 reference)即可关此项。
  2. 若未覆盖:按 page-header-subtitle-alias 先例落 conversion + os migrate meta 支持,使 serve 面只出现规范拼写。
  3. spec 裁定(同卡):reference_field vs referenceField 的协议归属——哪个是规范拼写、另一个是否进 conversion/拒绝面。spec 域按既定豁免由 spec 席位自审。

下游

半 2 = objectui 删 17 处臂 + 调用链 + 52 处仓内 legacy 发射点(ui#6837,Blocked-by: 本卡),BYO/手写 TS 破坏性在 changeset 显式声明,附 dev-mode 警告 pin。

Blocks: objectstack-ai/objectui#6837

Activity

  1. added
    bugSomething isn't working
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    and removed on Aug 31, 2026
  2. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    分诊 → domain:spec · pm:blocking · p2(自 p3 上调,理由见末)· bug。

    锚定。 ADR-0087 的 conversion 链在 packages/spec/src/conversions/registry.ts(page-header-subtitle-alias 先例即在此),⇒ domain:spec(唯一所有者)。os migrate meta 的 CLI 半边随卡,由 spec 席在认领评论申报文件面。pm:blocking:objectui#6837 半 2 挂在本卡上。

    工作项 1 已经答了:未覆盖

    在 origin/main 上直接查了,省掉这一步:

    git grep -n "reference_to" -- packages/spec/src/conversions packages/spec/src/migrations
      → 0 命中
    控制:同文件 registry.ts 对 subtitle|alias 有 89 处命中 —— 查询有效,零是读数不是空查
    

    旁证同向且更强,packages/drivers/driver-mongodb/src/mongodb-schema.ts:114 的 docblock 逐字写着:

    reference_to is a REJECTED ALIAS, not a normalised one.
    FieldSchema.safeParse({ name:'company_id', type:'lookup', reference_to:'company' })
    → "Unrecognized key(s) on this field: reference_to. Did you mean reference_to -> reference?"

    ⇒ 现状是拒收面有、归一化面无。而本卡的论证恰恰指出拒收面不解决问题:stored metadata 是运行时 JSON,不过 zod parse、不回溯存量。所以工作项 2 成立,直接按 page-header-subtitle-alias 先例落 conversion + os migrate meta。 ⛔ 不要再花一轮去测「是不是已经覆盖了」。

    ⚠️ 工作项 3 的前提可能不成立 —— 请先证伪再裁

    卡片把它写成「reference_field vs referenceField,哪个是规范拼写」,即假定其中一个是现行 spec 键。查下来不是:

    git grep -rn "reference_field\|referenceField" -- packages/spec/src   (非测试)
      → 仅 2 处,且都在 describe 散文里作占位符:
         field.zod.ts:1172  `{ [referenceField]: parentId }` — the effective predicate is …
         field.zod.ts:1201  …AND-composed with the parent-relationship condition { [referenceField]: parentId }…
    

    两处都是方括号里的变量占位(「以引用字段名为键」),不是声明的 schema 键。⚠️ 这是一次限定在 packages/spec/src 的部分读数(有控制:邻近词命中,查询有效),不是全树结论。但它把问题掉了个头:要裁的可能不是「两个拼写谁规范」,而是 —— 这两个拼写到底由谁发射、又被谁读?objectui 那 17 处 legacy 臂读的是什么,需要先做一次全树 + objectui 的发射点普查,再谈协议归属。⛔ 不要在普查之前就把其中一个写进 conversion 表。

    定级 p2(原 p3)

    上调的理由是承重性,不是严重性:本卡是「两半」执行的半 1,而半 2(objectui 删 17 处臂)一旦先落或误落,存量带 reference_to 的 metadata 会静默降级 —— 卡片自己把这条论证得很清楚,且这正是维护者定调「后端的元数据只要对,前端按协议执行就行了呀」所依赖的前提。一张挡着跨仓下游、且失败模式是静默数据降级的卡,不该在 p3。不判 p1:目前没有实际发生的降级,前端臂还在。

    ⚠️ 跨仓顺序是硬的:本卡落地 且 serve 面证据出具之后,objectui#6837 半 2 才可动。请在半 2 的卡上确认 Blocked-by: 已经落到标签与评论上,而不是只写在正文散文里 —— 散文对解锁扫描不可见。

    Clause-② 申报必填(路径肢 packages/spec/src/**)。加一条 conversion 是把先前被拒的拼写变成可接受并归一化 ⇒ 我的读数是 Clause-②: yes(扩宽 accept 面),按 CONTRACT_REVIEW_TIER 派发。spec 域按既定豁免自审,但该行不得省略。


    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 Aug 31, 2026
  4. self-assigned this
    on Aug 31, 2026
  5. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Claim: session_01PBjwYLS6BciTQW3c9xQiD2 · branch claude/issue-13700-reference-to-conversion · seat domain:spec (os-warren)

    Clause-②: yes(采分诊读数:conversion 把先前被拒拼写变为可接受并归一化 = 扩 accept 面)⇒ fable 派发;PR 创建时 draft + needs:contract-review 双载体停靠,放行走 席内 contract review 新流程(维护者 2026-08-31 裁定,固化中 #13795)。

    文件面申报(分诊要求):packages/spec/src/conversions/registry.ts(+migrations 若先例带)+ conversion 测试;os migrate meta CLI 半边预计触 packages/cli 的 migrate 命令面——⛔ 不触 packages/rest/src/rest-server.ts(cli 席跨席围栏);若实际需要触碰,dev 停下回报,本席与 cli 席协调后再动。

    范围钉死(按分诊):工作项 1 已答(未覆盖,⛔ 不再花轮重测);工作项 2 直接按 page-header-subtitle-alias 先例落 reference_to → reference conversion + os migrate meta + serve 面证据(存量含 reference_to 的 metadata 经 serve 路径下发只含 reference);工作项 3 只做普查不裁(reference_field/referenceField 全树+objectui 发射/读取点普查,⛔ 普查前不把任一拼写写进 conversion 表,结果交回本席送裁)。下游 ui#6837 半 2 的 Blocked-by 落标核查随卡带回。


    Generated by Claude Code

  6. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 31, 2026
  7. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 13700,
      "status": "done",
      "branch": "claude/issue-13700-reference-to-conversion",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
      "premise_still_valid": true,
      "summary": "Item 1 (not re-tested, per dispatch): premise re-verified once at pickup against origin/main — grep exit 1 for reference_to in packages/spec/src/conversions + migrations, positive control page-header-subtitle-alias present (registry.ts, walk.ts, tests) — still uncovered, no race. Item 2 (landed): conversion field-reference-to-alias in the protocol-18 bucket, field key reference_to renames to reference on objects[].fields and objectExtensions[].fields with house #4923 precedence; retiredFromLoadPath from day one because FieldSchema always refused the key, so the authoring rejection with its rename hint is untouched and the accept-surface widening is exactly stored rehydration + migration chain. Registered in step-18 conversionIds + rationale; os migrate meta needed NO CLI code (it replays the chain — the claim comment's predicted packages/cli touches did not materialize); rest-server.ts untouched (fence respected). Changeset @objectstack/spec minor. gen:spec-changes and gen:upgrade-guide ran byte-identical — both artifacts project released majors only (all 14 sibling protocol-18 conversions equally absent). Serve-face evidence: three end-to-end pins in packages/metadata-protocol/src/protocol.stored-conversions.test.ts seed a reference_to row around the schema gate and prove getMetaItems, getMetaItem (with _diagnostics.valid true) and loadMetaFromDb all emit only reference — the credential ui#6837 half 2 waits on, written into the PR body. Item 3 (census only, not judged): see open_questions. Item 4: ui#6837's Blocked-by on this card lives ONLY in comment 5475055291 ('Blocked-by: objectstack-ai/objectstack#13700'); the body has zero mention of 13700 and labels are domain:ui + pm:queue + priority:p3 (no blocked marker). Per the standing platform reading, the unlock scan greps BODY only — so the current placement is invisible to it; the dispatch asked whether it sits in labels/comments, and it does sit in a comment, but the body channel the scanner reads is empty. Not modified by me (fence); PM to backfill the body line. Also note: my dispatch prompt's premise ('labels/comments = scanner-visible') conflicts with the platform-readings fact table ('scan greps body only') — flagged rather than silently picking a side.",
      "tests": "All heavy runs via scripts/pm/os-verify-lock.sh, slot issue-13700, exits captured before any pipe; union ran at head ef1563e4d (= PR head, quoted from git rev-parse at the run). (1) spec build + deps: VERDICT command-exit 0. (2) Targeted: vitest conversions.test.ts + migrations.test.ts + stored.test.ts — 'Test Files 3 passed, Tests 310 passed'. (3) Serve pin: metadata-protocol protocol.stored-conversions.test.ts — 'Test Files 1 passed, Tests 9 passed', serve log shows the live notice 'stored object/crm_contact carries a pre-protocol shape; converted field.reference_to at objects[0].fields.company_id.reference' with the os migrate meta --stored --apply prescription. (4) Ablation, prediction stated first (3 new pins red, 6 old green): mutation of the two apply-call key args to a marker, proven on disk by anchored grep (marker 0 to 2, callsites 2 to 0); spec REBUILT; ablation-dist-preflight plant mode exit 0 (marker in dist); run: 'Tests 3 failed | 6 passed' — exactly the three #13700 pins. Restore proven byte-identical (git hash-object 78009dd... equals HEAD blob, git diff HEAD empty); spec REBUILT again; preflight --absent: 'marker absent from all 215 built files'; rerun 9/9 green. Mutation script carried trap restore on EXIT INT TERM with absolute paths. (5) Full spec suite: 'Test Files 444 passed, Tests 11911 passed'. (6) spec typecheck green incl. test layer: 'check:test-typecheck: OK — @objectstack/spec's test layer compiles'. metadata-protocol declares no typecheck script — NOT MEASURED locally, rides CI. (7) dispatch-gates.mjs derived at ef1563e4d (stderr header names this tree; only dispatch-gates.mjs itself differs on origin/main, no workflow gate sources); implicated families green: check:adr-0087-registration, check:spec-changes, check:upgrade-guide, check:migration-registry, check:authorable-surface, check:engine-double-contract, check:cross-package-test-inputs, check:test-source-alias, check:query-options-erasure, check:objectql-double-limit, check:where-matcher, plus check-nul-bytes ('OK, scanned 7599 text files'). Full farm is CI's.",
      "mcp_calls": "7 — issue_read get_comments ui#6837 (payload timeline was windowed: 56 items, 15 served), create_pull_request, issue_read get_labels 13700, issue_write 13700 (union write), issue_write 13847 (PR label), issue_read get_labels 13700 (compare read-back), add_issue_comment (this report). REST channel probed first and gated: repo-scoped GET returned 403 'GitHub access is not enabled for this session' — channel switch declared per the degradation ladder. All card/PR reads otherwise went git + public-page payload (zero quota); PR label read-back via data-name grep on the PR page confirmed needs:contract-review present alongside the auto labels (documentation, size/m, tests, tooling); card read-back shows the 6-label union intact.",
      "open_questions": [
        {
          "question": "Work item 3 ruling input — reference_field vs referenceField protocol ownership. Census (anchored, with positive controls; objectstack at ef1563e4d, objectui at origin/main a7807b4): OBJECTSTACK: reference_field 0 hits tree-wide (control reference_to: 212); referenceField 14 hits, ALL non-key — 12 are the docs/describe prose placeholder pattern '[referenceField]: parentId' (field.zod.ts docblock + describe, field.test.ts pin, 5 docs mdx, 2 CHANGELOGs, liveness note) and 2 are the unrelated identifier referenceFieldNames in packages/rest. Zero emission points, zero schema keys. OBJECTUI: the two spellings are TWO DIFFERENT CONCEPTS, both declared in objectui's OWN @object-ui/types: reference_field (39 hits) = 'display field on the referenced object' (declared field-types.ts:459, views.ts:86, zod/views.zod.ts:50; read in fallback chains like display_field or displayField or reference_field or 'name' in LookupField/UserField/ObjectChart/ListView/UserFilters/deriveMasterDetail/resolveActionParams; emitted by enrichment in RecordDetailView/RelatedList/RecordMetaFooter/ObjectGallery/buildDefaultPageSchema/fieldEnrichment; taught in content/docs/fields/lookup.mdx); referenceField (105 hits) = 'FK field on the child object pointing back to the parent' on related-list config (declared views.ts:648, zod/views.zod.ts:132; read heavily by RelatedList/DetailView/deriveRelatedLists) plus the same prose placeholder; RecordDetailDrawer.tsx:301 runs a tolerant reference_field ?? referenceField arm ACROSS the two concepts. So the card's framing 'which spelling is canonical' is a category error: there is no single concept with two spellings.",
          "options": [
            "A: Rule both spellings as objectui-local contract keys, NOT protocol keys — spec declares neither, stores neither, serves neither; no conversion entry, no rejection-face change in spec; objectui reconciles its own two concepts (and the cross-concept ?? arm) on its side of the protocol line.",
            "B: Elevate the display-field concept into the spec (declare a key on lookup/master_detail, then convert/reject the other spellings) and separately judge the related-list FK key.",
            "C: Add one or both spellings to the spec rejection face only (named refusal, no declaration)."
          ],
          "recommendation": "A, because: real business need — zero measured server-side population (0 emission points in objectstack; stored rows cannot legally carry either key past FieldSchema); long-term soundness — the two spellings name two different concepts, so a conversion pair would encode a false synonym; AI-hard-to-get-wrong — FieldSchema's strict unknown-key rejection already refuses both at publish by name via the generic surface, which is the structural guard; startup scope — declaring a new display-field capability in the spec is implementation-first territory (B) that nothing currently pulls for, and C adds alias-table weight for keys with no at-rest population. Per the 5475017957 ruling this goes to the spec seat for the formal 裁明."
        },
        {
          "question": "referenceTo (camelCase) was deliberately NOT added to the conversion — the card names reference_to only, and referenceTo is objectui's resolved action-param dialect (own alias table in ui/action.zod.ts), not a spelling the objectql runtime wrote into stored object rows. Should a follow-up conversion cover it anyway?",
          "options": [
            "A: No — rejected-only stance stays; no measured at-rest population.",
            "B: Yes — widen field-reference-to-alias or add a sibling entry for defense in depth."
          ],
          "recommendation": "A; the docblock records the reasoning, and B can ride any future measurement that finds a real stored row carrying it."
        }
      ],
      "out_of_scope_findings": [
        "Not filed as cards (dispatch fence: findings go to PM). (a) ui#6837 Blocked-by is comment-only (5475055291); body has no 13700 line and no blocked label — invisible to the body-grep unlock scan; PM to backfill the body line per the platform-readings Blocked-by channel rule. (b) Driver docblocks (driver-sql sql-driver.ts #11567, driver-mongodb mongodb-schema.ts #13222) state 'reference_to is a REJECTED ALIAS, not a normalised one' — after this PR that sentence is half-stale (still rejected at authoring, now normalized at stored/migrate); cross-package doc drift left untouched to respect the declared file face."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  8. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    席内 contract review:PASS — PR #13847(head ef1563e4),依 objectstack#13795 流程,provenance = 维护者 2026-08-31 席内复核裁定。

    契约判断清单:

    裁定记录(spec 席按 ui#6837 裁定评论 5475017957 的委任自审)

    工作项 3:reference_field / referenceField 协议归属 = 选项 A——两拼写均为 objectui 本地契约键,⛔ 非协议键。 依据(dev 普查,锚定+阳性对照):objectstack 全树 reference_field 0 命中(对照 reference_to 212 命中)、referenceField 14 命中全为占位散文/无关标识符——零发射点、零 schema 键、零存量;objectui 侧两拼写是两个不同概念(被引对象的显示字段 vs 关联列表子侧 FK 列),各自在 @object-ui/types 有独立声明与消费链。卡片「谁是规范拼写」的设问是范畴错误——不存在同一概念的两拼写。⇒ spec ⛔ 不声明、不 convert、不加具名拒绝(结构性 unrecognized-keys 已覆盖);objectui 侧两概念的收敛(含 RecordDetailDrawer 跨概念 ?? 臂)归 objectui 车道自理,证据链在本评论与 dev 报告。选项 B(升协议)= 无拉动的能力声明,C(具名拒绝)= 无存量的别名表配重,均拒。
    referenceTo 后续 conversion = 选项 A(不加):无实测存量;将来任何测得真实存量的读数可另立卡。registry docblock 已记推理。

    移交处置:ui#6837 的 Blocked-by: objectstack-ai/objectstack#13700 已回填 body(解锁扫描可见通道,平台事实表为准);驱动 docblock 半陈旧(「rejected, not normalised」在 #13847 后只对编写面为真)→ 本席立 finding 卡。

    放行:剥标双载体 → ready → auto-merge;MERGED 后本卡经 Fixes 关闭 ⇒ 剥残留,ui#6837 半 2 的解锁条件(落地 + serve 证据)即告齐备。


    Generated by Claude Code

  9. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 31, 2026
  10. added a commit that references this issue on Aug 31, 2026
  11. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions