Skip to content

crud-permission-matrix: an allowed member create of showcase_task returns 500 while the row is written (summary recompute runs under caller context) #7673

Description

@huangyiirene

Symptom

An allowed create returns 500 while the row is written. Boot showcase, sign up a plain member (no explicit grants), admin creates a project, then as the member:

POST /api/v1/data/showcase_task {title, project:<id>, status:'todo'}

  • Expected 201 — the access matrix says create:true and /security/explain confirms allowed=true.
  • Actual 500 INTERNAL_ERROR, yet an admin read shows the task did persist with owner_id = the member. Reproduced 4×.

Server log: WARN Roll-up summary recompute failed {childObject:showcase_task, parentObject:showcase_project, field:task_count, error: Access denied: operation update on showcase_project …} ×2, then ERROR SummaryRecomputeError '…the triggering records WERE written (summary values may be stale)'.

Contrast isolating it: the identical POST succeeds 201 for showcase_contributor (holds allowEdit on showcase_project) and for admin.

Root cause

Located by the run at packages/objectql/src/engine.ts. recomputeSummaries issues the parent roll-up write under the caller's execution context (opCtx.context), so the internal recompute passes through security as the caller. showcase_project declares two Field.summary roll-ups over showcase_task, and member_default grants read but not edit on showcase_project, so the recompute is refused with PERMISSION_DENIED. withTransientRetry then retries a non-transient permission denial, and the engine throws SummaryRecomputeError, which REST maps to 500.

Confirmed still present on origin/main: recomputeSummaries (engine.ts ~L6112) calls this.update(desc.parentObject, …, { context: execCtx }) with execCtx = opCtx.context, and the update path (engine.ts ~L8478/8503) still throws SummaryRecomputeError on any failure.

Reproduction

  1. Boot showcase; sign up a plain member_default persona with no explicit grants.
  2. As admin, create a showcase_project.
  3. As the member: POST /api/v1/data/showcase_task {title, project:<project id>, status:'todo'}.
  4. Observe 500 INTERNAL_ERROR; re-read as admin → the task row exists with owner_id = the member.

Suggested fix

Run the roll-up recompute in a system context — it is an engine-internal derived write, not a caller write. At minimum, stop retrying a permission denial via withTransientRetry, and stop mapping a committed write to a 500.

Source

Extracted from the QA run #7637 (framework 92f26f7, console 09987b680).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions