Repository navigation
QA run · identity-auth.admin-lifecycle-operations (1/1) · 17f8217b · 2026-10-25 · 1 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN #25823
Description
This is the addendum to the 18.2.0 release sweep's manual set (#25817, wave anchor #25807, card T3), for the Version Packages PR #25640. #25626 (multi-org Create User joins the administrator's current organization) merged at 15:24Z, after #25817's subject 6679644a was fixed. This record runs the Create User item on the new main head. Under the manual set's own rule #25626 adds no unpinned item. The only P1 item whose source[] cites admin-user-endpoints.ts is identity-auth.admin-lifecycle-operations, which carries an automated.ref, so it was run here by its pins plus live legs. It was run with the checklist-test skill following docs/qa/platform-checklist/RUNNER.md.
Environment fingerprint
- Framework:
17f8217b7c(objectstackmain, the feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626 merge). The run used a detached worktree withpnpm install --frozen-lockfileandpnpm build --concurrency=2, which completed 76/76 tasks in 8m10s.check-dev-prereqswas green. - Console:
.objectui-sha=b41ed7f68956c8cfda21bc790548361af81b09ac, unchanged from QA run · release-18.2.0-manual-set (29/29) · 6679644a · 2026-10-25 · 25 PASS / 4 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN #25817. The console dist was copied from QA run · release-18.2.0-manual-set (29/29) · 6679644a · 2026-10-25 · 25 PASS / 4 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN #25817's build, and its stamp equals the pin. - Boot:
OS_PORT=41501 setsid pnpm -C WT/examples/app-showcase exec objectstack dev --ui --seed-admin -p 41501 -d file:SCRATCH/item.db. This was a stocksingleboot, with no walled boot and no scratch app. - Vitest: one package at a time,
--maxWorkers=1. - Timings: environment prep about 11 min; testing about 7 min (pins about 3, live about 3, probes about 2–3). The worktree was removed afterwards, and the shared tree was untouched.
Scope and verdict
| item | priority | surface | rev | verdict |
|---|---|---|---|---|
| identity-auth.admin-lifecycle-operations | P1 | mixed | 9 | PASS |
Derived: 1 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN.
Pin layers. The four dogfood pins run at the HTTP layer: @objectstack/verify bootStack serves the real route table with a real ObjectQL engine (sqlite-wasm). They gave 22/22. The [#3023] case in packages/plugins/plugin-security/src/security-plugin.test.ts is a plugin unit pin, 1/1. Live legs ran on the boot above.
Per-clause evidence
Labels: A0, … are acceptance[0], …; N0, … are negative[0], … (zero-based).
- A0 pass.
- Pin (HTTP): C0 shows the ban persisting and reversing, re-read from storage.
- Live:
ban-user200, and the row readsbanned:truewithban_reason. The banned user's sign-in is refused with403 BANNED_USER. - Live:
unban-user200, the row readsbanned:false, ban_reason:null, and the same credentials sign in 200. - The sign-in halves are live only, because the pin does not drive them.
- A1 pass.
- Pin (HTTP): the rotation case passes.
- Live:
set-user-password200 (revoked.sessions:1). The new password signs in 200, and the old one gets401 INVALID_EMAIL_OR_PASSWORD.
- A2 pass.
- Pin (HTTP): the explicit password wins over
generatePassword:true, checked against the generate-only contrast. - Live: create-user 200 with no password field in
data. The explicit password signs in 200, and asys_accountrow exists withprovider_id:"credential".
- Pin (HTTP): the explicit password wins over
- A3 pass (ruled,
impersonate_userandset_user_rolestill 403 every platform admin — neither route is safely raw-mountable, and each blocks for a different reason #9968/finding: seven better-auth/admin/routes refuse platform admins and have no ObjectStack consumer — decide whether they are capability or just vendor surface #9969).- Pin (HTTP): the by-design refusal of the eight consumer-less routes, with its no-effect control.
- Live:
set-rolegets403 YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE, andsys_user.rolereadsuserbefore and after.
- A4 pass (ruled).
- Pin (HTTP): status and code.
- Live: the target's protected read answers 200.
revoke-user-sessionsgets403 YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS, and the same protected read still answers 200.
- A5 pass (ruled).
- Live: a throwaway user owns a
showcase_private_note.remove-usergets403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS. Afterwards the user row reads 200, the note'sowner_idis unchanged, and the user still signs in. - Exemption half: scored from the plugin-unit
[#3023]pin, as the item itself prescribes, because no public door is left for it.
- Live: a throwaway user owns a
- A6 pass.
- Pin (HTTP): C6 shows
sys_session.impersonated_by= admin. - Live:
impersonate-user200. get-session shows the target withimpersonatedBy= admin, and the old admin bearer gets 401. - Live:
stop-impersonating200. get-session returns to the admin withimpersonatedBy:null, and the impersonated token gets 401. - The stop half is live only.
- Pin (HTTP): C6 shows
- A7 pass (live, screenshot).
- Path: Setup → Users → row menu → Impersonate User → confirm → Continue.
- A full-width banner reads "You are impersonating QA E target — every action is recorded as them…", with a "Stop impersonating" button. The in-page get-session agrees.
- Stop clears the banner and restores the admin session.
- A8 pass. Pin (HTTP):
admin-identity-audit-trail.dogfood.test.ts3/3, covering thesys_audit_logshapes for create-user, set-user-password and import-users. - A9 pass.
- Pin (HTTP):
admin-route-nonadmin-refusal7/7 over the 31 routes derived from the stack, plus the forged-request cases inadmin-credential-lifecycle. - Live, as a plain member (positions
org_member,everyone):ban-user,create-userandremove-user: 403PERMISSION_DENIEDlist-users: 403YOU_ARE_NOT_ALLOWED_TO_LIST_USERSset-role: 403YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLErevoke-user-sessions: 403YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS
- State unchanged: roster 9 → 9, no forged row, and the victim still signs in.
- Pin (HTTP):
- N0 pass, N1 pass (the banned sign-in is refused server-side), N2 pass (moot while finding: seven better-auth
/admin/routes refuse platform admins and have no ObjectStack consumer — decide whether they are capability or just vendor surface #9969 stands), N3 pass (the 属主守卫与级联 set_null 冲突:非特权删除 sys_user 时 owner_id 级联置空被 #3004 守卫拦截(级联中途失败) #3023 half from the plugin-unit pin; the auth: create-user 400s when a password is typed whilegeneratePasswordkeeps its default (true) #3031 half from the HTTP pin plus the live sign-in).
#25626 beside the clauses (rule 8 probe; scores nothing)
packages/qa/dogfood/test/create-user-organization-join.dogfood.test.ts: 3/3 passed on a realisolatedstack. That covers the posture guard, J1 (joins the admin's current organization asmember, even when the body names another) and J2 (no current organization gives400 NO_ACTIVE_ORGANIZATIONand nosys_userrow).packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts: 76/76 passed.- On stock
single, create-user binds the Default Organization:organizationIdis thedefaultorg and asys_memberrow withrole: memberexists.- A bogus body
organizationIdis ignored. - With the admin's active organization set to null, create-user still answers 200 and binds the Default Organization. The 400 is walled-only, as designed.
- The response reads
membershipCreated: falsebecause theuser.create.afterreconciler binds first and the endpoint's own bind readsalready-member. That function is byte-identical at6679644a, so feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626 did not change it. Read thesys_memberrow, never this flag.
- A bogus body
- Other boundary values:
- create-user: an empty email or empty body → 400; a duplicate →
400 USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL. - ban, unban and set-password with
""→ 400; with an unknown id → 404. - impersonate with an unknown id →
404 USER_NOT_FOUND.
- create-user: an empty email or empty body → 400; a duplicate →
- A ban kills the user's live session: a protected read goes from 200 to 401.
- Found: admin create-user accepts an explicit password shorter than
minPasswordLength. It is extracted at close-out. It predates feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626 (the code is the same at6679644a), and it is a password-policy gap reachable only by an administrator, not an authentication or authorization hole. - Not filed: the impersonation banner names the administrator by raw user id rather than a name or email (objectui, cosmetic).
Checklist-accuracy findings (wave anchor #25807, not extracted)
-
Coverage gap. No item in
areas/*.jsonat17f8217basserts feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626: zero hits forNO_ACTIVE_ORGANIZATIONorcreate-user-organization-join. The missing clauses are:- a walled create-user joins the caller's active organization as
member, and a bodyorganizationIdchooses nothing; - no active organization gives
400 NO_ACTIVE_ORGANIZATIONand no row; invite-onlystill joins.
The candidate pin is the J1/J2 dogfood above.
- a walled create-user joins the caller's active organization as
-
Stale after feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626. These were read from the PR, the code and the J1/J2 pin, not driven on a walled boot. Each recipe now needs the operator to create and
set-activean organization first:access-security.no-active-org-session-semanticsrev 8:personas[0],fixtures.requiresand the knownGaps[0] producer (1) describe an operator create-user underinvite-onlyansweringmembershipCreated:falsewith nosys_member. Under a wall that producer no longer exists: the user joins the admin's active organization, or the call is refused 400.identity-auth.workspace-org-switchrev 5:personas[2].- RUNNER.md "Walled personas come from the admin door": the
membershipCreated: falsesentence.
-
This item's
automated.refoverclaims.- The A0 pin does not drive the banned or post-unban sign-in.
- The A4 pin has no target session, so the "protected request still 2xx" half is unpinned.
- The A6 pin does not drive stop-impersonating.
- The A9 pin reads state back only for create-user and set-user-password.
- Step 9's "positions[] unchanged" cannot be read from the data API, because
sys_usercarries nopositions.
Each was driven live here.
Handover
None. The item has a verdict.
Activity
objectstack-fleet commentedon Oct 25, 2026
Close-out for this record.
There is no
failand nopartialto dispose of. The banner naming the administrator by raw id is cosmetic and is not extracted.This record is the #25626 addendum to #25817.