What was measured
Building examples/app-crm (pnpm --filter '@objectstack/example-crm...' build)
emits an author-time warning nothing has acted on:
⚠ object "crm_opportunity_line_item": detail object "crm_opportunity_line_item"
(master_detail "opportunity" → "crm_opportunity") has no object-level CRUD
grant in any permission set. A master-detail child derives its RECORD-level
access from the master (ADR-0055 controlled_by_parent), but object-level CRUD
is a SEPARATE gate that is never derived — role-bound non-admin users are
denied (403) before the parent-derived access is ever consulted (the silent
"can't submit the subtable" trap).
Grant "crm_opportunity_line_item" in at least one permission set that already
grants its master "crm_opportunity" — e.g. permissions[i].objects.crm_opportunity_line_item
= { allowRead: true, allowCreate: true, allowEdit: true }.
rule: security-master-detail-ungranted at objects[5].fields.opportunity
examples/app-crm/src/security/sales-positions.ts declares the app's only
non-guest permission set (crm_sales_user) with grants on 5 objects
(crm_account, crm_contact, crm_opportunity, crm_lead, crm_activity)
— crm_opportunity_line_item is not among them, even though the object exists
specifically to be edited inline on the Opportunity form
(inlineEdit: 'grid', crm_opportunity_line_item.object.ts).
Confirmed live: objectstack verify --app examples/app-crm/objectstack.config.ts --rls (run against main after #8060 bound the app's three positions to
crm_sales_user) shows every position persona probe-blocked on exactly this
one object — LIST 403, object gate refuses before record scope is ever
consulted — while the other 5 granted objects resolve normally.
Why this is a real gap, not decoration
The build's own lint rule (security-master-detail-ungranted) names the
consequence precisely: any role-bound (non-admin) user — including one holding
sales_rep/sales_manager, the positions this app ships to demonstrate
selling — gets a silent 403 the moment they try to add/edit a product line on
an Opportunity, despite fully owning the parent record. The example ships
inlineEdit: 'grid' specifically to make line-item editing look like a normal
part of the Opportunity form; nothing in the UI would explain the resulting
403 to whoever hits it.
Suggested fix
Add crm_opportunity_line_item to crm_sales_user's objects map in
examples/app-crm/src/security/sales-positions.ts, e.g.
{ allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false }
— matching the grant shape already used for its parent crm_opportunity.
Not fixed here
Found while implementing #8060 (bind positions → crm_sales_user), which was
scoped to the missing sys_position_permission_set join only, per the
triage ruling — deliberately not the permission set's own grant contents.
Filed unassigned, unlabeled for triage.
What was measured
Building
examples/app-crm(pnpm --filter '@objectstack/example-crm...' build)emits an author-time warning nothing has acted on:
examples/app-crm/src/security/sales-positions.tsdeclares the app's onlynon-guest permission set (
crm_sales_user) with grants on 5 objects(
crm_account,crm_contact,crm_opportunity,crm_lead,crm_activity)—
crm_opportunity_line_itemis not among them, even though the object existsspecifically to be edited inline on the Opportunity form
(
inlineEdit: 'grid',crm_opportunity_line_item.object.ts).Confirmed live:
objectstack verify --app examples/app-crm/objectstack.config.ts --rls(run againstmainafter #8060 bound the app's three positions tocrm_sales_user) shows every position personaprobe-blockedon exactly thisone object — LIST 403, object gate refuses before record scope is ever
consulted — while the other 5 granted objects resolve normally.
Why this is a real gap, not decoration
The build's own lint rule (
security-master-detail-ungranted) names theconsequence precisely: any role-bound (non-admin) user — including one holding
sales_rep/sales_manager, the positions this app ships to demonstrateselling — gets a silent 403 the moment they try to add/edit a product line on
an Opportunity, despite fully owning the parent record. The example ships
inlineEdit: 'grid'specifically to make line-item editing look like a normalpart of the Opportunity form; nothing in the UI would explain the resulting
403 to whoever hits it.
Suggested fix
Add
crm_opportunity_line_itemtocrm_sales_user'sobjectsmap inexamples/app-crm/src/security/sales-positions.ts, e.g.{ allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false }— matching the grant shape already used for its parent
crm_opportunity.Not fixed here
Found while implementing #8060 (bind positions →
crm_sales_user), which wasscoped to the missing
sys_position_permission_setjoin only, per thetriage ruling — deliberately not the permission set's own grant contents.
Filed unassigned, unlabeled for triage.