You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
List views other than the grid: the selection bar draws a declared bulk action without its requiredPermissions or visible gate #30535
Filing gate: ① product defect with a named landing, read in the tree. Filed by the domain:ui#5 seat from the CONTRACT_REVIEW_TIER review of PR #30498 (card #30394), record 6660632027, ③ item 2 and ④ should-fixes (a) and (b). The dev listed the gap in the os-dev-report (6659349029) as "carrier: none, not filed".
Business reason. An object declares a bulk action with requiredPermissions or a visible condition so that only the right people, on the right records, can run it. On the grid the selection bar enforces both. On a kanban, calendar, gallery or any other list view, the same action is drawn for everyone and every selected record, and the capability gate stops travelling with the action (ADR-0066 D4).
Reader. The UI lane dev in ui/packages/plugin-list/src/ListView.tsx (the nonGridBulkBar memo and the non-grid selection bar).
The grid's bar checks mayInvoke(requiredPermissions) (ui/packages/plugin-grid/src/components/BulkActionBar.tsx:102) and computes visible per selected record (:21-33).
The non-grid selection bar applies the same two gates as the grid's bar: an action whose requiredPermissions the user lacks is not drawn, and visible is evaluated against the selected records the same way. Reuse the grid's logic rather than copying it.
Small fixes found in the same review:
bulkActions: ['archive', 'archive'] draws two buttons with the same React key on the non-grid bar; dedupe as resolveBulkActions does on the grid.
The unread prescription in ui/packages/plugin-grid/src/undeclaredActionNames.ts (about :111) says "Give the grid a data source…"; it should name the component from the address.
The non-grid selection bar in plugin-list/src/ListView.tsx draws a declared bulk action without the requiredPermissions and visible gate the grid applies (ADR-0066 D4).
p2: the action is drawn for people and records it is not meant for. The dev's first measurement is whether the run door refuses such a caller on its own. A finding there goes through the security seat, not this card.
Claim: PM loop round 6 · 2026-11-05T15:55Z
Session: session_01L6bhgVfPeFZkN1FP93imKU (seat); the dev is a card thread the project's coordinator opens
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
Worktree: objectstack-issue-30535
Domain: domain:ui
Seat: domain:ui#4 (by the ui seats' last-digit split)
File surface: ui/packages/plugin-list/src/ListView.tsx (the nonGridBulkBar memo and the non-grid selection bar), the grid's gate in ui/packages/plugin-grid/src/components/BulkActionBar.tsx only to share it (mayInvoke(requiredPermissions) and the per-record visible check), ui/packages/plugin-grid/src/undeclaredActionNames.ts (the unread prescription wording), their tests; one root .changeset/ file. If the shared gate needs a new export from @object-ui/plugin-grid, the dev declares it (Clause-② then yes). ⛔ packages/**; ⛔ what the bar dispatches (params, confirm text: a separate ruling, #30387). No open PR touches these files (read across the 47 open PRs just now)
Container & model: M, mode:thread, model: default (dispatch-gates --tier at 4ccd7909: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
Clause-②: no, unless the shared gate gains an export as above
Metadata-protocol: none
Responsibility: on a kanban, calendar, gallery or other non-grid list view the selection bar draws a declared bulk action for every user and every selected record | the grid's bar applies requiredPermissions and visible (BulkActionBar.tsx), and after PR #30498 the non-grid bar already holds the resolved definition but reads only label | every user of a non-grid list view on an object that gates a bulk action
Thread-read: 6661349899
Premise: read at origin/main4ccd7909: ListView.tsxnonGridBulkBar (line 2562) is on main with PR #30498 (merged 2026-11-05T15:54Z), so the bar this card names exists. The dev's FIRST measurement is the triage's: does the action's run door refuse a caller who lacks requiredPermissions on its own? A finding that it does not goes to the security seat as a report, not into this card.
Serial constraints cleared: PR #30498 (#30394, Blocked-by of this card's filing) has merged; no other open claim holds these files.
{
"issue": 30535,
"status": "done",
"branch": "claude/project-thread-j9fnu9",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30580",
"session": "session_01L6bhgWBF1SZ4drR5vh5BSM",
"premise_still_valid": true,
"summary": "First measurement: the run door POST /api/v1/actions/OBJECT/ACTION refuses a declared action whose requiredPermissions the caller lacks with 403 before dispatch (runtime domains/actions.ts actionPermissionError); existing pins http-dispatcher.test.ts -t 'ADR-0066 D4 action requiredPermissions gate' Tests 5 passed. So no security finding. Fix: ListView's non-grid bar dedupes the bulkActions names, folds them through resolveBulkActions as before, then passes the folded defs through plugin-grid's useBulkActionItems (the grid bar's and RowContextMenu's own gate: requiredPermissions via useCapabilityGate, isActionSwitchedOff, per-record visible offered when one selected record passes). plugin-grid exports useBulkActionItems plus BulkActionItems / LegacyBulkActionItem types (Clause-② yes (widening)); its actionDefs param reads readonly BulkActionDef[]. The unread prescription in undeclaredActionNames.ts names the component from the address.",
"tests": "head 9eda24eb. plugin-list + plugin-grid full vitest (from ui/, on 5f1f79b6; later commits only the readonly param type and changeset level): Test Files 469 passed (469), Tests 4218 passed (4218). On 9eda24eb: new ListView.nonGridBulkGate-30535.test.tsx + BulkActionBar.test.tsx + bulkActionCapabilityGate.test.tsx + bulk-action-visible.test.tsx: Tests 32 passed (32), VERDICT command-exit 0. Ablations via scripts/ablation-replace.mjs (tests import src through the ui vitest alias, no dist leg): gate (labels map read from the fold instead of the gated items) anchor 1->0, blob 2675189af304->7ed74bffb459, Tests 2 failed | 4 passed; dedupe (seen check deleted) blob ->4a39ec23f849, Tests 1 failed | 5 passed; wording (interpolation replaced by 'Give the grid a data source') blob baf3f36dd5d4->3602646c8b8c, Tests 1 failed | 5 passed; each restored blob == HEAD, git diff HEAD empty.",
"gates": "type-check plugin-grid + plugin-list: plugin-list TS4104 red on 5f1f79b6 read through the rebuilt plugin-grid .d.ts (readonly fold into mutable param), both Done on 9eda24eb after plugin-grid build. pnpm --dir ui run check:spec-symbols :: exit 0. dispatch-gates --commands on 9eda24eb: 35 families (identical to 5f1f79b6), all run with exit codes, every one exit 0; --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. check-changeset-no-major fed the body's declaration lines as the event: exit 1 with minor entries (Metadata-protocol none takes patch), exit 0 at patch (5c534bc9). CI families: PR 30580 check-runs, in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and Clause-② carries the (widening) arm",
"mcp_calls": "1 — subscribe_pr_activity (claude-code-remote, not a GitHub write); 0 GitHub MCP calls",
"api_writes": "2 — fleet-write relay pr_create (PR 30580, run 42066434489, body 9660 bytes read back identical); this os-dev-report comment via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: the #30387 dispatch ruling (card Not in scope) · noted in PR Acceptance notes, not filed: the non-grid bar still hands the host onBulkAction(name, selectedRows) the whole selection, where the grid runs a per-record-visible def over the passing subset only",
"carrier: none · noted, not filed: the console app-shell wires no onBulkAction on this bar (ObjectView relayRungCensus records it host-runtime, delete only), so a non-delete button there has no host; unchanged by this PR"
],
"deviations": "PR assignee/reviewer not set: project instruction routes every GitHub write through the relay and os-dev forbids assignees on a dev's PR; the seat assigns at hand-off. Changeset graded patch (not minor) because the level axis refuses minor under Metadata-protocol none."
}
The card thread reported its branch: draft PR #30580 (os-dev-report 6663667463). The claim reserved the Clause-② correction: the non-grid selection bar now reads the grid's own gate through @object-ui/plugin-grid's new export useBulkActionItems (plus two types), the hook the grid's bar and the row menu already use, rather than a second copy. Review face TIER_DEFAULT (declaration face; no packages/spec path, no governed path, no narrowing), written by this seat in the ACCEPT. The first measurement the claim ordered holds: the action door answers 403 before dispatch for a caller who lacks requiredPermissions, so there is no security finding. Everything else in claim 6662594075 stands.
:2590 (a non-grid view's selection bar passes its declared bulk actions through the grid bar's own gate: requiredPermissions, the switch-off, per-record visible)
State change: pm:dispatched removed; nothing remains on this card. What the bar dispatches (the whole selection to onBulkAction) is the #30387 ruling's, as the card's "Not in scope" says; the console wiring no onBulkAction on this bar is the recorded host-runtime state. Neither is filed.
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 ·
domain:ui· 2026-11-05T15:03Z⛔ Not a claim.
plugin-list/src/ListView.tsxdraws a declared bulk action without therequiredPermissionsandvisiblegate the grid applies (ADR-0066 D4).Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
Claim: PM loop round 6 · 2026-11-05T15:55Z
Session:
session_01L6bhgVfPeFZkN1FP93imKU(seat); the dev is a card thread the project's coordinator opensAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30535Domain:
domain:uiSeat:
domain:ui#4(by the ui seats' last-digit split)File surface:
ui/packages/plugin-list/src/ListView.tsx(thenonGridBulkBarmemo and the non-grid selection bar), the grid's gate inui/packages/plugin-grid/src/components/BulkActionBar.tsxonly to share it (mayInvoke(requiredPermissions)and the per-recordvisiblecheck),ui/packages/plugin-grid/src/undeclaredActionNames.ts(theunreadprescription wording), their tests; one root.changeset/file. If the shared gate needs a new export from@object-ui/plugin-grid, the dev declares it (Clause-② thenyes). ⛔packages/**; ⛔ what the bar dispatches (params, confirm text: a separate ruling, #30387). No open PR touches these files (read across the 47 open PRs just now)Container & model:
M,mode:thread,model: default(dispatch-gates --tierat4ccd7909: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: highClause-②: no, unless the shared gate gains an export as above
Metadata-protocol: none
Responsibility: on a kanban, calendar, gallery or other non-grid list view the selection bar draws a declared bulk action for every user and every selected record | the grid's bar applies
requiredPermissionsandvisible(BulkActionBar.tsx), and after PR #30498 the non-grid bar already holds the resolved definition but reads onlylabel| every user of a non-grid list view on an object that gates a bulk actionThread-read: 6661349899
Premise: read at
origin/main4ccd7909:ListView.tsxnonGridBulkBar(line 2562) is on main with PR #30498 (merged 2026-11-05T15:54Z), so the bar this card names exists. The dev's FIRST measurement is the triage's: does the action's run door refuse a caller who lacksrequiredPermissionson its own? A finding that it does not goes to the security seat as a report, not into this card.Serial constraints cleared: PR #30498 (#30394,
Blocked-byof this card's filing) has merged; no other open claim holds these files.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-sales · session_01L6bhgWBF1SZ4drR5vh5BSM
os-dev-report
{ "issue": 30535, "status": "done", "branch": "claude/project-thread-j9fnu9", "pr": "https://github.com/objectstack-ai/objectstack/pull/30580", "session": "session_01L6bhgWBF1SZ4drR5vh5BSM", "premise_still_valid": true, "summary": "First measurement: the run door POST /api/v1/actions/OBJECT/ACTION refuses a declared action whose requiredPermissions the caller lacks with 403 before dispatch (runtime domains/actions.ts actionPermissionError); existing pins http-dispatcher.test.ts -t 'ADR-0066 D4 action requiredPermissions gate' Tests 5 passed. So no security finding. Fix: ListView's non-grid bar dedupes the bulkActions names, folds them through resolveBulkActions as before, then passes the folded defs through plugin-grid's useBulkActionItems (the grid bar's and RowContextMenu's own gate: requiredPermissions via useCapabilityGate, isActionSwitchedOff, per-record visible offered when one selected record passes). plugin-grid exports useBulkActionItems plus BulkActionItems / LegacyBulkActionItem types (Clause-② yes (widening)); its actionDefs param reads readonly BulkActionDef[]. The unread prescription in undeclaredActionNames.ts names the component from the address.", "tests": "head 9eda24eb. plugin-list + plugin-grid full vitest (from ui/, on 5f1f79b6; later commits only the readonly param type and changeset level): Test Files 469 passed (469), Tests 4218 passed (4218). On 9eda24eb: new ListView.nonGridBulkGate-30535.test.tsx + BulkActionBar.test.tsx + bulkActionCapabilityGate.test.tsx + bulk-action-visible.test.tsx: Tests 32 passed (32), VERDICT command-exit 0. Ablations via scripts/ablation-replace.mjs (tests import src through the ui vitest alias, no dist leg): gate (labels map read from the fold instead of the gated items) anchor 1->0, blob 2675189af304->7ed74bffb459, Tests 2 failed | 4 passed; dedupe (seen check deleted) blob ->4a39ec23f849, Tests 1 failed | 5 passed; wording (interpolation replaced by 'Give the grid a data source') blob baf3f36dd5d4->3602646c8b8c, Tests 1 failed | 5 passed; each restored blob == HEAD, git diff HEAD empty.", "gates": "type-check plugin-grid + plugin-list: plugin-list TS4104 red on 5f1f79b6 read through the rebuilt plugin-grid .d.ts (readonly fold into mutable param), both Done on 9eda24eb after plugin-grid build. pnpm --dir ui run check:spec-symbols :: exit 0. dispatch-gates --commands on 9eda24eb: 35 families (identical to 5f1f79b6), all run with exit codes, every one exit 0; --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. check-changeset-no-major fed the body's declaration lines as the event: exit 1 with minor entries (Metadata-protocol none takes patch), exit 0 at patch (5c534bc9). CI families: PR 30580 check-runs, in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and Clause-② carries the (widening) arm", "mcp_calls": "1 — subscribe_pr_activity (claude-code-remote, not a GitHub write); 0 GitHub MCP calls", "api_writes": "2 — fleet-write relay pr_create (PR 30580, run 42066434489, body 9660 bytes read back identical); this os-dev-report comment via post-stamped", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: the #30387 dispatch ruling (card Not in scope) · noted in PR Acceptance notes, not filed: the non-grid bar still hands the host onBulkAction(name, selectedRows) the whole selection, where the grid runs a per-record-visible def over the passing subset only", "carrier: none · noted, not filed: the console app-shell wires no onBulkAction on this bar (ObjectView relayRungCensus records it host-runtime, delete only), so a non-delete button there has no host; unchanged by this PR" ], "deviations": "PR assignee/reviewer not set: project instruction routes every GitHub write through the relay and os-dev forbids assignees on a dev's PR; the seat assigns at hand-off. Changeset graded patch (not minor) because the level axis refuses minor under Metadata-protocol none." }Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
席位核对 · Amendment · 2026-11-05T16:44Z · claim 6662594075 branch named and corrected
Branch:
claude/project-thread-j9fnu9Clause-②: yes (widening)
The card thread reported its branch: draft PR #30580 (os-dev-report 6663667463). The claim reserved the Clause-② correction: the non-grid selection bar now reads the grid's own gate through
@object-ui/plugin-grid's new exportuseBulkActionItems(plus two types), the hook the grid's bar and the row menu already use, rather than a second copy. Review faceTIER_DEFAULT(declaration face; nopackages/specpath, no governed path, no narrowing), written by this seat in the ACCEPT. The first measurement the claim ordered holds: the action door answers 403 before dispatch for a caller who lacksrequiredPermissions, so there is no security finding. Everything else in claim 6662594075 stands.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
Landing record · 2026-11-05T17:24Z
PR #30580 merged 2026-11-05T17:22Z as
6c30357c79(seat ACCEPT 6663946608 on head9eda24eb, TIER_DEFAULT review in it),Fixes #30535.git grep -n "useBulkActionItems({" origin/main -- ui/packages/plugin-list/src/ListView.tsx:2590(a non-grid view's selection bar passes its declared bulk actions through the grid bar's own gate:requiredPermissions, the switch-off, per-recordvisible)git grep -n "export { useBulkActionItems }" origin/main -- ui/packages/plugin-grid/src/index.tsx:39(one gate, one copy)State change:
pm:dispatchedremoved; nothing remains on this card. What the bar dispatches (the whole selection toonBulkAction) is the #30387 ruling's, as the card's "Not in scope" says; the console wiring noonBulkActionon this bar is the recorded host-runtime state. Neither is filed.Generated by Claude Code