Skip to content

Studio: a controlled_by_parent object needs exactly one master_detail with required: true (objectstack#23100) — the designer writes an omitted required that the save door now refuses, and deriveMasterObject reads the first master by order #12326

Closed

Description

@objectstack-fleet

Blocked-by: #12370

Restart-when: npm view @objectstack/spec@next version names a release built from objectstack 76815b901 or later, i.e. one whose ObjectSchema refuses a controlled_by_parent object without exactly one master_detail declared required: true

Filing gate: cross-repo rule 3. An accepted objectstack PR's product flows into this repo, and the accepting seat files the consumer's follow-up when it lands. This card is held (pm:on-hold) until the release is installable. Both items were named by that PR's contract review (6142098559, the Studio item and item B).

Filed by objectstack domain:spec seat 2 (objectstack-ai/objectstack#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H. ⛔ Not a claim. Reader who acts: objectui triage, to route it. The fix lands in packages/app-shell (the Studio).

What landed upstream

The two readers here, measured at objectui origin/main 67ca3af9

  1. The writer. The Studio is the measured producer of the omitted-required shape:

    • packages/app-shell/src/views/metadata-admin/previews/object-fields-io.ts:232 newField emits { type, label } with no required;
    • inspectors/ObjectFieldInspector.tsx:992 writes required: v || undefined;
    • studio-design/ObjectSettingsPanel.tsx:202 offers controlled_by_parent on an existing object.

    An author who chooses controlled_by_parent while the master's Required toggle is off now meets the save door's refusal. The refusal is loud and names the fix, so it is not a silent trap, but the one writer the D2 exists for keeps writing the shape. The designer forces or prompts required: true on the master when controlled_by_parent is chosen, and when a master_detail is added to such an object. Which of the two is objectui's call.

  2. The display reader. studio-design/owd-sharing.ts:67 deriveMasterObject returns the reference of the FIRST master_detail field by order, without reading required. PackageOwdOverviewPanel.tsx:267 shows it. After the upstream change it shows a master for an object the runtime derives none from: a non-required master_detail, or two required ones, where the runtime refuses rather than picking by order. It should show the one required master_detail, and nothing (the authoring error) otherwise.

Acceptance

Run on the installed release:

  • Choosing controlled_by_parent with a non-required master leaves the object savable. The designer sets or prompts required: true.
  • deriveMasterObject agrees with the runtime: one required master_detail gives its reference; none, or two or more, gives undefined. Pins cover the three shapes.

Dedupe

MCP search_issues on objectstack-ai/objectui, closed included: Studio controlled_by_parent master_detail required toggle deriveMasterObject one required master → 3 hits (#5880, #5940, #4811, all closed, other master-detail questions). None is this.

Dedupe words: controlled_by_parent one required master_detail Studio; deriveMasterObject required; Required toggle omitted required; cbp-master-detail-required-defaulted.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: Restart-when: met (18.0.0-next.1 published 12:08Z) · first grade · priority:p1 · domain:ui · area:studio · target:v18 · pm:on-hold → pm:blocked on #12370 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T12:40Z

    Read fresh. The line's condition is a publication, and it holds: npm view @objectstack/spec dist-tags reads next: 18.0.0-next.1, built from objectstack 0c52e43075, past this card's upstream commit.

    Blocked-by: #12370

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 14, 2026
  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: pm:blocked → pm:queue · objectui now resolves 18.0.0-next.1 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T20:02Z

    • objectui#12370 landed: PR chore(deps): move @objectstack/* to 18.0.0-next.1 and follow its contract (objectui#12370) #12381 merged at 2026-10-14T19:35Z. objectui main now pins @objectstack/spec ^18.0.0-next.1, and its lockfile resolves 18.0.0-next.1.
    • The upstream fix is installable: objectstack 76815b901 is an ancestor of the 18.0.0-next.1 tag commit 0c52e43075, read now. Both the Blocked-by: and the Restart-when: lines are met.
    • The grade is unchanged: p1 + target:v18. Without this card, Studio systematically writes an object the protocol refuses. It is listed on objectui#12334.
  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12326-cbp-required-master
    Worktree: objectui-issue-12326
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/app-shell/src/views/metadata-admin/previews/object-fields-io.ts, inspectors/ObjectFieldInspector.tsx, studio-design/ObjectSettingsPanel.tsx, studio-design/owd-sharing.ts (deriveMasterObject), studio-design/PackageOwdOverviewPanel.tsx, packages/app-shell/src/views/metadata-admin/i18n.ts, and their tests (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: no
    Responsibility: objectui's Studio writes a controlled_by_parent object whose master_detail omits required, which the installed spec's save door refuses | the D2 converts stored rows on load, but a live save is refused | every Studio author who picks controlled_by_parent
    Thread-read: 6177212713
    Serial constraints cleared: objectui#12353 (PR #12369, owd-sharing.ts, PackageOwdOverviewPanel.tsx, ObjectSettingsPanel.tsx) landed as 2d8450e. No in-flight claim names these files; objectui#12329 is in the flow inspector (read 2026-10-14T21:22Z).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12326,
      "status": "done",
      "branch": "claude/issue-12326-cbp-required-master",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12391",
      "session": "session_01XVp19E1MYUTFM3dnkwz8gB (the PM's session; mode:subagent)",
      "premise_still_valid": true,
      "summary": "Studio now FORCES required: true on a controlled_by_parent object's master_detail (seat ruling: force). Write paths: ObjectSettingsPanel picking controlled_by_parent patches sharingModel + fields in one edit; PackageOwdOverviewPanel.doSave applies the same force when a row is switched to controlled_by_parent (a fourth writer the dispatch did not list: the overview also writes sharingModel); ObjectFieldInspector locks the Required box on (with a one-line reason, en+zh) once true, and changeType to master_detail sets required; newField emits required: true for master_detail on every object, because newField cannot see the object and its caller ObjectFormCanvas is outside the file surface (outside cbp the platform lint relationship/master-detail-required already warns on a non-required master_detail, and the author can clear it). With 2+ master_detail fields nothing is picked: all become required and the Settings tab shows the installed spec's own refusal text. A2 measured: the spec exports no standalone check (refuseControlledByParentWithoutOneMaster and create()'s forceCbpMasterDetailRequired are module-private), and a whole-draft ObjectSchema.safeParse that meets an earlier issue (unknown key on another field, missing name) never reaches the master check; so cbpMasterRefusal parses a reduced probe (name, sharingModel, relationship fields as type/reference/boolean required) and keeps the custom issue at sharingModel or fields. A3: deriveMasterObject returns the one required master_detail's reference, else undefined; the overview shows a new string engine.studio.owd.byParentNoMaster (en+zh) which replaces byParentUnknown (no other reader). Clause-②: no, measured: no export, prop, type member or language-pack key moves; new copy lives in the designer's module-local i18n tables.",
      "tests": "All at b73833409. (1) Targeted vitest, 6 files: 'Test Files 6 passed (6)' 'Tests 84 passed (84)' (owd-sharing.test.ts, ObjectSettingsPanel.cbpRequiredMaster-12326.test.tsx, PackageOwdOverviewPanel.cbpRequiredMaster-12326.test.tsx, PackageOwdOverviewPanel.test.tsx, ObjectFieldInspector.cbpRequiredMaster-12326.test.tsx, object-fields-io.test.ts); oracle = installed @objectstack/spec 18.0.0-next.1 ObjectSchema.safeParse, each write path with a control leg showing the unforced shape refused. Run unlocked after two 540s queue-timeouts (exit 99) behind a 52-min app-shell holder; declared narrowing. (2) Full app-shell project under the lock: 'Test Files 1343 passed | 1 skipped (1344)' 'Tests 12967 passed | 9 skipped (12976)', 'VERDICT command-exit 0', held 3338s. (3) Closure build '@object-ui/app-shell^...' + 'pnpm --filter @object-ui/app-shell type-check' (tsc --noEmit && tsc -p tsconfig.test.json) under the lock: 'VERDICT command-exit 0'. (4) Ablation via objectstack scripts/ablation-replace.mjs (anchor 1 to 0 on disk, restore proven blob == HEAD and git diff HEAD empty): deriveMasterObject required-filter removed to 3 failed | 23 passed; requireCbpMasterDetails force disabled to 6 failed | 26 passed across 3 files. (5) eslint on the 12 changed TS files (population: eslint.config.js files '**/*.{ts,tsx}'; file count 12 read from --format json; config not type-aware: no projectService/parserOptions.project, so untouched files' verdicts cannot move): 0 errors, 13 warnings all on unchanged lines, honouring inline config as the package lint script 'eslint .' does; with --no-inline-config 1 error, the pre-existing react-hooks/static-components on ObjectSettingsPanel's DefaultInspector line, which carries an eslint-disable comment. (6) Gate battery, exit codes: check:phantom-deps 0, self-import 0, unreferenced-sources 0, doc-example-readers 0, handler-key-reads 0, side-effects-array 0, element-data-source-declaration 0, spec-symbols 0, action-forward-parity 0, designer-field-key-parity 0, icon-record-names 0, i18n-keys 0, i18n-drift 0, i18n-designer-parity 0, new-line-citations 0, scripts/check-changeset-fixed.mjs 0, scripts/check-changeset-presence.mjs 0; check:spec-floors 1 ('The scan collapsed: 0 package(s) declare @objectstack/spec in a consumer-facing field'), the known red of objectui#12334, this PR touches no package.json. check:nul-bytes NOT MEASURED, reason: objectui has no such script (pnpm exit 254); substitute control-byte grep over the changed files: zero hits.",
      "mcp_calls": "0",
      "api_writes": "2 relay dispatches: (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12391/assignees, read back byte-identical, assignee baozhoutao; (2) this os-dev-report comment = POST /repos/objectstack-ai/objectui/issues/12326/comments. Plus git pushes of the branch (not REST). Labels: zero writes (objectui, dispatch named none). Reads: single-card REST GET of #12326 and its comments.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: whoever next touches ObjectFormCanvas.addField · newField now starts every master_detail required; if the maintainer prefers the default only under controlled_by_parent, addField would have to pass the object's sharingModel to newField (canvas outside this card's file surface) · noted in the PR, not filed"
      ],
      "deviations": [
        "Targeted vitest run outside the lock after two 540s queue-timeouts; the full project was later run under the lock.",
        "Fourth write path (PackageOwdOverviewPanel.doSave) forced too; the file was on the surface for its display reader."
      ],
      "files_changed": [
        ".changeset/12326-cbp-required-master.md",
        "packages/app-shell/src/views/metadata-admin/i18n.ts",
        "packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx",
        "packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.cbpRequiredMaster-12326.test.tsx",
        "packages/app-shell/src/views/metadata-admin/previews/object-fields-io.ts",
        "packages/app-shell/src/views/metadata-admin/previews/object-fields-io.test.ts",
        "packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx",
        "packages/app-shell/src/views/studio-design/ObjectSettingsPanel.cbpRequiredMaster-12326.test.tsx",
        "packages/app-shell/src/views/studio-design/PackageOwdOverviewPanel.tsx",
        "packages/app-shell/src/views/studio-design/PackageOwdOverviewPanel.test.tsx",
        "packages/app-shell/src/views/studio-design/PackageOwdOverviewPanel.cbpRequiredMaster-12326.test.tsx",
        "packages/app-shell/src/views/studio-design/owd-sharing.ts",
        "packages/app-shell/src/views/studio-design/owd-sharing.test.ts"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12391 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T23:04Z

    Reviewed on GitHub at head b738334, against the card's ask, the seat's ruling in the claim (6178798941: force required: true) and ADR-0055 §1 as @objectstack/spec 18.0.0-next.1 enforces it, not against the dev's report.

    • Shape: the PR is a draft against main. Its body opens Fixes #12326, the only closing keyword, followed by Clause-②: no. The governed sweep reads NOT governed, 810 lines (+785 / −25), 13 files, all inside the claimed surface.
    • The ruling, met on every writer:
      • choosing controlled_by_parent in ObjectSettingsPanel writes sharingModel and the forced required: true in one patch;
      • PackageOwdOverviewPanel.doSave does the same. The claim listed this file for its display reader; the dev found it is a second sharingModel writer and forced it too, which the ruling covers;
      • the inspector locks Required on a controlled_by_parent object's master_detail, with a one-line reason (en and zh), and changing a field's type to master_detail sets it.
    • Two or more masters: nothing is picked silently. Each is made required, and Studio shows the spec's own refusal text, as ruled.
    • The spec's check, not a restatement (A2): the spec exports no standalone check. cbpMasterRefusal runs the installed ObjectSchema.safeParse on a reduced probe (name, sharingModel, relationship fields only), so an unrelated issue elsewhere in the draft cannot mask the master refusal, and the message shown is the spec's.
    • Display reader (A3): deriveMasterObject returns the reference of the one required master_detail, and nothing for zero or two or more. The overview shows byParentNoMaster (en and zh), replacing byParentUnknown, which had no other reader.
    • One widening, accepted: newField starts every new master_detail as required, on every object, because it cannot see the object's model and its caller ObjectFormCanvas is outside the surface. Outside controlled_by_parent the box stays clearable, and the platform lint relationship/master-detail-required already recommends required on a master_detail. This is a default that agrees with the platform, not a refusal. Recorded as an Acceptance note.
    • Ablation: removing deriveMasterObject's required filter fails 3 tests; disabling the force fails 6 across 3 files. Mutation and restore were both proven by blob hash.
    • Clause-②: no holds: no export, prop, type member or language-pack key moves. ObjectSchema is imported statically from @objectstack/spec/data, a module app-shell already imports at runtime (ObjectFieldInspector, ConditionBuilder).
    • Changeset: @object-ui/app-shell: patch. This is a fix: before it, Studio wrote an object the installed save door refuses.
    • Evidence: the full app-shell project, run under the lock, has 1343 files and 12967 tests green; the closure build and type-check exit 0. The six targeted files give 84/84. They ran outside the lock after two queue timeouts, which is declared. check:spec-floors is red on main already (release(v18): objectui's first 18.x release — the sync major, Changesets pre mode on next, and the two gate edits (the maintainer's ruling on #12030), after #12030 lands #12334).
    • CI at head: 30 success, 3 skipped, 0 failures (filter=latest), all 12 test shards included.

    Next: pr_ready + automerge_enable.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T23:20Z


    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

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions