Repository navigation
fix(plugin-auth): Create User with no email answers the field-level VALIDATION_FAILED refusal - #28354
Conversation
…level (red first) Claude-Session: https://claude.ai/code/session_01FEkQ5n7KCBaXqGnnMcHsiD Co-authored-by: Claude <noreply@anthropic.com>
… VALIDATION_FAILED refusal Claude-Session: https://claude.ai/code/session_01FEkQ5n7KCBaXqGnnMcHsiD Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5n7KCBaXqGnnMcHsiD Co-authored-by: Claude <noreply@anthropic.com>
… place, not as a parameter check:dispatcher-error-vocabulary read a code parameter in the entry literal as a stamped ADR-0112 code. Claude-Session: https://claude.ai/code/session_01FEkQ5n7KCBaXqGnnMcHsiD Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 31 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 22 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 b3afeb010df9c7d48c28aeb4adc358a2550bce55 && git checkout b3afeb010df9c7d48c28aeb4adc358a2550bce55
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 488b3ed092796136551c36a5eb6d143c44a2b878 f2bc72d5a0d511039a58611ac5e4af3bc68bcc23 && git checkout -B drift-repro 488b3ed092796136551c36a5eb6d143c44a2b878 && git merge --no-ff f2bc72d5a0d511039a58611ac5e4af3bc68bcc23
node scripts/docs-audit/affected-docs.mjs --json 488b3ed092796136551c36a5eb6d143c44a2b878
|
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Contract reviewServed-tier: The review is owed by the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #28289
Clause-②: yes (widening: the refusal gains
error.fields, one{ field, code, message }entry per input that would satisfy it, coderequired; itserror.codemoves fromINVALID_REQUESTtoVALIDATION_FAILED, the code the same endpoint's permission-set refusal and the data doors already answer withfields)Metadata-protocol: none
维护者速读
INVALID_REQUEST改为字段级的VALIDATION_FAILED,并带fields:email(及phoneNumber)各一条,code: required。英文提示不变。INVALID_REQUEST判断这条拒绝的客户端会看到新码。回滚即还原本 PR。packages/plugins/plugin-auth/src/admin-user-endpoints.ts(requiredFieldsRefusal与缺标识分支)、.changeset/28289-create-user-email-field-refusal.md。pnpm --filter @objectstack/plugin-auth exec vitest run src/admin-user-endpoints.test.ts,预期全绿,含两条VALIDATION_FAILED用例与四条对照。Part of。What changes
plugin-authadmin-user-endpoints.ts,POST /api/v1/auth/admin/create-user: with neither an email nor a phone number, the refusal is the field-level envelope.400{ code: 'INVALID_REQUEST', message: 'Email is required' }, or "Email or phone number is required" where phone sign-in is on.400{ code: 'VALIDATION_FAILED', message, fields }.fieldsis[{ field: 'email', code: 'required', message }], plus{ field: 'phoneNumber', code: 'required', message }where phone sign-in is on. The Englishmessageis unchanged.requiredFieldsRefusal(message, fields)besidebadRequest(). Its entry coderequiredis checked againstFieldErrorCode(the closed ADR-0114 catalog,@objectstack/spec/api) withsatisfies, so no code is invented. The code is written in the entry literal, not passed as a parameter: as a parameter,check:dispatcher-error-vocabularyread it as a stamped ADR-0112 code (objlithelpershape) and went red; fixed in f2bc72d.badRequest()and answersINVALID_REQUESTas before.Which field envelope, measured (the triage named
VALIDATION_ERRORwithissues[])The order said to use the field-level shape that already exists for this door, and not to invent a second one. At
origin/mainb339465:issueslist on the error envelope. Its per-field list isfields(EnhancedApiErrorSchema.fields, entriesFieldErrorSchema, inpackages/spec/src/api/errors.zod.ts; ADR-0114 D4 renamedfieldErrorstofieldsbecausefieldsis what the wire carries). On the wire,issuesis a thrown error's raw Zod issues passed through (@objectstack/resterror-response.ts, onebatch-routes.tsrefusal, the metadata save door that the console's metadata editors read). It is not the record/form field shape.grantRefusal()passes a permission-set refusal through asVALIDATION_FAILEDwitherror.fields(EndpointResult.body.error.fields).VALIDATION_FAILEDis in the error-code ledger under@objectstack/plugin-auth, so this adds no ledger row.@objectstack/restcrud-routes.tsandform-routes.ts(validationFailure,400 VALIDATION_FAILEDwithfields[]).extractFieldErrors(ui/packages/react/src/utils/error-message.ts), readserror.fields,error.details.fieldsorvalidationErrors, whatever the code.So the refusal uses
VALIDATION_FAILED+error.fields, the shape of the permission-set refusal one branch below it.Tests
admin-user-endpoints.test.ts: the Create User with Email empty says "Either email or phoneNumber is required" and the dialog marks nothing required (#27521 row 10) #27857 pair (phone sign-in off and on) now asserts400,VALIDATION_FAILED, the unchanged English message, andfieldsexactly. Four new CONTROL cases pin that the refusals beside it keep{ code: 'INVALID_REQUEST', message }with nofields: a malformed email, a phone number with phone sign-in off, a malformed phone number, apermissionSetthat is not a name. A valid Create User is pinned by the suite's existing success cases.request-json-body-refusal.test.ts(the real Hono route): the three empty-body controls for/admin/create-usernow expectVALIDATION_FAILEDandfields: [{ field: 'email', code: 'required', message: 'Email is required' }], through new optionalemptyCode/emptyFieldskeys on the route table./admin/set-user-passwordand every other route keepINVALID_REQUEST.Red first, on the base implementation
git restore --source=b3394651 -- packages/plugins/plugin-auth/src/admin-user-endpoints.tswith the new tests, thenvitest run src/admin-user-endpoints.test.ts src/request-json-body-refusal.test.ts: Test Files 2 failed (2), Tests 5 failed, 165 passed (170). The 5 are the twoVALIDATION_FAILEDcases (expected 'INVALID_REQUEST' to be 'VALIDATION_FAILED') and the three real-route empty-body cases. The four controls stayed green. Restored withgit checkout HEAD --;git diff HEADempty, blob equal toHEAD.Green, and the ablation
pnpm --filter @objectstack/plugin-auth test(head f2bc72d): Test Files 205 passed (205), Tests 3921 passed, 10 skipped (3931). Lock VERDICT command-exit 0.pnpm --filter @objectstack/plugin-auth typecheck(head f2bc72d, after building the package itself, whichtsconfig.examples.jsonresolves): exit 0;check:test-typecheckOK, the debt ledger unchanged.scripts/ablation-replace.mjson head f2bc72d: therequiredFieldsRefusal(...)return replaced byreturn badRequest(message);(anchor 1 to 0, blob1715c5359286tof25398fd46e8) reds the same 5 cases, 165 passed. Restored by the tool, blob after restore1715c5359286equal toHEAD,git diff HEADempty. (The same ablation on the first head, 0c1a502, read the same 5.) The subject is the package's ownsrc/, imported by relative path, so no build is in the path.eslint --no-inline-config packages/plugins/plugin-auth: exit 0.Acceptance notes
Part of. The console does not yet use the newfields. Create User is thecreate_userapi action onsys_user. A refused run reachesuseConsoleActionRuntime'srefused(), thenapiActionFailureMessage, thenactionErrorDetail, which reads onlyerror.message.ActionParamDialogshows that text above the buttons ("ActionResult carries the refusal as text only"). So today the dialog shows the same English sentence as before. The per-field mark and its translation are the ui lane's half:Seam: spec:EnhancedApiErrorSchema.fields → renderer:ActionParamDialog (ui/packages/app-shell/src/views/ActionParamDialog.tsx; useConsoleActionRuntime.tsx refused() → flowResponse.ts apiActionFailureMessage → actionErrorDetail reads error.message only).FieldErrorSchema.messageis described as rendered in the caller's locale. This door renders English only, like the rest of plugin-auth's refusals. Two routes for the translated sentence: the dialog wordscode: 'required'from its own language pack (it already words its client-side required check), or the server rendersrequiredthroughrenderValidationMessage(@objectstack/spec/system) in the caller's locale, which would need this door to read the request locale and the translated field label. Left to the ui-lane card; the report recommends the first.INVALID_REQUEST(pinned as controls, as the order asked). They could takeinvalid_email/invalid_phoneentries the same way..changeset/27857-create-user-email-refusal.md(from fix(plugin-auth): Create User with no email names Email, and a wrong password on the password-change screen reads in the user's language #28288, unreleased) says the codeINVALID_REQUESTis unchanged. This PR does not edit it; its own changeset carries a line that supersedes it.Clause-②is declared as measured. The claim's line saidyeswas expected;request-json-body-refusal.test.tsreadINVALID_REQUESTfor this refusal, so the published answer moved and a contract review is owed.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstacknamed 62 commands; each run with its exit code captured before any pipe. All 62 exit 0, includingcheck:dual-build-cjs-loads(113 entry points across 70 packages load) andcheck:dispatcher-error-vocabulary(OK, 56 sites all classified).--ranreconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN.check:dispatcher-error-vocabularywent red ([unclassified-site] … stamps unregistered code 'required' (objlithelper)), because the helper took the field code as a parameter. f2bc72d writescode: 'required'in the entry literal; the gate is green and no ledger row or classification row was added.eslint --no-inline-config packages/plugins/plugin-auth: exit 0.packages/plugins/plugin-auth/src/by title; fix(plugin-auth): Create User with no email names Email, and a wrong password on the password-change screen reads in the user's language #28288, the last change toadmin-user-endpoints.ts, is merged and is this branch's base.test:repo(CI Test Core), the dogfood battery (the title carries no!andClause-②has no(narrowing)arm).Generated by Claude Code