Out-of-scope finding from objectui#3055 (PR pending on claude/issue-3055-record-approval-actions). Filed per PD #10 — unassigned, ungraded. Concrete defect, not observation-class: a correctly-authored predicate hides the action it guards.
Fact
useCondition(pred, recordData, { throwOnError: true }) builds its evaluator as new ExpressionEvaluator({ ...scope, ...context }) (packages/react/src/hooks/useExpression.ts:132), where context is the row spread at the top level. So only the shorthand spelling resolves:
| predicate |
verdict |
status == "pending" |
evaluates |
record.status == "pending" |
record is not defined → throws → fail-closed → hidden |
record.* is not a mistaken spelling — it is the canonical one:
ExpressionEvaluator.evaluateCelCondition binds it explicitly: "the record key as the record namespace and the whole context bag as top-level scope" (packages/core/src/evaluator/ExpressionEvaluator.ts:276-291);
- the record header and list rows bind the row three ways through
evalRowPredicate — record.status, bare status, data.status (packages/components/src/renderers/layout/containers.tsx:1074-1082);
- the server enforces with it.
The renderers that bind only the bare root:
| renderer |
line |
action-button.tsx |
:59 (visible), :69 (disabled), :70 (enabled) |
action-icon.tsx |
:46, :50, :51 |
action-menu.tsx / action-group.tsx |
pass no record context at all |
Measured (probe against the shipped evaluator, throwOnError: true, row { id, status, viewer: { can_act: true } }):
ROOT-CONTEXT => threw: true, 'record is not defined'
WITH-RECORD-KEY => threw: false, true
Why it is worth fixing rather than noting
This was live, not theoretical. DeclaredActionsBar had the same root-only binding, and every declared action on framework sys_approval_request gates on record.viewer.can_act / record.viewer.is_submitter (framework#3310 / #3424) — so the entire server-declared approval decision set evaluated to "hidden" on every surface that bar renders, the approvals inbox included. objectui#3055's PR fixes it there by binding the row all three ways; the four renderers above still carry the original binding.
The failure is silent in the worst direction: fail-closed means the button simply is not there, which is indistinguishable from "the gate said no".
Suggested fix (left to triage, not self-selected)
- Bind the record the three canonical ways in each renderer's predicate context (what objectui#3055's PR does for the declared-action bar), or
- move the four renderers onto
evalRowPredicate, the helper the header already uses, so there is one binding rule rather than two.
Leaning 2 — one implementation behind one name is the standing preference in this area (objectui#3367, objectui#3842) — but 1 is the smaller step and does not disturb the objectui#3492 family's pinned evaluation entry.
Dedup
Searched open issues in this repo for visible predicate record. root, declared action visible record.viewer, predicate scope record root, action-button visible bare field binding useCondition. One neighbour, not a duplicate: objectui#3888 is about an ADR paragraph asserting fail-open for server enforcement, whose unbound-root counter-example is the same family of fault but a documentation fix in another repo's rule validator. No issue covers the client renderers' binding.
Refs: objectui#3055, objectui#3888, objectui#3835, objectui#3871, framework#3310, framework#3424.
Out-of-scope finding from objectui#3055 (PR pending on
claude/issue-3055-record-approval-actions). Filed per PD #10 — unassigned, ungraded. Concrete defect, not observation-class: a correctly-authored predicate hides the action it guards.Fact
useCondition(pred, recordData, { throwOnError: true })builds its evaluator asnew ExpressionEvaluator({ ...scope, ...context })(packages/react/src/hooks/useExpression.ts:132), wherecontextis the row spread at the top level. So only the shorthand spelling resolves:status == "pending"record.status == "pending"record is not defined→ throws → fail-closed → hiddenrecord.*is not a mistaken spelling — it is the canonical one:ExpressionEvaluator.evaluateCelConditionbinds it explicitly: "therecordkey as therecordnamespace and the whole context bag as top-level scope" (packages/core/src/evaluator/ExpressionEvaluator.ts:276-291);evalRowPredicate—record.status, barestatus,data.status(packages/components/src/renderers/layout/containers.tsx:1074-1082);The renderers that bind only the bare root:
action-button.tsx:59(visible),:69(disabled),:70(enabled)action-icon.tsx:46,:50,:51action-menu.tsx/action-group.tsxMeasured (probe against the shipped evaluator,
throwOnError: true, row{ id, status, viewer: { can_act: true } }):Why it is worth fixing rather than noting
This was live, not theoretical.
DeclaredActionsBarhad the same root-only binding, and every declared action on frameworksys_approval_requestgates onrecord.viewer.can_act/record.viewer.is_submitter(framework#3310 / #3424) — so the entire server-declared approval decision set evaluated to "hidden" on every surface that bar renders, the approvals inbox included. objectui#3055's PR fixes it there by binding the row all three ways; the four renderers above still carry the original binding.The failure is silent in the worst direction: fail-closed means the button simply is not there, which is indistinguishable from "the gate said no".
Suggested fix (left to triage, not self-selected)
evalRowPredicate, the helper the header already uses, so there is one binding rule rather than two.Leaning 2 — one implementation behind one name is the standing preference in this area (objectui#3367, objectui#3842) — but 1 is the smaller step and does not disturb the objectui#3492 family's pinned evaluation entry.
Dedup
Searched open issues in this repo for
visible predicate record. root,declared action visible record.viewer,predicate scope record root,action-button visible bare field binding useCondition. One neighbour, not a duplicate: objectui#3888 is about an ADR paragraph asserting fail-open for server enforcement, whose unbound-root counter-example is the same family of fault but a documentation fix in another repo's rule validator. No issue covers the client renderers' binding.Refs: objectui#3055, objectui#3888, objectui#3835, objectui#3871, framework#3310, framework#3424.