You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] SCIM forces the better-auth admin plugin on (pluginConfig.admin ?? scimEffective) and a deployment has no declared way to decline it — split from #13439 by the 2026-08-31 ruling #13816
Split out of #13439 by the maintainer's ruling (2026-08-31, director batch #19; the approved recommendation for #13439 was A for the three keys, with the admin amplification split to its own card). #13439 now carries only the three-key half — declare scim / sso / ssoDomainVerification, explicit config outranks env. This card records the amplification half so it does not evaporate with that fix.
:5064 — the advertised-feature surface mirrors the same resolution.
Any deployment where SCIM ends up enabled — including via OS_SCIM_ENABLED alone — gets better-auth's admin plugin mounted as a side effect: impersonate-user, set-user-password, ban-user. A deployment that never asked for the admin surface has the same non-existent means of declining it that #13439 documented for scim itself. This is a larger permission surface than the three keys.
The coupling is NOT an accident. The comment block above :2426 records it explicitly: SCIM's active:false deprovisioning path runs through the admin plugin (ban), and org-scoped tokens need the organization plugin — see ADR-0071. So "declare admin tri-state and let a host decline it while SCIM is on" is not a config-reading fix: declining admin under SCIM plausibly breaks SCIM deprovisioning semantics. Deciding it needs an ADR-0071 revisit, which would have widened the p1 fix that objectstack-ai/cloud#1451 is parked on.
The question this card carries
Should a host be able to decline the admin surface in a SCIM-enabled deployment — and if so, what happens to SCIM's active:false → ban path? Candidate shapes, neither costed:
scope the admin dependency internally to what SCIM actually needs (ban), instead of mounting the full admin action set;
declare admin tri-state with a documented conflict refusal at construction time when SCIM is on and admin is explicitly false — a loud conflict instead of silent forcing.
Claim: session_01Mciyv38maJ6HYVMiaM26T1 (domain:spec execution seat, dispatch batch 5) · branch claude/issue-13816-scim-admin-tristate-conflict. Executing the 2026-09-01 director-batch-#21 ruling (comment 5491056008): Shape 2, immediate — declare admin tri-state on the auth-config surface; when SCIM is effective and admin is explicitly false, refuse loudly at construction time with a documented conflict (no more silent forcing). Measurement card #14150 (action-granularity + better-auth upgrade evaluation) runs in parallel and is NOT this dispatch; ⛔ Shape 1 and any ADR-0071 revision are out of scope here — the ruling records Shape 2 as the honest floor pending #14150's readings. Clause-②: yes (declared-surface widening + a new construction-time refusal) ⇒ contract-review tier; in-seat review before enqueue. Declared file surface: packages/spec/src/system/auth-config.zod.ts (+ regen), packages/plugins/plugin-auth/src/auth-manager.ts (the :2426/:2439/:5064 resolution sites), tests, changeset — the same file family as the landed sibling #13439 → PR #14066, whose three-key precedence shape this extends.
{
"issue": 13816,
"status": "done",
"branch": "claude/issue-13816-scim-admin-tristate-conflict",
"pr": "https://github.com/objectstack-ai/objectstack/pull/14246",
"head": "9238222d3",
"premise_still_valid": true,
"summary": "Shape 2 implemented per the 2026-09-01 director-batch-#21 ruling (comment 5491056008). plugins.admin is now tri-state (z.boolean().optional(), the #14066 shape, same file as its scim/sso siblings); effective SCIM beside an explicit admin:false is refused loudly with a documented conflict error naming both keys, the ADR-0071 coupling, and both exits (accept admin, or disable SCIM). The refusal runs at three entry points: AuthManager constructor, applyConfigPatch (merged result), and buildPluginList (env read after boot). The three '?? scimEffective' fallbacks are kept - absent admin under effective SCIM still forces the admin plugin on. New resolveScimEnabled() is the single scim-effective decision point shared by the mount, the advertised features flag, and the assert. The default move is declared in DEFAULT_CHANGES_BY_MAJOR under major 17 (the #4666 ratchet accepted it); authorable-defaults and reference docs regenerated. No wire code added: the refusal is a boot-time throw (assertAudienceConfig class), never an HTTP envelope - check:dispatcher-error-vocabulary green confirms no ledger entry is due.",
"files": [
"packages/spec/src/system/auth-config.zod.ts",
"packages/spec/scripts/lib/default-changes.ts",
"packages/spec/authorable-defaults/system.json (regen)",
"content/docs/references/system/auth-config.mdx (regen)",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/auth-manager.test.ts",
".changeset/scim-admin-tristate-conflict-refusal.md"
],
"consumer_package_tests": {
"@objectstack/plugin-auth": "pnpm --filter @objectstack/plugin-auth test at 9238222d3: Test Files 87 passed (87), Tests 1803 passed (1803); includes 7 new pins (4 refusal + 3 acceptance); the six #14066 precedence tests still green",
"@objectstack/spec": "pnpm --filter @objectstack/spec test at 9238222d3: Test Files 449 passed (449), Tests 12052 passed (12052)",
"typecheck": "spec exit 0 and plugin-auth exit 0 at 9238222d3; NOTE plugin-auth tsconfig excludes its test files from typecheck with no sibling test-tsconfig, so the edited test file is vitest-exercised only - pre-existing repo-wide gap already tracked by open finding 14062, no new filing"
},
"gates": {
"dispatch_listed_all_at_9238222d3": {
"check-adr-0087-registration": "exit 0",
"check-ci-filter-parity": "exit 0",
"check-comment-mask-adoption": "exit 0",
"check-cross-package-test-inputs (script + pnpm)": "exit 0",
"check-dev-prereqs": "exit 0 - 67 package artifacts present; spec built from sources on disk",
"check-keyed-text-bounds": "exit 0",
"check-plugin-teardown-shape": "exit 0",
"check-shard-attestation": "exit 0",
"check-tenant-audit-census": "exit 0",
"check-test-completeness": "NOT MEASURED - exit 3 PREREQUISITE NOT MET by its own contract (grades a saved CI test log; unreachable locally); left to CI",
"check-undeclared-dep-imports": "exit 0",
"docs-audit affected-docs + drift-comment": "exit 0",
"lint check:doc-formula-expressions": "exit 0",
"spec check:authorable-surface / docs / empty-state / liveness / llms-txt / skill-refs / strictness-ledger / variant-docs / yaml-examples": "all exit 0",
"check:changeset-gate-self-tests": "exit 0",
"check:dispatcher-error-vocabulary": "exit 0 - no new dispatcher-reachable code site",
"check:doc-authoring": "exit 0 after one fix: the refusal message initially carried the tracker id 13816 in string prose (gate red); id removed from the string, kept in comments, rerun green",
"check:dual-build-cjs-loads / logger-receiver-detach / merge-driver / page-declaration-shape / published-files / slot-lookup / spec-parsed-alias / test-source-alias / type-source-resolution": "all exit 0",
"check:type-check-debt": "exit 0 re-measured on merged tree - 27 entries, none above recorded, ledger untouched"
},
"derived_beyond_dispatch_list": {
"note": "dispatch-gates.mjs no-paths derivation at 9238222d3; re-derived after the final merge, list unchanged",
"check-changeset-no-major / doc-frontmatter / doc-route-spelling / docs-section-name / empty-changeset / section-landing-index / pm check-half-states / release-rehearsal-clone --self-test": "all exit 0",
"lint check:doc-security-posture": "exit 0",
"spec check:api-surface / browser-reachable-entries / dual-source-exports / entry-nameability / export-origins / exported-any / skill-examples / generated": "all exit 0 - check:generated reproduces all 15 artifacts byte-identical",
"check:corpus-claim-drift / doc-anchors / docs-audit-scope / docs-redirects / docs-single-h1 / objectql-double-limit / objectui-changeset / pm-governed-merges / pm-half-states / published-readme-links / quick-reference-counts / react-page-adapter-contract / role-word / skill-identifier-liveness / vendor-version-stamps / where-matcher": "all exit 0",
"check:nul-bytes": "exit 0"
},
"default_move_ratchet": "gen:schema initially refused the .default(false) removal (#4666 guard); declared as 'system/AuthPluginConfig:admin false to (none)' under major 17 in default-changes.ts with full reason; build accepts and prints it"
},
"ablation": "assertScimAdminCoherence neutralized via perl insert; mutation proved on disk (marker grep 0 to 1); exactly the 4 refusal tests red (4 failed / 263 passed), acceptance tests stayed green; restore via 'git checkout HEAD --' then proven by blob identity (git hash-object equals HEAD blob, git diff HEAD empty). Test imports in-package src, so no dist rebuild leg applies - stated, not assumed.",
"derived_judgments": [
"Default-removal parse-consumer census: zero runtime parse consumers of AuthConfigSchema/AuthPluginConfigSchema (the runtime receives plain objects); the two live plugins.admin read points are the ?? chains in auth-manager.ts (buildPluginList + the /auth/config features mirror), absent-key behavior byte-identical; the admin-gated 501 paths gate on the mounted API (typeof authApi.createUser), not on config; AuthPluginConfigParsed has zero external consumers; the z.input type is unchanged, z.infer moves from boolean to boolean-or-undefined",
"Tri-state semantics: absent / true / false are three distinct authored states; a parse no longer materializes false onto silent documents, making parse output faithful to the runtime raw-config contract (a parse-materialized false would have silently defeated the ADR-0071 forced coupling)",
"Refusal trigger surface: exactly one corner refuses (admin explicitly false AND scim effective); proven-accepted shapes: scim:false + admin:false beside ambient OS_SCIM_ENABLED (explicit wins - declining both is legal), admin:false with SCIM off, admin:true anywhere, absent admin under SCIM (forced on, unchanged). Deployment-visible: a config already carrying the contradiction previously booted with silently broken SCIM deprovisioning and now refuses to construct with the remedy in the message - the ruled loud stop"
],
"semver": "spec minor + plugin-auth minor (launch-window MINOR rule): declared-surface change plus a new refusal behavior, no removal or rename; ADR-0087 marker not-required (no-migration-prescription) - a conflicted config needs a human choice between accepting admin and disabling SCIM, nothing mechanical to rewrite",
"boundary_flags": [
"Shape 1 (internal admin-dependency narrowing) and ADR-0071 revision: not touched - #14150 carries the measurement",
"better-auth upgrade evaluation: not touched - split to #14150 by the maintainer wording",
"scim/sso/ssoDomainVerification keys and their #14066 precedence chain: untouched except one ADR-0071-coupling sentence in the scim docblock/describe, amended because the old wording (the admin-unset clause) became inaccurate under the refusal - flagged, not silently reworded",
"SCIM deactivation semantics: untouched",
"not merged, no auto-merge - landing is the PM seat contract review"
],
"mcp_calls": "0 - card+comments via the public-repo payload channel and repo-scoped REST (probe 200); PR and comments via REST; dedup via REST finding-label listing + local grep",
"open_questions": [],
"out_of_scope_findings": [
"plugin-auth test files compile under no tsc program (tsconfig excludes them, no sibling test tsconfig) - already covered by open finding 14062 (no plugin package compiles its tests), so no new card filed"
]
}
Reviewed PR #14246 at head 9238222d (7 files +324/−12, three-dot verified; no forbidden surface — content/docs/releases/** untouched, skills/** untouched). Tier fuse read before review: last_served_model = claude-fable-5 (= CONTRACT_REVIEW_TIER); in-seat review per the 2026-08-31 ruling.
Contract judgment list:
Derived judgments — verified from hunks. (a) AuthPluginConfigSchema.admindefault(false) → tri-state optional(): the parse ACCEPT set is unchanged; the parse OUTPUT change (no materialized false) is declared in DEFAULT_CHANGES_BY_MAJOR under major 17 with an airtight omitted-path argument — plugin-auth resolves the wired plugin set from the RAW config, so deployed behavior on the silent path is byte-identical, and the kept ?? scimEffective fallbacks preserve the ADR-0071 forcing exactly. (b) The one new REJECT is the ruled corner precisely: effective SCIM + explicit admin: false refused loudly at all three config entry doors (constructor, applyConfigPatch merged result, buildPluginList where the env var is actually read — the post-boot-env hole covered), with the documented message naming both keys, ADR-0071, and both exits. resolveScimEnabled() unifies mount / advertised-features / refusal on one decision point (the [finding] plugins.scim / plugins.sso are undeclared config keys that read as configuration but cannot ever change the outcome — the env var always outranks them #13439 explicit-over-env order preserved), so the three can never disagree. (c) No ADR-0112 wire code owed (boot-time throw, assertAudienceConfig class; check:dispatcher-error-vocabulary green) — consistent with precedent.
Evidence accepted as reported: plugin-auth 87/1803 (7 new pins), spec 449/12052, ablation exactly-4-red with blob-verified restore, gate union green at the merged head, check:generated current.
PASS ⇒ stripping needs:contract-review from both carriers in this same stroke; proceeding to landing (governed test on final file list → ready → auto-merge queue).
Reading note from #14150 (the measurement this card's 2026-09-01 ruling asked for; report 14150#issuecomment-5503768456). PM seat, domain:services, session_01AUF1NoViznQK32gqpK8wS8. No state change on this card — its ruling stands until the maintainer says otherwise.
No upgrade is in play: dist-tags.latest is 1.7.2 for all five better-auth packages, exactly what is pinned.
Shape 1 is therefore feasible on the current version, without an upgrade, and per this card's own fork shape 2 (three-state + construction-time loud refusal) is transitional, not terminal.
Activity
os-warren commentedon Aug 31, 2026
分诊定级 →
domain:spec·needs-user-decision· p1 · security。摘finding。这张卡是我今天上午在 #13439 上提的那一问的答案的另一半。 我当时问「admin 放大项是随 A 同批裁,还是单独立卡」,维护者答(批 #19)单独立卡 —— 就是本卡。⇒ #13439 已转
pm:queue(复核过,标签已正确),本卡承接被拆出来的那一半。锚定。 机制在⚠️ 若最终裁定走第 1 个形状(内部收窄 admin 依赖,不动声明面),落点全在
packages/plugins/plugin-auth/src/auth-manager.ts(domain:services面),但两个候选形状里的第 2 个要声明admin三态 ⇒ 触packages/spec⇒ 按唯一所有者规则归domain:spec。plugin-auth⇒ 改挂domain:services。现按「会触 spec 的那个选项」挂,并写明改挂条件。保留
needs-user-decision,理由是两条独立成立的人工地板admin插件:impersonate-user/set-user-password/ban-user。一个从未要求过管理面的部署,没有任何声明手段拒绝它。这比 [finding]plugins.scim/plugins.ssoare undeclared config keys that read as configuration but cannot ever change the outcome — the env var always outranks them #13439 那三个键是更大的权限面。:2426上方的注释块明确记着 SCIM 的active:false停用路径经由 admin 插件(ban),org-scoped token 需要 organization 插件,见 ADR-0071。⇒ 「声明 admin 三态、让 host 在 SCIM 开着时拒绝它」不是一个读配置的修复 —— 拒绝 admin 很可能打断 SCIM 的停用语义。裁它要重审 ADR-0071。⇒ ⛔ 分诊不代裁。
定级 p1
不是因为今天有东西坏了 —— 今天的行为是设计如此且有 ADR 背书。判 p1 是因为受影响的是权限面,而缺的是拒绝的手段:一个只想要 SCIM 用户同步的部署,无法说「我不要 impersonate/ban/set-password」。在一个多租户平台上,「无法拒绝一个管理面」本身就是 p1 级的属性,与它是否被滥用无关。
⭐ 且它与 #13439 是同一条链上的两环:#13439 落地后,host 终于能显式说
scim: false了 —— 但只要 SCIM 在任何路径上被打开,admin 仍然自动跟上。⇒ 不裁本卡,#13439 的修复会给出一个「我控制住了 SCIM」的错觉,而权限放大照旧。这一点请在裁决时一并考虑:两张卡合起来才是完整答案。需要你答的那一句
卡片给了两个候选形状,都未计价,我按它的原样转述并各补一句代价判断:
admin三态,并在 SCIM 开着而admin显式为 false 时于构造期以文档化的冲突拒绝 —— 响亮的冲突,而不是静默的强制。⇒ 代价最小、最快,且符合本仓「声明即强制」的方向;但它把问题交还给部署方(你要么接受 admin,要么关掉 SCIM),没有解决「只想要同步、不想要管理面」这个真实诉求。⇒ 我的读数:若 ADR-0071 的耦合确实不可拆,形状 2 是诚实的下限(至少让作者知道自己在接受什么,而不是无声获得);若形状 1 可行,它才是真的修复。⛔ 但这依赖一次我没做的测量 —— better-auth 的 admin 插件能否按动作粒度挂载。建议裁决前先要这一个读数。
Refs:#13439(已裁的姊妹卡,
pm:queue)· ADR-0071 · objectstack-ai/cloud#1451 · objectstack-ai/cloud#1265。Generated by Claude Code
huangyiirene commentedon Sep 1, 2026
裁决:条件裁 —— 形状 2 即刻排队;测量卡 #14150 同笔立,加测 better-auth 升级(维护者 2026-09-01,总监批 #21)
项目总监席 · session
session_01KGtaLpkW1mycWgkbSb3H6t维护者逐字:「13816 还要同时评估是否需要升级 better-auth」+ 对呈报推荐「其他同意」。合并为:
admin三态;SCIM 生效而admin显式为false时,构造期以文档化的冲突响亮拒绝 —— 不再静默强制。落点触packages/spec(声明面)⇒ 条款②契约复审档位适用。状态转移(同笔)
needs-user-decision→pm:queue;priority:p1securitydomain:spec不动。#14150 独立排队,两卡可并行(2 不堵 1 的路,先 2 后 1 是加法)。Generated by Claude Code
os-support-ai commentedon Sep 1, 2026
Claim:
session_01Mciyv38maJ6HYVMiaM26T1(domain:spec execution seat, dispatch batch 5) · branchclaude/issue-13816-scim-admin-tristate-conflict. Executing the 2026-09-01 director-batch-#21 ruling (comment 5491056008): Shape 2, immediate — declareadmintri-state on the auth-config surface; when SCIM is effective andadminis explicitlyfalse, refuse loudly at construction time with a documented conflict (no more silent forcing). Measurement card #14150 (action-granularity + better-auth upgrade evaluation) runs in parallel and is NOT this dispatch; ⛔ Shape 1 and any ADR-0071 revision are out of scope here — the ruling records Shape 2 as the honest floor pending #14150's readings.Clause-②: yes(declared-surface widening + a new construction-time refusal) ⇒ contract-review tier; in-seat review before enqueue. Declared file surface:packages/spec/src/system/auth-config.zod.ts(+ regen),packages/plugins/plugin-auth/src/auth-manager.ts(the:2426/:2439/:5064resolution sites), tests, changeset — the same file family as the landed sibling #13439 → PR #14066, whose three-key precedence shape this extends.Generated by Claude Code
feat(spec,plugin-auth): declare plugins.admin tri-state; refuse expli…
chore(spec): regen auth-config docs + authorable-defaults for the adm…
fix(plugin-auth): drop tracker id from the conflict-refusal message (…
claude commentedon Sep 1, 2026
os-dev-report
{ "issue": 13816, "status": "done", "branch": "claude/issue-13816-scim-admin-tristate-conflict", "pr": "https://github.com/objectstack-ai/objectstack/pull/14246", "head": "9238222d3", "premise_still_valid": true, "summary": "Shape 2 implemented per the 2026-09-01 director-batch-#21 ruling (comment 5491056008). plugins.admin is now tri-state (z.boolean().optional(), the #14066 shape, same file as its scim/sso siblings); effective SCIM beside an explicit admin:false is refused loudly with a documented conflict error naming both keys, the ADR-0071 coupling, and both exits (accept admin, or disable SCIM). The refusal runs at three entry points: AuthManager constructor, applyConfigPatch (merged result), and buildPluginList (env read after boot). The three '?? scimEffective' fallbacks are kept - absent admin under effective SCIM still forces the admin plugin on. New resolveScimEnabled() is the single scim-effective decision point shared by the mount, the advertised features flag, and the assert. The default move is declared in DEFAULT_CHANGES_BY_MAJOR under major 17 (the #4666 ratchet accepted it); authorable-defaults and reference docs regenerated. No wire code added: the refusal is a boot-time throw (assertAudienceConfig class), never an HTTP envelope - check:dispatcher-error-vocabulary green confirms no ledger entry is due.", "files": [ "packages/spec/src/system/auth-config.zod.ts", "packages/spec/scripts/lib/default-changes.ts", "packages/spec/authorable-defaults/system.json (regen)", "content/docs/references/system/auth-config.mdx (regen)", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-auth/src/auth-manager.test.ts", ".changeset/scim-admin-tristate-conflict-refusal.md" ], "consumer_package_tests": { "@objectstack/plugin-auth": "pnpm --filter @objectstack/plugin-auth test at 9238222d3: Test Files 87 passed (87), Tests 1803 passed (1803); includes 7 new pins (4 refusal + 3 acceptance); the six #14066 precedence tests still green", "@objectstack/spec": "pnpm --filter @objectstack/spec test at 9238222d3: Test Files 449 passed (449), Tests 12052 passed (12052)", "typecheck": "spec exit 0 and plugin-auth exit 0 at 9238222d3; NOTE plugin-auth tsconfig excludes its test files from typecheck with no sibling test-tsconfig, so the edited test file is vitest-exercised only - pre-existing repo-wide gap already tracked by open finding 14062, no new filing" }, "gates": { "dispatch_listed_all_at_9238222d3": { "check-adr-0087-registration": "exit 0", "check-ci-filter-parity": "exit 0", "check-comment-mask-adoption": "exit 0", "check-cross-package-test-inputs (script + pnpm)": "exit 0", "check-dev-prereqs": "exit 0 - 67 package artifacts present; spec built from sources on disk", "check-keyed-text-bounds": "exit 0", "check-plugin-teardown-shape": "exit 0", "check-shard-attestation": "exit 0", "check-tenant-audit-census": "exit 0", "check-test-completeness": "NOT MEASURED - exit 3 PREREQUISITE NOT MET by its own contract (grades a saved CI test log; unreachable locally); left to CI", "check-undeclared-dep-imports": "exit 0", "docs-audit affected-docs + drift-comment": "exit 0", "lint check:doc-formula-expressions": "exit 0", "spec check:authorable-surface / docs / empty-state / liveness / llms-txt / skill-refs / strictness-ledger / variant-docs / yaml-examples": "all exit 0", "check:changeset-gate-self-tests": "exit 0", "check:dispatcher-error-vocabulary": "exit 0 - no new dispatcher-reachable code site", "check:doc-authoring": "exit 0 after one fix: the refusal message initially carried the tracker id 13816 in string prose (gate red); id removed from the string, kept in comments, rerun green", "check:dual-build-cjs-loads / logger-receiver-detach / merge-driver / page-declaration-shape / published-files / slot-lookup / spec-parsed-alias / test-source-alias / type-source-resolution": "all exit 0", "check:type-check-debt": "exit 0 re-measured on merged tree - 27 entries, none above recorded, ledger untouched" }, "derived_beyond_dispatch_list": { "note": "dispatch-gates.mjs no-paths derivation at 9238222d3; re-derived after the final merge, list unchanged", "check-changeset-no-major / doc-frontmatter / doc-route-spelling / docs-section-name / empty-changeset / section-landing-index / pm check-half-states / release-rehearsal-clone --self-test": "all exit 0", "lint check:doc-security-posture": "exit 0", "spec check:api-surface / browser-reachable-entries / dual-source-exports / entry-nameability / export-origins / exported-any / skill-examples / generated": "all exit 0 - check:generated reproduces all 15 artifacts byte-identical", "check:corpus-claim-drift / doc-anchors / docs-audit-scope / docs-redirects / docs-single-h1 / objectql-double-limit / objectui-changeset / pm-governed-merges / pm-half-states / published-readme-links / quick-reference-counts / react-page-adapter-contract / role-word / skill-identifier-liveness / vendor-version-stamps / where-matcher": "all exit 0", "check:nul-bytes": "exit 0" }, "default_move_ratchet": "gen:schema initially refused the .default(false) removal (#4666 guard); declared as 'system/AuthPluginConfig:admin false to (none)' under major 17 in default-changes.ts with full reason; build accepts and prints it" }, "ablation": "assertScimAdminCoherence neutralized via perl insert; mutation proved on disk (marker grep 0 to 1); exactly the 4 refusal tests red (4 failed / 263 passed), acceptance tests stayed green; restore via 'git checkout HEAD --' then proven by blob identity (git hash-object equals HEAD blob, git diff HEAD empty). Test imports in-package src, so no dist rebuild leg applies - stated, not assumed.", "derived_judgments": [ "Default-removal parse-consumer census: zero runtime parse consumers of AuthConfigSchema/AuthPluginConfigSchema (the runtime receives plain objects); the two live plugins.admin read points are the ?? chains in auth-manager.ts (buildPluginList + the /auth/config features mirror), absent-key behavior byte-identical; the admin-gated 501 paths gate on the mounted API (typeof authApi.createUser), not on config; AuthPluginConfigParsed has zero external consumers; the z.input type is unchanged, z.infer moves from boolean to boolean-or-undefined", "Tri-state semantics: absent / true / false are three distinct authored states; a parse no longer materializes false onto silent documents, making parse output faithful to the runtime raw-config contract (a parse-materialized false would have silently defeated the ADR-0071 forced coupling)", "Refusal trigger surface: exactly one corner refuses (admin explicitly false AND scim effective); proven-accepted shapes: scim:false + admin:false beside ambient OS_SCIM_ENABLED (explicit wins - declining both is legal), admin:false with SCIM off, admin:true anywhere, absent admin under SCIM (forced on, unchanged). Deployment-visible: a config already carrying the contradiction previously booted with silently broken SCIM deprovisioning and now refuses to construct with the remedy in the message - the ruled loud stop" ], "semver": "spec minor + plugin-auth minor (launch-window MINOR rule): declared-surface change plus a new refusal behavior, no removal or rename; ADR-0087 marker not-required (no-migration-prescription) - a conflicted config needs a human choice between accepting admin and disabling SCIM, nothing mechanical to rewrite", "boundary_flags": [ "Shape 1 (internal admin-dependency narrowing) and ADR-0071 revision: not touched - #14150 carries the measurement", "better-auth upgrade evaluation: not touched - split to #14150 by the maintainer wording", "scim/sso/ssoDomainVerification keys and their #14066 precedence chain: untouched except one ADR-0071-coupling sentence in the scim docblock/describe, amended because the old wording (the admin-unset clause) became inaccurate under the refusal - flagged, not silently reworded", "SCIM deactivation semantics: untouched", "not merged, no auto-merge - landing is the PM seat contract review" ], "mcp_calls": "0 - card+comments via the public-repo payload channel and repo-scoped REST (probe 200); PR and comments via REST; dedup via REST finding-label listing + local grep", "open_questions": [], "out_of_scope_findings": [ "plugin-auth test files compile under no tsc program (tsconfig excludes them, no sibling test tsconfig) - already covered by open finding 14062 (no plugin package compiles its tests), so no new card filed" ] }Generated by Claude Code
Generated by Claude Code
os-support-ai commentedon Sep 1, 2026
Collection review: ACCEPT · Clause-② contract review: PASS
Reviewed PR #14246 at head
9238222d(7 files +324/−12, three-dot verified; no forbidden surface —content/docs/releases/**untouched,skills/**untouched). Tier fuse read before review:last_served_model = claude-fable-5(= CONTRACT_REVIEW_TIER); in-seat review per the 2026-08-31 ruling.Contract judgment list:
AuthPluginConfigSchema.admindefault(false)→ tri-stateoptional(): the parse ACCEPT set is unchanged; the parse OUTPUT change (no materializedfalse) is declared inDEFAULT_CHANGES_BY_MAJORunder major 17 with an airtight omitted-path argument — plugin-auth resolves the wired plugin set from the RAW config, so deployed behavior on the silent path is byte-identical, and the kept?? scimEffectivefallbacks preserve the ADR-0071 forcing exactly. (b) The one new REJECT is the ruled corner precisely: effective SCIM + explicitadmin: falserefused loudly at all three config entry doors (constructor,applyConfigPatchmerged result,buildPluginListwhere the env var is actually read — the post-boot-env hole covered), with the documented message naming both keys, ADR-0071, and both exits.resolveScimEnabled()unifies mount / advertised-features / refusal on one decision point (the [finding]plugins.scim/plugins.ssoare undeclared config keys that read as configuration but cannot ever change the outcome — the env var always outranks them #13439 explicit-over-env order preserved), so the three can never disagree. (c) No ADR-0112 wire code owed (boot-time throw,assertAudienceConfigclass;check:dispatcher-error-vocabularygreen) — consistent with precedent.not-requiredwith a correct no-migration-prescription rationale. Boundary flags — none; the plugin-auth test-tsc gap correctly attributed to open [finding] No plugin package has a tsc program that compiles its tests — plugin-approvals alone hides 345 type errors, and 0 of 14 are in the test-typecheck ledger #14062, no duplicate filed.Evidence accepted as reported: plugin-auth 87/1803 (7 new pins), spec 449/12052, ablation exactly-4-red with blob-verified restore, gate union green at the merged head,
check:generatedcurrent.PASS ⇒ stripping
needs:contract-reviewfrom both carriers in this same stroke; proceeding to landing (governed test on final file list → ready → auto-merge queue).Generated by Claude Code
os-sales commentedon Sep 2, 2026
Reading note from #14150 (the measurement this card's 2026-09-01 ruling asked for; report 14150#issuecomment-5503768456). PM seat,
domain:services, session_01AUF1NoViznQK32gqpK8wS8. No state change on this card — its ruling stands until the maintainer says otherwise.@better-auth/scim@1.7.2SCIM needs nothing from the admin plugin: the ban coupling left the vendor in 1.7.0 (tarball-measured; [finding] SCIM active:false no longer disables the account — the vendor ban coupling was removed upstream in @better-auth/scim 1.7.0 and nothing in this repo replaced it #14360 carries the detail). The forcing atauth-manager.ts:2494/:2513/:5149rests on a belief that is no longer true of the vendor.dist-tags.latestis 1.7.2 for all five better-auth packages, exactly what is pinned.identity.reconcileUser, the admin plugin is needed only for its schema and init hooks (thebannedcolumn and the sign-in refusal), not for any of its 15 endpoints — so shape 1 is "narrow the forced mount to schema + init via the endpoints-record technique already landed atadmin-impersonate-endpoint.ts:198", not "drop the mount". Sequence: [finding] SCIM active:false no longer disables the account — the vendor ban coupling was removed upstream in @better-auth/scim 1.7.0 and nothing in this repo replaced it #14360 (limb A) → shape-1 card (Blocked-by: #14360) → this card's shape 2 re-evaluated against both.Generated by Claude Code