<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
# Which Problems Are Solved
- Setup step `79_backfill_unique_constraint_owners` re-ran after
`repeatable.done` stored `finalized: true` whenever the binary version
(git SHA) changed.
- Deleting an owner entity (project, org, grant, IDP, user) mixed a
keyed unique-constraint PK lookup with `owners @>` in one `DELETE …
WHERE a OR b`, which times out as `V3-C8l3V` on large tables.
# How the Problems Are Solved
- Setup 79 `Check` returns false whenever `lastRun.finalized` is true,
even if the version changed. First run still does not finalize;
`ForceFinalize` or the next unfinalized version still finalizes.
- Bulk lifecycle events emit either keyed unique-constraint deletes or
`RemoveByOwner`, never both, matching `ApplicationRemovedEvent`.
`UserRemovedEvent` is flag-only: empty username does not imply by-owner.
- Commands use the by-owner constructors when `ownerDeleteReady` and
skip keyed unique-constraint deletes on cascaded child events in the
same push. After finalize those child rows are tagged with the parent
owner.
- `handleUniqueConstraints` executes keyed deletes and `RemoveByOwner`
as two statements so a mixed push cannot OR a PK lookup with a GIN scan.
That split is the timeout fix; cascade skip is an optimization on top.
# Additional Changes
- Cascade events (`UserGrantCascadeRemoved`, membership cascade, IDP
link cascade) can omit unique-constraint deletes via
`SkipCommandUniqueConstraints`. Skip is a no-op on commands that are not
skippers.
- After backfill, unmatched rows are counted per `unique_type`. Leftover
`usernames` or `org_name` log a warning and still finalize. Owner-delete
is not held back for orphan rows 79 cannot stamp.
# Additional Context
- Until 79 stores `finalized: true`, lifecycle events delete unique
constraints by key only. Child rows such as `project_role` and `appname`
are not removed when the parent project is deleted. That matches pre-79
behavior; after finalize, `RemoveByOwner` covers them.
- Unmatched rows (`owners = '{}'`) are not retried by this job.
Owner-delete and cascade skip assume tags were stamped.
- After `finalized: true`, extra files under `cmd/setup/79/` will not
run, including on a new binary. `ForceFinalize` does not restamp a
finalized install. A later stamp needs a new setup step, not a version
bump of 79.
- Main-only projections are unchanged: `16_group_name.sql` /
`GroupProjectionTable`, and `organization_settings` in
`02_usernames.sql`. Groups stay keyed-only on `GroupRemovedEvent`; after
finalize, `group_name` is tagged `org:…` so org owner-delete covers
them.
- Follow-up for unique-constraint owners backfill (setup 79).
- Backports: v4.x-wo-te (#12831), v4.x (#12834).
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a
href="https://cursor.com/agents/bc-64e93fdd-8395-4027-9d86-1bfc7c3d13e8?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a
href="https://cursor.com/background-agent?bcId=bc-64e93fdd-8395-4027-9d86-1bfc7c3d13e8&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: s2s4gq4jby <s2s4gq4jby@privaterelay.appleid.com>