Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

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

  8. added a commit that references this issue on Oct 28, 2026
    28fb2a0
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