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).
Claim: PM loop round 1 · card thread · 2026-11-01T10:21Z
Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5mifB6V9sjWvKcperj
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-2v6d35 (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#3
Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Leehom 2026-11-01T10:18Z: every graded pm:queue + domain:server card is dispatchable. Contract review 6539927716 answer A (the Console words the message) binds. This claim takes the card's remaining half (ui/).
File surface: the Console action dialog failure display (ui/packages/app-shell: ActionParamDialog, useConsoleActionRuntime / apiActionFailureMessage / actionErrorDetail, or an existing per-field error reader it reuses), the i18n bundles if a key is missing, tests, and changesets for the shipped ui/ packages. ⛔ Not packages/spec; ⛔ not the server answer of plugin-auth.
Container & model: S (p2), mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
Clause-②: no — the Console reads the error.fields shape PR #28354 already serves; nothing is served or refused anew.
Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the Console action-dialog failure path · the platform path is the Create User refusal PR #28354 landed (VALIDATION_FAILED + error.fields) · reached by an administrator in a non-English Console who leaves Email empty
Thread-read: 6540250311
Premise: holds (landing record of PR #28354: the dialog reads error.message only); the dev re-measures first
Serial constraints cleared: none: no open PR found on the dialog path at this read; the dev re-reads overlap at start and before pr_create.
Resume-from: none — fresh from origin/main
State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28289.md (project folder).
os-dev-report
{
"issue": 28289,
"status": "done",
"branch": "claude/project-thread-2v6d35",
"pr": "#28649",
"session": "session_01FEkQ5mifB6V9sjWvKcperj",
"premise_still_valid": true,
"summary": "Console half of #28289 (contract review 6539927716 answer A: the Console words the message). Measured the wire first: a throwaway probe on api-action-forward.test.ts's harness shows the action door relays plugin-auth's Create User refusal whole, 400 VALIDATION_FAILED with error.fields (and httpStatus). The existing per-field reader behind ActionResult.fieldErrors (@object-ui/core refusalFieldErrors) read only error.details.fields and the legacy inner fields, so it missed this member; it now also reads error.fields (EnhancedApiErrorSchema.fields) and is exported. A new app-shell helper apiActionRefusalResult gives both api hosts (useConsoleActionRuntime, RecordDetailView door and direct fetch) the same failed ActionResult: apiActionFailureMessage text plus fieldErrors. ActionParamDialog marks each required entry naming a collected param with its own actionDialog.requiredError key and the param's localized label, and draws no refusal box when every entry is marked; any entry it cannot mark keeps the box with the server's text. Producer check: server fields email / phoneNumber match the create_user param names email (field-backed, name = field) / phoneNumber. No server answer, error code or packages/spec change.",
"tests": "Head cd27110. New e2e test useConsoleActionRuntime.createUserRequiredField-28289.test.tsx (real ActionRunner, console runtime api + param handlers, real ActionParamDialog, real I18nProvider zh, mocked door answering the measured body). Red on base: the five changed source files restored from a798ae8 with the new test kept: Test Files 1 failed (1), Tests 4 failed | 2 passed (6), each red 'Unable to find an element with the text: 邮箱 为必填项'; the 2 controls (refusal without fields keeps its box and marks nothing; success closes the dialog) pass on base; restored with git checkout HEAD, git diff HEAD empty. Green at head: Tests 6 passed (6), VERDICT command-exit 0. Core test actionRefusalFields-28289.test.ts: error.fields read, /actions refusal answers fieldErrors, error.details.fields still read, no-field refusal answers none. Ablations via scripts/ablation-replace.mjs, subjects are src (@object-ui/core aliased to packages/core/src in ui/vitest.config.mts; app-shell by relative import), so no dist in the path: (1) ' ?? b?.error?.fields' deleted from actionResponse.ts, anchor 1 -> 0, blob 00c84412e26a -> 197eaad93a94: Tests 4 failed | 2 passed (6); restored blob == HEAD, git diff HEAD empty. (2) 'setErrors(marks);' deleted from ActionParamDialog.tsx, anchor 1 -> 0, blob 29087c772629 -> 79e9f69aba8a: Tests 4 failed | 2 passed (6); restored blob == HEAD, git diff HEAD empty. Suites: pnpm --dir ui exec vitest run packages/core/: Test Files 228 passed (228), Tests 4226 passed | 27 skipped (4253), VERDICT 0. app-shell narrowed to the test files naming ActionParamDialog / useConsoleActionRuntime / flowResponse / RecordDetailView / apiActionDoor / refusalFieldErrors / interpretActionResponse under app-shell/src and apps/console/src: Test Files 284 passed (284), Tests 2391 passed (2391), VERDICT 0. pnpm --filter @object-ui/core run type-check exit 0; pnpm --filter @object-ui/app-shell run type-check exit 0 (both run tsc on src and tsconfig.test.json). ESLint on the 7 touched files, root config --no-inline-config and ui config: 0 errors each.",
"gates": "Local, head cd27110: dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands; each run with exit captured before any pipe; all 34 exit 0. check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: its closure build queued behind this branch's own app-shell run on the lock, NOT MEASURED); re-run under the lock: OK, 0 raw tsc errors, exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (record: pm-orders/server/28289-gates-ran.txt in the project folder). Also pnpm check:new-line-citations (ui): 0 new citations, exit 0. CI check-runs on PR #28649 at report time: in_progress (Check Changeset, Lint and Type Check families, Governed Surface Queue Guard, PR-body guards), not waited for per os-dev.md.",
"line_budget": "n/a — no skills/** or ratcheted ledger in the diff; PR size 350 additions, 20 deletions",
"files_changed": [
".changeset/28289-create-user-required-field-mark.md (+14/-0)",
"ui/packages/app-shell/src/hooks/tests/useConsoleActionRuntime.createUserRequiredField-28289.test.tsx (+202/-0)",
"ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx (+4/-3)",
"ui/packages/app-shell/src/utils/flowResponse.ts (+24/-1)",
"ui/packages/app-shell/src/views/ActionParamDialog.tsx (+38/-4)",
"ui/packages/app-shell/src/views/RecordDetailView.tsx (+3/-5)",
"ui/packages/core/src/actions/tests/actionRefusalFields-28289.test.ts (+53/-0)",
"ui/packages/core/src/actions/actionResponse.ts (+12/-7)"
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub tool was called; card, comments, PR and check-runs were read with gh api GETs",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 10096 bytes sent = stored, identical) and this os-dev-report comment (post-stamped, POST /repos//issues/28289/comments). git push x2 is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #28289 · noted, not filed: the runner still toasts the refused run's error (the server's English sentence) even when the dialog marks every entry; whether ActionRunner's toast should step aside for a fully marked refusal is the runner's behaviour, unchanged here (PR Acceptance notes)",
"carrier: #28289 · noted, not filed: with phone sign-in on, the server sends a required entry for both email and phoneNumber, so both inputs read 'required' although either satisfies the server; a one-of wording would need a second i18n key (PR Acceptance notes)",
"carrier: none · noted, not filed: a refusal's field list rides two envelope members on the wire, error.details.fields (data and action doors) and error.fields (plugin-auth's own refusals); both are on EnhancedApiErrorSchema, and this PR reads both rather than choosing one. dedupe words: refusal fields envelope member, error.fields vs details.fields"
],
"deviations": [
"app-shell suite narrowed: the full 2,029-file run on 2 workers was stopped after 34 minutes (my own PID, no result); ran the 284 files that name the changed modules instead. CI Test UI runs the full set.",
"origin/main moved 13 commits past base a798ae8 before pr_create; none touches a file this diff changes, so the branch was not merged with main before opening the PR (AGENTS.md section 10 asks for a pull first).",
"No live Console session was driven in a browser; the end-to-end reading is the component test with a mocked door answering the body the door probe measured.",
"PR labels: none sent; the order names no label and the diff carries changesets, so skip-changeset does not apply."
]
}
Landed: merged as 9da2f4b0; Fixes #28289 closed the card. The Console Create User dialog marks a server required refusal on the Email field (and Phone when phone sign-in is on) in the user's language; refusalFieldErrors in @object-ui/core reads error.fields too.
Claim closed by the merge. Resume-from: main at 9da2f4b0 or later.
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
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim: PM loop round 1 · card thread · 2026-11-01T10:21Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5mifB6V9sjWvKcperjAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-2v6d35(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#3Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Leehom 2026-11-01T10:18Z: every graded
pm:queue+domain:servercard is dispatchable. Contract review6539927716answer A (the Console words the message) binds. This claim takes the card's remaining half (ui/).File surface: the Console action dialog failure display (
ui/packages/app-shell:ActionParamDialog,useConsoleActionRuntime/apiActionFailureMessage/actionErrorDetail, or an existing per-field error reader it reuses), the i18n bundles if a key is missing, tests, and changesets for the shippedui/packages. ⛔ Notpackages/spec; ⛔ not the server answer ofplugin-auth.Container & model:
S(p2),mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: no — the Console reads the
error.fieldsshape PR #28354 already serves; nothing is served or refused anew.Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the Console action-dialog failure path · the platform path is the Create User refusal PR #28354 landed (
VALIDATION_FAILED+error.fields) · reached by an administrator in a non-English Console who leaves Email emptyThread-read: 6540250311
Premise: holds (landing record of PR #28354: the dialog reads
error.messageonly); the dev re-measures firstSerial constraints cleared: none: no open PR found on the dialog path at this read; the dev re-reads overlap at start and before
pr_create.Resume-from: none — fresh from
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28289.md(project folder).objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5mifB6V9sjWvKcperj
os-dev-report
{
"issue": 28289,
"status": "done",
"branch": "claude/project-thread-2v6d35",
"pr": "#28649",
"session": "session_01FEkQ5mifB6V9sjWvKcperj",
"premise_still_valid": true,
"summary": "Console half of #28289 (contract review 6539927716 answer A: the Console words the message). Measured the wire first: a throwaway probe on api-action-forward.test.ts's harness shows the action door relays plugin-auth's Create User refusal whole, 400 VALIDATION_FAILED with error.fields (and httpStatus). The existing per-field reader behind ActionResult.fieldErrors (@object-ui/core refusalFieldErrors) read only error.details.fields and the legacy inner fields, so it missed this member; it now also reads error.fields (EnhancedApiErrorSchema.fields) and is exported. A new app-shell helper apiActionRefusalResult gives both api hosts (useConsoleActionRuntime, RecordDetailView door and direct fetch) the same failed ActionResult: apiActionFailureMessage text plus fieldErrors. ActionParamDialog marks each required entry naming a collected param with its own actionDialog.requiredError key and the param's localized label, and draws no refusal box when every entry is marked; any entry it cannot mark keeps the box with the server's text. Producer check: server fields email / phoneNumber match the create_user param names email (field-backed, name = field) / phoneNumber. No server answer, error code or packages/spec change.",
"tests": "Head cd27110. New e2e test useConsoleActionRuntime.createUserRequiredField-28289.test.tsx (real ActionRunner, console runtime api + param handlers, real ActionParamDialog, real I18nProvider zh, mocked door answering the measured body). Red on base: the five changed source files restored from a798ae8 with the new test kept: Test Files 1 failed (1), Tests 4 failed | 2 passed (6), each red 'Unable to find an element with the text: 邮箱 为必填项'; the 2 controls (refusal without fields keeps its box and marks nothing; success closes the dialog) pass on base; restored with git checkout HEAD, git diff HEAD empty. Green at head: Tests 6 passed (6), VERDICT command-exit 0. Core test actionRefusalFields-28289.test.ts: error.fields read, /actions refusal answers fieldErrors, error.details.fields still read, no-field refusal answers none. Ablations via scripts/ablation-replace.mjs, subjects are src (@object-ui/core aliased to packages/core/src in ui/vitest.config.mts; app-shell by relative import), so no dist in the path: (1) ' ?? b?.error?.fields' deleted from actionResponse.ts, anchor 1 -> 0, blob 00c84412e26a -> 197eaad93a94: Tests 4 failed | 2 passed (6); restored blob == HEAD, git diff HEAD empty. (2) 'setErrors(marks);' deleted from ActionParamDialog.tsx, anchor 1 -> 0, blob 29087c772629 -> 79e9f69aba8a: Tests 4 failed | 2 passed (6); restored blob == HEAD, git diff HEAD empty. Suites: pnpm --dir ui exec vitest run packages/core/: Test Files 228 passed (228), Tests 4226 passed | 27 skipped (4253), VERDICT 0. app-shell narrowed to the test files naming ActionParamDialog / useConsoleActionRuntime / flowResponse / RecordDetailView / apiActionDoor / refusalFieldErrors / interpretActionResponse under app-shell/src and apps/console/src: Test Files 284 passed (284), Tests 2391 passed (2391), VERDICT 0. pnpm --filter @object-ui/core run type-check exit 0; pnpm --filter @object-ui/app-shell run type-check exit 0 (both run tsc on src and tsconfig.test.json). ESLint on the 7 touched files, root config --no-inline-config and ui config: 0 errors each.",
"gates": "Local, head cd27110: dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands; each run with exit captured before any pipe; all 34 exit 0. check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: its closure build queued behind this branch's own app-shell run on the lock, NOT MEASURED); re-run under the lock: OK, 0 raw tsc errors, exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (record: pm-orders/server/28289-gates-ran.txt in the project folder). Also pnpm check:new-line-citations (ui): 0 new citations, exit 0. CI check-runs on PR #28649 at report time: in_progress (Check Changeset, Lint and Type Check families, Governed Surface Queue Guard, PR-body guards), not waited for per os-dev.md.",
"line_budget": "n/a — no skills/** or ratcheted ledger in the diff; PR size 350 additions, 20 deletions",
"files_changed": [
".changeset/28289-create-user-required-field-mark.md (+14/-0)",
"ui/packages/app-shell/src/hooks/tests/useConsoleActionRuntime.createUserRequiredField-28289.test.tsx (+202/-0)",
"ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx (+4/-3)",
"ui/packages/app-shell/src/utils/flowResponse.ts (+24/-1)",
"ui/packages/app-shell/src/views/ActionParamDialog.tsx (+38/-4)",
"ui/packages/app-shell/src/views/RecordDetailView.tsx (+3/-5)",
"ui/packages/core/src/actions/tests/actionRefusalFields-28289.test.ts (+53/-0)",
"ui/packages/core/src/actions/actionResponse.ts (+12/-7)"
],
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0 — no MCP GitHub tool was called; card, comments, PR and check-runs were read with gh api GETs",
"api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 10096 bytes sent = stored, identical) and this os-dev-report comment (post-stamped, POST /repos//issues/28289/comments). git push x2 is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #28289 · noted, not filed: the runner still toasts the refused run's error (the server's English sentence) even when the dialog marks every entry; whether ActionRunner's toast should step aside for a fully marked refusal is the runner's behaviour, unchanged here (PR Acceptance notes)",
"carrier: #28289 · noted, not filed: with phone sign-in on, the server sends a required entry for both email and phoneNumber, so both inputs read 'required' although either satisfies the server; a one-of wording would need a second i18n key (PR Acceptance notes)",
"carrier: none · noted, not filed: a refusal's field list rides two envelope members on the wire, error.details.fields (data and action doors) and error.fields (plugin-auth's own refusals); both are on EnhancedApiErrorSchema, and this PR reads both rather than choosing one. dedupe words: refusal fields envelope member, error.fields vs details.fields"
],
"deviations": [
"app-shell suite narrowed: the full 2,029-file run on 2 workers was stopped after 34 minutes (my own PID, no result); ran the 284 files that name the changed modules instead. CI Test UI runs the full set.",
"origin/main moved 13 commits past base a798ae8 before pr_create; none touches a file this diff changes, so the branch was not merged with main before opening the PR (AGENTS.md section 10 asks for a pull first).",
"No live Console session was driven in a browser; the end-to-end reading is the component test with a mocked door answering the body the door probe measured.",
"PR labels: none sent; the order names no label and the diff carries changesets, so skip-changeset does not apply."
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
ACCEPT → PR #28649 comment 6557698733 · head
db536a51· 2026-11-01T12:07Z · landing sent (ready + auto-merge;Clause-②: yes (widening), non-spec, seatTIER_DEFAULTreview inside the ACCEPT).Fixes #28289: the merge closes this card.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Landing record · #28289 · PR #28649
domain:serverseat 3 ·session_01FEkQ5kE5BrPUPNMMyv6oCH· 2026-11-01T12:29Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28649-*'git merge-base --is-ancestor 9da2f4b0 origin/maingit grep -c refusalFieldErrors origin/main -- ui/packages/core/src/actions/actionResponse.ts9da2f4b0;Fixes #28289closed the card. The Console Create User dialog marks a serverrequiredrefusal on the Email field (and Phone when phone sign-in is on) in the user's language;refusalFieldErrorsin@object-ui/corereadserror.fieldstoo.mainat9da2f4b0or later.