Skip to content

feat(register): rename the register slug voorzieningen -> stackiq - #722

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/rename-register-slug-to-stackiq
Aug 24, 2026
Merged

feat(register): rename the register slug voorzieningen -> stackiq#722
rubenvdlinde merged 1 commit into
developmentfrom
feat/rename-register-slug-to-stackiq

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

The catalog register's slug moves with the app's identity:

before after
primary register slug voorzieningen stackiq
GEMMA reference register vng-gemma vng-gemma (unchanged, deliberately)

New repair step MigrateRegisterSlug (+ MigrateRegisterSlugDecisions + 17 unit tests), registered in both <post-migration> and <install>, ahead of RenameDutchSchemaSlugs and InitializeSettings — the step that triggers the register import.

Why a repair step is required

OpenRegister resolves a register by SLUG and by nothing else — RegisterMapper::find(), and ImportHandler::autoCreateRegisterIfApplication() on filters: ['slug' => $slug]. Its DoesNotExistException branch is not an error path — it is the "create a new one" path.

So shipping the renamed slug in the register JSON without renaming the existing row first renames nothing: the import finds no match, creates a second, empty register, and every stored object stays behind on the old row, reachable by nothing. Nothing errors. The app simply looks new.

Renaming the row first makes the import recognise it and update it in place.

Why it moves no data (measured, not assumed)

An object is bound to its register by numeric id, not by slug:

  • every shard table's _register column holds the id;
  • the tables themselves are named oc_openregister_table_<registerId>_<schemaId>, composed from $register->getId() at every call site.

The slug appears nowhere in the physical layout. The rename is therefore a one-column UPDATE on one row — on the dev instance this register is id 11, and it keeps that id. Every object, table, schema link and folder follows it untouched.

Step behaviour

  • Idempotent — renames only when the old slug is present and the new one is not. A second run finds nothing to do and says so. (It runs in both hooks, so running twice is the expected case.)
  • Non-destructive — one column, one row.
  • Never throws — under <install> an escaping exception aborts the install and the app never enables at all. Read failures, write failures and app-config type conflicts are logged and counted, never raised.
  • Refuses rather than merges — when old and new both exist it renames neither. OpenRegister caps a slug lookup at one row ordered by id, so two rows sharing a slug means the lower id silently wins every lookup; choosing between them is a decision about data, not a rename.
  • Re-points a stored app-config register value that still holds an old slug — guarded on the VALUE, not the key, so an app storing a numeric register id there (stackiq does, inside voorzieningen_config) never matches, and an admin's deliberate override is never overwritten.

x-openregister.app — an inconsistency this closes

For a type: application configuration, autoCreateRegisterIfApplication() reads $slug = $xOpenregister['app'] ?? $appIdthat field is a register slug, not an attribution label.

The app-id rename (#708) moved it to stackiq while the register row still said voorzieningen — precisely the fork described above. This PR closes that gap by moving the row to meet it. No change to the field itself was needed. ⚠️ Installs that have imported since #708 may already carry a forked, empty stackiq register; on those the step refuses and logs, rather than merging.

Related: RenameDutchCatalogColumns::REGISTER_SLUG was blanket-renamed to 'stackiq' by #708 while the register was still voorzieningen, so that step has been scoping to a register that did not exist. This PR makes that constant correct rather than aspirational. It is left as-is on purpose.

vng-gemma stays exactly as it is

It holds the VNG GEMMA reference model imported from outside this app. It is not this app's own store and its name is answered to elsewhere, so it is absent from SLUG_MAP and untouched everywhere in the sweep. A test asserts that (testTheGemmaReferenceRegisterIsNotTouched).

The sweep

voorzieningen is also ordinary Dutch vocabulary ("facilities/provisions"), so this was moved only where it names the register:

  • lib/Settings/softwarecatalogus_register.json — the components.registers.voorzieningen key, its slug, one targetId: voorzieningen/organisatie, and 3 seedData @self.register values
  • PHP constants: ContractApprovalService::SUBJECT_REGISTER, PortalContributionProvider::REGISTER, RenameDutchSchemaSlugs::REGISTER_SLUGS
  • PHP call sites: register: 'voorzieningen' in ContactpersonenController (×2), ContactpersoonService (×2); the slug comparison in SettingsService::configureVoorzieningen()
  • Vue/JS: register="voorzieningen", registerSlug:, register.slug === …, two component prop defaults
  • src/manifest.json — the tour's advanceOn.register
  • Postman: 7 path arrays + their raw URLs, the register_name environment variable in 3 env files, 2 collection variable definitions, and the integration collection's slug-resolution preflight
  • e2e ci-seed.sh required-registers list; dev/test shell scripts (objects/voorzieningen/<schema> URLs)
  • tests: the two register-shape tests that index components.registers[...], and the manifest sentinel map

Deliberately NOT moved (and why)

Left as-is Why
vng-gemma Reference data, not this app's store; answered to elsewhere
/api/voorzieningen/config route (appinfo/routes.php + ~12 callers) An app HTTP route, not a register slug. Moving it is a separate, breaking API change
@resolve:voorzieningen_register sentinel (~30 refs) A manifest sentinel token name, provisioned from the voorzieningen_config app-config key. It resolves to a numeric id; the name is not a slug
App-config keys voorzieningen_config, voorzieningen_register, voorzieningen_*_schema, getValueString(APP_ID, 'voorzieningen') Stored data. Renaming a config key orphans the operator's value silently
SettingsService registerTypes['voorzieningen'], the Voorzieningen settings tab key, voorzieningen* variables/methods/CSS classes Internal identifiers and a UI grouping key, not resolved against OpenRegister
The register's title: "Voorzieningen" and Dutch description Display strings
AangebodenGebruikService's labelForLogs: 'voorzieningen' (and its test) A human log label
l10n/** (37 locales × 2 files), GEMMA_*.xml, schema descriptions Dutch domain vocabulary — renaming a property or label would be a real regression
openspec/specs/** scenario bodies and prose ("the voorzieningen register") Touching Scenario lines re-scopes gate-19; the one literal that contradicted code (subjectRegister: voorzieningen) was moved
issues.md, issues/*.md, reacties/*.md, aanvullende-informatie.md, FIX_VERIFICATION_SUMMARY.md, BUG_FIX_*.md, README_DEBUG.md Historical records of what happened at the time
openspec/changes/archive/** Out of scope by instruction
softwarecatalog.conduction.nl docs-host refs #711's job — untouched here

Verification

  • php vendor/bin/phpunit --no-coveragecannot run standalone here: the bootstrap needs a real Nextcloud (Class "OC_App" not found). Pre-existing, not introduced by this PR; CI runs it in-container. Against a minimal local stub bootstrap the new suite is 17/17 green (48 assertions), and the three affected register-shape/sentinel suites are 13/13 (224 assertions). The full tests/Unit run under that stub shows 762 tests / 0 failures; its 155 errors are all missing OCA\OpenRegister\* classes the real bootstrap supplies.
  • npx vitest run233/233 tests pass. One suite (tests/vitest/adminApi.spec.js) fails to collect with window is not defined; that file and src/utils/adminApi.js are untouched by this PR — pre-existing.
  • composer cs:fix → 3 files fixed (the new ones). composer phpcs0 errors, 104 pre-existing @spec warnings, none in the new files.
  • composer phpmd → clean. composer psalmNo errors found. composer phpstan[OK] No errors.
  • eslint on the 11 changed frontend files → 0 errors.
  • gate-16 check_spec_coverage.py# count=0. check_spec_anchors.pyempty log. gate-19 check_e2e_coverage.py → PASS (123 refs). check_manifest_crossref.js → passed.

Follow-up (not in this PR)

stackiq has no MigrateRegisterApplicationId step. Schemas are matched by findByApplicationAndSlug() — the pair — so the #708 app-id move may have left the application column on the register/schema rows behind. Sibling apps ship a step for that; it needs its own measurement.

The catalog register's slug moves with the app's identity, and a repair step
renames the existing row BEFORE the import so the rename lands on the register
that already holds the data.

Why the repair step is not optional. OpenRegister resolves a register by SLUG
and by nothing else, and its not-found branch is not an error path — it is the
"create a new one" path. Shipping the renamed slug in the register JSON alone
would therefore rename nothing: the import finds no match, CREATES A SECOND,
EMPTY REGISTER, and every stored object stays behind on the old row, reachable
by nothing. Nothing errors; the app just looks new.

Why it moves no data. An object is bound to its register by NUMERIC id — every
shard table's `_register` column holds the id, and the tables are named
`oc_openregister_table_<registerId>_<schemaId>`. The slug appears nowhere in
the physical layout, so this is a one-column UPDATE on one row (id 11 on the
dev instance, which it keeps).

The step is idempotent, non-destructive, and never throws (it runs under
<install>, where an escaping exception aborts the install and the app never
enables). When both the old and new slugs already exist it REFUSES and renames
neither — merging two registers is a decision about data, not a rename. It also
re-points a stored app-config `register` value that still holds an old slug,
guarded on the VALUE rather than the key.

`vng-gemma` deliberately stays exactly as it is: it holds VNG GEMMA reference
data, is not this app's own store, and its name is answered to elsewhere.

Also fixes an inconsistency the app-id rename left behind: `x-openregister.app`
has said `stackiq` since #708 while the register row still said `voorzieningen`
— which is precisely the fork this step exists to prevent. Installs that have
imported since then may already carry an empty `stackiq` register, and the step
refuses rather than merging on those.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 00bbba7

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
composer ✅ 130/130
npm ✅ 720/720
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-24 08:49 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 474cc7e into development Aug 24, 2026
83 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/rename-register-slug-to-stackiq branch August 24, 2026 08:55
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