Skip to content

feat(spec): re-introduce fieldGroups[].visibleWhen with its enforcement side - #13030

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-12715-fieldgroups-visiblewhen
Aug 29, 2026
Merged

feat(spec): re-introduce fieldGroups[].visibleWhen with its enforcement side#13030
os-zhuang merged 2 commits into
mainfrom
claude/issue-12715-fieldgroups-visiblewhen

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes #12715

Executes the maintainer's 2026-08-28 option-A ruling (issue comment 5448852266): re-introduce fieldGroups[].visibleWhen WITH its enforcement side, closing the ADR-0085 / ADR-0049 enforce-or-remove loop — removed while unenforced, re-declared now that the objectui section-gating contract (objectstack-ai/objectui#6236, pinned by objectstack-ai/objectui#6111) consumes a section predicate. Clause-② applies on both limbs: this stops at draft; the review chain owns enqueue.

The four ruled conditions

  1. Same predicate vocabularyvisibleWhen: ExpressionInputSchema.optional() on ObjectFieldGroupSchema (packages/spec/src/data/object.zod.ts), the exact spelling and CEL machinery actions/rows use (mirrors the field-level declaration at field.zod.ts). Bare-string shorthand normalizes to the { dialect: 'cel', source } envelope; no second predicate language, no new semantics.
  2. One atomic spec-side PR — all in this diff:
    • the key re-introduced on the fieldGroups vocabulary;
    • BOTH tombstone rows updated: the visibleWhen tombstone (was at object.zod.ts:1152) is retired — the key is real again; the visibleOn guidance no longer says group predicates were removed and now points at the real slot (kept rejected-with-guidance, the pointed-not-aliased posture);
    • deriveFieldGroupLayout (field-group-layout.ts) passes the predicate through verbatim — string or envelope, tolerant of un-parsed metadata exactly like its collapse-alias handling; new FieldGroupSection.visibleWhen + exported FieldGroupVisibleWhen type; never stamped on the ungrouped trailing bucket;
    • strictness tests updated: the batch-20 tombstone pin flips to acceptance pins (both authorable shapes, normalization asserted) and a visibleOn-still-rejected pin; five new passthrough pins in field-group-layout.test.ts;
    • ADR-0085 amended with a dated addendum (the §5 derivation note and the re-introduction record);
    • changeset (minor, additive widening) — the ADR-0087 gate's own verdict: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)", so no registry entry is demanded;
    • regenerated artifacts via repo tooling only: gen:schema (authorable-surface), gen:docs (content/docs/references/data/object.mdx), gen:api-surface, gen:export-origins;
    • customer doc content/docs/data-modeling/schema-design.mdx: the stale "group-level visibility predicates deferred/removed" prose replaced with the real key (property table row + supported-migrations row). New prose cites ADRs only (check:doc-authoring Rule 3 compliant, gate green).
  3. Condition-3 verification — the card's "no further objectui work" claim is FALSE on the current objectui ref (origin/main f9653aec0, read via the pinned checkout's git, read-only). Both derived section-divider synthesis sites (ModalForm / DrawerForm derived-fieldGroups branches) already stamp visibleWhen: section.visibleWhen beside the membership claim — but the adapter they read from, deriveFieldGroupSections (packages/plugin-form/src/fieldGroups.ts), maps the spec's FieldGroupSection onto ObjectFormSection copying only name/label/fields/collapse and never carries visibleWhen, so the stamped value stays undefined on the derived path. Follow-up filed: deriveFieldGroupSections drops the new spec fieldGroups[].visibleWhen — the two derived section-divider stamps still can never gate objectui#6709 with a Blocked-by: back-link to The spec fieldGroups vocabulary has no section-predicate slot, so a section grouping gate is structurally unreachable on fieldGroups-derived forms #12715 — no objectui code changed from this seat (contract-first). That card also owns turning the six-way matrix's two previously-undiscriminable rows (ModalForm derived-fieldGroups, DrawerForm derived-fieldGroups) into discriminating pins — they become discriminable in objectui, where the stamp renders, once the adapter carries the predicate.
  4. Whole-group disappearance (header included) rides the landed A section hidden by FormSection.visibleWhen still renders its FIELDS on the plugin-form chain — the console renderer drops the whole section objectui#6236 contract — the spec key's describe/JSDoc states the fail-closed whole-group semantics; evaluation stays the renderer's. The tabbed arm The tabbed modal arm (ModalForm contentLayout: 'tabbed') cannot carry a section visibleWhen at all — FormFieldTab declares no predicate slot objectui#6237 reuses the same grouping contract and meets this vocabulary at its own card; fix(service-datasource): reject a pool block the memory arm cannot honour (#5931) #6237 remains open and is out of scope here.

Evidence (all runs on the final tree at d19f166ee unless marked)

  • Build: pnpm --filter @objectstack/spec build — verify-lock VERDICT command-exit 0 (gen:schema + gen:openapi + tsup + dts, 34/34 declarations present).
  • Tests: pnpm --filter @objectstack/spec test — 439 files passed | 1 skipped, 11680 tests passed | 1 skipped, lock VERDICT command-exit 0. Targeted battery (field-group-layout, batch-20, object) 255/255.
  • Typecheck: pnpm --filter @objectstack/spec typecheck — lock VERDICT command-exit 0; includes check:test-typecheck "OK — test layer compiles" (the edited .test.ts files are inside the measured test program, not excluded).
  • Reverse verification (both legs from committed state; tests resolve the mutated modules via RELATIVE src imports, no dist in the path, so no rebuild per leg; mutation and restore both proven on disk — anchored grep count 1 to 0 per leg, restore by git checkout HEAD -- path with blob-hash equality against the HEAD blob and empty git diff HEAD):
    • Leg A (drop the derivation passthrough): predicted exactly 2 RED in field-group-layout.test.ts (the two passthrough pins) — observed 2 failed | 15 passed, the predicted test names.
    • Leg B (remove the schema key): predicted exactly 2 RED in object-strictness-batch20.test.ts (the two acceptance pins), visibleOn pin stays green — observed 2 failed | 51 passed, the predicted test names.
  • Gate union (derived by node scripts/pm/dispatch-gates.mjs, no hand-fed paths; stderr header confirms derivation from objectstack-ai/objectstack at this worktree): 40/40 path-derived root gates exit 0; 16/17 filtered+convention gates exit 0; check:type-check-debt on the turbo-built closure: "OK — 31 ledger entries re-measured … none above its recorded number" (exit 0, captured pre-pipe). Earlier in the run the union caught two real regen gaps (check:docs → object.mdx; check:generated → api-surface + export-origins) — regenerated with the prescribed commands and re-run green.
  • NOT MEASURED (declared, not green): scripts/pm/check-half-states.mjs exits 3 "PREREQUISITE NOT MET — no valid GitHub credential in this container" (its own text: no reading at all) — CI/PM seat owns it. check:react-declaration-parity needs objectui's browser-built manifest, which this repo cannot produce (check:generated's own documented posture).
  • Repo-wide eslint narrowing (proven, three evidences): ① population — the repo's single eslint.config.mjs never enables type-aware linting for any file (config's own comment at ~line 325: "no parserOptions.project, no typed rules"), so a diff cannot move an untouched file's verdict; ② pnpm exec eslint --no-inline-config --format json over the diff's 4 TS files: 4 linted, 0 errors, 0 warnings, exit 0 captured pre-pipe; ③ the diff's other 7 files (mdx/md/json) all report "File ignored because no matching configuration was supplied" — outside the population entirely. The 4 TS files are therefore the diff's complete linted population.
  • Channel deviation declared: repo-scoped REST probe returned 403 in this container (whole class), so the objectui dedup ran as one targeted MCP search_issues (9 hits including the known control cards A section hidden by FormSection.visibleWhen still renders its FIELDS on the plugin-form chain — the console renderer drops the whole section objectui#6236/v17 GA 交付物:AI 一键升级客户元数据项目(升级 skill,骑在 D2 conversions 之上) #6111 — non-zero, channel live); the follow-up card was filed via MCP.

Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 28, 2026 17:25
…nt side

Option-A maintainer ruling (2026-08-28): the section predicate slot returns
to the fieldGroups vocabulary now that the section-gating contract consumes
it — CEL via ExpressionInputSchema, the ADR-0089 canonical spelling; both
tombstone rows updated; deriveFieldGroupLayout passes the predicate through
verbatim; strictness pins flipped from curated rejection to acceptance;
ADR-0085 amended; authorable-surface regenerated by gen:schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
gen:docs picks up the new ObjectFieldGroup.visibleWhen row; api-surface and
export-origins pick up the FieldGroupVisibleWhen type export. Repo tooling
only (gen:docs / gen:api-surface / gen:export-origins), no hand edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/authorable-surface/data.json, packages/spec/export-origins/data.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via visibleWhen (symbol))
  • content/docs/automation/flows.mdx (via visibleWhen (symbol))
  • content/docs/data-modeling/field-types.mdx (via visibleWhen (symbol))
  • content/docs/data-modeling/fields.mdx (via visibleWhen (symbol))
  • content/docs/data-modeling/formulas.mdx (via visibleWhen (symbol))
  • content/docs/data-modeling/schema-design.mdx (via visibleWhen (symbol))
  • content/docs/data-modeling/validation-rules.mdx (via visibleWhen (symbol))
  • content/docs/deployment/environment-variables.mdx (via visibleWhen (symbol))
  • content/docs/protocol/objectui/layout-dsl.mdx (via visibleWhen (symbol))
  • content/docs/ui/create-vs-edit-form.mdx (via deriveFieldGroupLayout (symbol))
  • content/docs/ui/field-grouping-and-order.mdx (via deriveFieldGroupLayout (symbol))
  • content/docs/ui/pages.mdx (via visibleWhen (symbol))
  • content/docs/ui/views.mdx (via visibleWhen (symbol))

5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via visibleWhen (symbol))
  • content/docs/releases/v12.mdx (via visibleWhen (symbol))
  • content/docs/releases/v15.mdx (via visibleWhen (symbol))
  • content/docs/releases/v16.mdx (via visibleWhen (symbol))
  • content/docs/releases/v17.mdx (via visibleWhen (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/authorable-surface/data.json, packages/spec/export-origins/data.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 0da7cd2c1114649755b7f9f45a7dbbe34985ac6epackageMentionDocs.

Which tree this was computed on

This run read content/docs from 1bf110992729ee3a8601e00112b67c00a2ce223e — the merge of head d19f166eef9d5d72fe5e23f66c50b48464b55714 into base 0da7cd2c1114649755b7f9f45a7dbbe34985ac6e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1bf110992729ee3a8601e00112b67c00a2ce223e && git checkout 1bf110992729ee3a8601e00112b67c00a2ce223e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0da7cd2c1114649755b7f9f45a7dbbe34985ac6e d19f166eef9d5d72fe5e23f66c50b48464b55714 && git checkout -B drift-repro 0da7cd2c1114649755b7f9f45a7dbbe34985ac6e && git merge --no-ff d19f166eef9d5d72fe5e23f66c50b48464b55714

node scripts/docs-audit/affected-docs.mjs --json 0da7cd2c1114649755b7f9f45a7dbbe34985ac6e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 0da7cd2c1114649755b7f9f45a7dbbe34985ac6e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 28, 2026
@os-trump
os-trump marked this pull request as ready for review August 28, 2026 18:47
@os-trump
os-trump requested a review from hotlong as a code owner August 28, 2026 18:47
@os-trump
os-trump enabled auto-merge August 28, 2026 18:47
@os-trump
os-trump added this pull request to the merge queue Aug 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 28, 2026
@os-trump
os-trump marked this pull request as draft August 28, 2026 18:50

Copy link
Copy Markdown
Collaborator Author

Landing note (domain:spec seat, session_01LpRNHxWZgSUgVnFT9mQQo4): converted back to DRAFT per the Governed Surface Queue Guard's preferred remedy — this seat armed auto-merge without accounting for the governed surface in the diff, and the guard correctly refused the merge group (run 33200997095, exit 3).

State, so nobody re-derives it:

  • The governed path is docs/adr/0085-object-semantic-roles-over-surface-hint-blocks.md (docs/adr/** — architecture decision records). The addendum is part of the maintainer's ruled condition ② ("ADR-0085 §5 note amended"), so it belongs in this PR and is not coming out.
  • Everything else is done: contract review PASS on the card (The spec fieldGroups vocabulary has no section-predicate slot, so a section grouping gate is structurally unreachable on fieldGroups-derived forms #12715, comment 5456439173), all 33 PR-level checks green on head d19f166ee, and the queue group's other three workflows were green/in-progress when the guard refused.
  • What lands this: either a maintainer merge (a human merge IS the review record for a governed surface — the guard's own preferred regime), or an APPROVED review by an authorized approver (os-zhuang / hotlong, per the 2026-08-27 ruling quoted in the guard) pinned to head d19f166eef9d5d72fe5e23f66c50b48464b55714, after which this seat re-queues. Any push after that approval goes stale and reopens the refusal. This seat will not submit that approval under any account, per the guard.
  • @hotlong is already on the reviewer list.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 29, 2026 09:59
@os-zhuang
os-zhuang enabled auto-merge August 29, 2026 10:00
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33247193112 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Temporal Conformance (live PG + MySQL) — 失败步骤: Run driver-sql suite against both live servers

    × drops the orphaned generated column, and keeps the one still carrying a constraint 5383ms
    FAIL  src/sql-driver-13056-orphan-shadow-column-cleanup.test.ts > a retired shadow-carried UNIQUE leaves no column behind (#13056) > drops the orphaned generated column, and keeps the one still carryi
      ↳ 失败原因: Error: Test timed out in 5000ms.
    
  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL  test/serve-publishes-bound-port.e2e.test.ts > #13062 the non-zero half — nothing an ordinary boot publishes may move > publishes exactly the port it was asked for when th
      ↳ 失败原因: @objectstack/cli:test: Error: ENOENT: no such file or directory, open '/tmp/os-bound-port-home-n2gf2T/runtime.env_local.json'
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 16 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Copy link
Copy Markdown
Collaborator Author

Queue ejection triaged (domain:spec seat, session_01LpRNHxWZgSUgVnFT9mQQo4). Neither failure is this PR's, and I am deliberately NOT re-queueing it.

Both failures are foreign to this diff, by file surface

This PR's complete file list is packages/spec/** (4 sources + 3 generated api/authorable/export-origin manifests), content/docs/** (2 pages), docs/adr/0085-…md, and one changeset. Zero files under packages/cli. Zero files in any driver-sql package.

failing job failing test reason line verdict
Test Core (1/6) packages/clitest/serve-publishes-bound-port.e2e.test.ts ENOENT … runtime.env_local.json not this PR's — different package, and a known queue flake
Temporal Conformance driver-sql → src/sql-driver-13056-orphan-shadow-column-cleanup.test.ts Error: Test timed out in 5000ms. not this PR's — different package, and a timeout, not an assertion

The triage comment's own rule separates these two: a timeout is a load/timing reading, an assertion is a behaviour change. Both rows above are a timeout and an environment ENOENT; neither is an assertion, and neither test exercises anything this diff declares. A new optional visibleWhen key on ObjectFieldGroupSchema has no path to a CLI port-publishing probe or to a SQL shadow-column cleanup.

Why no re-queue from this seat

Two independent reasons, either sufficient:

  1. serve-publishes-bound-port.e2e.test.ts is a live queue flake with a standing owner. It has ejected 12 independent PRs in a rolling 24 hours. The owning card is Queue-flake anchor: test/serve-publishes-bound-port.e2e.test.ts #13158 (priority:p1, domain:cli, dispatched and assigned) — it predates this ejection and is the place the fix belongs. The triage comment's own item 2 is explicit: with an aggregation issue already open, re-queueing before the fix lands only burns another full-queue rebuild for every PR behind this one. Blind re-queueing here would be a shared-resource cost paid by other lanes to buy this PR nothing.
  2. This PR is on the governed path. The diff includes docs/adr/0085-object-semantic-roles-over-surface-hint-blocks.md, and docs/adr/** is in the governed register (Prime Directive 14). ⛔ No AI seat merges, queues, arms auto-merge, or re-queues it — landing is the maintainer's hand-merge or a GOVERNED_APPROVERS approval pinned to the head. So the re-queue decision is not this seat's to take even setting reason 1 aside.

What is actually true about this PR's own state

Its own PR-side CI was 36/36 green at head d19f166ee, with the approval pinned to that head. Nothing measured here contradicts that. The ejection is the merge queue running the full suite (PR CI runs the affected subset), which is how two unrelated packages' failures reach a spec-only diff in the first place.

Recommended next action for whoever holds the merge controls: one re-queue attempt once #13158 lands, or a hand-merge. Not a second re-queue before then.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The spec fieldGroups vocabulary has no section-predicate slot, so a section grouping gate is structurally unreachable on fieldGroups-derived forms

3 participants