Skip to content

server: serve the target's name and id for a referenceNameVisible lookup when the caller cannot read the target (#29171 ruling B) #29460

Closed

Description

@objectstack-fleet

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Filing gate: ③ follow-up the ruling assigns. Director ruling 6574398310 (batch #454 item 2, letter B, maintainer 「454 同意」) says "server and ui cards are filed by the spec seat once the key lands, citing this ruling". The key landed in PR #29379 (9467a1b40e, 2026-11-02 15:23Z), which closes #29171. ⛔ Not a claim. No grade: triage sets it.
Reader: domain:server, the objectql / REST read and $expand path.

What is declared and not served

FieldSchema.referenceNameVisible (packages/spec/src/data/field.zod.ts ≈L1731) is an optional boolean on lookup and user fields only, default off, refused on every other type. Its describe opens [PLANNED — not yet served]. Today a caller who can read the referencing record but not the target gets the restricted-record posture: no name and no id (objectui#13335's 「受限记录」).

What the ruling binds (verbatim scope)

  • When a field declares referenceNameVisible: true and the caller can read the referencing record but cannot read the target, the engine returns the target's name and id, nothing else.
  • $expand on that field returns the same: name + id, never other fields.
  • displayField does not widen it.
  • The same key covers sys_user names, through user fields and through lookups to sys_user. There is no separate carve-out.
  • A caller who can read the target gets exactly what it gets today.
  • Placement is the referencing field. No target-object switch.

Readings the contract review asked the implementer to take from ADR-0079 D4

  • Resolve the display name through nameField ?? displayNameField ?? derivation, with the floor Record #id, not a literal nameField read.
  • A multiple: true lookup returns name + id per element.

Done when

  • The read and $expand branches serve name + id under the conditions above. A test pins each condition with a control: the field without the key stays restricted, a readable target is unchanged, and an $expand of another field returns nothing extra.
  • The planned marker on the key flips to served. Add or flip the liveness row in packages/spec/liveness/field.json citing the server test. That one spec edit is owed to the spec lane, or rides with a seat-agreed surface amendment.
  • Hand the ui card (filed beside this one) the response shape it renders.

Ruling-ref: 6574398310 · parent #29171 · key PR #29379.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:server · pm:queue · 2026-11-02T16:03Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Claim: PM loop round 1 · card thread · 2026-11-02T17:21Z
    Session: session_01ADHkmQJdgarUjPLNgFBY67 (the seat); the card thread is cse_01ADHkmRXbaXBLCJeJXfoh21
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-jdgmh0
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#3
    Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
    File surface: packages/objectql/src/engine.ts ($expand sub-read and the plain read of a reference) and the files it cites for the display-name resolver; REST read tests in packages/rest/src/; packages/spec/src/data/field.zod.ts (the referenceNameVisible describe marker only) and packages/spec/liveness/field.json (its row), a seat-agreed surface amendment; generated reference docs that describe derives; a changeset in .changeset/
    Container & model: card thread cmsg_01FdL1XeUyvVNxLreh3F4Qi95v1bE93v4RhtAn6F7LSiDY, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: xhigh
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: serve the declared key under ruling 6574398310 (name and id only, for a target the caller cannot read); controls and ablations as the delta lists; full dogfood battery on the final head
    Thread-read: 6581850357
    Premise: holds (site re-read on origin/main a9227793 by the seat before dispatch)
    Serial constraints cleared: none on seat 3 ($expand path free); protocol.ts frozen by seat 5 and not on this surface
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29460-delta.md.

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmRXbaXBLCJeJXfoh21
    os-dev-report

    {
      "issue": 29460,
      "status": "done",
      "branch": "claude/project-thread-jdgmh0",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29576",
      "session": "session_01ADHkmRXbaXBLCJeJXfoh21",
      "premise_still_valid": true,
      "summary": "A `lookup` or `user` field declaring `referenceNameVisible: true` now answers, on `$expand`, `{ id, name, readRefused: true }` for a target the reader is refused (the object's read gate, its required permissions, or its row scope), per element on `multiple: true`; everything else keeps today's answer. The name is read by the engine from the refused ids and the ADR-0079 name column only, under the tenant-kept elevation; the caller's nesting (projection, nested expand, filter, order) and the field's displayField never reach that read. A field-level rule on the name column (FLS, maskingRule, a formula reading through a reference, a non-title column) serves the floor `Record #<id>` (open question 1). The describe drops its planned marker and the liveness row records the key live, citing the REST test.",
      "readings_baseline": "main a9227793, real ObjectQL over SQLite + security plugin + REST server, a member caller: plain engine read, REST GET by id and REST list (no $expand) hold the bare FK id for every target (readable, row-scope-hidden, gate-refused, dangling). $expand: readable target = full record; row-scope-hidden, gate-refused or dangling = bare id (the gate-refused sub-read logs a warn). sys_user: a user the row scope admits is expanded, one it does not keeps the bare id. The card's 'no name and no id' is the ui's placeholder (its own follow-up read of the target is refused). No sys_user name carve-out exists in the engine or the plugins.",
      "ui_handoff": "#29462 — after $expand of a referenceNameVisible field each value is: the expanded record (reader may read the target, exactly as without the key) | { id, name, readRefused: true } (reader refused the target; name may be the floor 'Record #<id>') | the bare id (no record has the id, a record of another organization, no key on the field, or no $expand). Per element on multiple: true. The table is in the PR body.",
      "report_face": "record-recognisers.mjs --template --paths <the 12 paths>: the schema face, CONTRACT_REVIEW_TIER, Scope hits packages/spec/src/data/field.zod.ts",
      "tests": "packages/rest/src/data-reference-name-visible.test.ts (new): 23/23 on this head (`cd packages/rest && pnpm exec vitest run --maxWorkers=2 src/data-reference-name-visible.test.ts`, under os-verify-lock, VERDICT command-exit 0). Red leg on the pre-change objectql build: 13 failed | 8 passed | 1 skipped (22, the suite as it then stood; every served case red, every control green). Spec: field-reference-name-visible + field + liveness suites 3 files 181 passed. Package suites on the final head: objectql 534/534 files, 10391 tests passed; rest 374/374 files, 7080 passed, 332 skipped; spec 792/792 files, 23522 passed, 1 todo. Ablations (scripts/ablation-replace.mjs --hold, objectql rebuilt, ablation-dist-preflight present, suite run, --restore proven blob == HEAD; final rebuild, every marker --absent, git status --porcelain empty, suite 23/23): A1 caller nesting + displayField forwarded into the name read and its row spread into the value → 15 failed (nested projection, nested expand, displayField pins among them); A2 key gate removed → 2 failed (without-key controls, user and lookup); A3 FLS answer ignored → 1 failed (FLS floor, received 'Payroll' for 'Record #l1'); A4 maskingRule ignored → 1 failed (masking floor); A5 formula hop computed under elevation → 2 failed (formula floor, nested-expand pin); A6 name read loses the caller's organization → 1 failed (other-organization pin, 'v_other' named); A7 names served on the unexpanded read → 1 failed (unexpanded-read pin); A8 a name invented for an id the name read did not return → 4 failed (dangling, per-element, list, other-organization); A9 the probe's readable answer dropped → 1 failed (readable-target-own-filter control). Three plants (A1, A7, A8) were first refused by ablation-replace because the replacement carried the anchor verbatim (anchor count did not fall); rewritten so the anchor changes and rerun.",
      "gates": "after the last commit (7e8a7b30, the main merge): `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → 109 commands; each run with < /dev/null, exit captured before any pipe: 109/109 exit 0 (check-plugin-teardown-shape pin fetched first). `--ran` reconcile (separately): 109 derived, 109 run, 0 NOT-MEASURED (a DERIVED zero), 0 UNRUN. `pnpm lint` exit 0. typecheck objectql / rest / spec exit 0 (each includes check:test-typecheck). Build: turbo build ./packages/** 78/78. CI check-runs: not yet read, the draft was just opened.",
      "dogfood_battery": "os-verify-lock VERDICT command-exit 0 (held 29m15s, shared box) · Test Files 397 passed, 3 skipped (400) · Tests 3277 passed, 28 skipped (3305) · no failures",
      "line_budget": "12 files, +872 / -35: objectql src +315 / -3 (new module 246, engine.ts 64, hydration 5/3), rest test +489, spec src +39 / -26 (field.zod.ts 27/15, forced test edit 12/11), liveness +6 / -1, generated docs +5 / -5, changeset +18",
      "files_changed": [
        ".changeset/29460-reference-name-visible-served.md",
        "content/docs/references/data/field.mdx (generated)",
        "content/docs/references/data/object.mdx (generated)",
        "content/docs/references/system/migration.mdx (generated)",
        "packages/objectql/src/engine-file-reference-hydration.ts",
        "packages/objectql/src/engine-reference-name-visible.ts (new)",
        "packages/objectql/src/engine.ts",
        "packages/rest/src/data-reference-name-visible.test.ts (new)",
        "packages/spec/liveness/field.json",
        "packages/spec/liveness/state-counts/field.md (generated)",
        "packages/spec/src/data/field-reference-name-visible.test.ts (forced test edit)",
        "packages/spec/src/data/field.zod.ts"
      ],
      "deviations": [
        "Served on $expand only, not on the plain read: ADR-0104 D1 has every unexpanded reference answer its stored form (the bare id, already the 'id' half); serving a name there would hand a refused reader a richer value than a reader of the target gets. Open question 2.",
        "A name column kept from the reader by a field-level rule serves the floor 'Record #<id>' rather than the name (the delta listed FLS among the refusals the key serves through; the describe as declared did not mention field-level rules, so serving would bypass the grant). Decided toward no bypass, declared, and put in open question 1 with options; not silent.",
        "The served value carries `readRefused: true` beside `{ id, name }` (the FileRefusedValueSchema `metadataRefused` precedent), so a renderer can tell a refused target from a readable one expanded with a narrow projection. Public name chosen by the thread (names are the seat's call).",
        "File surface beyond the Claim's list: the body lives in a new module `packages/objectql/src/engine-reference-name-visible.ts` (the engine split host pattern; engine.ts carries the call site and a thin private method), `isReadRefusal` exported from `engine-file-reference-hydration.ts` (one refusal predicate for both sub-reads), the forced spec test edit, and the regenerated `object.mdx`, `migration.mdx` and liveness `state-counts/field.md`.",
        "PR opened through the relay op pr_create with no assignees and no reviewer requests (standard order; MCP writes forbidden).",
        "The main-merge commit's message was amended before the first push to carry the model-free trailer pair."
      ],
      "mcp_calls": "4 — mcp__github__issue_read (get #29460, get_comments #29460 x2, get #29462); no write tools",
      "api_writes": "2 — fleet relay pr_create objectstack-ai/objectstack#29576 (run 41511593123, 12176 bytes sent = stored); fleet relay comment on #29460 via post-stamped.mjs (this report)",
      "open_questions": [
        {
          "question": "The target's name column is itself kept from the reader by a field-level rule (FLS read false, a maskingRule, or a formula reading through another reference). Serve the name, or the floor?",
          "options": [
            "A (this PR): the floor 'Record #<id>' with the id; the describe says field-level rules on the name column still hold",
            "B: serve the name regardless; amend the describe to say the key lifts field-level rules on the name column too (drop three checks in servableNameField; ablations A3 to A5 show the effect)"
          ],
          "recommendation": "A, because it serves the ruling's 'name and id' without widening any field grant, B stays a contained follow-up if ruled, and a reader who can read the row but not its name column is served exactly what it is served without the key ('can read the target => as today')."
        },
        {
          "question": "Plain (unexpanded) reads: serve the name there too?",
          "options": [
            "A (this PR): $expand only; an unexpanded read answers the stored bare id for every reader (ADR-0104 D1)",
            "B: serve { id, name, readRefused } on the plain read for refused targets only (an ADR-0104 D1 amendment; a refused reader would get a richer value than a reader of the target)"
          ],
          "recommendation": "A, because the bare id already is the 'id' half, and the ui expands the field when it wants the name."
        },
        {
          "question": "A named Zod schema for the served value beside FileRefusedValueSchema in packages/spec/src/data/field-value.zod.ts?",
          "options": [
            "A: a spec-lane follow-up card (the lane is winding down; this PR exports the TypeScript shape ReferenceNameOnlyValue from the engine module only)",
            "B: leave it unnamed in spec; the describe states the shape"
          ],
          "recommendation": "A when the spec lane next opens, because the ui card can then import the shape instead of restating it."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [],
      "head": "7e8a7b30a46677129ad483194fb982a74f36e85b"
    }

    Stamped 2026-11-02T19:38Z.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

    Landing record · #29460 · PR #29576

    domain:server seat 3 · session_01ADHkmQJdgarUjPLNgFBY67 · 2026-11-02T21:04Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29576-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor b8615258 origin/main yes
    Content git grep -n "export async function serveReferenceNames" origin/main -- packages/objectql/src/engine-reference-name-visible.ts line 111
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain 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