|
1100 | 1100 | }, |
1101 | 1101 | { |
1102 | 1102 | "id": "identity-auth.linked-accounts-social", |
1103 | | - "title": "Linked accounts: the caller's social/OIDC links appear in mine-view; unlink removes one; the surface offers no link affordance, so a provider-less boot degrades honestly", |
| 1103 | + "title": "Linked accounts: link a social/OIDC identity through the redirect round-trip → a sys_account row appears in mine-view; unlink removes it; provider-less boot degrades honestly", |
1104 | 1104 | "since": "v16", |
1105 | 1105 | "status": "active", |
1106 | | - "revision": 3, |
| 1106 | + "revision": 2, |
1107 | 1107 | "priority": "P2", |
1108 | 1108 | "surface": "mixed", |
1109 | | - "personas": ["a signed-in user holding a second (social/OIDC) identity link", "the same user after unlinking"], |
| 1109 | + "personas": ["a signed-in user linking a second identity", "the same user after unlinking"], |
1110 | 1110 | "fixtures": { |
1111 | 1111 | "app": "showcase", |
1112 | 1112 | "requires": [ |
1113 | | - "at least one social/OIDC provider configured (socialProviders or oidcProviders) and the caller holding a link to it, made OUTSIDE the console — sys_account declares no link action (link_social retired under ADR-0049); a social sign-in, or the signed-in POST /api/v1/auth/link-social (SDK auth.accounts.linkSocial), makes one. Stock showcase ships no provider, so this item is blocked(fixture) until one is configured", |
| 1113 | + "at least one social/OIDC provider configured (socialProviders or oidcProviders) so link_social has a provider to dance with — stock showcase ships none, so this item is blocked(fixture) until a provider is configured", |
1114 | 1114 | "the Account 'Identity Links' surface (nav_accounts → sys_account) reachable" |
1115 | 1115 | ] |
1116 | 1116 | }, |
1117 | | - "blocked": { "by": "fixture", "ref": "no stock showcase social/OIDC IdP — the identity link this item unlinks needs a configured provider, same fixture class as identity-auth.sso-enforced-first-paint / oauth-app-consent-loop" }, |
| 1117 | + "blocked": { "by": "fixture", "ref": "no stock showcase social/OIDC IdP — link_social needs a configured provider, same fixture class as identity-auth.sso-enforced-first-paint / oauth-app-consent-loop" }, |
1118 | 1118 | "steps": [ |
1119 | 1119 | "as a signed-in user, open Account → Identity Links (sys_account `mine` view, filter user_id={current_user_id}); screenshot the initial link set", |
1120 | | - "confirm the fixture's link is a sys_account row in the caller's mine-view (provider_id = the provider, user_id = the caller) — (provider_id, account_id) is the whole account identity; there is no issuer to check, sys_account.issuer retired with better-auth 1.7.3 (packages/spec/src/migrations/entries/semantic/18.sys-account-issuer-retired.ts)", |
| 1120 | + "invoke link_social for a configured provider: the action is type:'url' — GET /api/v1/auth/sign-in/social?provider=<p>&callbackURL=<origin>/_console/apps/account/sys_account (full-page navigation, NOT XHR, so the OAuth 302 dance and link cookie work); complete the provider round-trip and land back on the Identity Links view", |
| 1121 | + "confirm a sys_account row now exists for that provider in the caller's mine-view (provider_id = the provider, user_id = the caller) — (provider_id, account_id) is the whole account identity; there is no issuer to check, sys_account.issuer retired with better-auth 1.7.3 (packages/spec/src/migrations/entries/semantic/18.sys-account-issuer-retired.ts)", |
1121 | 1122 | "unlink it: the unlink_account action → POST /api/v1/auth/unlink-account with accountId = the sys_account ROW id (better-auth 1.7 keys on the row id); confirm the row is gone from mine-view", |
1122 | | - "both-sides / degradation: on a boot with NO provider configured, confirm the surface offers no link affordance — sys_account declares only unlink_account since link_social retired, so nothing offers a link that dead-ends (open-edition honest-degradation posture)", |
| 1123 | + "both-sides / degradation: on a boot with NO provider configured, confirm link_social degrades honestly — the affordance is absent or names the missing provider, rather than offering a link that dead-ends (open-edition honest-degradation posture)", |
1123 | 1124 | "confirm sys_account is read-only over the data API — a forged direct insert/delete is refused (apiMethods ['get','list'], writes 405)" |
1124 | 1125 | ], |
1125 | 1126 | "acceptance": [ |
| 1127 | + { |
| 1128 | + "clause": "linking a social identity creates a sys_account row in the caller's mine-view after the redirect round-trip completes", |
| 1129 | + "oracle": "api", |
| 1130 | + "verify": "GET /api/v1/data/sys_account (mine view) after the link shows a new row with provider_id = the provider and user_id = the caller; a row with no issuer is the healthy shape, never a FAIL — the column is retired", |
| 1131 | + "evidence": "the post-link mine-view read" |
| 1132 | + }, |
1126 | 1133 | { |
1127 | 1134 | "clause": "the link surface renders the caller's own links only (mine-view scoped) — screenshot-confirmed", |
1128 | 1135 | "oracle": "screenshot", |
|
1136 | 1143 | "evidence": "the unlink response + the follow-up read" |
1137 | 1144 | }, |
1138 | 1145 | { |
1139 | | - "clause": "a provider-less boot degrades honestly: the surface offers no link affordance — never one that dead-ends", |
| 1146 | + "clause": "a provider-less boot degrades honestly: link_social is absent or names the missing provider — never a link affordance that dead-ends", |
1140 | 1147 | "oracle": "screenshot", |
1141 | | - "verify": "with no provider configured, the Identity Links surface shows no link action at all (sys_account declares none; its one action, unlink_account, is a row action)", |
| 1148 | + "verify": "with no provider configured, the Identity Links surface shows no dead link action (or an explicit unavailable state)", |
1142 | 1149 | "evidence": "the provider-less screenshot" |
1143 | 1150 | }, |
1144 | 1151 | { |
|
1149 | 1156 | } |
1150 | 1157 | ], |
1151 | 1158 | "negative": [ |
| 1159 | + "a link that appears in the UI but does not create a sys_account row (client-only) is a FAIL — the row is the durable identity link", |
1152 | 1160 | "unlink that hides the row from the list but leaves the sys_account (so the provider still signs the user in) is a FAIL", |
1153 | 1161 | "one user's identity links appearing in another's mine-view is an RLS FAIL", |
1154 | | - "a link affordance on the Identity Links surface that navigates to a dead endpoint is a dishonest-degradation FAIL — sys_account declares none" |
| 1162 | + "a provider-less boot offering a link_social action that navigates to a dead endpoint is a dishonest-degradation FAIL" |
1155 | 1163 | ], |
1156 | 1164 | "traps": ["wrong-persona", "dispatcher-vs-hono-route", "hydration-race"], |
1157 | 1165 | "source": [ |
1158 | | - "packages/platform-objects/src/identity/sys-account.object.ts#user_id (unlink_account → /api/v1/auth/unlink-account accountId=row id; no link action, link_social retired under ADR-0049; mine view user_id={current_user_id} vs all_links; apiMethods ['get','list'])", |
| 1166 | + "packages/platform-objects/src/identity/sys-account.object.ts#user_id (link_social type:'url' → /api/v1/auth/sign-in/social?provider=&callbackURL=; unlink_account → /api/v1/auth/unlink-account accountId=row id; mine view user_id={current_user_id} vs all_links; provider options; apiMethods ['get','list'])", |
1159 | 1167 | "packages/plugins/plugin-auth/src/auth-route-ledger.ts#AUTH_ROUTE_LEDGER (POST link-social=auth.accounts.linkSocial, GET list-accounts=auth.accounts.list, POST unlink-account=auth.accounts.unlink — re-pointed #18104: linkSocial is a CLIENT METHOD name, carried in this file only inside a dotted string value)", |
1160 | 1168 | "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_accounts (nav_accounts → 'Identity Links', objectName sys_account)" |
1161 | 1169 | ], |
1162 | 1170 | "history": [ |
1163 | 1171 | { "revision": 1, "date": "2026-08-08", "change": "new item: social/OIDC account linking round-trip → sys_account mine-view row, unlink removal, provider-less honest degradation; blocked(fixture) pending a configured IdP (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" }, |
1164 | | - { "revision": 2, "date": "2026-09-23", "change": "the 'issuer stamped' requirement is withdrawn from both the step and the first acceptance clause's verify: better-auth 1.7.3 rolled the issuer-scoped account identity back and sys_account.issuer retired with it, so nothing stamps it and the old verify failed a healthy system. The reason is kept — the step now says (provider_id, account_id) is the whole identity and names the retirement's migration entry in packages/spec — and the verify states that an issuer-less row is the expected shape", "ref": "#19217" }, |
1165 | | - { "revision": 3, "date": "2026-10-05", "change": "the link half retires with the sys_account link_social action (ADR-0049 enforce-or-remove, the maintainer-ratified ruling on #21849): it navigated to a GET of the POST-only social sign-in route with a fixed provider list, dead on every boot. Removed the link step, the first acceptance clause (linking creates a row) and the client-only-link negative; the social/OIDC link the unlink half needs moves into the fixture, made outside the console (a social sign-in, or the signed-in POST /api/v1/auth/link-social = auth.accounts.linkSocial). The unlink, mine-view and read-only clauses stay; the provider-less degradation clause now reads against 'no link affordance', since sys_account declares none", "ref": "#21849" } |
| 1172 | + { "revision": 2, "date": "2026-09-23", "change": "the 'issuer stamped' requirement is withdrawn from both the step and the first acceptance clause's verify: better-auth 1.7.3 rolled the issuer-scoped account identity back and sys_account.issuer retired with it, so nothing stamps it and the old verify failed a healthy system. The reason is kept — the step now says (provider_id, account_id) is the whole identity and names the retirement's migration entry in packages/spec — and the verify states that an issuer-less row is the expected shape", "ref": "#19217" } |
1166 | 1173 | ] |
1167 | 1174 | }, |
1168 | 1175 | { |
|
0 commit comments