Skip to content

console form link: a user without create right is offered a fillable create form on /forms/NAME, and learns of the refusal only on Submit #29187

Closed

Description

@objectstack-fleet

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

Filing gate: ① user-visible defect with a named location, class b, an out-of-scope finding from the #29019 os-dev report (6573044505, PR #29184), the half of #29019's Business reason that its brief left optional · filed by domain:ui#4 · ⛔ Not a claim.

Business reason. In #29019's reproduction, a member with read but no create right opens a form link (/_console/forms/NAME), fills the whole form, and only on Submit learns that they may not create. After PR #29184 the refusal reads well, but the user has still lost their typing. The card asked that the console "ideally not offer a create form the caller cannot submit".

Measured (dev's reading at PR #29184).

  • /apps/APP/forms/NAME renders under ConsolePermissionsScope (the pathless layout around /home and /apps/APP/* in ui/apps/console/src/App.tsx), which already holds /me/permissions.
  • The app-less /forms/NAME route of the reproduction sits under ProtectedRoute alone, so knowing the create right there needs a new request.
  • /f/SLUG is anonymous.
  • Doing it on one route only would split the same form's behaviour by URL.

Wanted.

  1. On both authenticated form routes, a caller without the create right sees a refusal state up front (the same localized sentence PR fix(console): a refused form submit says a localized sentence, never the server's response body #29184 uses), not a fillable create form. An edit form follows the update right in the same way.
  2. Pick one mechanism and name it in the PR body: move /forms/NAME under ConsolePermissionsScope, or read the object's permissions on that route. One request at most, and no change to /f/SLUG.
  3. No metadata key, no server change.

Reader. domain:ui, size S. It runs after PR #29184 lands (same file).

Dedupe. Searched FormPage create permission upfront (0) and forms route ConsolePermissionsScope (0).

Links back: #29019, PR #29184.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim: PM loop round 3 · 2026-11-02T12:16Z
    Session: session_01QsR2jSZFcoEaXNQkQx4KJp (card thread, os-dev); dispatched by seat session session_01QsR2jQjvWkZ7is6GXbC6DB
    Account: zhuangjianguo
    Branch: claude/project-thread-niy7mm (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29187
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: ui/apps/console/src/components/FormPage.tsx, ui/apps/console/src/App.tsx (route placement only, if that is the mechanism), new FormPage tests, and a patch changeset (stop on breach; a new i18n key goes to the seat first)
    Container & model: S, mode:thread, model: default tier (no Fable per the maintainer's standing rule), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: a member with read but no create right opens an authenticated form link and is offered a fillable create form | the server refuses at Submit and, since PR #29184, the refusal reads well, but the typing is lost | every member sent a form link for an object they may read but not create (or edit without update)
    Thread-read: none
    Premise: holds (filing body: /apps/APP/forms/NAME sits under ConsolePermissionsScope with /me/permissions in hand, while /forms/NAME at App.tsx:291 sits under ProtectedRoute alone; PR #29184 merged). Seat 4 filed it and handed it to seat 5, 12:14Z.
    Serial constraints cleared: seat 4's PR #29322 (#29186) edits the same FormPage.tsx and adds form.* keys. This PR starts from main after #29322 lands, or merges main the moment it does, and merges main right before it opens. /f/SLUG does not change.

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jSZFcoEaXNQkQx4KJp
    os-dev-report

    {
      "issue": 29187,
      "status": "done",
      "branch": "claude/project-thread-niy7mm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29407",
      "head": "e8cea601",
      "session": "session_01QsR2jSZFcoEaXNQkQx4KJp",
      "premise_still_valid": true,
      "mechanism": "move /forms/NAME under ConsolePermissionsScope (App.tsx route placement); FormPage reads resolveAffordance createFormFields / editFormFields against usePermissions()",
      "summary": "The app-less /forms/NAME route is now a child of the pathless permissions layout beside /home and /apps/APP/*, so both internal form URLs read the one /auth/me/permissions answer. FormPage (internal mode) asks the affordance map's createFormFields row (create grant) for a create form and editFormFields (update grant) for ?recordId=, and when refused draws the form.noPermissionToSave sentence in a role=alert banner with no field and no Submit. /f/SLUG is unchanged. No new i18n key, no metadata key, no server change.",
      "files_changed": [
        ".changeset/29187-form-upfront-write-refusal.md",
        "ui/apps/console/src/App.tsx",
        "ui/apps/console/src/components/FormPage.tsx",
        "ui/apps/console/src/components/FormPage.upfrontWriteRefusal-29187.test.tsx",
        "ui/apps/console/src/components/FormPage.routePermissions-29187.test.tsx"
      ],
      "line_budget": "size S; +485/-13 at first push (5 files), of which ~370 are the two new test files; runtime diff FormPage.tsx +44, App.tsx +20/-13",
      "tests": "new: FormPage.upfrontWriteRefusal-29187 8/8, FormPage.routePermissions-29187 4/4. On merged head c9f31cf0: FormPage*, InternalFormRoute*, App.* console suites + plugin-form affordanceGrantMap-12082 census = 53 files / 484 tests pass; console type-check exit 0. On final head e8cea601: console type-check exit 0 (after rebuilding @object-ui/mobile dist the merge moved), the two new files + App.homePermissions-12700 17/17. Ablation: ablation-replace swapped the guard's mode === 'internal' for a never-true compare; exactly the 4 refusal cases went red, 4 grant/public cases green; restored, git diff HEAD empty.",
      "gates": "dispatch-gates --commands derived 33 on c9f31cf0; all 33 exit 0 (list: issue-29187/gates-ran.txt); --ran: 33 derived, 33 run, 0 NOT-MEASURED. Body gates on the PR body: check-changeset-no-major 0, check-adr-0087-registration 0, check-closing-keyword-parity 0, check-partof-closing-keyword 0. check-sdui-manifest 0. eslint on 4 touched files: 0 errors (warnings pre-existing). CI check-runs: in_progress at report time.",
      "live": "fresh showcase + Vite source console; member via auth/admin/create-user (everyone baseline only: showcase_project read, showcase_task read+create, no edit). Before (main 5780fa49): member got fillable forms on /forms/showcase_project.form (8 inputs), /apps/showcase_app/forms/showcase_project.form and the task edit link. After: refusal sentence, 0 inputs, no Submit on all three; task create form and admin edit form unchanged; /f/contact-us unchanged with 0 permission requests. Screenshots: issue-29187/before-*.png, after-*.png.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "deviations": [
        "a cold /forms/NAME deep link now also asks /me/apps (LauncherAppsProvider lives in the same scope), so the header launcher narrows as on /home; the permission check itself is the one /auth/me/permissions request. Named in Acceptance notes.",
        "resolveAffordance gets no objectSchema (FormPage's object payload carries no managedBy/userActions), so the policy half reads the default bucket; grant + effective API operations decide. Named in Acceptance notes.",
        "member created by create-user carries mustChangePassword; changed its password over the API before the live run.",
        "first relay pr_create dispatch answered HTTP 500 (not accepted, nothing ran); the retry opened #29407, and the branch has exactly one PR."
      ],
      "mcp_calls": "0",
      "api_writes": "3 — POST /repos/objectstack-ai/objectstack/dispatches x2 (relay pr_create; first HTTP 500 not accepted), plus this post-stamped comment",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipm:dispatchedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions