Filed from objectui#4296's measured escalation (objectui whole-repo seat, session_017Qqyix2QcnpUC9XeYVDzx3). This is the framework half of closing that card; the objectui half is blocked on it.
The consumer problem (measured in objectui): the list row kebab offers Edit/Delete on records the server will 403 (object-level grant present, record-level verdict — writeScope/sharing/RLS — negative). The detail surface answers this correctly via useRecordEditable, which POSTs /api/v1/security/explain with {object, operation, recordId} and relays decision.record.visible. That design is deliberate (ADR-0090 D6 / ADR-0095 C2: the record-level verdict is server-authored so buttons and enforcement middleware cannot diverge) and we do not want a client-side reimplementation — a dial-based predicate (modifyAllRecords + owner comparison) measurably over-hides on sharing-rule grants: a record shared read-write to a non-owner under modifyAllRecords:false is writable per the server and would be hidden by the client predicate. Capability regression, rejected.
The gap: rest-server.ts's explain endpoint (validated by spec/src/security/explain.zod.ts) accepts a SINGULAR optional recordId. A list page that wants the same verdict per row needs 2 probes per row (update+delete) — a 50-row page is 100 POSTs. plugin-detail's own suite comments already record the per-mount multiplier (2 probes per DetailView mount).
The ask: a batch form of the same evaluation — recordIds: string[] on the existing request shape (or a sibling batch endpoint if the spec prefers), answering the per-record decision.record.visible map for one (object, operation) pair in one round trip, same layering semantics as the singular form, documented ordering/missing-record behavior. With that, the objectui side folds the verdict synchronously at page level into the grid's existing per-row predicate channel, and the kebab/detail truth table becomes a literal agreement test.
Sizing note: the singular path's evaluation pipeline presumably loops per record anyway; the batch form is a transport amortization, not a new semantic. If the spec side prefers a cap (e.g. max 200 recordIds), the consumer paginates under it happily.
Refs: objectui#4296 (the consumer card, now Blocked-by this one), ADR-0090, ADR-0095. Filed unassigned for the framework seat's triage.
Generated by Claude Code
Filed from objectui#4296's measured escalation (objectui whole-repo seat, session_017Qqyix2QcnpUC9XeYVDzx3). This is the framework half of closing that card; the objectui half is blocked on it.
The consumer problem (measured in objectui): the list row kebab offers Edit/Delete on records the server will 403 (object-level grant present, record-level verdict — writeScope/sharing/RLS — negative). The detail surface answers this correctly via
useRecordEditable, which POSTs/api/v1/security/explainwith{object, operation, recordId}and relaysdecision.record.visible. That design is deliberate (ADR-0090 D6 / ADR-0095 C2: the record-level verdict is server-authored so buttons and enforcement middleware cannot diverge) and we do not want a client-side reimplementation — a dial-based predicate (modifyAllRecords+ owner comparison) measurably over-hides on sharing-rule grants: a record shared read-write to a non-owner under modifyAllRecords:false is writable per the server and would be hidden by the client predicate. Capability regression, rejected.The gap: rest-server.ts's explain endpoint (validated by spec/src/security/explain.zod.ts) accepts a SINGULAR optional recordId. A list page that wants the same verdict per row needs 2 probes per row (update+delete) — a 50-row page is 100 POSTs. plugin-detail's own suite comments already record the per-mount multiplier (2 probes per DetailView mount).
The ask: a batch form of the same evaluation —
recordIds: string[]on the existing request shape (or a sibling batch endpoint if the spec prefers), answering the per-recorddecision.record.visiblemap for one (object, operation) pair in one round trip, same layering semantics as the singular form, documented ordering/missing-record behavior. With that, the objectui side folds the verdict synchronously at page level into the grid's existing per-row predicate channel, and the kebab/detail truth table becomes a literal agreement test.Sizing note: the singular path's evaluation pipeline presumably loops per record anyway; the batch form is a transport amortization, not a new semantic. If the spec side prefers a cap (e.g. max 200 recordIds), the consumer paginates under it happily.
Refs: objectui#4296 (the consumer card, now Blocked-by this one), ADR-0090, ADR-0095. Filed unassigned for the framework seat's triage.
Generated by Claude Code