Repository navigation
feat(spec): declare features.sso on the auth config response - #29455
Conversation
AuthFeaturesConfigSchema gains the optional boolean `sso` that GET /api/v1/auth/config already serves (refined to "usable" by isSsoUsable) and the console login form reads. Claude-Session: https://claude.ai/code/session_01ADHkmPzZFPzq2roY9uo1me Co-authored-by: Claude <noreply@anthropic.com>
…ce for features.sso Claude-Session: https://claude.ai/code/session_01ADHkmPzZFPzq2roY9uo1me Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6e016402d734596609e6ed858ac6a4b0d0c7e6bd && git checkout 6e016402d734596609e6ed858ac6a4b0d0c7e6bd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aca58cdb62bdb8ed350f0b45885728374ca1f6ad 0d814466bcee1734d0ae90b71458f7ed412c5784 && git checkout -B drift-repro aca58cdb62bdb8ed350f0b45885728374ca1f6ad && git merge --no-ff 0d814466bcee1734d0ae90b71458f7ed412c5784
node scripts/docs-audit/affected-docs.mjs --json aca58cdb62bdb8ed350f0b45885728374ca1f6ad |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 0d81446 · 2026-11-02T15:45Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut 待裁:新公开键名
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs read: card #29344 (body and all five comments, rulings and the os-dev-report 6581383101 included), PR #29455 (body, five-file list, net diff against merge base ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (os-dev-report 6581383101), each answered:
Three readings the brief asked for:
④ Design judgment
Holds:
Tried and did not break:
Observations, not findings on this file: the hono door's refinement has no test of its own ( Findings: must-fix — none. Should-fix — (1) Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地,只等名字。契约复审在现 head 判 PASS(6581701011),CI 全绿。 席位核对 — ACCEPT ·
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Withdrawn: the name question on this PR. The pm-dispatch skill carries no rule that a new public name needs the maintainer's approval; that line lived only in this seat's standard order. Under SKILL.md 〈升级与决策〉 a name escalates only when the options truly diverge on product semantics or contract shape and no existing norm decides; here the dev's name is fit and no fork exists, so it is the seat's call: the name stays as built. Prompted by the maintainer's question at 2026-11-02T17:03Z. |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41489495736 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41491862089 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Steve · project thread
Fixes #29344
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none —
git grepforfeatures.ssoandauth/configunderskills/finds 0 files;content/docs/permissions/sso.mdxalready tells readers that/auth/configservesfeatures.ssowhen SSO is configured, which stays true; the API reference page is regenerated in this diff维护者速读
GET /api/v1/auth/config响应契约AuthFeaturesConfigSchema新增可选布尔键sso(服务端早就在发、登录页早就在读,只是契约没声明)。packages/spec/src/api/auth-endpoints.zod.ts(新键与说明)、packages/spec/src/api/auth-endpoints.test.ts(新测试)、.changeset/29344-auth-config-features-sso.md。pnpm --filter @objectstack/spec exec vitest run src/api/auth-endpoints.test.ts,预期 31 个测试全部通过。sso(PR 上挂 needs-user-decision);其余席位落地。What changes
AuthFeaturesConfigSchema.sso(packages/spec/src/api/auth-endpoints.zod.ts), an optional boolean besidessoEnforced, no default. Its describe says what the producer means: true when enterprise SSO is usable (the domain-routed SSO plugin is wired AND at least one SSO provider is configured), false when SSO is off or on with no provider yet, absent means not usable. It names which producer value is served (theisSsoUsableanswer, not the coarse wired flag, falling open to the wired flag when providers cannot be counted) and how it differs fromssoEnforced(that one takes the password form away; this one only says whether the SSO entry exists). The tracker id sits in an adjacent//comment only.packages/spec/authorable-surface/api.json(api/AuthFeaturesConfig:sso, written by the build) andcontent/docs/references/api/auth-endpoints.mdx(gen:docs, the one artifactcheck:generatednamed stale). The same set thefeatures.cloudManagedprecedent (spec: declare the console flag features.cloudManaged — a PUBLIC_AUTH_FEATURES entry with a hide-when-on gate semantics, and AuthFeaturesConfigSchema declares it (child of #25083, ruling 6341690661 Q3 A) #25292, PR feat(spec, platform-objects): declare features.cloudManaged with a hide-when-on gate, and hide the six doors that add people while it is on #25309) touched on the spec side..changeset/29344-auth-config-features-sso.md:@objectstack/specminor, both declaration lines.Producer readings (origin/main
96fb7b5d):packages/plugins/plugin-auth/src/auth-manager.tsgetPublicConfig()setssso: this.isSsoWired()(the coarse "plugin wired" flag).GET /auth/configrefine it toisSsoUsable()before answering: plugin-auth's/configroute (auth-plugin.ts,if (config.features?.sso) config.features.sso = await isSsoUsable()) and the hono adapter's auth route (packages/adapters/hono/src/index.ts, same refinement).git grep getPublicConfigoverpackages/finds no third door. So a client always sees the usable answer; the unrefined value is read only inside the server (the boot sign-in reachability check,auth-plugin.ts), which no client sees. No producer disagreement a client can observe, so nodomain:serverfinding.Classification:
PUBLIC_AUTH_FEATURES.sso(packages/spec/src/kernel/public-auth-features.ts) issurface: 'login',opt-in, exempt with the reason "the served value is refined to usable via isSsoUsable()": it agrees with the new describe. No pin ties the registry's keys toAuthFeaturesConfigSchema's keys (the plugin-auth drift guard ties the registry to the served booleans, not to the schema), so nothing there moved. See Acceptance notes.No liveness row.
packages/spec/liveness/governs metadata types; no ledger names this API response (git grep AuthFeaturesConfig packages/spec/livenessfinds nothing), the same as forcloudManaged.No forced test edits. No existing test was changed.
Why
GET /api/v1/auth/configservesfeatures.ssoand the console's login form reads it (ui/packages/auth/src/LoginForm.tsx,config?.features?.sso === true, through a hand-typedsso?: booleaninui/packages/auth/src/types.ts), as does the mobile shell per the card. The contract did not declare it, so a client parsing throughGetAuthConfigResponseSchemadropped it silently.One correction to the card's premise text:
ui/packages/app-shell/src/runtime-config.ts'sbody.features.sso === truereadsGET /api/v1/runtime/config(the plan-entitlement flag the cloud-connectionRuntimeConfigPluginpasses through), a different response. The premise still holds through the LoginForm reader above.Tests
packages/spec/src/api/auth-endpoints.test.ts, new blockAuthFeaturesConfig.sso (served, now declared), 8 cases:sso: trueandsso: falseparse and are kept;GetAuthConfigResponseSchemaparse keepsfeatures.ssobesidessoEnforced;ssostays absent (no default is invented);sso("yes",1,null) is refused with exactly oneinvalid_typeissue at pathsso, expectedboolean;isSsoUsableandssoEnforcedand carries no tracker id.Local readings (head 0d81446)
pnpm --filter @objectstack/spec buildunder os-verify-lock, thencheck:generatedexit 1 naming one stale artifact (check:docs);gen:docsVERDICT 0; committed with the source.pnpm --filter @objectstack/spec test: VERDICT command-exit 0, Test Files 791 passed (791), Tests 23474 passed, 1 todo.src/api/auth-endpoints.test.ts+src/kernel/public-auth-features.test.ts: 57 passed.pnpm --filter @objectstack/spec typecheck: VERDICT 0 (test layer compiles; 131 pinned signatures held).turbo run build --concurrency=2 --filter='./packages/**'VERDICT 0 (78 tasks, 0 cached).@objectstack/clienttypecheck (it re-exportsAuthFeaturesConfig/GetAuthConfigResponse): VERDICT 0. Reverse check that the consumer reads the rebuilt declarations: a probe file inpackages/clienttyping{ sso: true }asAuthFeaturesConfigfrom@objectstack/spec/apicompiles (exit 0), and{ sso: 'yes' }is refused TS2322 (exit 2). Probe deleted.@objectstack/plugin-authfull suite (the producer, incl. the/configroute andisSsoUsabletests): VERDICT 0, Test Files 211 passed (211), Tests 3643 passed, 10 skipped.hono-auth-mount-basepath,hono-head-dispatched-as-get,hono-thrown-message-disclosure, the files that drive its/auth/configdoor): Test Files 3 passed (3), Tests 33 passed.ui/:packages/auth/src/__tests__/LoginForm.test.tsx(the real/auth/configreader) +packages/app-shell/src/runtime-config.test.ts(named by the card): Test Files 2 passed (2), Tests 85 passed.ssorenamed tossoAblatedin the schema withscripts/ablation-replace.mjs(anchor 1 to 0, blobfb278750to8c4aae77):auth-endpoints.test.tsexit 1, 7 failed, CONTROL green as expected. Restored: blob equal to HEADfb278750,git diff HEADempty.!in the title and no(narrowing)arm.Gates (dispatch-gates --commands)
0d814466: 103 derived commands, all 103 exit 0 (each with stdin closed, exit captured before any pipe).node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 (PREREQUISITE NOT MET: its pinned fixture commit was outside the shallow clone); aftergit fetch --depth=1 origin 621a4876…it exited 0 (48 cases).dispatch-gates --ran: 103 derived, 103 run, 0 NOT-MEASURED (a derived zero), 0 UNRUN.turbo run build --concurrency=2 --filter='./packages/**'under os-verify-lock, VERDICT 0 (78 tasks).check-changeset-no-major.mjs --base origin/main --eventexit 0 (level axisyes (widening)+added, specminor);check-partof-closing-keyword.mjsexit 0.pnpm exec eslint --no-inline-configover the two changed .ts files, exit 0.check:skill-frame-freshnessexit 0;check:dev-prereqsexit 1 because 35 of 106 workspace packages (theui/tree among them; onlypackages/**was built here) have no dist in this container: a machine-state reading, not a finding of this diff (packages/specdist is fresh).Acceptance notes
getPublicConfig().featuresserves at least nine more keysAuthFeaturesConfigSchemadoes not declare:multiOrgEnabled,tenancyPosture,audiencePosture,degradedTenancy,oidcProvider,deviceAuthorization,admin,termsUrl,privacyUrl(most are classified inPUBLIC_AUTH_FEATURESorPUBLIC_AUTH_CONFIG_NON_FLAG_KEYS, and several have readers, e.g.deviceAuthorizationin the console's device-auth page). A contract parse strips them all. No pin ties the registry to the schema keys. Reported to the seat as one family card, not filed here.ui/packages/auth/src/types.tskeeps a hand-written mirror of the features type (itsssocomment says "wired", while the served value is "usable"). Not touched; noted.Verification
See the readings above. The PR stays a draft; the seat runs the contract review, and the new public key name
ssogoes to the maintainer asneeds-user-decision.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (scoped to the changed files locally; the repo-wide run is CI's).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code