Repository navigation
Description
domain:server#1 · os-elon-musk · session_016WV4czVnUdQQCVKSW5J2uk
Filing gate: seat ruling on #27857 PR 2 (the dialog half was dropped from that PR because it trips the feature-gate registry guard). Filed by domain:server seat 1. ⛔ Not a claim. No grade: triage sets the lane and grade.
Reported by the #27857 card thread (session_016WV4cwXFwjU2YZW2dRuZZt), measured on objectstack main 8ec00cf (on 10-30). Not built: seat ruling B dropped it from PR 2.
What the administrator sees
Setup → Users → Create User shows Email as an optional field, with no required mark. With phone sign-in off (the default), Email is in fact required: the endpoint refuses an empty email with 400 INVALID_REQUEST "Email is required" (wording from PR 2). With phone sign-in on, an email or a phone number is required.
Why it is not a one-line fix
sys_user.create_user(packages/platform-objects/src/identity/sys-user.object.ts) declares{ field: 'email', required: false }. A param'srequiredis a static boolean (ActionSchemaparams,packages/spec/src/ui/action.zod.ts). No required-when predicate exists.- The measured workaround is two params on the one field:
{ field: 'email', required: true, visible: 'features.phoneNumber != true' }and{ field: 'email', required: false, requiresFeature: 'phoneNumber' }.ObjectSchema.safeParseaccepts it.- The console's
ActionParamDialogcollects and submits only visible params, so exactly one Email field shows.getPublicConfigalways servesfeatures.phoneNumberas a boolean, so neither gate faults. - The pins, and the ablation (required made optional → 3 failed / 1 passed), were green.
- It trips
packages/platform-objects/src/feature-gate-guard.test.ts(feat(spec,auth): guard that every feature-gated capability is UI-gated (generalize the create-user phone fix) #2874):- Reverse guard. Every
features.*reference in an action or paramvisiblemust be booked inPUBLIC_AUTH_FEATURES[flag].gatedInputs(packages/spec/src/kernel/public-auth-features.ts, a spec-lane file). Both params referencefeatures.phoneNumberand resolve to the same path,sys_user.actions.create_user.params.email. Result: 2 failed. - Forward guard. It resolves a path to the FIRST param with that name or field, and wants the flag's canonical gate as a conjunct (
features.phoneNumber == true, opt-in). The required param carries the negation, which the registry does not model.
- Reverse guard. Every
Options for whoever takes it
- Book
sys_user.actions.create_user.params.emailinphoneNumber.gatedInputsand put the optional, positive-gated param first. This is mechanical: one spec line plus the object edit. The cost is that one booked path stands for two params, one of them negatively gated, which the guard was not written for. - Model it: a required-when predicate on action params (spec + console), so one Email param says "required unless phone sign-in is on". This is a contract widening (
Clause-②: yes). - Teach the registry a negated booking (for example
hiddenWhenOnInputs) and the guard to read it.
Dedupe words: create_user email required, action param required when, feature gate negated booking, gatedInputs phoneNumber email.
Activity
objectstack-fleet commentedon Oct 31, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 能用的表单 | none | P②
Triage: p3 ·
domain:server·area:identity· 2026-10-31T02:55Z⛔ Not a claim.
phoneNumber.gatedInputsand put the positive-gated param first. That is one spec line, under a one-PR exception forpublic-auth-features.ts, and the PR states the one-path, two-param booking in the guard's header.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-01T13:29Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5nn2N4syy7fVBUEXffAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-5fg7d3(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 队列还是很多任务,再开一个席位吧」. Triage p3 with lean option 1 and a one-PR exception for
public-auth-features.ts; Leehom 2026-11-01T12:11Z: 「定级你可以自己决定」.File surface:
packages/platform-objects/src/identity/sys-user.object.ts(two Email params oncreate_user), one line inpackages/spec/src/kernel/public-auth-features.ts(phoneNumber.gatedInputs, under triage's one-PR exception) with the guard's header note,packages/platform-objectsguard/test fixtures, the app-shell Create User dialog test, and changesets. ⛔ No required-when predicate, ⛔ no registry model change, ⛔ noui/**source change unless a test proves it is needed.Container & model:
S,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: default (the coordinator sets effort only from the maintainer's words)Clause-②: no — expected: an action param is split into two declared params with no key, route or shape change; the dev measures and declares the final value. The
packages/specpath owes a contract review atCONTRACT_REVIEW_TIERbefore landing.Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the
sys_user.create_useraction declaration and the feature-gate registry · the platform path is Setup → Users → Create User rendering its params · reached by every administrator creating a user with phone sign-in off (the default), who sees Email as optional although the endpoint requires itThread-read: 6538087552
Premise: holds as filed (measured on
8ec00cf0); PR #28649 (#28289, merged9da2f4b0) marks the server refusal after submit but not the required mark before it; the dev re-measures firstSerial constraints cleared: none: seat 1 agreed (13:27Z); no open PR on
sys-user.object.tscreate_user orpublic-auth-features.tsfound at this read; the dev re-reads overlap at start and beforepr_create.Resume-from: none — fresh from
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28280.md(project folder).objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5nn2N4syy7fVBUEXff
os-dev-report
{
"issue": 28280,
"status": "done",
"branch": "claude/project-thread-5fg7d3",
"pr": "#28707",
"session": "session_01FEkQ5nn2N4syy7fVBUEXff",
"premise_still_valid": true,
"summary": "Option 1 as ruled. sys_user.create_user now declares two params on the one email field: first { field: 'email', required: false, requiresFeature: 'phoneNumber' } (lowers to the canonical features.phoneNumber == true), then { field: 'email', required: true, visible: 'has(features.phoneNumber) && features.phoneNumber != true' }. One spec line books sys_user.actions.create_user.params.email in PUBLIC_AUTH_FEATURES.phoneNumber.gatedInputs, and the feature-gate guard header states the one-path, two-param booking; guard logic untouched, both guards green. With phone sign-in off the dialog shows one required Email and refuses a blank one before any request; with it on, one optional Email beside Phone Number. Premise re-measured on origin/main 49a6a5c: the declaration was still { field: 'email', required: false } (red-first 2 failed / 2 passed). Card assignee not touched.",
"tests": "Red first at origin/main 49a6a5c + test only: platform-objects src/identity/sys-user-create-user-email-required.test.ts 2 failed / 2 passed (VERDICT command-exit 1). Head 2310b30: that file + src/feature-gate-guard.test.ts 116/116 (forward row create_user.params.email gated on phoneNumber, reverse rows x3 booked); pnpm --filter @objectstack/platform-objects test 107 files / 1496 tests green. ui: useConsoleActionRuntime.createUserEmailRequiredMark-28280.test.tsx 4/4 plus #28649's createUserRequiredField-28289 6/6 (Test Files 2 passed, Tests 10 passed). Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty: A1 required param made optional -> 1 failed / 115 passed; A2 has() term dropped -> 1 failed / 114 passed (the no-served-flag case shows two Email inputs); A3 params swapped -> 2 failed (pin + guard forward row); U1 ui mirror optional -> 1 failed / 3 passed; U2 ui mirror bare negation -> 1 failed / 3 passed. No rebuild needed: the pin imports the object from src/, ui vitest aliases @object-ui/* to src/.",
"gates": "Local at head 2310b30, derived by dispatch-gates --commands --repo objectstack-ai/objectstack (85 families), exit captured before any pipe; full list in the PR Verification block and /mnt/project-files/pm-orders/server/28280-gates-ran.txt. 84 exit 0; check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package dist) = NOT MEASURED. Re-runs: check-plugin-teardown-shape --self-test first exit 3 (shallow), exit 0 after fetching the fixture commit; pnpm --dir ui exec vitest run scripts/ first exit 1 (check-hand-rolled-comment-mask 15s timeout on a contended box, not in this diff), file alone 22/22 then whole run exit 0 (169 files); check:i18n first exit 3 (cli closure unbuilt), exit 0 after the build. dispatch-gates --ran: 85 derived, 84 run, 1 NOT-MEASURED, 0 UNRUN. Package: platform-objects test + typecheck exit 0; spec test + typecheck exit 0; ui/packages/app-shell type-check exit 0; eslint --no-inline-config on packages/platform-objects, packages/spec/src/kernel and the new ui test exit 0 (0 errors, 27 warnings of the #28289 test's shapes). CI check-runs: in_progress at report time (PR just opened).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"line_budget": "323 changed lines (+320 / -3) across 6 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/28280-create-user-email-required-mark.md",
"packages/platform-objects/src/feature-gate-guard.test.ts",
"packages/platform-objects/src/identity/sys-user-create-user-email-required.test.ts",
"packages/platform-objects/src/identity/sys-user.object.ts",
"packages/spec/src/kernel/public-auth-features.ts",
"ui/packages/app-shell/src/hooks/tests/useConsoleActionRuntime.createUserEmailRequiredMark-28280.test.tsx"
],
"deviations": [
"The required param's gate is has(features.phoneNumber) && features.phoneNumber != true, not the order's bare features.phoneNumber != true. Measured (A2, U2): the console's features is {} until /auth/config answers (AppContent useState({})) and stays {} if it fails; both bare gates then fault, filterVisibleParams shows a faulting param, and two Email inputs render. The has() term keeps the order's own hypothesis (exactly one Email field) true in that state; precedent spelling sys-user.page.ts TWO_FACTOR_OFFERED. The first param keeps the canonical gate; still option 1, no predicate, no registry change.",
"The reverse guard red without the booking is cited from the card (2 failed), not re-run (it needs two spec rebuilds).",
"Commit trailers are model-free (Co-Authored-By: Claude) per the order; the harness reminder named a model trailer and was not followed."
],
"Clause-②": "no — measured: two declared params on one existing field, no key, route, schema or response shape change; one value appended to a registry constant",
"Metadata-protocol": "none — no save/publish door refuses or accepts anything new",
"changeset": "@objectstack/platform-objects patch + @objectstack/spec patch (both ship dist; spec dist kernel chunk and .d.ts carry the new path)",
"mcp_calls": "4 — mcp__github__issue_read x3 (get, get_comments x2), mcp__github__pull_request_read x1 (get, PR body read-back); no write tool",
"api_writes": "5 — git push x3 (branch claude/project-thread-5fg7d3), fleet-write pr_create x1 (POST /repos/objectstack-ai/objectstack/dispatches -> PR #28707, read-back 13788 bytes identical, no assignees, no labels), post-stamped os-dev-report comment x1 on #28280",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5nn2N4syy7fVBUEXff
os-dev-report
{
"issue": 28280,
"status": "done",
"round": "rework 2 (contract review FAIL on 2310b30)",
"branch": "claude/project-thread-5fg7d3",
"pr": "#28707",
"head": "9684fb3b",
"session": "session_01FEkQ5nn2N4syy7fVBUEXff",
"premise_still_valid": true,
"summary": "Seat ruling (2) applied. Census first: an AST scan of every params / actionParams array literal in objectstack (430 arrays, ui/ and examples/ included), hotcrm origin/main (7) and the 2 JSON params arrays finds no name declared twice except create_user's email, so nothing relied on last-wins. validateActionParams (packages/spec/src/ui/action-params.zod.ts) now merges same-name declarations: required only when every declaration is, value shape from the first, unknown-key handling unchanged. A runtime test drives the real REST action route with the real SysUser: phone-only reaches the endpoint; a blank email passes the door and the ENDPOINT's 400 VALIDATION_FAILED 'Email is required' is relayed (the door does not refuse it); an email is forwarded; a non-string email is still refused at the door. The MCP bridge is not driven: create_user has no ai.exposed, so run_action refuses it as a system-object action before any param check (measured). Should-fixes done: public-auth-features.ts row notes and both docblocks; revised PR body (declaration lines, 维护者速读 says Email shows unmarked before /auth/config answers, as on main) is at /mnt/project-files/pm-orders/server/28280-pr-body-v2.md for the seat to write.",
"tests": "Red first (action-params.zod.ts at 2310b30, spec rebuilt): REST door test phone-only and blank-email failed with 400 VALIDATION_ERROR 'Invalid action params: Action param "email" is required' (six-case draft 3 failed / 3 passed; the third was an MCP case failing on the exposure refusal, dropped); spec cases 2 failed / 33 passed. Green at head: door test 5/5, spec action-params.test.ts 35/35. Merge ablated via scripts/ablation-replace.mjs (declared.set(p.name, p)), spec rebuilt, dist hits of the merge 0: door test 2 failed / 3 passed, spec 2 failed / 33 passed; restored to the HEAD blob (git diff HEAD empty), rebuilt, dist hits 1: 5/5 and 35/35. Round-1 pins unchanged and green (platform-objects 107 files / 1496 tests).",
"gates": "Local at head 9684fb3, dispatch-gates --commands (87 families, adds check:error-code-casing and check:route-envelope): 86 exit 0; check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET = NOT MEASURED. Re-run: pnpm --dir ui exec vitest run scripts/ first exit 1 (unit-registry-absence-collision could not import app-shell modules while @objectstack/client dist was being built in the fresh worktree), exit 0 (169 files) after. dispatch-gates --ran: 87 derived, 86 run, 1 NOT-MEASURED, 0 UNRUN. Package: spec test (783 files, 23175 tests) + typecheck exit 0; runtime test (505 files, 8191 tests) + typecheck exit 0; platform-objects test exit 0; eslint --no-inline-config on touched paths exit 0. CI: Check Changeset red until the PR body carries the new declaration lines (the seat writes the body); the rest in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"line_budget": "round 2: 5 files changed, +206 / -9 against 2310b30 (action-params.zod.ts, action-params.test.ts, public-auth-features.ts, the new runtime test, the changeset); PR total 8 files, well under 3000",
"files_changed": [
".changeset/28280-create-user-email-required-mark.md",
"packages/platform-objects/src/feature-gate-guard.test.ts",
"packages/platform-objects/src/identity/sys-user-create-user-email-required.test.ts",
"packages/platform-objects/src/identity/sys-user.object.ts",
"packages/runtime/src/domains/api-action-create-user-email-param.test.ts",
"packages/spec/src/kernel/public-auth-features.ts",
"packages/spec/src/ui/action-params.test.ts",
"packages/spec/src/ui/action-params.zod.ts",
"ui/packages/app-shell/src/hooks/tests/useConsoleActionRuntime.createUserEmailRequiredMark-28280.test.tsx"
],
"deviations": [
"The door-level test drives the REST door only; the MCP door cannot reach create_user (no ai.exposed, system-object exposure refusal first, measured). Both doors call the same enforceActionParams.",
"The PR body is not patched by the dev (os-dev rule); the full revised body is at /mnt/project-files/pm-orders/server/28280-pr-body-v2.md.",
"Round-1 deviation stands: the required param's gate is has(features.phoneNumber) && features.phoneNumber != true."
],
"Clause-②": "yes (widening) — a twice-declared param name is no longer required at the doors unless every declaration is; agreed by the seat 15:11Z",
"Metadata-protocol": "semantic — the meaning of params[].required on a twice-declared name changes at the doors; agreed by the seat",
"changeset": "@objectstack/spec minor + @objectstack/platform-objects patch",
"mcp_calls": "4 (round 1, reads only); round 2: 0",
"api_writes": "round 2: git push x2 (96a7c56, 9684fb3) + this os-dev-report comment = 3; cumulative 8 (5 git push, 1 pr_create, 2 report comments)",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim amendment to
6558617122· 2026-11-01T15:51Z · round-2 file surface and contract face, declaredclaude/project-thread-*card thread's round 2 on PR fix(platform-objects): Setup Create User marks Email required while phone sign-in is off #28707 (os-dev-report6560214983):packages/spec/src/ui/action-params.zod.ts(validateActionParamsmerges same-name declarations, so a name is required only when every declaration of it is required),packages/spec/src/ui/action-params.test.ts, andpackages/runtime/src/domains/api-action-create-user-email-param.test.ts(the REST/actionsdoor test). This answers the contract review's must-fix (6559443163): the door's last-wins fold made the twice-declaredemailrequired unconditionally.Clause-②: yes (widening),Metadata-protocol: semantic, specminor(a twice-declared param'srequiredchanges meaning at the doors). Census by the dev: no other params array in objectstack (430), hotcrm (7) or JSON (2) repeats a name.CONTRACT_REVIEW_TIERowed on the new head before landing (spec path), as a delta on6559443163.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
ACCEPT → PR #28707 comment 6560321699 · head
9684fb3b· 2026-11-01T15:59Z · landing sent (ready + auto-merge;Clause-②: yes (widening),Metadata-protocol: semantic,CONTRACT_REVIEW_TIERbase FAIL 6559443163 → delta PASS 6560309650).Fixes #28280, so the merge closes this card.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Landing record · #28280 · PR #28707
domain:serverseat 3 ·session_01FEkQ5kE5BrPUPNMMyv6oCH· 2026-11-01T16:22Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28707-*'git merge-base --is-ancestor 8bceb6a2 origin/maingit grep -n "required: Boolean(first.required" origin/main -- packages/spec/src/ui/action-params.zod.ts8bceb6a2;Fixes #28280closed the card. Create user marks Email required before submit while phone sign-in is off (the default), and leaves it optional beside Phone Number when it is on. The action-param door folds a twice-declared name as required only when every declaration is, so a phone-only create reaches the endpoint, and a blank email still gets the endpoint's own 400 "Email is required".CONTRACT_REVIEW_TIER): base FAIL6559443163on2310b302(the door's last-wins fold narrowed its accept set) → delta PASS6560309650on the landed head9684fb3b. ACCEPT6560321699.pm:queue):ActionSchema.paramsstates the same-name rule in its description.mainat8bceb6a2or later.