-
Mahmoud Aglan authored
Everything so far was validated against one client database. The tenants are not all shaped like OC-Sport, every one of them runs `migrate --force` on its next container start, and there is no staging in between — so the risk worth chasing was never OC-Sport, it was the tenant I cannot see. Reproduced by building one. On a database where a programme's groups run at two branches, 2026_09_13_000002 declines to guess and falls through to the main-branch fallback. The group at the other branch keeps pointing at a programme that branch can no longer see: its name renders blank, and PricingService cannot find a base price for it, so the enrolment cannot be billed at all. `لا يوجد سعر محدد`, on a group that worked the day before. Silent, and caused by the migration rather than found by it. - 2026_09_13_000003 replicates instead of picking a winner. The programme keeps its identity where it was pinned, every other branch using it gets a copy of its own, and that branch's groups, enrolments, active prices and product bundles are repointed at the copy. Nothing is deleted and nothing changes branch. Two branches running "فريق 2018" now have two rows that can diverge, which is the point — the same answer the product already gives for groups. Verified on a constructed tenant carrying the fault, and a no-op on OC-Sport. - `php artisan branch:audit` reports what is silent in the UI: strictly-scoped rows with no branch (not a leak — a disappearance, present in SQL and on no screen), children in a different branch from their parent, and programmes with live enrolments and no active price. Exits non-zero so it can gate a deploy check. On OC-Sport it finds one genuine pre-existing problem — programme #32 has six active enrolments and no price at all — and no branch integrity faults. - BranchValidationRulesTest closes a gap in the suite itself: every other branch test needs a restored Postgres tenant and skips without one, so on an ordinary `php artisan test` none of them run. This one reads source, so it runs everywhere — banning a raw `exists:` rule on a branch-owned table (they compile to a raw query that accepts any id in the academy, and the property feeding one is usually browser-settable), and failing when a model carries branch_id in $fillable without declaring how it is scoped. - Event now declares BRANCH_SCOPE_EXEMPT with its reasoning, so that being academy-wide reads as a decision rather than as a model somebody forgot. Standard suite: 231 tests, no failures. Against a restored tenant: 28 branch tests, 23,093 assertions — and the same suite passes against the constructed tenant that carried the split fault. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
e22cd9e4