Skip to content

QA run · area:identity-auth (23/23) · 2034975a · 2026-10-16 · 18 PASS / 5 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN #23943

Closed

Description

@objectstack-fleet

QA run record for the area:identity-auth slice of wave #23878 (cloud runner for session session_01K3qUoyh1xFGoWzAACPMbXJ). Text only, per checklist-test §4. Selector resolved with node scripts/checklist-select.mjs area:identity-auth --json.

Environment fingerprint

  • Framework: objectstack-ai/objectstack main @ 2034975a47193f04db4791614f4ac9331ff211d3. pnpm install --frozen-lockfile && pnpm build: 75/75 tasks, 6m52s. All servers booted from a second linked worktree at the same sha (../os-qa) holding copies of every built dist.
  • Console: .objectui-sha = daeb77fc84dd824e74c95b87cdede85afdb248e7. pnpm objectui:build fails on this sha (Console pin does not build on main: objectui daeb77fc imports checkPageComponentViewData, deleted from spec by #23784 #23908, not re-filed), so the console was built in a worktree at a35f7a67^ (34b740f) through scripts/build-console.sh, then copied in. pnpm check:console-sha reads Console dist matches the objectui pin (objectui@daeb77fc84dd). Browser verdicts ran on objectui daeb77fc built against spec a35f7a67^.
  • Boots: OS_PORT=P pnpm exec objectstack dev --ui --seed-admin -p P -d file:DIR/data.db from examples/app-showcase. Each runner group used its own ports and file DBs (47110–47161), with fresh DBs per variant. Variant env, always exported before boot:
    • OS_AUTH_TWO_FACTOR=true
    • runtime PUT /api/settings/auth {mfa_required}
    • OS_AUTH_REQUIRE_EMAIL_VERIFICATION=true
    • OS_AUTH_AUDIENCE_POSTURE=open + OS_AUTH_AUDIENCE_SELF_REGISTRATION_PERMISSION_SET=showcase_contributor
    • OS_DISABLE_SIGNUP=true on a --no-seed-admin zero-user DB
    • OS_AUTH_PHONE_NUMBER_ENABLED=true
    • NODE_ENV=production + scratch OS_AUTH_SECRET + OS_OIDC_PROVIDER_ENABLED=false
    • OS_AUTH_SSO_ONLY=true
    • OS_TENANCY_POSTURE=group / isolated
    • OS_AUTH_URL (custom issuer)
  • Scratch fixtures (never committed, no product change):
    • A local authorization-code + PKCE (S256) OIDC provider: discovery, authorize, token, RS256 id_token with nonce, userinfo, jwks.
    • It is registered through auth.applyConfigPatch({ oidcProviders }) from an env-gated plugin in a scratch copy of the showcase. No env switch for oidcProviders exists, and a config file beside the showcase config is not git-ignored.
    • Walled-tenancy boots ran from a scratch showcase copy that adds @objectstack/organizations.
  • Seed: --seed-admin (admin@objectos.ai) plus the showcase demo personas. Disposable users were minted per item through invite → sign-up or admin/create-user; no demo persona was banned or removed.
  • Mail capture: the persisted sys_email row (admin GET /api/v1/data/sys_email). See the checklist-accuracy notes on the close-out.
  • Browser: Playwright with chromium.launch({executablePath:'/opt/pw-browsers/chromium'}). Each persona had its own context and signed in through the console form; screenshot first, then DOM. Identity was proven with GET /api/v1/auth/get-session before each cell.
  • Execution: six runner subagents in parallel groups, each with its own ports and DBs, at most 4 groups at once. Automated pins ran afterwards one vitest at a time (--maxWorkers=1), with no server up.
  • Date: 2026-10-16, about 12:15–13:25 UTC.

Scope

Selector area:identity-auth → 23 runnable items. Planned: none. Blocked-hidden: none (--include-blocked returns the same 23).

Derived item verdicts

18 PASS / 5 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN. Item verdicts are derived strictly: any clause that is not pass, a skipped negative included, makes the item partial.

item rev prio surface verdict clauses pass non-pass clauses
identity-auth.sso-enforced-first-paint 2 P2 browser PASS 6/6 —
identity-auth.phone-signin-surfaces 3 P2 browser PASS 6/6 —
identity-auth.auth-method-matrix 7 P1 mixed PARTIAL 13/15 A0 blocked(environment); A4 blocked(environment)
identity-auth.invitation-scope-gates 3 P1 mixed PASS 10/10 —
identity-auth.admin-lifecycle-operations 7 P1 mixed PARTIAL 13/14 N2 skipped
identity-auth.session-list-revoke 2 P1 mixed PASS 10/10 —
identity-auth.api-key-ui-lifecycle 1 P1 mixed PASS 10/10 —
identity-auth.self-service-profile-password 1 P1 mixed PARTIAL 8/10 A5 blocked(fixture); N3 skipped
identity-auth.org-membership-team-management 3 P1 mixed PASS 11/11 —
identity-auth.teams-bu-membership 2 P2 mixed PARTIAL 7/8 N2 skipped
identity-auth.self-service-password-reset 1 P1 api PASS 9/9 —
identity-auth.oauth-app-consent-loop 2 P2 mixed PASS 10/10 —
identity-auth.linked-accounts-social 3 P2 mixed PASS 9/9 —
identity-auth.identity-import-wizard 2 P2 mixed PASS 10/10 —
identity-auth.two-factor-enrollment-reveal 2 P1 api PASS 9/9 —
identity-auth.two-factor-verify-to-activate 1 P1 api PASS 8/8 —
identity-auth.two-factor-backup-codes 2 P1 api PASS 10/10 —
identity-auth.two-factor-disable-lifecycle 1 P1 api PASS 10/10 —
identity-auth.first-run-owner-bootstrap 1 P1 mixed PASS 11/11 —
identity-auth.self-signup-gate 3 P1 mixed PASS 10/10 —
identity-auth.email-verification-loop 3 P2 mixed PASS 11/11 —
identity-auth.workspace-org-switch 3 P2 mixed PARTIAL 10/11 A5 blocked(fixture)
identity-auth.implicit-account-linking-ownership 2 P1 mixed PASS 12/12 —

Automated pins run (RUNNER rule 6)

All seven ran on os-qa @ 2034975a (vitest --maxWorkers=1) and all passed:

  • packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts: 3/3
  • delegated-admin-invite.dogfood.test.ts: 4/4
  • admin-identity-audit-trail.dogfood.test.ts: 3/3
  • admin-credential-lifecycle.dogfood.test.ts: 5/5
  • admin-route-nonadmin-refusal.dogfood.test.ts: 7/7
  • admin-platform-admin-standing.dogfood.test.ts: 7/7
  • packages/plugins/plugin-auth/src/implicit-account-linking.test.ts: 23/23

Reproduction rules

No clause scored fail, so none are owed. No authentication or authorization hole was found; nothing on this record is withheld under the RUNNER rule-2 carve-out.

Non-pass clauses: reasons

  • auth-method-matrix A0, A4: blocked(environment). Every variant that could be booted advertised and rendered exactly its config. That covers stock; phone + 2FA; production with phone on, OTP undeliverable and the OIDC provider off; a generic-OIDC fixture; and a custom issuer. The on-sides of deviceAuthorization and the @better-auth/sso plugin cannot be booted on os dev, so the "every variant" claim is not fully proven.
  • self-service-profile-password A5: blocked(fixture). N3: skipped. The passwordless (SSO/social-only) set-initial-password branch needs a provisioned passwordless user. Contrast proven: set-initial on a user with a password → 409 PASSWORD_ALREADY_SET.
  • workspace-org-switch A5: blocked(fixture). The server half holds: a forged set-active to a non-member org → 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION, and the active org becomes none, never the foreign org. The "UI surfaces an error" half has no UI gesture that can trigger it.
  • admin-lifecycle-operations N2: skipped. Moot while revoke-user-sessions stays refused by design (finding: seven better-auth /admin/ routes refuse platform admins and have no ObjectStack consumer — decide whether they are capability or just vendor surface #9969); the platform admin's 403 is recorded under A4.
  • teams-bu-membership N2: skipped. No intermediate-depth BU scope was authored; the depth matrix belongs to access-security.scope-depth-asymmetry.

Fixture gaps

  • No provisionable passwordless user on stock showcase. This blocks self-service-profile-password A5/N3.
  • Device authorization and the enterprise-SSO plugin have no os dev boot switch. This blocks auth-method-matrix A0/A4 on-sides.
  • Generic OIDC (oidcProviders) has no env door. The local-IdP fixture needs a scratch app copy plus applyConfigPatch; the recipe is held by this runner.

Checklist-accuracy findings and fixture gaps close out on #23878. Extracted defect cards are listed in the close-out comment.

Per-clause verdicts (text evidence)

A = acceptance index and N = negative index, both 0-based as in the item JSON. Evidence is trimmed to one line; secrets, tokens and backup codes are redacted.

item clause verdict evidence
sso-enforced-first-paint A0 pass cold-started b2 (OS_AUTH_SSO_ONLY=true + qa-oidc fixture), first request before any browser: GET /api/v1/auth/config → features.ssoEnforced true, features.sso false, socialProviders [{id:'qa-oidc',na…
sso-enforced-first-paint A1 pass 3 fresh contexts (2 plain, 1 with /auth/config delayed 3 s) using an init-script MutationObserver on every DOM change: input[type=password] count 0 at every recorded state (t=0 empty → ~0.8–4.0 s bre…
sso-enforced-first-paint A2 pass enf1-settled.png: 'Sign in with your organization's single sign-on', SSO button, no sign-up link (0 sign-up matches), 'Use a password instead' present; clicking it shows 1 password input and admin@ob…
sso-enforced-first-paint A3 pass hung sign-in simulated by holding POST /api/v1/auth/sign-in/social unanswered (Playwright route never fulfilled): at +1.5 s still pending; at ~+20 s (wd-2-after-window.png) the inline error 'Sign-in …
sso-enforced-first-paint A4 pass without enforcement: b1 (same fixture, no SSO flag) /auth/config ssoEnforced false + qa-oidc listed, first paint has visible email+password inputs at 894 ms next to the OIDC button (unenf-settled.png…
sso-enforced-first-paint N0 pass no password input appeared in any DOM state on 3 enforced loads (2 plain, 1 with the config fetch delayed 3 s), so the hydration-race trap was ruled out (the spinner holds until the config resolves).
phone-signin-surfaces A0 pass Both A and B: GET /api/v1/data/sys_user?filter=[[phone_number,=,+8613800138000]] as admin → 1 row: id usr_showcase_phone_demo, name 'Mei Phone (demo)', email phone.demo@example.com, phone_number +861…
phone-signin-surfaces A1 pass B, through console screenshots. The All Users list (/apps/com.objectstack.setup/sys_user/view/all_users) has a Phone Number column: Mei row +8613800138000, Ph One +15551230001. Mei's record detail hi…
phone-signin-surfaces A2 pass B: the console Create User posted POST /api/v1/auth/admin/create-user {email:ph2@example.test, phoneNumber:+15551230002, generatePassword:true} → 200. A fresh read, GET /api/v1/data/sys_user/jt9QOe8c…
phone-signin-surfaces A3 pass A (default): /auth/config features.phoneNumber:false; the Create User dialog (screenshot first) has inputs email/name/password only, no phoneNumber; the server refuses a forged create-user with phone…
phone-signin-surfaces N0 pass The seeded persona is present on both boots.
phone-signin-surfaces N1 pass With the flag false (A) the dialog offered no phoneNumber param.
auth-method-matrix A0 blocked(environment) Every variant I could boot advertised exactly what it booted. phoneNumber false/true/true on A/B/C. twoFactor false on A, true on B. oidcProvider true on A, false on C. phoneNumberOtp false on A, tru…
auth-method-matrix A1 pass A: uninvited sign-up/email → 403 SELF_REGISTRATION_CLOSED; invite-member 200 then sign-up 200; sign-in/email 200 + better-auth.session_token cookie; get-session 200 user ep1@example.test; sign-out 20…
auth-method-matrix A2 pass B: admin/create-user {phoneNumber:+15551230001} 200. POST /sign-in/phone-number with the right password → 200 + token; get-session names ph1@example.test, phoneNumber +15551230001. Wrong password → 4…
auth-method-matrix A3 pass C (NODE_ENV=production, phone on, no SMS provider): POST /phone-number/send-otp → 400 {code:SMS_SERVICE_REQUIRED, message names Setup → Settings → SMS Delivery}. /auth/config on the same boot: phoneN…
auth-method-matrix A4 blocked(environment) Login screenshots matched each booted config. A: an 'Email' field only, no OTP link, no provider button. B: 'Email or phone number' + 'Sign in with verification code'. C: 'Email or phone number', no …
auth-method-matrix A5 pass A (all off): POST /sign-in/phone-number 404; /phone-number/send-otp 404; /two-factor/enable (authed) 404; /device/code 404; /sign-in/sso 404; /sign-in/magic-link 404; GET /passkey/generate-register-o…
auth-method-matrix A6 pass B: enable 2FA for ph1 (two-factor/enable + verify-totp, sys_user.two_factor_enabled=true). Then sign-in/email → 200 {twoFactorRedirect:true, twoFactorMethods:[totp]} with only a better-auth.two_facto…
auth-method-matrix A7 pass test: oidc-authorization-code-flow.dogfood.test.ts 3/3 passed; plus live generic-OIDC round trip on a local IdP fixture (sign-in/social 200 → authorize 302 → callback 302 → /_console/home, get-sessio…
auth-method-matrix A8 pass A: both GET /.well-known/openid-configuration and /.well-known/oauth-authorization-server (app root) → 200 JSON with issuer http://localhost:47160/api/v1/auth. authorize/token/userinfo/jwks/register/…
auth-method-matrix A9 pass A: change-email (authed) 200 {status:true}; the session email stays ep1@example.test (unflipped); sys_email holds a 'Verify your ObjectStack email address' mail to ep1-new@ plus a security notice. An…
auth-method-matrix N0 pass No disabled method answered 2xx (see acceptance 5).
auth-method-matrix N1 pass With SMS genuinely undeliverable (production boot C) the answer is a loud 400 SMS_SERVICE_REQUIRED, not a hang or a 500. The dev-boot 200 is deliberate (log transport counts as deliverable outside pr…
auth-method-matrix N2 pass Magic-link and passkey browser lanes were not ticked: blocked(dependency, objectui#4179 closed not_planned). Their server routes 404 on stock boot A.
auth-method-matrix N3 pass No magicLink or passkeys key appears in /auth/config on boots A, B, C or D.
auth-method-matrix N4 pass Discovery issuer/endpoints resolve on the live server for both the default and the bespoke-issuer boot (acceptance 8).
invitation-scope-gates A0 pass live-REST plus browser. g3deleg (positions delegated_admin) POST /auth/organization/invite-member {role:'member'} -› 200 status=pending. Console: Account app › Invitations › 'Invite User' dialog › Co…
invitation-scope-gates A1 pass The same principal with role:'admin' -› 403 '[Security] Access denied: you may not invite a member as 'admin' — an invitation can never confer a role above the issuer's own (ADR-0090 D12...)'. This w…
invitation-scope-gates A2 pass sys_invitation re-read right after each refused admin/owner attempt (REST and UI) holds only the member invitations. There is no row for g3invitee2, g3invitee3 or g3invitee8.
invitation-scope-gates A3 pass g3forger (org_member) invite-member {role:'member'} -› 403 YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION. A forged POST /data/sys_invitation -› 405 OBJECT_API_METHOD_NOT_ALLOWED. The invit…
invitation-scope-gates A4 pass Screenshots (Account app, /_console/apps/com.objectstack.account/sys_invitation and /sys_user). g3deleg's toolbar shows 'Invite User' on both lists. g3forger's has no Invite User: the Invitations pag…
invitation-scope-gates A5 pass Every status read is in the spec InvitationStatus enum {pending,accepted,rejected,expired,canceled}. Resend (invite-member resend:true) -› 200, same row id, expires_at moved from 12:59:30 to 12:59:54…
invitation-scope-gates A6 pass GET /security/my-delegable-scope. g3deleg -› 200 {isTenantAdmin:false, scopes:[], placeableBusinessUnitIds:[], assignablePositions:[]}, and adding ?userId=‹admin› changes nothing (no target param). g…
invitation-scope-gates N0 pass The admin and owner invitations were refused and left no row behind.
invitation-scope-gates N1 pass The member's affordance is hidden AND the forged request is refused server-side (403/405).
invitation-scope-gates N2 pass my-delegable-scope did not over-report: it was empty for both the delegate and the member.
admin-lifecycle-operations A0 pass live-REST: admin POST /auth/admin/ban-user {userId,banReason:'g3 qa ban'} -› 200. sys_user re-read shows banned=true, ban_reason='g3 qa ban'. Target sign-in/email -› 403 BANNED_USER, and the target's…
admin-lifecycle-operations A1 pass live-REST: admin POST /auth/admin/set-user-password -› 200 {mustChangePassword:true, revoked.sessions:1}. Sign-in with the NEW password -› 200. Sign-in with the OLD password -› 401 INVALID_EMAIL_OR_P…
admin-lifecycle-operations A2 pass live-REST: POST /auth/admin/create-user {password:'Explicit#Pass1', generatePassword:true} -› 200, and the response carries no generated password. Sign-in with the EXPLICIT password -› 200. GET /data…
admin-lifecycle-operations A3 pass live-REST: admin POST /auth/admin/set-role {role:'admin'} -› 403 YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE. The sys_user re-read shows role='user' before and after, and the target's get-session positi…
admin-lifecycle-operations A4 pass live-REST: the target's protected GET /data/sys_user read 200 just before. Admin POST /auth/admin/revoke-user-sessions -› 403 YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS. The same protected read rig…
admin-lifecycle-operations A5 pass live-REST: throwaway g3owner created showcase_inquiry UG2_X6nONucrmVB8 (owner_id=g3owner). Admin POST /auth/admin/remove-user -› 403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS. Re-reads show the user still …
admin-lifecycle-operations A6 pass live-REST: admin POST /auth/admin/impersonate-user -› 200. get-session then returns user=g3target with session.impersonatedBy=‹admin id›, and the sys_session row has impersonated_by=‹admin id›. POST …
admin-lifecycle-operations A7 pass screenshot (STILL-MANUAL clause): Setup › Users › row menu (G3 Target) › 'Impersonate User' › confirm dialog › Continue. A red banner then sits across the top of every page: 'You are impersonating G3…
admin-lifecycle-operations A8 pass test: admin-identity-audit-trail.dogfood.test.ts 3/3 passed (vitest, os-qa 2034975) — create-user / set-user-password / import-users audit shapes; live corroboration: sys_audit_log rows attributed t…
admin-lifecycle-operations A9 pass live-REST, g3forger (positions org_member,everyone). ban-user 403 PERMISSION_DENIED. list-users 403 YOU_ARE_NOT_ALLOWED_TO_LIST_USERS. create-user 403 PERMISSION_DENIED. set-role 403 YOU_ARE_NOT_ALLO…
admin-lifecycle-operations N0 pass No forged non-admin admin call succeeded: 8 routes plus 1 anonymous call were all refused, and the state was unchanged.
admin-lifecycle-operations N1 pass The ban is enforced at sign-in (403 BANNED_USER), not only as a list filter.
admin-lifecycle-operations N2 skipped Moot while revoke-user-sessions stays ruled (#9969). The route refused the platform admin, as recorded under clause 4.
admin-lifecycle-operations N3 pass Partial live check: the explicit password signed in, so there is no #3031 regression on that path. The owner_id cascade half is left to the plugin-security pin.
session-list-revoke A0 pass A had 2 live sessions (device-1 nLwRN9r1…, device-2 RF3pqsfy…, both from browser contexts) + B 1 (zp6Bu96d…). A GET /auth/list-sessions 200 -› exactly those 2 ids; A GET /data/sys_session 200 -› only…
session-list-revoke A1 pass Screenshot of Account › Active Sessions (sys_session/view/mine) as A: 'My Sessions' tab, 2 rows with IP Address/Active Organization/Created At/Expires At columns, footer '2 records' = API count of li…
session-list-revoke A2 pass device-1 POST /auth/revoke-session {token:‹device-2 token›} 200 {status:true}; device-2 GET /data/sys_session -› 401 UNAUTHENTICATED and get-session 401; device-1 GET /data/sys_session -› 200. Unenti…
session-list-revoke A3 pass Added device-3 (TaCewA4M…); list-sessions = 2; device-1 POST /auth/revoke-other-sessions 200; list-sessions -› 1 row [nLwRN9r1… = device-1 sid]; device-3 GET /data/sys_session -› 401; device-1 -› 200…
session-list-revoke A4 pass Admin Setup › Sessions all_sessions screenshot: cross-user rows (Dev Admin, Mei Phone (demo), Ada Auditor (demo)) with User column, 5 records. Revoke metadata is on the sibling 'Revoked' tab (view re…
session-list-revoke A5 pass As A: PATCH/DELETE/POST /data/sys_session[/id] -› 405 OBJECT_API_METHOD_NOT_ALLOWED (allowed get,list,aggregate,export); PUT -› 405 METHOD_NOT_ALLOWED. As admin: PATCH/DELETE on B's session -› 405 sa…
session-list-revoke N0 pass No B row in A's list-sessions or data read.
session-list-revoke N1 pass device-1 stayed 200 after revoking device-2.
session-list-revoke N2 pass revoke-other-sessions kept device-1 (200) and device-3 got 401.
session-list-revoke N3 pass Revoked sessions answered 401 on the very next request.
api-key-ui-lifecycle A0 pass UI Generate key on Account › Developer › Integrations (/_console/apps/com.objectstack.account/developer/integrations) fired POST /api/v1/keys 201 {data:{id:Zy20EaCpp1VD-udR,name,prefix:osk_2PFDqPdb,k…
api-key-ui-lifecycle A1 pass GET /data/showcase_task with x-api-key (no cookie/bearer) 200, 10 rows, id set identical to owner's session read. Not a superuser: x-api-key GET /data/sys_session returns only user_id=usr_showcase_au…
api-key-ui-lifecycle A2 pass Owner PATCH /data/sys_api_key/{id} {revoked:true} 200 -› next x-api-key call refused 403 API_KEY_REVOKED (reproduced on 3 cycles). Clause text says 401; 403 is the deliberate #23367 ruling (packages/…
api-key-ui-lifecycle A3 pass PATCH {revoked:false} 200 -› x-api-key GET showcase_task 200 (10 rows), reproduced 3 cycles.
api-key-ui-lifecycle A4 pass Second member phone.demo: GET /data/sys_api_key -› 0 rows, GET by id -› 404 RECORD_NOT_FOUND, mine-view screenshot 'No identity records'; forged PATCH {revoked:true} on A's key -› 404 and key still 2…
api-key-ui-lifecycle A5 pass Dialog screenshot: 'Your new API key — Copy this now — for your security it will not be shown again.' with the osk_ secret + Copy button; re-opened Account › API Keys (mine) screenshot: one row qa-g4…
api-key-ui-lifecycle N0 pass Raw secret absent from get-by-id, list, mine grid, admin list.
api-key-ui-lifecycle N1 pass Revoked key -› 403 API_KEY_REVOKED, never 200.
api-key-ui-lifecycle N2 pass Key read of sys_session = owner rows only vs admin 3 users.
api-key-ui-lifecycle N3 pass phone.demo list 0 rows / get 404.
self-service-profile-password A0 pass qa.prof (form sign-in) edited Name -› 'QA Prof Renamed' + Save Changes: POST /auth/update-user 200 {status:true}; get-session user.name='QA Prof Renamed'; after page.reload the Name input and identit…
self-service-profile-password A1 pass setInputFiles on [data-testid=profile-avatar-file] (64x64 PNG): POST /storage/upload/presigned 200 -› PUT /storage/_local/raw/‹signed› 200 -› POST /storage/upload/complete 200 {fileId,path:user/‹id›.…
self-service-profile-password A2 pass Click profile-avatar-remove-btn -› POST /auth/update-user 200; get-session image=null; screenshot shows 'QP' initials fallback and Upload button again (2 runs).
self-service-profile-password A3 pass PasswordCard (#current-password/#new-password/#confirm-password, 'Change password') with correct current -› POST /auth/change-password 200; API sign-in with OLD -› 401 INVALID_EMAIL_OR_PASSWORD, with…
self-service-profile-password A4 pass UI with wrong current -› POST /auth/change-password 400 INVALID_PASSWORD, card shows 'Invalid password' (screenshot); old password still signs in 200, new one 401. Direct forged API change-password w…
self-service-profile-password A5 blocked(fixture) see ref
self-service-profile-password N0 pass Name survived reload; get-session confirms server write.
self-service-profile-password N1 pass image is /api/v1/storage/files/‹id›, a committed storage object, not data:.
self-service-profile-password N2 pass Wrong current -› 400 INVALID_PASSWORD (UI and forged API).
self-service-profile-password N3 skipped Needs the passwordless persona (blocked fixture).
org-membership-team-management A0 pass Screenshot: Setup › People & Organization › Organization opens /_console/apps/com.objectstack.setup/sys_organization/record/org_mvayuz49hd2evoo1, the record page (not the list), with tabs Members 10 …
org-membership-team-management A1 pass g3m2 as member: create-team -› 403 YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION. Admin update-member-role to 'admin' -› 200, list-members shows admin and positions become ['org_admin','ev…
org-membership-team-management A2 pass update-member-role {role:'guest'} -› 400 {code:ROLE_NOT_FOUND}. {role:'contributor'} (a showcase position) -› 400 ROLE_NOT_FOUND. invite-member with guest/contributor -› 400 'ROLE_NOT_FOUND: …'. The …
org-membership-team-management A3 pass POST /auth/organization/update {data:{name:'G3 Renamed Org'}} -› 200. get-full-organization.name went from 'Default Organization' to 'G3 Renamed Org', sys_organization agrees, and the console record …
org-membership-team-management A4 pass Before: g3m3 had get-full-organization 200 and list-members 200 (10). Admin remove-member -› 200 and m3 is gone from the roster. m3's live session is killed at once: get-session and data reads -› 401…
org-membership-team-management A5 pass create-team g3-team-A -› 200. add-team-member for m2 and m3 -› 200 each. GET /data/sys_team_member returns exactly two (team_id,user_id) rows. A duplicate add returns the existing row id and the row …
org-membership-team-management A6 pass g3forger (member) AND g3deleg (delegated_admin) each called update-member-role, update-member-role to owner on self, remove-member, update, create-team, add-team-member and remove-team-member. All go…
org-membership-team-management N0 pass Hidden affordance with a forged success: not observed. Every forged call was 403.
org-membership-team-management N1 pass remove-member changed authorization: the session was killed, org_member was dropped, and org endpoints were refused.
org-membership-team-management N2 pass No role outside the 4-name vocabulary was stored. guest and contributor -› 400 ROLE_NOT_FOUND, and the role change bites.
org-membership-team-management N3 pass The nav resolved to the record page for the active org, not the list.
teams-bu-membership A0 pass sys_team_member for g3-team-A: [m2, m3] (one row each). A duplicate add-team-member returns the existing row and creates no second one. Add forger -› 3 rows. remove-team-member {teamId,userId:forger}…
teams-bu-membership A1 pass POST /data/sys_business_unit {name:'G3 Child Dept', kind:'department', parent_business_unit_id:'bu_acme'} -› 201, and the re-read shows parent bu_acme. Org Chart screenshot (Setup › Business Units, O…
teams-bu-membership A2 pass POST /data/sys_business_unit_member -› 201. The read shows business_unit_id=‹child›, user_id=g3m2, function_in_business_unit='lead', is_primary=true.
teams-bu-membership A3 pass Scoped persona g3m2 (plain member). Before any placement: showcase_inquiry 0 rows (admin sees 4: 2 status=new). Placed in G3 Child Dept under bu_acme (outside the bu_field_ops subtree): still 0. Chil…
teams-bu-membership A4 pass g3forger: POST/PATCH/DELETE sys_business_unit and POST/PATCH sys_business_unit_member -› 403 PERMISSION_DENIED each. create-team -› 403 YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION. add-t…
teams-bu-membership N0 pass add-team-member created a row and did not duplicate it.
teams-bu-membership N1 pass The placement plus re-parent moved the scoped read (0 -› 2 -› 0 -› 2).
teams-bu-membership N2 skipped No intermediate-depth BU scope was authored in this run. The rule exercised is unit_and_subordinates and it enforced exactly. The depth matrix belongs to access-security.scope-depth-asymmetry.
self-service-password-reset A0 pass POST /auth/request-password-reset {email:reset1@g2.test} -› 200 {status:true,message:'If this email exists...'}; LogTransport-sent sys_email row (message_id ‹dev-...@objectstack.local›, subject 'Rese…
self-service-password-reset A1 pass POST /auth/reset-password {token,newPassword:NewPass#24680} -› 200 {status:true}; sign-in new pw -› 200; sign-in old pw -› 401 INVALID_EMAIL_OR_PASSWORD
self-service-password-reset A2 pass second POST /auth/reset-password with the same token -› 400 {code:INVALID_TOKEN}; the pw it tried to set -› sign-in 401
self-service-password-reset A3 pass garbage token -› 400 INVALID_TOKEN, and a fresh real token with its last char flipped -› 400 INVALID_TOKEN; the current pw still signs in (200) and the garbage pw is refused (401). Natural expiry not…
self-service-password-reset A4 pass known (reset1@g2.test) and unknown (nobody-here@g2.test) requests both -› 200 with a byte-identical body {status:true,message:'If this email exists in our system, check your email for the reset link'…
self-service-password-reset N0 pass reset 2xx, then new pw 200 and old pw 401
self-service-password-reset N1 pass reused token -› 400 INVALID_TOKEN, pw unchanged
self-service-password-reset N2 pass identical status and body for known and unknown
self-service-password-reset N3 pass garbage token -› 400 INVALID_TOKEN, not a silent success
oauth-app-consent-loop A0 pass Runner env had no OS_MCP_SERVER_ENABLED or OS_OIDC_PROVIDER_ENABLED. On stock A: /auth/config oidcProvider:true; GET /.well-known/oauth-authorization-server 200 with authorization_endpoint .../api/v1…
oauth-app-consent-loop A1 pass A, admin: POST /oauth2/create-client {client_name 'QA G5b App', redirect_uris [https://qa-rp.example.test/cb], client_secret_post} → 201 carrying client_id + client_secret. Later GET /oauth2/get-clie…
oauth-app-consent-loop A2 pass The resource owner ep1 signed in through the console form. GET /oauth2/authorize (scope 'openid profile email', PKCE S256) → 302 to /_console/oauth/consent. Screenshot: 'QA G5b App wants to access yo…
oauth-app-consent-loop A3 pass Authorize → POST /oauth2/consent 200 → redirect https://qa-rp.example.test/cb?code=…&state=st-t1&iss=… . POST /oauth2/token (code + code_verifier + client_secret_post) → 200 access_token, id_token, s…
oauth-app-consent-loop A4 pass A repeat authorize for the same client and scopes as ep1 → 302 straight to https://qa-rp.example.test/cb?code=rrL94…&state=st-t2. The consent page never loaded, and that code exchanged for a token (2…
oauth-app-consent-loop A5 pass Authorize with the added offline_access scope → consent page → Deny → redirect cb?error=access_denied&error_description=User+denied+access&state=st-t3, with no code. Afterwards sys_oauth_access_token…
oauth-app-consent-loop A6 pass ep1 get-consents → only userId IaOa… (ep1). Admin, after granting their own consent, sees only userId kcRN… with scopes [openid, email]; before that grant, admin saw []. Admin GET /oauth2/get-consent…
oauth-app-consent-loop N0 pass Neither get-client nor the data API returns the secret (acceptance 1).
oauth-app-consent-loop N1 pass Approve recorded a consent (and the screen was skipped on repeat); deny minted no tokens.
oauth-app-consent-loop N2 pass No cross-user consent visibility (acceptance 6).
linked-accounts-social A0 pass [local OIDC fixture] member L (invite+sign-up, signed in through the console FORM; get-session ll1@example.test) → in-page POST /api/v1/auth/link-social {provider:'qa-oidc', callbackURL:‹origin›/_con…
linked-accounts-social A1 pass screenshots la-2-linked-after.png (All tab) and lb-1-mylinks.png (nav 'Linked Accounts' → /apps/com.objectstack.account/sys_account/view/mine) show only User L's 2 rows (credential + qa-oidc); U's an…
linked-accounts-social A2 pass row menu 'Unlink Account' → confirm → network POST /api/v1/auth/unlink-account {accountId:'e9qpJ8tozG19FsiGw2JHlHqKYE3izXvR' (the ROW id), organizationId} 200 {status:true}; view re-renders '1 record…
linked-accounts-social A3 pass provider-less stock boot b3 (/auth/config socialProviders []): Linked Accounts (pl-1-linked.png) has no link affordance — header '…' menu = 'Add to favorites' only, row menu = 'Unlink Account' only, …
linked-accounts-social A4 pass as member L and as admin: POST /api/v1/data/sys_account → 405 OBJECT_API_METHOD_NOT_ALLOWED; DELETE /data/sys_account/‹id› → 405; PATCH → 405; body allowed:['get','list','aggregate','export']; the ta…
linked-accounts-social N0 pass every link that appeared in the UI had a durable sys_account row in the server-side admin read (L e9qp…, U NXsop…, W FDaLG…).
linked-accounts-social N1 pass after unlink the row is gone server-side (admin 404) AND a fresh-context implicit sign-in as L through qa-oidc is refused: final redirect /_console/login?error=account_not_linked ('This sign-in metho…
linked-accounts-social N2 pass L's mine/All views and data-API list never contained U's or W's rows; cross-user probe: L POST unlink-account {accountId: W's row id} → 400 ACCOUNT_NOT_FOUND, W's row intact.
linked-accounts-social N3 pass provider-less boot offers no link affordance at all (see acceptance 3); nothing navigates to a dead endpoint.
identity-import-wizard A0 pass Console wizard (Setup › Users › Import › upload scratch CSV › Mapping auto-matched email+name › Preview 'Automatic (recommended)' › Import 2 Rows). POST /auth/admin/import-users {passwordPolicy:'auto…
identity-import-wizard A1 pass Wizard with 'Temporary passwords': import-users {passwordPolicy:'temporary'} -› 200, delivery {temporary:2, emailInvite:0}, and every row carries temporaryPassword. The result step shows 'Temporary p…
identity-import-wizard A2 pass Temporary row: sign-in/email g3imp3 with the revealed password -› 200. A protected read is then 403 PASSWORD_RESET_REQUIRED until change-password, after which sign-in with the chosen password -› 200.…
identity-import-wizard A3 pass Wizard re-run of the same auto CSV with 'Update if matched, else create' + Match on Email: import-users {mode:'upsert', matchBy:'email'} -› 200 {created:0, updated:2}. REST upsert of the temporary CS…
identity-import-wizard A4 pass Neither temporary password string appears in /data reads of sys_user, sys_account, sys_session, sys_audit_log, sys_email or sys_notification (limit 500), in boot.log, or in a raw byte grep of data.db…
identity-import-wizard A5 pass g3forger (member) and g3deleg (delegated_admin): import-users (insert and upsert) -› 403 PERMISSION_DENIED 'Admin role required'. Anonymous -› 401 UNAUTHENTICATED. sys_user is unchanged: no g3forgedi…
identity-import-wizard N0 pass Every temporary row carried a one-time credential.
identity-import-wizard N1 pass The one-time password was not found in any server-side read, log or DB file.
identity-import-wizard N2 pass Upsert updated and did not duplicate (the set stays 4).
identity-import-wizard N3 pass Non-admin import refused, 403.
two-factor-enrollment-reveal A0 pass GET /api/v1/auth/config -› features.twoFactor true; anon POST /auth/two-factor/enable {} -› 400 VALIDATION_ERROR (not 404). Contrast unarmed boot: false + 404
two-factor-enrollment-reveal A1 pass enable {password} -› 200 {method,totpURI,backupCodes}; totpURI otpauth://totp/ObjectStack:admin@objectos.ai, issuer=ObjectStack, algorithm absent, digits=6, period=30, secret 52 base32 chars -› base3…
two-factor-enrollment-reveal A2 pass backupCodes: array of 10 strings, each 11 chars, alnum with '-' (values redacted)
two-factor-enrollment-reveal A3 pass meta sys_two_factor.enable_two_factor resultDialog.fields = [totpURI/qrcode, backupCodes/code-list]; response.totpURI string, response.backupCodes string[10] — both resolve
two-factor-enrollment-reveal A4 pass POST /two-factor/get-totp-uri: pre-verify {} -›400 VALIDATION_ERROR, wrong pw -›400 INVALID_PASSWORD, correct pw -›200 SAME secret as enable, anon -›401; post-verify identical (400/400/200 same secre…
two-factor-enrollment-reveal A5 pass GET /api/v1/meta/object/sys_user item.actions enable_two_factor: resultDialog fields [totpURI qrcode, backupCodes code-list], acknowledge 'I have saved my backup codes', successMessage null, target /…
two-factor-enrollment-reveal N0 pass wrong-password enable -› 400 INVALID_PASSWORD with 0 sys_two_factor rows after; anon enable {password} -› 401 UNAUTHORIZED
two-factor-enrollment-reveal N1 pass guard scored first on armed boot; unarmed contrast confirmed 404
two-factor-enrollment-reveal N2 pass get-totp-uri refuses without/with wrong password; no unauthenticated re-reveal
two-factor-verify-to-activate A0 pass same guard as enrollment-reveal on this boot: features.twoFactor true, enable probe 400
two-factor-verify-to-activate A1 pass Diverges from the expected wording, recorded as observed: between enable and verify, POST /auth/sign-in/email -› 200 with session token, NO twoFactorRedirect and NO better-auth.two_factor cookie (get…
two-factor-verify-to-activate A2 pass sqlite read-only row read ~2ms after enable 200: sys_two_factor.verified = 0 (healthy). Note: the physical column DDL default is '1' (sqlite dflt_value), i.e. the declared defaultValue:true reached t…
two-factor-verify-to-activate A3 pass session-lane POST /two-factor/verify-totp {correct} -› 200 {token,user} + rotated better-auth.session_token; row verified 0-›1, failed_verification_count 0; sys_user.two_factor_enabled 0-›1; get-sess…
two-factor-verify-to-activate A4 pass post-activation sign-in -› 200 twoFactorRedirect:true, no token, only better-auth.two_factor cookie; get-session with that jar -› 401; cookie-lane verify-totp correct -› 200 hasToken + Set-Cookie ses…
two-factor-verify-to-activate N0 pass no never-verified enrollment satisfied a challenge (no challenge issued for it at all)
two-factor-verify-to-activate N1 pass session-lane wrong code -› 401 INVALID_CODE; failed_verification_count stayed 0, verified stayed 0, two_factor_enabled 0; subsequent correct code activated
two-factor-verify-to-activate N2 pass armed boot guard confirmed
two-factor-backup-codes A0 pass guard as above + verified enrollment (verified=1, two_factor_enabled=1) and challenge trace twoFactorRedirect:true from verify-to-activate on this boot
two-factor-backup-codes A1 pass challenge -› POST /two-factor/verify-backup-code {code[0]} via two_factor cookie -› 200 hasToken + session_token cookie; get-session 200 admin
two-factor-backup-codes A2 pass fresh challenge, code[0] again -› 401 INVALID_BACKUP_CODE (get-session 401); fresh challenge code[1] -› 200 session; reproduced: code[1] re-spend at a further challenge -› 401 INVALID_BACKUP_CODE
two-factor-backup-codes A3 pass generate-backup-codes {password} -› 200 {status,backupCodes[10]}, 0 overlap with old; old unspent code[3] -› 401 INVALID_BACKUP_CODE, old code[4] -› 401 (twice); new code[0] -› 200 session
two-factor-backup-codes A4 pass sqlite read: backup_codes is a 362-char hex string (non-JSON), changes on regenerate, shrinks to 334 after a spend; no plaintext code (with or without dashes) is a substring — encrypted blob, healthy…
two-factor-backup-codes A5 pass meta sys_user.generate_backup_codes: resultDialog fields [backupCodes code-list], acknowledge 'I have saved the new codes', successMessage null, refreshAfter false; regenerate response.backupCodes st…
two-factor-backup-codes N0 pass no code spendable twice (two codes tested)
two-factor-backup-codes N1 pass two old-set codes refused after regeneration
two-factor-backup-codes N2 pass wrong-pw generate -› 400 INVALID_PASSWORD (no codes returned); anon -› 401 UNAUTHORIZED
two-factor-backup-codes N3 pass after refused wrong-pw regenerate, old code[2] still spent -› 200 session (refusal was a no-op)
two-factor-disable-lifecycle A0 pass features.twoFactor true; BEFORE trace: sign-in twoFactorRedirect:true + two_factor cookie with active (verified=1) enrollment
two-factor-disable-lifecycle A1 pass disable {wrong pw} via a full session -› 400 INVALID_PASSWORD; row still present verified=1, two_factor_enabled=1; fresh sign-in still twoFactorRedirect:true. anon disable -› 401
two-factor-disable-lifecycle A2 pass disable {correct} -› 200 {status:true} (+ rotated session cookie); sqlite: 0 sys_two_factor rows for user; sys_user.two_factor_enabled 0
two-factor-disable-lifecycle A3 pass post-disable sign-in -› 200 with token, twoFactorRedirect absent, only better-auth.session_token cookie (no two_factor); get-session 200 twoFactorEnabled false
two-factor-disable-lifecycle A4 pass retained unspent code post-disable: no cookie -› 401 INVALID_TWO_FACTOR_COOKIE; with session -› 400 BACKUP_CODES_NOT_ENABLED; with leftover pre-disable 2FA cookie -› 400 BACKUP_CODES_NOT_ENABLED. Re-…
two-factor-disable-lifecycle A5 pass 47111 mfa_required:true grace 0 (patch also flipped features.twoFactor false-›true, enable 404-›400): first authed GET /api/v1/data/sys_user -› 403 MFA_REQUIRED, stamp mfa_required_at=12:39:24.484Z w…
two-factor-disable-lifecycle N0 pass wrong-pw disable refused; row-gone and challenge-gone agree
two-factor-disable-lifecycle N1 pass old TOTP / old backup codes refused at the re-enrolled challenge
two-factor-disable-lifecycle N2 pass gated user's /two-factor/enable answered 400/200, never 404
two-factor-disable-lifecycle N3 pass immediate-bite recorded as the retained-stamp branch, not filed
first-run-owner-bootstrap A0 pass zero-user OS_DISABLE_SIGNUP=true --no-seed-admin fresh DB: anonymous GET /auth/bootstrap-status -› 200 {hasOwner:false}; after the wizard, anonymous -› 200 {hasOwner:true}
first-run-owner-bootstrap A1 pass /auth/config disableSignUp:true on that boot; the wizard's POST /auth/sign-up/email (owner@g2.test) -› 200 with a token; get-session -› owner@g2.test, positions [platform_admin,org_owner,everyone], i…
first-run-owner-bootstrap A2 pass CJK-only org name '测试公司': the network shows organization/list polls then POST organization/update -› 200 {name:'测试公司',slug:'default',id:org_mvaytwl5yk2g2tzr}, and no create call; organization/list af…
first-run-owner-bootstrap A3 pass after set-active, document requests for /_console/ fire and a window marker set before submit is gone, so this was a full-document navigation. Landed on /_console/home: the screenshot shows 'Good aft…
first-run-owner-bootstrap A4 pass disabled boot after bootstrap: two anonymous forges -› 403 SELF_REGISTRATION_CLOSED (audience door); an invited address -› 400 EMAIL_PASSWORD_SIGN_UP_DISABLED (toggle door); sys_user still [owner@g2.…
first-run-owner-bootstrap A5 pass hasOwner:true. Anonymous fresh context /_console/setup lands on /_console/login?redirect=%2Fsetup (screenshot: sign-in card, no wizard). Owner context (session 200 owner@g2.test) /_console/setup land…
first-run-owner-bootstrap N0 pass the wizard does not render once hasOwner:true, for anonymous or owner
first-run-owner-bootstrap N1 pass every post-bootstrap anonymous sign-up refused on both boots; no new rows
first-run-owner-bootstrap N2 pass full-document exit, landed console shows the owner's apps
first-run-owner-bootstrap N3 pass CJK-only name applied as the display name, slug 'default' kept
first-run-owner-bootstrap N4 pass one org only, renamed through organization/update
self-signup-gate A0 pass /auth/config per boot: stock {audiencePosture:invite_only, disableSignUp:false}; OS_DISABLE_SIGNUP=true boot {invite_only, disableSignUp:true}; OS_AUTH_AUDIENCE_POSTURE=open boot {open, disableSignUp…
self-signup-gate A1 pass stock POST /auth/sign-up/email uninvited@g2.test -› 403 {code:SELF_REGISTRATION_CLOSED}; admin GET /api/v1/data/sys_user has no uninvited row (users: reset1, admin, invitee, auditor.demo, phone.demo)
self-signup-gate A2 pass invite-member invitee@g2.test -› 200 status pending; sign-up with that exact email -› 200 with a token; get-session -› invitee@g2.test, positions [org_member,...]. Console: /register?redirect=/accept…
self-signup-gate A3 pass disabled boot: /_console/register?redirect=%2Fapps%2Fshowcase_app lands on /_console/login?redirect=%2Fapps%2Fshowcase_app; the screenshot shows the sign-in card, and the DOM has only email/password/…
self-signup-gate A4 pass disabled boot after an owner exists: uninvited forge -› 403 SELF_REGISTRATION_CLOSED (audience door answers first); INVITED email (invite 200) -› 400 {code:EMAIL_PASSWORD_SIGN_UP_DISABLED} (the disab…
self-signup-gate N0 pass uninvited -› 403 code, no synthetic 200
self-signup-gate N1 pass no row created for uninvited@g2.test
self-signup-gate N2 pass endpoint refuses on the disabled boot (403 or 400 per door), so this is not UI-only
self-signup-gate N3 pass invited registrant admitted 200 on stock invite_only
self-signup-gate N4 pass stock (hasOwner:true) /login has no Sign up link (DOM: dev-admin-hint, email, Forgot password?, password, Sign In); /register renders data-testid=register-by-invitation 'Self-registration is not open…
email-verification-loop A0 pass (a)-style boot OS_AUTH_REQUIRE_EMAIL_VERIFICATION=true, invited verify1/2/3: unverified POST /auth/sign-in/email -› 403 {code:EMAIL_NOT_VERIFIED}; anonymous get-session -› 401. Console: the login for…
email-verification-loop A1 pass sign-up -› 200 {token:null}; sys_email row (LogTransport id ‹dev-...›, status sent) subject 'Verify your ObjectStack email address' carries .../api/v1/auth/verify-email?token=‹JWT›&callbackURL=...; t…
email-verification-loop A2 pass verify2: the sign-up mail's token, GET /auth/verify-email?token= (no callbackURL) -› 200 {status:true,user:null}; sign-in -› 200; get-session -› verify2@g2.test emailVerified:true. POST /verify-email…
email-verification-loop A3 pass API resend POST /send-verification-email -› 200 {status:true}, and the console prompt's 'Resend verification email' button -› POST 200, UI 'Verification email sent!'. A second sys_email capture with …
email-verification-loop A4 pass GET /auth/verify-email?token=garbage.tok.en -› 401 {code:INVALID_TOKEN}; sign-in afterwards still 403 EMAIL_NOT_VERIFIED; the console /verify-email?token=garbage screenshot shows the 'Verification fa…
email-verification-loop A5 pass stock /auth/config emailPassword.requireEmailVerification:false, and an invited stock registrant (emailVerified:false) signs in 200 with a token, no challenge. Forced boot (posture open via env, no o…
email-verification-loop N0 pass unverified sign-in refused server-side 403
email-verification-loop N1 pass every runtime send landed a sys_email row (status sent). The only failure, the seed-time TEMPLATE_NOT_FOUND, was thrown into the log, not swallowed
email-verification-loop N2 pass open boot advertises true
email-verification-loop N3 pass stock boot does not challenge
email-verification-loop N4 pass garbage token -› 401 and the error card; the account stays unverified
workspace-org-switch A0 pass STOCK /auth/config tenancyPosture=single multiOrgEnabled=false: admin and A top bar = no workspace-switcher, no current-organization-indicator (screenshots + DOM 0/0); a 2nd membership is unreachable…
workspace-org-switch A1 pass Both walled boots: dropdown click -› POST /auth/organization/set-active {organizationId:‹Second Org›} 200; post-reload GET /auth/organization/get-active-member -› organizationId = Second Org id.
workspace-org-switch A2 pass After set-active the browser issued a document request for /_console/ (resourceType document), then main-frame navs /_console/ -› /_console/apps/showcase_app/…; a window.__marker set before the click…
workspace-org-switch A3 pass ISOLATED: pre-switch GET /data/showcase_task 10 rows all organization_id=Default, sentinel 7zaJjuUtkNIzQbc5 present; post-switch 10 rows all organization_id=Second Org, sentinel absent, GET /data/sho…
workspace-org-switch A4 pass GROUP dropdown screenshot: header 'Working organization', hint 'New records are created here. Views show data from all your organizations.' (data-testid workspace-switcher-group-hint count 1). ISOLAT…
workspace-org-switch A5 blocked(fixture) see ref
workspace-org-switch N0 pass single: no indicator, no switcher (admin and member).
workspace-org-switch N1 pass isolated reads moved to the new org after switch; meta unchanged.
workspace-org-switch N2 pass 1 membership: no dropdown; indicator has no click target/menu.
workspace-org-switch N3 pass non-member set-active 403; foreign org never active.
workspace-org-switch N4 pass switch exits via document reload.
implicit-account-linking-ownership A0 pass [local OIDC fixture, IdP answers email=uu1@example.test email_verified:true, signed id_token] premise: U (invite+sign-up) get-session emailVerified false. Fresh jar: POST /auth/sign-in/social {provid…
implicit-account-linking-ownership A1 pass verified U through the product: POST /auth/send-verification-email → captured link in the sys_email row 'Verify your ObjectStack email address' → GET /auth/verify-email?token=… 200 {status:true}; ema…
implicit-account-linking-ownership A2 pass U (password session, get-session uu1) POST /auth/unlink-account {accountId:'vfnJRf10…' row id} 200 {status:true}; admin read 0 rows; signed out; fresh-jar implicit sign-in → 302 /_console/login?error…
implicit-account-linking-ownership A3 pass U explicit: POST /auth/link-social with U's session → round-trip → 302 /_console/apps/account/sys_account; (U, qa-oidc) row NXsopcb5… back; next fresh-jar implicit sign-in → 302 /_console/ no error, …
implicit-account-linking-ownership A4 pass test: plugin-auth implicit-account-linking.test.ts 23/23 passed — incl. "exempts the platform identity provider…", "binds the platform exception to the OAuth sign-in method, not the provider id alone…
implicit-account-linking-ownership N0 pass unverified U: no sys_account row and no session after the implicit sign-in (0 rows, get-session 401).
implicit-account-linking-ownership N1 pass after the refusal U's email_verified re-read as false; W's stayed false after the explicit link.
implicit-account-linking-ownership N2 pass post-unlink implicit sign-ins for U (and for L in the sibling item) were refused and re-created no row.
implicit-account-linking-ownership N3 pass explicit signed-in links for U (verified) and W (unverified) both succeeded.
implicit-account-linking-ownership N4 pass test: same pin — "binds the platform exception to the OAuth sign-in method, not the provider id alone" passed (SSO under the same id gets no exception)
implicit-account-linking-ownership N5 pass the fixture answered email_verified:true in both id_token and userinfo, and the refusal text names the LOCAL row's unverified state, so the run measured this gate, not the provider-side one.
implicit-account-linking-ownership N6 pass every implicit sign-in used a newly created empty cookie jar (anon1..anon5), and get-session was read on it before/after.

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out (checklist-test §5) for this record. No clause scored fail, so no four-way disposition is owed.

    The defects below were found during the run and extracted. Each was confirmed on two boots or loads and passed the automation self-check. None is an authentication or authorization hole.

    Extracted: #23944 · identity-auth.api-key-ui-lifecycle · acceptance 2
    Extracted: #23945 · identity-auth.invitation-scope-gates · acceptance 1
    Extracted: #23946 · identity-auth.email-verification-loop · negative 1
    Extracted: objectstack-ai/objectui#12682 · identity-auth.sso-enforced-first-paint · acceptance 1
    Parked-on: #23878 · identity-auth.auth-method-matrix A0/A4 — deviceAuthorization and @better-auth/sso on-sides not bootable on os dev
    Parked-on: #23878 · identity-auth.self-service-profile-password A5/N3 — no provisionable passwordless (SSO-only) user on stock showcase
    Parked-on: #23878 · identity-auth.workspace-org-switch A5 — UI-error half has no UI gesture; server half proven
    Parked-on: #23878 · identity-auth.admin-lifecycle-operations N2 — moot by ruling #9969
    Parked-on: #23878 · identity-auth.teams-bu-membership N2 — intermediate-depth scope owned by access-security.scope-depth-asymmetry
    
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out (checklist-test §5) for this record. No clause scored fail, so no four-way disposition is owed.

    The defects below were found during the run and extracted. Each was confirmed on two boots or loads and passed the automation self-check. None is an authentication or authorization hole.

    Extracted: #23944 · identity-auth.api-key-ui-lifecycle · acceptance 2
    Extracted: #23945 · identity-auth.invitation-scope-gates · acceptance 1
    Extracted: #23946 · identity-auth.email-verification-loop · negative 1
    Extracted: objectstack-ai/objectui#12682 · identity-auth.sso-enforced-first-paint · acceptance 1
    Parked-on: #23878 · identity-auth.auth-method-matrix A0/A4 — deviceAuthorization and @better-auth/sso on-sides not bootable on os dev
    Parked-on: #23878 · identity-auth.self-service-profile-password A5/N3 — no provisionable passwordless (SSO-only) user on stock showcase
    Parked-on: #23878 · identity-auth.workspace-org-switch A5 — UI-error half has no UI gesture; server half proven
    Parked-on: #23878 · identity-auth.admin-lifecycle-operations N2 — moot by ruling #9969
    Parked-on: #23878 · identity-auth.teams-bu-membership N2 — intermediate-depth scope owned by access-security.scope-depth-asymmetry
    
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Closing at 2026-10-16T15:22Z: this record's §5 close-out is posted — comment 6216600094 (repeated verbatim in 6216608645) — with 18 pass · 5 partial · 0 fail of 23. Each partial has its Parked-on: #23878 line there, and the four extractions are #23944, #23945, #23946 and objectstack-ai/objectui#12682. The slice's three unfiled objectui observations were filed on the maintainer's pointer: objectstack-ai/objectui#12697 (import wizard update mode), objectstack-ai/objectui#12698 (stale organization header), #23957 (invite role options).

    Closed by the domain:devx seat 1 PM (seat post #6023) · os-sales · session_014eFvbvGK4FjXvscggDNTD4, as part of the paced close-out of the #23878 wave.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions