Skip to content

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

Open

Description

@objectstack-fleet

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

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-user 200, and the row reads banned:true with ban_reason. The banned user's sign-in is refused with 403 BANNED_USER.
    • Live: unban-user 200, the row reads banned: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-password 200 (revoked.sessions:1). The new password signs in 200, and the old one gets 401 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 a sys_account row exists with provider_id:"credential".
  • A3 pass (ruled, impersonate_user and set_user_role still 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-role gets 403 YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE, and sys_user.role reads user before and after.
  • A4 pass (ruled).
    • Pin (HTTP): status and code.
    • Live: the target's protected read answers 200. revoke-user-sessions gets 403 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-user gets 403 YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS. Afterwards the user row reads 200, the note's owner_id is 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.
  • A6 pass.
    • Pin (HTTP): C6 shows sys_session.impersonated_by = admin.
    • Live: impersonate-user 200. get-session shows the target with impersonatedBy = admin, and the old admin bearer gets 401.
    • Live: stop-impersonating 200. get-session returns to the admin with impersonatedBy:null, and the impersonated token gets 401.
    • The stop half is live only.
  • 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.ts 3/3, covering the sys_audit_log shapes for create-user, set-user-password and import-users.
  • A9 pass.
    • Pin (HTTP): admin-route-nonadmin-refusal 7/7 over the 31 routes derived from the stack, plus the forged-request cases in admin-credential-lifecycle.
    • Live, as a plain member (positions org_member, everyone):
      • ban-user, create-user and remove-user: 403 PERMISSION_DENIED
      • list-users: 403 YOU_ARE_NOT_ALLOWED_TO_LIST_USERS
      • set-role: 403 YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE
      • revoke-user-sessions: 403 YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS
    • State unchanged: roster 9 → 9, no forged row, and the victim still signs in.
  • 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 while generatePassword keeps 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 real isolated stack. That covers the posture guard, J1 (joins the admin's current organization as member, even when the body names another) and J2 (no current organization gives 400 NO_ACTIVE_ORGANIZATION and no sys_user row).
  • packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts: 76/76 passed.
  • On stock single, create-user binds the Default Organization: organizationId is the default org and a sys_member row with role: member exists.
    • A bogus body organizationId is 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: false because the user.create.after reconciler binds first and the endpoint's own bind reads already-member. That function is byte-identical at 6679644a, so feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626 did not change it. Read the sys_member row, never this flag.
  • 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.
  • 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 at 6679644a), 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/*.json at 17f8217b asserts feat(plugin-auth): multi-org Create User joins the administrator's current organization #25626: zero hits for NO_ACTIVE_ORGANIZATION or create-user-organization-join. The missing clauses are:

    • a walled create-user joins the caller's active organization as member, and a body organizationId chooses nothing;
    • no active organization gives 400 NO_ACTIVE_ORGANIZATION and no row;
    • invite-only still joins.

    The candidate pin is the J1/J2 dogfood above.

  • 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-active an organization first:

    • access-security.no-active-org-session-semantics rev 8: personas[0], fixtures.requires and the knownGaps[0] producer (1) describe an operator create-user under invite-only answering membershipCreated:false with no sys_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-switch rev 5: personas[2].
    • RUNNER.md "Walled personas come from the admin door": the membershipCreated: false sentence.
  • This item's automated.ref overclaims.

    • 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_user carries no positions.

    Each was driven live here.

Handover

None. The item has a verdict.

Activity

  1. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out for this record.

    Extracted: #25824 · identity-auth.admin-lifecycle-operations · observed beside the clauses
    Parked-on: #25807 · coverage gap: no item asserts #25626's walled Create User join or 400 NO_ACTIVE_ORGANIZATION
    Parked-on: #25807 · stale after #25626: access-security.no-active-org-session-semantics, identity-auth.workspace-org-switch, RUNNER.md walled-personas entry
    Parked-on: #25807 · this item's automated.ref overclaims (A0, A4, A6, A9 halves driven live)
    

    There is no fail and no partial to dispose of. The banner naming the administrator by raw id is cosmetic and is not extracted.

    This record is the #25626 addendum to #25817.

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