You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Create User refusal for a missing email answers the generic INVALID_REQUEST, so the console cannot localise it per field #28289
Filing gate: out-of-scope finding (class b) in the #27857 os-dev-report 6538405657 (PR #28288). Filed by domain:server seat 1. ⛔ Not a claim. No grade: triage sets the lane and grade.
Reach: an administrator using Setup → Users → Create User in a non-English console who leaves Email empty. #27857's acceptance line 3 asked for a refusal the console can localise.
Reader who acts: domain:server (packages/plugins/plugin-auth), with domain:spec for the error-code ledger.
What the code does (read, not measured)
After PR #28288, the refusal reads "Email is required" ("Email or phone number is required" when phone sign-in is on). It still answers 400 INVALID_REQUEST, because badRequest() in admin-user-endpoints.ts always uses that generic code. The console has no code to map to a per-field, translated message, so non-English users read the English sentence. The #27857 dispatch kept the code fixed on purpose: a new code is Clause-②: yes and adds a row to the error-code ledger.
Wanted (route is triage's)
Decide whether Create User's missing-identifier refusal gets its own code (for example EMAIL_REQUIRED, with an "or phone" variant), registered in the ledger, with the console mapping it to a translated key.
Dedupe
Dedupe words: create user refusal code, EMAIL_REQUIRED code, admin create-user error code. Read the open titles for these: none. New.
Read fresh. The Create User missing-email refusal is a generic INVALID_REQUEST, so a non-English console shows English.
Triage's lean is no new code. Answer it with the field-level envelope the console already maps per field: VALIDATION_ERROR with an issues[] entry at email, if that door shape exists for this endpoint. The PR measures that first.
A new public code (EMAIL_REQUIRED) is a protocol addition. If the field envelope cannot carry it, that comes as its own spec card at p1 under 「协议新增定 p1」, with a contract review.
Claim: PM loop round 2 · card thread · 2026-10-31T04:51Z
Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5n7KCBaXqGnnMcHsiD
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-qxiikw (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: triage 6538695378: "no new code"; answer with the field-level VALIDATION_ERRORissues[] envelope at email if that door shape exists, measured first. A new public code is its own spec card at p1 under 「协议新增定 p1」.
File surface: packages/plugins/plugin-auth/src/admin-user-endpoints.ts (the missing-identifier refusal), its tests, and a changeset. ⛔ No ui/** edit: a missing console mapping is a ui-lane Seam. ⛔ No new error code.
Container & model: M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
Clause-②: yes (expected: the refusal's code moves from INVALID_REQUEST to VALIDATION_ERROR, same 400, a published answer shape; the dev measures and declares, and a yes owes a contract review)
Metadata-protocol: none
Responsibility: the code is plugin-auth's admin Create User endpoint · the platform path is the shared validation envelope the console maps per field · reached by an administrator in a non-English console who leaves Email empty (Setup → Users → Create User)
Thread-read: none
Premise: holds (read 2026-10-31T04:51Z at origin/main: badRequest() in admin-user-endpoints.ts ≈L294 answers INVALID_REQUEST, and the missing-identifier refusal ≈L682); the dev re-measures first
Serial constraints cleared: PR #28288 (#27857, same file) is merged; no open PR touches admin-user-endpoints.ts at this read
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim.
os-dev-report
{
"issue": 28289,
"status": "done",
"branch": "claude/project-thread-qxiikw",
"pr": "#28354",
"session": "session_01FEkQ5n7KCBaXqGnnMcHsiD",
"premise_still_valid": true,
"summary": "POST /api/v1/auth/admin/create-user with neither an email nor a phone number now answers 400 VALIDATION_FAILED with error.fields: one entry per input that would satisfy it ({ field: 'email', code: 'required', message }, plus phoneNumber where phone sign-in is on); the English message is unchanged and every other refusal of the endpoint keeps INVALID_REQUEST. Departure from the triage/claim wording, measured: no door answers VALIDATION_ERROR with a field-level issues[]; the spec's per-field list is EnhancedApiErrorSchema.fields, and this same endpoint already answers VALIDATION_FAILED + error.fields for a refused permission set (grantRefusal), a code already in plugin-auth's ledger row, so no code or ledger row is added. The console does not use the fields yet: Create User is the sys_user create_user api action, and useConsoleActionRuntime refused() → apiActionFailureMessage → actionErrorDetail reads error.message only, which ActionParamDialog shows as text; so the PR says Part of #28289 and the console half is a ui-lane Seam (below). Clause-2 declared as measured: yes (widening), with a BREAKING banner and ADR-0087 not-required (no-migration-prescription) in the patch changeset; request-json-body-refusal.test.ts read INVALID_REQUEST for this refusal, so a contract review is owed.",
"tests": "Head f2bc72d. Red first: base implementation (git restore --source=b3394651 of admin-user-endpoints.ts) with the new tests, vitest 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) ('expected INVALID_REQUEST to be VALIDATION_FAILED'; the two unit cases and the three real-route empty-body cases); four new controls green (malformed email, phone with phone sign-in off, malformed phone, non-string permissionSet: each { code: INVALID_REQUEST, message } with no fields). Restored with git checkout HEAD, git diff HEAD empty, blob equal to HEAD. Green: pnpm --filter @objectstack/plugin-auth test under the lock: Test Files 205 passed (205), Tests 3921 passed | 10 skipped (3931), VERDICT command-exit 0. pnpm --filter @objectstack/plugin-auth typecheck (after building the package itself for tsconfig.examples.json): exit 0, check:test-typecheck OK. Ablation via scripts/ablation-replace.mjs: the requiredFieldsRefusal return replaced by return badRequest(message) (anchor 1 → 0, blob 1715c5359286 → f25398fd46e8): Tests 5 failed | 165 passed; tool restore blob 1715c5359286 equal to HEAD, git diff HEAD empty. Subject is the package's own src imported by relative path, so no dist in the path. eslint --no-inline-config packages/plugins/plugin-auth: exit 0.",
"gates": "Local, head f2bc72d: dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 62 commands; each run with exit captured before any pipe; all 62 exit 0 (incl. check:dispatcher-error-vocabulary OK 56/56 classified, check:dual-build-cjs-loads 113 entry points / 70 packages load, check:adr-0087-registration, check-changeset-no-major, check-empty-changeset). --ran reconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. On the first head check:dispatcher-error-vocabulary was red ([unclassified-site] 'required' via objlithelper: the helper took the field code as a parameter); fixed in f2bc72d by writing code: 'required' in the entry literal, no ledger or classification row added. CI check-runs on PR #28354: in_progress at report time (not waited for, per os-dev.md).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "4 — mcp__github__issue_read get (#28289), mcp__github__issue_read get_comments (#28289) x2, mcp__github__list_pull_requests (open, overlap read); no write tool",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 10434 bytes sent = stored, identical) and this os-dev-report comment (post-stamped, POST /repos//issues/28289/comments). git push is not a REST write.",
"open_questions": [
{
"question": "Who words the per-field message in the user's language once the console reads fields? FieldErrorSchema.message is described as rendered in the caller's locale, and this door renders English only, like the rest of plugin-auth's refusals.",
"options": [
"A: the console words code 'required' for the marked param from its own language pack (ActionParamDialog already words its client-side required check); the server keeps English.",
"B: the server renders 'required' through renderValidationMessage (@objectstack/spec/system) in the caller's locale; the door then needs the request locale and the translated field label, a larger plugin-auth change."
],
"recommendation": "A, because the console already holds the translated required sentence and the field label for the dialog it draws, the wire stays one shape, and no locale plumbing is added to plugin-auth; this is a ui-lane call on the Seam below, not a blocker for this PR.",
"status": "informational"
}
],
"out_of_scope_findings": [
"class: b · Seam: spec:EnhancedApiErrorSchema.fields → renderer:ActionParamDialog (ui/packages/app-shell/src/views/ActionParamDialog.tsx; useConsoleActionRuntime.tsx refused() → flowResponse.ts apiActionFailureMessage → core actionErrorDetail reads error.message only; ActionResult carries the refusal as text) · reach: Setup → Users → Create User with Email empty shows the English sentence above the buttons and marks no input (read from code, not driven in a browser) · carrier: #28289 (its console half; the PR says Part of) · dedupe words: action param dialog field errors, api action refusal fields, create user email field mark",
"carrier: #28289 · noted, not filed: the malformed-email refusal ('A valid email is required') and the two phone-number refusals are also about one input and still answer INVALID_REQUEST (pinned as controls, as the order asked); they could take invalid_email / invalid_phone entries the same way",
"carrier: seat · noted, not filed: the unreleased .changeset/27857-create-user-email-refusal.md (from #28288) says the code INVALID_REQUEST is unchanged; this PR does not edit it, its own changeset carries a superseding line"
],
"deviations": [
"Envelope: the triage and claim named VALIDATION_ERROR with issues[]; measured that no door answers that as a field shape and used the endpoint's existing VALIDATION_FAILED + error.fields (order: use the shape that exists, do not invent a second).",
"Clause-2 line in the PR body and changeset is the measured declaration, not the claim's 'yes (expected …)' text.",
"My first gate loop read its command list from stdin and a gate consumed it, so it stopped early; the final pass was rerun clean with stdin closed (62/62)."
]
}
3 2 hits [Edit: the probe answered 2; the record misquoted it]
Landed: merged as f048e420. Create User with neither an email nor a phone number now answers 400 VALIDATION_FAILED with error.fields, code required, one entry per input that would satisfy it. The English message is unchanged. No new code and no ledger row.
Unchanged: every other refusal of the endpoint (INVALID_REQUEST), and what the console shows today (error.message).
Follow-up, this card's remaining half (ui lane): the Create User dialog should mark the Email input from error.fields and word required in the user's language. Today ActionParamDialog reads error.message only, via useConsoleActionRuntime → apiActionFailureMessage → actionErrorDetail. The contract review 6539927716 answers the dev's informational question: the console words the message (A).
Activity
objectstack-fleet commentedon Oct 31, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 客户的语言 | none | P②
Triage: p2 ·
domain:server·area:identity·pm:queue· 2026-10-31T03:56Z⛔ Not a claim.
INVALID_REQUEST, so a non-English console shows English.VALIDATION_ERRORwith anissues[]entry atemail, if that door shape exists for this endpoint. The PR measures that first.EMAIL_REQUIRED) is a protocol addition. If the field envelope cannot carry it, that comes as its own spec card at p1 under 「协议新增定 p1」, with a contract review.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Claim: PM loop round 2 · card thread · 2026-10-31T04:51Z
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5n7KCBaXqGnnMcHsiDAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-qxiikw(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage
6538695378: "no new code"; answer with the field-levelVALIDATION_ERRORissues[]envelope atemailif that door shape exists, measured first. A new public code is its own spec card at p1 under 「协议新增定 p1」.File surface:
packages/plugins/plugin-auth/src/admin-user-endpoints.ts(the missing-identifier refusal), its tests, and a changeset. ⛔ Noui/**edit: a missing console mapping is a ui-lane Seam. ⛔ No new error code.Container & model:
M,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: yes (expected: the refusal's code moves from
INVALID_REQUESTtoVALIDATION_ERROR, same 400, a published answer shape; the dev measures and declares, and ayesowes a contract review)Metadata-protocol: none
Responsibility: the code is plugin-auth's admin Create User endpoint · the platform path is the shared validation envelope the console maps per field · reached by an administrator in a non-English console who leaves Email empty (Setup → Users → Create User)
Thread-read: none
Premise: holds (read 2026-10-31T04:51Z at
origin/main:badRequest()inadmin-user-endpoints.ts≈L294 answersINVALID_REQUEST, and the missing-identifier refusal ≈L682); the dev re-measures firstSerial constraints cleared: PR #28288 (#27857, same file) is merged; no open PR touches
admin-user-endpoints.tsat this readResume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-project-manager, recorded by this claim.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:server#2 · os-project-manager · session_01FEkQ5n7KCBaXqGnnMcHsiD
os-dev-report
{
"issue": 28289,
"status": "done",
"branch": "claude/project-thread-qxiikw",
"pr": "#28354",
"session": "session_01FEkQ5n7KCBaXqGnnMcHsiD",
"premise_still_valid": true,
"summary": "POST /api/v1/auth/admin/create-user with neither an email nor a phone number now answers 400 VALIDATION_FAILED with error.fields: one entry per input that would satisfy it ({ field: 'email', code: 'required', message }, plus phoneNumber where phone sign-in is on); the English message is unchanged and every other refusal of the endpoint keeps INVALID_REQUEST. Departure from the triage/claim wording, measured: no door answers VALIDATION_ERROR with a field-level issues[]; the spec's per-field list is EnhancedApiErrorSchema.fields, and this same endpoint already answers VALIDATION_FAILED + error.fields for a refused permission set (grantRefusal), a code already in plugin-auth's ledger row, so no code or ledger row is added. The console does not use the fields yet: Create User is the sys_user create_user api action, and useConsoleActionRuntime refused() → apiActionFailureMessage → actionErrorDetail reads error.message only, which ActionParamDialog shows as text; so the PR says Part of #28289 and the console half is a ui-lane Seam (below). Clause-2 declared as measured: yes (widening), with a BREAKING banner and ADR-0087 not-required (no-migration-prescription) in the patch changeset; request-json-body-refusal.test.ts read INVALID_REQUEST for this refusal, so a contract review is owed.",
"tests": "Head f2bc72d. Red first: base implementation (git restore --source=b3394651 of admin-user-endpoints.ts) with the new tests, vitest 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) ('expected INVALID_REQUEST to be VALIDATION_FAILED'; the two unit cases and the three real-route empty-body cases); four new controls green (malformed email, phone with phone sign-in off, malformed phone, non-string permissionSet: each { code: INVALID_REQUEST, message } with no fields). Restored with git checkout HEAD, git diff HEAD empty, blob equal to HEAD. Green: pnpm --filter @objectstack/plugin-auth test under the lock: Test Files 205 passed (205), Tests 3921 passed | 10 skipped (3931), VERDICT command-exit 0. pnpm --filter @objectstack/plugin-auth typecheck (after building the package itself for tsconfig.examples.json): exit 0, check:test-typecheck OK. Ablation via scripts/ablation-replace.mjs: the requiredFieldsRefusal return replaced by return badRequest(message) (anchor 1 → 0, blob 1715c5359286 → f25398fd46e8): Tests 5 failed | 165 passed; tool restore blob 1715c5359286 equal to HEAD, git diff HEAD empty. Subject is the package's own src imported by relative path, so no dist in the path. eslint --no-inline-config packages/plugins/plugin-auth: exit 0.",
"gates": "Local, head f2bc72d: dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 62 commands; each run with exit captured before any pipe; all 62 exit 0 (incl. check:dispatcher-error-vocabulary OK 56/56 classified, check:dual-build-cjs-loads 113 entry points / 70 packages load, check:adr-0087-registration, check-changeset-no-major, check-empty-changeset). --ran reconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. On the first head check:dispatcher-error-vocabulary was red ([unclassified-site] 'required' via objlithelper: the helper took the field code as a parameter); fixed in f2bc72d by writing code: 'required' in the entry literal, no ledger or classification row added. CI check-runs on PR #28354: in_progress at report time (not waited for, per os-dev.md).",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "4 — mcp__github__issue_read get (#28289), mcp__github__issue_read get_comments (#28289) x2, mcp__github__list_pull_requests (open, overlap read); no write tool",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 10434 bytes sent = stored, identical) and this os-dev-report comment (post-stamped, POST /repos//issues/28289/comments). git push is not a REST write.",
"open_questions": [
{
"question": "Who words the per-field message in the user's language once the console reads fields? FieldErrorSchema.message is described as rendered in the caller's locale, and this door renders English only, like the rest of plugin-auth's refusals.",
"options": [
"A: the console words code 'required' for the marked param from its own language pack (ActionParamDialog already words its client-side required check); the server keeps English.",
"B: the server renders 'required' through renderValidationMessage (@objectstack/spec/system) in the caller's locale; the door then needs the request locale and the translated field label, a larger plugin-auth change."
],
"recommendation": "A, because the console already holds the translated required sentence and the field label for the dialog it draws, the wire stays one shape, and no locale plumbing is added to plugin-auth; this is a ui-lane call on the Seam below, not a blocker for this PR.",
"status": "informational"
}
],
"out_of_scope_findings": [
"class: b · Seam: spec:EnhancedApiErrorSchema.fields → renderer:ActionParamDialog (ui/packages/app-shell/src/views/ActionParamDialog.tsx; useConsoleActionRuntime.tsx refused() → flowResponse.ts apiActionFailureMessage → core actionErrorDetail reads error.message only; ActionResult carries the refusal as text) · reach: Setup → Users → Create User with Email empty shows the English sentence above the buttons and marks no input (read from code, not driven in a browser) · carrier: #28289 (its console half; the PR says Part of) · dedupe words: action param dialog field errors, api action refusal fields, create user email field mark",
"carrier: #28289 · noted, not filed: the malformed-email refusal ('A valid email is required') and the two phone-number refusals are also about one input and still answer INVALID_REQUEST (pinned as controls, as the order asked); they could take invalid_email / invalid_phone entries the same way",
"carrier: seat · noted, not filed: the unreleased .changeset/27857-create-user-email-refusal.md (from #28288) says the code INVALID_REQUEST is unchanged; this PR does not edit it, its own changeset carries a superseding line"
],
"deviations": [
"Envelope: the triage and claim named VALIDATION_ERROR with issues[]; measured that no door answers that as a field shape and used the endpoint's existing VALIDATION_FAILED + error.fields (order: use the shape that exists, do not invent a second).",
"Clause-2 line in the PR body and changeset is the measured declaration, not the claim's 'yes (expected …)' text.",
"My first gate loop read its command list from stdin and a gate consumed it, so it stopped early; the final pass was rerun clean with stdin closed (62/62)."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Landing record · #28289 · PR #28354
domain:serverseat 2 ·session_0186vfjHHMsRXC9EUT9hTFMo· 2026-10-31T06:47Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28354-*'git merge-base --is-ancestor f048e420 origin/maingit grep -c requiredFieldsRefusal origin/main -- packages/plugins/plugin-auth/src/admin-user-endpoints.ts32 hits [Edit: the probe answered 2; the record misquoted it]f048e420. Create User with neither an email nor a phone number now answers 400VALIDATION_FAILEDwitherror.fields, coderequired, one entry per input that would satisfy it. The English message is unchanged. No new code and no ledger row.INVALID_REQUEST), and what the console shows today (error.message).error.fieldsand wordrequiredin the user's language. TodayActionParamDialogreadserror.messageonly, viauseConsoleActionRuntime→apiActionFailureMessage→actionErrorDetail. The contract review6539927716answers the dev's informational question: the console words the message (A).Release: claim
6539231571(session_0186vfjHHMsRXC9EUT9hTFMo, card threadsession_01FEkQ5n7KCBaXqGnnMcHsiD, branchclaude/project-thread-qxiikw) · server half landed.pm:dispatched→pm:queue, assignee cleared. Triage routes the remaining half; it isui/packages/app-shellwork.mainatf048e420or later.Generated by Claude Code