Skip to content

docs(adr): ADR-0095 D1 note — Layer 0 is inert on 22 of the 23 platform identity objects; Layer 1 silence is to mean the object's declared default row scope (#24164 ruling E′) - #24408

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-24339-adr-0095-default-row-scope-note
Oct 19, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-24339-adr-0095-default-row-scope-note

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24339
Clause-②: no

What this changes

A correction to the ruling's sentence (please read)

Ruling E′ item 8 reads "Layer 0 is inert on these objects". Measured, that is true for 22 of the 23 objects item 3 names.

sys_user_preference is managedBy: 'system-data' and has no tenancy opt-out, so registration gives it an organization_id column. Under the isolated and group postures, Layer 0 walls it to the organisation. The note says this instead of repeating the ruling's sentence.

The 23 declarers, the key, the collection point and the registry rule do not change. The dev reported the difference as needs_decision (os-dev-report 6256329876 on #24339). The seat answered A to both questions (comment 6256351977 on #24339). The maintainer can veto the correction on this PR.

Premises, each with its reading

Landing order

PR #24398 (#24338, the defaultRowScope key) was read once at write time: it is open and not merged (it is in the merge queue). So the note cites #24338, not a merge commit, and this PR lands after #24398. Today defaultRowScope has zero hits on origin/main; the control nameField hits in the same file.

Size

The card estimated about 15 lines; this PR adds 29. The extra lines come from spelling out the 22 inert names (the seat's Q2 answer: no glob) and the sys_user_preference sentence.

Local gates (final head 569c58e)

  • dispatch-gates --commands --repo objectstack-ai/objectstack with no paths derives 19 commands from the 2 changed paths. All 19 exit 0.
  • --ran reconciles: "19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded.
  • The ADR families:
    • check-adr-links: "770 relative link destination(s) under docs/adr/ resolve".
    • check-adr-symbol-anchors: "2357 anchors across 142 records resolve".
    • check:adr-anchors: OK.
  • check:doc-formula-expressions needs @objectstack/formula and @objectstack/lint built first. Its first run refused with exit 3 for that prerequisite, which is not a measurement. After the build it exits 0.
  • The rest of the farm runs in CI.

维护者速读(草稿)

改了什么: 本 PR 只改文档,没有代码。

  • ADR-0095 的 D1(租户隔离 Layer 0)下加了一段注明日期的说明,状态行加了一条对应记录。
  • ADR-0090 D12 的 10 月 17 日说明末尾加了一句交叉引用。

说明的内容是您 10 月 18 日的 E′ 裁决:平台身份对象的「默认行范围」由对象自己声明。当用户持有的权限集都没有为某张表写行级规则时,应用这张表声明的默认范围,而不是「不过滤」。

有一处修正需要您看到。 裁决第 8 条说「这些对象上组织墙(Layer 0)不起作用」。实测 23 张表里有 22 张是这样。sys_user_preference(用户偏好表)带组织列,组织墙会把它限制在本组织内。

为什么改:

风险与代价(含回滚):

席位意见:

你要做的:

  1. 读 ADR-0095 D1 下的新说明,确认 sys_user_preference 这处修正可以接受。
  2. 等 feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398 合入后,批准或合并本 PR。

如果不同意这处修正,在本 PR 上说明,席位会重开这个问题。


Generated by Claude Code

…rm identity objects; Layer 1 silence is to mean the object's declared default row scope

ADR-0095 gains a dated 2026-10-18 note under D1 and a matching status-line
entry for ruling E' (record 6250748677): Layer 0 is inert on 22 named
objects that carry no organization_id column; sys_user_preference carries
it, so Layer 0 bounds it to its organisation and its declared default is to
apply inside that wall (measured by the tenancy census). On all 23, Layer 1
silence is to mean the object's declared default row scope, a protocol key
on the object. ADR-0090 D12's 2026-10-17 note points to it: the boundary
keeps its three conditions and gains no fourth.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
The note cites the ruling comment and all four cards of ruling E':
the key, the collection point, the declarations, and this note.

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 569c58e52e4784ff6a0b5229ede2af1f51978198
Local-runs: none
Scope: docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md, docs/adr/0090-permission-model-v2-concept-convergence.md + scripts/platform-object-tenancy-census.json, scripts/check-platform-object-tenancy-census.mjs, packages/platform-objects/src/identity/sys-user-preference.object.ts, the 21 packages/platform-objects/src/identity/*.object.ts files the note names, packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts, packages/core/src/security/tenant-layer0-verdict.ts, packages/plugins/plugin-security/src/security-plugin.ts, packages/plugins/plugin-security/src/objects/default-permission-sets.ts, packages/spec/src/data/injected-system-columns.ts, packages/objectql/src/tenancy/system-write-organization.ts, packages/spec/src/data/object.zod.ts (on origin/main after #24398)

Rules face, the two governed docs/adr/** paths. Read-only: every tree reading is git show / git grep on refs/review/pr-24408 (fetched to a ref of my own; head 569c58e52e…, merge base with origin/main 78652c093c…) and on origin/main (fetched at 2026-10-18T23:32:35Z: 31e7d0ba27…); every GitHub reading is a gh api GET. Nothing built, tested, re-run or ablated; the only tool runs were the brief's own record-recognisers --template and post-stamped --dry-run. Inputs: card #24339 (body, claim 6256231065, dev reports 6256329876 and 6256506911, seat answer 6256351977, weighed as a seat record, not as a verdict), ruling E′ 6250748677 on #24164 and the three sibling cards #24338 / #24340 / #24341, PR #24408 (body, file list, net diff 78652c093c…..569c58e52e…: +29 / −0 in the two ADR files, two commits), and the check-runs on the head. Record written 2026-10-18T23:39Z.

① Derived judgments

Every sentence of the two hunks, named right or wrong against the ruling and the tree at the head.

  1. "Layer 0 is inert on 22 of the 23 objects the ruling names, which carry no organization_id column" — right. The census at the head (scripts/platform-object-tenancy-census.json, totals 83 / 44 in / 39 out, unexplained: []) reads all 22 as reach: out, tenantField: null: 21 by managedBy: 'better-auth' (sys_api_key also tenancy.enabled: false), sys_audit_log by systemFields.tenant: false. None of the 21 identity object files declares an organization_id field (0 declarations each, read file by file); sys_audit_log carries tenant_id, a plain attribution lookup its own header says the tenant-field resolver does not claim (ADR-0131 D7). The injection plan gives a better-auth table nothing (packages/spec/src/data/injected-system-columns.ts:206) and no tenant column under either opt-out (:155). plugin-security feeds the wall objectHasOrgIdField = objectFields.has('organization_id') && !meta.tenantAnchorIsPhantom (security-plugin.ts:7469) and tenancyDisabled from the object's two opt-outs (:9142, :9587); computeTenantLayer0Verdict answers none on either (tenant-layer0-verdict.ts:137-138), under every posture. The note's reason is the code's second exit; on sys_audit_log and sys_api_key the first exit fires too, and the reason the note gives is still true of them.
  2. "The 23rd, sys_user_preference, carries the organisation column, so Layer 0 bounds it to its organisation" — right. managedBy: 'system-data' (sys-user-preference.object.ts:30), no opt-out, so the plan injects organization_id; the census reads reach: in, tenantField: organization_id, reasons: []; its unique index is unique: 'organization' on [user_id, key] (:129, ADR-0120 D1). With the column present and no opt-out the verdict is organization under isolated and organizations under group. One implicit qualifier: under single Layer 0 is inert on every object (D1's own rule, two paragraphs above the note; tenant-layer0-verdict.ts:131). The note sits under D1, which carries that qualifier, and the PR body spells it out; an observation, not a misstatement.
  3. "its declared default is to apply inside that wall" — right, and decided-not-built. D1 AND-composes Layer 0 before and independently of Layer 1; the declared default is a Layer 1 policy applied at the collection point (ruling item 2), so it composes inside the wall. The literal it will carry is sys_user_preference_self, shipped in three sets today (default-permission-sets.ts:546, :974, :1301).
  4. "On all 23, Layer 1 silence (no held set carries a row policy for the object) is to mean the object's declared default row scope, not no filter" — right. Ruling item 2 and item 8's second clause; "is to".
  5. "The default is a protocol key on the object (ObjectSchema.defaultRowScope, spec(data): ObjectSchema declares an object's default row scope — the existing RLS policy shape minus object — applied when no held permission set carries a policy for it (#24164 ruling E′) #24338)" — present tense; false on this head's base, true on origin/main now and at merge time. At the merge base 78652c093c…, git grep defaultRowScope on packages/spec/src/data/object.zod.ts exits 1 (control nameField: 16 hits). PR feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398 (spec(data): ObjectSchema declares an object's default row scope — the existing RLS policy shape minus object — applied when no held permission set carries a policy for it (#24164 ruling E′) #24338) merged at 2026-10-18T23:30:16Z as 31e7d0ba27…, and on that origin/main object.zod.ts:2993 reads defaultRowScope: DefaultRowScopeSchema.optional().describe(; the key name the note writes is the one that landed. The PR body's landing order ("this PR lands after feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398") is therefore satisfied by the tree, and the queue rebuilds this PR onto current main, so the sentence is true at merge time. Right.
  6. "the platform objects are to declare it themselves (platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341)" — right, ruling item 3; "are to". platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341 is open, pm:blocked on spec(data): ObjectSchema declares an object's default row scope — the existing RLS policy shape minus object — applied when no held permission set carries a policy for it (#24164 ruling E′) #24338 and docs(adr): ADR-0095 D1 dated note — Layer 0 is inert on the platform identity objects; Layer 1 silence means the object's declared default row scope, a protocol key (#24164 ruling E′; Tier H) #24339.
  7. "the one collection point is to apply it (plugin-security: collectRLSPolicies applies an object's declared default row scope when no held set carries a policy for it — the platform identity objects stop depending on every set copying the same literals (#24164 ruling E′) #24340)" — right, ruling item 2; "is to". plugin-security: collectRLSPolicies applies an object's declared default row scope when no held set carries a policy for it — the platform identity objects stop depending on every set copying the same literals (#24164 ruling E′) #24340 is open, pm:blocked on the same two.
  8. "the registry is to refuse an extend contributor that sets or loosens it (platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341)" — right, ruling item 4 ("cannot set or loosen … refused at registration with a named reason"); "is to".
  9. The precedent line — right: the card's ## What changes sentence, verbatim.
  10. The 23 names — right, each spelled as the census and the object files spell it, no glob. The seven SCIM names equal the seven object: values inside scimProjectionRowScope() (default-permission-sets.ts:171-191); sys_verification / sys_jwks equal privateCredentialRowScope() (:243-245); sys_audit_log equals auditLedgerRowScope()'s sys_audit_log_org (:291); the 13 identity names equal ruling item 3's list and platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341's. 7 + 2 + 13 + 1 = 23; the note names 22 plus sys_user_preference. The card's sys_oauth_* glob expands to 7 objects in the tree, three of which (sys_oauth_resource, sys_oauth_client_resource, sys_oauth_client_assertion) carry no shipped row policy; the card's "better-auth-managed" label does not fit sys_user_preference (system-data) or sys_audit_log (append-only); the tree's eighth sys_scim_ object, sys_scim_connection_credential, reads reach: in and is no declarer. The note follows the ruling in all three places.
  11. The ADR-0095 status-line entry — right and in form. It restates the note, keeps "are to declare" / "is to mean", and names the key as "a protocol key (spec(data): ObjectSchema declares an object's default row scope — the existing RLS policy shape minus object — applied when no held permission set carries a policy for it (#24164 ruling E′) #24338)". Shape · **Amended** (date, ruling of record N on #card, letter X) — … See the note under D1. on the line after **Status**: and before **Deciders**:, as ADR-0090's six entries are.
  12. The ADR-0090 sentence — right. The D12 2026-10-17 note's last paragraph holds "the general case … for the maintainer's decision on [Decision] The organization administrator's assignment boundary: an assignable set with a wildcard read and no row scope reaches other organizations' SCIM rows in a deployment with no platform baseline #24164"; the appended sentence says that case is ruled (E′, 6250748677), restates items 1–2 in "is to" form, points to ADR-0095's note under D1, and states item 9 in substance: plugin-security: organisation admins assign positions and permission sets to their own members inside the D12 boundary — gate branch, overrides, explain and my-delegable-scope, three-posture acceptance (ruling B on #23960) #24136 keeps the three conditions (a) (b) (c), present at the head, and gains no fourth. No status-line entry on ADR-0090 is right: nothing ADR-0090 decided moves, and the card asked for one cross-reference line.
  13. The quoted maintainer word — exact. Ruling 6250748677 line 1 reads Ruling: batch #365 item 4 · letter E′ · maintainer 「同意」 followed by its own stamp (the comment's creation instant, 2026-10-18T13:55:32Z), and its body "The maintainer's words in this session's chat: 「同意」"; the note's heading quotes the same two characters in the same — the maintainer's 「同意」) form the 2026-10-17 note under ADR-0090 D12 uses.
  14. Fidelity to item 8 and the card's ## What changes. The note carries all four of item 8's terms: a dated note under D1, Layer 1 silence means the declared default rather than no filter, the default is a protocol key, and it lands before the code (plugin-security: collectRLSPolicies applies an object's declared default row scope when no held set carries a policy for it — the platform identity objects stop depending on every set copying the same literals (#24164 ruling E′) #24340 and platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341 are blocked on it). It carries nothing the ruling did not decide: items 2, 3, 4 and 9 are cited where they appear; the scope limit, pins and declarations (items 5–7) are the code cards' and are rightly absent. The one departure from item 8's letter — 22 inert, one walled — is attributed to the census, never to the ruling.
  15. Amendment form against the repo's rules. Prime Directive [WIP] Add Chinese version of the documentation #13: the decision is extended on the old ADR with an amended status line, not reversed by a new one, and the ADR lives in the repo whose code it governs. No scripts/adr-anchors/*.json pins a line inside either ADR (seven anchor files cite ADR-0095 or ADR-0090 by symbol, none by ADR line), so the +24 / +5 line shift moves nothing. Dated blockquote at the end of D1, just before ### D2; the card number #24339; added to the heading is a harmless addition the 2026-10-17 precedent lacks.

② Semver level

Docs only: two docs/adr/** files, nothing a released package publishes. skip-changeset is the right disposition (Post-Task Checklist 3: the label is for a diff that publishes nothing from any released package), and Clause-②: no is right for a note; the contract key's own Clause-②: yes (widening) rode on #24398. Check-runs, read twice: at 2026-10-18T23:32:34Z 40 runs, Check Changeset run 117820388210 failure (completed 2026-10-18T23:29:37Z, before the label), skip-changeset labeled by objectstack-fleet[bot] at 2026-10-18T23:30:43Z, re-run 117821105107 skipped at 2026-10-18T23:30:46Z, four lanes still in_progress; at 2026-10-18T23:34:46Z 43 runs, all completed: 31 success, 11 skipped, 1 failure — the superseded first Check Changeset run. Governed Surface Queue Guard, every Lint · lane, every Type Check · lane, Test Core 1–6, Check Documentation Links, Check PR Size and the three card-claim guards are success; Build Core and the full-battery jobs are skipped on the docs-only path filter. PR at read: draft, mergeable_state: clean, labels documentation, size/s, skip-changeset, assignee the card's.

③ Boundary flags

Implemented-by: claude/issue-24339-adr-0095-default-row-scope-note
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · PR #24408 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T23:40Z

改了什么
只改两份 ADR 文本,共 +29 行,不碰代码,也不发版。

为什么改

风险与代价(含回滚)

席位意见
同意合并。契约复核 6256600360 判 PASS,没有 FAIL 项。几处已逐条核对:

  • 22/1 的实测;
  • 23 个对象名逐个拼对,没有用通配;
  • 每句「将要」都和代码现状一致;
  • 修订格式沿用 ADR-0090 的先例;
  • 引用的「同意」与裁决原文一致。

只有一个小观察,不必改:单租户(single)姿态下 Layer 0 对所有对象都不起作用。D1 正文两段之上已经写明,这里不重复。

你要做的(一个动作)
用 os-zhuang 或 hotlong 在本 PR 上提交 Approve,之后席位翻 ready 并经合并队列落地。亲手合并也可以。不同意上面那处修正,就在本 PR 留言,席位会转回决策箱。

席位核对 (seat check)

ACCEPT at head 569c58e52e4784ff6a0b5229ede2af1f51978198 (claim 6256231065; dev reports 6256329876 (stop on P1) and 6256506911; seat answer 6256351977). Tier H (docs/adr/**): the PR stays draft until an authorised approval. needs-user-decision goes on this PR in this act, and review is requested from os-zhuang and hotlong.

item reading
Shape Draft, base main, assignee zhuangjianguo. Line 1 is Fixes #24339, line 2 is Clause-②: no, no other closing keyword. 2 files, +29/−0, both governed (check-governed-merges: 2 of 2 paths hit the register; 29 lines, under the human-merge threshold). skip-changeset (docs only).
Premises measured P1 holds for 22 of 23. It fails for sys_user_preference: census reach: in, tenantField: organization_id, re-read by the seat at 78652c093 and by the review at the head. The dev stopped as told, and the seat answered A. P2: the note's 23 names equal ruling item 3 and #24341, with no glob. P3: ADR-0090's 2026-10-17 amendment is at :9 and :535.
Gates (check-runs on the head) 43 runs: 31 success and 11 skipped (check-expected-skips: OK). The one failure is Check Changeset run 117820388210, from before the label; it was superseded by run 117821105107, skipped by the job's own skip-changeset condition. Dev: 19 derived families run, all exit 0, reconciled with --ran.
Accepted deviations +29 lines against the card's "about 15": the 22 spelled-out names and the one sys_user_preference sentence. The card number in the note's heading.
Out of scope The card body's sys_oauth_* glob and its "better-auth-managed" label are inexact. The note does not copy them, and the card stays as filed (answer 6256351977). A pointer on #24164 records the 22 + 1 correction in the ruling's thread, in this act.
Landing After an approval by os-zhuang or hotlong (or the maintainer's merge): pr_ready then automerge_enable. The merge closes #24339, which unblocks #24340 (both its blockers then closed) and #24341.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 19, 2026 00:32
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 97cb5c5 Oct 19, 2026
50 of 51 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-24339-adr-0095-default-row-scope-note branch October 19, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants