|
| 1 | +--- |
| 2 | +'@objectstack/spec': major |
| 3 | +'@objectstack/plugin-auth': patch |
| 4 | +--- |
| 5 | + |
| 6 | +refactor(auth)!: stop advertising `passkeys` / `magicLink` on `/api/v1/auth/config` — two flags nothing consumed (#7481, ADR-0049) |
| 7 | + |
| 8 | +<!-- adr-0087: registered auth-config-unadvertised-reserved-features --> |
| 9 | + |
| 10 | +**FROM → TO:** reading `config.features.passkeys` or `config.features.magicLink` off |
| 11 | +`GET /api/v1/auth/config` → delete the read; both keys are gone from the payload and there |
| 12 | +is no replacement flag. Neither capability was reachable by a user, so nothing a client |
| 13 | +gated on them was ever offered. `AuthPluginConfig.plugins.passkeys` / `plugins.magicLink` |
| 14 | +are **unchanged** — this narrows the served payload, not the server configuration. |
| 15 | + |
| 16 | +Both flags were served from introduction and read by no client: no login UI anywhere |
| 17 | +renders a passkey or magic-link affordance off them. So the payload advertised two sign-in |
| 18 | +methods a user could never reach, and a deployer who set either plugin flag flipped a |
| 19 | +switch with no observable effect — ADR-0049's enforce-or-remove, on a deployment-facing |
| 20 | +contract. The maintainer ruled remove over keep-as-reserved on 2026-08-11: declared = |
| 21 | +enforced, and a deployer must not be able to flip a flag that does nothing anywhere. |
| 22 | + |
| 23 | +The two are not equally empty, and the prescriptions say so separately rather than sharing |
| 24 | +one string: |
| 25 | + |
| 26 | +- **`passkeys`** has nothing behind it at all — no better-auth passkey plugin is wired, so |
| 27 | + `/passkey/*` does not answer. There is no capability to detect. |
| 28 | +- **`magicLink`** loses only its **advertisement**. `plugins.magicLink` still wires |
| 29 | + better-auth's magic-link plugin, and `/api/v1/auth/magic-link/send` + `/magic-link/verify` |
| 30 | + answer exactly as before — drive them from your own UI. |
| 31 | + |
| 32 | +Both return to the payload in the change that ships the login UI (objectui#4179); until |
| 33 | +then the standing record is `PUBLIC_AUTH_FEATURES_NOT_ADVERTISED` in |
| 34 | +`kernel/public-auth-features.ts`, and their `PUBLIC_AUTH_FEATURES` entries — which pointed |
| 35 | +at the now-closed objectui#2514 — are gone with them. |
| 36 | + |
| 37 | +The retirement kit: |
| 38 | + |
| 39 | +- **Tombstone, not deletion** (`retiredKey()`): `AuthFeaturesConfigSchema` is not |
| 40 | + `.strict()`, so a plain delete would let a payload carrying either key parse clean and |
| 41 | + lose it in silence (the ADR-0104 shape). Each key carries its own prescription. |
| 42 | +- **ADR-0087 D3 `SemanticMigration`** (`auth-config-unadvertised-reserved-features`) plus |
| 43 | + the two exact `RETIRED_KEYS_BY_MAJOR` entries. No D2 conversion, deliberately: this is a |
| 44 | + response surface the server mints per request — nobody authors or persists an |
| 45 | + `AuthFeaturesConfig` — so there is no source for `os migrate meta` to rewrite. The |
| 46 | + `EnhancedApiError.fieldErrors` disposition. |
| 47 | +- `requiresFeature` narrows with the registry: neither name is a gateable flag any more, |
| 48 | + which is what stops a spec input from being written against a capability that is not |
| 49 | + served. |
| 50 | +- Generated baselines (`authorable-surface/api.json` gains two `[RETIRED]` lines, |
| 51 | + `authorable-defaults/api.json` loses two default lines), `spec-changes.json`, the upgrade |
| 52 | + guide, `export-origins/` and the reference docs regenerated. |
0 commit comments