docs(adr): ADR-0134 mirrors the open half of cloud ADR-0071 — env-side SCIM, and this platform writes the deactivation - #16476
Merged
Conversation
…e SCIM, with the ban written by this platform Mirrors the mechanism half of `cloud ADR-0071` (Enterprise Identity — SCIM v1, 2026-06-27) into this repo under the maintainer's 2026-09-02 ruling on #14496: D1 (adopt @better-auth/scim), D2 (the Service Provider lives in the env), D3 (single-organization; active:false disables, DELETE never erases), D4 (default standing only, group->role a declared seam) and the open half of D7 (CI coverage). D5, D6 and the real-IdP E2E stay in cloud and are cited as `cloud ADR-0071`. The record states, and this branch re-measured, the vendor-version fact: at the pinned @better-auth/scim 1.7.2 the substring `ban` occurs zero times in the package (1.6.30: 14, incl. resolveSCIMActiveDeactivation; removed in 1.7.0), so the disable is written by this platform's identity.reconcileUser hook and judged at the engine by the break-glass last-administrator guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
os-warren
pushed a commit
that referenced
this pull request
Sep 7, 2026
…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-zhuang
approved these changes
Sep 7, 2026
os-zhuang
marked this pull request as ready for review
September 7, 2026 06:36
os-zhuang
enabled auto-merge
September 7, 2026 06:36
Collaborator
Author
PM 独立复核 —— PASS独立 worktree,checkout 到 head
⭐ 这份镜像最有价值的一段,是它纠正了 cloud 原记录cloud ADR-0071(2026-06-27)把停用描述为
⇒ 上游在 1.7.0 移除了 ban。今天写这个 ban 的是本平台自己: ⭐ 这是镜像存在的意义的最好例证:它不是把 cloud 记录抄一遍,而是把今天真实的机制写对,并把那句话标了日期——因为 D1 把一个第三方放上了离职停用路径,机制的真相从此是一个版本事实。 与本轮组织包搬迁(#16215 / ADR-0132)的关系PM 实测:本文件不受影响。它的商业半边是 cloud ADR-0071 的 D5 / D6(开闭边界的治理、以及「付费 = 受治理 / 受支持 / 可审计的 SCIM」),与多组织运行时开源无关;文中「单组织 V1、多组织留缝」的表述在 (受影响的是姊妹镜像 ADR-0133 / PR #16267,已更新并复核。) ⛔ 保持 draft,等人工合并。 PM 未 flip ready、未 arm auto-merge、未 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14507
⛔ GOVERNED SURFACE — draft PR, human merge.
docs/adr/**is on the governed register, so no seat flips this ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14). Verified on this branch, both directions:node scripts/pm/check-governed-merges.mjs --test docs/adr/0134-env-side-scim-provisioning.mddocs/adr/** x1 — architecture decision records)node scripts/pm/check-governed-merges.mjs --test packages/plugins/plugin-auth/src/auth-manager.tsWhat this writes
One new record,
docs/adr/0134-env-side-scim-provisioning.md(312 lines, the only file in the diff). It is a mirror of the open, mechanism half ofcloud ADR-0071(Enterprise Identity — SCIM v1, Status there: Proposed, 2026-06-27), written in this repo's own words under the maintainer's 2026-09-02 ruling on #14496 (option 2 — mirror the open half, do not move files, do not renumber).The number 0134 was assigned by the PM seat, not chosen as "the next free slot":
origin/maintops out at 0131, 0132 is the sibling mirror card #14506, and 0133 is taken by the in-flight PR #16267 (card #14508 — confirmed on this branch by diffing that remote head, which addsdocs/adr/0133-org-management-open-basics.md). 0134 is free onorigin/mainand on every remote head.Restated, with the cloud record's own numbering kept so the two documents read side by side:
@better-auth/scimas the mechanism; the seven vendor models are bridged tosys_scim_*platform objects, never reimplemented.plugins.scim/OS_SCIM_ENABLED; connections are runtime data resolved by an app-owned bearer verifier.active:falsedisables and revokes sessions,DELETEnever erases the user (the vendor tombstones the source and the disable arrives through the same path).What stays in cloud (cited as
cloud ADR-0071, not restated here)D5 (the open/closed boundary), D6 (the commercial repositioning: paid = governed / supported / auditable SCIM), the real-IdP end-to-end run on cloud's staging, and the Enterprise Identity pillar framing. The record says so explicitly and adds nothing the cloud record did not decide.
The vendor-version fact — re-measured on this branch, not copied from the card
The card carried a reading; per the dispatch this branch measured it independently, on the packages themselves.
@better-auth/scimbanindist/index.mjsidentity.reconcileUserhookresolveSCIMActiveDeactivation, which setsbanned: trueandbanReasonto the literalDeactivated via SCIMThe pin, read from this worktree:
packages/plugins/plugin-auth/package.jsondeclares^1.7.2,pnpm-lock.yamlresolves@better-auth/scim@1.7.2, and the installed package's ownpackage.jsonreports1.7.2. In the installed 1.7.2 the reconciliation path calls the optional host hookidentity.reconcileUserwith the aggregate state and then, when the aggregate is inactive, deletes the user's sessions — and writes no ban.Ban ownership, stated as the record states it: this platform writes it, not the vendor.
auth-manager.ts'sreconcileScimUserLifecycleimplements the host hook and lands the disable throughuser-ban-write.ts'sapplyUserBan/SCIM_DEACTIVATION_BAN_REASON— field for field the write theadminmount makes — and it is judged at the engine by the break-glass last-administrator guard. Theadminplugin is still required because it supplies the column and theBANNED_USERsign-in refusal, which is what keepsassertScimAdminCoherencecoherent. Session revocation stayed with the vendor. This matches the card, and the wiring is already onmain(#14360), so nothing here waited on it.Code anchors — every one opened and confirmed on this branch
All in symbol-anchor form (
path#symbol);pnpm check:adr-symbol-anchorsresolves every one of them.packages/plugins/plugin-auth/src/auth-manager.ts— thescim({...})options block,resolveScimEnabled,assertScimAdminCoherence,reconcileScimUserLifecycle,SCIM_PROTOCOL_PATH_PREFIX,isScimProtocolPathpackages/plugins/plugin-auth/src/user-ban-write.ts—applyUserBan,applyUserUnban,SCIM_DEACTIVATION_BAN_REASON,UserBanWriterpackages/plugins/plugin-auth/src/last-admin-guard.ts—registerLastAdminGuard;packages/plugins/plugin-auth/src/admin-ban-endpoints.ts—runAdminBanUser/runAdminUnbanUserpackages/plugins/plugin-auth/src/scim-connection-service.ts—verifyScimBearerToken;packages/plugins/plugin-auth/src/objectql-adapter.ts—AUTH_MODEL_TO_PROTOCOLpackages/platform-objects/src/identity/sys-scim-*.object.ts— all eight objects, incl.SysScimProjectionGrant(the D4 seam) andSysScimConnectionCredential.organization_id(the D3 seam)packages/spec/src/system/auth-config.zod.ts— theplugins.scimandplugins.admindescribe blocks;packages/spec/src/identity/scim.zod.ts—SCIM_SCHEMASClause-②: no.
packages/spec/**was read as an anchor and is not in the diff; nothing needed changing there.One reader-safety fact the record states in its Provenance, because it is load-bearing for #14361:
docs/adr/0071-*in this repo is Dataset semantic-layer depth, an unrelated record. A bareADR-0071in identity code therefore resolves, by this repo's own convention, to the wrong document. Always writecloud ADR-0071for the SCIM record andADR-0134for this one.Changeset
None —
skip-changeset, which is what every recentdocs/adr/**-only landing did (245c6a23c,932acc3df,3a534facb,e6c99643eeach land the ADR alone, with no.changeset/*.md). The judgement is the rule, not the list: this PR publishes nothing from any package.Gates — the full derived family, exit codes captured before any pipe
Derived mechanically:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat commit5b987ca64(change set from the merge base, three-dot). 17 commands, all run, all green.node scripts/check-adr-links.mjsnode scripts/check-adr-links.mjs --self-testnode scripts/check-adr-symbol-anchors.mjsnode scripts/check-adr-symbol-anchors.mjs --self-testnode scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjspnpm --filter @objectstack/lint run check:doc-formula-expressionsturbo run build --filter=@objectstack/formula --filter=@objectstack/lintpnpm check:adr-anchorspnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:refd-timer-probepnpm check:watch-hint-literalRun at head
5b987ca64, which is this PR's final commit. Every exit code was captured withcmd > log 2>&1; EXIT=$?— never through a pipe. The one exit 3 is reported as NOT MEASURED, then as a real green after its declared prerequisite was built; it is not counted as a pass in the first state.Not claimed as measured: the whole-tree families
dispatch-gatesdeclares too wide to place, the three that take a value from the workflow, and the 41 artifact rosters — CI runs the farm.🤖 Generated with Claude Code
https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
Generated by Claude Code