← all branches

feat/sites-1n

needs attentionviewing older commit
23c04fc · incrementalpre-PRreviewed 2026-07-07 20:30 UTC3H · 3M · 2L · 3I
The branch
Purpose
Correct the Site↔RPU 1-to-N relationship: stop fusing unrelated RPUs onto one site, dedup only when internal ID and name both match, and give users UX to see and manage the relationship.
Goal
Make the Site↔RPU relationship correct, visible, and manageable — shared site-resolution decision, match-driven Nuevo RPU drawer, editable Site cell with pencil→modal, and group-by-site view.
Sub-goals
  • SG-1: Shared site-resolution decision + rewired coordinators (done)
  • SG-2: Site match/search endpoint — extended sites.list with search + contractCount (done)
  • SG-3: createFromWizard accepts existingSitePublicId (done)
  • SG-4: Nuevo RPU drawer — Crear/Usar-existente segmented with debounced search (done)
  • SG-5: Site cell pencil → SiteEditModal: edit/move/create-new (done)
  • SG-6: Agrupar por sitio toggle (done)
  • SG-7: Batch preview grouping with existing-site flags (done)
  • SG-8: 4 analytics events + CLAUDE.md update (done)
The changes (whole branch)
What
This incremental window (2 commits): org-scope the SUC move candidate in linkSiteToContractShell (uses findSitesForContractInOrg instead of global findByContractId to prevent cross-org SUC hijacking) + correct stale RBAC annotations in sites.contract.ts (admin+ → member+ on create/update/delete/updateLocation — comments now match the handlers).
Why
Global findByContractId returned all org SUCs for a contract, so in the shared-resource model (same utility contract linked across multiple orgs) the shell could accidentally move another org's SUC. The RBAC comment corrections reflect the actual handler enforcement that was already in place.
Areas
apps/platform+1211173domains/cross-domain+452131domains/utility/src/site-utility-contract+615domains/core/src/site+331packages/api + packages/analytics+632docs/development + .branch + .github/workflows+410108
Blast
~25 files, +2,230/−420 across the branch; this incremental window is narrow (+17/−9, 2 files).
auth multi-tenant test-gap
CI· No PR open — no CI rollup availableCodeRabbit· No .coderabbit.yaml in repo

Findings · 12

correctness2

info

newSite-null guard is safe: decision rejects before existingSuc is inspected

domains/utility/src/site-utility-contract/site-utility-contract.shells.ts:300

When newSite is null, decideLinkSiteToContract returns SiteNotFound before reaching the MoveLinkedSite/FirstClaim branch. Outcome identical to old code. No silent failure introduced.

info

Ordering improvement: findSitesForContractInOrg adds deterministic ORDER BY

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:130

Old findByContractId had no ORDER BY — existingLinks[0] was nondeterministic. New query orders by createdAt ascending, making [0] the oldest org-scoped SUC. Correctness improvement.

security2

high

DELETE site at member-level with no SUC dependency guard enables data orphaning

domains/core/src/site/site.queries.ts:152

getDependencies is a stub returning {assets:0}; no FK on site_utility_contracts.site_id→sites.id. Any org member can delete a site with active SUC rows, silently breaking contract bindings and cascade-deleting monitoring subscriptions. Pre-existing bug amplified by the member-level RBAC change.

medium

No FK on site_utility_contracts.site_id creates a TOCTOU race window

domains/utility/src/site-utility-contract/site-utility-contract.shells.ts:300

A concurrent site deletion between the handler read and shell write would produce a dangling site_id. Pre-existing structural gap made more reachable at member-level. Fix: add FK with ON DELETE RESTRICT or extend getDependencies to count SUCs.

conventions1

medium

Branch name in JSDoc RBAC comments will rot (3 occurrences)

apps/platform/src/api/contracts/sites.contract.ts:52

'— see feat/sites-1n RBAC audit' appears in 3 comment blocks. CLAUDE.md prohibits referencing the current task/fix/branch in comments. Drop the trailing clause; the preceding sentence conveys the policy without the volatile reference.

improvement2

low

Null-guard short-circuit semantics are implicit

domains/utility/src/site-utility-contract/site-utility-contract.shells.ts:300

contract && newSite guard returns [] when either is null; the decision errors on the missing entity anyway. The fallback to [] never has semantic meaning — a reader must trace the decision to see why it is safe.

info

updateLocation RBAC comment wording diverges from 3 sibling endpoints

apps/platform/src/api/contracts/sites.contract.ts:230

Uses 'Any member of the organization, checked via site lookup' vs the template 'Any member of the organization (handlers enforce member-level; ...)' used by create/update/delete. Minor inconsistency, no correctness issue.

test-coverage5

critical

No integration test for the cross-org SUC isolation fix in linkSiteToContractShell

domains/utility/src/site-utility-contract/__tests__/site-utility-contract.decisions.test.ts:1

Behavioral change lives in the shell fetch-step, not the decision. Missing scenario: Org A has SUC for contract C; Org B member moves C to a different Org B site — should move only Org B's SUC, leave Org A's untouched. No shells integration test file exists at all.

high

FirstClaim path for cross-org contract sharing is untested

domains/utility/src/site-utility-contract/__tests__/site-utility-contract.decisions.test.ts:1

When Org A has a SUC for contract C and Org B calls linkSiteToContractShell, the shell routes to FirstClaimLinkedSite (insert new SUC). No test verifies this path — a future global-dedup change would collapse two org claims silently.

high

resolveVersionServerSide OCC path is untested

domains/utility/src/site-utility-contract/__tests__/site-utility-contract.decisions.test.ts:79

Server-side version resolution now uses the org-scoped existingSuc. No test verifies in-transaction version threading or that a concurrent move surfaces as a 409 via reassignSiteWithVersion.

medium

findSitesForContractInOrg query has no integration test

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:110

Load-bearing primitive for the cross-org fix. Should verify: (1) returns only org-scoped SUCs when contract has multi-org links; (2) returns [] for no match in org; (3) ORDER BY createdAt makes [0] deterministic.

low

No shell-level test for null contract/site early-exit path

domains/utility/src/site-utility-contract/site-utility-contract.shells.ts:300

The contract && newSite guard now skips the DB query when either is null. Decision tests cover the not-found errors at the decision layer but no test verifies the shell propagates them end-to-end.

History · 8 commits

  1. 7b5ff24needs attentionincremental0H · 2M · 3L2026-07-09 00:58
  2. 6231fe5needs attentionincremental0H · 2M · 4L2026-07-08 22:34
  3. 8fa38b5needs attentionincremental2H · 4M · 5L2026-07-08 01:45
  4. 83fd5a0needs attentionincremental3H · 8M · 11L2026-07-08 01:37
  5. 23c04fcneeds attentionincremental3H · 3M · 2L2026-07-07 20:30current
  6. e70f9f5blockedincremental6H · 11M · 6L2026-07-07 19:28
  7. 499b1a5blockedincremental6H · 9M · 5L2026-07-07 18:26
  8. ff23301needs attentionincremental2H · 6M · 5L2026-07-07 01:05