Skip to content

docs(spec): re-measure the lookup-picker reader claims against objectui and date them - #17004

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-15540-liveness-lookup-reader-prose
Sep 9, 2026
Merged

docs(spec): re-measure the lookup-picker reader claims against objectui and date them#17004
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-15540-liveness-lookup-reader-prose

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #15540

Three written-down claims in packages/spec said objectui reads a snake_case spelling it no longer reads, and one evidence string described a landed call in the future tense. All four are re-measured and rewritten to the measurement. No verdict, schema arm, accept set, default, bound or refusal moves — this is evidence prose and one docblock's factual claim about a foreign consumer.

Base fe2b7554a · head af158289e · objectui measured at ed971e8fc9e698f6fbe07355cfdbaef910037cb7.

Clause-②: no
Every item is prose: two note strings and one evidence string in an evidence
ledger, and one docblock above a declaration. No schema arm, bound, default or
refusal moves; no export is added, removed or renamed; no accept set widens or
narrows. 拉回已声明契约(把写下来的断言拉回可测量的事实)⇒ 常规档.


The objectui measurement — re-derived, not inherited

The dispatch carried a week-old six-key table from the domain:ui seat with an explicit caveat that it may have moved. It had: dependsOn has since lost its snake leg too, and allowCreate (unmeasured then) has one.

Measured on objectui ed971e8 (main, 2026-09-08 23:30 UTC), over the three readers the docblock names — packages/fields/src/widgets/{LookupField.tsx,RecordPickerDialog.tsx,deriveLookupColumns.ts}:

$ git rev-parse HEAD
ed971e8fc9e698f6fbe07355cfdbaef910037cb7

KEY                camel_reads snake_reads
displayField       34         0
descriptionField   9          0
lookupColumns      3          1
lookupPageSize     2          1
lookupFilters      14         0
dependsOn          11         0
allowCreate        3          1
key snake leg still read? site
displayField ⛔ no — camel-only LookupField.tsx:260
descriptionField ⛔ no — camel-only LookupField.tsx:261
lookupColumns yes LookupField.tsx:276
lookupPageSize yes LookupField.tsx:277
lookupFilters ⛔ no — camel-only LookupField.tsx:278
dependsOn no — changed since the dispatch table LookupField.tsx:309
allowCreate yes — unmeasured in the dispatch table LookupField.tsx:271

The three surviving snake legs, verbatim:

271:  const explicitAllowCreate = fieldMeta?.allow_create ?? fieldMeta?.allowCreate;
276:  const lookupColumns: ... = fieldMeta?.lookup_columns ?? fieldMeta?.lookupColumns;
277:  const lookupPageSize: ... = fieldMeta?.lookup_page_size ?? fieldMeta?.lookupPageSize;

The four camel-only reads, verbatim:

260:  const displayField = fieldMeta?.displayField || fieldMeta?.reference_field || 'name';
261:  const descriptionField: string | undefined = fieldMeta?.descriptionField;
278:  const lookupFilters: ... = fieldMeta?.lookupFilters;
309:    const raw = cascadeMeta?.dependsOn;

dependsOn's retirement is stated in objectui's own source at LookupField.tsx:296-302, which is why the dispatch's table is now one row out of date:

The key is read THROUGH THE DECLARED TYPE (objectui#6153): dependsOn is BaseFieldMetadata.dependsOn, the spec's field-level spelling — and since objectui#7357 the ONLY one. That card retired objectui's snake_case twin depends_on under ADR-0049 enforce-or-remove.

Firing controls on every zero

A grep returning 0 is not a reading until an adjacent term known present returns non-zero. Same command shape, same file set:

.lookup_columns in radius (known PRESENT):    1     <- the regex shape fires
.display_field  in radius (the reported zero): 0
.displayField   in radius (control, PRESENT):  1     <- the key exists, only the spelling is gone

Scope note on the measurement radius

The docblock's claim is scoped to the three readers it names, and so is the table. Outside packages/fields, other objectui consumers do still normalise snake spellings (app-shell/src/utils/resolveActionParams.ts, plugin-list/src/ListView.tsx, plugin-charts, plugin-form). The rewritten docblock therefore says what those three readers do, not what the whole objectui tree does — a blanket "objectui is camelCase-only" would have been a new false claim.


Item A — the two displayField notes (packages/spec/liveness/field.json)

Criterion: no display_field token survives in either note, and each note names the reader it actually has.

A1 — /props/displayField. Before:

"objectui LookupField/RecordPickerDialog — candidate label field in the record picker (reads displayField || display_field)."

After:

"objectui LookupField/RecordPickerDialog — candidate label field in the record picker. Read camelCase-only: packages/fields/src/widgets/LookupField.tsx does fieldMeta?.displayField || fieldMeta?.reference_field || 'name' and hands the result to RecordPickerDialog as its displayField prop. The snake_case twin this note used to name was deleted from LookupFieldMetadata by objectui#7155 (maintainer ruling A′ of 2026-09-03 — no deprecation window, no dual read), so the camelCase spelling this schema declares is the only authored one the picker reaches for; reference_field is objectui's own third leg of that chain, not a spec key (objectui#6875 grades it no-producer). Claim dated to objectui ed971e8, re-measured 2026-09-09 — a reading of a pinned foreign tree that nothing in this repo re-checks."

A2 — /props/inlineColumns/children/displayField. Before:

"objectui GridField.tsx lookup cells — LookupField display_field."

After:

"objectui GridField.tsx lookup cells — the column's camelCase displayField is handed straight to LookupField as displayField at all three lookup-cell call sites (packages/fields/src/widgets/GridField.tsx: the read-only table cell, the list form-factor cell and the editable cell; the column type declares displayField?: string). The snake_case twin this note used to name was deleted from LookupFieldMetadata by objectui#7155. Claim dated to objectui ed971e8, re-measured 2026-09-09 — a reading of a pinned foreign tree that nothing in this repo re-checks."

A2's claim is checkable at GridField.tsx:774, :870 and :898, all three passing displayField: c.displayField, with the column type declaring it camelCase at :117.

Criterion met, machine-checked:

A1 contains display_field: false
A2 contains display_field: false
FIRING CONTROL A1 contains displayField: true
FIRING CONTROL A2 contains displayField: true

Both entries keep status: "live". Nothing is re-classified.


Item B — the LOOKUP PICKER docblock (packages/spec/src/data/field.zod.ts)

Criterion: the per-key tolerance statement matches the fresh measurement key-for-key, including the keys where the snake leg IS still read.

Before (the tail of the docblock):

All optional: the renderer auto-derives a sensible multi-column result from the referenced object's schema when omitted (objectui packages/fields: LookupField / RecordPickerDialog / deriveLookupColumns, which read both these camelCase keys and their snake_case aliases).

After — the blanket clause is dropped and replaced with a per-key paragraph:

All optional: the renderer auto-derives a sensible multi-column result from the referenced object's schema when omitted (objectui packages/fields: LookupField / RecordPickerDialog / deriveLookupColumns).

SNAKE_CASE TOLERANCE — measured on the consumer, not assumed. Those readers accept a snake_case alias for three of the seven keys declared below, and for three only: lookupColumns, lookupPageSize and allowCreate are each read as SNAKE-then-CAMEL nullish-coalescing in LookupField.tsx. The other four — displayField, descriptionField, lookupFilters and dependsOn — are read camelCase-only, so a snake_case spelling of any of those four arrives at the picker as nothing at all. Three of them lost their twin in objectui#7155's LookupFieldMetadata alias sweep (no deprecation window, no dual read); dependsOn lost its own later, retired under ADR-0049 enforce-or-remove in objectui#7357. The three aliases that survive are objectui's own back-compat, never a spelling this schema declares — the camelCase key is the authored one in every case.

Claim dated to objectui ed971e8, re-measured 2026-09-09. It is a reading of a pinned foreign tree and no gate here re-checks it: when the console pin moves, this paragraph is what goes stale.

(The rendered file spells that coalescing operator literally; it is transliterated here only so this body survives the comment sanitizer.)

The three keys on the exclusion list — lookupColumns, lookupPageSize, dependsOn — are handled exactly as instructed: the two whose tolerance claim is true are named as still carrying a live snake leg and are not rewritten away; dependsOn is moved to the camel-only side because the measurement moved it, and the PR body states that as a change against the dispatch's own table rather than folding it in silently.


Item C — the valueDomain evidence tense

Criterion: the sentence's tense matches the landed state its own first clause describes.

Before: …#isValueDomainMember (the ONE membership predicate the write path **will** call — shared with the settings door)

After: …#isValueDomainMember (the ONE membership predicate the write path **calls** — shared with the settings door)

The same string's first clause already quotes the landed call site, and it is landed on this base:

packages/objectql/src/validation/record-validator.ts:712:      !isValueDomainMember(def.valueDomain, s)
packages/objectql/src/validation/record-validator.ts:60:import { isValueDomainMember, type ValueDomain } from '@objectstack/spec/shared';

Residual will call in the ledger: 0. The evidence pointers themselves are unchanged, and check:liveness is green. The valueDomain note was not touched — that was corrected separately and is off this PR's scope.


Changeset — both halves measured

A real patch changeset is present: .changeset/lookup-picker-reader-prose-remeasured.md.

Half 1 — is the path in @objectstack/spec's files[]? Yes, via two distinct entries:

["dist","json-schema","liveness","prompts","llms.txt","README.md",
 "src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"]

Half 2 — is the changed text in what the package actually publishes? Measured with npm pack --dry-run --json on this tree (@objectstack/spec@17.3.0, entryCount 2073):

path verdict role
liveness/field.json SHIPPED changed (A + C)
src/data/field.zod.ts SHIPPED changed (B)
liveness/object.json SHIPPED positive control
src/data/object.zod.ts SHIPPED positive control
src/index.ts NOT SHIPPED discriminating negative control
scripts/liveness/check-liveness.mts NOT SHIPPED discriminating negative control

The negative controls discriminate rather than merely being absent: src/data/object.zod.ts ships while src/index.ts, its sibling in the same directory tree, does not — so the src/**/*.zod.ts glob is genuinely selective, not "everything under src". Bucket counts from the same tarball: liveness/ 38 entries, src/**/*.zod.ts 206, src/ non-.zod.ts 1 (src/migrations/entries/README.md), scripts/ 0.

And the changed text, not just the path, is in the published bytes:

liveness/field.json contains the new dating clause:  2 occurrences
src/data/field.zod.ts contains SNAKE_CASE TOLERANCE: 1 occurrence

The docblock additionally reaches dist/ui/index.js, dist/ui/index.mjs, dist/browser/** as a preserved comment, so it publishes twice over. Firing control: a describe() string from the same block (Rows per page in the record-picker dialog) resolves to the same dist/ui/index.js.

⇒ Both halves say yes. skip-changeset would have been wrong.


Governance and gates

Governed-surface verdict, run on the exact final file list:

$ node scripts/pm/check-governed-merges.mjs --test \
    packages/spec/liveness/field.json \
    packages/spec/src/data/field.zod.ts \
    .changeset/lookup-picker-reader-prose-remeasured.md
governed-surface predicate: 0 of 3 path(s) hit the register (5 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.
exit=0

Gate families derived from the real change set, not a hand-written list:

$ node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
dispatch-gates: change set derived from git — 3 path(s) vs merge base fe2b7554a
71 command(s) — 41 pnpm, 30 direct node

All 71 were run. 68 green on the first pass; 3 returned exit 3 (PREREQUISITE NOT MET — explicitly NOT MEASURED, neither pass nor failure). Two of those were cleared by a targeted build and are now green:

gate result
@objectstack/lint check:doc-formula-expressions exit 3 → exit 0 after turbo build --filter=@objectstack/formula --filter=@objectstack/lint
check:lean-entry-closure exit 3 → exit 0 after turbo build --filter=@objectstack/objectql (2 published conditions measured from a real load)
check:dual-build-cjs-loads NOT MEASURED — declared narrowing. Its prerequisite is built output for 87 packages ("… 79 more"), i.e. a full-tree pnpm build, which does not fit this session's foreground ceiling. CI's Build Core supplies it. This diff is a JSON data file plus a TSDoc comment and moves no package's exports, entry points or dual-build emission shape.

Named gates of interest, all exit 0: check:liveness, check:docs, check:authorable-surface, check:api-surface, check:objectui-pin-citations, check:nul-bytes, check:published-files, check:closing-keyword-parity, check:empty-changeset, check:changeset-no-major, check:objectui-changeset, check:merge-driver, check:comment-mask-adoption.

check:generated — regenerated nothing, as expected:

$ pnpm --filter @objectstack/spec check:generated        # EXIT=0
✓ All 15 generated artifacts are up to date.
$ git status --porcelain                                  # only the new changeset

Tests and typecheck, at head af158289e. The split projects are addressed by their real spellings; test:repo is implicated because four of its files read the liveness/field surface (scripts/liveness/evidence.test.ts, scripts/liveness/proof-registry.test.ts, scripts/file-description.test.ts, scripts/gen-sdui-manifest-collision.test.ts):

$ pnpm --filter @objectstack/spec test          # --project local
 Test Files  467 passed (467)
      Tests  13100 passed (13100)

$ pnpm --filter @objectstack/spec test:repo     # --project repo
 Test Files  27 passed (27)
      Tests  403 passed (403)

$ pnpm --filter @objectstack/spec typecheck     # tsc --noEmit + scripts + test layer
check:test-typecheck: OK — 54 file(s) / 261 error(s) / 145 pinned signature(s) held

Lint — the full repo scan, not a narrowing. It fit inside the foreground budget, so no scoping argument is needed:

$ npx eslint --no-inline-config --format json .     # EXIT=0, at head af158289e
entries returned: 6391 | actually in eslint population: 6391
TOTAL errors: 0 | warnings: 0

Control characters. pnpm check:nul-bytes exit 0, plus a self-scan of the two changed files with grep -naP over the non-tab control class: 0 hits, with a firing control (a planted U+000B matched at exit 0, so the regex shape fires).


验收备注

Two findings adjacent to this work, reported rather than folded in. Neither is filed as a card by this PR — the scoping call is the dispatching seat's.

  1. packages/spec/liveness/field.json carries two MORE notes of exactly this class, both measured false on objectui ed971e8. They were not in this dispatch's enumerated scope and are not touched here.

    • /props/descriptionField — "…(reads descriptionField || description_field)." Measured: LookupField.tsx:261 reads fieldMeta?.descriptionField only; the snake leg went with the same objectui#7155 sweep. Zero snake reads in the reader radius, firing control as above.
    • /props/inlineColumns/children/idField — "objectui GridField.tsx lookup cells — LookupField id_field." Measured: GridField.tsx passes idField: c.idField at all three call sites and the column type declares idField?: string; LookupField.tsx:262 reads fieldMeta?.idField || 'id'. objectui#7155 removed id_field from LookupFieldMetadata alongside the other three.

    Why it matters more than usual: they now sit two and four entries away from freshly-dated notes in the same file, so the corrected notes lend credibility to the stale ones. Successor: whoever next holds this file's serial slot — this PR is that holder for today, and a follow-up on the same file is the cheapest place to take them.

  2. dependsOn's snake leg is gone, which retires the dispatch's own table row. Not a defect in this repo; recorded because the dispatch asked for the measurement to be re-derived and it moved. The docblock now states it, dated.

Noted, not filed, deliberately: the docblock is prose in the spec about what a consumer reads, which the domain:ui seat flagged as a larger question than this card. Unchanged here; no successor named.


Not done, on purpose

  • No gate was built. Triage's costing was adopted: a gate whose input is a pinned foreign tree goes stale at every pin bump and needs its own anti-vacuity self-test. The dating clause carries the freshness signal instead, in prose.
  • No status: verdict moved. Every entry touched stays live.
  • No verifiedAt / evidenceScope field was added. Those are recognised ledger fields, but adding one would have made this diff more than prose and falsified the Clause-② ruling reproduced above. The dating lives inside the note text instead, which is what the seat's option-1-plus-dating actually asks for.
  • objectui was read only. A detached read-only worktree at origin/main; no edit, no branch, no PR there.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

…ui and date them

packages/spec/liveness/field.json carried two displayField notes crediting
objectui with reading a snake_case spelling that upstream deleted from
LookupFieldMetadata, and field.zod.ts's LOOKUP PICKER docblock told authors that
all seven camelCase keys below it are read with their snake_case aliases too.
Both claims are re-derived against objectui ed971e8 and rewritten to the
measurement: three of the seven keys keep a live snake leg (lookupColumns,
lookupPageSize, allowCreate), four are camelCase-only (displayField,
descriptionField, lookupFilters, dependsOn).

Also corrects the valueDomain evidence string's tense: it described the
write-path membership call in the future while its own first clause already
quotes the landed call site.

Each rewritten claim names the objectui commit it is dated to, so a later reader
can see how old the evidence is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Both changed paths are inside @objectstack/spec's published files[] and both were
measured present in the real tarball (npm pack --dry-run), so the corrected text
ships.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/s label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/concepts/metadata-driven.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/external-datasources.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/field-types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/validation-rules.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/troubleshooting.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/validating-metadata.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/getting-started/quick-reference.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/kernel/contracts/data-engine.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/backward-compatibility.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectql/types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/concept.mdx (via FieldSchema (symbol, a top-level const))

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

  • content/docs/releases/v17.mdx (via FieldSchema (symbol, a top-level const))

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
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/field.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 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; 100 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 — 131 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 8d4690b8f55d21da6b8530081ab76aa10ac524a7packageMentionDocs.

Which tree this was computed on

This run read content/docs from c64a8d8e0f1eb09297393152e3d76dfc93d8ec1e — the merge of head af158289eee46ea7401d69b3fe1872f730b473c0 into base 8d4690b8f55d21da6b8530081ab76aa10ac524a7, 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 c64a8d8e0f1eb09297393152e3d76dfc93d8ec1e && git checkout c64a8d8e0f1eb09297393152e3d76dfc93d8ec1e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d4690b8f55d21da6b8530081ab76aa10ac524a7 af158289eee46ea7401d69b3fe1872f730b473c0 && git checkout -B drift-repro 8d4690b8f55d21da6b8530081ab76aa10ac524a7 && git merge --no-ff af158289eee46ea7401d69b3fe1872f730b473c0

node scripts/docs-audit/affected-docs.mjs --json 8d4690b8f55d21da6b8530081ab76aa10ac524a7

⚠️ 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 8d4690b8f55d21da6b8530081ab76aa10ac524a7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tooling labels Sep 9, 2026
@yinlianghui
yinlianghui marked this pull request as ready for review September 9, 2026 02:09
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit f7a9740 Sep 9, 2026
36 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-15540-liveness-lookup-reader-prose branch September 9, 2026 02:34
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/s tooling

Projects

None yet

2 participants