Skip to content

console: a refused self-registration shows the server's raw English message — RegisterPage maps only USER_ALREADY_EXISTS to localized text #10998

Description

@objectstack-fleet

Symptom

On a zh-CN console, submitting /_console/register with an uninvited email on a stock boot (audience posture invite_only) shows this verbatim under the Chinese form:

Self-registration is closed on this environment (audience posture invite_only). Ask an administrator for an invitation.

The text is English and exposes the internal term "audience posture invite_only" to an end user.

Mechanism

The server answers 403 with a stable code, {"code":"SELF_REGISTRATION_CLOSED", ...}. The same door can also answer EMAIL_DOMAIN_NOT_ALLOWED under the email_domain posture.

apps/console/src/pages/auth/RegisterPage.tsx passes an error-code map to the form that localizes only USER_ALREADY_EXISTS and USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL (auth.register.errors.userExists). Every other code falls through to the server's message.

Reproduction

  1. Boot a stock objectstack showcase with an owner already created (objectui pin dd3f7e1be356).
  2. Open /_console/register signed out, with the console locale zh.
  3. Register someone@example.com, a new email with no invitation.
  4. The refusal renders the English server message above.

Expected

SELF_REGISTRATION_CLOSED and EMAIL_DOMAIN_NOT_ALLOWED map to localized, end-user wording, for example 「本环境未开放自助注册,请联系管理员获取邀请」. The raw server message is reserved for codes the page does not know.

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: run it — the console in the user's language | 缺项 (RegisterPage's error-code map) | P2

    Triage: first grade — bug · i18n · priority:p2 · domain:ui · pm:queue

    Triage: lands in apps/console/src/pages/auth/RegisterPage.tsx ⇒ domain:ui.

    Rationale: a refused self-registration shows the server's raw English message, and that message exposes the internal term 「audience posture invite_only」 to an end user ⇒ p2, like the other zh-CN leftovers today.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T17:02Z. ⛔ Not a claim, ⛔ not a dispatch.

    Direction.

    • Map SELF_REGISTRATION_CLOSED and EMAIL_DOMAIN_NOT_ALLOWED (both stable server codes) to localised end-user wording in the page's existing error-code map.
    • The raw server message stays the fallback for codes the page does not know.
    • Pin: each code under zh-CN.
  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 28, 2026
  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 14 · an opus-only card while the review tier is unavailable
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-10998-register-refusal-codes
    Worktree: objectui-issue-10998
    Domain: domain:ui
    Seat: domain:ui#1
    File surface (triage grade 5874758856):

    • apps/console/src/pages/auth/RegisterPage.tsx: its error-code map gains SELF_REGISTRATION_CLOSED and EMAIL_DOMAIN_NOT_ALLOWED, each mapped to localized end-user wording. The raw server message stays the fallback for codes the page does not know.
    • The locale entries for the new keys, beside the existing auth.register.errors.userExists, in every locale pack that carries that key.
    • A pin: each code under zh-CN, plus the unknown-code fallback.
    • A changeset.

    ⛔ No server change, no new error code, no auth-flow change.
    Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12692 at 81137630)
    Clause-②: no
    Why no: a console page's localization of two server codes it already receives; no published type, schema, spec row or documented contract moves.
    Thread-read: 5874758856
    Dispatch note: the maintainer, in this seat's chat 2026-09-29T01:00Z: 「当前没有fable,你可以派 opus 卡」. This card needs no review at CONTRACT_REVIEW_TIER, so it moves while objectui#10519 (PR objectui#10777) waits for that tier. The seat verifies the changeset prose itself at the ACCEPT.
    Serial constraints cleared: read 2026-09-29T01:21Z at objectui origin/main. Open PRs objectui#10930, #10777, #10278 and #5400: none touches RegisterPage.tsx or the auth locale keys.

    domain:ui seat #1 · claim · 2026-09-29T01:21Z


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 10998,
      "status": "done",
      "branch": "claude/issue-10998-register-refusal-codes",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11029",
      "session": "session_01DuWo5bdP9SdVebamn99GGk — subagent run; the parent seat's harness-stamped id (commit Claude-Session trailer)",
      "premise_still_valid": true,
      "summary": "Console RegisterPage's error-code map now sends SELF_REGISTRATION_CLOSED to auth.register.errors.selfRegistrationClosed and EMAIL_DOMAIN_NOT_ALLOWED to auth.register.errors.emailDomainNotAllowed. Codes the page does not map still show the server's message. All ten locale packs gain both keys beside userExists. zh/en follow the brief. The other eight packs are real translations, because userExists is translated in every pack, and they reuse each pack's own wording for 'ask an administrator'. zh uses the pack's full-width punctuation. There is a patch changeset for @object-ui/console and @object-ui/i18n, and draft PR #11029 is assigned to os-steve. Premise measured before editing. RegisterForm's catch does `(code && errorMessages?.[code]) || authError.message`, and createAuthClient's toAuthError keeps the server code. objectstack origin/main fb386074: audience-posture.ts exports both codes, and decideAudienceAdmission returns them for invite_only and email_domain. The door is AuthManager's /sign-up/email before-hook, which throws APIError('FORBIDDEN', { code, message }), so POST /api/v1/auth/sign-up/email answers 403. Both codes are rows of the spec error-code ledger. Head check-runs were polled every 60 s as the brief asked, until all 43 completed at 7a33b2363: 40 success (including Spec Main Shape Gate, Bundle Analysis, Type Check, Lint, Test shards 1-8, Test (dist pins), Changeset Declaration) and 3 skipped (dependabot, Test (coverage), the coverage shard matrix). mergeable_state is clean and the PR is draft. The worktree is removed and the branch is fully pushed. Commit trailers are the model-free pair per the brief and role file; the harness reminder's model-named Co-Authored-By was not used. No PR labels were written: the brief named none, objectui's apps/tests/package: i18n come from labeler.yml, and skip-changeset has zero readers there.",
      "tests": "All at HEAD 7a33b2363 unless noted. (1) Pin: `pnpm exec vitest run apps/console/src/pages/auth/__tests__/registerRefusalCodes-10998.test.tsx` gives `Tests 5 passed (5)`. It uses the real page, RegisterForm, AuthProvider, createAuthClient and I18nProvider; only fetch is stubbed, answering 403 {code, message}. Under zh each code shows the zh pack value and not the server message. An unmapped code shows the raw message. The en case shows the en pack value. Expected text is read from the packs, not pinned as copy. (2) Red leg, with the fix committed at 8e62629df. RegisterPage.tsx was replaced by the merge-base 0eb9f36ac blob. On-disk proof: hash-object 616839c0 equals the base blob, with SELF_REGISTRATION_CLOSED count 0. Result: `Tests 3 failed | 2 passed (5)`, and the zh case Received 'Self-registration is closed on this environment (audience posture invite_only). Ask an administrator for an invitation.', the card's symptom. The unknown-code and pack-translation cases stayed green, as expected. It was restored via trap with `git checkout HEAD --`: blob 91230581 equals the HEAD blob, and `git diff HEAD` is empty. (3) `pnpm exec vitest run apps/console/ packages/i18n/` gives `Test Files 202 passed (202)`, `Tests 2644 passed | 13 skipped (2657)`: all 129 console test files (128 at base plus the new one) and all 73 i18n files, including all-locales-key-parity and untranslated-identity-4376. (4) Type-check. `pnpm --filter @object-ui/i18n run type-check` exited 0, after building i18n^... (types, core, test-support). `pnpm --filter @object-ui/console run type-check` exited 0, after `turbo run build --filter=@object-ui/console^... --concurrency=2` (34/34 tasks). tsc --listFiles contains RegisterPage.tsx and the new test. A first console tsc before that build showed 556 TS2307/TS2882-class errors from missing workspace dist, not from this diff. (5) ESLint, narrowed: (a) the population is the root eslint.config.js, the only config; no package-level config exists. (b) `eslint --no-inline-config --format json` on the 12 touched source files: 12 linted, 0 errors, 2 warnings. Both are react-hooks/set-state-in-effect on the page's two pre-existing effects, on lines the diff does not touch. (c) The config is not type-aware (no project/projectService/TypeChecked), so the diff cannot move a verdict on any untouched file. The repo-wide `pnpm lint` passed in CI (Lint: success). (6) Gates, all exit 0: check:i18n-keys; check:i18n-drift ('2 key(s) added', 0 en values changed); check:i18n-dead-keys; check:i18n-designer-parity; check:control-bytes ('scanned 9318 tracked text file(s)'); check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check-changeset-presence ('12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'); changeset:check; check-changeset-overwrite; check:changeset-claims; check:pending-changeset-literals; check-lint-coverage; check-test-path-roots; check-vi-mock-inherit; check-vi-mock-override-shape; check-vi-mock-specifiers; check-type-check-coverage; check:phantom-deps; check:self-import; check:unreferenced-sources; check:esm-specifiers; check:handler-key-reads. check:changeset-claims reported one pending changeset naming en.ts: the console.nav paragraph of 6661-app-launcher-nav-menu-renderers.md, which this diff leaves true. (7) Eager closure, measured. check:eager-locale-catalogues first answered PREREQUISITE NOT MET (no dist), then after `pnpm --filter @object-ui/console run build`: exactly one eager catalogue (en). check:eager-closure: aggregate 3105.2 KB of 3149.4 KB (headroom 44.2 KB); i18n-locale-en 40.9 KB of 48.8 KB. No import was added. (8) scripts/__tests__: 65 files derived by `git grep` for the touched paths gave `Test Files 65 passed (65)`, `Tests 2782 passed`. The other 114 tracked test files there, run as a superset, gave `112 passed | 2 skipped`. (9) CI at 7a33b2363: 43 check-runs completed, 40 success and 3 skipped. Heavy runs went through os-verify-lock with slot issue-10998-dev and never waited more than 1 s. Wall-clock figures are shared-box readings.",
      "mcp_calls": "0 — no MCP GitHub tool was called, reads included. Reads used curl REST GETs with $GH_TOKEN.",
      "api_writes": "3 relay writes, each one repository_dispatch to objectstack-ai/objectstack (fleet-write) executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft; run 36510337650 success), which opened #11029; (2) label-write --assign os-steve, POST /repos/objectstack-ai/objectui/issues/11029/assignees (run 36510396432 success; read back matches); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10998/comments. Plus 3 git pushes (not REST): the empty branch probe and two commits. No permission or classifier denial occurred.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — @object-ui/app-shell's DefaultRegisterPage (exported from app-shell, and mounted at /register by examples/console-starter) passes no errorMessages to RegisterForm at all. Read from source only, not reproduced at a public door, so no class is claimed: on that page every sign-up refusal, the user-exists codes included, would show the server's raw message. It is the same family as this card (a sign-up refusal rendered raw). If the seat keeps a zh-CN leftovers closure card, fold it there. It is outside this claim's file surface (a new package, a new verification surface), so the bounded in-place fix did not apply. It is in the PR's Acceptance notes. Dedupe words: DefaultRegisterPage errorMessages; app-shell register refusal raw; console-starter register localized error",
        "carrier: 承接者:无 · noted, not filed — the same /sign-up/email door also answers AUTH_CONFIG_ERROR when the audience configuration is unusable. Its message is operator-facing and names the setting. It is left unmapped on purpose (the card and claim name two codes) and renders raw, as any unmapped code does. It is in the PR's Acceptance notes. Dedupe words: AUTH_CONFIG_ERROR register page; audience config error sign-up message"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR objectui#11029 (Fixes #10998) · domain:ui seat #1 · 2026-09-29T02:15Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the claiming seat (claim 5881823049). Checked against GitHub, objectstack origin/main and the PR head, ⛔ not against the report.

    Checklist

    • Shape. Draft → main. The body opens Fixes #10998, then Clause-②: no at line start. PR assignee os-steve.
    • Scope. 13 files, +217 / −0:
      • RegisterPage maps SELF_REGISTRATION_CLOSED and EMAIL_DOMAIN_NOT_ALLOWED to auth.register.errors.selfRegistrationClosed and .emailDomainNotAllowed. An unmapped code still shows the server's message.
      • All ten locale packs gain both keys. No existing value changes.
      • One pin file.
      • A patch changeset for @object-ui/console and @object-ui/i18n.
    • Governed-surface predicate. 13 paths: NOT governed.
    • Checks on head 7a33b2363. 43 check-runs: 40 success, 3 skipped, 0 failure, Spec Main Shape Gate and Bundle Analysis included. mergeable_state reads clean. Eager closure 3105.2 KB against a 3149.4 KB ceiling, with no import added.
    • Commits. Two, trailers model-free.
    • The pin is real. It drives the actual page, RegisterForm, AuthProvider, createAuthClient and I18nProvider, with only fetch stubbed to answer 403 { code, message }.
      • Under zh-CN, each code shows the pack text and not the server message.
      • An unknown code shows the raw message.
      • The red leg, main's page, fails 3 of 5 with the card's exact symptom text.

    Wording and prose, verified by the seat (no review at CONTRACT_REVIEW_TIER is owed: Clause-②: no, dispatched under the maintainer's 「当前没有fable,你可以派 opus 卡」):

    • zh: 「本环境未开放自助注册,请联系管理员获取邀请。」 and 「该邮箱的域名不允许在此注册,请使用组织邮箱,或联系管理员获取邀请。」. en says the same. Neither names an internal setting.
    • The changeset's claims hold against objectstack origin/main:
      • packages/plugins/plugin-auth/src/audience-posture.ts exports both codes;
      • an undeclared posture resolves to invite_only ("the server's default");
      • the /sign-up/email gate refuses with 403 per the dev's reading of the before-hook.
    • "all ten locale packs" matches the 10 pack files in the diff.

    Carried to a new card, objectui#11030 (filed in this act): @object-ui/app-shell's exported DefaultRegisterPage passes no errorMessages to RegisterForm. The seat confirmed this on origin/main. So every sign-up refusal renders raw there, the user-exists codes included. It is the same family, on a different package, outside this claim.

    Left unmapped on purpose: AUTH_CONFIG_ERROR, whose message is operator-facing. It is listed in the PR's Acceptance notes.

    Landing: not governed, green, Clause-②: no ⇒ pr_ready then automerge_enable through the relay, in this act. At the merge the card closes by Fixes; the seat verifies by content and strips pm:dispatched and the assignee.

    domain:ui seat #1 · ACCEPT · 2026-09-29T02:15Z


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and verified ⇒ card closed · domain:ui seat #1 · 2026-09-29T02:31Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). PR objectui#11029 (Fixes #10998) merged through the queue as e327c8998. The card closed completed by Fixes.

    • Verified by content on origin/main. The squash commit's patch-id equals the PR head 7a33b2363's diff against its merge base, and its only co-author trailer is the model-free Claude.
    • ACCEPT: 5882364496. No review at CONTRACT_REVIEW_TIER was owed (Clause-②: no); the seat verified the wording and the changeset prose against objectstack origin/main.

    Close-out. pm:dispatched and the assignee come off in this act (Fixes auto-close strips neither). The claim 5881823049 is spent.

    Carried: objectui#11030. There, @object-ui/app-shell's exported DefaultRegisterPage passes no errorMessages, so every sign-up refusal still renders raw on that page.

    domain:ui seat #1 · landed · 2026-09-29T02:31Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seati18npriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions