Skip to content

QA run · identity-auth (20/28) · 5b14b7f3 · 2026-10-19 · 18 PASS / 2 PARTIAL / 0 FAIL / 0 BLOCKED / 8 NOT-RUN #24538

Closed

Description

@objectstack-fleet

Environment fingerprint

  • framework: 5b14b7f3ad8e2e0786d228772f473d07b36b2477 (objectstack-ai/objectstack main, detached, not pulled)
  • console: .objectui-sha = 4016370af3c9c9dafec2bc5299afd8e8079701b8, built with pnpm objectui:build; dist stamp matched the pin
  • app: examples/app-showcase, OS_PORT=P objectstack dev --ui --seed-admin -p P -d file:/tmp/qa18/RUN/data.db — every runner its own port (472xx) and file DB; per-item extra env recorded in the run scratch
  • seed: --seed-admin (admin@objectos.ai) + showcase demo personas; members minted via admin create-user / invite-then-signup
  • date: 2026-10-19 (UTC); runner: checklist-test card of the 18.0 release QA wave (dispatch session session_01CfRDy7SuTfUqaTu2AEH2Hi); items driven by parallel runner sub-agents, each on its own port + DB + browser context (Playwright, chromium via /opt/pw-browsers/chromium)
  • plan: maintainer-approved option B — Tier 1 (pinned, vitest batch) + Tier 2 (unpinned P0/P1, driven live); Tier 3 (unpinned P2) deferred

Scope

Selector area:identity-auth → 28 runnable items (node scripts/checklist-select.mjs area:identity-auth --json); planned items: none; blocked items excluded by default.

item priority tier revision item verdict port
identity-auth.sso-enforced-first-paint P2 3 3 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.phone-signin-surfaces P2 3 3 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.auth-method-matrix P1 1 8 partial -p 47241
identity-auth.invitation-scope-gates P1 1 4 pass -p 47241
identity-auth.admin-lifecycle-operations P1 1 8 pass -p 47241
identity-auth.session-list-revoke P1 2 3 pass -p 47271
identity-auth.api-key-ui-lifecycle P1 2 3 pass -p 47271
identity-auth.self-service-profile-password P1 2 1 pass -p 47271
identity-auth.org-membership-team-management P1 2 4 pass -p 47231
identity-auth.teams-bu-membership P2 3 2 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.self-service-password-reset P1 2 2 pass -p 47211
identity-auth.oauth-app-consent-loop P2 3 3 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.linked-accounts-social P2 3 5 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.identity-import-wizard P2 3 2 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.two-factor-enrollment-reveal P1 2 3 pass -p 47211
identity-auth.two-factor-verify-to-activate P1 2 2 pass -p 47211
identity-auth.two-factor-backup-codes P1 2 3 pass -p 47211
identity-auth.two-factor-disable-lifecycle P1 2 1 pass -p 47211
identity-auth.first-run-owner-bootstrap P1 2 2 pass -p 47232
identity-auth.self-signup-gate P1 2 4 pass -p 47231
identity-auth.email-verification-loop P2 3 4 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.workspace-org-switch P2 3 4 not-run Tier 3 deferred by the 18.0 release-sweep plan
identity-auth.implicit-account-linking-ownership P1 1 3 partial -p 47241
identity-auth.auth-gate-held-session-raw-host-families P1 1 1 pass -p 47241
identity-auth.api-key-expiry-lifecycle P1 1 1 pass -p 47241
identity-auth.cookie-session-renewal-in-process P2 1 1 pass -p 47241
identity-auth.default-organization-boot-invariant P0 1 1 pass -p 47241
identity-auth.import-invitation-and-sso-domain-answers P2 1 1 pass -p 47241

Per-clause verdicts

Tier 1 clauses carry [pin] when the pinned test asserts them (evidence = the vitest batch below), [live] when driven against the boot.

item clause verdict oracle evidence (text)
identity-auth.auth-method-matrix 0 blocked(environment: device-authorization has no switch on os dev, @better-auth/sso half … [live] proven variants only — OFF boot 47241: socialProviders [], phoneNumber false, phoneNumberOtp false, twoFactor false, sso false, deviceAuthorization false, magicLink/passkeys keys ABSENT; ON boot 47242 (OS_AUTH_PHONE_NUMBER_ENABLED, OS_AUTH_TWO_FACTOR, GOOGLE_…
identity-auth.auth-method-matrix 1 pass [live] sign-in 200 + better-auth.session_token cookie; get-session 200 mx.ep@example.com; sign-out 200 {success:true} clears session_token/session_data; replayed cookie get-session 401 UNAUTHENTICATED user null, protected data read 401 (sign-up/email w/o invite 403 …
identity-auth.auth-method-matrix 2 pass [live] ON boot: POST /sign-in/phone-number +8613800138000/showcase123 → 200; get-session 200 phone.demo@example.com id usr_showcase_phone_demo; wrong password 401 INVALID_PHONE_NUMBER_OR_PASSWORD
identity-auth.auth-method-matrix 3 pass [live] NODE_ENV=production boot + OS_AUTH_PHONE_NUMBER_ENABLED: send-otp 400 application/json code SMS_SERVICE_REQUIRED (x2, ~100-190ms, named message); /auth/config phoneNumberOtp false; dev-boot control send-otp 200 'code sent'
identity-auth.auth-method-matrix 4 blocked(dependency: magic-link/passkeys have no login UI — objectui#4179 closed not_plann… [live] screenshots of proven variants (console 4016370): OFF login = Email + Password + Forgot password only; ON login = 'Continue with Google', 'Email or phone number' field, 'Sign in with verification code' — matching each boot's /auth/config
identity-auth.auth-method-matrix 5 pass [live] stock boot, anonymous forged: /sign-in/phone-number 404, /phone-number/send-otp 404, /two-factor/enable 404, /two-factor/verify-totp 404, /device/code 404, /magic-link/send 404, /sign-in/magic-link 404, /sign-in/social google 404 PROVIDER_NOT_FOUND, /sign-in/…
identity-auth.auth-method-matrix 6 pass [live] ON boot (OS_AUTH_TWO_FACTOR=true): enable 200 + verify-totp activate 200; password sign-in 200 {twoFactorRedirect:true,twoFactorMethods:[totp]} with better-auth.two_factor cookie; get-session before challenge 401 UNAUTHENTICATED user null; protected read 401;…
identity-auth.auth-method-matrix 7 pass [pin] pin: packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts › authorize: skip_consent client gets an immediate code redirect / token exchange succeeds and persists the access-token row / userinfo answers with the signed-in subject (3 tests) ✓ (…
identity-auth.auth-method-matrix 8 pass [live] GET /.well-known/openid-configuration 200 JSON and /.well-known/oauth-authorization-server 200 JSON, issuer http://localhost:47241/api/v1/auth, endpoints /api/v1/auth/oauth2/{authorize,token,userinfo} mounted (authorize 200, token 415 on JSON body = mounted, …
identity-auth.auth-method-matrix 9 pass [live] change-email authed 200 {status:true} (address not flipped: still mx.ep@example.com), anon 401 UNAUTHORIZED; delete-user authed 404 empty body, anon 401 UNAUTHORIZED, GET /delete-user/callback 404 NOT_FOUND, account survives (get-session 200)
identity-auth.invitation-scope-gates 0 pass [live+pin] pin: delegated-admin-invite.dogfood.test.ts › 'a delegated_admin CAN issue an invitation' ✓ (log t1log-identity-auth-packages_qa_dogfood.log; asserts 200 + 1 row role member, not status); live: delegate (set via owner POST /organization/update-member-role → d…
identity-auth.invitation-scope-gates 1 pass [pin] pin: delegated-admin-invite.dogfood.test.ts › 'the same delegate issuing admin is REFUSED, with no invitation row left behind' ✓ (log t1log-identity-auth-packages_qa_dogfood.log); live corroboration 403 INVITE_ROLE_ABOVE_ISSUER
identity-auth.invitation-scope-gates 2 pass [pin] pin: same test asserts 0 rows for the admin invitee ✓; live: 0 rows for inv.admin.target, sys_invitation total 1→1
identity-auth.invitation-scope-gates 3 pass [pin] pin: delegated-admin-invite.dogfood.test.ts › 'a plain member still cannot invite at all — the ROLE is what opened the endpoint' ✓; live: 403 YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION, 0 rows
identity-auth.invitation-scope-gates 4 pass [live] screenshots /_console/apps/com.objectstack.account/sys_invitation (form sign-in): delegated_admin header shows 'Invite User' button; plain member same page has NO Invite User button (admin control: present); server side proven by clause 3
identity-auth.invitation-scope-gates 5 pass [live] statuses observed pending/accepted/rejected/canceled ⊂ InvitationStatus enum; delegate cancel 403 YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION row stays pending; owner resend 200 same row pending, expires_at moved forward; owner cancel 200 → canceled; invite…
identity-auth.invitation-scope-gates 6 pass [live] delegate 200 {isTenantAdmin:false,scopes:[],placeableBusinessUnitIds:[],assignablePositions:[]} (DelegableScope shape, no role field — under-reports, expected); plain member same empty shape; delegate ?userId=‹admin› ignored (still own empty scope, while admi…
identity-auth.admin-lifecycle-operations 0 pass [live+pin] pin: admin-platform-admin-standing.dogfood.test.ts › 'C0 — ban persists and reverses for a platform admin, through the ObjectStack mounts' ✓ (storage read); live: ban-user 200 → sys_user banned=true ban_reason=qa-ban-reason; banned sign-in 403 BANNED_USER (x2…
identity-auth.admin-lifecycle-operations 1 pass [pin] pin: packages/qa/dogfood/test/admin-credential-lifecycle.dogfood.test.ts › set-user-password rotates the credential: the new password signs in, the old is refused (old → 401 INVALID_EMAIL_OR_PASSWORD) ✓ (log t1log-identity-auth-packages_qa_dogfood.log, file ✓…
identity-auth.admin-lifecycle-operations 2 pass [pin] pin: packages/qa/dogfood/test/admin-credential-lifecycle.dogfood.test.ts › create-user with BOTH an explicit password and generatePassword:true applies the EXPLICIT one (+ contrast 'a generated-password create is the contrast') ✓ (log t1log-identity-auth-pack…
identity-auth.admin-lifecycle-operations 3 pass [pin+live] pin: admin-platform-admin-standing.dogfood.test.ts › 'the eight consumer-less admin routes refuse the platform admin BY DESIGN, each with its ruled code' ✓ (incl. no-effect role control); live: 403 YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE, sys_user.role stays…
identity-auth.admin-lifecycle-operations 4 pass [pin+live] pin: same by-design test ✓ (status+code); live: target protected GET /data/showcase_task 200 → revoke-user-sessions 403 YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS → same protected read 200
identity-auth.admin-lifecycle-operations 5 pass [pin+live] live: owner lc.owner creates showcase_task (201, owner_id=owner); remove-user 403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS; sys_user 200 still, owned row 200 owner_id unchanged; pin (run by me): plugin-security security-plugin.test.ts › '[#3023] an engine referent…
identity-auth.admin-lifecycle-operations 6 pass [pin+live] pin: admin-platform-admin-standing › 'C6 — the platform admin may impersonate, and the session records the attribution' ✓; live (cookie lane): impersonate 200; get-session = lc.target with session.impersonatedBy=admin id; sys_session.impersonated_by=admin; st…
identity-auth.admin-lifecycle-operations 7 pass [live] screenshot after Setup → Users row ⋯ → Impersonate User → Continue: red banner 'You are impersonating lc.target — every action is recorded as them. Started by administrator ‹admin id›.' + 'Stop impersonating' button; in-page get-session = lc.target@example.com
identity-auth.admin-lifecycle-operations 8 pass [pin] pin: packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts › POST /admin/create-user … / POST /admin/set-user-password … / POST /admin/import-users … (3 tests) ✓ (log t1log-identity-auth-packages_qa_dogfood.log, file ✓, RC 0, 0 skipped)
identity-auth.admin-lifecycle-operations 9 pass [pin+live] pin: admin-route-nonadmin-refusal.dogfood.test.ts › 'no anonymous caller and no plain member ever gets a 2xx from an /admin/ route' ✓ (+4 per-bucket tests, 31 derived routes); live member: ban-user 403 PERMISSION_DENIED, list-users 403 YOU_ARE_NOT_ALLOWED_TO_…
identity-auth.session-list-revoke 0 pass A (2 form sign-ins, distinct tokens): GET /auth/list-sessions 200 -› 2 rows both user_id=A (tokens match device-1/device-2); GET /data/sys_session as A 200 -› same 2 ids, revoked_at null; B's own read 200 -› 1 row id oBS8he... (user B); B's id absent from bot…
identity-auth.session-list-revoke 1 pass screenshot /_console/apps/com.objectstack.account/sys_session/view/mine: 'My Sessions' tab, 2 rows (IP Address '—', Active Organization, Created At, Expires At), footer '2 records' = API count 2; ip_address empty for local headless clients
identity-auth.session-list-revoke 2 pass POST /auth/revoke-session {token:‹device-2, len 32›} 200 {status:true}; device-2 GET /data/sys_session -› 401, get-session -› 401 UNAUTHENTICATED; device-1 GET /data/sys_session -› 200, get-session still usera
identity-auth.session-list-revoke 3 pass device-3 signed in (200 before); list-sessions 2 -› POST /auth/revoke-other-sessions from device-1 200 -› list-sessions 1 row (token == device-1); device-3 -› 401; device-1 -› 200; user B unaffected 200
identity-auth.session-list-revoke 4 pass screenshot Setup Sessions all_sessions: 5 live rows with User column (Dev Admin x3, QA userb, QA usera), no tombstones; Revoked tab: 2 'QA usera' rows with Revoked At + Revoke Reason 'user_revoked'; admin GET /data/sys_session: both A tombstones revoked_at se…
identity-auth.session-list-revoke 5 pass admin PATCH /data/sys_session/{id} -› 405 OBJECT_API_METHOD_NOT_ALLOWED allowed [get,list,aggregate,export]; admin DELETE -› 405 same; member A PATCH -› 405 same
identity-auth.api-key-ui-lifecycle 0 pass UI Generate key -› POST /api/v1/keys 201 data{id,name,prefix,key(len 47, starts with prefix osk_UMSDCWO2),...}; GET /data/sys_api_key/{id} 200 fields id..prefix,revoked (no key), raw absent; owner list raw absent; mine-view page HTML lacks raw, has prefix
identity-auth.api-key-ui-lifecycle 1 pass GET /data/sys_session with x-api-key (no cookie/bearer) 200: 2 rows, all user_id=owner, id set == owner's session read (2); admin session read of same route 11 rows across 4 users
identity-auth.api-key-ui-lifecycle 2 pass owner PATCH /data/sys_api_key/{id} {revoked:true} 200 record.revoked=true; next x-api-key call 403 body {error:{code:API_KEY_REVOKED,...}} no records; control: unknown key -› 401 UNAUTHENTICATED
identity-auth.api-key-ui-lifecycle 3 pass PATCH {revoked:false} 200 revoked=false; x-api-key GET /data/sys_session 200, 2 rows
identity-auth.api-key-ui-lifecycle 4 pass 2nd member keyother: list 200 [] excludes owner key, GET by id 404; keyother minted own key; owner list excludes it; admin GET /data/sys_api_key lists both ids with distinct user_ids; Setup API Keys all_keys screenshot shows row with Owner column
identity-auth.api-key-ui-lifecycle 5 pass screenshot: dialog 'Your new API key' — 'Copy this now — for your security it will not be shown again.' with key + Copy button; Account API Keys My Keys screenshot: row qa18-key, Prefix osk_UMSDCWO2, no secret
identity-auth.self-service-profile-password 0 pass Save Changes -› POST /auth/update-user 200 {status:true}; get-session name='QA Profile Renamed'; after reload #profile-name value same, screenshot card header shows new name
identity-auth.self-service-profile-password 1 pass form sign-in; setInputFiles -› POST /storage/upload/presigned 200 (fileId 9584434f...), PUT /storage/_local/raw/‹signed› 200, POST /storage/upload/complete 200 (8551 B image/png), POST /auth/update-user 200; get-session image=/api/v1/storage/files/9584434f...…
identity-auth.self-service-profile-password 2 pass profile-avatar-remove-btn -› POST /auth/update-user 200; get-session image null; after reload no ‹img›, screenshot shows 'QP' initials and Upload button
identity-auth.self-service-profile-password 3 pass UI change-password -› POST /auth/change-password 200; sign-out 200; sign-in new pw 200; sign-in old pw 401 INVALID_EMAIL_OR_PASSWORD
identity-auth.self-service-profile-password 4 pass UI with wrong current -› POST /auth/change-password 400 INVALID_PASSWORD, card shows 'Invalid password'; old pw sign-in still 200, new pw 401
identity-auth.self-service-profile-password 5 pass credential-less user via admin import-users passwordPolicy none; email+pw sign-in before 401; session via admin impersonate-user; Profile card 'Set Local Password', no #current-password (only new/confirm), list-accounts []; POST /auth/set-initial-password 200…
identity-auth.org-membership-team-management 0 pass admin form sign-in; Setup nav 'Organization' href=/_console/apps/com.objectstack.setup/sys_organization/record/org_mvf97wck43euyjub = session.activeOrganizationId and get-full-organization id; screenshot: record page 'QA18 Renamed Org' with tabs Members 6 / I…
identity-auth.org-membership-team-management 1 pass m1 member: invite-member 403 YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION; update-member-role admin 200, list-members shows m1 admin; m1 invite-member 200; demote to member 200 -› m1 invite-member 403 again; delegated_admin on m3 200 (sys_member r…
identity-auth.org-membership-team-management 2 pass update-member-role guest/contributor(position)/showcase_contributor(permission set) -› 400 {code:ROLE_NOT_FOUND}; invite-member guest/contributor -› 400 {message:'ROLE_NOT_FOUND: guest'}; follow-up sys_member 7 rows roles {delegated_admin,member,owner}, m2 st…
identity-auth.org-membership-team-management 3 pass get-full-organization name 'Default Organization' -› POST organization/update {name:'QA18 Renamed Org'} 200 -› get-full-organization name 'QA18 Renamed Org' slug default; org record page header shows the new name (nav item label is the static 'Organization')
identity-auth.org-membership-team-management 4 pass invited1 fresh session: get-full-organization?organizationId=ORG 200, set-active 200; admin remove-member 200; old session get-session 401 UNAUTHENTICATED; fresh sign-in (activeOrg null): get-full-organization 403 and set-active 403 USER_IS_NOT_A_MEMBER_OF_TH…
identity-auth.org-membership-team-management 5 pass create-team 'QA18 Team' 200; add-team-member m1, m2 each 200; re-add m1 returned the same row id (no duplicate); GET /api/v1/data/sys_team_member total 2: (team t6mQ..., m2 userId) and (team t6mQ..., m1 userId)
identity-auth.org-membership-team-management 6 pass m2 (get-session=m2, role member): update-member-role x2 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER, remove-member 403 ..._DELETE_THIS_MEMBER, update 403 ..._UPDATE_THIS_ORGANIZATION, create-team 403 ..._CREATE_TEAMS..., add-team-member 403 YOU_ARE_NOT_ALLO…
identity-auth.self-service-password-reset 0 pass anon POST /auth/request-password-reset 200 {status:true,message:"If this email exists..."}; sys_email row to "QA Reset ‹qa.reset.member@example.com›", subject "Reset your ObjectStack password", status sent, body carries /api/v1/auth/reset-password/‹token len …
identity-auth.self-service-password-reset 1 pass POST /auth/reset-password {token,newPassword} 200 {status:true}; sign-in NEW 200; sign-in OLD 401 INVALID_EMAIL_OR_PASSWORD
identity-auth.self-service-password-reset 2 pass second reset same token 400 INVALID_TOKEN; that password 401, current still 200
identity-auth.self-service-password-reset 3 pass garbage 400 INVALID_TOKEN; tampered fresh token 400 INVALID_TOKEN; fresh token with sys_verification.expires_at forced to 2020 -› 400 INVALID_TOKEN; credential unchanged (current pw 200, attempted pw 401)
identity-auth.self-service-password-reset 4 pass known and unknown email both 200 with byte-identical body; no sys_email row for the unknown address
identity-auth.two-factor-enrollment-reveal 0 pass GET /auth/config features.twoFactor=true; anon POST /two-factor/enable {} -› 400 VALIDATION_ERROR (not 404)
identity-auth.two-factor-enrollment-reveal 1 pass enable 200 keys [method,totpURI,backupCodes]; otpauth://totp label "ObjectStack:‹user email›", issuer=ObjectStack, digits=6, period=30, algorithm absent; secret ‹len 52› base32-decodes to 32 bytes
identity-auth.two-factor-enrollment-reveal 2 pass backupCodes: string[10], all strings, each 11 chars shape xxxxx-xxxxx (values redacted)
identity-auth.two-factor-enrollment-reveal 3 pass meta sys_two_factor.enable_two_factor resultDialog fields totpURI(qrcode)-›string, backupCodes(code-list)-›string[10]; successMessage absent
identity-auth.two-factor-enrollment-reveal 4 pass pre-verify: no pw 400 VALIDATION_ERROR, wrong pw 400 INVALID_PASSWORD, right pw 200 SAME secret, anon 401; post-verify: no pw 400, wrong pw 400, right pw 200 SAME secret. Password-gated re-reveal, no ungated path
identity-auth.two-factor-enrollment-reveal 5 pass meta sys_user.enable_two_factor resultDialog fields [{totpURI,qrcode},{backupCodes,code-list}] acknowledge "I have saved my backup codes", no successMessage; totpURI-›string, backupCodes-›string[10]
identity-auth.two-factor-verify-to-activate 0 pass features.twoFactor=true; enable probe 400 (non-404)
identity-auth.two-factor-verify-to-activate 1 pass post-enable pre-verify sign-in 200 with token, no twoFactorRedirect, only better-auth.session_token cookie (no two_factor cookie); session usable (get-session 200); sys_user.two_factor_enabled=0. TOTP_NOT_ENABLED branch cited from lockout pin, not driven
identity-auth.two-factor-verify-to-activate 2 pass row read immediately after enable: verified=0 (integer) — healthy reading; note column DB default is '1' (pragma dflt_value), so better-auth writes 0 explicitly
identity-auth.two-factor-verify-to-activate 3 pass POST /two-factor/verify-totp (bearer) 200 keys [token,user] + set-auth-token (rotated); row verified=1, sys_user.two_factor_enabled=1, failed_verification_count 0
identity-auth.two-factor-verify-to-activate 4 pass fresh sign-in 200 twoFactorRedirect:true, no token, cookies session_token/session_data/two_factor; get-session with challenge cookies 401 UNAUTHENTICATED; cookie-lane verify-totp 200 with token; that token get-session 200
identity-auth.two-factor-backup-codes 0 pass features.twoFactor=true; verify-to-activate walk completed on this boot (row verified=1, challenge twoFactorRedirect:true)
identity-auth.two-factor-backup-codes 1 pass challenge -› POST /two-factor/verify-backup-code via two_factor cookie 200 + token; get-session with it 200 (user matches)
identity-auth.two-factor-backup-codes 2 pass same code at fresh challenge 401 INVALID_BACKUP_CODE; different retained code at next challenge 200 + usable session
identity-auth.two-factor-backup-codes 3 pass generate-backup-codes 200 {status,backupCodes[10]}, 0 overlap with old; old unspent code at fresh challenge 401 INVALID_BACKUP_CODE; new code 200 + usable session
identity-auth.two-factor-backup-codes 4 pass sys_two_factor.backup_codes: 334-char opaque base64/hex-like blob, not JSON-parseable, contains none of the 20 issued plaintext codes (encrypted); secret column 144 chars, not the plaintext secret
identity-auth.two-factor-backup-codes 5 pass meta sys_user.generate_backup_codes resultDialog fields [{backupCodes,code-list}], acknowledge, no successMessage, refreshAfter:false; regenerate response.backupCodes -› string[10]
identity-auth.two-factor-disable-lifecycle 0 pass features.twoFactor=true; BEFORE sign-in twoFactorRedirect:true + two_factor cookie
identity-auth.two-factor-disable-lifecycle 1 pass disable wrong pw 400 INVALID_PASSWORD (no pw 400 VALIDATION_ERROR, anon 401); row still present verified=1, two_factor_enabled=1; fresh sign-in still twoFactorRedirect:true
identity-auth.two-factor-disable-lifecycle 2 pass disable 200 {"status":true}; sys_two_factor find for user -› 0 rows; sys_user.two_factor_enabled=0
identity-auth.two-factor-disable-lifecycle 3 pass sign-in 200 with token, no twoFactorRedirect, only session_token cookie; get-session 200
identity-auth.two-factor-disable-lifecycle 4 pass post-disable verify-backup-code: no cookie 401 INVALID_TWO_FACTOR_COOKIE, with session 400 BACKUP_CODES_NOT_ENABLED; re-enable: new secret differs, 0 code overlap; old-secret TOTP 401 INVALID_CODE (session lane) and at new challenge 401; prev-set and original…
identity-auth.two-factor-disable-lifecycle 5 pass PUT /api/settings/auth mfa_required=true grace=7 (200, same boot); disable 200 (not refused); stamp null before, lazily stamped 12:46:47Z on first request -› meta/app 200 (in grace); stamp aged to 2026-10-01 -› 403 MFA_REQUIRED; while gated /two-factor/enable…
identity-auth.first-run-owner-bootstrap 0 pass anonymous GET /api/v1/auth/bootstrap-status on fresh --no-seed-admin boots: 47233 {hasOwner:false} 200, 47232 {hasOwner:false} 200; after owner creation both answer {hasOwner:true} 200 (47232 read in-page after wizard)
identity-auth.first-run-owner-bootstrap 1 pass /auth/config disableSignUp:true audiencePosture invite_only on both boots; 47233 direct POST sign-up/email 200 -› get-session owner-c@qa18.test (positions platform_admin,org_owner); 47232 wizard POST sign-up/email 200 -› in-page get-session owner-b@qa18.test
identity-auth.first-run-owner-bootstrap 2 pass wizard org field '测试组织': network POST organization/update {organizationId:org_mvf9ev61..., data:{name:'测试组织'}} (no slug sent) 200; organization/list -› exactly 1 org {name:'测试组织', slug:'default'}; no create call observed
identity-auth.first-run-owner-bootstrap 3 pass main-frame document requests GET /_console/ after submit (window marker cleared = new document), landed /_console/home; screenshot: 'Good afternoon, Owner B', Your apps 1 (Showcase), activity 'Owner B Updated Organization 测试组织'; authed /api/v1/meta/app names …
identity-auth.first-run-owner-bootstrap 4 pass door 1 (OS_DISABLE_SIGNUP boots): invited address (owner invite-member 200) -› 400 EMAIL_PASSWORD_SIGN_UP_DISABLED on 47233 (invitee-c@) and 47232 (invitee-b@); uninvited -› 403 SELF_REGISTRATION_CLOSED (audience gate, recorded not scored); sys_user stays 1 r…
identity-auth.first-run-owner-bootstrap 5 pass post-bootstrap 47232: fresh anon context /_console/setup -› /_console/login?redirect=%2Fsetup (screenshot: Welcome back sign-in card, no wizard, #orgName absent); owner /_console/setup -› /_console/apps/com.objectstack.setup/dashboard/system_overview (screens…
identity-auth.self-signup-gate 0 pass stock 47231: data.features.audiencePosture 'invite_only' + emailPassword.disableSignUp false; OS_DISABLE_SIGNUP boots 47232/47233: audiencePosture 'invite_only' + disableSignUp true
identity-auth.self-signup-gate 1 pass POST sign-up/email uninvited1@qa18.test -› 403 {code:SELF_REGISTRATION_CLOSED}; admin sys_user full list (3 rows: auditor.demo, phone.demo, admin) and email filter (0) show no row
identity-auth.self-signup-gate 2 pass invite-member invited1@qa18.test 200 (pending) -› sign-up/email 200 -› sign-in + get-session identifies invited1@qa18.test with activeOrganizationId set (also m1-m3 same path)
identity-auth.self-signup-gate 3 pass 47232 anon /_console/register?redirect=%2Fapps%2Fcom.objectstack.setup -› /_console/login?redirect=%2Fapps%2Fcom.objectstack.setup (screenshot: sign-in card only); login links = Forgot password/Privacy/Terms, no Sign up
identity-auth.self-signup-gate 4 pass invited (owner invite 200) invitee-b@ on 47232 and invitee-c@ on 47233 -› 400 EMAIL_PASSWORD_SIGN_UP_DISABLED; uninvited anon-b@/second-anon@ -› 403 SELF_REGISTRATION_CLOSED (audience gate); sys_user 1 row (owner) on each boot
identity-auth.implicit-account-linking-ownership 0 pass [pin] pin: packages/plugins/plugin-auth/src/implicit-account-linking.test.ts › implicit link on external sign-in, end to end › refuses an unverified local row, writes no link and leaves the row unverified (asserts error=IMPLICIT_LINK_REFUSED, 0 sys_account, email_v…
identity-auth.implicit-account-linking-ownership 1 blocked(fixture: local OIDC IdP runtime fixture (scratch app plugin + applyConfigPatch) n… [pin] pin: packages/plugins/plugin-auth/src/implicit-account-linking.test.ts › 'links a verified local row and signs the caller in' ✓ asserts redirect == callbackURL with no error and 1 sys_account row, but never reads get-session — 'the session is U's' unproven
identity-auth.implicit-account-linking-ownership 2 pass [pin] pin: packages/plugins/plugin-auth/src/implicit-account-linking.test.ts › after an unlink, an implicit sign-in is refused; an explicit link is allowed and lifts the refusal (unlink 200, row gone, refused redirect error=IMPLICIT_LINK_REFUSED, 0 rows) ✓ (log t1l…
identity-auth.implicit-account-linking-ownership 3 pass [pin] pin: packages/plugins/plugin-auth/src/implicit-account-linking.test.ts › after an unlink … explicit link is allowed and lifts the refusal (+ next implicit sign-in no error) AND › allows an explicit link-social for an unverified user, without marking the email…
identity-auth.implicit-account-linking-ownership 4 pass [pin] pin: packages/plugins/plugin-auth/src/implicit-account-linking.test.ts › exempts the platform identity provider from the local-verification precondition / binds the platform exception to the OAuth sign-in method, not the provider id alone / keeps the platform…
identity-auth.auth-gate-held-session-raw-host-families 0 pass [pin] packages/services/service-storage/src/storage-routes.auth-gate.pin.test.ts ✓ (42 tests, log t1log-identity-auth-packages_services_service-storage.log); packages/services/service-settings/src/settings-routes.auth-gate.pin.test.ts ✓ (22 tests, log t1log-identit…
identity-auth.auth-gate-held-session-raw-host-families 1 pass [live] member created by admin create-user (mustChangePassword) signed in, get-session user.authGate=PASSWORD_RESET_REQUIRED; cookie lane HELD/ADMIN/ANON: GET /api/settings/auth 403 PASSWORD_RESET_REQUIRED/200/401; GET /api/v1/datasources 403/200/401; GET /api/v1/sh…
identity-auth.auth-gate-held-session-raw-host-families 2 pass [live] admin create-user (mustChangePassword:true) → first held call error.code PASSWORD_RESET_REQUIRED, message 'You must change your password before continuing.'
identity-auth.auth-gate-held-session-raw-host-families 3 pass [live] member m3 changed password, minted key (201), key read 200; org require_mfa=true (PATCH sys_organization) + scratch-DB mfa_required_at 10d ago → x-api-key GET /data/showcase_task 403 MFA_REQUIRED (after ~70s org-MFA cache TTL); then require_mfa=false, setting…
identity-auth.api-key-expiry-lifecycle 0 pass [pin] packages/rest/src/rest-api-key-lifecycle.pin.test.ts ✓ (4 tests, log t1log-identity-auth-packages_rest.log); packages/runtime/src/api-key-lifecycle.pin.test.ts ✓ (14 tests, log t1log-identity-auth-packages_runtime.log); packages/qa/dogfood/test/api-key-lifecy…
identity-auth.api-key-expiry-lifecycle 1 pass [live] POST /api/v1/keys 201, expires_at 2027-01-17T12:54:25.458Z vs server Date 19 Oct 2026 12:54:25 GMT = 90.0000 days
identity-auth.api-key-expiry-lifecycle 2 pass [live] 400 {error.code API_KEY_EXPIRY_TOO_LONG, latest allowed 2027-10-19}; sys_api_key count 1→1
identity-auth.api-key-expiry-lifecycle 3 pass [live] revoked via revoke_api_key action shape (PATCH /data/sys_api_key/{id} {revoked:true} 200): before 200, after 403 API_KEY_REVOKED; key minted with expires_at now+8s: before lapse 200, after 403 API_KEY_EXPIRED (message names the date)
identity-auth.cookie-session-renewal-in-process 0 pass [pin] packages/runtime/src/in-process-session-renewal.pin.test.ts ✓ (11 tests, log t1log-identity-auth-packages_runtime.log); packages/rest/src/in-process-session-read.pin.test.ts ✓ (3 tests, log t1log-identity-auth-packages_rest.log) — RC 0, 0 skipped
identity-auth.cookie-session-renewal-in-process 1 pass [live] session aged by setting sys_session.expires_at = now+5d (expiresIn 7d, updateAge 1d) in scratch DB → 2026-10-24T12:54:00.429Z; cookie-only GET /data/showcase_task 200 and GET /meta/object/showcase_task 200, each Set-Cookie [] and expires_at unchanged 2026-10-…
identity-auth.cookie-session-renewal-in-process 2 pass [live] cookie GET /auth/get-session 200 → Set-Cookie better-auth.session_token=‹redacted›; Max-Age=604800; HttpOnly; SameSite=Lax; expires_at 2026-10-24T12:54:00Z → 2026-10-26T12:54:01Z
identity-auth.default-organization-boot-invariant 0 pass [pin] packages/plugins/plugin-auth/src/ensure-default-organization.test.ts ✓ (55 tests, log t1log-identity-auth-packages_plugins_plugin-auth.log); packages/runtime/src/identity-objects-default-organization.pin.test.ts ✓ (4 tests incl. 'holds exactly the Default Org…
identity-auth.default-organization-boot-invariant 1 pass [live] sys_organization single row id org_mvf98n1h9g413t70 slug default created_at 12:52:54.840Z; first user admin@objectos.ai (--seed-admin) created_at 12:53:03.508Z (8.7s later); owner sys_member 12:53:03.558Z
identity-auth.default-organization-boot-invariant 2 pass [live] sys_member: admin role owner, m2.deforg (admin create-user) role member, both organization_id org_mvf98n1h9g413t70; get-session session.activeOrganizationId org_mvf98n1h9g413t70 for both (create-user response organizationId same)
identity-auth.default-organization-boot-invariant 3 pass [live] all 5 seeded BUs (Acme Corporation, East Coast, Field Operations, HQ Finance, West Coast) organization_id=org_mvf98n1h9g413t70, none NULL
identity-auth.import-invitation-and-sso-domain-answers 0 pass [pin] packages/plugins/plugin-auth/src/sso-domain-verification-unknown-provider.pin.test.ts ✓ (25 tests), packages/plugins/plugin-auth/src/admin-import-users-invite-link.test.ts ✓ (2 tests, '① through the mounted import, lands on the Console set-password page…'), l…
identity-auth.import-invitation-and-sso-domain-answers 1 pass [live] sys_email (dev transport, status sent): import → subject 'Dev Admin invited you to ObjectStack' (= auth.account_invitation subject '{{inviter.name}} invited you to {{appName}}'), body names inviter (admin@objectos.ai), address, 'Set your password (link expire…
identity-auth.import-invitation-and-sso-domain-answers 2 pass [live] GET mailed /api/v1/auth/reset-password/‹token›?callbackURL=…/_console/reset-password → 302 /_console/reset-password?token=‹24 chars›, no INVALID_TOKEN; POST /reset-password with it 200; invitee signs in with new password 200
identity-auth.import-invitation-and-sso-domain-answers 3 pass [live] boot OS_SSO_ENABLED=true OS_SSO_DOMAIN_VERIFICATION=true (47242): request-domain-verification 404 RESOURCE_NOT_FOUND 'SSO provider "qa-unknown-provider-xyz" was not found.' (verify-domain same); boot with =false: both 400 DOMAIN_VERIFICATION_DISABLED

Failures and reproduction rules

None — 0 FAIL.

Checklist-accuracy findings / fixture gaps

For the wave anchor card (not extracted, not edited here).

Checklist accuracy

  • identity-auth.auth-method-matrix: discovery documents are ALSO answered under the /api/v1/auth prefix (GET /api/v1/auth/.well-known/openid-configuration 200) — harmless, item only requires the root mount
  • identity-auth.invitation-scope-gates: fixtures.requires says 'a multi-org boot'; the stock single-tenancy showcase (multiOrgEnabled:false, features.organization:true) is sufficient — the pin itself boots single
  • identity-auth.invitation-scope-gates: pin's first case asserts row count + role, not status pending; status pending was proven live
  • identity-auth.admin-lifecycle-operations: UX note (not scored): the impersonation banner names the starting administrator by raw user id ('Started by administrator c9g6…'), not by name/email
  • identity-auth.api-key-ui-lifecycle: source[2] says sys_api_key apiMethods ['get','list']; live declaration is ['get','list','update'] (sys-api-key.object.ts:469, userActions.edit + D2 column whitelist) — which is what makes the revoke/restore PATCH answer 200. Citation stale, behavior consistent.
  • identity-auth.org-membership-team-management: step 'nav label follows' the rename: nav_organization's label is the static string 'Organization'; what follows the rename is the record page header (and get-full-organization). Suggest rewording to 'the org record page title follows'
  • identity-auth.self-service-password-reset: acceptance[0].oracle is "log" but the verify/evidence now read the sys_email row (an api read) — oracle label could be "api".
  • identity-auth.two-factor-enrollment-reveal: Clause 0/steps[1] expect the anonymous empty-body probe to answer 400/401; live answers 400 VALIDATION_ERROR (body validation precedes the auth check) — consistent with the clause, just noting the anon probe never reaches 401.
  • identity-auth.two-factor-verify-to-activate: fixtures.requires names an in-process ObjectQL system read; an HTTP-booted run can read the same rows from the -d SQLite file directly — worth naming as an accepted alternative rig.
  • identity-auth.two-factor-backup-codes: fixtures.requires names an in-process ObjectQL system read; an HTTP-booted run can read the same rows from the -d SQLite file directly — worth naming as an accepted alternative rig.
  • identity-auth.two-factor-disable-lifecycle: fixtures.requires names an in-process ObjectQL system read; an HTTP-booted run can read the same rows from the -d SQLite file directly — worth naming as an accepted alternative rig.
  • identity-auth.self-signup-gate: step 1 quotes the by-invitation notice as 'Self-registration is not open on this environment…'; the pinned console renders 'Self-registration is not open on this system. Ask an administrator for an invitation.' (data-testid register-by-invitation present, no form) — wording drift only
  • identity-auth.auth-gate-held-session-raw-host-families: fixtures say pnpm dev -- --fresh; canonical os dev boot with a fresh -d DB used
  • identity-auth.auth-gate-held-session-raw-host-families: MFA hold took ~70s to appear after require_mfa flipped (org-MFA cache TTL) — a runner probing immediately sees 200; poll
  • identity-auth.default-organization-boot-invariant: the default-level boot log prints no Default-Organization line, so step 1's 'read the boot log ordering' has nothing to read on os dev; ordering taken from created_at timestamps

Fixture gaps

  • identity-auth.auth-method-matrix: clause 0/4: generic-OIDC variant needs the scratch-plugin localhost IdP fixture (not provisioned this run); device authorization needs a custom host; @better-auth/sso half needs a registered provider
  • identity-auth.self-service-profile-password: knownGaps says set-initial branch needs an IdP; a passwordless user is provisionable on stock showcase: POST /api/v1/auth/admin/import-users {format:'json',rows:[{email,name}],passwordPolicy:'none'} then POST /api/v1/auth/admin/impersonate-user {userId} for a session (inject session_token cookie + localStorage auth-session-token). Recipe could replace the knownGap.
  • identity-auth.self-service-password-reset: Natural expiry (1h window) not waited out; expiry was forced by back-dating sys_verification.expires_at.
  • identity-auth.two-factor-disable-lifecycle: The elapsed-stamp branch of clause 5 needs an aged sys_user.mfa_required_at; there is no API to set it, so this run back-dated it in SQLite. mfa_grace_period_days=0 would also do it but gates the (unenrolled) admin too.
  • identity-auth.implicit-account-linking-ownership: the live lane needs the local OIDC IdP runtime fixture (QA run · follow-up: 6 previously unrun or published-only items (6/6) · 316be321 + 53021e3a · 2026-10-05 · 4 PASS / 0 PARTIAL / 2 FAIL / 0 BLOCKED / 0 NOT-RUN #21845 recipe: scratch app plugin calling applyConfigPatch({oidcProviders:[…]}) before the first auth request, from a scratch copy of the app); not provisioned in this Tier-1 run — clauses 0/2/3 rest on the pin's end-to-end cases, clause 1's session-identity half is unproven

Handover — items without a verdict

  • identity-auth.sso-enforced-first-paint (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.phone-signin-surfaces (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.teams-bu-membership (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.oauth-app-consent-loop (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.linked-accounts-social (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.identity-import-wizard (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.email-verification-loop (P2) — Tier 3 deferred by the 18.0 release-sweep plan
  • identity-auth.workspace-org-switch (P2) — Tier 3 deferred by the 18.0 release-sweep plan

Tier 1 evidence — vitest batch (RUNNER rule 6, one command per package, run once for the area)

package files result wall
packages/qa/dogfood 7 (7 passed (7)) 34 passed (34), RC 0 207s
packages/plugins/plugin-auth 4 (4 passed (4)) 105 passed (105), RC 0 53s
packages/services/service-storage 1 (1 passed (1)) 42 passed (42), RC 0 21s
packages/services/service-settings 1 (1 passed (1)) 22 passed (22), RC 0 20s
packages/plugins/plugin-sharing 1 (1 passed (1)) 33 passed (33), RC 0 30s
packages/plugins/plugin-webhooks 1 (1 passed (1)) 8 passed (8), RC 0 27s
packages/services/service-datasource 1 (1 passed (1)) 45 passed (45), RC 0 24s
packages/cloud-connection 1 (1 passed (1)) 12 passed (12), RC 0 29s
packages/runtime 5 (5 passed (5)) 43 passed (43), RC 0 45s
packages/verify 1 (1 passed (1)) 28 passed (28), RC 0 64s
packages/rest 3 (3 passed (3)) 12 passed (12), RC 0 22s
packages/core 1 (1 passed (1)) 19 passed (19), RC 0 19s
packages/plugins/plugin-email 1 (1 passed (1)) 28 passed (28), RC 0 14s

All files: 0 failed, 0 skipped, 0 todo (grep of each log).

Also run live by the Tier 1 runner (not in the area map): packages/plugins/plugin-security [#3023] exemption test — 1 passed (supports admin-lifecycle-operations clause 5).

Observations (not scored, no card)

  • invite-member answers ROLE_NOT_FOUND with a message only and no code field, while update-member-role carries the code (org-membership-team-management clause 2 passes on status + no-row read-back). Relevant to ADR-0112's assert-the-code rule; left for the anchor card.
  • After an invited address signs up it is joined to the org, but its sys_invitation row stays pending (no item in this area asserts the accept transition on that path).
  • Impersonation banner names the target by raw user id, not display name (UX note).
  • An authorization-adjacent observation on an admin-only path was recorded in the runner session and reported to the maintainer channel; it grants no capability an admin lacks and is not scored. Detail withheld pending maintainer.

Run notes

  • Authentication / authorization findings: none (0 FAIL in this area).
  • Execution: parallel runner sub-agents (5 for this area), each with its own port, file DB and browser context; browser concurrency kept at 3 or fewer.
  • Env time for the whole card (install 48 s, framework build ~13 min incl. one self-inflicted concurrent-build race, console build ~16 min) is reported in the wave hand-back.

Activity

  1. changed the title [-]QA run · area:identity-auth (20/28) · 5b14b7f3 · 2026-10-19 · 18 PASS / 2 PARTIAL / 0 FAIL / 0 BLOCKED / 8 NOT-RUN[/-] [+]QA run · identity-auth (20/28) · 5b14b7f3 · 2026-10-19 · 18 PASS / 2 PARTIAL / 0 FAIL / 0 BLOCKED / 8 NOT-RUN[/+] on Oct 19, 2026
  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Parked-on: #24579 · this record's checklist-accuracy findings, fixture gaps, partial coverage gaps and not-run handover (wave anchor §2–§4)


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Closure complete (checklist-test §5): every FAIL in this record has a disposition (the Extracted: / Parked-on: lines above), and its partial coverage gaps and not-run handover are parked on #24579.
    Closed by: the 18.0 QA wave dispatch session session_01CfRDy7SuTfUqaTu2AEH2Hi, on the maintainer's instruction there on 2026-10-19, verbatim 「qa-run的卡片处理完的是不是可以关闭」.


    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