Repository navigation
fix(organisations): never take over an existing organisation on a name match (REVIEW) - #1418
Merged
Merged
Conversation
Assisted-by: ClaudeCode:claude-opus-5-5
…to find, fetch and process steps processRelatedContactPersons drops its unused organisation object parameter and hands the work to findRelatedContacts, contactsFromOrganisationList, fetchListedContact, processRelatedContact and completeRelatedContact, each with at most two parameters (the sync run array, stats held by reference). Lookups, saves, log lines and stats are unchanged (golden of the previous commit). Assisted-by: ClaudeCode:claude-opus-5-5
…of an organisatie object The OrganisationMapper stub gains findBySlug, as on OpenRegister's mapper. Assisted-by: ClaudeCode:claude-opus-5-5
…nto refresh, adopt-by-slug and create steps ensureOrganisationEntity takes the organisatie object and one run array (stats held by reference, and an explicit sendEmails at every call site) and hands the work to fullOrganisationObject, refreshOrganisationEntity, adoptOrganisationEntityBySlug, createOrganisationEntity, mailOrganisation and makeEntityOwner. The public wrapper keeps its signature. Lookups, saves, mails, log lines and stats are unchanged (golden of the previous commit). Assisted-by: ClaudeCode:claude-opus-5-5
… it behaves today Creating an organisation escapes with an Error on both paths: the signed-in path calls OrganisationMapper::createWithUuid, which OpenRegister does not have, and the anonymous path saves and then reads the absent user. Assisted-by: ClaudeCode:claude-opus-5-5
…m the OpenRegister create and update steps Neither createOrganisationInOpenRegisterInternal nor updateOrganisationInOpenRegister reads the OrganisationService it was handed. The public create wrapper still fetches it first, so a missing OpenRegister fails at the same point. The golden masks mock and anonymous class names, which depend on test order; it was re-recorded on the code before this commit. Assisted-by: ClaudeCode:claude-opus-5-5
…rg-sync-related-contacts
…fuse-slug-adoption
…e match When an organisatie had no OpenRegister organisation yet, the sync looked one up by the slug its name gives and, on a match, wrote the organisatie's uuid onto that organisation: a registration named like an existing organisation took it over. ContactPersonHandler did the same for a contact person's organisation and then added the contact's user to it. The slug was also computed without OpenRegister's 100-character cut, so a long name missed the match and OpenRegister's own create overwrote the existing organisation. Now a taken slug is refused on both paths: nothing is created or changed, a neutral warning names the organisatie and the organisation holding the slug, and the sync counts it under slugConflicts. OpenRegister's OrganisationSlugConflictException (openregister#5462, a 409) is handled the same way. OrganisationSlug computes the slug exactly as OpenRegister does. `occ stackiq:organisation:link <organisatie> <organisation>` lets an administrator link the two explicitly. It refuses an unknown organisatie or organisation, an organisatie that already has one, and an organisation that belongs to another organisatie. Assisted-by: ClaudeCode:claude-opus-5-5
Contributor
Author
|
Local evidence on the head: full unit suite (phpunit-unit.xml, pcov, --fail-on-risky, through heavy.sh) exit 0, 1633 tests, 0 failures, 0 risky, 59 skipped. Hydra gates --scope-to-diff --base origin/development: 37 of 37 applicable gates ran, 0 FAIL. |
Contributor
Quality Report — ConductionNL/stackiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ❌ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 127/127 | |||
| npm | ❌ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1613/1613 · 20 skipped | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development. |
||||
| Hydra gates | ❌ |
Quality workflow — 2026-11-04 01:17 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
REVIEW (organisation ownership and membership). Held. This implements Ruben's decision on adopt-by-slug. It is stacked on #1405, because it changes
adoptOrganisationEntityBySlug, which #1405 extracted. #1405's commits leave this diff as soon as it merges.The problem
When an organisatie had no OpenRegister organisation yet,
OrganizationSyncServicelooked one up by the slug of the organisatie's name. On a match it wrote the organisatie's uuid onto that organisation and made it the organisatie's owner. A registration named like an existing organisation therefore took that organisation over.Two related paths had the same flaw:
ensureOrganizationEntitydid the same for a contact person's organisation, andaddUserToOrganizationEntitythen added the contact's user to that other organisation.createOrganisation()slug collision then overwrote the existing organisation. ORX8's openregister#5462 fixes that side with a 409.What changes
slugConflicts, and the organisatie stays among the active organisaties without an entity.OrganisationSlug::fromName()mirrorsOrganisationService::generateSlug(): lower case, runs of other characters become one hyphen, trimmed, and cut at 100 characters.OrganisationSlugConflictException(409) is handled as the same refusal.OrganisatieService::createOrganisationInOpenRegister()passes it on instead of swallowing it; every other failure still ends as null. The class is matched by name, so older OpenRegister releases without it keep working.occ stackiq:organisation:link <organisatie-uuid> <organisation-uuid>.Evidence
Golden, recorded on the old code first.
OrganizationSyncEntityGoldenTestgains five cases: same name as another organisation, long name with the slug cut at 100, long name with a free slug, OpenRegister refusing the slug, and the name "Unknown". The old cases are unchanged. The changed recordings:setUuid,save, owner write)slugConflicts: [o-1], warningfindBySlugwith the 100-character slug, refused, no createFailing first:
testASameNamedRegistrationDoesNotCaptureTheExistingOrganisation(a short and a 120-character name) failed on the old code.ContactPersonHandlerSlugConflictTest(same name not joined, refused slug joins nothing with a warning and no error, free slug creates and joins) failed on the old code.OrganisatieServiceSlugConflictTest(the refusal is passed on, other failures are null) failed on the old code.Link:
testLinkOrganisationEntitycovers all five outcomes and asserts the writes.OrganisationLinkCommandTestcovers 6 outcomes and exit codes.Mutants (capped, with a no-op control that survived):
Numbers (PHP, lizard, against development plus #1405)
Verified locally
--fail-on-risky: 78 tests, exit 0.@uses OrganisationSlugis added to the tests that cover the two services.Assisted-by: ClaudeCode:claude-opus-5-5