Skip to content

docs(adr): ADR-0135 mirrors the open half of cloud ADR-0024 — identity & access architecture - #16478

Draft
os-warren wants to merge 2 commits into
mainfrom
claude/issue-14506-adr-mirror-identity-access
Draft

docs(adr): ADR-0135 mirrors the open half of cloud ADR-0024 — identity & access architecture#16478
os-warren wants to merge 2 commits into
mainfrom
claude/issue-14506-adr-mirror-identity-access

Conversation

@os-warren

@os-warren os-warren commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14506

⚠️ RENUMBERED 0132 → 0135 (2026-09-07) — this is deliberate, ⛔ please do not "correct" it back. 0132 is free on main, but claimed on the branch of an open PR: #16215 adds docs/adr/0132-multi-organization-runtime-is-open-core.md. The first commit on this branch used 0132 (the number assigned before that scan existed); the second git mvs it to 0135 and moves the record's single self-citation with it. History was not rewritten and nothing was force-pushed.

Claim map, measured here by a full git diff --name-only origin/main...<pr-head> -- docs/adr/ over all 27 open PRs — a file-list scan, not a title scan:

number claimed by state
≤ 0131 origin/main (1ecee3e53) landed
0132 #16215 open PR
0133 #16267 (card #14508, third mirror) open PR
0134 #16476 (card #14507, sibling mirror) open PR
0135 this PR previously unclaimed

GOVERNED SURFACE — draft PR, human merge. The diff is one file under docs/adr/**. No seat flips this ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14). Verified mechanically rather than recalled, both directions, on the final filename:

reading command exit
positive (this PR's path) node scripts/pm/check-governed-merges.mjs --test docs/adr/0135-identity-and-access-architecture.md 3 — GOVERNED (docs/adr/** ×1)
negative control node scripts/pm/check-governed-merges.mjs --test packages/plugins/plugin-auth/src/auth-manager.ts 0 — NOT governed

What this writes

docs/adr/0135-identity-and-access-architecture.md — one new file, 272 lines, nothing else touched.

It records in this repository, in this repository's own words, the half of objectstack-ai/cloud ADR-0024 (Status: Accepted, founder, 2026-06-25) whose mechanism governs open code here. Under the maintainer's ruling of 2026-09-02 on #14496 (verbatim 「ok」 to option 2): mirror the open half, ⛔ do not move files, ⛔ do not renumber the cloud record. Shape copied from docs/adr/0079-record-display-name.md — a ## Provenance — read this before citing this file section first, naming the cloud record and its date, declaring that the cloud record is the original and keeps the commercial half, and declaring that this file was written from the cloud record's decision list plus the implementing code by a seat with no cloud access.

The slug is unchanged across the renumber and deliberately reuses the cloud original's slug, on the precedent ADR-0079 states in its own Provenance section ("deliberately reuses the original's slug … so the two are recognisably one record").

The clauses recorded, and the code anchor each one carries

Every anchor below is a symbol anchor (path#symbol), was opened and read before it was written, and resolves under node scripts/check-adr-symbol-anchors.mjs — line-number anchors are a hard finding in this corpus with no transition period, so there are none (the gate's own summary line: "0 line anchors survive").

clause recorded as anchors
D1 per-environment identity, never centralised better-auth is mounted and built in the environment's own process and persists through the environment's own data engine auth-plugin.ts#AuthPlugin, auth-manager.ts#AuthManager, objectql-adapter.ts#AUTH_MODEL_TO_PROTOCOL
D2 two user populations, two sources of truth externally-owned vs environment-owned identities; the platform does not collapse them into one authority sys-user.object.ts#source (idp_provisioned / env_native), sys-account.object.ts
D4 source-of-truth marking, managed vs env-native marked at two levels — the table (managedBy: 'better-auth') and the row (sys_user.source, stamped by two writers because the two creation paths do not share a seam) sys-user.object.ts#managedBy, sys-member.object.ts#managedBy, sys-sso-provider.object.ts#managedBy, plus the account.create.after stamp in auth-manager.ts and the SCIM-safe afterInsert stamp in auth-plugin.ts
D5 identity from the IdP, authorization in the env authentication may be delegated; authorization never is — standing is derived from the environment's own grant tables resolve-authz-context.ts#resolveUserAuthzGrants, #hasPlatformAdminStanding, invitation-role-cap.ts#isOrgAdminGrade, member-role-canonical.ts#registerMemberRoleCanonicalization
D5.2 the local user-management surface under SSO split by population: managed users' credential actions hide; break-glass keeps a local credential reachable; an environment may never be left with zero administrators who can sign in sys-user.object.ts#change_my_password, #change_my_email, #set_user_password, set-initial-password.ts#runSetInitialPassword, last-admin-guard.ts#registerLastAdminGuard
D6 SSO per production environment the registered-provider table, mutations routed through the vendor's own endpoints, and the opt-in DNS domain verification this tree cites as ADR-0024 ② sys-sso-provider.object.ts#SysSsoProvider, #request_domain_verification, #verify_domain, #domain_verified, register-sso-provider.ts, auth-schema-config.ts
D7 portability / self-host preserved every mechanism above is in this repo under Apache-2.0 and switched on by env var in self-host (OS_SSO_ENABLED, OS_SSO_DOMAIN_VERIFICATION) auth-manager.ts
D9 env users live in the env; org membership via better-auth sys_user and sys_member are rows in the environment's database, maintained by the auth stack's endpoints and only read by the guards sys-member.object.ts#SysMember, objectql-adapter.ts#AUTH_MODEL_TO_PROTOCOL

What stays in cloud, still cited as cloud ADR-0024

D3 cloud-as-IdP hub · D5.1 the cloud operator-portal membership gate · D8 billing · D10 prod/dev metering and population lifecycle · the V1 roadmap and commercial framing. The record carries this as a table, with a consequence stated for #14361: a bare ADR-0024 in this tree is not mechanically re-pointable, because some sites mean a clause that stays in cloud — auth-manager.ts cites ADR-0024 V1 for the SSO default-role provisioning. That re-pointing is #14361's per-site call and is not touched here. The env-side SCIM mechanism is cloud ADR-0071's, mirrored by #16476, and is likewise not restated here.

⛔ No decision is added and none is widened. Anything cloud ADR-0024 did not decide is not decided by this file, and the record says so in its own "What this record does NOT settle" section.

⛔ The record cites no number that does not yet exist under docs/adr/ — in particular not the sibling mirrors' 0133 / 0134, which check-adr-anchors would read as a squat.

Measurements I took myself

⚠️ Both re-checks the card lists were attempted; one could not be run and is declared, not faked.

1. git -C ../cloud show origin/main:docs/adr/0024-... — SKIPPED, not run. This seat has no cloud checkout and no cloud access. Nothing here is derived from the cloud file; the record's Provenance section states that limitation in the document itself, so a future reader is not misled about what was checked.

2. The bare-citation count — measured here, and it does not reproduce the card's figure.

git grep -n -E "ADR-0024" -- packages/plugins/plugin-auth/src packages/platform-objects/src/identity | grep -v -i "cloud ADR-0024" | wc -l
tree filtered (the card's command) unfiltered
5a9138703 (this branch's base) 64 76
00ff228fe (the commit the card cites) 59 71

So the card's # expect ~71 is the unfiltered count at 00ff228fe — the annotation and the command it is attached to disagree by the grep -v line. The record quotes 64 (this branch's own base), and labels it as such. Not a blocker for this card; it does matter to #14361, whose scope figure comes from the same reading.

3. docs/adr/PRIORITIZATION.md — NOT registered, deliberately. The card says to register the new file there if that index is maintained by hand, judged from the file's own header. Its own header says it is not an index:

⚠️ STALE (noted 2026-07-16): this review predates ADR-0050 onward … Treat it as a historical snapshot, not current state. The Status: header of each ADR file … is the authority; this document is retained for its method and its ranked-plan rationale.

It is a dated 2026-06-12 review of "All 49 ADRs in docs/adr/", not a registry, and docs/adr/ holds no other index file. Adding a 2026-09 record to a snapshot of 2026-06 would falsify it. No ADR since 0050 is listed there either.

Gates — the full table, mechanically derived

Derived, not recalled: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (17 commands), re-derived after the renumber commit and byte-identical to the earlier derivation. The union below was re-run in full at 3d7762e3e, the head of this PR; each exit code was captured immediately after its own redirect, never through a pipe.

# command exit
1 node scripts/check-adr-links.mjs 0
2 node scripts/check-adr-links.mjs --self-test 0
3 node scripts/check-adr-symbol-anchors.mjs 0
4 node scripts/check-adr-symbol-anchors.mjs --self-test 0
5 node scripts/check-ci-filter-parity.mjs 0
6 node scripts/check-closing-keyword-parity.mjs 0
7 node scripts/check-closing-keyword-parity.mjs --self-test 0
8 node scripts/check-comment-mask-corpus.mjs 0
9 pnpm --filter @objectstack/lint run check:doc-formula-expressions 0
10 pnpm check:adr-anchors 0
11 pnpm check:cross-package-test-inputs 0
12 pnpm check:doc-authoring 0
13 pnpm check:driver-memory-census 0
14 pnpm check:nul-bytes 0
15 pnpm check:pm-governed-merges 0
16 pnpm check:refd-timer-probe 0
17 pnpm check:watch-hint-literal 0

⚠️ On a fresh worktree #9 is an exit 3, and an exit 3 is NOT a green. check-doc-formula-expressions refuses with PREREQUISITE NOT MET — the workspace package @objectstack/formula is not built and measures nothing. Cleared both times by building the two declared prerequisites (turbo run build --filter=@objectstack/formula --filter=@objectstack/lint, run under scripts/pm/os-verify-lock.sh) and re-running, which is the 0 in the table.

Three further families the derivation names as NOT MEASURED here, quoted rather than silently omitted: check-cross-package-test-inputs --union-into …, check-shard-attestation --emit … and check-test-completeness … each take a value from the workflow that has none outside a CI run. The derivation also reports 10 families whose declared population is too wide to place and 41 artifact-roster families scored silent for every card in the tree — ⛔ neither group is a clearance, and none of their rosters sits in a directory this PR's single path is in.

Changeset

None, and skip-changeset applied — this diff publishes nothing from any package. Not from memory: the three most recent ADR-only landings in this repo did exactly this — #14976 (ADR-0131), #14151 (ADR-0130) and #12519 (ADR-0127) each landed with no .changeset/ file and the skip-changeset label, read back off the PRs themselves. The one recent counter-example proves the rule: #13067 (ADR-0129) carried a changeset because it also changed packages/spec and plugin-auth code.

Not in scope, filed separately

A stale filename found while verifying anchors: last-admin-ban-guard.ts has not existed since the guard was renamed to last-admin-guard.ts, yet five comments still point at it (invitation-role-cap.ts, objectql-adapter.ts, break-glass-local-credential.test.ts ×2, and objectql-adapter.test.ts pointing at a last-admin-ban-guard.test.ts that does not exist). Filed as #16477 (label finding, unassigned); ⛔ not fixed here — this PR touches docs/adr/** only. #16477 remains open.

…y & access architecture

Records, in this repository, the mechanism half of `objectstack-ai/cloud`
ADR-0024 (Accepted there 2026-06-25) whose code lives here: per-environment
identity (D1), the two user populations (D2), the managed-vs-env-native
source-of-truth marking (D4), identity-from-the-IdP with authorization decided
in the environment (D5) and the SSO-era local user-management surface (D5.2),
per-environment SSO with opt-in domain verification (D6), preserved
portability and self-host (D7), and env-local users with better-auth-backed
organization membership (D9).

Every clause carries a symbol anchor into this tree. The commercial half —
D3, D5.1, D8, D10 and the V1 roadmap — stays in cloud and is still cited as
`cloud ADR-0024`; a table in the record says so, because the re-pointing of
today's bare citations is per-site and semantic, not a search-and-replace.

No decision is added or widened. Files are not moved and numbers are not
reassigned, per the maintainer ruling of 2026-09-02.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…aimed by an open PR)

`0132` is free on `main` but claimed on the branch of an open PR — #16215 adds
`docs/adr/0132-multi-organization-runtime-is-open-core.md`. Verified here by a
full `git diff --name-only origin/main...<pr-head> -- docs/adr/` over all 27
open PRs, not by titles: 0132 is #16215's, 0133 is #16267's, 0134 is #16476's,
and 0135 is unclaimed.

File renamed with `git mv`, slug unchanged. The record's one self-citation (its
title line) moves with it; the `ADR-0105` and `ADR-0108` references are landed
records and are untouched. No line-number anchors were introduced — the file
still carries none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren os-warren changed the title docs(adr): ADR-0132 mirrors the open half of cloud ADR-0024 — identity &amp; access architecture docs(adr): ADR-0135 mirrors the open half of cloud ADR-0024 — identity & access architecture Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

PM 独立复核 —— PASS。改号已落实,三个门我自己重跑过

复核在一个独立 worktree 里做,checkout 到 PR head 3d7762e3e,⛔ 不采信席位报告,⛔ 未使用共享主 checkout(本会话已因它的陈旧工作树出过一次实错)。

门禁,PM 自跑

我的读数
check-governed-merges.mjs --test docs/adr/0135-identity-and-access-architecture.md EXIT=3 · GOVERNED ✅ ⇒ 保持 draft、人工合并、⛔ 永不 arm auto-merge
check-adr-symbol-anchors.mjs EXIT=01985 anchors across 136 records resolve — 224 symbol (202 declaration, 22 literal), 1740 file-level, 21 cross-repo, 6 exempt, 3 continuation. 0 line anchors survive.
check-adr-anchors.mjs EXIT=0

交付面

检查 读数
三点 diff 1 个文件:docs/adr/0135-identity-and-access-architecture.md
残留 0132 0 处
path:NNN 行锚点 0 处
首节 ## Provenance — read this before citing this file(第 29 行,⇒ 是第一节,照 ADR-0079 体例)
引用 cloud ADR-0024 ×11 · cloud ADR-0071 ×3 · ADR-0135(自引)×1 · ADR-0108 ×1 · ADR-0105 ×1 ⇒ ⭐ 限定词拼写与 #14496 裁定一致,⛔ 无裸号
⛔ 未引用未落地的姊妹号 确认(无 0133 / 0134)⇒ 不触发 check-adr-anchors 的 squat 判定

⭐ 决策覆盖面,逐条对过

### 决策标题实测为 D1 · D2 · D4 · D5 · D5.2 · D6 · D7 · D9 —— 恰好是卡面列举的开源半边,一条不多一条不少

D3 · D5.1 · D8 · D10 没有作为决策标题出现(只在 ## What stays in cloud ADR-0024 一节里被提及)。⇒ 卡面「⛔ nothing decided that the cloud record did not decide」的要求,在结构上成立。

席位对我的两处更正,我接受

  1. 编号核实的方法,席位的比我的强。 我做的是「标题 + 已知 diff」的扫描,并如实标注了它弱于逐 PR 全量 diff。席位实际做的是逐个 fetch 全部开放 PR 的 head ref、对每个跑 git diff --name-only origin/main...<head> -- docs/adr/,零 API 配额。⇒ 0135 未被任何开放 PR 认领这一结论,是按更强的方法量出来的。
    ⚠️ 席位同时正确指出:这仍是时点读数 —— 之后新开的 PR 仍可能认领 0135,除了落地顺序没有别的东西能拦。
  2. ⛔ 一处措辞纠正,免得传下去。 [Decision] Where does a decision that governs OPEN code live? — cloud (private) ADRs are cited from this public repo in 60 files with a qualifier and ~110 more times bare, squatting on unrelated local numbers (0024 · 0071 · 0081); mirror them here, or qualify only #14496 裁定里的 「⛔ 不搬文件、不重编号」 说的是 cloud 记录的编号(文件不搬家,cloud ADR-0024 保留它自己的号)。它不禁止给本仓一份尚未落地的镜像文件改号 —— 也就是刚做的这件事。PR 正文已写明这一点,免得审阅者对着裁定误以为改号违规。

⚠️ 开放 PR 计数不一致,如实记下

我读到 29,席位读到 27,都来自 list_pull_requests state=open perPage=100。两次读数时点不同(我的在前,其间有 PR 落地,例如 #16459 是在这段时间内合并的),所以这大概率是漂移而非矛盾 —— ⛔ 但我没有实测证明这一点,只标注不下结论。两份 claim map 完全一致,结论不受影响。

⛔ 席位诚实标注的两处未验

  • cloud 侧 re-check 跳过 —— 席位没有 cloud 仓访问权,卡面那条 git -C ../cloud show … 的复检未跑,已在 ADR 自己的 Provenance 段与卡上报告里声明为 SKIPPED。⭐ 这是对的:⛔ 不许假装跑过。
  • 后续杂事的编号要跟着改:卡面要求接受本 PR 的席位去 cloud 仓给 ADR-0024 加一行指针,那行现在必须写 0135,⛔ 不是 0132。

状态

保持 draft,等人工合并。 PM 未 flip ready、未 arm auto-merge、未 update_pull_request_branch


Generated by Claude Code

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/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants