Skip to content

refactor(auth)!: stop advertising passkeys/magicLink on /api/v1/auth/config (#7481) - #7508

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7481-stop-advertising
Aug 11, 2026
Merged

refactor(auth)!: stop advertising passkeys/magicLink on /api/v1/auth/config (#7481)#7508
os-zhuang merged 1 commit into
mainfrom
claude/issue-7481-stop-advertising

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7481

Implements the maintainer ruling recorded on the issue on 2026-08-11, quoted verbatim and untranslated:

Ruling: stop advertising — drop passkeys / magicLink from the /api/v1/auth/config payload until objectui#4179 ships real UI. declared=enforced posture: a deployer must not be able to flip a flag that does nothing anywhere. Re-anchor the two exempt.reason entries off closed objectui#2514 (→ objectui#4179) in the same change. State: needs-user-decisionpm:queue.

What changed

Both flags were served by getPublicConfig() from introduction and read by no client — no login UI anywhere renders a passkey or magic-link affordance off them. The payload therefore advertised two sign-in methods a user could never reach, and a deployer who set either plugin flag flipped a switch with no observable effect anywhere.

The two capabilities are not equally empty, and the prescriptions say so separately rather than sharing one string:

  • passkeys — nothing is wired behind it at all; no better-auth passkey plugin is registered, so /passkey/* does not answer. There is no capability to detect.
  • magicLink — loses only its advertisement. AuthPluginConfig.plugins.magicLink still wires better-auth's magic-link plugin, and /api/v1/auth/magic-link/send + /magic-link/verify answer exactly as before. A custom UI can drive them today.

The server-side authoring surface (AuthPluginConfig.plugins.*) is untouched — this narrows the served payload, not the deployment configuration.

The retirement kit

AuthFeaturesConfigSchema is not .strict(), so a plain delete would let a payload carrying either key parse clean and lose it in silence (the ADR-0104 shape). Measured, not assumed: plain removal tripped check:authorable-surface gate (a), 2 authorable key(s) disappeared from the contract.

  • Tombstone (retiredKey()) on both keys, each with its own prescription.
  • ADR-0087: one D3 SemanticMigration (auth-config-unadvertised-reserved-features) plus the two exact RETIRED_KEYS_BY_MAJOR entries. No D2 conversion, deliberately — this is a response surface the server mints per request, so there is no authored source for os migrate meta to rewrite. That is the EnhancedApiError.fieldErrors / api/ListNotificationsRequest:cursor disposition, and the prescriptions correspondingly carry no os migrate meta sentence.
  • Baselines, spec-changes.json, upgrade guide, export-origins/ and reference docs regenerated. Hand-written auth/SSO docs and the identity-auth QA checklist corrected — the checklist's flag-advertisement clause now inverts: seeing either key in the payload is itself a FAIL.

Registry shape — disclosed, not silently invented

The ruling presumes the two exempt.reason entries survive to be re-anchored. They cannot: the drift guard asserts the served key set is equivalent to PUBLIC_AUTH_FEATURES, so once the payload loses them the registry must too — and an entry is also what makes a flag requiresFeature-gateable, i.e. keeping them would leave spec inputs able to gate on a capability that is never served.

So the entries are removed and the honest record moves to a new exported constant in the same file, PUBLIC_AUTH_FEATURES_NOT_ADVERTISED, pointing at objectui#4179. This is new registry shape, added openly: it parallels the existing PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS (a list of keys deliberately not classified as flags), and it is pinned as a negative — these names must be absent from the registry, absent from the payload, and un-gateable. The ruling's intent is met: no live pointer at closed objectui#2514 remains anywhere. One historical mention survives inside the new docblock, which names objectui#2514 explicitly as closed while recording why the flags were reserved; that is provenance, not a stale pointer.

Verification

Build closure first in a fresh worktree, then:

gate result
@objectstack/spec test 374 files / 9804 passed
@objectstack/plugin-auth test 40 files / 997 passed
@objectstack/dogfood test 87 files / 546 passed, 3 skipped
spec + plugin-auth typecheck pass
check:generated All 13 generated artifacts are up to date
check:liveness · check:empty-state · check:authorable-surface · check:docs · check:api-surface · check:spec-changes · check:upgrade-guide · check:migration-registry · check:skill-refs · check:skill-docs · check:skill-examples · check:variant-docs · check:export-origins all pass
card gates: check:adr-anchors · check:changeset-gate-self-tests · check:docs-audit-scope · check:i18n · check:merge-driver · check:release-body · check:spec-parsed-alias · check:nul-bytes all pass

Downstream consumer sweep — prefix direction (--filter '...' is downstream; here run per-package after building each one's full dependency closure): @objectstack/client, @objectstack/hono, @objectstack/dogfood, @objectstack/platform-objects, @objectstack/lint, @objectstack/cli all typecheck green. An earlier red on hono and client was the stale-artefact trap (unbuilt siblings), not this change; it cleared once the closures were built.

Reverse verification — direction predicted before running: re-serving passkeys in the features literal should turn the drift guard and the new pin red. It did, three ways at once:

× every boolean flag served by getPublicConfig() is classified in the registry, and vice versa
× reserved-but-unadvertised capabilities stay out of the payload even when their plugin flag is on
× should return safe public configuration

restored → 997/997 green again.

Two notes for the reviewer

  • check:doc-formula-expressions, named in the dispatch card's gate list, does not exist in this repo (Command "check:doc-formula-expressions" not found). The nearest real gate, check:doc-authoring, was run instead and passes.
  • Every generated file this PR touches is routed merge=os-regen, but the branch is based on origin/main with no merge performed, so AGENTS.md's four-step regen-on-merge sequence has no merge to apply to. check:generated is green.

Out of scope, filed separately: buildPluginList()'s local enabled.passkeys is dead — nothing reads it, and no passkey plugin is constructed from it. Pre-existing and untouched here.


Generated by Claude Code

…config (#7481)

Both flags were served by `getPublicConfig()` from introduction and read by no
client: no login UI anywhere renders a passkey or magic-link affordance off
them. The payload therefore advertised two sign-in methods a user could never
reach, and a deployer who set either plugin flag flipped a switch with no
observable effect anywhere — ADR-0049 enforce-or-remove, on a deployment-facing
contract. Maintainer ruling 2026-08-11 chose stop-advertising over
keep-as-reserved: declared = enforced.

The two capabilities are not equally empty, and the prescriptions say so
separately rather than sharing one string. Nothing is wired behind `passkeys`.
`magicLink` loses only its ADVERTISEMENT: `plugins.magicLink` still wires
better-auth's magic-link plugin and `/magic-link/{send,verify}` answer as
before.

- `AuthFeaturesConfigSchema`: both keys tombstoned with `retiredKey()` rather
  than deleted — the schema is not `.strict()`, so a plain delete would let a
  payload carrying either key parse clean and lose it in silence (ADR-0104).
- ADR-0087: one D3 `SemanticMigration` plus the two exact
  `RETIRED_KEYS_BY_MAJOR` entries. No D2 conversion, deliberately — a response
  surface has no authored source for `os migrate meta` to rewrite (the
  `EnhancedApiError.fieldErrors` disposition).
- `PUBLIC_AUTH_FEATURES`: the two entries are removed, which is what the
  drift guard requires once the served set loses them, and it also narrows
  `requiresFeature` so no spec input can gate on an unserved flag. Their
  standing record moves to the new `PUBLIC_AUTH_FEATURES_NOT_ADVERTISED`,
  re-anchored off closed objectui#2514 onto objectui#4179.
- Generated baselines, spec-changes, upgrade guide, export-origins and
  reference docs regenerated; hand-written auth/SSO docs and the identity-auth
  QA checklist corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M33yPsTgKmkB5rh2WzZwQk
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 4:13am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-auth, @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via @objectstack/plugin-auth, packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-auth, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth, @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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/m tests tooling

Projects

None yet

2 participants