Skip to content

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

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-22493-sso-domain-verification-callout
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-22493-sso-domain-verification-callout

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

…t-found on

The callout under "Domain verification (opt-in)" said that with
OS_SSO_DOMAIN_VERIFICATION unset the two steps report it differently and that
verify-domain passes the inner 404 through. Both doors answer
400 DOMAIN_VERIFICATION_DISABLED with the switch unset, without reaching the
inner endpoint; with it on, an unknown providerId gets 404 RESOURCE_NOT_FOUND
from either door. The "match on the code" advice keeps a true reason: step 2's
NO_PENDING_VERIFICATION is a 404 as well.

Claude-Session: https://claude.ai/code/session_01LYXc6ckoWuZyVZpWYizdMh
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Oct 9, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 9, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 16:01
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 16:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 446c8b2 Oct 9, 2026
37 of 38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22493-sso-domain-verification-callout branch October 9, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants