Skip to content

fix(plugin-auth)!: admin import-users sizes a generated temporary password to the deployment's length range - #26018

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-brnfvi
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-brnfvi

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread
Fixes #25932

Clause-②: no (narrowing) — the exported IdentityImportDeps gains a required member, getAuthContext(), so a deps object without it no longer type-checks against runAdminImportUsers. No request shape is widened or refused.
Metadata-protocol: none

Why

POST /api/v1/auth/admin/import-users minted every temporary password at 16 characters: generateTemporaryPassword() was called with no length, and the import deps carried no auth context to read the range from. Under a configured minimum above 16 the row got a password below it and the user signed in with it (the card's measurement). This is the import half of what PR #25927 did for create-user.

What changed

  • IdentityImportDeps.getAuthContext() (required): better-auth's $context, the reader create-user already holds. AuthPlugin's import route wires it to AuthManager.getAuthContext(). No second reader of the range exists: the import reads password.config from that context, as create-user does.
  • runAdminImportUsers reads the range once, before any row is written, and sizes each generated temporary password min(max(16, minPasswordLength), maxPasswordLength). If the context cannot be read the request is refused through mapAuthApiError (import-users failed) with nothing created, the same mapping create-user uses for the same read.
  • The sizing has one home. temporaryPasswordLength moved from admin-user-endpoints.ts to the internal module temporary-password-length.ts (not re-exported from the package index), and both doors import it.
  • Nothing else about import changes. With the default range a temporary password is still 16 characters.
  • Every test fixture that builds IdentityImportDeps gains the member (real-manager fixtures wire manager.getAuthContext(), mocks answer better-auth's default 8 to 128 range).

Changeset: .changeset/25932-import-users-temporary-password-length.md (@objectstack/plugin-auth patch, BREAKING banner for a host that calls runAdminImportUsers itself, ADR-0087 not-required (runtime-interface-only packages/plugins/plugin-auth/src/admin-import-users.ts#IdentityImportDeps), before / now / fix).

Verification

Code head 71668cb6 (the changeset commit after it touches no code).

  • Red first (admin-import-users-password-length.test.ts, commit 0c9118a4, run before the fix): real AuthManager over SQLite, the plugin's real mounted route.
    • ① temporary policy, minPasswordLength: 24: expected 'nw33z7aozuB%upS@' to have a length of 24 but got 16.
    • ② auto policy, no email service (a row with no deliverable channel): expected '2#jZWgdP#H2oKgGL' to have a length of 24 but got 16. So auto takes the same path, now measured rather than read from code.
    • ④ maxPasswordLength: 14: the row failed (created: 0), because better-auth refused the 16-character password.
    • ③ default range: 16, green before and after.
  • Green after the fix: ① and ② answer 24-character passwords and sign-in/email answers 200 with each; ④ answers 14 characters and signs in; ③ stays 16.
  • New unit pin in admin-import-users.test.ts: a context read that throws is refused 500 INTERNAL_ERROR with createUser and the engine update never called.
  • Ablation (scripts/ablation-replace.mjs, anchor generateTemporaryPassword(temporaryLength) to generateTemporaryPassword(), 1 hit, blob a7809b32 to e056f1db): ①, ② and ④ red, ③ green; restored, blob equals HEAD and git diff HEAD empty. The tests import src, so no build leg.
  • pnpm --filter @objectstack/plugin-auth exec vitest run: 196 files, 3726 passed, 10 skipped, lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/plugin-auth typecheck (after building the package, which the examples program reads): exit 0, check:test-typecheck OK.
  • eslint --no-inline-config packages/plugins/plugin-auth: exit 0.
  • Derived gates (dispatch-gates --commands at 425cc7db, 73 commands): 72 exit 0, dispatch-gates --ran reconciles 73 derived, 72 run, 1 NOT MEASURED. The one is check:dual-build-cjs-loads, PREREQUISITE NOT MET (exit 3: it reads every package's dist, and this worktree built only the plugin-auth and dogfood closures); CI's Lint & Repo Gates runs it. Three first-pass reds were prerequisites, re-run green: check-plugin-teardown-shape --self-test (shallow clone, fixed by fetching its pinned fixture commit), check:dts-closure (a concurrent closure build was rewriting dist) and check:type-check-debt (lock queue timeout, then exit 0 under the lock).
  • Full dogfood at 425cc7db (title carries !): 295 of 296 files pass, 2522 tests pass. The one red is authz-probe-blind-spot.test.ts on packages/runtime/src/domains/i18n.ts (control handleI18nRequest: expected 3, got 4). This diff does not touch packages/runtime; the file already holds 4 occurrences at the base 05f7758f, since 69fa23d6 (fix(runtime): GET /i18n/translations/:locale carries an ETag and Cache-Control: private, no-cache, and a matching If-None-Match is answered 304 (#25785) #25956), so it is red on main's own dogfood battery and is reported to the seat rather than changed here.

Acceptance notes


Generated by Claude Code

…deployment range (red)

Claude-Session: https://claude.ai/code/session_01A5wzhKHa98KsGQNHM9Z3Tq
Co-authored-by: Claude <noreply@anthropic.com>
…loyment's length range

The identity import minted every temporary password at 16 characters, below a
configured minPasswordLength above 16, and the user signed in with it.
IdentityImportDeps now carries getAuthContext (the reader create-user holds);
the batch reads password.config once before any write and sizes each
generated password min(max(16, min), max). The sizing moves to an internal
module both doors import.

Claude-Session: https://claude.ai/code/session_01A5wzhKHa98KsGQNHM9Z3Tq
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a3e5bfa69348cd8dd7067ac3c734f753875d4bc4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3e74e51a240827a52ac05019472e29dde562f871 — the merge of head 425cc7db21b4e5ac643f0ae4bf7994e900b82c4b into base a3e5bfa69348cd8dd7067ac3c734f753875d4bc4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3e74e51a240827a52ac05019472e29dde562f871 && git checkout 3e74e51a240827a52ac05019472e29dde562f871
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a3e5bfa69348cd8dd7067ac3c734f753875d4bc4 425cc7db21b4e5ac643f0ae4bf7994e900b82c4b && git checkout -B drift-repro a3e5bfa69348cd8dd7067ac3c734f753875d4bc4 && git merge --no-ff 425cc7db21b4e5ac643f0ae4bf7994e900b82c4b

node scripts/docs-audit/affected-docs.mjs --json a3e5bfa69348cd8dd7067ac3c734f753875d4bc4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 425cc7db21b4e5ac643f0ae4bf7994e900b82c4b
Local-runs: none
Scope: packages/plugins/plugin-auth/src/admin-import-users.ts, packages/plugins/plugin-auth/src/admin-user-endpoints.ts, packages/plugins/plugin-auth/src/auth-plugin.ts, packages/plugins/plugin-auth/src/temporary-password-length.ts, packages/plugins/plugin-auth/src/admin-import-users-password-length.test.ts, packages/plugins/plugin-auth/src/admin-import-users.test.ts, packages/plugins/plugin-auth/src/admin-import-users-manager-pass.test.ts, packages/plugins/plugin-auth/src/admin-import-users-invite-link.test.ts, packages/plugins/plugin-auth/src/admin-import-users-invite-delivery-failure.test.ts, packages/plugins/plugin-auth/src/admin-import-users-account-invitation.test.ts, packages/plugins/plugin-auth/src/account-invitation-marker.webcontainer.test.ts, packages/plugins/plugin-auth/src/durability-swallow-repair.test.ts + packages/plugins/plugin-auth/src/index.ts, packages/plugins/plugin-auth/package.json, packages/plugins/plugin-auth/src/auth-manager.ts (getAuthContext), packages/plugins/plugin-auth/src/auth-manager-auth-instance.ts (the range's writer into better-auth), packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts, .changeset/25932-import-users-temporary-password-length.md (level and declaration lines only, under ②), scripts/check-changeset-no-major.mjs and scripts/check-adr-0087-registration.mjs (the gates the changeset answers to), AGENTS.md §3 (the convention)

Read against main at the PR's base 05f7758f (PR #25927 / 1d2a246c, create-user's sizing, is already in it) with the net diff overlaid; the card's body and all three comments (triage 6408548839, claim 6411247608, dev report 6412451931); and the 49 check-runs on the head.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged:

  1. IdentityImportDeps gains a REQUIRED getAuthContext() — a narrowing, and correctly declared as one. The interface is on the package entry: packages/plugins/plugin-auth/src/index.ts:61 is export * from './admin-import-users.js', and package.json exports["."] serves dist/index.*, so IdentityImportDeps and runAdminImportUsers are public. The interface is the input parameter of runAdminImportUsers (contravariant position): adding a required member shrinks the set of deps objects a host may pass. Nothing new is accepted. Compile-time, a TypeScript host without the member no longer type-checks; runtime, a JavaScript host without it has every import refused at the new read (deps.getAuthContext is not a function, caught, mapped) before any row is written. That is the accept-set NARROWING Clause-②: no (narrowing) names, not a widening. The member's type, Promise<{ password: Pick<AuthContextLike['password'], 'config'> }>, is the minimum the sizing needs and is satisfied both by AuthManager.getAuthContext() (Promise<any>, auth-manager.ts:4049) and by the test mocks' { password: { config } } literal. Right. The HTTP request shape of POST /admin/import-users is unchanged, so the PR body's "no request shape is widened or refused" is also right.

  2. Every in-repo constructor of the deps supplies it. At the base, the files naming runAdminImportUsers or IdentityImportDeps anywhere under packages/ outside node_modules/dist are exactly nine: admin-import-users.ts, auth-plugin.ts (the one non-test caller, the route at :3766) and seven test files. All nine are in the PR's file list, and every one of the deps literals in them gains getAuthContext (route and real-manager fixtures wire manager.getAuthContext(); the three mock fixtures answer better-auth's default 8–128 range). No package outside plugin-auth, no example and no app constructs these deps (the only out-of-package mentions are CHANGELOG.md prose). Right; the compiler would have named any miss, and Type Check · workspace / Type Check · consumer gates on the head are the gate verdicts for that (see ③).

  3. Exactly one reader of the length range. Both doors read the range from the same object by the same expression: create-user admin-user-endpoints.ts:724 (await deps.getAuthContext()).password.config, and the import (new) temporaryPasswordLength((await deps.getAuthContext()).password.config). Both getAuthContext deps are wired by AuthPlugin to AuthManager.getAuthContext(), which returns better-auth's own $context. The other minPasswordLength/maxPasswordLength sites in non-test source are the range's WRITERS into better-auth (auth-manager-auth-instance.ts:375-378, auth-plugin.ts:2083-2087 mapping the ADR-0069 settings), and admin-user-endpoints.ts:378-383 consumes the same lengthPolicy value already read at :724 for the explicit-password check. No second reader (no settings read, no config read, no env read) was added. Right, and it satisfies the claim's ⛔ no second reader of the range ruling.

  4. The sizing has one home and its public surface did not move. temporaryPasswordLength was a module-private (non-exported) function in admin-user-endpoints.ts; it is now the exported function of the new internal module temporary-password-length.ts, which index.ts does not re-export (the barrel is untouched in the diff). So the package's public surface gains nothing for it. mapAuthApiError, AuthContextLike and generateTemporaryPassword were already exported from admin-user-endpoints.ts (and on the barrel) at the base; the import only adds imports of them. Right.

  5. The formula and the default. min(max(16, minPasswordLength), maxPasswordLength) is the card's wanted formula, byte for byte the create-user one. With better-auth's default range (8–128) it is 16, pinned by test ③ (real stack) and by the mock fixtures' comment. Right; the card's second acceptance line holds.

  6. Read once, before any write; refusal with nothing created. The read sits after the createUser capability check and before the protocol is built and runImport runs, so it precedes every row write and every sys_user update, and runs once per request rather than per row. A throw is mapped by mapAuthApiError(error, 'import-users failed'), the same mapping create-user uses for the same read (:725-727): a plain Error becomes 500 INTERNAL_ERROR. The new unit pin verifies createUser and engine.update are never called. A dryRun request also pays the read; that is acceptable (an unreadable auth context is a deployment fault, and a dry run that silently reported "would create" under it would misreport). Right.

  7. auto policy's no-channel fallback. The card read it from code only; the PR measures it (test ②, real stack, no email service): the fallback resolves plan.kind === 'temporary' and takes the same generateTemporaryPassword(temporaryLength) branch. Right.

  8. The red-first test pins the route's wiring, not just the function. admin-import-users-password-length.test.ts mounts the plugin's real registerAuthRoutes over a real AuthManager + SQLite, so a route that forgot getAuthContext would red it. The one stand-in is the admin gate's session read. Right; this is the test the card's acceptance asked for (minimum above 16 → the row's password meets it and the user signs in with it: tests ① and ② assert length 24 and sign-in/email 200).

  9. Boundary noted, not a defect. generateTemporaryPassword floors its length at 12 (unchanged, shared with create-user). A deployment with maxPasswordLength below 12 would get a 12-character password that better-auth then refuses, on the import and on create-user alike. That is inherited from fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range #25927's sizing, which the card's ruling says to follow exactly ("the way plugin-auth: admin create-user accepts an explicit password shorter than minPasswordLength (a 1-character password saves and signs in) while set-user-password and change-password refuse it #25824 sizes create-user's"), so it is out of this card's scope and consistent across the doors. Recorded for the seat; no change asked.

  10. Security family. The change only raises or caps the length of a generated credential to the configured policy; nothing about delivery, storage, the must-change gate or the admin gate moves. No new information leaves the door (the 500 message mirrors create-user's existing mapping). No reproduction recipe is recorded here.

② Semver level

  • Changeset .changeset/25932-import-users-temporary-password-length.md: '@objectstack/plugin-auth': patch. The diff publishes only from @objectstack/plugin-auth. Right.
  • Declaration lines, line-initial and identical on the PR body and the changeset: Clause-②: no (narrowing) and Metadata-protocol: none. The narrowing is the required deps member (① item 1). No spec key, authorable value, metadata type or default changes, so none is the right kind, and under AGENTS.md §3 none takes patch; a non-protocol breaking change ships patch with its BREAKING banner and ADR-0087 disposition. The changeset carries **BREAKING** for a host that calls runAdminImportUsers itself, a Before / Now / Fix block (the fix: pass getAuthContext wired to AuthManager.getAuthContext()), and the title carries !. This is the same shape the sibling fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range #25927 (1d2a246c) landed with. Right.
  • ADR-0087: <!-- adr-0087: not-required (runtime-interface-only packages/plugins/plugin-auth/src/admin-import-users.ts#IdentityImportDeps) ... -->. Checked against the gate's four predicates: the <path>#<Symbol> resolves to an exported interface at HEAD; its declaration site is a plain .ts (not *.zod.ts, not packages/spec/src/contracts/**, not an object definition); it is not a Zod projection; and no metadata surface references it in code (the only files naming IdentityImportDeps are the nine plugin-auth sources above). The body carries no FROM → TO migration prescription, so the inherited no-migration-prescription refusal does not bite. Right.
  • Gate verdicts: Check Changeset (the job that runs check-changeset-no-major — launch-window guard, level axis and lockstep — and the real check-adr-0087-registration scan) is success on the head, twice (the two check-runs of that name). Lint · docs & code guards, which hosts the changeset-family self-tests, is success.
  • check-changeset-no-major constraints: no major is introduced; none grades its one entry patch and moves no authorable-surface/ or authorable-defaults/ baseline (none is in the diff); the (narrowing) arm beside no is well-formed (no (widening) would be the malformed one). Consistent.

③ Boundary flags

Dev report (6412451931): open_questions: [] — nothing to answer there. The flags its summary raises, each answered:

  • Clause-② measured no (narrowing) against the claim's expected yes. The deviation is correct (① item 1): a required member on an exported deps interface is an accept-set narrowing, and the claim itself said "expected; the dev measures". The title !, the BREAKING banner and the ADR-0087 marker follow from it and are all present.
  • Required rather than optional. Agreed. An optional member needs an absent-branch fallback, and the only fallback is the unsized 16, which is the defect; required makes the compiler name every host. The PR body's acceptance note says the same.
  • Worktree / branch note (harness branch re-pointed at origin/main 05f7758f in a dedicated worktree; no unique commits lost). The branch is claude/project-thread-brnfvi, the claim's identity record; the check-run The card this PR closes must claim this branch is success, so the claim/branch pairing holds.
  • check:dual-build-cjs-loads NOT MEASURED locally (prerequisite: every package's dist). It is hosted by Build Core in ci.yml (:2906); see the check-run roll-up below for its conclusion on the head.
  • Three first-pass local reds re-run green (check-plugin-teardown-shape --self-test, check:dts-closure, check:type-check-debt), all prerequisite faults. CI's own runs decide: Lint · repo & tooling gates, Lint · CI, release & engine gates, Lint · scoped · tooling self-tests and Type Check · debt ledger are success on the head.
  • Out-of-scope finding (class a): authz-probe-blind-spot.test.ts red on main's dogfood battery at the base (packages/runtime/src/domains/i18n.ts, control handleI18nRequest 3 → 4, since 69fa23d6 / fix(runtime): GET /i18n/translations/:locale carries an ETag and Cache-Control: private, no-cache, and a matching If-None-Match is answered 304 (#25785) #25956). Not this diff (packages/runtime untouched). On the head the Dogfood Regression Gate aggregate is success and its three matrix shards are skipped (path-filtered), so CI did not re-measure it here. Escalated to the seat as a main-side item for a card of its own; it does not bear on this PR's verdict.
  • Author. The PR is authored by objectstack-fleet[bot] (relay-written via the card thread session_01A5wzhKHa98KsGQNHM9Z3Tq); no non-relay author note is needed.
  • Check-runs on 425cc7db (49 in total). Every completed non-skipped run concluded success: Auto Label, Check Changeset (x2), Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · scoped · tooling self-tests, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · repo & tooling gates, Lint · CI, release & engine gates, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, Type Check · debt ledger, Type Check · source gates, filter. Build Core is success, so check:dual-build-cjs-loads, the one derived gate the dev could not measure locally, is measured green by CI. Skipped (path- or opt-in-gated): the UI jobs, the dogfood shards, Dogfood Verify CLI, Temporal Conformance, Build Docs, Packed-tarball smoke, 18.x upgrade harness. No run has concluded other than success or skipped. Still in_progress at render time (2026-10-26T09:24Z, all started 09:16Z): Test Core (1/8) through Test Core (8/8), Type Check · workspace and Type Check · consumer gates. Those ten are the CI verdicts for the plugin-auth vitest run and for the compile check of ① item 2; the dev measured both locally (3726 passed; typecheck exit 0, check:test-typecheck OK) and this review verified every constructor by reading, so the verdict below rests on the diff and does not wait for them, but the seat must not arm auto-merge until all ten conclude success, and a non-success conclusion on any of them re-opens this record on this head.
  • No packages/spec/** path and no governed path is in the diff (the claim's ⛔ No packages/spec/** holds; Governed Surface Queue Guard is success).

Implemented-by: claude/project-thread-brnfvi
Reviewed-by: session_0186vfjHHMsRXC9EUT9hTFMo

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-26T09:26Z

Head 425cc7db21, report 6412451931 (on #25932), claim 6411247608, contract review 6412607172 (PASS, narrowing face, by server seat 2).

item reading
Shape draft, base main, Fixes #25932, Clause-②: no (narrowing) (the claim expected yes; the dev measured the narrowing and the review confirms it), Metadata-protocol: none, patch on @objectstack/plugin-auth with the BREAKING banner, ! on the title and the ADR-0087 marker.
Narrowing the exported IdentityImportDeps gains a REQUIRED getAuthContext(); required rather than optional, because the only fallback for an absent member is the unsized 16 the card reports. Every in-repo constructor (the route and seven test files) supplies it; no package outside plugin-auth builds these deps. The HTTP request shape of POST /admin/import-users is unchanged.
Fix the import door sizes a generated temporary password min(max(16, minPasswordLength), maxPasswordLength) from the same auth context create-user reads (PR #25927); the default range keeps 16.
Evidence red first and ablation; plugin-auth 3726 green; 72/73 derived gates (dual-build-cjs-loads answered by CI's Build Core); every check-run on the head green or an expected skip.
Carried the dogfood authz-probe-blind-spot red was main's own, fixed on main by #26002.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready + auto-merge through the relay.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 09:27
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 09:27
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit e3e936e Oct 26, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-brnfvi branch October 26, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants