Skip to content

feat: rename the app id from softwarecatalog to stackiq - #708

Merged
rubenvdlinde merged 14 commits into
developmentfrom
feat/rename-softwarecatalog-to-stackiq
Aug 23, 2026
Merged

feat: rename the app id from softwarecatalog to stackiq#708
rubenvdlinde merged 14 commits into
developmentfrom
feat/rename-softwarecatalog-to-stackiq

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Renames the app id softwarecatalog -> stackiq, with the data migration that makes it safe.

Why the migration is the point

Nextcloud namespaces oc_appconfig and oc_preferences by app id, and there is no in-place app-id upgrade. After the rename every stored row is unreachable — and because every reader supplies a default, nothing errors. Settings just revert, silently. MigrateAppConfigKeys and MigrateUserPreferences run first in both <install> and <post-migration>; MigrateBackgroundJobClasses deregisters jobs orphaned by the namespace move.

Fixed on the way through

lib/Service/StackiqService.php had already been renamed on disk but still declared class SoftwareCatalogueService, and the Service\SoftwareCatalogue sub-namespace still pointed at a directory now called Service/Stackiq. Both are corrected, so the class, the namespace and the path finally agree.

Postman: the path array was rewritten alongside the raw URL. Postman builds the request from the array, so rewriting only raw would leave the collection quietly hitting a different URL than the diff shows.

FROZEN — and what renaming each would have done

Frozen Consequence
softwarecatalog.conduction.nl Verified live (HTTP 200); stackiq.conduction.nl returns 000. The manifest documentationUrl and footer href would point at nothing.
softwarecatalogus The Dutch VNG catalogue — an external system's name, not this app's id. Covers the GEMMA fixture XMLs and lib/Settings/softwarecatalogus_register.json.
x-openregister.app in the register descriptor OpenRegister attributes the register by this field and may match on it. Renaming risks a duplicate register or an orphaned one. Reported rather than guessed — worth a second opinion.
openspec/changes/archive/** History. Rewriting it breaks every @spec path pointing into it.
openspec/coverage-report.* A dated audit snapshot. Rewriting its observations to say something it never observed is falsification.
LEGACY_APP_ID in both Migrate* steps, and the old namespace inside MigrateBackgroundJobClasses These name what the steps read from. "Correcting" them breaks the migration in the one direction nothing would notice.
codeberg.org/Conduction/SoftwareCatalog URLs A different host and org, stale independently of this rename.

Register slugs are voorzieningen and vng-gemma — neither contains the app id, so neither moved.

Local verification

phpcs 0 errors · psalm clean · eslint 0 errors · 120/120 jest. PHPUnit cannot run outside a Nextcloud container here (Class "OC_App" not found in the bootstrap) — CI's matrix is the check that matters for it.

Conduction Release Bot and others added 7 commits August 22, 2026 18:23
…ob classes across the app-id rename

Nextcloud namespaces oc_appconfig and oc_preferences by app id and has no
in-place app-id upgrade, so every stored row becomes unreachable when the id
moves. Every reader supplies a default, so nothing errors -- settings just
revert. oc_jobs has the same shape one level down: it stores the job CLASS
NAME, so the namespace rename orphans all four job rows.

Three IRepairSteps, registered FIRST in both <install> and <post-migration>.
<install> matters because an app-id rename presents to Nextcloud as a FIRST
install of stackiq, and installAppLastSteps() guards <post-migration> with
$previousVersion !== '' -- so <install> is the only hook that fires on the
very upgrade these steps exist for. FIRST matters because InitializeSettings
writes config itself; running it first would make every key look already
present and strand the operator's value in the old namespace.

Exhaustive enumeration (getKeys / callForSeenUsers + getUserKeys, never
getUsersForUserValue which matches on a VALUE and so migrates nothing over an
open value set). Reserved keys enabled/installed_version/types are skipped --
copying 'enabled' as STRING makes the next app:enable fail permanently with
AppConfigTypeConflictException. Idempotent, non-destructive, and every read
AND write inside the try so a throw cannot abort the install.

28 unit tests covering all three.
…d dev scripts

Also repoints this app's own badges and clone URL from the retired
codeberg.org host to github.com/ConductionNL/stackiq, and replaces the
softwarecatalog.app doc link (DNS: no record) with the live
softwarecatalog.conduction.nl. The .conduction.nl host itself stays --
stackiq.conduction.nl does not resolve.
Completes the sweep: PHP namespace OCA\SoftwareCatalog -> OCA\Stackiq (the
sub-namespace Service\SoftwareCatalogue -> Service\Stackiq, and the class
SoftwareCatalogueService -> StackiqService, which had been left disagreeing with
its already-renamed file), /apps/softwarecatalog -> /apps/stackiq routes across
code, tests, postman collections, docs and cursor rules.

Postman: the path ARRAY was rewritten alongside the raw URL. Postman builds the
request from the array, so rewriting only raw would leave the collection hitting
a different URL than the diff shows.

FROZEN, each because renaming would fail silently rather than loudly:
  - softwarecatalog.conduction.nl (HTTP 200; stackiq.conduction.nl is 000) --
    the manifest documentationUrl and footer href would point at nothing.
  - softwarecatalogus -- the Dutch VNG catalogue, an external system's name,
    not this app's id. Includes the GEMMA fixture XMLs and the register file.
  - x-openregister.app in the register descriptor: OpenRegister attributes the
    register by this field and may match on it; renaming risks a duplicate or
    an orphan. Reported rather than guessed.
  - openspec/changes/archive/** -- history; rewriting breaks @SPEC paths.
  - openspec/coverage-report.* -- a dated audit snapshot. Rewriting its
    observations to say something it never observed is falsification.
  - LEGACY_APP_ID in the two Migrate* repair steps, and the old namespace in
    MigrateBackgroundJobClasses -- these name what the steps read FROM.
  - codeberg.org URLs -- a different host and org, stale independently.

Register slugs are voorzieningen and vng-gemma; neither contains the app id, so
neither moved.

Local: phpcs 0 errors, psalm clean, eslint 0 errors, 120/120 jest.
The register descriptor attributes the register to an owning app through
x-openregister.app. It was left on the old app id when the id moved, so the
descriptor claimed ownership by an app that no longer answers to that name.

Safe to move now: these instances are development-only, so there is no live
register whose attribution could be split.

The register SLUG is deliberately NOT touched here — that is the key objects
are stored against, and it is a separate decision from attribution. Other
apps' ids appearing in the same file (e.g. opencatalogi) are cross-app
references and stay as they are.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ b1c9f4b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-22 17:41 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ f75af66

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-22 18:01 UTC

Download the full PDF report from the workflow artifacts.

…rfaced

The scripted sweep was fast but blunt. Five distinct failures, each a case of
a name moving without the thing that answers to it.

1. TEST CONSTANTS INVERTED THE MIGRATION. The sweep rewrote the tests'
   LEGACY constant from 'softwarecatalog' to 'stackiq', so they asserted that
   the migration reads from the namespace it writes TO -- a migration that
   does nothing, with a green bar. Both now bind to the step's own
   LEGACY_APP_ID constant, so they cannot drift again.

2. 50 DANGLING @SPEC REFERENCES. The sweep rewrote citation PATHS to
   openspec/specs/stackiq-* while the directories are still
   softwarecatalog-*. Capability ids are frozen -- gate-46 dereferences them
   and archived changes cannot move -- so the citations are reverted, not the
   directories. gate-46 now resolves every anchor.

3. l10n KEYS ARE THE ENGLISH SOURCE STRING, so renaming a user-visible string
   renames its key. Two keys drifted out of en.json. Repointed, with the Dutch
   VALUES carried across (Softwarecatalogus -> Stackiq only where it is the
   product name, never where it means the VNG catalogue).
   The .js artifacts were stale too: the l10n check reads JSON, but Nextcloud
   SERVES the .js, so nl.js/en_US.js would have kept the old keys and Dutch
   users would have silently fallen back to English past a green check.

4. phpstan: callForSeenUsers() expects Closure(IUser): (bool|null); a void
   closure does not satisfy it. The IJobList::has()/remove() class-string
   errors are unsatisfiable BY DESIGN -- this step exists to deregister jobs
   whose class no longer exists -- so they carry a scoped ignore with that
   reason, not a baseline entry.

5. gate-16 and prettier: four changed methods tagged with the requirement
   they implement; 50 files reflowed because 'stackiq' is shorter than
   'softwarecatalog' (measured 50 against 2 on development).

Local: phpcs 0 errors, phpstan clean, phpmd 0, eslint 0, 120/120 jest,
l10n OK, prettier clean, gate-16 0, gate-46 0.
Both CI failures had the same root cause, and it is a subtle one.

eslint-suppressions.json and phpmd.baseline.xml are keyed BY FILE PATH. The
sweep repointed those keys to the new filenames, but the rename of two source
files was a COPY, not a move -- so the old files stayed on disk, now with no
suppression entry pointing at them, and their long-standing findings resurfaced
as new errors. Neither file was reachable any more:

  - src/store/plugins/softwarecatalogPlugin.js -- superseded by stackiqPlugin.js,
    which orClient.js and store/modules/object.js already import.
  - src/views/settings/SoftwareCatalogSettings.vue -- superseded by
    StackiqSettings.vue, which registry.js already imports. The registry KEY
    stays SoftwareCatalogSettingsPage: that is a manifest identifier, not a
    filename.

Deleting them is the fix; repointing the suppressions back would have kept dead
code alive and silenced.

Also removed a genuine new else in MigrateAppConfigKeys::run() (guard plus
continue), rather than adding it to phpmd.baseline.xml -- a baseline records
debt already inherited, and same-day code does not belong in it.

Local: phpcs 0, phpmd 0, phpstan OK, eslint 0 errors, 120/120 jest, l10n OK,
prettier 0, gate-16 0.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 72fb368

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 07:29 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 23d30d9

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 07:47 UTC

Download the full PDF report from the workflow artifacts.

The earlier bulk pass built its file list with a case-SENSITIVE grep for
'softwarecatalog'. Every file whose only occurrences were 'SoftwareCatalog' or
'SoftwareCatalogue' -- the PHP namespace and class-name spellings -- was
therefore never in the list, and never rewritten. CI found them as 32 errors:
Class "OCA\SoftwareCatalog\EventListener\SoftwareCatalogEventListener" not
found, because composer's psr-4 now maps only OCA\Stackiq\.

Renamed the missed files and directories with git mv, and removed four stale
DUPLICATES the earlier rename had left behind by copying rather than moving --
tests/Unit/EventListener/SoftwareCatalogEventListener{,Decomposition}Test.php
and tests/Unit/Service/SoftwareCatalogueService{Decomposition,OrganisationMapper}Test.php.
Each had a live counterpart under the new name; the old copies still declared
the old namespace, so they could only ever fail.

lib/Settings/softwarecatalogus_register.json stays: softwarecatalogus is the
Dutch VNG catalogue, not this app, and the file is loaded by an explicit path.

Local: phpcs 0, phpstan OK, phpmd 0, eslint 0, 120/120 jest, l10n OK,
prettier 0, gate-16 0, gate-46 0.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 6784d17

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 08:18 UTC

Download the full PDF report from the workflow artifacts.

An earlier flatten of tests/Unit/Service/SoftwareCatalogue left BOTH
tests/Unit/Service/Stackiq/ and tests/Unit/Service/Stackiq/Stackiq/ tracked,
with byte-identical files. PHP fataled on the second copy:

  Cannot declare class OCA\Stackiq\Tests\Unit\Service\Stackiq\
  ContactPersonHandlerDecompositionTest, because the name is already in use

which aborted the whole PHPUnit run (exit 255) rather than failing a test.

Local: phpcs 0, phpstan OK, gate-16 0.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 2101d55

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 08:37 UTC

Download the full PDF report from the workflow artifacts.

…ions

The coverage ratchet failed with 'coverage of the files this change touches
dropped by 3.02%'. The cause is the deletions in the previous commits: four
stale duplicate test files and two orphaned source files, all of which had live
counterparts under the new name.

The guard's own CI notice named the fix:

  scripts/coverage-guard.php predates --deletion-neutral, so deleting
  well-tested dead code will still read as a coverage drop. Copy the canonical
  version from ConductionNL/.github at quality-config/coverage-guard.php

--deletion-neutral compares method buckets asymmetrically, so a pure deletion
is exactly neutral while a regression in surviving code and new untested code
both still fail. Without it this PR could not comply from inside its own
subject: the only moves would be delete less, add filler, or delete additional
UNCOVERED statements until the arithmetic balanced.

Copied verbatim from .github@main (837 lines); --capabilities now reports
deletion-neutral alongside against/update-baseline/changed-files.
'admin settings: the settings section renders' asserted the host contained the
text 'SoftwareCatalog'. info.xml now declares <name>Stackiq</name>, so the UI
renders the new name and the element was genuinely not found.

Missed for the same reason as the namespace files: the sweep's file list was
built with a case-SENSITIVE grep for 'softwarecatalog', and this literal is
CamelCase inside a string. The later CamelCase pass only listed files matching
the namespace patterns, so a bare 'SoftwareCatalog' in a test string fell
between the two.

MigrateBackgroundJobClassesTest still asserts 'SoftwareCatalog' -- correct, it
pins the old namespace the repair step reads FROM.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 74982bb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 08:42 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ bafe7f9

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 08:59 UTC

Download the full PDF report from the workflow artifacts.

The merge-base coverage check dropped 0.05% -- 6 statements, same denominator.
Not the deletions (the four removed duplicate tests had identical test counts to
their live counterparts, so they added no unique coverage) but the else-removal
in MigrateAppConfigKeys::run(): replacing if/else with a guard plus continue
adds a 'continue' statement, and every existing fixture in that file migrates a
SINGLE key, so the loop never iterated past the first one.

Two tests added:
  - testCopiesEveryCopyableKeyNotJustTheFirst -- two copyable keys. This is
    worth having beyond the ratchet: a migration that silently stopped after
    the first key would have passed every one-key test in the file.
  - testAnUncopyableValueIsSkippedRatherThanWritten -- an empty string and an
    empty array, which copyValue() refuses, reaching the trailing $skipped++.

Cannot run PHPUnit locally (the bootstrap needs OC_App, absent from a bare
clone), so CI is the check for these. gate-16 0, gate-46 0, phpcs 0.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ e17582f

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
format
composer ✅ 130/130
npm ✅ 703/703
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-23 09:22 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Merging with the coverage ratchet red, deliberately, because it cannot be satisfied from inside a rename PR.

Overall coverage is flat: 60.50% head vs 60.55% base (8303/13723 vs 8309/13723).

The scoped check compares head 5884/31684 against base 5673/26273different populations, 5,411 statements apart. The cause is the 12 renamed lib/ files: a renamed file has no entry in the base clover, so every one of its statements counts on the head side and none on the base side. StackiqService.php alone is ~676 statements.

Renamed files sit at the app's ordinary coverage, so including them only on one side must lower the ratio. The only ways to make the arithmetic balance would be to add filler tests or delete additional uncovered code — gaming the number rather than improving the app.

Same family as gate-16 and gate-57: a diff-scoped instrument that a rename defeats by construction.

Everything else on this PR is green, including E2E and the other five PHPUnit cells. The failing cell is the one that runs the guard; Quality Report is its aggregator.

Note: syncing the canonical coverage-guard.php (which CI's own notice asked for) was still correct, but it did not fix this — old and new guard both report 3.02%, because the problem is the renamed-file population, not deletions.

@rubenvdlinde
rubenvdlinde merged commit b95d4e4 into development Aug 23, 2026
79 of 81 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/rename-softwarecatalog-to-stackiq branch August 23, 2026 09:23
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