Repository navigation
Release: merge development into beta - #1269
github-actions[bot] wants to merge 155 commits into
Conversation
…0261008095136 chore(sync): carry beta back into development
…x 47 @SPEC paths Two findings from Thijn's re-review of the beta promotion (#1026), after #1261 restored the frozen widget id: Restoring `softwarecatalog_concept_organisaties_widget` keeps the widget for users from before the rename, but drops it for everyone who added it while stable 0.1.148 to 0.2.0 and beta registered it as `stackiq_concept_organisaties_widget`. The Dashboard app keeps that choice in `oc_preferences` under its own app id `dashboard`, key `layout`, as a comma-separated list of widget ids, which IConfig reaches like this app's own rows. The new post-migration repair step RepointDashboardWidgetId rewrites the renamed id to the frozen one for every seen user, in place and without listing the frozen id twice; a layout without the renamed id is left alone. The widget comment and the app-id-rename spec no longer claim the rows are out of reach. The archive pass (1aa2346, 7339b35) moved four changes without updating their @SPEC tags: wizard-dataset-card-load (4), portal-contribution (8), publication-field-rules (20) and rename-app-id-to-stackiq (14), 47 tags in 16 files now point at openspec/specs/<capability>/spec.md. The anchors were checked against the synced specs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… runs Hydra gate-110: a repair step added without moving <version> in appinfo/info.xml makes `occ upgrade` answer "No upgrade required." and run none of them. 0.2.4-unstable.20261001120000 → 0.2.5-unstable.20261008120000, the same shape as #997 for the EOL repair step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fix(dashboard): re-point dashboards from the renamed widget id and fix 47 @SPEC paths
chore(parity): normalise features in the capability matrix
…cations list by _ids The Applications page sent the facet/search match as a plain `id` filter, which OpenRegister's objects endpoint ignores (verified on 2.1.36: `id[]=` returns the whole list, `_ids[]=` narrows), so a search or GEMMA facet selection narrowed nothing while the counts said it did. The narrowing filter now uses `_ids` (and the no-match sentinel still yields an empty list); the helper has its own spec. Reference columns rendered the raw uuid: `provider` on Applications, the organisation on an application's Usages, the application on an organisation's usages, connections and compliance claims, and the module on Module versions and Applications in use. Those columns are now column objects with the library's `fkResolve` cell widget (uuid → name through the object store) and their own label — an object-typed property gets no schema-generated column, so a bare key showed the key as its header. An organisation's Applications list named no application (only licence and BBN level) and had no way to the rest beyond "+N more"; it now leads with the name and links to the Applications page filtered on the organisation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review of #1275 (f1): "In development since" and "In use since" were the only two of the thirteen new column labels missing from nl.json, so Dutch users saw them in English. Added to en.json and nl.json and the browser files rebuilt with `npm run l10n:build`; `npm run test:l10n` passes (every used key present in en.json). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
chore(parity): every spec under a feature, every capability on a screen
CI on #1275: `quality / Frontend Check (format)` flagged the three new or changed JS/Vue files (`npx prettier --write` on them; no semantic change, `npm run format` now clean) and Hydra gate-16 spec-coverage flagged `facetNarrowingFilter` as a changed method without an @SPEC tag — the tag now sits on the function docblock (both anchors are requirement headings in openspec/specs/gemma-faceted-search/spec.md). eslint 0 errors; the helper's spec still passes (3 tests). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ings fix(ui): name references in the catalogue tables and narrow the Applications list by _ids
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.15.2 to 8.2.0. - [Release notes](https://github.com/guzzle/guzzle/releases) - [Changelog](https://github.com/guzzle/guzzle/blob/8.2/CHANGELOG.md) - [Commits](guzzle/guzzle@7.15.2...8.2.0) --- updated-dependencies: - dependency-name: guzzlehttp/guzzle dependency-version: 8.2.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… 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>
…ive them the Search and Columns sidebar A free-text search on the faceted Applications page narrowed the list by every id the facets matched, sent as _ids[] in the query string. From about 170 matches the request line was too long, the server answered HTTP 414 and the list showed an error on every search after that. The page now has the Search and Columns sidebar the other index pages have. Its search is the list's own _search, which OpenRegister pages server side; the facet counts follow the same term. Only a GEMMA facet selection still narrows the list by the matched ids. The Services page uses the same view and gets the same sidebar, as its toolbar search box is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…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>
…se list The business owner column showed the contact person's uuid. A contact person in OpenRegister carries only a contactsUid; the name is in Nextcloud Contacts (the CMDB import puts its owners in the importing admin's "Stackiq CMDB owners" address book). GET /api/contact-names?ids[]=<uuid> gives the names for a signed-in user: it reads the contact persons with the caller's own OpenRegister rights and looks each contactsUid up in the address books Nextcloud gives the caller. A name is only returned to someone who can already read both the contact person and the contact. There is no public route and nothing is stored, so the owner stays unreadable for anonymous visitors, OpenCatalogi and portaliq. The new contactName cell widget batches the cells of one page into one request and keeps the uuid when the caller has no name for it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The release build runs npm ci, so it bundles the version in the lock. 2.73.1 carries nextcloud-vue#1390: the page-size selector of an index page works again, and the Related panel of a detail page opens the related object and shows names instead of uuids. The floor in package.json moves with it, so an install can never resolve below the version that has those fixes. The lock also drops "dev": true from five optional platform binaries; that is the newer npm writing the lock, not a dependency change. Co-Authored-By: Claude Opus 5.5 (1M context) <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
…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>
…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
…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
…sidebar fix(ui): search the Applications and Services lists server side, with the Search and Columns sidebar
feat(contacts): show a contact person's name in Applications in use, on its page and in the organisation's list
…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>
…chive-reconciliation-1-reader feat(cmdb-import): read the archive sheet and let module and usage be archived
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>
…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>
perfectionist/sort-named-imports wants MISSING_RECORDS_MODES before missingApplicationsText. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pping-view-1-endpoint feat(cmdb-import): run the mapping packs stored in OpenRegister, seeded from the shipped files
| { "id": "org-modules", "type": "object-list", "title": "Applications", "icon": "Package", "content": { "register": "@resolve:voorzieningen_register", "schema": "module", "filter": { "provider": "@objectId" }, "columns": [ { "key": "licentietype", "label": "License type" }, { "key": "bbnLevel", "label": "BBN level" } ], "limit": 25, "rowRoute": "ModuleDetail", "emptyText": "No applications registered for this organisation yet" } }, | ||
| { "id": "org-usages", "type": "object-list", "title": "Applications in use", "icon": "OfficeBuilding", "content": { "register": "@resolve:voorzieningen_register", "schema": "usage", "filter": { "consumer": "@objectId" }, "columns": [ { "key": "module", "label": "Application" }, { "key": "moduleVersion", "label": "Version" }, { "key": "status", "label": "Status" } ], "limit": 25, "rowRoute": "GebruikDetail", "viewAllRoute": "Gebruik", "viewAllQuery": { "consumer": "@objectId" }, "allowCreate": false, "emptyText": "No applications in use recorded for this organisation yet" } }, | ||
| { "id": "org-contactpersonen", "type": "object-list", "title": "Contact persons", "icon": "AccountGroup", "content": { "register": "@resolve:voorzieningen_register", "schema": "contactPerson", "filter": { "organization": "@objectId" }, "columns": [ { "key": "role", "label": "Function" }, { "key": "roles", "label": "Roles" } ], "limit": 25, "rowRoute": "ContactpersoonDetail", "emptyText": "No contact persons linked to this organisation yet" } } | ||
| { "id": "org-modules", "type": "object-list", "title": "Applications", "icon": "Package", "content": { "register": "@resolve:voorzieningen_register", "schema": "module", "filter": { "provider": "@objectId" }, "columns": [ { "key": "name", "label": "Name" }, { "key": "licentietype", "label": "License type" }, { "key": "bbnLevel", "label": "BBN level" } ], "limit": 25, "rowRoute": "ModuleDetail", "viewAllRoute": "Modules", "viewAllQuery": { "provider": "@objectId" }, "emptyText": "No applications registered for this organisation yet" } }, |
There was a problem hiding this comment.
🟢 GEMMA facet counts still ignore the provider narrowing from "View all" (spot-check of the open thread on #1275)
The organisation page's org-modules widget opens Modules with viewAllQuery: { provider: @objectId }. The list applies that key, but fetchFacets in src/store/modules/facets.js still sends only the _gf_* filters, the search term and organization, so the sidebar counts cover all applications while the table shows the organisation's. Unchanged at the head; cosmetic, as the thread already says.
Fix: forward non-_gf_ query keys to the facets request, or hide the sidebar while the list is pre-filtered. Fine to leave for a later change.
Done when: grep -n provider src/views/FacetedCatalogIndexView.vue src/store/modules/facets.js finds the key being forwarded, or the thread is closed as a known limitation.
Verification: grep -n provider src/views/FacetedCatalogIndexView.vue src/store/modules/facets.js -> no match at the head; fetchFacetsFromApi(schema, { filters, search, organization }) at facets.js:201
| type="radio" | ||
| :disabled="importing" | ||
| data-testid="cmdb-import-missing-records-archive"> | ||
| {{ t('stackiq', 'Archive or delete them') }} |
There was a problem hiding this comment.
🟢 Without an archive sheet the option texts still promise the trash (spot-check of the open thread on #1288)
cmdbImport.js switches its info paragraph on archiveSheet (line ~1237 says "is archived" only when no archive sheet is configured), but the radio label "Archive or delete them" (line 141), its help "...archived or moved to the trash" (line 156) and the info line "...archived or deleted" (line ~420) in CmdbImport.vue do not. Still true at the head. The shipped profile always names archiveSheet, so it does not show today; it matters only once a profile without one is supported (the thread says so too).
Fix: pass the profile's archiveSheet into these three texts, or record in the profile docs that archiveSheet is required.
Done when: a mounted section with archiveSheet: null renders no "delete"/"trash" wording (add the case to CmdbImport.spec.js, which today asserts not.toContain('moved to the trash') only for the null-archive info panel at line 383).
Verification: git grep -n "Archive or delete them\|moved to the trash\|archived or deleted" -- src/views src/utils -> unconditional in CmdbImport.vue:141,156,420
| { "key": "module", "label": "Application", "widget": "fkResolve", "widgetProps": { "register": "@resolve:voorzieningen_register", "schema": "module", "labelField": "name" } }, | ||
| { "key": "moduleVersion", "label": "Version", "widget": "fkResolve", "widgetProps": { "register": "@resolve:voorzieningen_register", "schema": "moduleVersion", "labelField": "version" } }, | ||
| "status", | ||
| { "key": "businessOwner", "label": "Business owner", "widget": "contactName" }, |
There was a problem hiding this comment.
🟢 The Technical owner column of Applications in use was not given the contactName widget
#1292 fixes the Business owner column, which showed the contact person's uuid because a contactPerson carries only contactsUid (its objectNameField) and the name lives in Nextcloud Contacts. technicalOwner is the same $ref contactPerson relation, set through the same picker, but its column is still the plain key on the next line, so it presumably shows the same uuid. The PR description names only the business owner. Heads-up, not verified on a rig.
Fix: { "key": "technicalOwner", "label": "Technical owner", "widget": "contactName" }, plus the same in the organisation list if it shows owners.
Done when: on a rig, a usage with a technical owner that is in the viewer's address book shows the name in the list, and the uuid for a viewer without access.
Verification: sed -n 26,27p src/manifest.d/usages.json -> businessOwner has "widget": "contactName", technicalOwner is the bare string
| { "key": "businessOwner", "label": "Business owner", "widget": "contactName" }, | |
| { "key": "businessOwner", "label": "Business owner", "widget": "contactName" }, |
| }, | ||
| "screen": { | ||
| "canvas": "design-system", | ||
| "board": "SkModuleversie" |
There was a problem hiding this comment.
🟢 screen.board is a string on 118 capabilities and a list on 44; 41 decided-no rows carry screen: {}
The new screen block uses "board": "SkModuleversie" (a string) on 118 rows and "board": ["SkApplicaties", "SkCmdb"] (a list) on 44, while specScreens.*.board is always a list. A reader that iterates board reads a string character by character (I did, by accident, while counting boards). Also 41 decided-no rows have an empty screen object with no reason, whereas 13 other rows and 22 specScreens entries give a reason when there is no board. And four specs that sit under a feature (ai-system-inventory, contract-administration, maintenance-and-supplier-roadmap, open-data-publishing) have no specScreens entry although the commit says every spec is placed on a screen or given a reason; their rows may cover them, not verified.
Fix: make board always a list, give the empty screen rows a reason, and add the four specs to specScreens (or state the rule that a spec with rows needs no entry).
Done when: python3 -c over the file finds type(board) is list for every non-empty screen, and every spec dir under openspec/specs is a key of specScreens or named in a feature with a row on a board.
Verification: python3 -c "import json,collections;d=json.load(open('openspec/parity/capabilities.json'));print(collections.Counter(type((c.get('screen') or {}).get('board')).__name__ for c in d['capabilities']))" -> str 118, list 44, NoneType 54
| |---|---|---| | ||
| | `maxFileBytes` | `10485760` (10 MB) | the size of the uploaded file | | ||
| | `maxRowsPerSheet` | `10000` | the rows with data on one CMDB sheet | | ||
| | `maxRowsPerSheet` | `10000` | the rows with data on one CMDB sheet, and on the archive sheet | |
There was a problem hiding this comment.
🟢 "Five limits are read from the profile" above a table of six
The sentence at line 400 says five limits, the table under it has six rows (maxFileBytes, maxRowsPerSheet, maxUncompressedBytes, maxPartBytes, maxSharedStrings, maxReferencedStringBytes; the profile JSON has the same six). The sentence is older than these PRs (it was already five at the merge-base against six rows), the table rows are rewritten in #1285 (line 406 etc.), so it is worth fixing together. Anchored on the nearest changed line.
Fix: "Six limits".
Done when: git grep -n 'Five limits' -- docs finds nothing and the count matches the table.
Verification: git grep -n 'Five limits' -- docs openspec src lib -> docs/features/cmdb-import.md:400 only; grep -c '^| max' docs/features/cmdb-import.md` -> 6
| - GIVEN the e2e THEN after the first import, importing the variant archives 1234 and soft-deletes 2, the Applications page lists 1234 under "Archived", and importing the original again reports 1234 `unarchived` and 2 `restored` with the object counts back to the first import's | ||
| - GIVEN the docs THEN "Repeat imports" describes archive, soft delete, the archive sheet, the option and the return of an application, and the error table has `ARCHIVE_UNAVAILABLE` and the new meaning of `MISSING_RECORDS_UNSUPPORTED` | ||
| - [x] Implement | ||
| - [ ] Test |
There was a problem hiding this comment.
🟢 The archive-reconciliation Playwright test was written but never run, and the two import changes have not been exercised together
Task 7's - [ ] Test says the Playwright test (tests/e2e/spec-coverage/cmdb-import.spec.ts: archive 1234, soft-delete 2, bring both back) is "written and not run yet"; only the PHPUnit fixture round trip ran. The mapping-view change (#1288/#1289) then moved the packs the import runs into OpenRegister's store, so the combined path (stored packs + missingRecords=archive default + OpenRegister's ArchiveHandler, which throws when a schema lacks x-openregister-archive) exists only as unit tests with doubles. The checkbox is honest, so this is a gap in evidence, not in the tasks file. The release also changes the default for every beta import to archive.
Fix: run cmdb-import.spec.ts and cmdb-import-mapping.spec.ts on a rig with the head of development (hardened OpenRegister, module schema 0.3.9, usage 1.5.7) before the release; tick Task 7's Test.
Done when: both specs pass on that rig and the checkbox is ticked.
Verification: grep -n '^- \[ \]' openspec/changes/cmdb-import-archive-reconciliation/tasks.md -> line 87 only, with the 'not run yet' status below it
WilcoLouwerse
left a comment
There was a problem hiding this comment.
Verdict: APPROVE (Thorough)
Ready for beta: the CMDB import work (WOO-587 / WOO-588) composes cleanly on development, and what remains is seven minors, two of them earlier threads that still hold.
🟢 Minor (7)
- Facet counts ignore the provider narrowing from View all —
src/manifest.json:424(introduced in #1275). The organisation page'sorg-moduleswidget opensModuleswithviewAllQuery: { provider: @objectId }. - Option texts still promise the trash without an archive sheet —
src/views/settings/sections/CmdbImport.vue:141(introduced in #1287).cmdbImport.jsswitches its info paragraph onarchiveSheet(line ~1237 says "is archived" only when no archive sheet is configured), but the radio label "Archive or delete them" (line 141), its help "...archived or moved to the trash" (line 156) and the inf - Technical owner column still shows the raw uuid —
src/manifest.d/usages.json:26(introduced in #1292). #1292 fixes the Business owner column, which showed the contact person's uuid because acontactPersoncarries onlycontactsUid(itsobjectNameField) and the name lives in Nextcloud Contacts. - Parity matrix: screen.board is string or list —
openspec/parity/capabilities.json:939(introduced in #1276). The newscreenblock uses"board": "SkModuleversie"(a string) on 118 rows and"board": ["SkApplicaties", "SkCmdb"](a list) on 44, whilespecScreens.*.boardis always a list. - Capability area differs from its feature's area (34 rows) —
openspec/parity/capabilities.json:7003(introduced in #1273, #1276), no changed line within reach, so no thread. Every capability now names onefeature, and every feature has anarea, but for 34 of 216 rowscapability.area != feature.area, for exampleland-usage-record(landscape) underoffering-and-usage-listings(market),ins-faceted-searchandins-saved-view(insight) under an architecture feature,sec-multi-factor-sign-in(security) under an organisations feature. If the matrix is meant to roll up by feature area, those rows are counted in the wrong area; if a feature may span areas, the feature's singleareafield is misleading. Row ids, ratings andbuiltstates are otherwise unchanged by these PRs (diffed per id against the merge-base: onlyscreen,feature,featureConfidencechanged). Fix: either move the 34 rows'area(orfeature) so they agree, or document thatfeature.areais the feature's home andcapability.areamay differ. Done when: a script comparingcapability.areawith its feature'sareaprints no rows, or the rule is written next tofeatures. Verification:python3 -cjoining capabilities to features onfeature-> 34 mismatches (list in this finding's verification) - Docs say five limits, table lists six —
docs/features/cmdb-import.md:406(introduced in #1285). The sentence at line 400 says five limits, the table under it has six rows (maxFileBytes,maxRowsPerSheet,maxUncompressedBytes,maxPartBytes,maxSharedStrings,maxReferencedStringBytes; the profile JSON has the same six). - Archive reconciliation e2e never run, never with stored packs —
openspec/changes/cmdb-import-archive-reconciliation/tasks.md:87(introduced in #1286, #1287). Task 7's- [ ] Testsays the Playwright test (tests/e2e/spec-coverage/cmdb-import.spec.ts: archive 1234, soft-delete 2, bring both back) is "written and not run yet"; only the PHPUnit fixture round trip ran.
✅ Verified clean
- The stacked #1286, #1287 and #1289 reached
development: each PR's distinctive symbols occur exactly once at the head, and the stack merges left no duplicate definitions or JSON keys. - WOO-587 / WOO-588 seam: an admin-edited OpenRegister pack cannot move the reconcile key (
externalNumbercomes from the profile key column;externalKey,moduleandconsumerare refused). - The TOPdesk profile version going from 2.0.0 (beta) to 1.0.0 is cosmetic: the pack store decides seed, update and edited by a sha256 of the definition, and nothing compares versions.
- Dependabot #1035 (guzzle ^7.8 → ^8.2) is
require-devonly; the one integration test uses verbs and anautharray that Guzzle 8.2 still supports. package-lock.jsonpins@conduction/nextcloud-vue2.73.1.
Scope decision: promotion of 154 commits, 0 direct. 15 merged PRs: the 10 CMDB-import PRs by WilcoLouwerse (#1271–#1292, ~33k lines) trusted on their earlier self-review after a spot-check; Ruben's #1268, #1273, #1276, #1283 and dependabot's #1035 read at full depth, plus an integration pass. Residual: the reconcile and transition bodies in CmdbExportImportService, CmdbWorkbookReader, the profile validation internals and the tests of the self-reviewed PRs were not re-read.
Gates: quality / Hydra Gates and quality / Quality Report green on 7bc44c09.
Before the next round: the archive-reconciliation Playwright test has never run; worth one run on a rig before beta → main.
…1039) Bumps [conduction/hydra-gates](https://github.com/ConductionNL/.github) from 1.16.1 to 1.18.0. - [Release notes](https://github.com/ConductionNL/.github/releases) - [Commits](ConductionNL/.github@v1.16.1...v1.18.0) --- updated-dependencies: - dependency-name: conduction/hydra-gates dependency-version: 1.18.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
| } | ||
|
|
||
| $target = CmdbImportReport::ARCHIVED; | ||
| if ($archive['present'] === true && $archive['uncachedRows'] === [] && $this->unreadableKeyRows === []) { |
There was a problem hiding this comment.
🔴 A workbook without one of the two CMDB sheets soft-deletes every application from that sheet, with no warning
CmdbWorkbookReader::sourceSheets() keeps array_intersect($expected, $available) and throws NO_SOURCE_SHEET only when none of the profile's sheets is present. So a workbook that has Onbeh Applicaties CMDB and Gearchiveerde Applicaties but not Beheerde Applicaties CMDB (the sheet was renamed, or someone exported a partial workbook) is read without an error or a warning. The only warning for a missing sheet in the reader is the one for an optional column (line 862).
In reconcileTarget() the workbook counts as authoritative for deletion when the archive sheet is present and no key was unreadable. Whether every source sheet was there is never checked. As a result, every application that an earlier import created from the absent sheet is missing from winningSheets and moves to DELETED: its usage and module are soft-deleted. missingRecords defaults to archive (DEFAULT_MISSING_RECORDS), so this is what happens on a plain re-import. The objects are recoverable from the trash, but the report does not say why they left. No unit test covers a workbook with only some of the source sheets.
Fix: have the reader return the expected sheets that are absent. Treat a missing sheet like an unreadable key: never DELETED (archive at most, or skip reconciliation), and add an import warning naming the sheet.
There was a problem hiding this comment.
Tracked as #1304: filed rather than fixed here because the fix lands through development and this release PR is re-cut after every merge.
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES (Quick)
Not ready for beta: a CMDB workbook that lacks one of its source sheets imports without an error and soft-deletes every application that sheet created, on a plain re-import with the default missingRecords: archive.
🔴 Blockers (1)
- A missing CMDB source sheet soft-deletes all its applications, silently —
lib/Service/CmdbExportImportService.php:2681(introduced in #1288). The reader refuses a workbook only when none of its source sheets is present, and the reconciliation scope is every earlier import, so a workbook missing one sheet soft-deletes every application that sheet created, with no reason in the report. Tracked in #1304.
Tracking issues
- #1304 — A missing CMDB source sheet soft-deletes all its applications, silently
Scope decision: full, provenance-driven review at Quick strictness (definite blockers only), in two slices (backend; frontend, manifest and docs). 16 merged PRs, 0 direct commits. Most of the code (#1285–#1292) was written by Wilco, so his dismissed approval of this release was a self-review. His 6 open threads were not repeated.
Checked and holding: guzzle 8 is dev-only and unused in lib/; the dashboard-widget repair step is idempotent; the mapping endpoint is admin-only; reconciliation is limited to this municipality's own imports; every new manifest key and frontend endpoint resolves.
Not read: about 40 files (the CmdbImportPack* classes, contact sync, docs, most tests).
CI: all checks green on af446eb; E2E (Playwright) was skipped, and the earlier status comment made approval conditional on a green E2E run.
🤖 Generated with Claude Code
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.
Changelist
Snapshot of
developmentat af446eb (16 Oct 2026) againstbetaat 612cad4: 12 PRs, 3 more bundled inside them. Thechore(sync)PR that carries beta back into development is left out. PRs merged to development after this snapshot are not listed yet.The Rotterdam CMDB import (WOO-586, WOO-587, WOO-588): archive reconciliation, mapping packs run from OpenRegister with a link to their editor, server-side search in the Applications and Services lists, contact person names, and
@conduction/nextcloud-vue2.73.1.Features (3)
Fixes (3)
@specpathsDependencies and security (3)
@conduction/nextcloud-vuefrom 2.65.0 to 2.73.1Maintenance (CI, gates, specs, docs) (3)