Skip to content

Portal UX: let a lookup show the target's name when the viewer cannot read the target (decision) #29171

Closed

Description

@objectstack-fleet

director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

Ruled: 6574398310 · letter B · 2026-11-02T10:06Z

Filing gate: ② maintainer decision (a product/security trade-off only the maintainer can make).

维护者速读

  • 一句话:外部门户用户能看到自己的记录,但记录上指向他读不到的对象(品类、物料、内部员工)的关联字段只显示「受限记录」,供应商看不懂这条记录是哪个物料、找谁。
  • 要你定:平台是否提供「引用目标不可读时仍显示其名称」的能力(按字段或按对象声明),还是维持现状,由应用自己加只读文本快照。
  • 不做会怎样:每个有门户的应用都要为每个这样的关联字段加冗余文本字段和写入 flow,并且各自保证不泄露。

Problem

An external portal user reads a record they are allowed to see (an admission application, a corrective action request) and the record carries lookups to objects they must not read in full: srm_category, srm_material (product catalogue with no supplier field, so no membership-key RLS can narrow it), and sys_user (portal users may only read themselves). Since objectui#13335 these lookups render as "Restricted record". That is safe, but the supplier cannot tell which material a corrective action is about or which category they applied for.

Reach: measured on 18.1.0 in the hotsrm v0.6 portal exit test (local objectstack dev, two external portal users): the record page shows the raw id for these lookups on 18.1.0; current objectui shows "Restricted record" (objectui#13335).

Business reason

Who benefits: every app with a supplier/customer portal (hotsrm today). Scenario: the portal user must act on a record whose meaning lives in a lookup name (which material failed inspection, which category is under review). Without it: each app adds a read-only text snapshot per lookup plus a record_change flow to keep it, and each re-derives that it does not leak.

Options

Option What it does Customer-visible effect Effort
A. Keep as is Lookups to unreadable targets stay "Restricted record" Apps add snapshot text fields (hotsrm does this now: objectstack-ai/hotsrm#95) none on platform
B. Declared name exposure A field-level (or object-level) declaration that the target's name field may be shown to anyone who can read the referencing record, resolved elevated for the name only Portal shows "PCB-1001 Main board" instead of "Restricted record"; nothing else of the target is readable spec key + objectql/REST expand + renderer; no precedent
C. Denormalized name on read The engine returns the target's name for every lookup regardless of target access Simplest for apps, but silently leaks names (e.g. internal user names, other suppliers' material names) small, but reverses the #13335 stance

Recommendation: B, if the platform wants to own portal UX; otherwise A. C is not recommended: it leaks silently.

os-decision-facets

  • ① Long-term soundness: B adds one declared key but removes a per-app pattern (snapshot field + sync flow) that every portal app would otherwise repeat.
  • ② Real business pull: one app today (hotsrm v0.6 portal, 3 lookups found; more expected on portal pages for contracts and performance reviews).
  • ③ AI-error resistance: B is an explicit, closed declaration per field; the failure mode of A is a missing snapshot (visible "Restricted record"), the failure mode of C is a silent leak.
  • ④ Startup-stage restraint: A declares nothing on the platform; B is a permanent key.

Prior rulings read: "Restricted record" lookup, "lookup unreadable target" → objectui#13335 (closed, the current stance), objectui#13377 (closed, tooltip id); objectstack 0 direct hits; ADR none found; thread: none.

Who acts

Maintainer decides A/B. If B: a platform seat implements spec + objectql + objectui renderer. If A: nothing on the platform; hotsrm keeps its snapshot fields (objectstack-ai/hotsrm#95).

Dedupe: repo:objectstack-ai/objectstack is:issue lookup name unreadable target (156 hits incl. closed, none on this ask), … lookup display name restricted record permission (35, none), repo:objectstack-ai/objectui is:issue lookup restricted (closest: objectui#13335, #13377, both closed and about hiding, not exposing).

Filed from the hotsrm project (requested by 田騏). Session: session_017ihL2fedQbjJWWSwaVb31Q

Activity

  1. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    domain:specDomain lane — seat card indexed by label:pm:seat
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    priority:p1High: required for production / M2
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: domain:spec · p1 · needs-user-decision · 2026-11-02T09:07Z

    ⛔ Not a claim.

    • Read fresh. The card calls itself a decision and asks the maintainer to choose between A (no platform change) and B (a new declared name-exposure key), so it goes to needs-user-decision. The recommended option B adds a protocol key, which would land first in packages/spec before objectql/REST and the renderer, so it is p1 under the ruling that protocol additions are p1. If the maintainer picks A, nothing lands on the platform.

    Generated by Claude Code

  3. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #454 · 2026-11-02T09:27Z

    Director seat (第 56 场). This card is presented to the maintainer as batch #454 item 2 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. ⛔ Not a claim.


    Generated by Claude Code

  4. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Ruling: batch #454 item 2 · letter B · maintainer 「454 同意」 2026-11-02T10:04Z

    What the ruling binds

    • A new optional key on the referencing lookup field (name chosen by the domain:spec seat; meaning: "a principal who can read this record may see the target record's display name"). Declared per field by the application author; default off, so the objectui#13335 posture (「受限记录」) is unchanged for every field that does not declare it.
    • Placement is the referencing field, not the target object: the application decides whose name its own portal pages reveal. A target-object-level "my name is public to every referrer" switch is refused.
    • What the engine returns for such a field when the caller cannot read the target: the target's nameField value and the id, nothing else. expand on that field returns the same (name + id), never other fields. The same key covers sys_user names; no separate carve-out.
    • The console renders the name as text with no link to the target record.
    • Option A (every portal app keeps its own snapshot field + sync flow) and option C (every lookup returns the target name regardless of read access) are refused.

    Workload of the letter

    M across three lanes: domain:spec defines the key and its semantics (Metadata-protocol: added, Clause-②: yes (widening), director contract review before landing); domain:server implements the objectql/REST read and expand branch; domain:ui the text-only rendering. hotsrm removes its snapshot fields afterwards (that is the cloud/hotsrm lane's card, not this one).

    Execution


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim: PM loop round 1 · 2026-11-02T12:19Z
    Session: session_01ADHkmPi25XXKx92Xmg4viS (seat); dev = card thread session_01ADHkmQAss6gtFU3jRjApfz
    Account: os-steve
    Branch: claude/project-thread-yert63
    Worktree: the card thread's own checkout of objectstack-ai/objectstack
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/data/field.zod.ts (the lookup key block beside referenceVia) and its tests; regenerated references and surfaces that check:generated moves; root .changeset/; not objectql, REST or ui (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (published schema face, a new key) before landing, effort: high
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: packages/spec field.zod.ts lookup | objectui#13335 renders a lookup to an unreadable target as 「受限记录」 and no key lets the app declare the name readable | portal apps (hotsrm) that add a snapshot field and sync flow per lookup
    Thread-read: 6574398310
    Premise: holds (on origin/main 2f7977cd the lookup field carries no name-exposure key; ruling B binds the semantics)
    Serial constraints cleared: none (no in-flight card of this seat holds field.zod.ts; PR #29331 touches its alias tables only, disjoint lines — this PR lands after PR #29331 and merges main first)

    Ruling-ref: director 6574398310, letter B, maintainer 「454 同意」 — a per-field optional key on the referencing lookup, default off; name chosen by the spec seat: referenceNameVisible. Name + id only, expand the same, sys_user covered, text-only render. Target-object switch refused. Server and ui cards filed by this seat once the key lands.

  6. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 2, 2026
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmQAss6gtFU3jRjApfz
    os-dev-report

    {
      "issue": 29171,
      "status": "done",
      "branch": "claude/project-thread-yert63",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29379",
      "session": "session_01ADHkmQAss6gtFU3jRjApfz",
      "premise_still_valid": true,
      "summary": "FieldSchema gains referenceNameVisible (optional boolean) on lookup and user fields, carrying ruling 6574398310 letter B: TRUE declares that a reader of the record may see the target's nameField value and id even when it cannot read the target, nothing else ($expand the same, displayField does not widen it, text with no link). It is refused at parse on every other type (master_detail included, an off-type false included) with one custom issue naming the types. It is declared ahead of its producer (describe opens [PLANNED — not yet served], so liveness counts it planned). The four closed FieldSchema key censuses gained one entry each. Main was merged after #29331 landed (head 32bc8d47); the branch delta is unchanged (13 files, +293/-2 vs origin/main).",
      "tests": "New packages/spec/src/data/field-reference-name-visible.test.ts (19 tests). Head 32bc8d47 (after merging main), each through os-verify-lock (VERDICT command-exit 0): spec test 792/792 files, 23471 passed, 1 todo; spec typecheck exit 0; metadata-protocol test 304 passed + 4 skipped files, 22155 passed; driver-sql builtin-column-collision.test.ts 11/11; metadata-core object-schema-fls-references.test.ts 60/60. Pre-merge head 734d67a6: driver-sql full suite 236 files (3847 passed) + typecheck exit 0; metadata-core 22 files 885 passed + typecheck exit 0; lint 189 files 7830 passed. At 4c09cb03 four census pins were red as designed (driver-sql collision, metadata-core FLS, metadata-protocol field count 82 vs 81, spec form reconciliation); each got one entry. No ablation: new parse code with its own refusal and control pins; no existing tested behaviour changed.",
      "gates": "Head 32bc8d47: dispatch-gates --commands = 106 commands; all 106 exit 0 on the first pass; --ran: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero). check:generated exit 0 (14/14). Offline check-changeset-no-major --event on the PR body: exit 0, LEVEL AXIS accepted (yes (widening) + added, spec minor; driver-sql, metadata-core patch). eslint --no-inline-config on the 7 changed .ts files exit 0 (734d67a6). PR check-runs at 32bc8d47: 65 runs, every completed one success or skipped, including the ten required (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Lint UI skipped, Type Check UI, Test UI, Build & E2E UI, Live E2E UI), plus Check Changeset success, PR-body guards success, Spec property liveness success.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "6 — mcp__github__issue_read x3 (get, get_comments x2), mcp__github__pull_request_read x3 (get #29331 x2, get_check_runs #29379); no write tools",
      "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#29379, run 41453321390) and this os-dev-report comment via post-stamped.mjs; git push is not a REST write",
      "files_changed": [
        "packages/spec/src/data/field.zod.ts",
        "packages/spec/src/data/field-reference-name-visible.test.ts",
        "packages/spec/src/data/field-customization.test.ts",
        "packages/spec/src/system/metadata-form-zod-reconciliation.test.ts",
        "packages/drivers/driver-sql/src/builtin-column-collision.ts",
        "packages/metadata-core/src/object-schema-fls-references.ts",
        "packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts",
        "packages/spec/authorable-surface/data.json",
        "packages/spec/liveness/state-counts/field.md",
        "content/docs/references/data/field.mdx",
        "content/docs/references/data/object.mdx",
        "content/docs/references/system/migration.mdx",
        ".changeset/29171-lookup-reference-name-visible.md"
      ],
      "line_budget": "+293 / -2 vs origin/main (13 files), non-governed, size well under 3000",
      "deviations": [
        "Claim file surface breach, forced-by-gate class (seat pre-accepted on the thread): driver-sql builtin-column-collision.ts, metadata-core object-schema-fls-references.ts, metadata-protocol protocol.meta-types-degenerate-derivation.test.ts, spec metadata-form-zod-reconciliation.test.ts, each one census entry; none in objectql, REST or ui. The seat amends the claim surface.",
        "Changeset also names @objectstack/driver-sql and @objectstack/metadata-core at patch (their published src moved by one table entry each).",
        "The delta said beside referenceFilters: that key is a retired tombstone on main, so the key sits beside referenceVia / referenceDeletedWhen.",
        "No field.form row: an omit ledger entry instead (lookupAutofill precedent); the ui follow-up adds the row with the renderer.",
        "PR body readings are at 734d67a6 (pre-merge); the 32bc8d47 readings are in this report. The seat may append them to the body.",
        "Harness-written attribution footer in the PR body uses the session URL; the commit trailers are model-free."
      ],
      "open_questions": [
        {
          "question": "Which field types accept referenceNameVisible?",
          "options": ["A lookup + user (as built): user is the sys_user reference the ruling says the key covers; master_detail refused", "B lookup only: a user field would have to become lookup('sys_user') to declare it", "C lookup + user + master_detail"],
          "recommendation": "A, seat-accepted on the thread: the ruling's 'covers sys_user names, no separate carve-out' reads onto the dedicated sys_user type, and master_detail stays out because the ruling places the key on the referencing lookup (a later widening stays additive)."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: spec seat 1 · follow-up cards per the ruling, filed once this lands: server (objectql/REST read + $expand name-only elevated branch; drops the PLANNED marker and flips the pin in field-reference-name-visible.test.ts), ui (text-only render with no link + field form row, removing the reconciliation omit entry), hotsrm (remove snapshot fields, objectstack-ai/hotsrm#95) · noted, not filed"
      ]
    }

    Stamped 2026-11-02T14:46Z.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim amendment on #29171 (amends claim 6576958258) · 2026-11-02T14:48Z

    File surface widened, accepted, as declared in os-dev-report 6580079196 (forced-by-gate class: four closed FieldSchema key censuses each needed one entry for the new key): packages/drivers/driver-sql/src/builtin-column-collision.ts, packages/metadata-core/src/object-schema-fls-references.ts, packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts, packages/spec/src/system/metadata-form-zod-reconciliation.test.ts, plus generated references and liveness counts. None is objectql, REST or ui. The changeset names @objectstack/driver-sql and @objectstack/metadata-core at patch. The open question is answered A, as agreed on the thread: the key is accepted on lookup and user (the sys_user reference the ruling covers) and refused on every other type, master_detail included. Lines unchanged: Clause-②: yes (widening) · Metadata-protocol: added, spec minor.

  9. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Landed · 2026-11-02T15:46Z

    Probe Reading
    PR #29379 MERGED 2026-11-02T15:23Z, merge commit 9467a1b40e, through the merge queue
    #29171 closed completed by Fixes #29171

    pm:dispatched is removed. The seat ACCEPTed it, with a claim amendment for four forced census entries, and a CONTRACT_REVIEW_TIER PASS on 32bc8d47. referenceNameVisible is declared on lookup and user fields, default off and refused on every other type, and marked [PLANNED — not yet served].

    The follow-ups ruling 6574398310 assigns:

    • Server: the objectql/REST read and $expand branch.
    • ui: text-only render and the Studio form row.

    The spec seat files both as cards citing the ruling; they cross-reference this card. The hotsrm snapshot-field removal belongs to the cloud/hotsrm lane, per the ruling, and is not filed here.

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions