fix(cli): os register requires a name, and the request-side as any that hid the mismatch is gone - #17455
Conversation
…id it
The live route refuses a sign-up without `name`: on a fresh environment (no
human user yet, so the audience gate's bootstrap bypass admits the request and
the route's own validation is the only judge left), `POST
/api/v1/auth/sign-up/email` answers 400 VALIDATION_ERROR "[body.name] Invalid
input: expected string, received undefined". The same run with a name supplied
answers 200 and creates the account.
So `RegisterRequestSchema` declares `name` correctly and the command was the
side that disagreed: it prompted "Name (optional)", typed its own payload with
`name?`, guarded email and password but not name, and cast the payload with `as
any` at the call site — which is the only reason that disagreement compiled.
- prompt: "Name (optional): " -> "Name: "
- guard: `if (!name) throw new Error('Name is required')`, beside email/password
- payload: annotated with the declared `RegisterRequest`, no local twin
- call site: the `as any` is gone, so the next divergence is a compile error
Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt
Co-authored-by: Claude <noreply@anthropic.com>
`tsconfig.test.json` covers this file, and `process.exitCode` is `string | number | null | undefined` there — the narrower annotation was a TS2322 the source-layer `tsc --noEmit` never sees. Claude-Session: https://claude.ai/code/session_01DapQyvYrFb1MxSYe7BL2nt Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 787a78840f65d0eb1db15f5bc0610278424cb128 && git checkout 787a78840f65d0eb1db15f5bc0610278424cb128
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9788f1e91983cb65413eb8e262716c9251900ac7 88e25d2985588d06ea6209c31446fcb8f9e43b0b && git checkout -B drift-repro 9788f1e91983cb65413eb8e262716c9251900ac7 && git merge --no-ff 88e25d2985588d06ea6209c31446fcb8f9e43b0b
node scripts/docs-audit/affected-docs.mjs --json 9788f1e91983cb65413eb8e262716c9251900ac7
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 34501395133 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #16932
The card turned on a measurement nobody had taken: does the live route refuse a sign-up with no
name, or accept it? It refuses. So the fix is the CLI's,packages/specis untouched, and the whole card ships here.The measurement
Driving the real command against a real route, not reading better-auth's source.
A showcase app booted
--fresh --no-seed-adminon a random high port, so the environment carries no human user yet. That matters: the audience gate refuses self-registration under the defaultinvite_onlyposture (the first attempt, against a seeded environment, came back403 SELF_REGISTRATION_CLOSEDand measured the gate rather than the route). With no human user the bootstrap bypass admits the request, and the route's own validation is the only judge left.Leg 1 — empty answer at the Name prompt:
Leg 2 — positive control, same environment, same route, same run, name supplied:
⇒ The route REQUIRES
name. The 400 is attributable tonameand nothing else about the environment.RegisterRequestSchema's requirednamedescribes the door correctly — the card's neighbouring-line control (imageon the next line carries.optional()andnamedoes not) reads as deliberate because it is.os registerfailed on exactly the answer its own prompt invited, on the first door of the first-use experience.What changed, all in
packages/cli/src/commands/register.tsThe command disagreed with the route in four places; all four now agree.
Name (optional):— a false promiseName:email,password— nonamenameguarded beside themname?: stringRegisterRequestclient.auth.register(payload as any)The cast is the card's real subject and comes out under either branch. With it gone the next divergence between this command and the declared request type is a compile error instead of a
400a user meets on their first command.The predicted compiler answer, verified — and it differs in wording
Dropping the cast on the unmodified command (mutation proven on disk, restored byte-identical to
HEADafterwards) yields exactly one error:Same code, same call site, same declaration as the card predicted. The sub-message differs: the card quoted the "Property 'name' is missing" form, which is what a bare object literal produces — the form PR #16926 fixed in
packages/client/README.md. Here the payload is a named variable annotatedname?: string, so tsc reports the assignability failure on the optional member instead. The declaration has not moved; the two carriers just differ in shape.⭐ And exactly one error, not two. The #5543 precedent the card cites (a cast also hiding genuinely misspelled keys) did not repeat here — nothing else was under the cast.
Tests
packages/cli/test/register-requires-name.test.ts(unit tier — no spawn, no kernel), four cases, pinning both halves:fetchis never called. The "route was never called" half is what distinguishes the fix from the defect: the pre-fix command also failed, one HTTP round trip later.Name:and no prompt matches/optional/i.{ email, password, name }toPOST .../api/v1/auth/sign-up/email. This is what stops the refusal being "fixed" by sending an empty string, which the route'sz.string()accepts and which would create an account with a blank display name.--nameis taken without prompting.Reverse verification (fix committed first; pre-fix source restored tree-only from the base commit, proven on disk, then restored byte-identical to
HEAD):The two defect pins go red. The two preservation pins stay green on both sides by construction — the pre-fix command also sent all three members once a name was supplied — and that is what they are for; they are not evidence about the defect and are not claimed as such.
Verification
dispatch-gates --commands(derived from the real change set,--repoasserted)--rancarrying exit codes: 62 accounted, 0 NOT-MEASURED, 0 UNRUNpnpm check:type-check-debt--max-old-space-size=4096; not a pass and not a finding). Re-run at the CI-shaped ceiling the script pins,--max-old-space-size=6144: exit 0check:cli-examples-paritypnpm --filter '@objectstack/cli^...' buildpnpm --filter @objectstack/cli typecheckcheck:test-typecheck)packages/cliunit tierpnpm lint(whole repo,eslint . --no-inline-config)Acceptance notes
os registerwrites~/.objectstack/credentials.json. Leg 2 therefore overwrote whatever that shared-container file held. Ephemeral dev state, but worth knowing before anyone drives this command as a measurement again.response as anyon the two lines below the call site stays.(response as any).token/.userread the raw wire keys beside the normalizeddata.*envelope, and@objectstack/client'sregisterdeliberately serves both — its TSDoc says the raw keys "are kept alongside for callers written against the wire". That is a declared compatibility read on the response seam, not the request-side mismatch this card is about, so it is out of scope here. noted, not filed — it is neither a reproducible defect nor a contract violation, and the successor is whoever next works theSessionResponseenvelope on the client seam; naming no one, there is no queued PR it belongs to today.Name (optional)had exactly one occurrence repo-wide, and the two docs pages that showos register(content/docs/permissions/authentication.mdx,content/docs/deployment/cli.mdx) never called the field optional — so no docs change rides along.ba9f0299:register.ts:115/:122-123/:126/:128,auth.zod.ts:93-98, and the instrument control (9export constinauth.zod.ts). None had moved.packages/specwas not edited. The measurement landed on the CLI half, so the declaration half never came up; thepackages/spechalf of the card has nothing left in it.Clause-②: no — re-declared from the delivered diff. Nothing published is narrowed and nothing is widened: no export, type or schema changes, no published request member becomes optional.
@objectstack/cli's behaviour changes only in that an input the route already refused is now refused locally with a clear message; no input that previously worked stops working.A
patchchangeset ships with it (@objectstack/clipublishesdist).Generated by Claude Code