Repository navigation
feat(cmdb-import): run the mapping packs stored in OpenRegister, seeded from the shipped files - #1288
Conversation
… archived Adds the OpenSpec change cmdb-import-archive-reconciliation (REQ-CMDB-015..017, REQ-CMDB-012 and REQ-CMDB-014 modified). The register fragment opts module (0.3.9) and usage (1.5.7) into OpenRegister's archive state, the import profile names the sheet "Gearchiveerde Applicaties" and the accepted missingRecords modes, and the workbook reader returns the APPIDs of that sheet, read under the same bounds as the CMDB sheets for its key column only. The archived-applications fixture covers it. First of three slices of #1281; nothing reads the archive sheet yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…export With missingRecords archive, the default, a completed import reconciles the municipality's usages and the modules its import created: an APPID on the archive sheet is archived, one on no sheet is soft-deleted, and without the archive sheet missing applications are archived only, with a report warning. A returning application is unarchived or restored before it is updated, never duplicated. Object state is read per lens (working, then _archived=true) and from the trash, because OpenRegister's list search hydrates no archive marker and drops soft-deleted rows. The endpoint accepts missingRecords keep or archive and translates ARCHIVE_UNAVAILABLE; OpenAPI and Postman describe the new values and outcomes. Second of three slices of #1281, on top of the archive-sheet slice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uick filter The CMDB import section lets the admin archive or delete applications missing from the export, or keep them, and shows the archived, unarchived, deleted and restored outcomes. The Applications and Applications in use pages get an Archived quick filter. Vitest and the Playwright spec cover it; the feature docs and the changelog describe it. Third of three slices of #1281, on top of the reconciliation slice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the OpenSpec change cmdb-import-mapping-view (ADDED REQ-CMDB-020,
MODIFIED REQ-CMDB-014) and the admin-only GET
/api/settings/cmdb-import/mapping. It loads the import profile and its five
packs through CmdbImportProfile, the loader the import uses, and answers the
flat {profile, packs}; a broken pack answers 503 MAPPING_UNAVAILABLE with
the loader's reason.
First of two slices of #1280.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt section A read-only mapping view in the CMDB import section shows which sheet column feeds which field. Vitest and the Playwright spec cover it; the feature docs describe it. Second of two slices of #1280, on top of the endpoint slice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1230/1230 · 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-10-12 10:38 UTC
Download the full PDF report from the workflow artifacts.
…rt-import spec The fragment test was renamed to testTheMergedModuleIsVersion039WithTheExternalIds when the module schema moved to 0.3.9; the live spec still named the 038 test. The archived change keeps its historical name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…' into feat/woo587-cmdb-import-archive-reconciliation-2-reconcile
trash() set the cached trash to empty before it read the listing, so a listing that threw failed its row and left every later row of the run matching against an empty trash: a soft-deleted module that returned was created a second time. The cache is now set only once the listing was read; the next row reads it again. The missing-archive-services path keeps caching the empty trash, since a retry cannot succeed there. Mutation-checked: testAFailedTrashReadIsNotCachedForTheNextRow fails on the previous trash() (outcomes and read count) and passes on this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…export-import change openspec/changes/cmdb-export-import was archived to openspec/changes/archive/2026-10-05-cmdb-export-import, so the tags this change added on addRunWarnings() and the module write path named a file that no longer exists. The older tags in lib/ share the stale path and are left for a separate sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile' into feat/woo587-cmdb-import-archive-reconciliation-3-ui
…d to split the export With missingRecords archive as the default, two texts were now wrong: - The section's notes said applications missing from a newer export "are left as they are"; they are archived or deleted unless the admin keeps them. - The TOO_MANY_ROWS, FILE_TOO_LARGE and WORKBOOK_TOO_LARGE hints, in the section and in the feature docs, advised splitting the export and importing the parts one after the other. An archiving import of one part treats the applications of the other parts as missing, so each part archived or deleted the others. The hints now say to import every part with "Keep them as they are". Dutch follows in nl.js and nl.json. Checked with jest (cmdbImport.spec.js, CmdbImport.spec.js: 86 passed), test:l10n and check:l10n-js; eslint could not run locally (Node lacks module.findPackageJSON). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…line The mapping endpoint returned the loader's exception message as details.reason unbounded, and its docblock and design D2 said that message named files and validator rules only. The validator's messages also quote the configuration value they refused, and nothing limited their length. The reason is now the message's first line, cut to 500 characters, and both texts say what it carries: the file, the rule and the refused value, never a workbook cell or a person. Mutation-checked: testTheReasonIsOneBoundedLine fails when the endpoint returns the raw message and passes with the bound. phpcs, phpmd and phpstan report 0; the CMDB and SettingsController suites pass (306 tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/woo588-cmdb-import-mapping-view-2-ui
A split export imported with Keep them as they are archives and deletes nothing, so the FILE_TOO_LARGE and TOO_MANY_ROWS rows now say that applications which left the export are then not archived or deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cmdb-import-mapping-view delta now says details.reason is one line of at most 500 characters and may quote the refused configuration value, as the endpoint and design D2 do since the reason is bounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/woo588-cmdb-import-mapping-view-2-ui
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1231/1231 · 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-10-12 12:52 UTC
Download the full PDF report from the workflow artifacts.
There was a problem hiding this comment.
Verdict: APPROVE (Thorough) — self-review posted as COMMENT (GitHub blocks self-APPROVE)
No blockers: the endpoint is admin-only with CSRF, answers flat, logs unexpected errors with a static 500, and loads the profile through the same autowired CmdbImportProfile as the import. The unbounded failure reason was fixed before posting; what remains is that the view does not yet show the import's write rules.
Update 2026-10-12: The remaining concern was fixed on the branch after posting.
🟡 Concerns (1)
- Overview omits the write rules, so 'what the next import runs' is overclaimed —
lib/Service/Cmdb/CmdbImportProfile.php:629. The docblock (and SettingsController.php:3899, design.md:11, proposal.md:10, spec.md:11) promise that the answer is exactly what the next import runs. ✅ Addressed inecc9c8b0, df28777e, 6ecb274a.
🟢 Minor (3)
- 'Same loader' and 403 are asserted by no test —
tests/Unit/Controller/SettingsControllerCmdbImportMappingTest.php:225. Contract-branch table: (1) 'the same loader the import uses': the test buildsnew CmdbImportProfile(...)itself, passes it in, and compares the answer with$profile->pack()of the same object, so it would stay green if the controller's fallback built a different directory or class; nothing ties it to the instance CmdbExportImportService gets (both are container-autowired today: no CmdbImportProfile registration … - New 503/500 messages are not extractable and not translated in this slice —
lib/Controller/SettingsController.php:3935. The two user-facing messages go through a privatetranslate(string $text)that calls$this->l10n->t($text)with a variable. - tasks.md marks Tasks 3-5 implemented; their files are not in this slice —
openspec/changes/cmdb-import-mapping-view/tasks.md:34. Task 3 (CmdbImportMapping.vue, vitest), Task 4 (l10n, docs section) and Task 5 (e2e) carry[x] Implement, and the spec's@e2e tests/e2e/spec-coverage/cmdb-import-mapping.spec.tspoints at a file that does not exist at this head (checked: CmdbImportMapping.vue, cmdbImportMapping.spec.js, cmdb-import-mapping.spec.ts are all absent).
✅ Fixed before posting (1)
- details.reason returns $e->getMessage() to the client — fixed in
0108ef0.
✅ Verified clean
- No auth attribute (Nextcloud default: admin + CSRF) with
@auth admin-only <reason>, asserted bytestTheRouteIsForNextcloudAdminsOnly. - 503 path: no absolute path or stack trace reaches the body; the 500 path returns a static message and logs the exception.
requiredis always a boolean; the spec'sdone→in-progressflip follows writing-specs' re-opening rule.
Scope decision: slice 1 of 2 of the closed #1280, stacked on development; reviewed against its own base only.
Gates: CI quality / Hydra Gates and the required PHP/Vue quality checks green on {h}; quality / PHPUnit was still running at submission.
ADRs verified: ADR-005, ADR-012, ADR-031, ADR-050 — all resolve on hydra main.
Jira context: WOO-588 — CMDB-import: mapping inzichtelijk en beheerbaar voor de beheerder (status: Refinement)
…ng progress In the reconciling phase the service reports processed = rows + index and total = rows + the municipality's applications, so the page showed "410 of 810" as if it were applications missing from the export, and the bar fell from 100% to about 50% when the phase began. The scope also holds applications that are on the sheets and need no change. The progress view now shows no count for that phase, says it checks the applications of the municipality against the export, and keeps the bar full. The l10n key is replaced in place; the docs describe the new progress line. Review #1287 f3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view The overview listed the pack field mappings only, so the usage fields interneAnnotation and timeClassification looked like ordinary mappings while the import writes them on create or when the stored value is empty, and the module type Application and the never-written publication dates showed nowhere. mappingOverview() now answers per pack `defaults`, `createOnlyDefaults`, `createOnlyFields` and `neverWritten`, built from the accessors the import uses, plus the profile's `externalKeyPrefix`. REQ-CMDB-020, tasks.md and design.md name the new keys; the controller and profile tests pin them. Review #1288 f2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Cancel the section said the import "stops before the next row" and "the rows processed before it stopped are kept". During the comparison it stops before the next application, and the applications archived or deleted until then stay too; the cancelled banner told the admin nothing of that while the report listed them. Both strings now name applications and the l10n keys are replaced in place; the docs say the same. The spec mounts the section for real so the banner and the status line are read from the page. Review #1287 f4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/woo588-cmdb-import-mapping-view-2-ui
The archive sheet is formula-derived. A row whose APPID formula carries no cached value read as an empty row and was skipped without trace, so the sheet reported present: true with one APPID fewer. The reconciliation would then see that application on no sheet and soft-delete it instead of archiving it, with nothing in the report saying why. REQ-CMDB-002 already treats such a cell on the CMDB sheets as empty plus a row warning. readRows() now hands back the numbers of rows that read as empty only because a formula in them is uncached, and the reader returns them as archive.uncachedRows. The spec delta (REQ-CMDB-015, new scenario), design D3/D5 and task 2 state the rule: such a sheet is incomplete, so the import warns naming the sheet and rows and archives every missing application, deleting none. The service side follows in the stacked PR. Review #1285 f1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REQ-CMDB-015 and design D3 promise that the archive sheet is read under the same bounds as the CMDB sheets, for its key column only, but only the row limit had a test. The part size, the row span, the referenced-string budget, the key-only read filter and the null cases of archiveSheetName() could regress unnoticed; swapping readSheetNames() for sheetNames() in read() left the reader tests green, which would blank an oversized archive sheet instead of refusing it. Adds one test per bound. RecordingXlsxReader now also keeps the read filter of each load, so the key-only claim is asserted on what the data pass is allowed to read, not on the returned array. Each test was checked by mutating the reader (archive sheet left out of that bound) and goes red. Review #1285 f5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…' into feat/woo587-cmdb-import-archive-reconciliation-2-reconcile
The pack rules read the base register and its register.d fragments, but skipped a fragment that was unreadable or invalid while the base register threw. The fragments now carry the hideOnForm and calculated protection (for example the service desk fields in sharing-itsm-exchange.json), so a skipped fragment made those fields mappable again. An unreadable fragment, or a register.d listing that fails, now throws MAPPING_UNAVAILABLE naming the fragment, as the base register does. Class sweep: schemaFields() is the only reader of the register files in the rules; the base register, each fragment and the listing all fail closed now. Test covers an invalid and an empty fragment and goes red with the check removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s in the Mapping block
- f1: each sheet pack gets a collapsible "Columns and fields" block with its
column-to-field mappings (column, field, required, transform) and the write
rules that have values (defaults, createOnlyDefaults, createOnlyFields,
neverWritten), as text in tables with captions and th scopes; the packs
table caption is now "Packs", so help and captions match what is shown.
- f2: docs/features/cmdb-import.md says what the block shows and what it does
not (empty values, sheet constants, limits).
- updatePending from the endpoint: the row says "Update pending: shipped X
replaces stored Y at the next import"; spec and tasks follow.
- f3: the role=status loading line moves outside the v-show block that is
hidden while the load runs, so assistive technology announces it.
- f4: remove the unreachable packTargetLabel('municipality') branch and its
l10n key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/woo588-cmdb-import-mapping-view-2-ui
… live region that renders empty first The live region was inserted already holding the loading text and a load failure while the block was collapsed was silent. The region now renders empty, the load starts after mount (mounted + nextTick), and the same region announces loading and a short translatable failure message (en, nl). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1368/1368 · 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-10-13 19:10 UTC
Download the full PDF report from the workflow artifacts.
There was a problem hiding this comment.
Verdict: APPROVE (Strict) — self-review posted as COMMENT (GitHub blocks self-APPROVE)
Round 3 under the convergence rule: the 2 blocking concerns and 6 minors found this round were fixed on the branch before posting, and so were all 14 open threads from rounds 1-2. Every thread is resolved. What still stands between this PR and a merge: 1 approval, and the merge order with openregister#4602 (the docs name it).
| Round | Found | Status |
|---|---|---|
1 (80dc551) |
🟡 overview omitted the write rules · 3 🟢 | resolved in ecc9c8b, d4d7f38, 3d8b789, 8f07929 |
2 (057abc0) |
5 🟡 (GET wrote, update race, docs/spec described #1289's section, unconditional editing claim, SHALLs without scenario) · 7 🟢 | resolved in 3d8b789, 8f07929, 3c81e23, 6111cb8, f7421aa |
3 (0a349a8) — this review |
2 🟡 (ITSM-owned fields mappable, the view showed a pack the import replaces) · 6 🟢 | fixed before posting in c797020 … 0a349a8 |
| Finding | Was | Now | Where |
|---|---|---|---|
| 🟡 Read-only mapping GET seeds and updates packs | 🟡 r2 | resolved in 3d8b789 + 81b1bc7 |
thread |
🟡 update() without expectedUpdatedAt |
🟡 r2 | resolved in 3d8b789 + 81b1bc7 |
thread |
| 🟡 Docs and spec describe the settings section | 🟡 r2 | resolved in 3c81e23 + 6111cb8 |
thread |
| 🟡 Docs promise editing unconditionally | 🟡 r2 | resolved in 3c81e23 |
thread |
| 🟡 Section SHALLs without a scenario | 🟡 r2 | resolved in 3c81e23 + 6111cb8 |
thread |
🟢 Fake update() not faithful |
🟢 r2 | resolved in 3d8b789 |
thread |
| 🟢 "Hardened" asserted by a marker class | 🟢 r2 | resolved in 3d8b789 + f7421aa |
thread |
| 🟢 Untranslated 500 message | 🟢 r2 | resolved in 8f07929 |
thread |
| 🟢 CHANGELOG omits the pack store | 🟢 r2 | resolved in 3c81e23 |
thread |
| 🟢 Half of the "all usages Planned" assertion vacuous | 🟢 r2 | resolved in 3c81e23 |
thread |
| 🟢 Task 6 unchecked | 🟢 r2 | resolved in 3c81e23 |
thread |
| 🟢 Canonical spec status | 🟢 r2 | resolved in 3c81e23 |
thread |
| 🟢 'Same loader' and 403 untested | 🟢 r1 | resolved in 3d8b789 |
thread |
| 🟢 503/500 messages not translatable | 🟢 r1 | resolved in 8f07929 |
thread |
| 🟡 Widened pack rule admits ITSM-owned usage fields | new | fixed before posting in c797020 + 0a349a8 |
lib/Service/Cmdb/CmdbImportPackRules.php |
| 🟡 Mapping view shows a pack the next import replaces | new | fixed before posting in 81b1bc7 |
lib/Service/Cmdb/CmdbImportPackStore.php (find()) |
Details of this round
Scope decision: delta d4d7f38…e2081bb (6 commits, 36 files, 1,553 changed lines: 1fe71a1, 057abc0, 3d8b789, 8f07929, 3c81e23, e2081bb), reviewed in two slices (backend; spec/docs/e2e), plus this run's fix commits c797020, 81b1bc7, f7421aa, 6111cb8, 94f976e, 1004ee7 and 0a349a8.
✅ Fixed before posting (8)
- ITSM-owned usage fields were mappable (
c797020,0a349a8):- A target is refused as
systemwhen any definition, in the base register or aregister.dfragment, carrieshideOnForm: trueor is anx-openregister-calculationskey. That covers the serviceDesk* fields,suggestedTimeClassification, and the module relations shown read-only. installedVersionis protected by name.- An unreadable fragment now fails closed (
MAPPING_UNAVAILABLE), because fragments carry this protection. - Tests:
testAFieldAnotherSubsystemOwnsIsRefused,testAHiddenCalculatedOrFilePropertyIsRefused,testAnUnreadableFragmentIsMappingUnavailable.
- A target is refused as
- The view showed a pack the next import replaces (
81b1bc7):find()answers an unedited, outdated pack as the import will run it. That means the shipped pack and version,storedVersionset to what OpenRegister holds, andupdatePending: true, still without writing. - Update guard off when
updatedAtis null (81b1bc7): such a pack is kept, inresolve()and in the view alike. - Probe tests opened one method per marker (
f7421aa): 5 write methods × 4 markers × attribute/docblock, plus the missing-method, missing-class, missing-service and throwing-container branches. - Spec, proposal and design text (
6111cb8): the section wording moved to #1289, the probe's requirements are stated, andtype: fileis in the REQ-CMDB-021 refusal list. - e2e left a deleted pack deleted (
94f976e): thefinallyre-imports.
✅ Verified clean
- Every stored-pack consumer goes through the same rules. Managed keys,
neverWritten, relations and the pinned defaults are still refused, and the shipped packs still pass. find()andresolve()branches are each covered by a named test: missing, current, outdated+unedited, outdated+edited,updatedAtnull, renamed, store unavailable.- The probe passes against openregister#4602 at
170f1cd, whose five write methods carry no opening marker.
Gates: CI quality / Hydra Gates ✅ on e2081bb. CI on 0a349a8 started with this push.
Checks after the fixes: phpunit --filter Cmdb 347 green (bootstrap remapped onto this tree, with a control mutation turning red first), phpcs and phpstan clean on lib/Service/Cmdb. The e2e suite was not run.
Jira context: WOO-588, CMDB-import mapping visible and manageable for the administrator.
Before the next round: this round's findings came mostly from the fixing. The next round's fixes go through the class sweep and the pre-push check first.
vendor/bin/phpunit --filter Cmdb # 347 green
git grep -n "updatePending" -- lib openspec docsAfter posting: PHP Quality (phpmd) went red on 0a349a8, because the fail-closed fragment check pushed CmdbImportPackRules over the class and method complexity limits. Fixed in afaa085: reading the register moved to CmdbRegisterSchemaFields, with unchanged behaviour (--filter Cmdb 347 green; a control mutation in the new class turns two red; phpcs, phpmd and phpstan clean). #1289 merged it in ba983a7.
…lass The fail-closed fragment check pushed CmdbImportPackRules over phpmd's class and method complexity limits (schemaFields: cyclomatic 13, NPath 288; class 58), which turned PHP Quality (phpmd) red. Reading the register and its fragments, and deciding why a property may not be mapped, moves to CmdbRegisterSchemaFields; the rules class asks it once. Behaviour is unchanged: --filter Cmdb stays at 347 green, and a control mutation in the new class turns two of them red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/woo588-cmdb-import-mapping-view-2-ui
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1368/1368 · 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-10-13 19:47 UTC
Download the full PDF report from the workflow artifacts.
…chive-reconciliation-3-ui feat(cmdb-import): offer the missing-records choice and an Archived quick filter
…chive-reconciliation-2-reconcile feat(cmdb-import): archive or soft-delete applications that left the export
…pping-view-2-ui feat(cmdb-import): show where each import pack comes from and link to its editor in OpenRegister
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1368/1368 · 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-10-15 08:33 UTC
Download the full PDF report from the workflow artifacts.
…on in their own method reconcileApplication() reached the phpmd cyclomatic complexity threshold of 10 once the archive-sheet branch landed. The choice between archiving and soft-deleting, with its archive reason, moves to reconcileTarget(); the writes and their error boundary stay where they were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed-list requirement The indexPageKey watcher and onQuickFilterChange() keep the Archived-tab note in step with the active tab; gate 16 needs their @SPEC tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…db-import-archive-reconciliation-1-reader Brings in #1290 (sidebar search sent as _search) and #1292 (contact person names). FacetedCatalogIndexView: development's remount key (listRemount) and sidebar search are kept; this branch's Archived-tab note, the tab tracking and the watcher that resets it on a remount are kept on top. Since #1290 the list sends the search term as _search, outside the filter, so the Archived tab's empty `_ids` no longer drops it. The note, the manifest _note, the docs, the task and the tests now say the tab ignores the GEMMA filters only; the note's string changed key in en/nl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…' into feat/woo588-cmdb-import-mapping-view-1-endpoint Brings in #1285-#1287 (archive or soft-delete applications that left the export) and, through it, development with #1290 and #1292. Both sides are kept, with these choices: - topdesk-profile.json stays at version 1.0.0, the first version for the administrator, with #1285's description (the archive sheet and the missingRecords modes). - The sheet texts keep this branch's list-based functions and name the archive sheet there: sheetsReadText() and the "delete the sheets the import does not read" hint. The old "{first}"/"{second}" keys are replaced in en/nl, not kept next to the new ones; l10n/*.js regenerated. - The shipped-fallback warning moves into addRunWarnings(), where #1285 put the run's other warnings, ahead of them as before. - The section keeps the one-sentence option help and puts the missing-applications explanation in the info panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CVE-2026-109413 (unbounded recursion in the YAML inline lexer) affects symfony/yaml below 6.4.45, which made gate 4 (composer-audit) fail. A dev dependency through consolidation/robo; only the lock file changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is missing Since openregister#4602 is on OpenRegister's development branch, the PHPUnit job's OpenRegister ships MigrationPackRefusedException and the other pack-store classes itself. loadHardenedOpenRegister() declared the stand-ins regardless, and the run stopped with "Cannot declare class OCA\OpenRegister\Exception\MigrationPackRefusedException". The fake now passes the slug to PackSlugExistsException, as the real constructor takes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1426/1426 · 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-10-15 10:48 UTC
Download the full PDF report from the workflow artifacts.
…ster's constructors The stand-ins had no constructors and no getErrors(), while the real classes take array $errors, string $message and string $packSlug. CI runs the real classes and a local run the stand-ins, so a test could pass locally and fail in CI. The stand-ins now mirror OpenRegister's signatures and messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hipped one The help texts took the source sheets from the mapping endpoint but the archive sheet from a constant, and the information panel named "Gearchiveerde Applicaties" literally. mappingOverview() now answers profile.archiveSheet (null when the profile names none), and the info text, the too-large hint and the missing-applications paragraph use it, with the shipped name only until the mapping has loaded. Without an archive sheet they leave it out, and the paragraph says the import only archives, as reconcileTarget() does. Tests cover a named sheet, none and the fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cmdb-import-mapping-view and cmdb-import-archive-reconciliation each modified REQ-CMDB-014 in full, each without what the other adds, so archiving the second would have dropped the first one's text. Both now carry one block with the missing-applications choice, the four counts, the mapping block and the archive sheet from the profile, plus a note that a change to it goes into both. The payload list, design D4 and the tasks name the archive sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| "name": "TOPdesk CMDB export", | ||
| "version": "2.0.0", | ||
| "description": "How stackiq reads a TOPdesk CMDB export (xlsx): the two CMDB sheets, the key and required columns, date and id columns, values that mean empty, the pack per target and the limits. Columns that neither this profile nor a pack names are never read.", | ||
| "version": "1.0.0", |
There was a problem hiding this comment.
🟢 Minor — the profile version goes back from 2.1.0 to 1.0.0
On development (since stackiq#1285) the profile is at 2.1.0; this PR resets it to 1.0.0, because the PR body treats 1.0.0 as the first version for the administrator. That reason holds for the four packs, which are stored in OpenRegister; the profile itself is never stored there and always comes from this file. No code reads the version except the payload of mappingOverview(), so nothing breaks.
Kept as is on purpose (decision 2026-10-15): the PR body chose 1.0.0 deliberately. Worth a second look only if something starts comparing profile versions.
Verification: git grep -n "\[.version.\]\|->version\|profile\[.version" -- lib → only the overview payload reads it
| * @spec openspec/changes/cmdb-import-archive-reconciliation/specs/cmdb-export-import/spec.md#requirement-an-application-missing-from-the-cmdb-sheets-shall-be-archived-when-the-archive-sheet-lists-it-and-soft-deleted-when-no-sheet-does-req-cmdb-015 | ||
| */ | ||
| export function missingApplicationsText(archive = mappingArchiveSheet(null)) { | ||
| if (!archive) { |
There was a problem hiding this comment.
🟢 Minor — without an archive sheet, the option texts still promise the trash
With archiveSheet null this paragraph says the import only archives, which matches reconcileTarget() (it deletes only when the archive sheet is present). The radio label "Archive or delete them", its help "…archived or moved to the trash" and the info line "…archived or deleted, unless you choose to keep them" in CmdbImport.vue do not switch on archiveSheet and still mention deleting.
The shipped profile always names an archive sheet, so this does not show today. Left as is: only relevant once a profile without an archive sheet is supported. Related, outside this diff: for such a profile addRunWarnings() reports Sheet "" was not found…, because archiveSheet() falls back to an empty name.
Verification: git grep -n "Archive or delete them\|moved to the trash" -- src/views → the three texts, none conditional on archiveSheet
perfectionist/sort-named-imports wants MISSING_RECORDS_MODES before missingApplicationsText. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WilcoLouwerse
left a comment
There was a problem hiding this comment.
Verdict: APPROVE (Strict) — self-review posted as COMMENT (GitHub blocks self-APPROVE)
Nothing blocking left: the 3 concerns and 2 of the 4 minors found this round were fixed on the branch before posting; 2 minors are left as-is on purpose, each in an open thread. What still stands between this PR and a merge: 1 approval, those 2 open threads (
developmentrequires every thread resolved), and CI on727793f, which started with the last push.
| Round | Found | Status |
|---|---|---|
1 (80dc551) |
🟡 overview omitted the write rules · 3 🟢 | resolved |
2 (057abc0) |
5 🟡 · 7 🟢 | resolved |
3 (0a349a8) |
2 🟡 · 6 🟢, fixed before posting | resolved |
4 (727793f) — this review |
3 🟡 (REQ-CMDB-014 split over two changes, archive sheet from a constant, stale description) · 4 🟢 (untested info sentence, test stand-ins without constructors, profile version back to 1.0.0, option texts without an archive sheet) | 3 🟡 + 2 🟢 fixed before posting; 2 🟢 left as-is |
| Finding | Was | Now | Where |
|---|---|---|---|
| 🟡 Two open changes each replace REQ-CMDB-014 in full | new | resolved in 8e027d2 |
openspec/changes/*/specs/cmdb-export-import/spec.md |
| 🟡 Archive sheet named from a constant, not the profile | new | resolved in e7988c3 |
src/utils/cmdbImport.js, CmdbImportProfile::mappingOverview() |
| 🟡 PR description no longer described the PR | new | resolved (description updated 2026-10-15) | PR description |
| 🟢 No test for the info panel's archive sentence | new | resolved in e7988c3 |
src/views/settings/sections/CmdbImport.spec.js |
| 🟢 Test stand-in exceptions without OpenRegister's constructors | new | resolved in e8dce55 |
tests/Unit/Support/OpenRegister/ |
| 🟢 Profile version back from 2.1.0 to 1.0.0 | new | left as-is (deliberate, see thread) | thread |
| 🟢 Without an archive sheet the option texts still promise the trash | new | left as-is (no such profile ships) | thread |
Details of this round
Scope: the delta since round 3 (0a349a8), 12 commits: afaa085 (the register's field rules into CmdbRegisterSchemaFields), the merge of stackiq#1285–#1287 into this branch (8a80c17, reviewed as its conflict resolutions with git show --remerge-diff, 16 files), 6070859 (symfony/yaml 6.4.40 → 6.4.48 for CVE-2026-109413) and 68c49f4 (OpenRegister stand-ins load only when OpenRegister does not ship the class, since openregister#4602 put them on its development). The merges of #1286, #1287 and #1289 were reviewed in their own PRs.
✅ Fixed before posting (5 + 6 from the pre-push check)
- REQ-CMDB-014 (
8e027d2): bothcmdb-import-mapping-viewandcmdb-import-archive-reconciliationnow carry one identical block (the missing-applications choice, the four counts, reconciliation rows, the mapping block, the archive sheet from the profile, the "keep missing applications" scenario), with a note in each that a change goes into both;openspec validate --strictvalid for both. - Archive sheet from the profile (
e7988c3):mappingOverview()answersprofile.archiveSheet;mappingArchiveSheet()falls back to the shipped name only until the mapping has loaded; the info text, the too-large hint and the missing-applications paragraph name the profile's sheet, and without one leave it out and say the import only archives (asreconcileTarget()does). Mutations turning the section's pass-through off make the new tests fail. - Stand-ins (
e8dce55): constructors andgetErrors()as in OpenRegister'sdevelopment. - Description: rewritten Checks and Merge-order sections; #1289,
CmdbRegisterSchemaFields, the yaml bump and the stand-in fix described. - The pre-push check on these fixes found the literal sheet name still in the info-panel paragraph, two defaults using the constant directly, an
@e2e excludereason that did not hold, small wording gaps and a singular/plural slip; all fixed before the push. CI then flagged an import order (perfectionist/sort-named-imports), fixed in727793f.
✅ Verified clean
afaa085: same behaviour (relation-wins merge,reasonOforder, fail-closed on an unreadable register, fragment or glob,hideOnForm/calculations as system, lazy cache);CmdbImportPackRulesTest31/31, probe 44/44.- Merge resolution: no live copy of the replaced
{first}/{second}keys; en/nl json and js in sync; warning order unchanged for every test; CmdbImport.vue help and info panel as ondevelopmentplus the mapping block; docs, CHANGELOG, spec list and e2e imports keep both sides. 6070859: lock only, tag v6.4.48 (2026-10-12), in packages-dev.68c49f4: every "store unavailable" test injects made-up class names, so the real classes make none of them pass vacuously.
Gates: CI quality / Hydra Gates ✅ on 68c49f4 (89 of 89 applicable gates); locally on the fixes 69 of 69 applicable gates. CI on 727793f started with the last push.
Checks after the fixes: jest 215/215, test:l10n, check:l10n-js, prettier, stylelint clean, phpcs/phpmd/phpstan clean on CmdbImportProfile.php. Not run locally: the PHPUnit service tests (need a Nextcloud tree, CI runs them), eslint (local Node too old; CI caught the one error), vitest's cmdbImportMapping.spec.js (local Node).
Jira context: WOO-588 (mapping), WOO-590 (beta release).
Before the next round: fix rules ON — fix-induced 5 + missed-sibling 0 ≥ new 1 (copy-missed 1). This round's findings came almost all from the conflict resolution and the CI fixes; the next round's fixes go through the class sweep and the pre-push check first.
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 | ✅ | ✅ 133/133 | |||
| npm | ✅ | ✅ 807/807 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 1426/1426 · 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-10-15 12:02 UTC
Download the full PDF report from the workflow artifacts.
What
The CMDB import runs the mapping packs stored in OpenRegister, where an admin views and edits them (ConductionNL/openregister#4602, the migration-pack editor). The packs under
lib/Settings/cmdb-import/stay the seed and the fallback. Rework of this PR's first version (read-only overview of the shipped files) after the decision of 2026-10-12: mappings are managed in OpenRegister. The settings UI for it, stackiq#1289 (which pack the next import runs, its source and version, "Edit in OpenRegister"), was stacked on this PR and is merged into it, so this PR carries both.How
CmdbImportProfile,CmdbImportPackStore): module, manufacturer, usage and business owner come from OpenRegister's migration-pack store by slug; the municipality pack maps the import options and always comes from its file. A stored pack is validated byPackDefinitionValidatorand by the import's own rules (CmdbImportPackRules): required targets, and the writable fields of the pack's target schema, read from the register stackiq ships (fragments included), so a property added to the schema can be mapped without a code change. Refused: object metadata, the keys and relations the import sets, every other relation, files, the publication dates and the admin fields; the business-owner pack writes into no schema and keepsnameandrole. A broken pack stops the import with 503MAPPING_UNAVAILABLEnaming the pack, never a silent fallback.SeedCmdbImportPacks, install + post-migration; version bumped so it runs on upgrade): the 4 packs are created as built-in with anx-stackiqmarker (shipped version + hash). A later release updates a stored pack that was not edited; an edited one is left alone and reported as "newer version shipped". A deleted pack is seeded again on the next import (not on a read of the mapping). The shipped TOPdesk packs start at version 1.0.0, their first version in OpenRegister; whether a stored pack is updated is decided by the shipped pack's hash, not its version.CmdbImportPackStoreProbe): OpenRegister's pack store counts only when it has the slug-lock class of openregister#4602 and no write method of its pack controller carries an attribute or annotation that skips the CSRF check or admits non-admins (checked by reflection). Without it the import uses the shipped files and says so in the report; once a seed happened, a missing store fails closed.loadForView()(storefind()only): a pack not stored yet is shown from its shipped file, nothing is seeded, updated or recorded. Only the import run (load()) and the repair step write.updatedAt; when an admin saved it in between, OpenRegister refuses (PackChangedException) and the store keeps the edit.GET /api/settings/cmdb-import/mappingadds per packsource,packId,storedVersion,shippedVersion,edited,newerShipped,editable,editUrl(IURLGenerator), and top-levelopenRegisterPacks;profile.archiveSheetnames the archive sheet (null when the profile names none), so the section's help texts name the sheets the server's profile reads, the archive sheet included.hideOnForm, calculations, fragments, fail-closed on an unreadable fragment) are read byCmdbRegisterSchemaFields, split out ofCmdbImportPackRulesto stay within the phpmd limits.cmdb-import-mapping-viewrewritten for this scope (one mapping per instance);docs/features/cmdb-import.mdexplains viewing and editing in OpenRegister.Checks
CI on
68c49f46: 39 green, 10 skipped, including PHPUnit (PHP 8.3, NC stable35, pgsql), Hydra Gates (89 of 89 applicable gates), phpcs, phpmd, phpstan, psalm and eslint. Since then (e8dce552,e7988c37,8e027d2c, review fixes): jest 215 of 215,test:l10n,check:l10n-js, prettier and stylelint clean,openspec validate --strictvalid for both changes, Hydra Gates locally 69 of 69 applicable gates; the PHPUnit service tests need a Nextcloud tree and run in CI.Earlier, at e2081bb: the Cmdb services, the repair step and the mapping endpoint 130 of 130; e2e on the rig (OpenRegister with #4602):
cmdb-import-packs.spec.tsandcmdb-import-mapping.spec.ts9 of 9.Rig (NC 35; OpenRegister development + openregister#4602; fixtures only): the 4 packs are seeded (on an instance that upgraded stackiq before OpenRegister had the hardened store, the seed runs at the next repair or import); the endpoint reports
source: openregisterwith edit links; an edited pack is what the import runs (2/2 rows); a pack mappingproviderstops the import with 503 naming the pack; restoring the pack makes it "not edited" again.Merge order and conflicts
source: filewith noeditUrl. Promote the OpenRegister beta before the stackiq beta.8a80c178), so this PR must be merged with a merge commit, not squashed. The resolution:topdesk-profile.jsonstays at version 1.0.0 with feat(cmdb-import): read the archive sheet and let module and usage be archived #1285's description; the list-based sheet texts name the archive sheet; the shipped-fallback warning moved intoaddRunWarnings(); both changes now carry the same REQ-CMDB-014 block, so archiving them in either order keeps the whole requirement.composer.lock: symfony/yaml 6.4.40 → 6.4.48 for CVE-2026-109413, which made the composer audit fail on every stackiq branch; development still has 6.4.40 until this PR merges.Jira: WOO-588 — plan: https://github.com/ConductionNL/readonly-mirror-wilco-claude-plans/tree/dev-mirror/issues/jira/WOO-586/sub-tasks/WOO-588/
🤖 Generated with Claude Code