Filed unassigned by the domain:devx @ objectui execution seat while converting batch 2 of objectui#7307 (PR #8013). No domain:*, type or grade set — triage's to produce. This is an OBSERVATION, not a defect report: nothing is broken, no assertion is wrong, and objectui#7307 needed none of it. It is recorded because it was measured rather than guessed, and the measurement is cheap to lose.
What was measured
A stack probe on the network-escape guard's attribution point, run over the four batch-2 files, attributed every escaped request. packages/plugin-detail/src/DetailView.tsx calls useRecordEditable twice:
DetailView.tsx:290 const canEditRecord = useRecordEditable(..., 'edit' ...)
DetailView.tsx:296 const canDeleteRecord = useRecordEditable(..., 'delete' ...)
Each call owns its own effect and its own verdictCache key, so each issues its own POST /api/v1/security/explain. Counted in the guard's attribution lines before the fix, the ratio is exactly 2 requests per drawer open in every one of the four files (3 opens gave 6 lines, 6 opens gave 12, 2 opens gave 4, 2 opens gave 4).
Why it is worth writing down
The route already supports the batched form. packages/plugin-grid/src/hooks/useRecordCrudVerdicts.ts posts recordIds and reads decision.records, one request for a whole page of rows; useRecordEditable posts a single recordId and reads decision.record.visible, one request per operation. So the detail header pays two serial round trips for two verdicts on the SAME record, on a route whose batched shape exists and is exercised elsewhere in this repo.
Both hooks fail open and both memoise, so the user-visible cost is limited to the first open of each record: two requests instead of one, and two chances for a slow or refused verdict rather than one.
What this is NOT
- Not a correctness problem. Both verdicts are read independently and both are correct.
- Not caused by, and not fixed by, objectui#7307's doubles — the double answers both shapes, so the conversion is neutral to whatever is decided here.
- Not the same finding as objectui#7996, which is about a landed double answering a key that neither hook reads. This one is about the product code's request shape, not about a test's stub.
Whether the two-operation lookup should fold into one batched request (a operations array, or the existing recordIds form used twice-in-one) is a design question for whoever owns the explain contract; it may well be deliberate, since the two verdicts have different cache lifetimes.
Duplicate check before filing: a semantic search over this repo's issues for this shape returned only closed cards (objectui#6332, objectui#4296, objectui#4302), none of which covers the request count; a control query in the same session returned objectui#7307 and objectui#7765, so the empty read is a real read and not a broken search.
Refs: objectui#7307 (the burn-down that measured it) · PR #8013 (batch 2) · objectui#7996 (the sibling finding, different subject).
Attribution, in prose because an issue body's footer block does not survive creation: generated by Claude Code, session session_01MM7kaS4dPpYHV5BsMyu4tQ.
Filed unassigned by the
domain:devx@ objectui execution seat while converting batch 2 of objectui#7307 (PR #8013). Nodomain:*, type or grade set — triage's to produce. This is an OBSERVATION, not a defect report: nothing is broken, no assertion is wrong, and objectui#7307 needed none of it. It is recorded because it was measured rather than guessed, and the measurement is cheap to lose.What was measured
A stack probe on the network-escape guard's attribution point, run over the four batch-2 files, attributed every escaped request.
packages/plugin-detail/src/DetailView.tsxcallsuseRecordEditabletwice:Each call owns its own effect and its own
verdictCachekey, so each issues its ownPOST /api/v1/security/explain. Counted in the guard's attribution lines before the fix, the ratio is exactly 2 requests per drawer open in every one of the four files (3 opens gave 6 lines, 6 opens gave 12, 2 opens gave 4, 2 opens gave 4).Why it is worth writing down
The route already supports the batched form.
packages/plugin-grid/src/hooks/useRecordCrudVerdicts.tspostsrecordIdsand readsdecision.records, one request for a whole page of rows;useRecordEditableposts a singlerecordIdand readsdecision.record.visible, one request per operation. So the detail header pays two serial round trips for two verdicts on the SAME record, on a route whose batched shape exists and is exercised elsewhere in this repo.Both hooks fail open and both memoise, so the user-visible cost is limited to the first open of each record: two requests instead of one, and two chances for a slow or refused verdict rather than one.
What this is NOT
Whether the two-operation lookup should fold into one batched request (a
operationsarray, or the existingrecordIdsform used twice-in-one) is a design question for whoever owns the explain contract; it may well be deliberate, since the two verdicts have different cache lifetimes.Duplicate check before filing: a semantic search over this repo's issues for this shape returned only closed cards (objectui#6332, objectui#4296, objectui#4302), none of which covers the request count; a control query in the same session returned objectui#7307 and objectui#7765, so the empty read is a real read and not a broken search.
Refs: objectui#7307 (the burn-down that measured it) · PR #8013 (batch 2) · objectui#7996 (the sibling finding, different subject).
Attribution, in prose because an issue body's footer block does not survive creation: generated by Claude Code, session
session_01MM7kaS4dPpYHV5BsMyu4tQ.