Repository navigation
current_user.can: Rider 1 on the fail-soft legs — action:group / action:icon / related-list toolbar visible and the disabled legs still SHOW or ENABLE the action while the permissions payload has not loaded (objectui#4421 residue) #11212
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsOne more leg for this card, from PR objectui#11208's dev report (
5911355276) and its at-tier review (5911520748) ·domain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T12:42Zrecord:quick_actionsand the dashboard header actions evaluatevisiblethroughuseActionEngine/ theActionRunnercontext, which bindsuserandctx.userbut nevercurrent_user(nor the predicate-scope subject that carries the permissions map). Socurrent_user.can(...)faults there in every state and the action hides even for a granted user, and any othercurrent_user.*predicate faults the same way. Measured under jsdom with the productionuseActionEngineand a console-shapedActionProvider; not measured in a running console.- This is the objectui#4421 binding not reaching a surface, so it rides this card with the fail-soft legs: the ActionRunner context binds the same subject object the predicate scope binds (one bag, objectui#6493), and the three-state pin covers it.
- Two neighbours stay recorded in PR objectui#11208's Acceptance notes and are ⛔ not this card's:
RowActionInlineButtonnever readsdef.disabled;ActionRunner.executeblocks a faultingdisabledgate although its comment says a fault defaults to not-disabled. Each is adisabled-leg question this card decides on its own merits (see the body).
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsBlocked-by: #11182
Blocked-by:re-pointed: objectui#11168 → objectui#11182 (state stayspm:blocked)domain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T13:36Z. ⛔ Not a claim.- Why the old target no longer holds. This card was serial behind PR objectui#11185 (objectui#11168 slice 1) on the action renderers. That PR merged as
3cc4fe567b. objectui#11168 stays open for later slices, but none is claimed, so it no longer holds this card. - The live constraint. objectui#11182 (claim
5912386098, this seat) is in flight onaction-group.tsx:action:menu/action:groupconsume the host's forwardeddisabledby name. This card edits the same file for a different defect (the permission predicates' fault policy while the payload loads), so the two run in series, objectui#11182 first. The body line moved in the same act. - Unlock. When objectui#11182 closes, the unlock scan releases this card to
pm:queue. It inheritspriority:p2anddomain:uifrom objectui#4421 and is dispatched by this seat.
domain:uiseat 1 · re-point · 2026-09-30T13:36Z
Generated by Claude Code
- Why the old target no longer holds. This card was serial behind PR objectui#11185 (objectui#11168 slice 1) on the action renderers. That PR merged as
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_0122Knsowci76D2rBWReCzzZ
Account:os-warren(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-11212-rider1-fail-soft-legs
Worktree:objectui-issue-11212
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/components/src/renderers/action/action-group.tsxandaction-icon.tsx(thevisibleevaluation's fault policy),packages/plugin-detail/src/RelatedList.tsx(RelatedToolbarButton'svisible),packages/core/src/evaluator/listConditional.ts(evalRowPredicate'sdisabledleg, only if measured to be decided here),packages/core/src/actions/ActionRunner.tsandpackages/react/src/hooks/useActionEngine.ts(binding the predicate-scope subject,current_user, into theActionRunnercontext), their tests, one.changeset/11212-*.md. The dev stops on a breach and explains it in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier;dispatch-gates.mjs --tier --repo objectstack-ai/objectuianswers no path-derived mandate for these paths)
Clause-②: yes
Thread-read: 5912428017
Serial constraints cleared: theBlocked-by:target objectui#11182 closedcompleted2026-09-30T14:55Z (PR objectui#11221 merged as2e3da72adc, theaction-group.tsxpredecessor). Re-derived per the state rule before release: objectui#11168 (domain:spec, later registry-parity slices) is open but unclaimed, so it holds no file; no open PR and no in-flight claim touchespackages/components/src/renderers/action/**,packages/plugin-detail/src/RelatedList.tsx,packages/core/src/actions/**,packages/core/src/evaluator/listConditional.tsorpackages/react/src/hooks/useActionEngine.ts(this seat's in-flight claims: objectui#11021, #10188, #8285 — other packages).pm:blocked→pm:dispatchedin this act; this seat carries the card as the in-scope sub-issue of objectui#4421, which staysBlocked-by: #11212.Scope: objectui#4421 Rider 1 (
5321072032), verbatim: "Rider 1 — fail direction: permission-shaped bindings are fail-closed while the permissions payload has not loaded — the opposite of the predicate default — or the first-paint leak reappears. Pin this." The card body's direction (option A, adopted by this seat), and the added leg5911534880(record:quick_actionsand the dashboard header actions through theActionRunnercontext). PR saysFixes #11212.
Clause-②: yes— why: theActionRunnerpredicate scope gains thecurrent_usersubject (a variable an action predicate on those surfaces can now answer from, where today it faults), and threevisiblelegs change their fault answer from shown to hidden; no schema key moves. Unsure ⇒yes, and a.changesetis a review face regardless.domain:uiseat 1 · claim · 2026-09-30T14:55Z
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11212, "status": "done", "branch": "claude/issue-11212-rider1-fail-soft-legs", "pr": "https://github.com/objectstack-ai/objectui/pull/11237", "session": "session_0122Knsowci76D2rBWReCzzZ", "premise_still_valid": true, "summary": "Rider 1 now holds on every leg the card listed. Measured through the real MePermissionsProvider, ExpressionProvider and ActionProvider, and the real renderers. Before, not loaded / granted / denied read: action:group inline member, dropdown member and host visible, action:icon visible and RelatedToolbarButton visible all SHOWN / shown / hidden; record:quick_actions visible hidden / HIDDEN / hidden, faulting in every state; page:header disabled: !current_user.can ENABLED / enabled / disabled. After: hidden / shown / hidden on every visible leg, and DISABLED / enabled / disabled on the header. How: (1) the action:group leaves (a same-file useMemberVisible hook that mirrors action:menu's useMenuActionVisible), the group host, action:icon and RelatedToolbarButton evaluate visible with throwOnError. That is the key's policy, not a can() special case, and a predicate faulting on an unbound root is hidden too. (2) page:header's evalHeaderPredicate takes the fault fallback per key, and disabled now falls back to true. Measured: the header's direction is decided by the caller's fallback, not in evalRowPredicate, so listConditional.ts is untouched. (3) useActionEngine binds the predicate scope's subject, the same object and not a copy, as current_user on the runner bag the location filter reads. Premise note: the dashboard half of the added leg does not exist. DashboardRenderer's header defs carry no visible, and the spec's dashboard header action is a strict object without one, so record:quick_actions is the only visible surface on that bag.", "tests": [ "Red first on origin/main dded788ada, before any source edit: pnpm exec vitest run packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx gave 'Tests 14 failed | 14 passed (28)', exit 1. The 14 reds match the card's table exactly: NOT LOADED shown on 5 visible legs; quick_actions GRANTED hidden and DENIED reporting a fault; header disabled NOT LOADED enabled; the 6 per-key unbound arms (5 visible legs plus header disabled).", "Fix committed as 8d44406f79. The same pin gave 'Tests 28 passed (28)', exit 0. Edited pins plus the objectui#4421 pins: pnpm exec vitest run over action-record-predicate-root, action-template-predicate-gate, page-header-predicate-dialect, related-toolbar-visible, currentUserCan-failClosed-11212, currentUserCan-4421, subjectPermissions-4421 and effectiveObjects-4421 gave 'Test Files 8 passed (8)', 'Tests 177 passed (177)', exit 0.", "Ablation at 8d44406f79, fix committed first. Tool: /home/user/objectstack/scripts/ablation-replace.mjs in nested WRAP legs, plus a driver trap restoring absolute paths. Mutations: 'throwOnError: true,' to false in action-group.tsx (--expect 2, 'anchor 2 -> 0, blob 86d09f093825 -> 0d2c4da69741'), action-icon.tsx ('anchor 1 -> 0, blob 13f463e6a9cd -> 4c4a4e983e32') and RelatedList.tsx ('blob f830068f177f -> 7c8b0801fe66'); the header 'disabled`, true);' to false ('blob 663c4d5517fc -> 74af7661f1de'); the useActionEngine bind to {} ('blob 7b0cdfc05efe -> f0349abd782e'). On-disk grep counts during the run: 2/1/1/1/1. Prediction recorded before the run: 25 red. Result: 'Tests 25 failed | 123 passed (148)'. By file: new pin 14 (the predicted arms, with quick_actions DENIED red through its silence assertion), action-record-predicate-root 9, page-header-predicate-dialect 1, related-toolbar-visible 1, template-gate 0. Every leg printed 'ok restored: blob == HEAD'. Driver hash check: all 5 paths equal their HEAD blob, 'git diff HEAD: empty'. Lock VERDICT command-exit 0. No dist involved: the pin imports src through deep paths and the @object-ui/react alias, and the useActionEngine mutation turning the quick_actions arms red shows src resolution.", "Union after the final commit, HEAD b8d647a3c4, clean tree, run through os-verify-lock.sh. (a) pnpm exec vitest run packages/components/ packages/react/ gave 'Test Files 447 passed | 1 skipped (448)', 'Tests 4758 passed | 24 skipped (4782)', lock 'VERDICT command-exit 0'. (b) pnpm exec vitest run packages/plugin-detail/ packages/app-shell/src/providers/__tests__/ packages/core/src/actions/ packages/core/src/evaluator/ packages/permissions/src/__tests__/effectiveObjects-4421.test.tsx plus 98 consumer test files gave 'Test Files 384 passed | 1 skipped (385)', 'Tests 6177 passed | 35 skipped (6212)', VITEST-EXIT=0, lock 'VERDICT command-exit 0'. The consumer set is git grep over *.test.* outside components/react/plugin-detail for page:header, action:group, action:icon, RelatedToolbarButton, RelatedList, quick_actions, RecordQuickActions, useActionEngine and DropdownActionItem, deduped against the listed dirs. The vitest guard's evaluateVitestInvocation accepted that argv before the run. The first attempt was refused by the vitest invocation guard (a positional filter subsumed by a listed dir). That exit 1 is NOT MEASURED, not a red; it was re-run as (b).", "Build and type-check at b8d647a3c4 through the lock: turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 gave 'Tasks: 28 successful, 28 total', BUILD-EXIT=0. type-check (tsc --noEmit && tsc -p tsconfig.test.json, script name echoed) exited 0 for @object-ui/components, @object-ui/react, @object-ui/plugin-detail and @object-ui/app-shell. tsc -p tsconfig.test.json --listFilesOnly shows the new pin in app-shell's program, the three edited component tests in components' program, and related-toolbar-visible in plugin-detail's program.", "@object-ui/app-shell full suite NOT MEASURED. Reason: its src is untouched (one new test file). The consumer sweep in (b) ran every app-shell test that names a changed surface, and CI runs the rest.", "Reach, public door /forms/:name. A throwaway probe, not committed, rendered the real App at /forms/showcase_task.edit with FormPage replaced by an action:icon gated on current_user.can plus a companion. At HEAD it read 'isLoaded=false subjectCarriesPermissions=false current_user.id=u1 gatedIcon=hidden companion=shown'. With the action:icon fix ablated ('ok mutation landed ... blob 13f463e6a9cd -> 4c4a4e983e32', 'ok restored') it read 'gatedIcon=SHOWN'.", "Probes for the report, not committed: RowActionMenu item disabled: !current_user.can read 'not-loaded: ENABLED', 'granted: ENABLED', 'denied: DISABLED'. RelatedToolbarButton visible:false read 'SHOWN'." ], "gates": [ "node scripts/check-changeset-presence.mjs exit 0: '11 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s): .changeset/11212-permission-gates-fail-closed.md.'", "pnpm check:control-bytes exit 0: 'check-control-bytes: OK (scanned 9626 tracked text file(s); skipped 85 binary).'", "pnpm check:action-forward-parity exit 0 (action:icon / action:group / action:menu 'payload excess-property CHECKED')", "pnpm check:new-line-citations exit 0: 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'", "pnpm check:doc-types exit 0: 'Every documented component type is registered.'", "pnpm check:doc-fences exit 0 · pnpm check:test-path-roots exit 0 · pnpm check:vi-mock-specifiers exit 0", "pnpm check:changeset-claims exit 0 (report-only). It named 7 pending changesets citing touched files; each paragraph was read, and none states a visible/disabled fault policy.", "pnpm check:pending-changeset-literals exit 0 · node scripts/check-changeset-no-major.mjs exit 0", "Lint, proven narrowing (the CI per-package 'eslint .' form; the full lint run is CI's). pnpm exec eslint --format json over the 11 touched .ts/.tsx files: JSON count 11 files, 0 errors, 241 warnings, exit 0. Base vs head per file, with base read through --stdin --stdin-filename: every file is equal to base except the new pin, which has 2 warnings (@typescript-eslint/no-explicit-any on React.ComponentType of any, the currentUserCan-4421 pin's pattern). Two new no-explicit-any warnings the first commit added were removed in b8d647a3c4. Population: eslint.config.js as configured. Invariance: the config is not type-aware, so the diff cannot move a verdict on an untouched file.", "CI at first read of head b8d647a3c4: 42 check-runs, 21 success, 18 in_progress, 3 skipped, 0 failure. Convergence belongs to the PM." ], "line_budget": "n/a: no skills/** and no governed file (AGENTS.md, CLAUDE.md, .claude/**, docs/adr/**) in the diff", "files_changed": [ ".changeset/11212-permission-gates-fail-closed.md", "content/docs/layout/page-header.mdx", "packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx", "packages/components/src/__tests__/page-header-predicate-dialect.test.tsx", "packages/components/src/renderers/action/__tests__/action-record-predicate-root.test.tsx", "packages/components/src/renderers/action/__tests__/action-template-predicate-gate.test.tsx", "packages/components/src/renderers/action/action-group.tsx", "packages/components/src/renderers/action/action-icon.tsx", "packages/components/src/renderers/layout/containers.tsx", "packages/plugin-detail/src/RelatedList.tsx", "packages/plugin-detail/src/__tests__/related-toolbar-visible.test.tsx", "packages/react/src/hooks/useActionEngine.ts", "packages/react/src/hooks/useExpression.ts" ], "deviations": [ "Surface vs the claim. The header disabled fix landed in packages/components/src/renderers/layout/containers.tsx, not in listConditional.ts: the direction is the caller's fallback, measured. listConditional.ts and ActionRunner.ts are untouched. Added beyond the listed files, each named in the PR body: the new pin under packages/app-shell (test only); four existing pins flipped or labelled; content/docs/layout/page-header.mdx, whose sentence 'a disabled predicate that faults leaves the action enabled' this change made false; and a comment-only correction in packages/react/src/hooks/useExpression.ts, which listed the throwing legs.", "Zone 2 assumption 2: not a bare copy of the option. action:group got a same-file hook, useMemberVisible, shared by its two leaves, as action:menu's useMenuActionVisible is. action:icon and the group host copy the option directly.", "Zone 2 assumption 5, half falsified: the dashboard header actions have no visible leg (see summary). The binding is in useActionEngine only; the runner's user / ctx.user / os.user stay the host's object, which carries systemPermissions.", "The changeset declares @object-ui/components, @object-ui/plugin-detail and @object-ui/react as minor (Clause-② yes). The presence gate counts 4 released packages, because app-shell's src gained a test file. app-shell is not in the frontmatter because nothing it ships changed.", "PR footer: the os-dev session-URL form, not the harness's emoji line. Commit trailers: the AGENTS.md model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness's model-named trailer.", "Incident, reported so it is not hidden. While editing the PR body in the scratchpad I used an unquoted heredoc, and bash ran the backtick-quoted command text in /home/user. Every command failed without touching anything. The command names were not found. pnpm answered ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND or RECURSIVE_EXEC_NO_PACKAGE. node scripts/check-changeset-presence.mjs gave MODULE_NOT_FOUND. tsc gave TS5058. A global eslint died importing a parser, with no --fix. No vitest or turbo run started. Afterwards: /home/user holds no new entries, and the worktree is clean. The body was rewritten from a file and read back byte-equal after pr_create (11301 bytes sent, 11301 stored, identical).", "Lock: two waiters hit queue-timeout 99 and resumed their kept slots (issue-11212-build, issue-11212-final); both reads above come from the resumed runs.", "Throwaway probe files were created under apps/console/src/__tests__, packages/app-shell/src/providers/__tests__ and packages/plugin-detail/src/__tests__ and deleted after each run. None was committed; git status was clean at the union run." ], "mcp_calls": "0. No MCP GitHub tool was called. Reads went through REST GET: the card and its 3 comments, comments 5321072032, 5911355276 and 5911520748, PR #11237's list, body and check-runs.", "api_writes": "3, all through the fleet relay. Each is one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectui/pulls, draft #11237, relay run 36744836119 success. (2) label-write.mjs --assign os-warren: POST /repos/objectstack-ai/objectui/issues/11237/assignees, relay run 36744950374 success, read-back 'MATCHES the target'. (3) post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11212/comments (this report). git push is not a REST write; the branch was pushed 3 times: the empty branch, 8d44406f79 and b8d647a3c4.", "open_questions": [ { "question": "Rider 1 on the remaining disabled legs: fork, not done here. The header's disabled leg now fails closed (DISABLED). The same permission-shaped gate still fails OPEN on the other evalRowPredicate-family disabled legs. Measured: the RowActionMenu item with disabled: !current_user.can('account','delete') reads ENABLED / enabled / disabled (not loaded / granted / denied); its own comment says 'disabled fails soft (not disabled)'. By the same code shape (fallback: false), not measured: the data-table row action's disabled, and the built-in disabledWhen on RecordDetailView / DetailView / ObjectView / ObjectDataPage, whose fail-soft posture PR #4515 documented ('an unevaluable predicate must not grey a button forever'). The not-loaded state is reachable only outside MePermissionsProvider (/forms/:name, embeds). Where does the Rider 1 disabled direction stop?", "options": [ "A: one sub-issue of objectui#4421 flipping the two authored-action disabled legs, RowActionMenu item and data-table row action, to fallback true, as the header now does. Built-in disabledWhen stays untouched. BUSINESS NEED: page-header.mdx tells authors that the row menu evaluates the same predicate as the header, so one declaration now gets DISABLED on the header and ENABLED on the row menu while the payload loads; measured, not speculative. LONG-TERM: one fail direction per key across every authored-action surface, and the useCondition-family legs are already DISABLED on fault. AI ERROR-RESISTANCE: an author or AI writing disabled: !current_user.can gets the same answer on every surface, so no surface-dependent behaviour has to be learned. STARTUP SCOPE: two call sites, one per file, no new surface, no new gate.", "B: A plus the built-in disabledWhen. BUSINESS NEED: weak. The built-in Edit/Delete are already permission-gated by the affordance rules, so a permission-shaped disabledWhen on them is redundant. LONG-TERM: uniform, but it reverses a documented, pinned posture (PR #4515) on a different key. AI ERROR-RESISTANCE: marginal. STARTUP SCOPE: more pins to flip and a documented contract to rewrite.", "C: stop at the record header, since the card's table named only that leg. BUSINESS NEED: the reach is narrow. LONG-TERM: leaves one key with two fail directions by surface. AI ERROR-RESISTANCE: worst of the three; the same metadata means different things by placement. STARTUP SCOPE: zero work." ], "recommendation": "A. The inconsistency is measured and reachable through documented authoring (one declaration, header plus row menu). The fix is two mechanical fallbacks that match both the header and the useCondition-family legs. B pays a documented-contract reversal for no measured need." } ], "out_of_scope_findings": [ "class: b · Seam: spec:current_user.can (ADR-0068 D1 alias set; objectui#4421 Rider 1) → renderer:RowActionMenuItem disabled leg (plugin-grid) and data-table row action disabled leg (components) · reach: NOT a public door. The production RowActionMenu was measured under jsdom with the real ExpressionProvider and MePermissionsProvider: ENABLED / enabled / disabled. · evidence: the probe above, plus RowActionMenu's own comment 'disabled fails soft (not disabled)' · disposition: open question 1, a sub-issue candidate of objectui#4421 (Rider 1 scope). The seat files it. · dedupe: 'row menu disabled current_user.can', 'RowActionMenuItem disabled fail-soft', 'Rider 1 disabled legs'", "carrier: none (承接者:无) · noted in PR #11237 Acceptance notes, not filed · RelatedToolbarButton gates visible on truthiness (visiblePred && !isVisible), so a list_toolbar action authored visible: false renders. The probe read SHOWN, and RelatedRecordActionsBridge's deriveActions passes visible through unfiltered. This is the objectui#3812 declared-gate class, a different defect class from this card, so no bounded in-place fix was made. No public-door reading and no named producer, so it is not filed.", "carrier: none (承接者:无) · noted in PR #11237 Acceptance notes, not filed · ActionRunner.execute's disabled-gate comment says an eval failure 'defaults to NOT-disabled', but the code blocks. This change rules DISABLED as the direction for disabled, so only the comment is wrong. It is the PR #11208 neighbour, untouched.", "carrier: none (承接者:无) · noted in PR #11237 Acceptance notes, not filed · the ActionProvider runner itself still binds no current_user. useActionEngine writes it onto a shared provider runner, the same way it already writes record / recordId / objectName, so a page with a record:quick_actions block also answers current_user in that provider's execute gates, and a page without one does not. This is the PR #11208 neighbour, where a header action with disabled: !current_user.can is refused by ActionRunner.execute for a grant holder. Not re-measured here." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11212, "patch_round": 1, "status": "done", "branch": "claude/issue-11212-rider1-fail-soft-legs", "pr": "https://github.com/objectstack-ai/objectui/pull/11237", "session": "session_0122Knsowci76D2rBWReCzzZ", "premise_still_valid": true, "head": "cb561e78bc46f35cbfd46619600df116f10830ef", "summary": "The blocking finding of contract review 5915761337 is addressed. The changeset headline no longer claims 'on every action surface' for disabled. It now reads: an action gated on current_user.can stays hidden until the permissions payload has loaded on every action visible surface, a page:header action gated through disabled stays disabled, and record:quick_actions can answer current_user at all. Nothing else changed: no source, no test, no other prose.", "diff": "1 file, 1 line: .changeset/11212-permission-gates-fail-closed.md, the headline line only (1 insertion, 1 deletion). Commit cb561e78bc on top of b8d647a3c4, pushed without force.", "tests": [ "node scripts/check-changeset-presence.mjs exit 0: '11 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s): .changeset/11212-permission-gates-fail-closed.md.'", "pnpm check:changeset-claims exit 0 (report-only): self-contradiction section 'Every package declared across those 1 body(ies) is either not negated'. The 7 pending changesets that name touched files are the same list as last round.", "Control-byte self-scan of the edited changeset: 0 hits. No test was re-run, because the diff is one prose line in a changeset." ], "mcp_calls": "0", "api_writes": "1: post-stamped.mjs POST /repos/objectstack-ai/objectui/issues/11212/comments (this report), through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches). git push (1, fast-forward b8d647a3c4 to cb561e78bc) is not a REST write.", "deviations": [ "The commit message went through a QUOTED heredoc (git commit -F - with 'EOF'), so no expansion was possible. The changeset line was edited with the Edit tool, and this report was written with the Write tool." ], "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actions✅ ACCEPT: PR objectui#11237 at
cb561e78b; ready and auto-merge follow this ACCEPTdomain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T17:10Z. Checked against the diff and the head's check-runs. Claim5913814153; dev reports5915507971and patch round 15915791474.- Contract review at
CONTRACT_REVIEW_TIER: round 15915761337onb8d647a3c, FAIL on one blocking finding (the changeset headline claimed "(or disabled) … on every action surface"); patch round 1 changed that one line; delta record5915883213PASS on this head, the landing head. Round 1's ① and ③ carry over: the source, test and doc diff is byte-identical between the heads.Implemented-by: claude/issue-11212-rider1-fail-soft-legs,Reviewed-by: session_0122Knsowci76D2rBWReCzzZ.
item reading Rider 1 ( 5321072032)measured through the real MePermissionsProvider→ExpressionProvider→ActionProviderand the registered renderers: everyvisibleleg on the card (action:groupinline member, dropdown member and host;action:icon;RelatedToolbarButton) now reads hidden / shown / hidden (not loaded / granted / denied), and the record header'sdisabled: !current_user.can(...)reads DISABLED / enabled / disabledone policy per key the visiblelegs takethrowOnError, the branchaction:button/action:menu/action:baralready take, so any faultingvisiblehides there, not only acan()fault (the card's option A; stated in the changeset andpage-header.mdx)the header disabledthe fallback moved in evalHeaderPredicate(containers.tsx);evalRowPredicatereturns the caller's fallback, solistConditional.tsis untouched and the row menu, data table and bulk fold do not movethe added leg ( 5911534880)useActionEnginebinds the predicate-scope subject ascurrent_user, the same object the scope carries the permissions map on, sorecord:quick_actionsanswerscurrent_user.can(...)at all; the dev's premise correction holds (the dashboard header defs carry novisible)red first, ablation 14 of 28 new-pin arms red on the base, exactly the card's table; five ablation legs turned 25 tests red where predicted, each restored to blob == HEAD semver components, plugin-detail and react minor; Clause-②: yes; the headline now names every actionvisiblesurface and scopesdisabledtopage:headerCI head cb561e78b: 43 runs, 40 success, 3 expected skips (the coverage matrix and dependabot), 0 failure;Type CheckandSpec Main Shape Gatesuccessmain since the merge base no file of this PR changed on mainsincedded788ada; a test merge ontomainis cleanscope / governed 13 files, +618 / −75; check-governed-merges.mjs: not governed;Fixes #11212Deviations, accepted as the records answer them: the header fix in
containers.tsx, not the claim'slistConditional.ts(measured: the caller owns the direction); a same-fileuseMemberVisibleforaction:group's two leaves; four existing pins flipped, thepage-header.mdxsentence corrected and auseExpression.tscomment enumeration updated (AGENTS.md #2);@object-ui/app-shellgains only a test file and owes no changeset line. The dev's reported incident (an unquoted heredoc that ran backticked text in/home/user, all commands failing) left no residue: the diff is the 13 listed files and the tree was clean.Open question, answered by this seat: A. The row-menu item and the data-table row action keep a fail-OPEN
disabledwhile the payload loads, while the header now fails closed; that is Rider 1 residue on authored actions, so it reuses the ruling. The built-indisabledWhenstays fail-soft (its posture is documented and pinned; option B not taken). → filed objectui#11242 (sub-issue of objectui#4421,pm:queue, p2), which also carries the review's page-shapedcurrent_useron theActionProviderrunner and theActionRunner.executecomment.Out-of-scope findings:
RelatedToolbarButtonrenders a toolbar action authoredvisible: false(a truthiness gate; the objectui#3812 declared-gate class; reproduced through the real component) → filed objectui#11244 (finding, for triage), as the review asked for a carrier.- the
ActionRunner.executedisabled-gate comment contradicts its code → carried by objectui#11242. - the provider runner answers
current_useronly on pages that mountrecord:quick_actions/record:alert/ a dashboard → carried by objectui#11242. - on the runner bag
user.can(...)still faults whilecurrent_user.can(...)answers → Acceptance notes (a doc note for objectui#11242's change).
Parent. objectui#4421 stays
pm:blocked; when this card closes itsBlocked-by:moves to objectui#11242.domain:uiseat 1 · ACCEPT · 2026-09-30T17:10Z
Generated by Claude Code
- Contract review at
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsLanded: PR objectui#11237 merged as
8bab1571d9; closedcompleteddomain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T17:56Z.- PR objectui#11237 (
Fixes #11212) merged through the merge queue as8bab1571d9, which is onorigin/main. The merge closed the card by itsFixeskeyword. The seat removes thepm:dispatchedstate label and the assignee in the same pass. - It merged at 2026-09-30T17:27:39Z. Verified by content: all 13 of its files on
origin/mainare blob-identical to the landing headcb561e78b. - What ships: Rider 1 on the fail-soft legs. While the permissions payload has not loaded, every
visibleleg on the card hides a permission-gated action. Those legs are theaction:groupinline and dropdown members and host,action:icon, andRelatedToolbarButton. The record header'sdisabledreads disabled.record:quick_actionsanswerscurrent_user.can(...)through the same subject. The record is5915761337(round 1) plus the delta5915883213(at-tier PASS on the landing head). The ACCEPT is5916059211. - What stays, and who holds it:
- The row-menu and data-table row-action
disabledlegs, and theActionProviderrunner answeringcurrent_useronly on some pages: objectui#11242 (pm:queue,priority:p2). The seat decided it in-seat at the ACCEPT, as option A. RelatedToolbarButtonrenders an action authoredvisible: false: objectui#11244 (finding, for triage).- objectui#4421's
Blocked-by:moves from this card to objectui#11242 in the same pass.
- The row-menu and data-table row-action
domain:uiseat 1 · landed · 2026-09-30T17:56Z
Generated by Claude Code
- PR objectui#11237 (
- added a commit that references this issue
on Oct 7, 2026
This card carries the Rider 1 residue of objectui#4421; the parent keeps the binding (PR objectui#11208,
Part of #4421) and closes when this card lands.Filing-gate category: ① a derived in-scope item of an in-flight card (the parent's acceptance needs it), carried by the claiming seat:
domain:uiseat 1 (session_0122Knsowci76D2rBWReCzzZ), which inherits the parent'sdomain:uiandpriority:p2.Dedup: none needed for a derived sub-item; the parent's own thread is the record.
The ruling it completes
objectui#4421 ruling (
5321072032), verbatim: "Rider 1 — fail direction: permission-shaped bindings are fail-closed while the permissions payload has not loaded — the opposite of the predicate default — or the first-paint leak reappears. Pin this."What PR objectui#11208 measured (dev report on objectui#4421)
The binding refuses
current_user.can(...)loudly while the payload is not loaded, so every leg that evaluatesvisiblefail-CLOSED hides the action (row menu, record header,action:button, and by the same entriesaction:menu,action:bar,DeclaredActionsBar, data-table rows). Three-state pins hold there. Legs that apply a fail-SOFT fault policy show the action instead. Measured per leg as not-loaded / granted / denied:action:groupinlinevisibleaction:iconvisibleRelatedToolbarButton)visibledisabled: !current_user.can(...)(evalRowPredicatedisabled leg)Reach: on the console's app routes the not-loaded state does not render (
MePermissionsProviderholds a loading screen until the first answer). It is reachable on/forms/:name(the internal form route mounts its ownExpressionProvideroutside that provider) and in standalone embeds.Direction (the dev's option A, adopted by the seat)
action:group,action:iconandRelatedToolbarButtonevaluatevisiblewiththrowOnError, asaction:buttonandaction:menualready do: one fail-closed policy per key on every actionvisibleleg, not acan()special case.disabledlegs are decided on their own merits, since a disabled gate that faults has its own fail direction (theActionRunnerexecute gate is recorded in PR objectui#11208's Acceptance notes).Serial
action:groupandaction:iconwere edited by PR objectui#11185 (objectui#11168 slice 1), merged as3cc4fe567b. This card's renderer half is now serial behind objectui#11182, in flight onaction-group.tsx(re-pointed at the seat's note on this card).Blocked-by: #11182