Skip to content

controlled_by_parent admits an optional master (1/3): spec, lint and the read owner branch: a null-master detail row is readable by its owner only #30905

Open

Description

@objectstack-fleet

epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

Filing-gate category: ② maintainer-directed task. Maintainer ruling, verbatim (Claude Code session, 2026-11-06): 「A」. The ruling: controlled_by_parent admits an optional master, and a detail row with no master is visible only to its owner (Salesforce parity: a contact without an account is a private contact). Rejected: B, a membership key collecting its object's own id; C, one-hop RLS traversal; D, no platform change. Routing, verbatim: 「能交给车队开发的你不要自己开发」. The plan was re-read by an isolated contract-review-tier reviewer before filing (PASS WITH FIXES, fixes folded in). It lands in three serial cards (this one and its two siblings), each safe on its own.

Why (measured 2026-11-06, the first live scenario run, objectstack-ai/cloud#3600)

The rule "a rep sees the contacts of accounts they own; a contact need not have an account" has no declarative form in spec 18.3.0:

  • RLS account.owner == current_user.id is refused: rls-predicate-unenforceable (packages/lint/src/validate-rls-predicate-enforceability.ts:316).
  • A membership key with valueField: 'id' is refused: membership-key-value-field-invalid (validate-membership-key-declarations.ts:80).
  • controlled_by_parent with required: false on the master is refused at two doors: ObjectSchema.create() (packages/spec/src/data/object.zod.ts:4406-4417) and the parse refinement, shape master-not-required (object.zod.ts:2743-2752, controlled-by-parent-master.ts:178-185).

The only working route today is a copied owner field plus two hooks. The AI-built app took that route, and its hook failed verify.

Clause-②: yes. Card 1 of 3: spec, lint, record, read.

Ask

  1. Spec. A controlled_by_parent object may declare its one master_detail with an explicit required: false. Retire the master-not-required refusal at both doors for that case; every other refusal stays (several masters, required lookup only, readonly / system master). Omitted required still means required: forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted stay untouched. Move isControlledByParentMasterField, resolveCbpRelation (security-plugin-cbp-relation.ts:164, today required === true only) and reportCbpMasterUnresolved's advice (:223-250) together.
  2. Compatibility. No accepted stored object changes meaning. A stored row the v18 upgrade listed as master-not-required (conversions/refused-shapes.ts:148-155; today registered [metadata_spec_invalid] and denied every read) becomes a valid optional-master detail. Retire that arm of refused-shapes.ts, and register a D3 semantic entry beside 18.cbp-master-relation-tiers-retired. Changeset: minor on spec, Metadata-protocol: added.
  3. Lint (data-model-rules.ts:398, relationship/master-detail-required): under cbp, error on an omitted required and on a readonly / system master; an explicit false passes.
  4. Read: the owner branch, at the one chokepoint. In computeControlledByParentFilter (security-plugin-cbp-read-filter.ts), OR a second arm with the unchanged master arm, for rows whose master FK is null: owner_id == caller, or viewAllRecords on the detail. This one function is ANDed by the authz middleware, getReadFilter (which feeds analytics and the engine's ReadableIds composer for attachment/comment/activity/approval parent gates) and computeDelegatedReadFilter, so search, export, MCP, REST and reports follow. The $or lives inside the cbp layer, which stays ANDed under the Layer-0 tenant wall. Rules to state and test:
    • a non-null-master row is never readable through the owner arm; a null-master row is never readable through the master arm;
    • View All on the detail admits null-master rows only; it does not bypass the master arm (unchanged);
    • a null-master row with owner_id null (an ADR-0142 queue row), or on an object with no owner_id column (ownership other than 'user', injected-system-columns.ts:234-243), is readable by nobody but View All until claimed;
    • chains: an intermediate level whose own master is null is readable to its owner, and its children's master arm sees exactly that ($or with nested scopes compiles, readable-ids-conformance.ts:226,262).
  5. Explain engine (explain-engine.ts:1991-2017, MASTER_UNRESOLVED_DETAIL at :1138): narrate the owner arm.
  6. Record: amend ADR-0055 §1 and §2.
  7. Tests: each rule above, including that a caller who can read another master cannot see null-master rows they do not own.

Why this card is safe alone: the write gate is unchanged, so it still refuses a null-master insert (MasterReferenceMissingError 422) and no null-master rows can exist yet. Note the new predicate shape fk IS NULL AND owner_id = ? for the driver review.

Reader: the domain:server lane seat (with domain:spec for item 1), from pm:queue. Contract-tier review owed.

Activity

  1. added
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    domain:serverDomain 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 Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop patrol · 2026-11-06T10:27Z
    Session: session_016WV4cytLmJxfAmgPVjUbbW
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-30905-cbp-optional-master (placeholder; the card thread's harness-pinned branch replaces it by a full re-post of this claim before any push)
    Worktree: objectstack-issue-30905
    Domain: domain:server (item 1 with domain:spec)
    Seat: domain:server#1
    File surface (card Asks 1–7, nothing else):

    • Spec: packages/spec/src/data/object.zod.ts (the master-not-required refusal at ObjectSchema.create() and the parse refinement, for an explicit required: false only), controlled-by-parent-master.ts, conversions/refused-shapes.ts (retire that arm) and the D3 semantic entry beside 18.cbp-master-relation-tiers-retired, with their tests.
    • Lint: packages/lint/src/data-model-rules.ts (relationship/master-detail-required under cbp) and its tests.
    • Security: packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts (resolveCbpRelation, reportCbpMasterUnresolved's advice), security-plugin-cbp-read-filter.ts (computeControlledByParentFilter, the null-master owner arm), the explain engine's cbp narration (explain-engine.ts), and their tests, including the readable-ids conformance for nested $or.
    • Record: docs/adr/0055-*.md §1 and §2 (Tier H).
    • .changeset/: spec minor with Metadata-protocol: added, plus the touched packages' levels.

    ⛔ The write gate (card 2/3, #30906), docs and skills (card 3/3, #30907), ui/**, .claude/**, forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted (an omitted required still means required). Stop on breach and explain in the report
    Container & model: L, mode:card-thread, model: default, effort: xhigh (p1, security read path, contract surface)
    Clause-②: yes, expected (widening: a shape the spec refused is accepted). The dev re-derives it from the diff
    Metadata-protocol: added
    Responsibility: the cbp optional-master contract and its read owner arm | ObjectSchema (controlled_by_parent master required: false) | every read door that composes computeControlledByParentFilter (authz middleware, getReadFilter, analytics, ReadableIds, delegated reads), lint, explain, and the v18 upgrade's refused-shapes list
    Thread-read: none
    Premise: as the card measures on spec 18.3.0: required: false on a cbp master is refused at both spec doors (object.zod.ts :4406-4417 and :2743-2752), and there is no declarative form for "a detail with no master is its owner's". The dev re-reads each cited line on current main first
    Owed before landing: a CONTRACT_REVIEW_TIER review (published schema), domain:spec consent for the spec hunk (all spec seats are vacant, so it goes to the maintainer), and Tier H approval for the ADR
    Serial constraints cleared: no open PR edits the cbp read filter, the cbp relation resolver or the lint rule; #30906 and #30907 are blocked behind this card

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop patrol · 2026-11-06T10:29Z (full re-post of claim 6682716818 with the card thread's branch)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (the seat); dev = card thread session_016WV4cxZsyeZedvM4Ea8Ahs
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-prdvgm (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-30905
    Domain: domain:server (item 1 with domain:spec)
    Seat: domain:server#1
    File surface (card Asks 1–7, nothing else):

    • Spec: packages/spec/src/data/object.zod.ts (the master-not-required refusal at ObjectSchema.create() and the parse refinement, for an explicit required: false only), controlled-by-parent-master.ts, conversions/refused-shapes.ts (retire that arm) and the D3 semantic entry beside 18.cbp-master-relation-tiers-retired, with their tests.
    • Lint: packages/lint/src/data-model-rules.ts (relationship/master-detail-required under cbp) and its tests.
    • Security: packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts (resolveCbpRelation, reportCbpMasterUnresolved's advice), security-plugin-cbp-read-filter.ts (computeControlledByParentFilter, the null-master owner arm), the explain engine's cbp narration (explain-engine.ts), and their tests, including the readable-ids conformance for nested $or.
    • Record: docs/adr/0055-*.md §1 and §2 (Tier H).
    • .changeset/: spec minor with Metadata-protocol: added, plus the touched packages' levels.

    ⛔ The write gate (card 2/3, #30906), docs and skills (card 3/3, #30907), ui/**, .claude/**, forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted (an omitted required still means required). Stop on breach and explain in the report
    Container & model: L, mode:card-thread, model: default, effort: xhigh (p1, security read path, contract surface)
    Clause-②: yes, expected (widening: a shape the spec refused is accepted). The dev re-derives it from the diff
    Metadata-protocol: added
    Responsibility: the cbp optional-master contract and its read owner arm | ObjectSchema (controlled_by_parent master required: false) | every read door that composes computeControlledByParentFilter (authz middleware, getReadFilter, analytics, ReadableIds, delegated reads), lint, explain, and the v18 upgrade's refused-shapes list
    Thread-read: none
    Premise: as the card measures on spec 18.3.0: required: false on a cbp master is refused at both spec doors (object.zod.ts :4406-4417 and :2743-2752), and there is no declarative form for "a detail with no master is its owner's". The dev re-reads each cited line on current main first
    Owed before landing: a CONTRACT_REVIEW_TIER review (published schema), domain:spec consent for the spec hunk (all spec seats are vacant, so it goes to the maintainer), and Tier H approval for the ADR
    Serial constraints cleared: no open PR edits the cbp read filter, the cbp relation resolver or the lint rule; #30906 and #30907 are blocked behind this card

  4. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop patrol · 2026-11-06T11:00Z (full re-post of claim 6682716818: the card thread's branch, file surface amended on the dev's declaration)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (the seat); dev = card thread session_016WV4cxZsyeZedvM4Ea8Ahs
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-prdvgm (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-30905
    Domain: domain:server (item 1 with domain:spec)
    Seat: domain:server#1
    File surface (card Asks 1–7, nothing else):

    • Spec: packages/spec/src/data/object.zod.ts (the master-not-required refusal at ObjectSchema.create() and the parse refinement, for an explicit required: false only), controlled-by-parent-master.ts, conversions/refused-shapes.ts (retire that arm) and the D3 semantic entry beside 18.cbp-master-relation-tiers-retired, with their tests.
    • Lint: packages/lint/src/data-model-rules.ts (relationship/master-detail-required under cbp) and its tests.
    • Security: packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts (resolveCbpRelation, reportCbpMasterUnresolved's advice), security-plugin-cbp-read-filter.ts (computeControlledByParentFilter, the null-master owner arm), the explain engine's cbp narration (explain-engine.ts), and their tests, including the readable-ids conformance for nested $or.
    • Amended 2026-11-06T11:00Z, declared by the dev, needed so the change is not false elsewhere:
      • packages/lint/src/validate-security-posture.ts and rule-explanations.ts (the no-relation rule must not call a valid optional master "no relation").
      • packages/objectql/src/engine-readable-ids-permission-formula.ts and a docblock in engine.ts (the boot refusal of an uncomposable master must not skip optional-master details).
      • packages/plugins/plugin-security/src/security-plugin.ts, security-plugin-authz-middleware.ts, security-plugin-read-filter.ts (CbpRelation gains optional / owner column; the five callers pass their resolved permission sets so the null-master arm can read View All on the detail). The read chokepoint stays one function, computeControlledByParentFilter.
    • Record: docs/adr/0055-*.md §1 and §2 (Tier H).
    • .changeset/: spec minor with Metadata-protocol: added, plus the touched packages' levels.

    ⛔ The write gate (card 2/3, #30906), docs and skills (card 3/3, #30907), ui/**, .claude/**, forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted (an omitted required still means required). Stop on breach and explain in the report
    Container & model: L, mode:card-thread, model: default, effort: xhigh (p1, security read path, contract surface)
    Clause-②: yes, expected (widening: a shape the spec refused is accepted). The dev re-derives it from the diff
    Metadata-protocol: added
    Responsibility: the cbp optional-master contract and its read owner arm | ObjectSchema (controlled_by_parent master required: false) | every read door that composes computeControlledByParentFilter (authz middleware, getReadFilter, analytics, ReadableIds, delegated reads), lint, explain, and the v18 upgrade's refused-shapes list
    Thread-read: none
    Premise: as the card measures on spec 18.3.0: required: false on a cbp master is refused at both spec doors (object.zod.ts :4406-4417 and :2743-2752), and there is no declarative form for "a detail with no master is its owner's". The dev re-reads each cited line on current main first
    Owed before landing: a CONTRACT_REVIEW_TIER review (published schema), domain:spec consent for the spec hunk (all spec seats are vacant, so it goes to the maintainer), and Tier H approval for the ADR
    Serial constraints cleared: no open PR edits the cbp read filter, the cbp relation resolver or the lint rule; #30906 and #30907 are blocked behind this card

  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop patrol · 2026-11-06T12:34Z (full re-post of claim 6682716818: the card thread's branch, file surface amended twice on the dev's declarations)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (the seat); dev = card thread session_016WV4cxZsyeZedvM4Ea8Ahs
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-prdvgm (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-30905
    Domain: domain:server (item 1 with domain:spec)
    Seat: domain:server#1
    File surface (card Asks 1–7, nothing else):

    • Spec: packages/spec/src/data/object.zod.ts (the master-not-required refusal at ObjectSchema.create() and the parse refinement, for an explicit required: false only), controlled-by-parent-master.ts, conversions/refused-shapes.ts (retire that arm) and the D3 semantic entry beside 18.cbp-master-relation-tiers-retired, with their tests.
    • Lint: packages/lint/src/data-model-rules.ts (relationship/master-detail-required under cbp) and its tests.
    • Security: packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts (resolveCbpRelation, reportCbpMasterUnresolved's advice), security-plugin-cbp-read-filter.ts (computeControlledByParentFilter, the null-master owner arm), the explain engine's cbp narration (explain-engine.ts), and their tests, including the readable-ids conformance for nested $or.
    • Amended 2026-11-06T11:00Z, declared by the dev, needed so the change is not false elsewhere:
      • packages/lint/src/validate-security-posture.ts and rule-explanations.ts (the no-relation rule must not call a valid optional master "no relation").
      • packages/objectql/src/engine-readable-ids-permission-formula.ts and a docblock in engine.ts (the boot refusal of an uncomposable master must not skip optional-master details).
      • packages/plugins/plugin-security/src/security-plugin.ts, security-plugin-authz-middleware.ts, security-plugin-read-filter.ts (CbpRelation gains optional / owner column; the five callers pass their resolved permission sets so the null-master arm can read View All on the detail). The read chokepoint stays one function, computeControlledByParentFilter.
    • Amended 2026-11-06T12:34Z, mechanical consequences of option A: packages/metadata-protocol/src/protocol.stored-migration.test.ts (an explicit required: false row now counts canonical) and the docblock of UnservableControlledByParentMaster.field in packages/objectql/src/engine-top-level.ts.
    • Conditional, only if the maintainer rules A on the open decision: the control legs of two ui/ tests that use the spec's ObjectSchema as an oracle for "an omitted required is refused", test-only, Studio's lock untouched: ui/packages/app-shell/src/views/metadata-admin/previews/object-fields-io.test.ts (about :411) and ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.cbpRequiredMaster-12326.test.tsx (about :107). Under B or C they stay untouched.
    • Record: docs/adr/0055-*.md §1 and §2 (Tier H).
    • .changeset/: spec minor with Metadata-protocol: added, plus the touched packages' levels.

    ⛔ The write gate (card 2/3, #30906), docs and skills (card 3/3, #30907), ui/** (except the conditional two test files above), .claude/**, forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted (an omitted required still means required). Stop on breach and explain in the report
    Container & model: L, mode:card-thread, model: default, effort: xhigh (p1, security read path, contract surface)
    Clause-②: yes, expected (widening: a shape the spec refused is accepted). The dev re-derives it from the diff
    Metadata-protocol: added
    Responsibility: the cbp optional-master contract and its read owner arm | ObjectSchema (controlled_by_parent master required: false) | every read door that composes computeControlledByParentFilter (authz middleware, getReadFilter, analytics, ReadableIds, delegated reads), lint, explain, and the v18 upgrade's refused-shapes list
    Thread-read: none
    Premise: as the card measures on spec 18.3.0: required: false on a cbp master is refused at both spec doors (object.zod.ts :4406-4417 and :2743-2752), and there is no declarative form for "a detail with no master is its owner's". The dev re-reads each cited line on current main first
    Owed before landing: a CONTRACT_REVIEW_TIER review (published schema), domain:spec consent for the spec hunk (all spec seats are vacant, so it goes to the maintainer), and Tier H approval for the ADR
    Serial constraints cleared: no open PR edits the cbp read filter, the cbp relation resolver or the lint rule; #30906 and #30907 are blocked behind this card

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cxZsyeZedvM4Ea8Ahs
    os-dev-report

    {
      "issue": 30905,
      "status": "done",
      "branch": "claude/project-thread-prdvgm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30940",
      "head": "2374556f915b4e48da446b382785aea5e6ba6519",
      "session": "session_016WV4cxZsyeZedvM4Ea8Ahs",
      "premise_still_valid": true,
      "summary": "Implemented card Asks 1-7 on option A of the open parse question: the only master_detail of a controlled_by_parent object may be declared required: false (an optional master) at the parse and at ObjectSchema.create(), with every other refusal kept; refused-shapes retires that arm and the D3 entry cbp-optional-master-admitted records it; relationship/master-detail-required and the posture rules follow. computeControlledByParentFilter ORs the unchanged master arm with a null-master arm (View All on the detail, else fk null AND owner_id = caller, else none); a required master's leaf is byte-identical. Explain narrates the arm; ADR-0055 §1/§2 amended. Test UI is expected red on two ui/ tests whose oracle assumed the refusal option A removes; per the seat (claim 6684936456) they are edited here only if the maintainer rules A.",
      "tests": "New plugin-security suite controlled-by-parent-optional-master.test.ts 24/24. Full: spec 817 files passed + 1 skipped (dist-freshness gate) / 25856 tests; lint 196/196 files, 8288 tests; plugin-security 247/247, 5710 passed 45 skipped; objectql 558/558, 11160; metadata-protocol 320 + 4 skipped, 22487 passed; verify 48/48, 408; plugin-sharing 67/67, 1436; service-analytics 211/211, 5015 passed; 13 cbp test files in cli, plugin-audit, service-storage, rest 347/347. Typecheck exit 0 for spec, lint, objectql, plugin-security. Ablation via scripts/ablation-replace.mjs, restore verified blob == HEAD 2ba0466291de: (a) null-master arm dropped -> 6 red (injected shape, owner reads own null-master rows x2 faces, chain x2, disjoint union); (b) owner arm without the fk-null conjunct -> 8 red (the six plus 'a row that NAMES a master is never read through the owner arm' x2).",
      "gates": {
        "derived": 108,
        "run": 108,
        "not_measured": 0,
        "nonzero_final": 0,
        "reconcile": "dispatch-gates --ran: 108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED",
        "reruns": [
          {
            "cmd": "pnpm check:dual-build-cjs-loads",
            "first": 3,
            "final": 0,
            "why": "no dist before the whole-tree build"
          },
          {
            "cmd": "pnpm check:i18n",
            "first": 3,
            "final": 0,
            "why": "no cli dist before the whole-tree build"
          },
          {
            "cmd": "node scripts/check-plugin-teardown-shape.mjs --self-test",
            "first": 3,
            "final": 0,
            "why": "shallow clone; fetched pinned fixture commit 621a4876"
          },
          {
            "cmd": "pnpm check:engine-double-contract",
            "first": 1,
            "final": 0,
            "why": "new suite's findOne double not in the ledger; --write committed"
          }
        ],
        "pnpm_lint": 0,
        "whole_tree_build": "turbo run build --filter=!@objectstack/docs: 118/118, lock VERDICT command-exit 0",
        "ci": "not read yet: PR just opened; Test UI expected red on the two ui/ tests named in the body"
      },
      "line_budget": {
        "skills": "n/a: no skills/** in the diff",
        "diff": "35 files, +1563 / -202"
      },
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "deviations": [
        "forceCbpMasterDetailRequired (claim ⛔ for its force half): only its explicit-false throw changed, narrowed to the object's only master_detail, as the seat agreed; the force half and the D2 conversion source are unchanged.",
        "File surface beyond the first claim, accepted by the seat in 6683296158: lint validate-security-posture.ts and rule-explanations.ts; objectql engine-readable-ids-permission-formula.ts and an engine.ts docblock; plugin-security security-plugin.ts, -authz-middleware.ts, -read-filter.ts.",
        "Declared after that, accepted in 6684936456: packages/metadata-protocol/src/protocol.stored-migration.test.ts and the UnservableControlledByParentMaster.field docblock in packages/objectql/src/engine-top-level.ts.",
        "Generated or ledger files: packages/spec/api-surface/data.json, packages/spec/export-origins/data.json, scripts/engine-double-contract.pinned.json.",
        "Branch not rebased: main moved 5 commits after the base 98f640d5 with no file in common; the branch is not claude/issue-* so no force push.",
        "git fetch --depth=1 of commit 621a4876 into the shallow worktree, for check-plugin-teardown-shape's pinned fixture."
      ],
      "files_changed": [
        ".changeset/30905-cbp-optional-master.md",
        "docs/adr/0055-master-detail-controlled-by-parent.md",
        "packages/lint/src/data-model-rules.master-detail-required.test.ts",
        "packages/lint/src/data-model-rules.ts",
        "packages/lint/src/rule-explanations.ts",
        "packages/lint/src/validate-security-posture-extension-merge.test.ts",
        "packages/lint/src/validate-security-posture.test.ts",
        "packages/lint/src/validate-security-posture.ts",
        "packages/metadata-protocol/src/protocol.stored-migration.test.ts",
        "packages/objectql/src/engine-readable-ids-permission-formula.ts",
        "packages/objectql/src/engine-top-level.ts",
        "packages/objectql/src/engine.ts",
        "packages/plugins/plugin-security/src/controlled-by-parent-optional-master.test.ts",
        "packages/plugins/plugin-security/src/controlled-by-parent-sharing.test.ts",
        "packages/plugins/plugin-security/src/explain-engine.ts",
        "packages/plugins/plugin-security/src/security-plugin-authz-middleware.ts",
        "packages/plugins/plugin-security/src/security-plugin-cbp-read-filter.ts",
        "packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts",
        "packages/plugins/plugin-security/src/security-plugin-read-filter.ts",
        "packages/plugins/plugin-security/src/security-plugin.ts",
        "packages/spec/api-surface/data.json",
        "packages/spec/export-origins/data.json",
        "packages/spec/src/conversions/cbp-master-detail-required-defaulted.test.ts",
        "packages/spec/src/conversions/refused-shapes.test.ts",
        "packages/spec/src/conversions/refused-shapes.ts",
        "packages/spec/src/data/controlled-by-parent-master.test.ts",
        "packages/spec/src/data/controlled-by-parent-master.ts",
        "packages/spec/src/data/object.test.ts",
        "packages/spec/src/data/object.zod.ts",
        "packages/spec/src/data/readable-ids-conformance.ts",
        "packages/spec/src/migrations/entries/semantic/18.cbp-optional-master-admitted.ts",
        "packages/spec/src/migrations/registry.ts.template",
        "packages/spec/src/shared/retired-key-removal-version.pin.test.ts",
        "packages/spec/src/stack-cbp-extension-master.test.ts",
        "scripts/engine-double-contract.pinned.json"
      ],
      "mcp_calls": "4 — reads only: pull_request_read get (#30908, body shape), issue_read get_comments (#30905), pull_request_read get_files and get_status (#30940); no write tool",
      "api_writes": "2 relay writes: fleet-write pr_create (#30940, read back identical, assignee os-elon-musk) and this os-dev-report comment via post-stamped; git push x3 (not REST)",
      "open_questions": [
        {
          "question": "Should the parse tell an omitted required apart from an explicit required: false on a controlled_by_parent master?",
          "options": [
            "A: no; the parse admits both as the optional master, the builder and D2 keep omitted as required, lint errors on the omission (implemented; costs the two ui/ test control legs, test-only)",
            "B: keep refusing the omission at every parse door; needs a new raw-input hook on ObjectSchema, outside this surface, medium",
            "C: a separate explicit opt-in key instead of required: false; reopens the ruling's spelling"
          ],
          "recommendation": "A, because it is in surface and small, the omission stays required wherever it is visible (builder, stored rows, Studio), lint reports it, and the runtime fails closed on an unparsed omission"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: b · reach: Test UI on this PR · evidence: ui/packages/app-shell/src/views/metadata-admin/previews/object-fields-io.test.ts L411 and ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.cbpRequiredMaster-12326.test.tsx L107 assert ObjectSchema refuses a cbp object whose only master_detail omits required; red on 2374556f · dedupe words: cbpRequiredMaster oracle control leg · carrier: this PR if ruled A (claim 6684936456 conditional surface), else none",
        "class: c · reach: Studio, not reproduced · evidence: ui owd-sharing.ts and PackageOwdOverviewPanel.tsx read only required: true as the master, and ObjectFieldInspector locks Required on for a cbp master, so Studio cannot author an optional master and shows none for one · dedupe words: Studio optional master OWD panel · carrier: #30907 or a ui card · noted, not filed",
        "class: c · reach: os db orphans, not reproduced · evidence: packages/cli/src/utils/orphan-detail-sweep.ts L24-27, L130, L138 keep findings to isControlledByParentMasterField (required master), so dangling master references on an optional-master detail are not swept · dedupe words: orphan sweep optional master · carrier: seat · noted, not filed",
        "class: c · reach: prose only · evidence: packages/spec/src/conversions/entries/18/cbp-master-detail-required-defaulted.ts docblock says the builder refuses an explicit required: false and that a live author still meets the parse's refusal; both are false for the sole master under A; the file is on the claim's ⛔ list so it was not edited · dedupe words: defaulted conversion docblock stale · carrier: seat (this PR or #30907) · noted, not filed"
      ]
    }
  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop patrol · 2026-11-06T13:20Z (full re-post of claim 6682716818: the card thread's branch, file surface amended three times on the dev's declarations)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (the seat); dev = card thread session_016WV4cxZsyeZedvM4Ea8Ahs
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-prdvgm (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-30905
    Domain: domain:server (item 1 with domain:spec)
    Seat: domain:server#1
    File surface (card Asks 1–7, nothing else):

    • Spec: packages/spec/src/data/object.zod.ts (the master-not-required refusal at ObjectSchema.create() and the parse refinement, for an explicit required: false only), controlled-by-parent-master.ts, conversions/refused-shapes.ts (retire that arm) and the D3 semantic entry beside 18.cbp-master-relation-tiers-retired, with their tests.
    • Lint: packages/lint/src/data-model-rules.ts (relationship/master-detail-required under cbp) and its tests.
    • Security: packages/plugins/plugin-security/src/security-plugin-cbp-relation.ts (resolveCbpRelation, reportCbpMasterUnresolved's advice), security-plugin-cbp-read-filter.ts (computeControlledByParentFilter, the null-master owner arm), the explain engine's cbp narration (explain-engine.ts), and their tests, including the readable-ids conformance for nested $or.
    • Amended 2026-11-06T11:00Z, declared by the dev, needed so the change is not false elsewhere:
      • packages/lint/src/validate-security-posture.ts and rule-explanations.ts (the no-relation rule must not call a valid optional master "no relation").
      • packages/objectql/src/engine-readable-ids-permission-formula.ts and a docblock in engine.ts (the boot refusal of an uncomposable master must not skip optional-master details).
      • packages/plugins/plugin-security/src/security-plugin.ts, security-plugin-authz-middleware.ts, security-plugin-read-filter.ts (CbpRelation gains optional / owner column; the five callers pass their resolved permission sets so the null-master arm can read View All on the detail). The read chokepoint stays one function, computeControlledByParentFilter.
    • Amended 2026-11-06T12:34Z, mechanical consequences of option A: packages/metadata-protocol/src/protocol.stored-migration.test.ts (an explicit required: false row now counts canonical) and the docblock of UnservableControlledByParentMaster.field in packages/objectql/src/engine-top-level.ts.
    • Conditional, only if the maintainer rules A on the open decision (amended 2026-11-06T13:20Z from the full Test UI run on head 2374556f, 16 shards, 6 red cases in 5 files): test-only re-points of ui/ tests that use the spec's ObjectSchema as an oracle for "an omitted required is refused". Studio source is untouched, and so is what Studio writes (it still parses).
      • The four control legs: ui/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.cbpRequiredMaster-12326.test.tsx (about :58), ui/packages/app-shell/src/views/metadata-admin/previews/object-fields-io.test.ts (about :411), ui/packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.cbpRequiredMaster-12326.test.tsx (about :107), ui/packages/app-shell/src/views/studio-design/PackageOwdOverviewPanel.cbpRequiredMaster-12326.test.tsx (about :90).
      • The two display-agreement rows, re-labelled and not re-pointed: ui/packages/app-shell/src/views/studio-design/owd-sharing.test.ts (about :80) and PackageOwdOverviewPanel.cbpRequiredMaster-12326.test.tsx (about :72). They now record that the parse admits a lone master with required omitted while the raw registry and Studio's OWD panel name no master.
      • ⛔ Not in this card: owd-sharing.ts deriveMasterObject (it hard-codes required === true) and the force at PackageOwdOverviewPanel.tsx :141. Changing them is Studio behaviour; under A that display gap becomes its own ui card.
      • Under B or C all of these stay untouched.
    • Record: docs/adr/0055-*.md §1 and §2 (Tier H).
    • .changeset/: spec minor with Metadata-protocol: added, plus the touched packages' levels.

    ⛔ The write gate (card 2/3, #30906), docs and skills (card 3/3, #30907), ui/** (except the conditional test files above), .claude/**, forceCbpMasterDetailRequired and the D2 conversion cbp-master-detail-required-defaulted (an omitted required still means required). Stop on breach and explain in the report
    Container & model: L, mode:card-thread, model: default, effort: xhigh (p1, security read path, contract surface)
    Clause-②: yes, expected (widening: a shape the spec refused is accepted). The dev re-derives it from the diff
    Metadata-protocol: added
    Responsibility: the cbp optional-master contract and its read owner arm | ObjectSchema (controlled_by_parent master required: false) | every read door that composes computeControlledByParentFilter (authz middleware, getReadFilter, analytics, ReadableIds, delegated reads), lint, explain, and the v18 upgrade's refused-shapes list
    Thread-read: none
    Premise: as the card measures on spec 18.3.0: required: false on a cbp master is refused at both spec doors (object.zod.ts :4406-4417 and :2743-2752), and there is no declarative form for "a detail with no master is its owner's". The dev re-reads each cited line on current main first
    Owed before landing: a CONTRACT_REVIEW_TIER review (published schema), domain:spec consent for the spec hunk (all spec seats are vacant, so it goes to the maintainer), and Tier H approval for the ADR
    Serial constraints cleared: no open PR edits the cbp read filter, the cbp relation resolver or the lint rule; #30906 and #30907 are blocked behind this card

  8. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 6, 2026
  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Ruling record · A · 2026-11-06T14:57Z · how a raw parse reads a cbp master with required omitted

    The maintainer answered the seat's decision card at 2026-11-06T14:57Z by choosing option A, label verbatim: 「A 放行为可选主字段(推荐)」. The card's question, verbatim: 「#30905 子对象主字段未写 required 时,原始解析入口怎么处理?」.

    What A means, as the card stated it: at the raw parse doors (an object written directly in defineStack, the save doors), a controlled_by_parent master with required omitted is read as an optional master. The builder, stored data and Studio still default it to required, and os lint errors on the omitted spelling. B (refuse the omitted spelling at every door through a new raw-input check) and C (a separate explicit key) are not taken.

    Consequences for PR #30940:

    • The spec hunk stands as built.
    • The conditional ui/ surface of claim 6685806956 is now in scope: the four control legs are re-pointed and the two display-agreement rows re-labelled, test-only. Studio source is untouched.
    • The contract-review hold 6685293293 is lifted; the CONTRACT_REVIEW_TIER review runs on the head that carries those test edits.
    • Still owed before landing: domain:spec consent for the spec hunk and Tier H approval for the ADR-0055 amendment.

    Findings to card now that A stands: the Studio OWD display gap (deriveMasterObject hard-codes required === true), Studio cannot author an optional master, os db orphans skips optional-master details, and the D2 conversion's docblock reads stale under A.

  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Spec consent recorded · domain:spec · 2026-11-06T15:09Z · #30905

    The maintainer (Elon) consented in the seat thread at 2026-11-06T15:07Z, verbatim: 「以上都同意。」. The message it answers was the seat's explanation of the pending spec changes, which named this card's hunk: PR #30940 lets a controlled_by_parent master declare required: false (packages/spec/src/data/object.zod.ts, controlled-by-parent-master.ts, the retired arm in conversions/refused-shapes.ts and the D3 semantic entry), as ruled A in record 6687830530. The consent is recorded here because no spec seat held these hunks while they waited (spec#1 is seated again since). It is consent to the spec hunk only. The CONTRACT_REVIEW_TIER review and every other landing condition stay owed.

  11. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Seat handoff: the lane signs off. PR #30940 is reviewed and waits only on a Tier H approval.

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:serverDomain lane — seat card indexed by label:pm:seatpm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-setpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority: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