Repository navigation
Commit aee9c47
feat(spec): declare features.sso on the auth config response (#29455)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9JdWLq8FiHUnLyAiWdBNVWa)_
Fixes #29344
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — `git grep` for `features.sso` and `auth/config`
under `skills/` finds 0 files; `content/docs/permissions/sso.mdx`
already tells readers that `/auth/config` serves `features.sso` when SSO
is configured, which stays true; the API reference page is regenerated
in this diff
## 维护者速读
- 改了什么:spec 的 `GET /api/v1/auth/config` 响应契约 `AuthFeaturesConfigSchema`
新增可选布尔键 `sso`(服务端早就在发、登录页早就在读,只是契约没声明)。
- 为什么改:按契约解析响应的客户端会把这个键静默丢掉,看不到「用 SSO 登录」入口;声明后与服务端实发一致。
- 风险与回滚:纯加宽,以前能解析的响应原样解析;没有默认值,不发就仍是不存在;运行时代码一行未动;回滚即撤掉此 PR。
-
先看:`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
beside `ssoEnforced`, 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 (the `isSsoUsable` answer, not the coarse
wired flag, falling open to the wired flag when providers cannot be
counted) and how it differs from `ssoEnforced` (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.
- Regenerated: `packages/spec/authorable-surface/api.json`
(`api/AuthFeaturesConfig:sso`, written by the build) and
`content/docs/references/api/auth-endpoints.mdx` (`gen:docs`, the one
artifact `check:generated` named stale). The same set the
`features.cloudManaged` precedent (#25292, PR #25309) touched on the
spec side.
- Changeset `.changeset/29344-auth-config-features-sso.md`:
`@objectstack/spec` `minor`, both declaration lines.
**Producer readings** (origin/main `96fb7b5d`):
- `packages/plugins/plugin-auth/src/auth-manager.ts` `getPublicConfig()`
sets `sso: this.isSsoWired()` (the coarse "plugin wired" flag).
- Both HTTP doors that answer `GET /auth/config` refine it to
`isSsoUsable()` before answering: plugin-auth's `/config` route
(`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
getPublicConfig` over `packages/` 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
no `domain:server` finding.
**Classification:** `PUBLIC_AUTH_FEATURES.sso`
(`packages/spec/src/kernel/public-auth-features.ts`) is `surface:
'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 to `AuthFeaturesConfigSchema`'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/liveness` finds nothing), the same as for `cloudManaged`.
**No forced test edits.** No existing test was changed.
## Why
`GET /api/v1/auth/config` serves `features.sso` and the console's login
form reads it (`ui/packages/auth/src/LoginForm.tsx`,
`config?.features?.sso === true`, through a hand-typed `sso?: boolean`
in `ui/packages/auth/src/types.ts`), as does the mobile shell per the
card. The contract did not declare it, so a client parsing through
`GetAuthConfigResponseSchema` dropped it silently.
One correction to the card's premise text:
`ui/packages/app-shell/src/runtime-config.ts`'s `body.features.sso ===
true` reads `GET /api/v1/runtime/config` (the plan-entitlement flag the
cloud-connection `RuntimeConfigPlugin` passes through), a different
response. The premise still holds through the LoginForm reader above.
## Tests
`packages/spec/src/api/auth-endpoints.test.ts`, new block
`AuthFeaturesConfig.sso (served, now declared)`, 8 cases:
- a served `sso: true` and `sso: false` parse and are kept;
- the whole `GetAuthConfigResponseSchema` parse keeps `features.sso`
beside `ssoEnforced`;
- CONTROL: an absent `sso` stays absent (no default is invented);
- a non-boolean `sso` (`"yes"`, `1`, `null`) is refused with exactly one
`invalid_type` issue at path `sso`, expected `boolean`;
- the describe names `isSsoUsable` and `ssoEnforced` and carries no
tracker id.
<details><summary>Local readings (head 0d81446)</summary>
- Build and regen: `pnpm --filter @objectstack/spec build` under
os-verify-lock, then `check:generated` exit 1 naming one stale artifact
(`check:docs`); `gen:docs` VERDICT 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).
- Consumer closure: `turbo run build --concurrency=2
--filter='./packages/**'` VERDICT 0 (78 tasks, 0 cached).
- `@objectstack/client` typecheck (it re-exports `AuthFeaturesConfig` /
`GetAuthConfigResponse`): VERDICT 0. Reverse check that the consumer
reads the rebuilt declarations: a probe file in `packages/client` typing
`{ sso: true }` as `AuthFeaturesConfig` from `@objectstack/spec/api`
compiles (exit 0), and `{ sso: 'yes' }` is refused TS2322 (exit 2).
Probe deleted.
- `@objectstack/plugin-auth` full suite (the producer, incl. the
`/config` route and `isSsoUsable` tests): VERDICT 0, Test Files 211
passed (211), Tests 3643 passed, 10 skipped.
- hono adapter (`hono-auth-mount-basepath`,
`hono-head-dispatched-as-get`, `hono-thrown-message-disclosure`, the
files that drive its `/auth/config` door): Test Files 3 passed (3),
Tests 33 passed.
- ui, from `ui/`: `packages/auth/src/__tests__/LoginForm.test.tsx` (the
real `/auth/config` reader) +
`packages/app-shell/src/runtime-config.test.ts` (named by the card):
Test Files 2 passed (2), Tests 85 passed.
- Ablation: `sso` renamed to `ssoAblated` in the schema with
`scripts/ablation-replace.mjs` (anchor 1 to 0, blob `fb278750` to
`8c4aae77`): `auth-endpoints.test.ts` exit 1, 7 failed, CONTROL green as
expected. Restored: blob equal to HEAD `fb278750`, `git diff HEAD`
empty.
- Dogfood battery: n/a, no `!` in the title and no `(narrowing)` arm.
</details>
<details><summary>Gates (dispatch-gates --commands)</summary>
- At head `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-test` first exited 3
(PREREQUISITE NOT MET: its pinned fixture commit was outside the shallow
clone); after `git fetch --depth=1 origin 621a487…` it exited 0 (48
cases).
- `dispatch-gates --ran`: 103 derived, 103 run, 0 NOT-MEASURED (a
derived zero), 0 UNRUN.
- Before the gates: `turbo run build --concurrency=2
--filter='./packages/**'` under os-verify-lock, VERDICT 0 (78 tasks).
- Offline PR-body checks with this body: `check-changeset-no-major.mjs
--base origin/main --event` exit 0 (level axis `yes (widening)` +
`added`, spec `minor`); `check-partof-closing-keyword.mjs` exit 0.
- Scoped lint: `pnpm exec eslint --no-inline-config` over the two
changed .ts files, exit 0.
- Local-only families: `check:skill-frame-freshness` exit 0;
`check:dev-prereqs` exit 1 because 35 of 106 workspace packages (the
`ui/` tree among them; only `packages/**` was built here) have no dist
in this container: a machine-state reading, not a finding of this diff
(`packages/spec` dist is fresh).
- CI: the seat reads the PR's check-runs.
</details>
## Acceptance notes
- **Same-class drift, larger than this card.**
`getPublicConfig().features` serves at least nine more keys
`AuthFeaturesConfigSchema` does not declare: `multiOrgEnabled`,
`tenancyPosture`, `audiencePosture`, `degradedTenancy`, `oidcProvider`,
`deviceAuthorization`, `admin`, `termsUrl`, `privacyUrl` (most are
classified in `PUBLIC_AUTH_FEATURES` or
`PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS`, and several have readers, e.g.
`deviceAuthorization` in 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.ts` keeps a hand-written mirror of the
features type (its `sso` comment 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 `sso` goes to the maintainer as
`needs-user-decision`.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (scoped to the changed
files locally; the repo-wide run is CI's).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmPzZFPzq2roY9uo1me)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c268b72 commit aee9c47
5 files changed
Lines changed: 73 additions & 1 deletion
File tree
- .changeset
- content/docs/references/api
- packages/spec
- authorable-surface
- src/api
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
| |||
159 | 160 | | |
160 | 161 | | |
161 | 162 | | |
162 | | - | |
| 163 | + | |
163 | 164 | | |
164 | 165 | | |
165 | 166 | | |
| |||
186 | 187 | | |
187 | 188 | | |
188 | 189 | | |
| 190 | + | |
189 | 191 | | |
190 | 192 | | |
191 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
| 218 | + | |
218 | 219 | | |
219 | 220 | | |
220 | 221 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
132 | 133 | | |
133 | 134 | | |
134 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
135 | 179 | | |
136 | 180 | | |
137 | 181 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
230 | 230 | | |
231 | 231 | | |
232 | 232 | | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
233 | 242 | | |
234 | 243 | | |
235 | 244 | | |
| |||
0 commit comments