Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

approval record page: a row read through the approvals door is re-filtered against /auth/me/permissions, so a non-admin approver sees a blank request page (18.1.0 release blocker, objectstack#25228) #13324

Closed

Description

@objectstack-fleet

Filed by the triage seat (objectstack #6015, session_01AavokzJ5DndAwitDXvKy4U). This is the objectui half of objectstack-ai/objectstack#25228, found by the 18.1.0 pre-release P0 run objectstack-ai/objectstack#25227 that the maintainer asked for before merging the Version Packages PR. ⛔ Not a claim.

Cross-repo rule: "正文抽掉 objectstack 还成立 ⇒ 转仓". The defect and its fix are objectui's. The objectstack card carries only the console pin move after this lands.

What happens (reproduced 8 times by two runners, stock showcase)

A non-admin pending approver opens a request from the account app's Approvals inbox. The page is blank: the heading and a chip, empty Details and Timeline tabs, and no decision actions. The admin sees the full page. The inbox's in-row Approve and Reject still work.

Mechanism (objectui cd14d8eeffe1, read by the runner)

  1. approvalRequestsDataSource.ts (routeApprovalRecordReads) routes the record read to the approvals door, which answers 200 with viewer.can_act: true.
  2. RecordDetailView.tsx folds that row through withoutDeniedFields against /auth/me/permissions (readablePageRecord = readableRow(...)).
  3. MePermissionsProvider.checkField answers false for an object missing from the permissions answer. The persona has no sys_approval_request entry, so every field but id is dropped, viewer and status included.
  4. Every action's visible predicate on record.viewer.can_act fails. Title, highlights and details are empty.

Causal check: granting read on the two objects in the persona's /auth/me/permissions response, in the browser, renders the page fully.

Why the tests stayed green: AppContent.approvalRecordReads.test.tsx stubs MePermissionsProvider, so nothing is stripped.

Regression: #12484 (a91f6bbb1) replaced the side sheet with the served record page. 18.0.0 shipped the side sheet and is unaffected.

Fix direction

objectstack keeps data-door read on sys_approval_request closed to non-admins on purpose: ADR-0103, request-read-gate.ts. So the page must not re-filter a row that the approvals door already filtered.

  • For rows read through that door, skip the readableRow fold and the details / highlights field-security fold.
  • Or have the data source mark such rows as already filtered and honour that mark.
  • ⛔ Not by asking objectstack to grant read on the object.

Pin: render the page under a real MePermissionsProvider with a permissions answer lacking sys_approval_request. The fields, the viewer block and the actions must render for a non-admin approver.

After landing

objectstack-ai/objectstack#25228 moves the console pin past this fix. Then the P0 clause (approvals.account-app-entry acceptance[3]) is re-run.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:workflowApprovals and automation — the work that runs without a person driving it
    and removed on Oct 22, 2026
  2. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: card thread, coordinator-dispatched (18.1.0 release blocker)
    Session: session_016ZEJXR398Mp5xcwuMncpK9
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13324-approval-door-reads
    Worktree: objectui-issue-13324
    Domain: domain:ui
    Seat: none (domain:ui has no open seat post; this project thread was dispatched by the project coordinator on the maintainer's decision recorded on objectstack-ai/objectstack#25228, 2026-10-22 comment "Maintainer decision (2026-10-22): wait for the objectui fix, then move the pin")
    File surface: packages/permissions/src/ (a field-read scope over the enclosing permission answer, with its tests), packages/permissions/README.md, apps/console/src/AppContent.tsx (ApprovalRecordReads) and the console's approval-page tests, content/docs/guide/ (the permissions guide page, if it lists the provider family), .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default judgment tier (dispatch-gates --tier names no path-derived mandate for this surface), effort: xhigh (p1)
    Clause-②: yes
    Responsibility: objectui's record page re-filters a row the approvals door already served against the data door's /auth/me/permissions answer | the approvals routes (GET /approvals/requests/:id and /actions) already apply the request read gate server-side; nothing on the client path honours that | every non-admin approver reaches it from the account app's Approvals inbox today; 18.1.0 would ship it
    Thread-read: none
    Serial constraints cleared: none (open objectui PRs read at claim time: none touches packages/permissions/src/, AppContent.tsx or approvalRequestsDataSource*; the release PR touches only packages/permissions/CHANGELOG.md and package.json)

    Plan, stated so the review can hold it to it: the console already routes a request's findOne and its timeline read to the approvals routes for every record surface below ApprovalRecordReads. The same provider will also scope the client's field-READ gate for sys_approval_request and sys_approval_action to the server that served the rows, so withoutDeniedFields and every checkField(..., 'read') fold stop stripping a row the door already filtered. Write/create verdicts, object verdicts and every other object keep the enclosing /auth/me/permissions answer. ⛔ No change asks objectstack to grant read on either object. Clause-②: yes because the scope is a new export of @object-ui/permissions. The pin renders the served page under a real MePermissionsProvider whose answer omits both objects.

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat correction · domain:ui seat 1 · session_01WJozybDmRH8z2c5SD7vJkj · 2026-10-22T15:00Z · ⛔ not a new claim, ⛔ not a state change.

    The claim 6343187310 reads Seat: none because no domain:ui seat 1 was sitting when it was written. The seat sat afterwards (objectui#5560) and adopts this card: read the claim's seat as domain:ui#1. The claim's session (session_016ZEJXR398Mp5xcwuMncpK9, a card session of the same project) stays the implementer and keeps the assignee. This seat owns the contract-review record of the Clause-②: yes declaration on PR #13326 and the landing (ready, auto-merge, follow to MERGED).

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    LANDED · domain:ui seat 1 · session_01WJozybDmRH8z2c5SD7vJkj · 2026-10-22T17:22Z

    reading value
    PR #13326 merged through the merge queue at 2026-10-22T17:00:36Z, merge commit 0d12e95361b20b1eb9012f97d2d36e5fa7f14374 (re-queued once after a Test-gate ejection; the gate is objectui#13327)
    origin/main content apps/console/src/AppContent.tsx at main mounts <ServerReadScope 1 time(s) (0 at 1aa88519)

    State: the card closed completed on the merge; pm:dispatched comes off in this stroke. The objectstack half (the console pin move and the non-admin re-test, objectstack-ai/objectstack#25228) stays with the card session session_016ZEJXR398Mp5xcwuMncpK9.

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

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions