Repository navigation
feat(spec): a lookup can declare its target's name visible to every reader of the record (referenceNameVisible) - #29379
Conversation
Declared name exposure on the referencing field (director ruling 6574398310, letter B): TRUE declares that a reader of the record may see the referenced record's nameField value and id even when it cannot read the target, nothing else. Refused at parse off lookup/user. Declared ahead of its producer (ADR-0049 [PLANNED — not yet served] marker). Claude-Session: https://claude.ai/code/session_01ADHkmQAss6gtFU3jRjApfz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQAss6gtFU3jRjApfz Co-authored-by: Claude <noreply@anthropic.com>
The key-census tables that close over FieldSchema each gain the new key: driver-sql storage classification (presentation), metadata-core FLS positions (keep), the metadata-protocol field property count (81 to 82), and the field form reconciliation ledger (omitted while planned). Claude-Session: https://claude.ai/code/session_01ADHkmQAss6gtFU3jRjApfz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQAss6gtFU3jRjApfz Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 170 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 30253f2d143c4c0dcdf12d2e00f650ab25b8bc40 && git checkout 30253f2d143c4c0dcdf12d2e00f650ab25b8bc40
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dfbe34d8874326be643042c36211ba6ec5e69c7f 32bc8d47a30a1a79950284a23555efdcc65ab590 && git checkout -B drift-repro dfbe34d8874326be643042c36211ba6ec5e69c7f && git merge --no-ff 32bc8d47a30a1a79950284a23555efdcc65ab590
node scripts/docs-audit/affected-docs.mjs --json dfbe34d8874326be643042c36211ba6ec5e69c7f
|
Claude-Session: https://claude.ai/code/session_01ADHkmQAss6gtFU3jRjApfz Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等契约复审。 席位核对 — ACCEPT ·
|
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head 32bc8d4 · 2026-11-02T14:49Z |
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract reviewServed-tier: Inputs read: card #29171 body and every comment, the director ruling 6574398310 letter B 2026-11-02T10:05:41Z (binding), seat claim 6576958258 naming ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentAgainst ruling 6574398310 letter B, clause by clause, on the describe and docblock at field.zod.ts lines 1705–1741: (a) name + id only — stated, with "Nothing else of the target"; (b) Can the server and ui follow-ups implement from this contract alone? Yes, with two readings they must take from the cited ADR rather than the describe: (1) the describe says "the target object's Must-fix: none. Implemented-by: VERDICT: PASS |
Requested by Steve · project thread
Fixes #29171
Clause-②: yes (widening)
Metadata-protocol: added
维护者速读
lookup(及user)字段新增可选布尔键referenceNameVisible。设为 true 表示:能读这条记录的人,即使读不到被引用的那条记录,也可以看到它的名称(目标对象nameField的值)和 id,别的都看不到;展开同样只给名称 + id,界面只显示文字、不带链接。master_detail)写这个键会在解析时被拒。本 PR 只是契约,服务端还不返回名称(describe 带[PLANNED — not yet served])。回滚即 revert。packages/spec/src/data/field.zod.ts(referenceNameVisible声明块与 superRefine 拒收分支)、packages/spec/src/data/field-reference-name-visible.test.ts、.changeset/29171-lookup-reference-name-visible.md。pnpm --filter @objectstack/spec exec vitest run src/data/field-reference-name-visible.test.ts,预期 1 个文件全绿(19 条)。What changes
FieldSchema(packages/spec/src/data/field.zod.ts) gains one optional boolean,referenceNameVisible, besidereferenceVia/referenceDeletedWhen. It carries the ruling's semantics (director ruling 6574398310, letter B):truedeclares that a principal who can read the record carrying the field may see the referenced record's display name (the target object'snameField, ADR-0079) and its id, even when it cannot read the referenced record. Nothing else of the target:$expandon the field returns the same name and id only, the field's owndisplayFielddoes not widen what is shown, and the name renders as text with no link.falsekeeps the restricted-reference posture (objectui#13335). No default materializes at parse.lookupand onuser. Auserfield is thesys_userreference, which the ruling says the same key covers. Refused at parse on every other type, with onecustomissue atreferenceNameVisiblenaming the legal types and the offending one, in themailAssociationrefusal shape. That includesmaster_detail(the ruling places the key on the referencing lookup) and an authoredfalseoff-type.[PLANNED — not yet served], which the liveness walker reads asplanned(liveness/state-counts/field.md: planned 5 → 6, classified 110 → 111). The docblock states the read and expand bounds as MUST statements for the server card, and says that no runtime serves the name yet.Generated files that ride along (
check:generated --fix, which named 2 of 14 artifacts stale):content/docs/references/data/field.mdx,data/object.mdx,system/migration.mdx,packages/spec/authorable-surface/data.json(+1 key),packages/spec/liveness/state-counts/field.md.Changeset:
.changeset/29171-lookup-reference-name-visible.md. It bumps specminor, plusdriver-sqlandmetadata-corepatchfor the census entries below.Forced edits (closed key censuses over
FieldSchema)A new
FieldSchemakey reds each of these by design. Each was measured red, then given one entry:packages/drivers/driver-sql/src/builtin-column-collision.tsFIELD_KEY_STORAGE_CLASS:presentation. The read and expand path reads the key, nevercreateColumn. Red wasbuiltin-column-collision.test.ts"classifies EVERY FieldSchema key".packages/metadata-core/src/object-schema-fls-references.tsFIELD_REFERENCE_POSITIONS:keep. It is a boolean facet that names no field. Red wasobject-schema-fls-references.test.ts"classifies every FieldSchema key".packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts: thefieldproperty count goes 81 → 82, with a dated comment line.packages/spec/src/system/metadata-form-zod-reconciliation.test.ts: anomitledger entry, on thelookupAutofill/conditionalFormattingprecedent. The field form does not offer a key nothing serves. The ui card adds the form row together with the renderer.packages/spec/src/data/field-customization.test.tsSTRUCTURAL_FIELD_KEYSgains the key (a permission-class key). This pins that it can never join the ADR-0146 D10 display whitelist, so a customer extension cannot patch a vendor's lookup into exposing names. It was not red; the edit is added coverage.Follow-ups (filed by the spec seat once this lands, citing ruling 6574398310)
domain:server): the objectql/REST read and$expandbranch. For a field declaringreferenceNameVisible: truewhose target the caller cannot read, it does an elevated name-only read: the target'snameFieldvalue and id, nothing else,displayFieldignored. It drops the[PLANNED — not yet served]marker and flips the pin infield-reference-name-visible.test.ts.domain:ui): the text-only rendering with no link, and the field form row (which removes the reconciliationomitentry).Tests
packages/spec/src/data/field-reference-name-visible.test.ts(new, 19 tests):sys_user, auserfield, and throughObjectSchema;falseis kept; absent stays absent; a non-boolean value givesinvalid_type;master_detail,tree,text,select,formulaandnumber. Each case checks onecustomissue at the key and the named subjects, plus a positive control without the key. An off-typefalseis refused too, and the refusal message carries no tracker number;Consumer suites (downstream of spec), at
734d67a6, each throughos-verify-lock.sh(VERDICT command-exit 0):@objectstack/spectest: Test Files 792 passed (792), Tests 23411 passed, 1 todo. typecheck: exit 0.@objectstack/driver-sqltest: 236 passed, 12 skipped (248); 3847 passed, 214 skipped. typecheck: exit 0.@objectstack/metadata-coretest: 22 files, 885 tests passed. typecheck: exit 0.@objectstack/metadata-protocoltest: 301 passed, 4 skipped (305); 22127 passed, 26 skipped.@objectstack/linttest: 189 files, 7830 tests passed.Before the census edits, at
4c09cb03, four pins were red as designed. Each namedreferenceNameVisibleas unclassified: driver-sqlbuiltin-column-collision.test.ts, metadata-coreobject-schema-fls-references.test.ts, metadata-protocolprotocol.meta-types-degenerate-derivation.test.ts(82 against 81), and specmetadata-form-zod-reconciliation.test.ts.No ablation: the parse refusal is new code with its own refusal and control pins, and no existing tested behaviour changed.
Acceptance notes
useris accepted besidelookup. This reads the ruling's "the same key coverssys_usernames; no separate carve-out" as covering the dedicatedsys_userfield type, which is stored the same way as a lookup. If the seat reads it as lookup only, the change is a one-line narrowing before landing (open question in the report).master_detailis refused. The ruling names the referencing lookup. Narrowing first keeps a later widening additive.referenceFilters": that key is a retired tombstone on main (16.x line), so the new key sits besidereferenceVia/referenceDeletedWhen.Verification
Derived gates at 734d67a
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 106 commands (13 changed paths against merge base2f7977cd2), each run with stdin closed. 104 exited 0 on the first pass.check:skill-examplesandcheck:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: no package dists). Afterturbo run build --filter='./packages/**'(78/78, under the lock), both exited 0.--ranreconciliation: "106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN" (a DERIVED zero).Also run:
pnpm --filter @objectstack/spec check:generatednamed 2 of 14 stale (check:docs,check:liveness), fixed with--fix. Offlinecheck-changeset-no-major --eventon this body: LEVEL AXIS accepted (yes (widening)+added, specminor; driver-sql and metadata-corepatch). eslint--no-inline-configon the 7 changed.tsfiles: exit 0.Dogfood: n/a. The title has no
!, and Clause-② carries no(narrowing)arm.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code