An upsert still needs create and update checks
A PUT that upserts feels like one endpoint. Authorization still has two decisions.
If the resource already exists, you need update on that object. If it does not exist yet, you need create in the parent collection or tenant — not a free pass because the client chose the id.
Common bug: the handler only calls canUpdate(id). On a miss it inserts. Callers who cannot create still mint rows by picking unused ids.
Fix: load-or-decide first. Missing row → authorize create. Existing row → authorize update. Same path, two checks. Return 403 (or 404 if you hide existence) when either fails — never “upsert anyway.”
One HTTP method does not collapse two permissions.
