Skip to content

fix(tenant): follow the merge before the UUID becomes a scope - #3336

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/tenant-resolution-follows-the-merge
Sep 2, 2026
Merged

fix(tenant): follow the merge before the UUID becomes a scope#3336
rubenvdlinde merged 1 commit into
developmentfrom
fix/tenant-resolution-follows-the-merge

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Why

resolveMergeTarget() and findByUuidFollowingMerge() landed in tasks 3.1 and 3.2 of consolidate-organisation-on-or, and then nothing called either of them. A merge was recorded and never followed.

That is the quiet kind of defect. A merged-away organisation still loads, its scoped queries still return rows, and nothing anywhere reports an error. The rows just come back under a tenant boundary that no longer applies.

What changed

Both entries into fetchActiveOrganisationFromDatabase() now walk the chain.

The stored active UUID is the obvious one. The auto-pick is the one that matters more: a user's membership list can still name a merged-away organisation, so without a walk there, the first login after a merge writes the dead UUID straight back into config and re-creates the defect it just fixed.

Three properties worth calling out:

  • The walk is guarded on isMerged(). The flag lives on the row that was merged away, and almost no row carries it, so the common case costs no extra query.
  • Membership is checked against the survivor. A user the merge did not carry over falls through to their own organisation list, rather than being handed a tenant on the strength of a membership that ended. It fails closed.
  • The survivor is written back to user config. Following on every read is correct but leaves the dead UUID in place forever; persisting it once makes the walk a one-off per user per merge.

An unresolvable survivor keeps the row already in hand and logs a warning. A read path is the wrong place to throw over a data defect, and the row it already holds is real.

Verification

  • 6 new tests in tests/Unit/Service/ActiveOrganisationFollowsMergeTest.php, including the negative case (an unmerged row is never walked) and the fail-closed case.
  • The full organisation suite: 634 tests, 1496 assertions, green.
  • phpcs, psalm and phpstan clean on the changed file.

Closes task 3.3 of consolidate-organisation-on-or.

resolveMergeTarget() and findByUuidFollowingMerge() were built and then
called by nothing, so a merge was recorded and never followed. A user
whose active organisation had been merged away kept resolving to the row
that was merged: it still loads, its queries still return rows, and every
one of them runs under a tenant boundary that no longer applies.

Both entries into fetchActiveOrganisationFromDatabase() walk it now. The
stored active UUID is the obvious one. The auto-pick is the one that
matters more: a user's membership list can still name a merged-away
organisation, so without the walk there the first login after a merge
writes the dead UUID straight back into config and re-creates the defect.

The walk is guarded on isMerged(), because the flag lives on the row that
was merged away and almost no row carries it, so the common case costs no
query at all. Membership is then checked against the SURVIVOR, which
fails closed: a user the merge did not carry over falls through to their
own organisations rather than being handed a tenant on the strength of a
membership that ended. An unresolvable survivor keeps the row already in
hand and logs, because a read path is the wrong place to throw over a
data defect.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 92a202d

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
test-l10n-parity
format
check-schema-l10n
check-l10n-js
composer ✅ 174/174
npm ✅ 543/543
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 11:55 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 2c23de6 into development Sep 2, 2026
47 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/tenant-resolution-follows-the-merge branch September 2, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant