Skip to content

Commit b79759d

Browse files
committed
docs(qa): identity-auth clauses say what the re-runs measured
The reset mail's to_addresses carries a display name, so the step matches on the address. remove-member revokes the member's live sessions, so the membership oracle is read on a fresh sign-in. The meta read still serves sys_account apiMethods get and list, so the linked-accounts step says that and how the aggregate and export routes answer. Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2af228c commit b79759d

1 file changed

Lines changed: 7 additions & 7 deletions

File tree

‎docs/qa/platform-checklist/areas/identity-auth.json‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@
800800
"prove the vocabulary is CLOSED, not merely conventional: attempt the same call with role 'guest' (and with any stack-declared position/permission-set name, e.g. showcase's 'contributor') — better-auth's role check refuses it (400 ROLE_NOT_FOUND) before any insert, and no membership/invitation row is left behind",
801801
"prove the role change bites: an operation the new role gates flips outcome for that member (e.g. promote to admin → an org-admin-only action now permitted; demote → refused)",
802802
"rename the organization: POST /api/v1/auth/organization/update (organizations.update) with a new name; re-read GET get-full-organization and confirm the rename persisted and the nav label follows",
803-
"remove a member: POST /api/v1/auth/organization/remove-member (organizations.removeMember); confirm the member's org-scoped access SHRINKS through a request that is answered BECAUSE of membership of that org — e.g. GET /api/v1/auth/organization/get-full-organization?organizationId=ORG, or POST /api/v1/auth/organization/set-active for it — succeeding before and refused after. ⛔ Not a data read: on the stock `single` posture the `everyone` baseline grants survive remove-member, so an ex-member still reads the same showcase rows and the check could not discriminate",
803+
"remove a member: POST /api/v1/auth/organization/remove-member (organizations.removeMember); confirm the member's org-scoped access SHRINKS through a request that is answered BECAUSE of membership of that org — e.g. GET /api/v1/auth/organization/get-full-organization?organizationId=ORG, or POST /api/v1/auth/organization/set-active for it — succeeding before and refused 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION after. Read the after-side on a FRESH sign-in: remove-member also revokes the member's live sessions (revoke_reason organization_membership_ended), so the old session answers 401 to every request and discriminates nothing. ⛔ Not a data read: on the stock `single` posture the `everyone` baseline grants survive remove-member, so an ex-member still reads the same showcase rows and the check could not discriminate",
804804
"create a team + add members: POST /api/v1/auth/organization/create-team (organizations.teams.create), then POST /api/v1/auth/organization/add-team-member (organizations.teams.addMember) for two users; read sys_team_member and confirm two join rows exist (unique on team_id+user_id)",
805805
"both-sides gate: as a NON-admin member, fire update-member-role / remove-member / update / create-team / add-team-member directly and capture each server refusal; screenshot that the management affordances are absent in the non-admin's UI"
806806
],
@@ -832,7 +832,7 @@
832832
{
833833
"clause": "remove-member shrinks access: after removal the ex-member is refused a resource they could reach as a member — removal is an authorization change, not just a roster edit",
834834
"oracle": "api",
835-
"verify": "an org-membership-scoped request (get-full-organization or set-active for that org) that succeeded for the member is refused after remove-member; a data read that the `everyone` baseline still serves is not this oracle",
835+
"verify": "an org-membership-scoped request (get-full-organization or set-active for that org) that succeeded for the member is refused 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION after remove-member, read on a fresh sign-in (the member's pre-removal sessions are revoked, organization_membership_ended, and answer 401 to everything); a data read that the `everyone` baseline still serves is not this oracle",
836836
"evidence": "the before/after member requests"
837837
},
838838
{
@@ -869,7 +869,7 @@
869869
{ "revision": 1, "date": "2026-08-08", "change": "new item: Setup Organization page {current_org_id} resolution (ADR-0081) with Members/Invitations/Teams tabs, update-member-role (4-name vocab)/remove-member/rename, create-team + add-team-member → sys_team_member rows, non-admin refused server-side (PENDING-GAPS §B). Teams membership deep-tested in identity-auth.teams-bu-membership; org-member management stays here", "ref": "claude/platform-test-checklist-ocwugl" },
870870
{ "revision": 2, "date": "2026-08-11", "change": "CORRECTION from run #7663: the role vocabulary named here was wrong. The enforced builtin set is {owner, admin, delegated_admin, member} (ADR-0108 BUILTIN_MEMBERSHIP_ROLE_OPTIONS), NOT {owner, admin, member, guest} — 'guest' is rejected 400 ROLE_NOT_FOUND and delegated_admin is legitimate. Corrected the step, the acceptance clause and the negative; added a closed-vocabulary clause (guest / a stack position / a PermissionSet name each refused, no row left behind); re-pointed source at membership-role.ts + ADR-0108 + the vocabulary dogfood pin instead of the stale organization.zod.ts doc-comment the wrong text came from (that doc-comment is fixed in the same PR)", "ref": "#7740" },
871871
{ "revision": 3, "date": "2026-09-12", "change": "PROVENANCE, no clause change: #16270 measured that no object under packages/platform-objects/src/identity/ declares Field.relatedList and read the Members/Invitations/Teams claim here as possibly stale. It is not stale — the strip is declared by SysOrganizationDetailPage (packages/platform-objects/src/pages/sys-organization.page.ts), a slotted page whose slots.tabs override replaces the synthesized strip. Added that file as a source entry so the next reader does not have to re-derive it. Steps, acceptance clauses, oracles and negatives are unchanged", "ref": "#16270" },
872-
{ "revision": 4, "date": "2026-10-17", "change": "steps[6] and acceptance[4].verify name an org-membership-scoped oracle (checklist-accuracy finding of the identity-auth slice report on #23878 (6216631966)). On the stock `single` posture the `everyone` baseline reads survive remove-member, so 'a resource they could read as a member now refuses' found no data resource to refuse; the oracle is now a request answered because of membership of that org. Not loosened: removal must still refuse it", "ref": "#24036" }
872+
{ "revision": 4, "date": "2026-10-17", "change": "steps[6] and acceptance[4].verify name an org-membership-scoped oracle (checklist-accuracy finding of the identity-auth slice report on #23878 (6216631966)). On the stock `single` posture the `everyone` baseline reads survive remove-member, so 'a resource they could read as a member now refuses' found no data resource to refuse; the oracle is now a request answered because of membership of that org, read on a fresh sign-in — the re-run on #24036 found remove-member revoking the member's live sessions (organization_membership_ended), while the fresh session still read 5 showcase rows and drew 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION from get-full-organization and set-active. Not loosened: removal must still refuse it", "ref": "#24036" }
873873
]
874874
},
875875
{
@@ -974,7 +974,7 @@
974974
},
975975
"steps": [
976976
"as anonymous, request a reset: POST /api/v1/auth/request-password-reset with the target email (NOTE the live route is request-password-reset / reset-password — the spec's AuthEndpointPaths says /forget-password, a divergence; trust the live server per RUNNER, and file the doc divergence)",
977-
"capture the reset email: as admin, read the newest sys_email row addressed to the requester (GET /api/v1/data/sys_email) and extract the token from body_text / body_html (the link is GET /api/v1/auth/reset-password/:token). The LogTransport line in the server log shows only to and subject — it never carries the link",
977+
"capture the reset email: as admin, read the newest sys_email row addressed to the requester (GET /api/v1/data/sys_email; to_addresses reads 'NAME <EMAIL>', so match on the address it contains, not on equality) and extract the token from body_text / body_html (the link is GET /api/v1/auth/reset-password/:token). The LogTransport line in the server log shows only to and subject — it never carries the link",
978978
"complete the reset: POST /api/v1/auth/reset-password with the token + a new password; capture the response",
979979
"sign in with the NEW password (2xx) and confirm the OLD password is now refused",
980980
"reuse the SAME token a second time (POST /api/v1/auth/reset-password again) and capture the loud refusal — a consumed token must not reset again",
@@ -1153,7 +1153,7 @@
11531153
"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)",
11541154
"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",
11551155
"both-sides / degradation: on a boot with NO provider configured, confirm the Linked Accounts surface degrades honestly — any link affordance is absent or names the missing provider, rather than offering a link that dead-ends (open-edition honest-degradation posture)",
1156-
"confirm sys_account is read-only over the data API — a forged direct insert/delete is refused (the served apiMethods are reads only — [get, list, aggregate, export] on the live meta read, though sys-account.object.ts declares ['get','list']; writes 405)"
1156+
"confirm sys_account is read-only over the data API — a forged direct insert/delete is refused (apiMethods ['get','list'] — declared in sys-account.object.ts and served so by the meta read; the read-family aggregate and export routes pass that method gate and meet their own checks instead, a bare aggregate 400 VALIDATION_ERROR and export 403 EXPORT_NOT_PERMITTED, never 405; writes 405)"
11571157
],
11581158
"acceptance": [
11591159
{
@@ -1183,7 +1183,7 @@
11831183
{
11841184
"clause": "sys_account is read-only over the data API — links are mutated only through the auth endpoints, not raw row writes",
11851185
"oracle": "api",
1186-
"verify": "a forged POST/DELETE to /api/v1/data/sys_account returns 405 (the served apiMethods are reads only — get, list, aggregate, export; identity write guard ADR-0092 D2)",
1186+
"verify": "a forged POST/DELETE to /api/v1/data/sys_account returns 405 (apiMethods ['get','list'] — reads only; the aggregate and export read routes are not method-refused; identity write guard ADR-0092 D2)",
11871187
"evidence": "the forged-write response"
11881188
}
11891189
],
@@ -1207,7 +1207,7 @@
12071207
{ "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" },
12081208
{ "revision": 3, "date": "2026-10-05", "change": "blocked lifted: run #21845 passed the link, mine-view, unlink and read-only clauses against a local authorization-code + PKCE OIDC provider registered through the auth manager's applyConfigPatch from a scratch app plugin ('auth:configure' fires too early); the provider recipe is written into fixtures.requires, not committed as code, and the missing-IdP gap is kept as a CLOSED-for-runs knownGap. Step 2 now links through the authenticated POST /api/v1/auth/link-social and navigates to the URL it returns — the old step drove the sign-in door, which signs in rather than links. The step, the provider-less clause and its negative no longer name a specific sys_account action: they describe the link affordance, so the clause indices stay stable while the action itself is handled on its own card. The user's nav label is 'Linked Accounts' in the Account app (nav_account_linked); 'Identity Links' is the Setup app's admin entry (card #21851 entry 3)", "ref": "#21845" },
12091209
{ "revision": 4, "date": "2026-10-16", "change": "PROVENANCE, no clause change: #23803 removed Setup's nav_accounts ('Identity Links') entry, so the source anchor that cited it no longer resolved. Re-pointed it at SysUserDetailPage, whose Linked Accounts tab (a related list over sys_account keyed on user_id) is where an administrator reaches a user's identity links now. Steps, acceptance clauses, oracles and negatives are unchanged: they drive the user's Account-app surface, which did not move", "ref": "#23803" },
1210-
{ "revision": 5, "date": "2026-10-17", "change": "steps[1] and [5] and acceptance[3].verify and [4].verify corrected (checklist-accuracy findings of the identity-auth slice report on #23878 (6216631966)). The live meta read serves sys_account apiMethods [get, list, aggregate, export] where the item quoted ['get','list'] — still reads only, so writes still 405; link_social is retired, so a provider-less boot's missing link action is the same absence every boot shows and clause 3 cannot discriminate on it; and the link callback lands on the 'All' tab. Not loosened: a forged write must still answer 405", "ref": "#24036" }
1210+
{ "revision": 5, "date": "2026-10-17", "change": "steps[1] and [5] and acceptance[3].verify and [4].verify corrected (checklist-accuracy findings of the identity-auth slice report on #23878 (6216631966)). The slice read sys_account's live methods as [get, list, aggregate, export]; the re-run on #24036 found the meta read still serving ['get','list'] while the aggregate and export routes pass the method gate (400 VALIDATION_ERROR / 403 EXPORT_NOT_PERMITTED, never 405), so the step now says both — reads only either way, and writes still 405; link_social is retired, so a provider-less boot's missing link action is the same absence every boot shows and clause 3 cannot discriminate on it; and the link callback lands on the 'All' tab. Not loosened: a forged write must still answer 405", "ref": "#24036" }
12111211
]
12121212
},
12131213
{

0 commit comments

Comments
 (0)