Repository navigation
[Decision] 字段分组漂移已裁定为平台问题(hotcrm#806)—— 定统一机制:lint 镜像规则(不动契约)与/或 section 差量引用形式(契约扩宽) #13855
Description
Activity
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actions裁决:B —— section 差量引用形式,直接处理(维护者 2026-08-31)
项目总监席 · session
session_01KGtaLpkW1mycWgkbSb3H6t维护者原话(逐字):「直接处理b」—— 选 B(spec 契约扩宽:section 按组引用继承成员);A(lint 镜像规则)未采,重提需新证据(B 落地后仍见漂移形状);「直接处理」= 本席即刻派发,不等车道轮询。
设计约束(裁决 + 实测约束,派发单同笔携带)
- 语义:section 增加按组引用(形如
group: contact_info),成员与组内呈现(collapse / visibleWhen / 空组丢弃)经deriveFieldGroupLayout(ADR-0085 §5,单源)派生 —— ⛔ 不在 section 侧重实现组装规则; - 面覆盖:两个逃生舱(record page
record:details的properties.sections、view 级form.sections)都在类内 —— 若两套 section 词表结构对称则一批设计;结构分叉则 record page 半边先落、另一半回报,⛔ 不硬凑; - 组 key 存在性校验:跨 schema 引用照既有先例走 reference diagnostics(
UserFilterFieldSchema.field的 "must exist — checked by reference diagnostics" 模式),parse 期收 string、引用诊断查存在,⛔ 不发明新校验通道; - 混用规则要设计并钉死:单个 section 引用组与逐字段枚举的互斥/共存、同页两种 section 并存的顺序语义 —— 设计判断进 PR 正文供复审;
- 时序解耦:hotcrm#806 已裁的应用侧简化(删手工 sections 回归派生)不等本卡 —— 本形式服务的是未来仍需局部编排的场景;
- 条款②:YES(路径肢
packages/spec/src/ui/**+ 内容肢扩宽 accept 面),draft PR +needs:contract-review随可复审增量同笔挂(⛔ 不预挂),changeset minor(加宽式扩展); - 渲染半边:objectui 消费卡已立(号见下一评论/执行报告),严格在 spec 半边合并且 objectui 采版后派发。
状态转移(同笔)
needs-user-decision→pm:queue;domain:spec不动。派发即行。
Generated by Claude Code
- 语义:section 增加按组引用(形如
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actionsClaim — spec half (option B, section group reference)
Dispatcher: 项目总监席 · session
session_01KGtaLpkW1mycWgkbSb3H6t(maintainer verbatim order 「直接处理b」).
Executor: os-dev developer subagent inside that same session.- Issue: [Decision] 字段分组漂移已裁定为平台问题(hotcrm#806)—— 定统一机制:lint 镜像规则(不动契约)与/或 section 差量引用形式(契约扩宽) #13855 — spec half only (the objectui renderer half is tracked separately).
- Branch:
claude/issue-13855-section-group-reference(pushed empty as the write-route probe before the first edit). - Worktree:
/home/user/objectstack-13855-group-ref(dedicated per-task worktree offorigin/mainat9b21037d). - Scope: the ruled delta form — a layout section may reference an object field group by key and inherit membership plus presentation via
deriveFieldGroupLayout(ADR-0085 section 5). Both escape hatches assessed; the mixing rule is designed and pinned, and stated in the PR body for contract review. - Clause-② applies (path limb
packages/spec/src/ui/**plus a content limb widening the accept set): draft PR withneeds:contract-reviewattached in the same stroke as PR creation, changeset@objectstack/specminor.
Channel note: repo-scoped REST returns 403 for this seat and
ghis absent, so GitHub writes go through MCP.
Generated by Claude Code
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actionsos-dev-report
{ "issue": 13855, "status": "done", "branch": "claude/issue-13855-section-group-reference", "pr": "https://github.com/objectstack-ai/objectstack/pull/13897", "premise_still_valid": true, "summary": "Spec half of ruling B landed on BOTH escape hatches — the two section vocabularies were measured symmetric enough for one design, so there is no record-page-only remainder. A section may now carry `group: 'contact_info'` in place of `fields`; membership and the group's presentation (label, icon, description, collapse, visibleWhen, empty-group drop) are derived by deriveFieldGroupLayout (ADR-0085 section 5) and nothing is re-implemented in section land. The mixing rule is declared once in packages/spec/src/shared/section-group-reference.ts and passed each surface's own key names: group and fields mutually exclusive, a section with neither refused (the guarantee the previously-required fields key gave), the keys the group already declares refused beside group rather than given a precedence, both section kinds coexisting in declared array order, and group refused on a wizard step because a group carries exactly the visibleWhen and collapse that #13704 refused there one day earlier. Existence is NOT a parse question: the spec door takes any well-formed snake_case key (the UserFilterFieldSchema.field precedent) and two new reference diagnostics report a dangling one — page-section-group-unknown in validate-page-field-bindings and form-section-group-unknown in validate-form-layout, both reading one shared index, both at the family's advisory severity. The key grammar is also single-sourced now as FIELD_GROUP_KEY_PATTERN so the declaring surface and the two referencing surfaces cannot drift apart. Type-surface consequence, named in the PR body and the changeset: fields becomes optional on both section shapes, so z.infer types it possibly-undefined; every in-repo reader already guards it and no authored metadata changes shape.", "tests": "All at final commit 4cb9ddba6, exit codes captured before any pipe. SUITES: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 EXIT=0 (Test Files 445 passed | 1 skipped (446); Tests 11962 passed | 1 skipped (11963)); pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 EXIT=0 (Test Files 87 passed (87); Tests 2409 passed | 5 skipped (2414)); pnpm --filter @objectstack/spec --filter @objectstack/lint run typecheck EXIT=0; pnpm lint (repo-wide eslint . --no-inline-config) EXIT=0. NEW PINS: 34 in packages/spec/src/ui/section-group-reference.test.ts through both real doors (ComponentPropsMap record:details and FormViewSchema) plus 11 across the two lint rule test files — accept pins prove the key SURVIVES the parse on both surfaces (form side through a .transform, the view-level .superRefine and the legacy groups fold), the enumerated form including an empty fields array is pinned unchanged, every non-refused surface key is pinned as still accepted, and the controls are an unknown sibling key still refused plus a near-miss spelling steered to group. NOT-MEASURED guard: confirmed with tsc --listFiles that the new spec test file really is inside the tsconfig.test.json program (1 hit), so the green typecheck says something about it. REVERSE VERIFICATION from the committed state, both legs source-resolved (the pins import the mutated modules by relative path, not through dist/, so no rebuild leg applies) — each mutation proved on disk by grep counts of both the removed anchor (1 to 0) and the injected text (0 to 1) plus a changed blob hash, each restore proved by blob-hash equality against the HEAD blob AND an empty git diff HEAD, with an absolute-path trap on EXIT INT TERM: (a) neutralising the group/fields mutual-exclusivity refusal gave Tests 2 failed | 32 passed — exactly the two exclusivity pins, one per surface — and restored 16e68e091a7d to 16e68e091a7d; (b) neutralising the dangling-group diagnostic gave Tests 7 failed | 45 passed across both lint rule files and restored 6779ac6870c6 to 6779ac6870c6; tree clean after both legs. GATE FAMILY derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack from the real change set (44 families, 49 runnable commands) and ALL RUN: 42 green; 7 NOT MEASURED by each gate's own printed verdict (check-dev-prereqs and check:dual-build-cjs-loads PREREQUISITE NOT MET, check:type-check-debt refuses --re-measure without the built workspace closure by design, lint check:doc-formula-expressions needs @objectstack/formula built, check:react-declaration-parity has no objectui manifest in this repo, check-test-completeness and check-half-states both exit 3 saying nothing was read) — reported as unread instruments, never as green; 3 went red and were repaired by their own prescribed regeneration then re-run green (check:docs regenerated three content/docs/references pages, the export-surface freshness pin regenerated export-origins plus api-surface for the new FIELD_GROUP_KEY_PATTERN export, and check-system-context-census was pure line rot from this PR's docblock in object.zod.ts, repaired with the gate's own --fix). check-nul-bytes EXIT=0, plus a control-byte self-scan over all 20 changed paths: no matches.", "mcp_calls": "9 — one issue get, one comments get, one assign, one claim comment, one open-PR listing for the serial-path check, one create_pull_request, one label write, one PR body read-back, and this report comment. Channel note: repo-scoped REST returned 403 for this seat and gh is absent, so every GitHub write went through MCP; everything else (remote branch survey, overlap check on the four spec-touching open PRs) was done locally with git.", "open_questions": [ { "question": "Rule 2 of the mixing rule: a group-referencing section is REFUSED the keys the group already declares (name, label, icon, description, the collapse pair, visibleWhen/visibleOn). The alternative reading — section keys OVERRIDE the group's, so the author writes only the delta — is textually available in the card's 「作者只写差量」 and is the one line a contract reviewer could reverse. The ruling delegated the mixing rule to this seat, so it is designed and pinned rather than escalated; this records it for the review the needs:contract-review label opens.", "options": [ "A — refuse (shipped): a section-level label beside group would put group presentation back in section land, which is the one thing ADR-0085 section 5 single-sources, and it re-creates the second writable spelling of the fact this whole form exists to stop copying. Refusing now and allowing overrides later is additive; the reverse is breaking.", "B — override: the section's own key wins over the group's. More forgiving for a one-off heading, but it is a precedence rule the ruling does not cover, and it hands AI-authored metadata a second place to write the same fact." ], "recommendation": "A, because it is the reversible direction and the only one that needs no new accept semantics. Pull on either side is currently zero — no producer writes a group-referencing section yet — so under startup scope discipline the narrow accept surface wins, and B stays a one-line widening the day a real author asks for it." } ], "out_of_scope_findings": [] }
Generated by Claude Code
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actions契约复审:PASS —— PR #13897,head
4cb9ddba6,同笔清标 + 落地链启动项目总监席契约复审记录 · session
session_01KGtaLpkW1mycWgkbSb3H6t· 档位当场机读认定(claude-fable-5= CONTRACT_REVIEW_TIER)· 对全 diff 复审,⛔ 非对报告。裁决七约束逐条核验
- 组装单源不动 ✅ —— spec 侧零解析零组装:section schema 只声明引用与拒绝,
deriveFieldGroupLayout(ADR-0085 §5)保持唯一组装点;共享模块注记明写「It never resolves the key」; - 两面一批 ✅ —— 词表对称性实测成表(PR 正文),一个共享规则模块(
shared/section-group-reference.ts)双面各传自己的键名,无剩余半边; - 存在性走既有 reference diagnostics ✅ ——
page-section-group-unknown/form-section-group-unknown落在恰好已管这两面字段存在性的两条规则里,共享一个索引(indexObjectFieldGroups),page 侧走COMPONENT_FIELD_SPECS[...].nestedSections同一张描述表;零新通道;severity=warning 与族一致、query 达例外保留 error; - 混用规则设计并钉死 ✅ —— 恰一来源(group ⊕ fields,双无拒绝保住旧 required 保证)、组有键拒绝(⭐ 保守方向 = 可逆方向,备选「section 覆盖组」在 PR 正文点到具体行供改判 —— 本席复审判定:拒绝方向正确,与裁决「不发明超出需要的配置面」同向,⛔ 不改)、跨 section 按声明序共存、
fields: []存在性语义保住存量;⭐ 附加抓着一个裁决没点名的走私口:wizard step 拒group(组会经派生把 Wizard view v1 — declaration-and-refusal tightening of FormViewSchema type:'wizard' (Card S, ruled shape of #13622) #13704 前日拒掉的visibleWhen/collapse 送回 step); - 时序解耦照旧(渲染半边 objectui#7051 具名剩余);
- 条款②合规 ✅ —— draft +
needs:contract-review随创建同笔挂,PR 自报 yes,changeset minor(加宽); - 键语法单源化(
FIELD_GROUP_KEY_PATTERN声明面与两引用面同读)是超出裁决要求的正确加固。
验证面
34 spec 钉 + 11 lint 钉(accept 钉覆盖 transform/superRefine/legacy fold 存活、未拒键逐个钉住防拒绝表写宽);反向验证双腿(互斥拒绝 + 悬空诊断)盘上证明 + 字节还原;spec 445 文件 11962 测试green、lint 2409 green、repo-wide lint 0;门禁族 42 绿 + 7 NOT MEASURED 如实申报 + 3 红按各自法定再生修复。
收口(同笔)
needs:contract-review从 PR 摘除(卡侧无预挂,无需清)→ PR 转 ready → auto-merge 挂上(入队资格 = 全检查绿,CI 收敛即落)。落地后本卡关闭,objectui#7051 时序门解锁的前半(spec 合并)成立,余下等 objectui 采版。
Generated by Claude Code
- 组装单源不动 ✅ —— spec 侧零解析零组装:section schema 只声明引用与拒绝,
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actionsPR #13897 synced with main and pushed as
a61844e50—check:docsis green. Correcting the cause on the record: the red was NOT a race with main, it was my own ordering miss (I regenerated the docs, then edited a.describe()string to satisfycheck:doc-authoring, and never re-rangen:docsafter that edit — the regenerated diff moves only my own sentence, and nothing from main touches that file's inputs). The merge itself was conflict-free but did surface a separate real defect the pre-commit hook caught:content/docs/permissions/system-context.mdxmerged without a text merge, silently dropping main's declarations-count bump and an 18-line paragraph; repaired by taking main's page and re-applying only the mechanical anchor shift. Full suites, typechecks and the gate family re-run at the merged head — including 17 documentation gates that were owed and unrun becausecontent/docsentered the change set after my first derivation. Details in the correction comment on the PR.
Generated by Claude Code
huangyiirene commented
on Aug 31, 2026 CollaboratorAuthorMore actions落地关卡:PR #13897 已合并 —— 本卡完成关闭(completed)
项目总监席 · session
session_01KGtaLpkW1mycWgkbSb3H6t落地事实:PR #13897 以 squash 合入 main,合并提交
39404f3d9(feat(spec,lint): a layout section can reference a declared field group instead of copying its members)。核验方法(记录在案,防后来者误判):squash 合并使分支头
a61844e50对origin/main的 ancestor 检查返回否 —— 这不是未合并,是 squash 的固有形状;落地以git log origin/main --grep="13897"命中合并提交为准。与 hotcrm#1438(squash8c40737)同一模式。红灯插曲(已在卡上如实更正):合并前一度红,根因是执行代理自己的
gen:docs次序遗漏(非与 main 的竞态 —— 本席最初的「分支落后」诊断已当场更正);修复推送转绿后,GitHub 在早前红头上已自动解除 auto-merge,重新挂上后入队合并。闭环链:裁决 B(维护者逐字「直接处理b」)→ 派发 → PR #13897 → 契约复审 PASS(七约束逐条,记录见上方评论)→ 红→修→绿 → 落地
39404f3d9。剩余(不在本卡):渲染半边 objectui#7051 时序门前半(spec 合并)自此成立,余下等 objectui 采版后派发 —— 已在该卡注记。hotcrm#806 应用侧简化按裁决第 5 条与本卡解耦,照旧走 hotcrm 车道。
状态转移(同笔):关闭 completed,摘
pm:queue;domain:spec留卡。
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
维护者裁定(2026-08-31,逐字):「#806 我认为就是协议的问题,元数据应用应该是简化的业务实现,不应该编码处理这中 lint,要处理也是平台统一处理。」
⇒ 定性已裁:hotcrm#806 测到的三套分组漂移是平台/协议缺口,应用仓⛔不自建诊断;本卡承载平台统一机制的形状选择。
机制根因(总监席 2026-08-31 实测,记录于 hotcrm#806)
协议在 ADR-0085 里把五种页面布局方言收敛为一个正典 + 逃生舱:
fieldGroups+Field.group(object.zod.ts:1125 起),成员关系声明在字段上,组装单源deriveFieldGroupLayout(ADR-0085 §5);record:details的properties.sections)—— 整页接管;form.sections—— 编辑表单编排。缺口:两个逃生舱都是整体接管 + 逐字段枚举 + 零派生 —— section 语法无法写「这一节 = 对象的
contact_info组」,组装 helper 在树上但 section 够不到;作者一拉逃生舱就必须把同一份成员事实手抄第二、三遍,抄本与正典零机械联系,对象每加一个字段抄本静默旧一分。且无互检:「哪些已声明字段组在此页/表单无处安放」机器可算而无人算(#715 的field-group-shadowed管相反方向)。实测后果:hotcrmcrm_lead三套分组互不相同,详情页看不到重复管理/禁止致电字段,间接参与 hotcrm#1207(p1,疑似重复线索被转成第二套客户)。形状选项
field-group-shadowed的姊妹规则 ——「自定义 record page / view form 使已声明字段组无处安放」逐组点名报告。纯诊断、读既有声明、对所有应用统一生效,把静默漂移变响亮。group: contact_info),成员经deriveFieldGroupLayout派生,作者只写差量 —— 从根上消灭手抄。需要 spec 设计(与逐字段枚举的混用规则、优先级、两个逃生舱是否同批)。拉动
hotcrm#806(实测三套漂移 + #1207 实害)+ 维护者本裁的架构立场本身(「元数据应用应该是简化的业务实现」⇒ 协议必须给出不产生抄写债的编排形式)。
总监席推荐:C(A 先行)。 A 无契约变化可直接入队;B 为 spec 设计卡、按条款②走契约复审档。
Refs: hotcrm#806 · hotcrm#1207 · #13848(应用仓基本原则落编,本裁为第二条原则的出处)· ADR-0085。