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
feat(grid): gate list row Edit/Delete and bulk delete on the effective operation set (objectstack#3720) (#2889)
The fourth surface objectstack#3391 left open. The toolbar (#2823), detail/form
(#2832 + #2876) and related lists (#2832) all route through `resolveCrudAffordances`;
the main list's row CRUD has its own resolver and none of those rounds reached it.
Its gate was `operations ?? { update: !!onEdit, delete: !!onDelete }`, and ObjectView
wires onEdit/onDelete unconditionally while view JSON rarely declares `operations` —
so it was effectively always-on. A caller whose effective set carried neither `update`
nor `delete` still got the row kebab's Edit/Delete and the bulk delete.
- plugin-grid `resolveRowCrudAffordances` takes `managedBy` + `effectiveApiOperations`
and resolves the object verdict through the shared `resolveCrudAffordances` policy,
so the row gate is the same decision every other face makes. It also returns
`objectCanDelete` — bulk delete rides `onBulkDelete`, a different callback from the
row `onDelete`, so it must not be judged by whether the row handler happens to be wired.
- plugin-grid `ObjectGrid` threads its existing `effectiveApiOps` (until now fed only to
Export) into the row gate, and applies the delete verdict to bulk delete: the implicit
`['delete']`, a declared `bulkActions: ['delete']`, and any `bulkActionDefs` entry with
`operation: 'delete'`. Custom ids and non-delete operations pass through untouched.
- plugin-list `ListView`'s own bulk bar (the non-grid views) drops its built-in `delete`
under the same verdict.
Also closes the ADR-0103 gap on this chain: the bucket lock was documented as applied
upstream via the view's `operations.*`, but the all-open default meant it never was —
an engine-owned system / append-only / better-auth object leaked a generic row
Edit/Delete the engine rejects. A `userActions` opt-in still re-opens it.
Intersection, never union. A missing effective set preserves current behavior.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0 commit comments