Skip to content

spec(page): record:related_list declares its parent record with a binding key of its own, beside the child-row dataSource — the spec half of objectstack-ai/objectui#12294's related-list leg (ruled B on objectstack-ai/objectui#12296) #23134

Closed

Description

@objectstack-fleet

Filing gate ③ maintainer-directed: the execution card for the director seat's ruling on objectstack-ai/objectui#12296 (batch #335, letter B, the maintainer's word 「同意」, ruling comment 6137226306). Filed by the director seat (seat post #12708, summon #40, session_01TJKP6W6UDYEBHtccVdpVoz). It lives here because the fix lands in @objectstack/spec (cross-repo rule 1). ⛔ Not a claim.

Reader who acts: the domain:spec seat. The objectui half (the related-list leg of objectstack-ai/objectui#12294) waits on the spec release that carries this key, then lands on that parent card.

The ruling, as recorded

B — record:related_list gains a parent-record binding key of its own. @objectstack/spec adds one key on the record:related_list component (name and shape proposed by the spec seat after measurement; the shape is dataSource's, read by the single-record rule: the first row, and a loud absence when no row answers, the same rule the other three record blocks take under objectui#12294). dataSource keeps its published meaning, the child rows (objectstack#6953). Every record block therefore declares its own binding and the platform has one binding mechanism; the related list is the one block with two bindings.

Governing text: objectstack-ai/cloud#2839 Q1 D (per-block bindings, as objectstack-ai/objectui#12294 quotes it); #4444 (no self-fetching record scope on react pages; its flip conditions are not declared met); the ELEMENT_DATA_SOURCE_INPUT contract in @object-ui/core ("per-element data binding"). ⛔ No new validate finding for a parent binding that differs from a sibling details block (a new gate defaults to no; none was named).

What to build (one PR, domain:spec)

  1. The key. On the record:related_list component props (packages/spec/src/ui/page.zod.ts, the ComponentPropsMap door that already declares this block's filter), one key that names the parent record: the shape of PageComponentSchema.dataSource (object, view, filter, sort, limit), read by the single-record rule (the first row; no row ⇒ the block renders a loud absence, never an empty list). The spec seat proposes the name after measuring what the three sibling record blocks read under record blocks honour the per-element dataSource the spec already declares — record:details / record:highlights / record:quick_actions / record:related_list on an app page bind their record from the component's dataSource, so a product page composes without the record shell (maintainer ruling cloud#2839 Q1 D, 2026-10-13) objectui#12294 (dataSource on those blocks), so the two spellings read as one family. The description states: dataSource on this block is the child rows (objectstack#6953); this key is the parent record; a record page's record context remains the fallback when the key is absent.
  2. Clause-②: yes (widening). A minor changeset on @objectstack/spec stating the new key, its single-record rule and the fallback; the contract review at CONTRACT_REVIEW_TIER before the queue.
  3. Pins: the key parses with dataSource's shape; it is refused on every other component (the ComponentPropsMap door's own refusal shape); dataSource on this block is unchanged (CONTROL, the PageComponentSchema.dataSource 在 list-view 之外仍无人消费:element:record_picker 丢 view,object-grid/form/kanban/calendar 等整条绑定不读(按 spec 写 dataSource 渲染出空) #6953 pins); the generated reference page and the skill references follow (check:generated).
  4. Not this PR: objectui's reader (the related-list leg of record blocks honour the per-element dataSource the spec already declares — record:details / record:highlights / record:quick_actions / record:related_list on an app page bind their record from the component's dataSource, so a product page composes without the record shell (maintainer ruling cloud#2839 Q1 D, 2026-10-13) objectui#12294, after the release); any record-scope component (决策记录:react 页的 <RecordScope> 不做——翻转条件与最低门槛见正文(#4413 后续) #4444 stands); any validate consistency finding.

Acceptance

Fork clause

dataSource on record:related_list is read by objectui as the child binding and by no reader as the parent. If the spec seat finds a reader that already treats it as the parent, or a shape the single-record rule cannot carry, it reports the fork on this card and stops; ⛔ no silent re-reading of dataSource (option C).

Related: objectstack-ai/objectui#12296 (the decision card) · objectstack-ai/objectui#12294 (the parent) · #6953 · #4444 · #5576 · objectstack-ai/cloud#2839.

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 13, 2026
  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23134, ruling B 6137226306 on objectstack-ai/objectui#12296: record:related_list gains a parent-record binding key of its own, beside its child-row dataSource) · 2026-10-13T05:21Z
    Session: session_01BRQ9Mp1yH7R1az6MaUM18H
    Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23134-related-list-parent-binding
    Worktree: objectstack-issue-23134
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    Ruling-ref: 6137226306 on objectstack-ai/objectui#12296 (batch #335 item 1, letter B), read in this act.
    File surface (at origin/main f931e765b or later; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns os-justin.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23134,
      "status": "done",
      "branch": "claude/issue-23134-related-list-parent-binding",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23205",
      "session": "session_01BRQ9Mp1yH7R1az6MaUM18H — subagent run; the parent PM seat's harness-stamped id (the relay envelope read the same id from the container)",
      "premise_still_valid": true,
      "summary": "ComponentPropsMap['record:related_list'] (RecordRelatedListProps, packages/spec/src/ui/component.zod.ts; not page.zod.ts as the card says, as the claim already noted) gains parentDataSource: ElementDataSourceSchema.optional(), the element object binding {object, view?, filter?, sort?, limit?} referenced by identity from page.zod.ts (pinned: same _zod.def as the object arm of PageComponentSchema.dataSource). The describe states the three facts the card names: this key is the parent record, read by the single-record rule (first row by sort; no row = loud absence, never an empty list); the record context remains the parent when the key is absent; the node-level dataSource keeps its child-row meaning. It opens with [PLANNED — not yet rendered]. page.zod.ts and the node dataSource are untouched (not in the diff). Name measured at objectui main f0532d8a: the three sibling blocks read node-level dataSource (recordBinding.tsx:124/147), and the one other parent-half block, record:line_items, spells it parentObject/parentId, so the name is parentDataSource; six rejected alternatives are in the PR body. Liveness: no ledger row is possible for a component-level key (page/regions is a recorded undrilled container), so the record is a PLANNED RECORD sentence in liveness/page.json _note, the describe marker, and the authorable-surface row, following the ElementApiDataSourceSchema precedent. Fork reading: no objectui renderer reads related_list dataSource as the parent. One objectstack reader, the os validate page-field-unknown rule, resolves the parent-side relationshipValueField against dataSource.object (a defect against its own stated reading). I judged that not a fork, did not stop, and named it in the PR body and below for the PM to confirm. Generated: authorable-surface/ui.json +1 row, content/docs/references/ui/component.mdx +11. Changeset: @objectstack/spec minor, Clause-② yes (widening). Merged origin/main d4aaeaf0e once through scripts/pm/os-regen-merge.sh; nothing to regenerate. No labels written: none named, and spec publishes, so skip-changeset does not apply.",
      "tests": "Final head d4b3e7264, all heavy runs through os-verify-lock (slot dev-23134): (1) pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0, 'check:test-typecheck: OK ... 52 file(s) / 246 error(s) / 135 pinned signature(s)' (unchanged debt); pnpm --filter @objectstack/spec test -> 'Test Files 662 passed (662) / Tests 20125 passed | 1 todo (20126)'. (2) Pre-merge 0e5ae9203: spec test 662 files / 20111 passed; spec test:repo 'Test Files 57 passed (57) / Tests 997 passed (997)' (held the lock 22m27s); not re-run post-merge (declared narrowing: merge brought disjoint spec changes, CI runs it). (3) New file component-record-related-list-parent-23134.test.ts: 17 cases (A accept + def identity, B refusals by code/path, C every other ComponentPropsMap row refuses by name, the 2 retired rows refuse every bag, derived; D CONTROL child dataSource unchanged; E planned marker). (4) ABLATION via scripts/ablation-replace.mjs WRAP mode + outer trap: anchor 'parentDataSource: ElementDataSourceSchema.optional().describe(' x1->x0, blob 5fc72026->c9701e67 -> 'Tests 12 failed | 5 passed (17)' (the 5 green are the controls); restore: blob == HEAD 5fc72026, git diff HEAD empty. Test imports src, so no dist leg. (5) Type-level reverse check on the REBUILT dist/ui/index.d.ts (scratch tsc project outside the repo): green leg EXIT 0 (key accepted, two @ts-expect-error lines consumed: object:1 and the api arm); mutated leg EXIT 2, 2 errors (TS2561 parentDataSourceX, TS2353 provider). (6) os validate door probe on the lint source with the rebuilt spec: record:details + parentDataSource -> component-props-unknown-key (warning); valid related_list binding -> no finding. (7) check:generated -> 'All 14 generated artifacts are up to date' on the merged tree (first run named only check:docs stale -> gen:docs). (8) Gates: dispatch-gates --commands --repo objectstack-ai/objectstack on d4b3e7264 = 110 commands, exit codes captured before any pipe: 109 exit 0 (6 first exited 3 PREREQUISITE NOT MET, re-run 0 after turbo build of lint/formula/client-react/objectql: 35/35 cached), 1 exit 1 = pnpm check:platform-checklist, main-red (8 ABSENT SYMBOL problems in metadata-protocol/service-storage; inputs byte-identical to origin/main d4aaeaf0e: git diff origin/main HEAD -- docs/qa packages/metadata-protocol packages/services scripts is empty). --ran reconcile: 'Run reconciliation — 110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN', 'all 110 recorded an exit code'. (9) ESLint narrowed at d4b3e7264: population from eslint.config.mjs globs (**/*.{ts,...}; no md/mdx/json glob), so the 2 changed .ts files; --format json: 2 files, 0 errors, 0 warnings; invariance: no parserOptions.project (no type-aware linting), so the diff cannot move an untouched file's verdict. Whole pnpm lint: CI's.",
      "mcp_calls": "0 — none",
      "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, each executed by fleet-write as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft forced) + POST /repos/objectstack-ai/objectstack/issues/23205/assignees [os-justin], read back 13994 bytes identical; (2) this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/23134/comments. git push (several pushes of the branch) is not a REST write. Zero label writes.",
      "open_questions": [
        {
          "question": "Fork clause: os validate's page-field-unknown rule (packages/lint/src/validate-page-field-bindings.ts:617 via page-walk.ts:136-137) checks a related list's parent-side relationshipValueField against dataSource.object when the child is bound through dataSource. Is that 'a reader that already treats dataSource as the parent'?",
          "options": [
            "A — not a fork: the rule's own comment names the page object as the parent, so this is a defect against its stated reading and against the published child meaning. File it as a lint defect; this PR stands.",
            "B — a fork: stop the card and re-present the ruling."
          ],
          "recommendation": "A. No renderer and no producer gives dataSource the parent meaning. The lint resolves through a generic walk (dataSource.object -> properties.object -> page object) that its related-list branch never corrected. Its fix gets a declared parent object to resolve against (parentDataSource.object, else the page object), which is a resolution fix, not a new finding."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: os validate (public door) on the published related_list child-binding shape: dataSource {object: contact} with relationshipValueField external_key (a field of page object account) yields a FALSE page-field-unknown 'not a field on object contact', and the child-side columns/sort/filter/relationshipField go unchecked (the rule reads only properties.objectName). Control: the same list with properties.objectName gives the correct single columns[0] finding · evidence: scratch probe against packages/lint/src at d4b3e7264 · Seam: spec:PageComponentSchema.dataSource (record:related_list child binding) → runtime:packages/lint/src/validate-page-field-bindings.ts:617 checkRefs(split.parent, objectName), objectName from packages/lint/src/page-walk.ts:136-137 · dedupe words: page-field-unknown related_list dataSource relationshipValueField; relatedListFieldRefs parent object; walkPageComponents dataSource object related list",
        "class: a · reach: repo gate pnpm check:platform-checklist exits 1 on origin/main d4aaeaf0e (this branch's inputs to it are byte-identical to main): 8 problems — ABSENT SYMBOL anonymousFormIntakeOrgScopeRefusal / anonymousFormIntakeReopenRefusal / envWideRawViewRows / countable in packages/metadata-protocol/src/protocol.ts, canEdit in packages/services/service-storage/src/attachment-access-hooks.ts, and attachments-storage 'SYMBOL ANCHORS LOST 27 vs floor 28' · evidence: gate log at d4b3e7264; git grep anonymousFormIntakeOrgScopeRefusal origin/main -- packages/metadata-protocol/src/protocol.ts exits 1 · dedupe words: platform-checklist ABSENT SYMBOL; access-security.json anonymousFormIntake; attachments-storage symbol anchors floor",
        "carrier: objectstack-ai/objectui#12294 related-list leg · the page:tabs count probe reads the parent from ctx.data.id (objectui components/src/renderers/layout/containers.tsx:614-626 at f0532d8a), so a list bound by parentDataSource on an app page gets no auto-derived tab count unless the leg feeds it · noted, not filed",
        "carrier: 承接者:无 · RecordRelatedListProps still requires objectName even when the child dataSource supplies it (pre-existing, described in objectui's own renderer docblock) · noted in Acceptance notes, not filed"
      ],
      "deviations": [
        "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named line: AGENTS.md takes precedence and pre-push refuses a model identifier. The PR footer uses AGENTS.md's session-URL form for the same reason.",
        "The pre-merge whole-package spec test was invoked as 'pnpm --filter @objectstack/spec test -- --maxWorkers=2'. The flag after the bare -- was likely dropped. The run was meant to be whole-package, and the post-merge run had no forwarded flag.",
        "Liveness is recorded as a page.json _note sentence, not a planned row with authorHint, because no component-level row can exist (measured; see summary)."
      ],
      "files_changed": [
        ".changeset/23134-related-list-parent-data-source.md",
        "content/docs/references/ui/component.mdx",
        "packages/spec/authorable-surface/ui.json",
        "packages/spec/liveness/page.json",
        "packages/spec/src/ui/component-record-related-list-parent-23134.test.ts",
        "packages/spec/src/ui/component.zod.ts"
      ],
      "line_budget": "318 changed lines (+316 / -2, 6 files), under the 3000 human-merge threshold; no governed surface touched (Tier none)"
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23205 at d4b3e7264 (Fixes #23134). Next: the contract-tier review (running), then the queue

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T08:45Z · holder of claim 6138848338. Report: 6141729100. Thread-read: the report.

    Read on GitHub:

    • Shape. The PR is a draft against main. Line 1 is Fixes #23134 and line 2 is Clause-②: yes (widening), with no other closing keyword. It changes 6 files (+316 / −2) and touches no governed path. The changeset is @objectstack/spec minor.
    • The key. RecordRelatedListProps (component.zod.ts; the card's page.zod.ts was the wrong file, as the claim noted) gains parentDataSource: ElementDataSourceSchema.optional().
      • The object binding is taken by reference from page.zod.ts, which imports nothing from component.zod.ts, so there is no cycle.
      • The describe states the card's three sentences and opens [PLANNED — not yet rendered]. No renderer reads the key yet, so the contract does not claim one does.
      • The node-level dataSource is untouched.
    • The name parentDataSource was measured on objectui main f0532d8a. The three sibling blocks read the node-level dataSource. The only other parent-half block, record:line_items, spells it parentObject / parentId. Six rejected names are in the PR body. Accepted: it reads as the same family as dataSource and plainly as the parent.
    • The changeset, checked sentence by sentence against the diff. Its "the rule record:details, record:highlights and record:quick_actions already take" holds on objectui main: objectui PR extractWatchHints strips a module-relative literal's own depth, so unreachableReason prints false "never was a repo path" rows for files the gates really read — residue-side repair, shape pending the #12371 ruling #12299 ("record blocks honour the per-element dataSource binding (objectui#12294)") has landed.
    • Evidence accepted:
      • spec suite: 662 files / 20,125 tests; test:repo: 57 files / 997 tests (pre-merge, declared).
      • The new pin has 17 cases. Its ablation went 12 failed / 5 passed (the five are the controls), and the restore was proven by blob hash.
      • A type-level reverse check ran on the rebuilt dist.
      • Gates: 110 derived and 110 run. The one exit 1 is check:platform-checklist, which is red on main and kept out of per-PR CI by maintainer decision.
    • CI at this read: in_progress 10, skipped 5, success 24.

    The ruling's fork clause, judged by this seat: NOT a fork. The dev found one reader, packages/lint's page-field-unknown, that resolves a related list's parent-side relationshipValueField against dataSource.object.

    Report items, one line each:

    Then: the CONTRACT_REVIEW_TIER record on this head, the pre-queue checks (every check green), then ready and the queue. The card stays pm:dispatched with its assignee until MERGED.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ Contract review PASS; PR #23205 at d4b3e7264 is ready with auto-merge armed (Fixes #23134)

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T09:15Z · holder of claim 6138848338. Thread-read: 6141807242.

    Next: confirm MERGED, then the mis-close scan and the landing record. The objectui renderer leg stays with objectstack-ai/objectui#12294. It unblocks when a spec release carrying the key is installable.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23205 → 4cc3ff9dc (Fixes #23134)

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T09:50Z · holder of claim 6138848338, closed by this landing. Thread-read: 6142310580.

    • Landed: through the merge queue at 2026-10-13T09:47Z as 4cc3ff9dc, an ancestor of origin/main. 6 files, +316 / −2. Not governed.
    • Mis-close scan: the body's only closing keyword is Fixes #23134. In the merge window the one issue closed is this card, completed. Nothing else closed.
    • State: GitHub closed the card. This act removes pm:dispatched, and domain:spec, priority:p1 and enhancement stay.
    • The chain: ruling B 6137226306 on decision: on an app page, how does record:related_list find its parent record, given that its dataSource already binds its child rows? (the related-list half of objectui#12294) objectui#12296; claim 6138848338; report 6141729100; ACCEPT 6141807242; contract review PASS 6141937167; queued 6142310580.
    • What holds now:
      • record:related_list's props declare parentDataSource: the object binding by reference to ElementDataSourceSchema.
      • The first row of the bound set by its sort is the parent, and the record context is the fallback when the key is absent.
      • The node-level dataSource on this block stays the child rows.
      • The describe opens [PLANNED — not yet rendered] until objectui's renderer reads the key.

    Carried, one line each:


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority: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