Skip to content

docs(permissions,protocol): owner-type sharing rules are rejected at parse, not skipped - #9904

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9891-owner-sharing-docs
Aug 19, 2026
Merged

docs(permissions,protocol): owner-type sharing rules are rejected at parse, not skipped#9904
os-steve merged 1 commit into
mainfrom
claude/issue-9891-owner-sharing-docs

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9891

Two docs pages taught type: 'owner' / ownedBy sharing rules as authorable. They were removed from SharingRuleSchema in v17 (#1878) rather than left declared-but-skipped, so they do not parse at all.

H1 — the removal, measured before rewriting prose about it

The card's exact example block, fed to the built SharingRuleSchema from packages/spec/dist/security/index.mjs:

success: false
---
code: invalid_value
path: ["type"]
message: Invalid input: expected "criteria"
---
code: invalid_union
path: ["condition"]
message: Invalid input
---
code: unrecognized_keys
path: []
message: Unrecognized key(s) on this sharing rule: `ownedBy`.
- `ownedBy` belongs to the removed `owner`-type sharing rule - it depends on live team/position
membership, which the static materialiser cannot track, so it was removed from the authoring
surface (ADR-0078). Only `criteria` rules are authorable; express membership-shaped access via
RLS dynamic membership (7.3.1) or business-unit depth scopes (ADR-0057).

Three issues, not one. Note the schema's own guidance message cites ADR-0078, while both docs pages and the v17 release note cite ADR-0049 — the ADR-0078 "validates but silently does nothing is an authoring trap" framing is the one the runtime actually prints, so the prose here cites it.

Ruling 1 — "skipped" and "rejected" are different author instructions

Both pages now say which one applies, in as many words. The protocol page carried the pre-v17 state verbatim ("the seed bootstrap skips them (logged)"); it now states that a skipped rule is still authorable and is ignored, whereas a removed one is rejected by the schema. The permissions-matrix callout was already correct on "no longer parse" and gained the same contrast sentence.

The still-accurate half of the old ADR-0049 sentence is kept: a condition the compiler cannot lower is skipped and logged, never seeded as a permissive match-all. Only the owner-rule half was stale.

H2 — OwnerSharingRuleSchema does not exist

Not "exists but unreachable" — the symbol is absent from the repo entirely.

  • grep -rn "OwnerSharingRuleSchema" over packages/ apps/ examples/ scripts/ returns zero hits; the only occurrence in the tree was the doc line this PR removes.
  • It is absent from packages/spec/api-surface/security.json, which lists CriteriaSharingRuleSchema (const), SharingRuleSchema (const), SharingRuleType (type) and no owner variant.
  • At runtime, Object.keys(spec/security) gives CriteriaSharingRuleSchema, OWDModel, ShareRecipientType, SharingLevel, SharingRuleSchema, SharingRuleType, defineSharingRule.

Verdict: the protocol reference must stop naming it. The section now points at CriteriaSharingRuleSchema and states that SharingRuleSchemais that schema. No finding to file — there is no live-but-unauthorable export here.

H3 — the sweep found four spots, not two

Sweeping content/docs/** for type: 'owner', ownedBy, OwnerSharingRuleSchema and pre-v17 "skipped (logged)" enforcement callouts: 7 occurrences across 4 files, of which 6 across 2 files were defects and are fixed here.

LocationStateAction
permissions-matrix.mdx introclaimed "a discriminated union with two type values - owner and criteria"fixed - SharingRuleType.options is ["criteria"], one member
permissions-matrix.mdx table row"Owner-Based / [experimental - not enforced] / skipped at seed time"row dropped - it contradicted the page's own callout four lines below
permissions-matrix.mdx examplethe type: 'owner' blockrewritten in the criteria form
protocol/objectql/security.mdx:375recipient comment user | group | position | unit_and_subordinates | guestfixed - group and guest do not parse; team and business_unit were missing
protocol/objectql/security.mdx Owner-Based sectionYAML example + OwnerSharingRuleSchemareplaced
protocol/objectql/security.mdx enforcement calloutpre-v17 "skipped (logged)"repaired
permissions/sharing-rules.mdx:234correctuntouched, and is the wording both pages converge on (ruling 2)

The two extra spots (the intro's union claim and the :375 recipient comment) are the same defect class as the card — docs teaching sharing vocabulary that no longer parses — and their correct form is pinned by the enum in packages/spec/src/security/sharing.zod.ts, measured directly:

ShareRecipientType enum: ["user","team","position","unit_and_subordinates","business_unit"]
group parses: false guest parses: false
team parses: true business_unit parses: true

content/docs/releases/v17.mdx is the fourth file; it is correct and is release-notes territory. Not touched.

H4 — the replacement examples grant something

Every rule shipped in this PR was fed through SharingRuleSchema.safeParse and parses. Parsing was not the bar, though — the neighbouring card established that a rule on a public_read_write object parses and grants nothing. Checked at source rather than from the docs table: effectiveSharingModel in packages/plugins/plugin-sharing/src/sharing-service.ts maps public_read_write and controlled_by_parent to 'public', and buildWriteFilter's own comment states the rule —

applies to BOTH private and read (public_read) models - public_read is read-open but write-owned; only a fully public object is write-open.

So buildReadFilter returns null unless the model is private, and buildWriteFilter returns null only when it is public. Postures of the examples:

  • opportunity is private (the page's own OWD table) - both gates apply, the read rule widens the read filter. Grants.
  • account on permissions-matrix.mdx is public_read - read is already universal there, so the rule is accessLevel: 'edit', which is what opens the write gate to non-owners. Grants.
  • account on the protocol page is private, declared in that page's own preceding account.object.yml block. Grants.

Because the matrix example's second rule only grants by virtue of its edit level, the page gained a callout making the posture requirement explicit rather than leaving the reader to rediscover it.

Scope

No changeset — docs-prose only, nothing user-visible ships. check:docs-audit-scope stayed at its existing count (179 hand-written docs); no .claude/ regeneration was needed, so the #9866 collision was not reached. Nothing under content/docs/releases/, docs/adr/, .claude/, skills/, AGENTS.md or CLAUDE.md was touched. The adjacent in-flight pages permissions/authorization.mdx and permissions/index.mdx are out of scope here and were not edited.

Gates

Run at d237370527, the final commit, after a full pnpm --filter @objectstack/spec build. Gate set derived from the changed paths with node scripts/pm/dispatch-gates.mjs, all 11 green:

HEAD at union run: d237370527
check:docs-audit-scope exit=0
check:doc-anchors exit=0
check:docs-redirects exit=0
check:role-word exit=0
check:published-readme-links exit=0
check:nul-bytes exit=0
check:cross-package-test-inputs exit=0
spec:check:empty-state exit=0
spec:check:liveness exit=0
spec:check:strictness-ledger exit=0
spec:check:variant-docs exit=0
UNION_ALL_GREEN=yes

Generated by Claude Code

…parse, not skipped
`type: 'owner'` / `ownedBy` were removed from `SharingRuleSchema` in v17
(#1878) rather than left declared-but-skipped (ADR-0049/ADR-0078). Two pages
still taught them as authorable, and the protocol reference still described
the pre-v17 "seed bootstrap skips them (logged)" state — a materially
different instruction to an author than "does not parse".
permissions-matrix.mdx
- the intro claimed a discriminated union with two `type` values; `SharingRuleType`
is a one-member enum and `SharingRuleSchema` IS `CriteriaSharingRuleSchema`
- dropped the "Owner-Based / [experimental — not enforced] / skipped at seed time"
table row, which contradicted the page's own enforcement callout four lines below
- the Configuration Example's second rule is now the enforced `criteria` form
- added an OWD-posture callout: a rule on a `public_read_write` object parses and
grants nothing
protocol/objectql/security.mdx
- the recipient enum comment listed the removed `group`/`guest` and omitted the
enforced `team`/`business_unit`
- replaced the "Owner-Based Sharing" section and its `OwnerSharingRuleSchema`
reference (no such export exists) with the removal note and a criteria-form
rewrite of the same rule
- repaired the enforcement callout, keeping the still-accurate half (an unlowerable
`condition` is skipped and logged, never seeded as a match-all)
Wording converges on content/docs/permissions/sharing-rules.mdx:234.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 19, 2026
@os-steveos-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 19, 2026 — with Claude
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT. The ADR flag resolves to "both, for different claims" — and your prose already assigns them correctly. Arming.

Verified at d237370527: 2 files, +47/-17, GOVERNED_HITS=NONE, no non-green gates. check:docs-audit-scope held at 179 hand-written docs — ruling 5 satisfied, no #9866 collision.

The ADR citation you flagged — checked, and there is nothing to reconcile

You raised that the schema's ownedBy guidance cites ADR-0078 while the docs and the v17 note cite ADR-0049, and asked whether the citation should go the other way. I read both on origin/main. They are not competing citations for one fact — they govern different halves:

ADRgoverns
ADR-0049 (enforce-or-remove)why the property no longer exists — declared-but-unenforced keys get removed rather than left inert
ADR-0078 (unknown-key strictness, permission.zod.ts:29, :400, :537)why passing it now produces an error instead of being silently stripped — the .strict() gate, the inverse-drift class

"a stale definition fails loudly at authoring time" is an ADR-0078 claim. "the property was removed at all" is an ADR-0049 claim.

And your shipped prose already draws exactly that line — ADR-0078 on the fails-loudly sentence, ADR-0049 on the separate claim that an unlowerable condition is never seeded as a permissive match-all. Two different facts, two correct citations, no drift.

Ruling: keep it as written. No follow-up card. Raising it was still right — a citation split across a schema/docs boundary is usually drift, and checking cost me two greps.

H1 — the parse error is three issues, not one, and that matters

[invalid_value] ["type"] Invalid input: expected "criteria"
[invalid_union] ["condition"] Invalid input
[unrecognized_keys] [] Unrecognized key(s) on this sharing rule: `ownedBy`.

Run against a freshly builtpackages/spec under the lock, so no stale dist. The third issue is the one that carries the schema's own guidance — which is precisely why the author-facing prose should cite what the runtime actually prints. And you fed every replacement rule back through safeParse: all parse, the retired form rejects. Docs that assert a runtime behaviour, verified against that runtime rather than against the previous docs.

H2 — the absence is stronger than the case the card anticipated

The card expected the "live-but-unauthorable export" case. Measured, OwnerSharingRuleSchema is absent from the tree entirely — zero grep hits across packages/ apps/ examples/ scripts/, absent from packages/spec/api-surface/security.json, and the built module exports exactly CriteriaSharingRuleSchema, OWDModel, ShareRecipientType, SharingLevel, SharingRuleSchema, SharingRuleType, defineSharingRule.

So correctly no finding filed — there is no orphaned export to report. Repointing the protocol reference at CriteriaSharingRuleSchema is the right substitution, and it is a real name rather than a plausible one.

H3 — 7 occurrences / 4 files, not the 2 the card named

The two extras are both the same defect wearing different clothes, and neither was in the card:

  • the matrix intro claimed a discriminated union with two type valuesSharingRuleType.options is ["criteria"], one member;
  • security.mdx:375's recipient comment listed the removed group/guest and omitted the enforced team/business_unit — measured false/false vs true/true against ShareRecipientType.

That second one is the more expensive direction: a reader is told an enforced recipient type does not exist. Taking both in place under the bounded exemption is right — same defect class, correct form pinned by the enum in sharing.zod.ts, no other claim on those files, same gate family, and named with evidence in the PR body.

Remaining two occurrences are the correct sharing-rules.mdx:234 and releases/v17.mdxuntouched, and the release page correctly left alone.

H4 — you verified from the source, not from the docs table you were fixing

buildWriteFilter's own comment: "applies to BOTH private and read (public_read) models — public_read is read-open but write-owned; only a fully public object is write-open."

Hence account on the matrix page is public_read, so the rule carries accessLevel: 'edit' — the level that actually opens the write gate. Deriving the posture rule from the service that implements it, rather than from the permissions table you were in the middle of correcting, is the only way that check means anything.

And the added callout making the OWD-posture requirement explicit is the part I most wanted: #9698 established that a rule on a public_read_write object parses fine and grants nothing. Without that callout this page would have shipped a parseable, inert example — a fresh instance of the exact family this card belongs to.

Ruling 1 — satisfied on both pages, in the words that matter

"Skipped" and "rejected" are different instructions to an author.

Both pages now say it outright, and converge on sharing-rules.mdx:234 nearly verbatim per ruling 2 rather than inventing a third phrasing.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 19, 2026 07:00
@os-steve
os-steve enabled auto-merge August 19, 2026 07:00
@os-steve
os-steve added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 23502e3Aug 19, 2026
25 checks passed
@os-steve
os-steve deleted the claude/issue-9891-owner-sharing-docs branch August 19, 2026 07:28
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: two pages still teach owner-type sharing rules (type: 'owner' / ownedBy), which have not parsed since v17

1 participant

@os-steve