Repository navigation
Commit 34d144c
docs(adr-0138): the guest reaches rows through share links only, never the object's sharing model (D3 item 6, ruling C) (#23326)
Fixes #23266
Clause-②: no
This executes item 3 of the maintainer's ruling C on #23266 (comment
`6148183570`, 「同意」, on the director seat's reading `6148041259`).
ADR-0138 D3 item 6 drops "through the object's declared sharing model
and". It now says the guest reaches rows through share links (and, when
it exists, an explicit guest sharing rule), never through the object's
sharing model. ADR-0090 D9 and D11 are unchanged. The PR has no code
path: E1's code is PR #23321.
**Widened before approval.** On the seat's answer `6153761503` (option
A), two more passages of the same record now match ruling C as well: the
Consequences bullet and item 3 of "What the ruling did not settle"
(sections 4 and 5 below). This lets the one Tier H approval cover a
record that does not contradict itself.
**Tier H** (`docs/adr/**`). This PR stays a draft until the maintainer
approves it. Before that approval no seat readies it, queues it or arms
auto-merge.
Drafted by the `os-dev` agent that `domain:engine` seat 1 dispatched
(`session_01P88ehrZ6hM19hBzdpsQTTo`), on the claim `6153293163`.
## The change: one file, +28 / −11
`docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md`
changes in four places:
- D3 item 6 (sections 1 and 2);
- one new line continuing the status line (section 3);
- the Consequences bullet "The guest is one principal on one pipeline."
(section 4);
- item 3 of "What the ruling did not settle" (section 5).
Nothing else in the file changes. The "Decided by" block is left as it
is, and ADR-0090 is untouched.
### D3 item 6, exact before and after
Before:
```text
6. **Row scope: one pipeline.** The guest's grant is evaluated by the same row-level pipeline as
every caller, with no guest special case. The guest owns nothing (D1), so it reaches rows only
through the object's declared sharing model and through share links — ADR-0090 D9's division of
labour (the guest position answers which object classes are reachable at all, share links answer
which records), and D11's statement that the guest is not an OWD audience. Card E1 pins the
guest's row scope for each sharing model before it lands; a result that departs from that
division of labour is that card's finding to raise, not a choice to make silently.
```
After:
```text
6. **Row scope: one pipeline.** The guest's grant is evaluated by the same row-level pipeline as
every caller, except that the guest never takes the pipeline's sharing-model (OWD) branch:
neither `sharingModel` nor `externalSharingModel`. The guest owns nothing (D1), so it reaches
rows only through share links (and, when it exists, an explicit guest sharing rule), never
through the object's sharing model — ADR-0090 D9's division of labour (the guest position
answers which object classes are reachable at all, share links answer which records), and D11's
statement that the guest is not an OWD audience. Card E1 pins the guest's row scope for each
sharing model before it lands: with a bound read set and no share link, `private`,
`public_read`, `public_read_write` and `controlled_by_parent` each read zero rows. This scope is
the maintainer's ruling C on [#23266](#23266)
([`6148183570`](#23266 (comment))),
which answered the finding E1 raised under this item; the explicit guest sharing rule is named
by that ruling and not built.
```
### 1. The ruled sentence (ruling item 3)
Before:
> The guest owns nothing (D1), so it reaches rows only through the
object's declared sharing model and through share links — ADR-0090 D9's
division of labour (…), and D11's statement that the guest is not an OWD
audience.
After:
> The guest owns nothing (D1), so it reaches rows only through share
links (and, when it exists, an explicit guest sharing rule), never
through the object's sharing model — ADR-0090 D9's division of labour
(…), and D11's statement that the guest is not an OWD audience.
### 2. Consistency with ruling C, beyond the ruled sentence (both inside
D3 item 6)
**2a. The first sentence of item 6.** Under C, "with no guest special
case" is no longer true. E1's implementation (PR #23321,
`SharingService.buildReadFilter` in `plugin-sharing`) returns deny-all
for the guest before the sharing-model branch, so the row-level
evaluator now has a guest branch. The new wording takes ruling item 1's
words ("the guest does not take the OWD branch (neither `sharingModel`
nor `externalSharingModel`)").
Before:
> The guest's grant is evaluated by the same row-level pipeline as every
caller, with no guest special case.
After:
> The guest's grant is evaluated by the same row-level pipeline as every
caller, except that the guest never takes the pipeline's sharing-model
(OWD) branch: neither `sharingModel` nor `externalSharingModel`.
**2b. The last sentence of item 6.** It told E1 to raise any departure
from the division of labour. E1 raised one, and ruling C answered it.
The pin now states what ruling item 1 states, and the sentence records
which ruling set the scope.
Before:
> Card E1 pins the guest's row scope for each sharing model before it
lands; a result that departs from that division of labour is that card's
finding to raise, not a choice to make silently.
After:
> Card E1 pins the guest's row scope for each sharing model before it
lands: with a bound read set and no share link, `private`,
`public_read`, `public_read_write` and `controlled_by_parent` each read
zero rows. This scope is the maintainer's ruling C on #23266
(`6148183570`), which answered the finding E1 raised under this item;
the explicit guest sharing rule is named by that ruling and not built.
### 3. The amendment record (ADR convention, outside item 6)
When a later ruling amends an accepted record, the repository records it
as a dated `· **Amended** (DATE, ruling …) — …` line continuing the
record's status line. ADR-0087 and ADR-0043 show the form for a ruling
on the record itself, and ADR-0090's line 4 shows it for an amendment by
another record. Prime Directive #13 names "an amended status line on the
old one" as the way a recorded decision changes. One line follows
ADR-0138's Status paragraph:
> · **Amended** (2026-10-13, ruling C on #23266, `6148183570`,
maintainer 「同意」) — D3 item 6: the guest reaches rows only through share
links (and, when it exists, an explicit guest sharing rule), never
through the object's sharing model; ADR-0090 D9 and D11 are unchanged.
### 4. Consistency with ruling C: the Consequences bullet (seat answer
`6153761503`)
The bullet said that no guest special case enters the row-level
evaluator. Under C, E1's row-level evaluator has exactly one: the
deny-all ahead of the sharing-model branch in
`SharingService.buildReadFilter`. The edit names that one case and keeps
the bullet's CRUD-gate and masker claims word for word.
Before:
```text
- **The guest is one principal on one pipeline.** No guest special case enters the row-level
evaluator, the CRUD gate or the masker; what differs for the guest is what it holds (D3) and where
it acts (D4), both resolved before any gate runs.
```
After:
```text
- **The guest is one principal on one pipeline.** The row-level evaluator carries one guest special
case: the guest never takes its sharing-model (OWD) branch, so it reaches rows only through share
links (and, when it exists, an explicit guest sharing rule; D3 item 6). No guest special case
enters the CRUD gate or the masker; what differs for the guest there is what it holds (D3) and
where it acts (D4), both resolved before any gate runs.
```
**What was checked, against E1 at `1d394b81c3`** (PR #23321's head at
this read; diff taken from its merge base `01d503432b`):
- **Row-level evaluator.** The one guest branch is in
`packages/plugins/plugin-sharing/src/sharing-service.ts`:
`isGuestPrincipal(context)` returns deny-all in `buildReadFilter`, ahead
of `effectiveSharingModel`. That is the case the new sentence names.
- **Masker: kept, true.** E1 does not touch
`packages/plugins/plugin-security/src/field-masker.ts`, and that file
has zero mentions of `guest` or `principalKind`.
- **CRUD gate: kept, under the bullet's own reading.** The permission
evaluation has no guest branch: once the sets are resolved, the guest is
judged like any caller. E1 does add two guest-specific steps to the
gate's prologue in `security-plugin.ts`, and both are the bullet's "what
it holds (D3) and where it acts (D4)":
- `resolvePermissionSetsForContextUnmemoized` leaves the guest's
position out of the set request. This is D3 item 3: the position-name
fold never reaches the guest.
- `refuseGuestWithoutOrganization` raises D4's located refusal for a
guest that carries no organization.
- **One nuance, left as is because it is not ruling C's matter.** E1
derives the organization at the guest entry (`resolveAuthzContext`),
before any gate runs. When none was derived, the refusal is raised in
the CRUD gate's prologue: the gate asks the same step again
(`resolveGuestOrganization`). So "both resolved before any gate runs"
holds for the derivation, and the refusal surfaces at the gate. This is
named for E1's review and not edited here.
### 5. Consistency with ruling C: "What the ruling did not settle", item
3 (seat answer `6153761503`)
The item listed the row scope as open ("one pipeline, no special case").
Ruling C has settled it, so the item now points at ruling C and E1's
pin. The item keeps its number, and the section's other two items are
unchanged.
Before:
```text
3. **The guest's row scope** is stated as a contract (one pipeline, no special case, ADR-0090 D9's
division of labour); its measured outcome per sharing model is E1's pin.
```
After:
```text
3. **The guest's row scope** is no longer open: ruling C on
[#23266](#23266)
([`6148183570`](#23266 (comment)))
settled it as share links only, never the object's sharing model (D3 item 6), and E1 pins it: with
a bound read set and no share link, `private`, `public_read`, `public_read_write` and
`controlled_by_parent` each read zero rows.
```
## Verification (head `58464a201a`)
- **Docs only.** The diff touches no package, so there is no build, test
or typecheck target.
- **Base.** `origin/main` moved to `4d4040ccb6` but left this ADR
unchanged, so no merge was made. The branch is still based on
`87e902dcbb`.
- **Gate derivation.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`, run on this branch's diff (1 path,
+28/−11), derives 19 commands, the same set as at dispatch.
- **Gate results.** All 19 exit 0 on `58464a201a`. The `--ran`
reconciliation reads: "19 derived famil(ies) accounted for — 19 run, 0
NOT-MEASURED (a DERIVED zero — all 19 recorded an exit code and none of
them is 3)".
- `check:doc-formula-expressions` needs `@objectstack/formula` and
`@objectstack/lint` built. They were built first under the verify lock
(`turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint`, VERDICT command-exit 0).
- Verdict lines:
- `check-adr-links: 730 relative link destination(s) under docs/adr/
resolve`
- `check-adr-symbol-anchors: 2334 anchors across 142 records resolve`
- `check-adr-anchors: OK (68 anchored file(s), every governing ADR still
referenced; … 45590 citation(s) across 5976 file(s) resolve …)`: one
fewer citation than at `86e71fdc39`, because the old item 3's "ADR-0090
D9's" is gone.
- `check-closing-keyword-parity: OK (3 parsers agree …)`
- `check-nul-bytes: OK (… no raw ASCII control bytes)`
- the doc-authoring guard: ✓
- **Anchors.** No anchor under `scripts/adr-anchors/` quotes any edited
passage, and this diff adds or removes no `path#symbol` anchor.
- **Control bytes.** A self-scan of the edited file finds no control
bytes.
- **Changeset.** None, because nothing publishes:
- The ADR path is in no published package's `files[]`. Of 71 non-private
manifests, 0 have an entry under `docs/`. The positive control is
`packages/spec`, which lists `dist`.
- The root package is private.
- No package build copies `docs/adr`.
- `skip-changeset` applies.
## Acceptance notes
These were checked and do not conflict with ruling C, so they are left
as they are:
- Consequences says "Not measured, and named: the guest's row scope for
each sharing model (E1's pins)". That stays true until E1 lands.
- D8 notes that the sharing-rule vocabulary already retired the `guest`
recipient (the ADR-0087 ledger entry
`sharing-rule-recipient-reconcile`). That describes today. The explicit
guest sharing rule in ruling item 2 would be a new rule: criteria-based,
per object, under the strictest anchor tier, and decided when it is
built. The words "when it exists" keep the two apart.
- The `**Decided by**` block lists the rulings on #22146 and #22147 but
not ruling C. The status-line continuation records C, which is how the
other records do it. The seat's answer leaves the block as it is.
- The CRUD-gate nuance in section 4 belongs to E1's review, not to this
record edit.
## 维护者速读(草稿)
- **改了什么:** 只改 ADR-0138 一个文件,共四处文字修改。
1. **D3 第 6
项**,裁决原句:访客只经分享链接触达行;将来有了显式的访客分享规则,也可经它;永不经对象的共享模型。同一项的首句和末句随之对齐(上文
2a、2b)。
2. **状态行**下加一条带日期的修订记录,写明裁决 C 和评论号(上文第 3 节)。
3.
**「Consequences」一节「访客是一条管线上的一个主体」**:原文说行级评估器不进访客特例,现改为写明行级评估器唯一的访客特例,即访客不走共享模型(OWD)分支,只经分享链接触达行;关于
CRUD 闸门和字段掩码的说法原样保留,已对 E1 代码逐一核对(上文第 4 节)。
4. **「What the ruling did not settle」第 3 条**:不再把访客行范围列为未定事项,改为指向裁决 C 和
E1 的钉:绑定了读集合、没有分享链接时,四种共享模型都是 0 行(上文第 5 节)。
以上每处都有逐字前后对照。ADR-0090 的 D9、D11 一字未动,「Decided by」一段未动,本 PR 不含任何代码。
- **为什么改:** 你在 #23266 上回了「同意」(裁决 C):访客的行范围只经分享链接,不经共享模型。记录第 6
项原文写的是「经对象声明的共享模型和分享链接」,与裁决相反。同一记录里另有两处也与裁决相抵,席位答复 A:本 PR
一并修订,让一次批准覆盖一份不自相矛盾的记录。按 AGENTS.md 第 13 条,改变已接受记录的决定,要改原文并留修订行。实现代码在 PR
#23321,改了原文,它才有记录可依。
- **风险与代价(含回滚):**
- 纯文档:不发布任何包,不改运行时行为。
- 第 1 处里的 2a、2b 以及第 2 至第 4 处,都超出裁决原句,属于与裁决对齐的措辞修改,请过目。
- 有一处细节留给 E1 评审,本 PR 不改:E1 把「组织无法确定」的拒绝放在 CRUD 闸门入口处抛出(上文第 4 节)。
- 回滚:revert 本 PR,原文即恢复。
- **席位意见:**
- **你要做的:**
1. 看第 1 处里的 2a、2b 以及第 2 至第 4 处这些超出裁决原句的对齐修改,确认同意。
2. 同意就批准本 PR(Tier H,需你本人批准)。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01P88ehrZ6hM19hBzdpsQTTo)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4f1e435 commit 34d144c
1 file changed
Lines changed: 28 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
18 | 23 | | |
19 | 24 | | |
20 | 25 | | |
| |||
365 | 370 | | |
366 | 371 | | |
367 | 372 | | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
374 | 385 | | |
375 | 386 | | |
376 | 387 | | |
| |||
564 | 575 | | |
565 | 576 | | |
566 | 577 | | |
567 | | - | |
568 | | - | |
569 | | - | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
570 | 583 | | |
571 | 584 | | |
572 | 585 | | |
| |||
584 | 597 | | |
585 | 598 | | |
586 | 599 | | |
587 | | - | |
588 | | - | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
589 | 606 | | |
590 | 607 | | |
591 | 608 | | |
| |||
0 commit comments