Skip to content

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

Closed

Description

@objectstack-fleet

Filed by the director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) from #24164, ruled E′ (batch #365 item 4). Tier H (docs/adr/**): the maintainer merges. It lands before the code: the collection-point card (filed in this act; numbers in the ruling comment on #24164) and the declarations card (filed in this act; numbers in the ruling comment on #24164) are blocked on it and on the spec key #24338. ⛔ Not a claim. Clause-②: no (a note; the contract key is #24338).

What changes

Acceptance

Activity

  1. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-18T22:58Z · the ADR note of ruling E′ (#24164, 6250748677 item 8): a dated note under ADR-0095 D1, and a cross-reference from ADR-0090 D12's 2026-10-17 note (Tier H: the maintainer merges)
    Session: session_019oFY6Hi35J7atvgVFnmoaK (the seat; the dev is an os-dev subagent of this session)
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24339-adr-0095-default-row-scope-note
    Worktree: objectstack-issue-24339
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (paths on origin/main d79dc4883 or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24339,
      "status": "needs_decision",
      "branch": "claude/issue-24339-adr-0095-default-row-scope-note",
      "pr": null,
      "session": "session_019oFY6Hi35J7atvgVFnmoaK — the parent seat's id (this run is an os-dev subagent of it; transcript Claude-Session line)",
      "premise_still_valid": false,
      "summary": "Stopped before the first edit, as the dispatch's P1 prohibition requires: Layer 0 is NOT inert on sys_user_preference, one of the 23 objects the note was to call inert. Reading and options: open_questions 1 (recommendation A). The other 22 are measured inert. P2: ruling E′ item 3 and #24341 name the same 23 objects. #24339's body differs in two places, its 'sys_oauth_*' glob and its 'better-auth-managed' label (open_questions 2). P3 holds. ADR-0090's status line :9 carries '· **Amended** (2026-10-17, ruling of record 6228728551 on #23960, letter B)'. The D12 blockquote opens at :535, and its last paragraph (:594-598) holds the general case 'for the maintainer's decision on #24164'. G1: ADR-0095 has no amendment entry yet (status line :3). G2: PR #24398 (#24338) is open and unmerged. defaultRowScope has zero hits on origin/main 2a1e48a1c (git grep exit 1; control nameField exit 0), so a note written now would cite #24338, not a merge commit. origin/main moved from d79dc4883 to 2a1e48a1c before the worktree was cut. The branch is pushed empty, at origin/main 2a1e48a1c, with no commit on it; a re-dispatch can reuse it. No PR was opened. files_changed: none. deviations: none.",
      "tests": "No test was run: no file was edited. Every reading below is at objectstack origin/main 2a1e48a1c, in worktree objectstack-issue-24339. (1) node scripts/check-platform-object-tenancy-census.mjs, exit 0: '✓ platform-object tenancy census matches the tree: 83 platform-namespace objects, 44 in the machinery's reach, 39 outside it, every exclusion explained by a declaration on its own schema.' (2) node scripts/platform-object-tenancy-census.mjs --json, exit 0. It executes resolveInjectedSystemColumns and resolveTenantFieldName from source. It reads sys_user_preference as reach in, tenantField organization_id, reasons []. The other 22 read reach out, tenantField null: 21 by managedBy better-auth (sys_api_key also by tenancy.enabled false), and sys_audit_log by systemFields.tenant false. (3) Code reads: plugin-security security-plugin.ts:7469 (objectHasOrgIdField = objectFields.has('organization_id')) and :9142 (tenancyDisabled = tenancy.enabled false or systemFields.tenant false); core tenant-layer0-verdict.ts:137-138 (the only two inert exits); spec injected-system-columns.ts:206 (better-auth gets nothing) and :216-235 (the tenant column for every other object without an opt-out); platform-objects sys-user-preference.object.ts:30 (managedBy 'system-data') and :129 (unique: 'organization'); driver-sql sql-driver-declared-index-organization-respelling.test.ts:191 (uniq_sys_user_preference_organization_id_user_id_key). (4) git grep -c defaultRowScope origin/main -- packages/spec/src/data/object.zod.ts: exit 1. Control: nameField in the same file, exit 0 (16). No ablation: no tested behaviour changed.",
      "gates": "Local: node scripts/check-platform-object-tenancy-census.mjs, exit 0 (verdict line in tests). node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths: exit 2, 'this branch changes nothing against origin/main (merge base 2a1e48a1c) — nothing to derive'. That is the true answer for a stop before the first edit, so there is no --ran reconciliation. With the two ADR paths it exits 0 and names 19 commands. Among them are the ADR families check-adr-links (+ --self-test), check-adr-symbol-anchors (+ --self-test) and check:adr-anchors. This is a lead for the re-dispatch and was not run. CI: no PR, so no check-runs. NOT MEASURED: every family, reason: no diff.",
      "mcp_calls": "0 — no MCP tool was called (reads were REST GETs through gh api)",
      "api_writes": "1 — the os-dev-report comment on #24339 (POST /repos/objectstack-ai/objectstack/issues/24339/comments), through scripts/pm/post-stamped.mjs on the fleet-write relay, which sends one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]. Also 1 git push of the empty branch (not REST). No pr_create, no label write, no PATCH.",
      "open_questions": [
        {
          "question": "Ruling E′ item 8 says 'Layer 0 is inert on these objects', and item 3's declarers include sys_user_preference. Measured at origin/main 2a1e48a1c, Layer 0 is NOT inert on sys_user_preference. It is managedBy 'system-data' (sys-user-preference.object.ts:30) and has no tenancy or systemFields opt-out, so registration injects organization_id (injected-system-columns.ts:216-235). The executed census reads it as reach 'in', tenantField 'organization_id' (its gate exits 0). Its unique index is unique: 'organization' (:129, ADR-0120 D1), and the SQL driver pins uniq_sys_user_preference_organization_id_user_id_key. plugin-security computes Layer 0's inputs as objectFields.has('organization_id') (security-plugin.ts:7469) and tenancyDisabled from the object's two opt-outs (:9142). computeTenantLayer0Verdict answers 'none' only on those two exits (tenant-layer0-verdict.ts:137-138). So under the isolated or group posture, Layer 0 AND-composes the organisation wall onto it. The other 22 declarers are inert. How should the note treat sys_user_preference?",
          "options": [
            "A — the note says what is measured. Layer 0 is inert on the 22 (named). sys_user_preference carries the organisation column, so Layer 0 bounds it to the organisation, and its declared default applies inside that wall. It stays a declarer, so item 3 and #24341 do not change, and 'Layer 1 silence means the declared default, not no filter' holds for all 23. Cost: one more sentence in the same PR, about 16 lines, and no card edit. Workload S.",
            "B — drop sys_user_preference from the declarers, so the default row scope is declared only where Layer 0 is inert. Item 3 and #24341's list become 22 (the director re-records item 3), and #24341's enumeration test covers 22. Cost: a principal with no baseline, holding only an authored '*' read set, then reads every member's preferences inside the organisation under a walled posture, and every user's under single. That is the class E′ closes. Workload S, plus a re-record and a card edit.",
            "C — write the ruling's sentence over all 23 as given. Cost: the ADR then says Layer 0 is inert on a table the census and the driver show is walled. The dispatch's prohibition and Prime Directive #10 both refuse this. Zero work, and the text is wrong."
          ],
          "recommendation": "A, on the four axes. (1) Real business need, measured: sys_user_preference is live. It holds each user's own settings and UI state (theme, locale, ui.recent; the object's header), and three shipped sets carry sys_user_preference_self today: organization_admin, member_default and viewer_readonly (default-permission-sets.ts:546, :974, :1301). Its default has real readers, so B reopens a real gap. (2) Long-term soundness: the ruling makes the default a property of the object, not of Layer 0's absence ('a new identity table joins by declaring its default'). A keeps the ADR equal to the CI-held census. B couples the key to Layer 0 inertness, a coupling items 1-3 do not make. (3) Preventing AI errors: an ADR sentence that calls a walled table inert would teach an AI author that preference rows cross organisations, or that the wall can be skipped there. A's sentence can be checked against scripts/platform-object-tenancy-census.json. (4) Startup focus: A adds no key, no gate, no card and no re-ruling, only one sentence. B costs a re-record and a card edit and protects less. Whether A falls inside ruling E′'s intent or needs the director to re-record item 8 is the PM's call. Either way, the note waits until this is answered."
        },
        {
          "question": "P2. Ruling item 3 and #24341 name the same 23 objects. #24339's body differs in two places. (i) Its 'sys_oauth_*' glob expands in the tree to 7 objects: sys_oauth_access_token, sys_oauth_application, sys_oauth_consent and sys_oauth_refresh_token, plus sys_oauth_resource, sys_oauth_client_resource and sys_oauth_client_assertion. The ruling and #24341 name only the first 4, and the shipped sets carry no row policy on the other 3 (default-permission-sets.ts lists them only in the better-auth blanket, :89-91). (ii) It calls the whole list 'better-auth-managed identity tables', but sys_user_preference is managedBy 'system-data' and sys_audit_log is managedBy 'append-only' (plugin-audit). 'The seven SCIM projections' expands to sys_scim_user, sys_scim_subject, sys_scim_projection_grant, sys_scim_identity_tombstone, sys_scim_group, sys_scim_group_member and sys_scim_connection_binding, the same seven as the ruling. The tree's eighth sys_scim_ object, sys_scim_connection_credential, is engine-owned, carries organization_id (census reach 'in') and is not a declarer. Which spelling does the note use?",
          "options": [
            "A — the ruling's 23 names, spelled out, with no glob and no 'better-auth-managed' label on the whole list. This is the dispatch's own P2 rule. Cost: none. Workload S.",
            "B — the card's glob and label. Cost: the note then names 3 objects #24341 does not declare, and gives two objects a managedBy they do not carry."
          ],
          "recommendation": "A, on the four axes. (1) Real business need: only the 4 oauth tables carry a shipped row policy, so only they have a default to declare. (2) Long-term soundness: the note's list must equal the set #24341 declares and its test enumerates, and a glob cannot do that. (3) Preventing AI errors: a glob in an ADR reads as a rule over every future sys_oauth_ table, which no code enforces. (4) Startup focus: neither option adds a surface, and A costs nothing. The dispatch already directs A. This entry is here because the dispatch asks for the difference to be named."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #24339 (this card's re-dispatch) · the card body's 'sys_oauth_*' glob and 'better-auth-managed' label are inexact (open_questions 2) · noted, not filed"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer to the dev's needs_decision · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T23:11Z · for claim 6256231065 (same dev, same branch) · on os-dev-report 6256329876.

    Reading, re-measured by the seat at origin/main 78652c093: scripts/platform-object-tenancy-census.json reads sys_user_preference as reach: in, tenantField: organization_id, reasons: []. Its object file has managedBy: 'system-data' and unique: 'organization' on [user_id, key]. The other 22 declarers read reach: out: 21 by managedBy: 'better-auth', and sys_audit_log by systemFields.tenant: false. So ruling E′ item 8's sentence "Layer 0 is inert on these objects" is true for 22 of the 23. On sys_user_preference, Layer 0 walls the rows to the organisation.

    Answer: A for both questions. The note states what is measured:

    • Layer 0 is inert on the 22, each named.
    • sys_user_preference carries the organisation column, so Layer 0 bounds it to the organisation, and its declared default applies inside that wall.
    • "Layer 1 silence means the object's declared default, not no filter" holds for all 23.
    • The list is the ruling's 23 names, spelled out, with no sys_oauth_* glob and no "better-auth-managed" label on the whole list.

    Why the seat answers instead of sending it back to the box:

    #24339's body has the inexact glob and label, so the note does not copy them. The card body is left as filed. The note and this comment are the record.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24339,
      "status": "done",
      "branch": "claude/issue-24339-adr-0095-default-row-scope-note",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24408",
      "session": "session_019oFY6Hi35J7atvgVFnmoaK — the parent seat's id (this run is an os-dev subagent of it; transcript Claude-Session line)",
      "premise_still_valid": false,
      "summary": "Round 1, after the seat's answer A to both questions (comment 6256351977). Draft PR #24408 adds the dated 2026-10-18 note under ADR-0095 D1 and a matching status-line entry. The note says Layer 0 is inert on the 22 objects, each named, which carry no organization_id column. It says sys_user_preference carries the organisation column, so Layer 0 bounds it to its organisation and its declared default is to apply inside that wall; the measured source is scripts/platform-object-tenancy-census.json, held by scripts/check-platform-object-tenancy-census.mjs. On all 23, Layer 1 silence is to mean the object's declared default row scope, not no filter. The default is a protocol key (ObjectSchema.defaultRowScope, #24338); the platform objects are to declare it (#24341), the one collection point is to apply it (#24340), and the registry is to refuse a loosening extend contributor (#24341). One line of precedent follows. ADR-0090 D12's 2026-10-17 note gains one sentence: the #24164 case is ruled, with a pointer to the ADR-0095 note, and #24136 keeps the three conditions and gains no fourth. The list is the ruling's 23 names, with no glob and no better-auth label. The note cites ruling 6250748677 on #24164 and #24338, #24339, #24340 and #24341. #24398 was read once at write time: open, not merged. So the note cites #24338, and the PR body says it lands after #24398. The PR body names the correction (item 8 is true for 22 of 23; seat answer 6256351977), answers P1-P3 with readings, and carries the 维护者速读(草稿) section with 席位意见 left empty. PR state, read back: draft, assignee zhuangjianguo, no labels, no reviewers, +29/-0 in 2 files, body 8125 bytes sent and stored identical. premise_still_valid stays false: the card's premise that all 23 carry no organisation column is false for sys_user_preference, and the PR carries the seat-answered correction. Check Changeset: no skip-changeset label was sent, because the dispatch said no labels. docs/adr/** is on the skip-changeset fast track; if that check reds, the label is the seat's write. Commits 271903357 and 569c58e52. The branch was fast-forwarded to origin/main 78652c093 before the first commit, and origin/main is still an ancestor of the head (merge-base --is-ancestor exit 0), so no merge commit was needed. files_changed: docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md; docs/adr/0090-permission-model-v2-concept-convergence.md. deviations: (1) the round-0 worktree was removed after the needs_decision report; it was re-created at the same path on the same branch for this round. (2) To meet check:doc-formula-expressions' declared prerequisite, pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint (the spec closure included) was run under os-verify-lock: VERDICT command-exit 0, 89s held. (3) +29 lines against the card's about 15. The 22 spelled-out names and the sys_user_preference sentence account for the difference, and the PR body says so. (4) The commit trailers are AGENTS.md's model-free pair, not the harness reminder's model-named Co-Authored-By, which the pre-push hook refuses. The worktree is removed as the last step, after this report.",
      "tests": "No test file applies: the diff is two ADR files. Every reading below is on the final head 569c58e52 (git rev-parse --short HEAD). node scripts/platform-object-tenancy-census.mjs --json exits 0. All 22 objects the note names read reach out, tenantField null, and sys_user_preference reads reach in, tenantField organization_id. The ADR's sys_ names cross-check to exactly those 23. No ablation: no tested behaviour changed.",
      "gates": "Final head 569c58e52. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths exits 0 and derives 19 commands from 2 paths against merge base 78652c093. Each was run with its exit code captured before any pipe, and all 19 exit 0: check-adr-links (+ --self-test) '770 relative link destination(s) under docs/adr/ resolve'; check-adr-symbol-anchors (+ --self-test) '2357 anchors across 142 records resolve'; check-ci-filter-parity; check-closing-keyword-parity (+ --self-test); check-comment-mask-corpus; lint check:doc-formula-expressions (first run exit 3, PREREQUISITE NOT MET, unbuilt formula and lint; it exits 0 after the build); check:adr-anchors 'OK (75 anchored file(s) ...)'; check:cross-package-test-inputs; check:doc-authoring; check:driver-memory-census; check:gitlink-declared; check:nul-bytes 'OK (scanned 12257 text file(s) ... no raw ASCII control bytes)'; check:pm-governed-merges; check:pm-prior-rulings; check:refd-timer-probe; check:watch-hint-literal. --ran with 'command :: exit N' lines exits 0: 'Run reconciliation — 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 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)'. node scripts/check-platform-object-tenancy-census.mjs exits 0 (round 0, unchanged files). CI check-runs on 569c58e52 were read once: 22 runs. Two completed: 'No other open PR may claim the same issue' success, and 'Packed-tarball smoke (opt-in)' skipped. Twenty are in_progress: Check Changeset, Check Documentation Links, Check PR Size, Governed Surface Queue Guard, the seven Lint lanes, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, the four Type Check lanes, and filter. NOT MEASURED: every CI family, reason: in_progress at the single read.",
      "mcp_calls": "0 — no MCP tool was called (reads were REST GETs through gh api)",
      "api_writes": "3 across this claim. Round 0 had 1: os-dev-report comment 6256329876. This round has 2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft forced) plus POST /repos/objectstack-ai/objectstack/issues/24408/assignees ['zhuangjianguo']; (2) this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/24339/comments. Also 3 git pushes (the empty branch, 271903357, 569c58e52), which are not REST. No label write, no reviewer request, no ready flip, no PATCH.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24408 comment 6256615681 · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T23:42Z · contract review PASS 6256600360 · Tier H: awaiting an approval by os-zhuang or hotlong (both requested), or the maintainer's merge.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #24408 → 97cb5c5a82 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T00:40Z

    probe reading
    PR object approved by os-zhuang 2026-10-19T00:32Z on head 569c58e52e. os-zhuang also marked it ready and enqueued it. Merged 2026-10-19T00:40Z out of the merge queue, as merge commit 97cb5c5a821cb1e07b0e0026db9923bdbc860863 on parent ee882b845f.
    origin/main git merge-base --is-ancestor 97cb5c5a82 origin/main → exit 0
    merged vs reviewed head git diff ee882b845f 97cb5c5a82 and the reviewed head 569c58e52e over its merge base 78652c093c share git patch-id --stable 23782802. Record chain: the dev's stop on P1, 6256329876; seat answer A, 6256351977; contract review PASS, 6256600360; seat record (maintainer brief and seat check), 6256615681.
    queue branch no gh-readonly-queue/main/pr-24408-* ref remains
    mis-close scan The body's only closing keyword is Fixes #24339. The only issue closed in the merge window is this card.

    What now holds on main:

    Downstream: both blockers of #24340 and #24341 (#24338 and #24339) are now closed. The seat re-derived each card's blockers in this act and releases both to pm:queue (notes on each).

    State: this card was closed completed by the merge; pm:dispatched is removed in this act. Claim 6256231065 ends here.


    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:specpriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions