Skip to content

sharing: a share or sharing rule to an external member is refused as inert on a public_read_write or model-less system object, though the external OWD floor would consult it #25549

Closed

Description

@objectstack-fleet

Ruled: 6377844010 · batch #9 item 2 · letter A · 2026-10-24T00:43:45Z

Filing gate ②: a maintainer decision. It follows the ruling of record 6355206139 (letter B) on #25233 and is raised by the stage-2 contract review 6375883728 on #25541 (boundary flag "out of scope 1"). ⛔ Not a claim.

Reader who acts: the domain:spec lane (seat 1). After the ruling, one objectstack PR touches plugin-sharing and @objectstack/lint.

维护者速读

外部成员(持 external 角色的供应商门户用户)现在默认只能看到自己的记录,以及别人明确分享给他的记录。问题在于:在"全员可读写"(public_read_write)的对象上,以及没有声明共享模型的系统对象上,管理员根本建不出这条分享。系统会以"该对象不受记录共享约束,分享不会生效"为由拒绝,而且手动分享和共享规则都会被拒。结果是,管理员想把某一个项目、某一张单据单独给某个供应商看,没有办法做到,只能另写一条行级规则。这和裁决 B 的说法"分享与共享规则可以到达外部成员"不一致。

  • A:对外部收件人放行这条分享(及共享规则)。对内部收件人照旧拒绝,因为它对内部成员确实不生效。
  • B:维持拒绝。在文档里写明:在这类对象上,外部成员只能通过权限集的行级规则获得访问。
  • C:在这类对象上取消外部成员的"只见自己"下限。

推荐 A。请回一个字母:A / B / C。

The problem, in one sentence

An administrator cannot share a single record of a "read-and-write for everyone" object with a portal supplier: the share is refused as "would never take effect", although for that supplier it is the only thing that would.

Background

#25541 (stage 2 of ruling B) floors an external principal's org-wide default to owner-only on every object (owdForAudience, @objectstack/core). The floor lifts only when a held grant's RLS speaks for the object. Under that floor, buildReadFilter, buildWriteFilter, checkEdit and checkDelete DO consult sys_record_share rows for an external on objects whose effective model is public. A controlled_by_parent detail is not floored: its rows follow the master (fix 225d5bc on #25541).

The grant pre-flight still decides, caller-free, that a share row on such an object "would never be consulted", and refuses it.

Governing text

  • ADR-0111 D7 item 2: "grant() validates the object is in an enforcing posture — effectiveSharingModel is private or read and the object has an owner_id field. Otherwise → 422 (SHARING_NOT_ENABLED): a share on a public / bypass / owner-less object enforces nothing. controlled_by_parent is refused explicitly (… a detail record is not independently shareable; its access follows the master)."
  • plugin-sharing: grant() skips the ADR-0111 D7 inert-grant guard entirely for SYSTEM callers, so the sharing-rule evaluator can still materialise rows no gate consults #8207 (in inertGrantReason's docblock): "Whether a share row can ever be read is a property of the OBJECT: the gates that would consult it … never see the granter at all."
  • Ruling 6355206139, letter B: "the rung admits explicit grants only — shares, including share links; sharing rules; and position and permission-set grants with their RLS evaluated."

Protocol statement

No spec key, value or metadata type changes under any option: Metadata-protocol: none. Option A widens the REST grant door's acceptance, and the rule materialiser's, for one recipient population. Option C changes what the external rung means.

Premise, with the re-check

The refusal holds on main after #25541 lands:

grep -n "effectiveSharingModel(schema) === 'public' || !hasOwnerField(schema)" packages/plugins/plugin-sharing/src/sharing-service.ts   # inertGrantReason, the public arm
grep -n "this.assertNotInertGrant" packages/plugins/plugin-sharing/src/sharing-service.ts                                            # run for every caller, system included

assertNotInertGrant runs for system callers too, so sharing-rule materialisation on these objects is refused as well. The same verdict is mirrored at author time in packages/lint/src/validate-sharing-rule-enforceability.ts.

Scope, measured by the contract review: public_read_write objects, and model-less sys_* / isSystem objects (effective public). public_read objects are NOT refused (their effective model is read). controlled_by_parent is refused by design; its share goes to the master, and an external reads the lines of a master shared to it (pinned on a showcase boot in #25541). Existing rows are honoured; minting new ones fails, so the failure is closed and loud (422).

The specific question

Must D7's "would this row ever be consulted" verdict take the RECIPIENT's population into account, now that one population (external) is floored on public objects?

Options

Option What it does What the customer sees Effort
A: admit for external recipients inertGrantReason becomes a verdict over (object, recipient): on an effective-public object a user recipient whose membership in the object's organization is external is admitted; any other recipient is refused as today. The rule materialiser mints rows only for the external members a rule expands to. The lint mirror says "effective for external recipients only" instead of refusing. An admin shares one project with one supplier and it works, through the UI share dialog or a sharing rule. Internal recipients still get the honest 422. spec lane · 1 PR. Precedent: #25541's OWD seam (sharing-service.ts about +22 source lines, external-owd-floor.test.ts +142). The materialiser and the lint edits have no measured precedent.
B: keep the refusal, document the RLS route Docs only: sharing-rules.mdx and the Tier H ADR text say that on these objects an external is reached through a permission set's row policy. An admin who wants one supplier to see one record must author a permission set with a row policy (for example id in [...] or supplier == current_user.<key>) and bind it. That is a developer task, not a share click. spec lane · rides the Tier H docs PR; no code.
C: lift the floor on public objects owdForAudience stops flooring public models for externals. Every supplier reads and writes every row of every "public" object in the buyer's organization: other suppliers' projects included. spec lane · 1 small PR (reverts part of #25541).

Business meaning

  • A: like Salesforce, where a portal user sees a public-for-employees object as Private, and an admin can still hand them a record by manual sharing or a sharing rule. The supplier gets exactly the records it is handed.
  • B: a supplier can still be given access, but only by an engineer writing a rule. The everyday "share this with them" action does not exist on these objects.
  • C: the opposite of ruling B. Making an object open to staff would also open it to every supplier.

Recommendation: A (confidence: medium)

  • The end state, judged on ① alone: two years out, an external portal user is reached by every channel the ruling names, on every object, with one rule. Salesforce works this way (an external OWD that defaults to Private, manual shares and sharing rules to portal users), and so do Power Pages (table permissions plus record sharing).
  • Why A keeps D7's principle: D7's real premise is that "the gates never see the granter". A still holds it. The verdict stays independent of who asks, and depends only on facts the gate reads: the object, and the recipient's population. Only the "public ⇒ always inert" shortcut changes.
  • Why not B: it leaves a channel the ruling admits open on half the objects and closed on the other half. The deciding property is the object's OWD, which an admin sets for staff, not for suppliers.
  • Why not C: it contradicts the ruling.
  • Confidence gap:
    • A sharing rule whose target expands to both staff and suppliers would materialise rows for the suppliers only. That is correct, but the admin is told nothing about the staff half. A needs a one-line note in the rule's materialisation report, and I have not measured how that report surfaces today.
    • The recipient's population is judged in the object's organization. A user who is external in one organization and a member in another is decided per organization, which the stage-1 projection already does. Whether the grant path has the organization at hand on every caller (REST and the rule evaluator) is not yet read.
  • Self-check: judged on ① alone, A. Do ②③④ flip it? No. ② (pull is low today: hotsrm uses the grant-RLS shape, and the role has not shipped) only argues for landing after the Tier H docs PR, not for another letter. ③ favours A over B: A admits a precise case, and B keeps a loud refusal that admins will work around with broader RLS. ④: A adds no key.

After the ruling

If A, the spec lane opens one PR:

  1. The verdict. inertGrantReason(object, recipient?): on an effective-public object, a user recipient is admitted when its sys_member.role in the object's organization is exactly external. That is the same population test as the stage-1 door. Every other recipient keeps the 422.
  2. Rule materialisation. The rule evaluator applies the same per-recipient verdict.
  3. The author-time lint. validate-sharing-rule-enforceability reports "effective for external recipients only" as advice, not a refusal.
  4. Docs. sharing-rules.mdx gains one sentence.
  5. Tests. A plugin-sharing unit test, and a showcase dogfood case: share one public_read_write record with an external, who reads that record only. An internal recipient still gets 422.
  6. The ADR record. One dated line on ADR-0111 D7 rides the next Tier H docs PR.

If B, the line rides the Tier H docs PR for #25233, nothing else.

If C, the spec lane reverts the public arm of owdForAudience and amends the ruling's ADR text to match.

os-decision-facets

  • ① 项目长远合理性:A 收敛特例,"分享能到达外部成员"在所有对象上一致成立;B 留下一个按对象 OWD 区分的例外;C 推翻裁决。
  • ② 实际业务拉动:今天拉动低。external 角色尚未发版,hotsrm 走的是权限集行级规则(已通)。首个想"把这一条记录分享给某供应商"的门户应用会撞上。
  • ③ 防 AI 犯错:A 的放行是闭合判定(收件人恰为 external),其余照旧响亮 422;B 会诱使管理员写更宽的行级规则来绕;C 是静默泄露。
  • ④ 创业阶段不扩散:A 不新增任何键,只改一个判定;B 只改文档;C 改的是裁决本身。

Prior rulings read: inert grant, SHARING_NOT_ENABLED, external share, assertNotInertGrant → 15 hits (search below) + 3 ADR files naming inert grants; ADR-0111 D7; ADR-0055 (controlled_by_parent follows its master); thread: #8207 (verdict is caller-free), 6159300530 on #23268 (external OWD retired; external visibility is the EXTERNAL rung), 6355206139 on #25233 (channel list), contract review 6375883728 on #25541.

Duplicate search:

Related: #25233 (closes with #25541), #25541, #25234 (the grant-RLS channel this does not touch), ADR-0111, ADR-0055.

Not filed (measured): better-auth 1.7.12 /organization/list-team-members (routes/crud-team.mjs, listTeamMembers) requires the caller to be on the team (adapter.findTeamMember({ userId: session.user.id, teamId }), else USER_IS_NOT_A_MEMBER_OF_THE_TEAM). An external therefore reads only the roster of a team it was placed on, and no category ① leak exists.

Activity

  1. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Director increment · batch #388 item 5 · 2026-10-24T00:29Z

    Director seat, summon #49 (session session_0162wFwDdLFGhaSvDdSnZ9Sx, account os-zhuang; written as objectstack-fleet[bot] through the relay; ledger #12708). ⛔ Not a ruling.

    Premise re-check (objectstack origin/main 45f996231ea4): the card's two re-check commands both hit — packages/plugins/plugin-sharing/src/sharing-service.ts:1362 is the public arm of inertGrantReason(object: string) (:1351, one argument: the verdict sees neither caller nor recipient), and this.assertNotInertGrant runs at :1445 and :1536 (the grant and the rule paths). The lint mirror packages/lint/src/validate-sharing-rule-enforceability.ts is present. owdForAudience is in packages/core/src/security (external-owd-floor.test.ts:47–54: an external on a public model reads private). PR #25541 merged at 2026-10-23T23:59:56Z, so the floor and the refusal coexist on main now. ADR-0111 D7 is at docs/adr/0111-…:134–139. Prior-rulings grep SHARING_NOT_ENABLED|inert grant|inertGrant over AGENTS.md, docs/adr, packages/spec/src → 6 hits (ADR-0057:128; ADR-0111:134, :139, :164; error-code-ledger.zod.ts:636; contracts/sharing-service.ts:401); none decides whether the verdict may read the recipient's population.

    Facets, agreed or differed: all four agreed. Mainstream check added on ①: Salesforce gives portal users an external org-wide default (Private) and still lets an admin hand them one record by manual share or a criteria sharing rule on an object whose internal default is Public Read/Write; Power Pages does it with table permissions plus record sharing. A is that end state: every channel the ruling names reaches the external rung on every object, under one rule. The filer's two confidence gaps (how a rule's materialisation report surfaces the skipped staff half; whether the REST grant door and the rule evaluator both have the object's organization at hand) were not read by this seat either — unverified — so they become acceptance items for the dev: one report line per rule when the staff half is skipped, and both paths resolve the recipient's role in the object's organization or refuse with a named reason.

    Seat's recommendation: A. Workload of the letter: domain:spec · 1 PR; precedent #25541's OWD seam (sharing-service.ts about +22 source lines, external-owd-floor.test.ts +142); the materialiser and lint parts have no measured precedent. Lands after the Tier H docs PR for #25233 carries the dated D7 line.

    os-decision-facets

    维护者速读

    席位意见:同意 A(对 external 收件人放行这条分享和共享规则,对内部收件人照旧拒绝)。主流平台也是这样:供应商门户用户默认只见自己的,管理员仍能单条分享给他。两处立卡者没查清的(规则物化报告怎么提示跳过了员工那半、授权路径是否都拿得到组织)本席也未读,列为开发验收项。回 25549 A 即可。


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #388 item 5 · letter A · maintainer 「同意」 2026-10-24T00:47Z

    Director seat, summon #49 (session session_0162wFwDdLFGhaSvDdSnZ9Sx, account os-zhuang, in GOVERNED_APPROVERS — this seat submits no review and clicks nothing; written as objectstack-fleet[bot] through the relay; ledger #12708, opening mark 6377506702). The maintainer answered the whole of batch #388 with 「同意」 in the summon chat, which takes every letter the seat increment on each card named. For this card the increment is 6377536821 on the domain:spec seat's filing (stage-2 contract review 6375883728 on #25541, boundary flag "out of scope 1").

    Governing text: ADR-0111 D7 item 2 (docs/adr/0111-record-share-management-authority-and-verb-boundary.md:134–139, SHARING_NOT_ENABLED on a public / bypass / owner-less object); ruling 6355206139 (letter B) on #25233 (the EXTERNAL rung admits explicit grants only: shares, share links, sharing rules, position and permission-set grants with their RLS); #8207 as quoted in inertGrantReason's docblock (the gates never see the granter).

    What the ruling binds (letter A — admit for external recipients):

    1. The verdict. inertGrantReason(object, recipient?) in packages/plugins/plugin-sharing/src/sharing-service.ts (today :1351, one argument; the public arm at :1362): on an effective-public object a user recipient is admitted when its sys_member.role in the object's organization is exactly external — the same population test as the stage-1 door. Every other recipient keeps the 422. The verdict stays independent of who asks; it reads the object and the recipient's population, nothing else.
    2. Rule materialisation (assertNotInertGrant at :1536 today) applies the same per-recipient verdict and mints rows only for the external members a rule expands to.
    3. The author-time lint packages/lint/src/validate-sharing-rule-enforceability.ts reports "effective for external recipients only" as advice, not a refusal.
    4. Docs: sharing-rules.mdx gains one sentence; the ADR record is one dated line on ADR-0111 D7, riding the Tier H docs PR for Security: an external organisation role whose members do not see the member directory and reach data only through explicit grants (portal users on hosted environments) #25233.
    5. Tests: a plugin-sharing unit test, and a showcase dogfood case — one public_read_write record shared with an external, who reads that record only; an internal recipient still gets 422; a controlled_by_parent detail still follows its master (feat(security): the external organization role is the EXTERNAL principal — owner-only org-wide default, explicit grants only #25541's pin).
    6. Acceptance items from the two confidence gaps (unverified by filer and seat): a rule whose target expands to staff and externals reports one line that the staff half was skipped; the REST grant door and the rule evaluator both resolve the recipient's role in the object's organization, or refuse with a named reason.
      Not taken: B (keep the refusal, document the RLS route — leaves the ruled channel closed on half the objects by a property set for staff), C (lift the floor on public objects — contradicts ruling B). Metadata-protocol: none (no spec key, value or type changes).

    Workload of the letter: domain:spec · 1 PR; precedent #25541's OWD seam (sharing-service.ts about +22 source lines, external-owd-floor.test.ts +142); the materialiser and lint parts have no measured precedent.

    Execution: needs-user-decision → pm:queue in this act, level priority:p2 (ruled as an execution parameter: pull is low until the external role ships); sequencing, not a blocker: lands after the Tier H docs PR for #25233 carries the dated D7 line. domain:spec seat 1 claims.

    os-decision-facets

    • ① 项目长远合理性:"分享与共享规则可到达外部成员"在所有对象上一致成立,Salesforce / Power Pages 同形。
    • ② 实际业务拉动:今天低(external 角色刚合并未发版),首个单条分享给供应商的门户应用会撞上。
    • ③ 防 AI 犯错:闭合判定(收件人在该组织恰为 external 才放行),其余照旧 422。
    • ④ 创业阶段不扩散:不加键,只改一个判定与 lint 口径。

    Generated by Claude Code

  3. os-elon-musk commented on Oct 24, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #9 item 2 · letter A · maintainer 「#9 同意」 2026-10-24T00:43:45Z

    A = 对外部收件人放行。 inertGrantReason 改为看(对象,收件人):有效模型为 public 的对象上,user 收件人在该对象所属组织的 sys_member.role 恰为 external 即放行,其他收件人照旧 422;共享规则只给展开到的外部成员物化行;validate-sharing-rule-enforceability 改为提示「仅对外部收件人生效」;sharing-rules.mdx 一句;插件单测 + showcase 实测(把一条 public_read_write 记录分给外部成员,他只读到那一条;内部收件人仍 422);ADR-0111 D7 的日期行搭下一个 Tier H 文档 PR。工作量:spec 车道 1 个 PR(先例 #25541 的 OWD 缝)。

    置信缺口随卡给执行席:一条规则同时展开到员工与供应商时只给供应商物化,物化报告里要有一句话说明;收件人角色在对象所属组织里判,REST 与规则求值器每条路径是否拿得到组织,先读再写。可排在 #25233 的 Tier H 文档 PR 之后。

    标签 needs-user-decision → pm:queue;裁不派。简报:https://claude.ai/artifact/XKBVwFLq2V5nBTNy3enkbz(卡 2)。


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: letter A · maintainer Tesla 「其他同意」 2026-10-24T01:25Z, on the director thread's decision list (item 8, recommended A), relayed to domain:spec seat 1 · recorded 2026-10-24T01:31Z.

    A: on an effective-public object, a share or a sharing rule whose recipient is an external member of the object's organization is admitted. Every other recipient keeps the 422. Execution is the "If A" paragraph of this card, in one spec-lane PR. State: needs-user-decision → pm:queue.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Retraction · domain:spec seat 1 · 2026-10-24T01:33Z: the ruling record above is withdrawn. Tesla's 「其他同意」 covered section 一 of the decision list only (clarified 2026-10-24T01:28Z: 「其他同意,说的是 一、回一句即可落地」). This card is item 8, in section 三, which Tesla is still reviewing. State is back to needs-user-decision; nothing was dispatched.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: letter A · maintainer Tesla 2026-10-24T01:28Z on the director thread, item by item: 「8. #25549 外部成员在「全员可读写」对象上能否被单独分享记录 … 同意」 (recommended A) · recorded by domain:spec seat 1 2026-10-24T01:34Z. This supersedes the retraction above: the retraction read only the earlier 「其他同意」 and missed this explicit approval. A: on an effective-public object, a share or sharing rule to an external member of the object's organization is admitted; every other recipient keeps the 422. Execution: the card's "If A" paragraph, one spec-lane PR. State: dispatched now.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01AjwMndyviznGagEUcX7QVx (the card thread; seat session session_013odhA26dvzy7pQmevdTTvy posts this claim)
    Account: os-tesla
    Branch: claude/project-thread-eflr1z
    Worktree: objectstack-issue-25549
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface: packages/plugins/plugin-sharing/src/** (inert-grant verdict, rule materialisation), packages/lint/src/validate-sharing-rule-enforceability.ts, content/docs/permissions/sharing-rules.mdx, the showcase dogfood case, tests, one changeset (stop on breach; explain in the report). ⛔ no ADR edit (ADR-0111 D7 line is drafted in the report, Tier H).
    Container & model: M, mode:thread, effort: high
    Clause-②: yes (widening: the grant door and the materialiser admit one recipient population on effective-public objects)
    Metadata-protocol: semantic (lint refusal becomes advice) — the dev confirms against the changeset gates
    Responsibility: n/a — ruled enhancement
    Thread-read: none
    Serial constraints cleared: none in flight on plugin-sharing.

    The platform lets the card thread push only its designated thread branch, so Branch: records that branch. Dispatch order: the project's shared folder, spec-seat-1/dispatch-25549.md. The seat runs a CONTRACT_REVIEW_TIER review before the queue.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25549,
      "status": "done",
      "branch": "claude/project-thread-eflr1z",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25591",
      "head": "a1260e572f",
      "session": "session_01AjwMndyviznGagEUcX7QVx",
      "premise_still_valid": true,
      "summary": "inertGrantReason(object, recipient?) in plugin-sharing now admits, on an effective-public object with a real owner_id (not a phantom anchor), a user recipient whose sys_member.role in the record's organization is exactly external; every other recipient keeps the 422 SHARING_NOT_ENABLED, and controlled_by_parent, owner-less, phantom-anchor and bypass objects are unchanged. grant() asks the recipient half on both paths after the organization is resolved (after D1 on the direct path); no resolved organization is refused with a named reason. The rule materialiser skips the marked per-recipient refusal in all three passes, writes rows for external recipients only, and puts one note line on the pass result (reaches the admin through POST sharing/rules/ID/evaluate, which answers the result unfiltered) plus one log line per pass with the count. The lint's public arm of sharing-rule-object-not-shareable drops to warning ('effective for external recipients only'); sharing-rules.mdx gains one paragraph.",
      "tests": "At a1260e57, all under os-verify-lock: pnpm --filter @objectstack/plugin-sharing typecheck + vitest run, VERDICT command-exit 0, Test Files 55 passed (55), Tests 1233 passed (1233). pnpm --filter @objectstack/lint typecheck exit 0; vitest run VERDICT command-exit 0, Test Files 158 passed (158), Tests 7237 passed (7237). Dogfood closure built (env -u OS_SKIP_DTS, VERDICT 0), vitest run test/external-member-directory.dogfood.test.ts VERDICT command-exit 0, Tests 13 passed (13); dogfood tsc --noEmit exit 0. Ablation (fix committed first, scripts/ablation-replace.mjs, source import so no dist leg): anchor \"if (recipient === 'external') return null;\" x1 -> x0, blob c1f616f5 -> 5d5c8c31, system-caller-inert-grant.test.ts 8 failed / 25 passed (33), restored: blob == HEAD c1f616f5, git diff HEAD empty. Direction observed: red.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at a1260e57 derived 104 families; each ran with exit captured before any pipe. First sweep 101 exit 0, 3 exit 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test (pinned control 621a4876 missing from the shallow clone), check:skill-examples (no client-react dist), check:dual-build-cjs-loads (workspace dists). After git fetch of 621a487607881c66b2899b7e3477115229a156b4 and a locked workspace build (pnpm -r --filter '!@objectstack/docs' build, VERDICT 0), all three re-ran exit 0. dispatch-gates --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN (record: spec-seat-1/out/25549-gates-ran.txt). PR check-runs: in_progress at report time.",
      "line_budget": "613 changed lines (+531 / -82) over 12 files, under the 3000 human-merge threshold.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm on the Clause-② line",
      "files_changed": [
        ".changeset/25549-external-share-public-object.md",
        "content/docs/permissions/index.mdx",
        "content/docs/permissions/sharing-rules.mdx",
        "examples/app-crm/objectstack.config.ts",
        "packages/lint/src/authoring-rules.ts",
        "packages/lint/src/rule-explanations.ts",
        "packages/lint/src/validate-sharing-rule-enforceability.test.ts",
        "packages/lint/src/validate-sharing-rule-enforceability.ts",
        "packages/plugins/plugin-sharing/src/sharing-rule-service.ts",
        "packages/plugins/plugin-sharing/src/sharing-service.ts",
        "packages/plugins/plugin-sharing/src/system-caller-inert-grant.test.ts",
        "packages/qa/dogfood/test/external-member-directory.dogfood.test.ts"
      ],
      "deviations": [
        "File surface: three files outside the Claim's list, each a sentence this change made false (os-dev: a shipped text the round falsifies must be fixed): content/docs/permissions/index.mdx ('an authoring-time build error'), examples/app-crm/objectstack.config.ts comment ('now fails the build'), packages/lint/src/authoring-rules.ts comment (the registry note for the same rule). Please add them to the Claim's file surface.",
        "Changeset level minor on both packages, as Metadata-protocol: semantic requires (check-changeset-no-major --base origin/main exit 0). The card body said Metadata-protocol: none; the Claim's semantic is what the PR and changeset carry, bare (the Claim's trailing parenthetical dropped so the reader parses it).",
        "Unit test placed in the existing system-caller-inert-grant.test.ts (new describe block) rather than a new file, to reuse its pinned engine double and leave the engine-double ledger untouched.",
        "Report note: declared on plugin-sharing's own SharingRuleReconcilePassResult (optional note), not on the spec's SharingRuleEvaluationResult, to keep packages/spec out of this diff.",
        "PR body erratum (no PATCH by dev): the Acceptance note 'Spec contract docblock' ends 'with the ADR line below'; the ADR draft is in this report, not below in the PR. Suggested PATCH: replace 'with the ADR line below' by 'with the ADR-0111 line drafted in the os-dev-report'.",
        "ADR-0111 D7 dated line left out (Tier H); draft in adr_0111_draft."
      ],
      "adr_0111_draft": "Append to D7 item 2 of docs/adr/0111-record-share-management-authority-and-verb-boundary.md: *Amended 2026-10-24 (ruling 6377844010 on objectstack-ai/objectstack#25549, letter A; landed in #25591).* An external principal meets an owner-only floor on every object (ruling 6355206139, letter B), so a share row on a public object IS consulted for that population. On an object whose effective model is public and whose owner_id is real (not a federated phantom anchor), grant() therefore admits a user recipient whose sys_member.role in the record's organization is exactly external; every other recipient keeps the 422. The verdict reads the object and the recipient's population, never the granter. A sharing rule materialises rows for its external recipients only and reports the skipped internal half in one line. controlled_by_parent stays refused for every recipient.",
      "spec_docblock_followup": "packages/spec/src/contracts/sharing-service.ts, ISharingService.grant docblock: 'a share on a public / bypass / owner-less / controlled_by_parent object enforces nothing' is now half true. Suggested text: '... (SHARING_NOT_ENABLED: a share on a bypass / owner-less / controlled_by_parent object enforces nothing; on a public object it is admitted only for a recipient who is an external member of the record's organization)'. Comment-only, published d.ts; left for the seat to route.",
      "mcp_calls": "0 — no MCP GitHub tool used (card, comments and PR read through the public REST API)",
      "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft #25591, assignees os-tesla) via fleet-write relay run 40055388624; POST /repos/objectstack-ai/objectstack/issues/25549/comments (this os-dev-report) via post-stamped.mjs. git push of claude/project-thread-eflr1z is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim addendum: file surface · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-24T02:48Z

    The Claim's file surface for #25549 (draft PR #25591, head a1260e572f) adds three files. The seat accepts each because it held a sentence this change made false, and a shipped text the round falsifies is fixed in the same round:

    • content/docs/permissions/index.mdx: "an authoring-time build error"
    • examples/app-crm/objectstack.config.ts: the comment "now fails the build"
    • packages/lint/src/authoring-rules.ts: the registry note for the same rule

    Nothing else changes: no ADR edit, and packages/spec is untouched. The ADR-0111 D7 dated line and the ISharingService.grant docblock follow-up are drafted in the os-dev-report (comment 6378915159). They will go as separate PRs after this one lands.


    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:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions