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. 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

  2. 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

  3. 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

  4. 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

  5. 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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions