Skip to content

Commit 446c8b2

Browse files
docs(sso): both domain-verification steps answer 400 DOMAIN_VERIFICATION_DISABLED with the switch unset, and an unknown provider 404 RESOURCE_NOT_FOUND with it on (#22504)
Fixes #22493 Clause-②: no ## What changed `content/docs/permissions/sso.mdx`, the callout under **Domain verification (opt-in)**. Only the middle sentence changed. The callout's headline, its first clause and the identity-before-capability sentence are byte-identical. Docs only: no code, no test, no changeset. **Before** (`origin/main` `e148ca98`, `:281`–`:284`): > Both routes exist whether or not `OS_SSO_DOMAIN_VERIFICATION` is set — and with it unset the two halves report that differently, so match on the code rather than the status: step 1 answers **400 `DOMAIN_VERIFICATION_DISABLED`**, step 2 passes the inner **404** through with an explanatory message. **After:** > Both routes exist whether or not `OS_SSO_DOMAIN_VERIFICATION` is set — and with it unset there is no endpoint behind them, so both steps answer **400 `DOMAIN_VERIFICATION_DISABLED`**. With it on, either step answers an unknown `providerId` with **404 `RESOURCE_NOT_FOUND`**; step 2's `NO_PENDING_VERIFICATION` is a 404 too, so match on the code rather than the status. The page still tells readers to match on the code, but the reason is now true. With the switch on, step 2 answers both an unknown provider and a provider with no pending verification with a 404. Only the code tells them apart. ## The code these answers were read from All paths are under `packages/plugins/plugin-auth/src`, read at `e148ca98`: - `auth-plugin.ts:3084`–`:3089` and `:3102`–`:3107`: each mount runs `gateAdmin(c)` first. It then calls its bridge with `{ domainVerificationEnabled: this.authManager!.isSsoDomainVerificationEnabled() }`. - `register-sso-provider.ts:455` and `:511`: `if (options.domainVerificationEnabled === false) return domainVerificationDisabled();`. This line comes before the inner request is built, so the vendor is never asked. - `register-sso-provider.ts:370`–`:372`: `domainVerificationDisabled()` returns `{ status: 400, … code: 'DOMAIN_VERIFICATION_DISABLED' … }`. Both doors share this one helper. - `register-sso-provider.ts:464` and `:530`: `if (resp.status === 404 && !parsed?.code) return codelessVendorNotFound(providerId, options);` - `register-sso-provider.ts:380`–`:381`: when `domainVerificationEnabled === true`, that 404 becomes `{ status: 404, … code: 'RESOURCE_NOT_FOUND' … }`. - `register-sso-provider.ts:541`: a vendor error that carries a code passes through with the vendor's own status and code. This is how step 2's `NO_PENDING_VERIFICATION` keeps its 404. - `auth-manager.ts:7036`–`:7040`: `isSsoDomainVerificationEnabled()` is false unless SSO is wired. Otherwise it reads `config.plugins.ssoDomainVerification`, then `OS_SSO_DOMAIN_VERIFICATION`, and defaults to `false`. The not-found answer is the bridge's own code, `RESOURCE_NOT_FOUND`. The page quotes no vendor wording. ## Pins that hold these answers - `sso-domain-verification-unknown-provider.pin.test.ts` runs a real `AuthManager` behind the plugin's real route registration. Its blocks pin these answers: - `:196`: switch on, unknown provider: 404 `RESOURCE_NOT_FOUND` on both doors. - `:210`: switch off: 400 `DOMAIN_VERIFICATION_DISABLED` on both doors, and the vendor is never asked. - `:232`: switch on, existing provider, no pending verification: 404 `NO_PENDING_VERIFICATION` on verify-domain. - `:244`: anonymous: 401 `UNAUTHENTICATED` on both doors, with the switch on or off. - `packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts`: - It asserts the 401 `UNAUTHENTICATED` and 403 `PERMISSION_DENIED` sentence (`:614`–`:619`), and that the platform admin is not refused (`:627`–`:632`). - The `400 DOMAIN_VERIFICATION_DISABLED` at `:307` and `:312` is in a `note:` string. It is not asserted. The plugin-auth file above is what pins the off answer. - I did not run either test locally. This card is docs-only, so no test is owed. The `Test Core` check concluded `success` on `e148ca98`. - No docs gate checks this page's claims. The nearest one, `check:error-status-conformance`, reconciles statuses on `api/error-catalog.mdx` and `protocol/kernel/error-handling.mdx` only. No gate is added. ## Sweep for other stale copies `git grep` before the edit, at `e148ca98`: - `inner \*\*404\*\*\|passes the inner` over `content/docs/**`: 1 hit, `sso.mdx:284`. - `report that differently` over `content/docs/**`: 1 hit, `sso.mdx:282`. - Both patterns over the whole tree: only those two lines. - `DOMAIN_VERIFICATION_DISABLED` over `content/**`, `skills/**`, `docs/**`, `apps/docs/**` and `*.md`/`*.mdx`: `sso.mdx:283`, plus the two generated code lists (`references/api/contract.mdx:143` and `references/api/error-code-ledger.mdx:310`, code names only). The rest were the pending `.changeset` for the code change and the released `plugin-auth/CHANGELOG.md`, which this PR does not touch. The callout was the only stale copy. ## Local gates, at `620e67e2` - `node scripts/pm/dispatch-gates.mjs --commands` derived 43 commands from merge base `e148ca984`, with 1 path changed (+4/−3). - All 43 ran, and every one ended with exit 0. - Four first exited 3 with `PREREQUISITE NOT MET` because `@objectstack/lint`, `@objectstack/formula` and `@objectstack/client-react` were not built. Those runs measured nothing. After those packages were built, all four ran again and exited 0. The four were `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples` and `check:docs-transcript-drift`. - `--ran` reconciliation: `43 derived famil(ies) accounted for — 43 run, 0 NOT-MEASURED (a DERIVED zero — all 43 recorded an exit code and none of them is 3)`. The four the dispatch named: | command | exit | verdict line | |---|---|---| | `pnpm check:doc-authoring` | 0 | `doc authoring guard: 418 files clean — no bare metadata literals.` | | `pnpm check:doc-anchors` | 0 | `472 internal #fragment link(s) across 417 source file(s)` all land on a real heading | | `pnpm --filter @objectstack/lint run check:doc-security-posture` | 0 | `28 ObjectSchema.create example(s) in 230 marked block(s) across 254 prose file(s) in 2 root(s) carry an os validate-clean security posture` | | `pnpm --filter @objectstack/spec run check:docs` | 0 | `225 generated files in sync with packages/spec` | NOT MEASURED here because these are CI's own shell: `ci.yml · Build Docs` (the docs app build), the `Test Core` shards and the type-check lanes. ## Acceptance notes - On this page, "the switch" means `OS_SSO_DOMAIN_VERIFICATION`. `isSsoDomainVerificationEnabled()` also reads the config key `plugins.ssoDomainVerification`, which wins over the env var, and it is false whenever SSO itself is not wired. The callout keeps the page's existing env-var framing. Nothing changed here. - The step text at `:274`–`:277` names `NO_PENDING_VERIFICATION` without a status. The callout now gives its status. That paragraph is otherwise untouched. - No changeset is needed: `content/docs/**` ships in no public package's `files[]`. I checked every tracked public `package.json` and found zero `files[]` entries naming content or docs. As a control, 69 public packages do list `dist`. --- _Generated by [Claude Code](https://claude.ai/code/session_01LYXc6ckoWuZyVZpWYizdMh)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent f8566a0 commit 446c8b2

1 file changed

Lines changed: 4 additions & 3 deletions

File tree

‎content/docs/permissions/sso.mdx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,10 @@ after DNS propagates.
279279
<Callout type="info">
280280
**The mounts are unconditional; the switch controls the endpoint behind them.**
281281
Both routes exist whether or not `OS_SSO_DOMAIN_VERIFICATION` is set — and with
282-
it unset the two halves report that differently, so match on the code rather than
283-
the status: step 1 answers **400 `DOMAIN_VERIFICATION_DISABLED`**, step 2 passes
284-
the inner **404** through with an explanatory message. Either way an anonymous
282+
it unset there is no endpoint behind them, so both steps answer **400
283+
`DOMAIN_VERIFICATION_DISABLED`**. With it on, either step answers an unknown
284+
`providerId` with **404 `RESOURCE_NOT_FOUND`**; step 2's `NO_PENDING_VERIFICATION`
285+
is a 404 too, so match on the code rather than the status. Either way an anonymous
285286
caller gets **401 `UNAUTHENTICATED`** and a signed-in non-platform-admin **403
286287
`PERMISSION_DENIED`** — identity is answered before capability, so a stranger
287288
cannot use these routes to probe which features an environment has enabled.

0 commit comments

Comments
 (0)