Skip to content

feat(cmdb-import): read the archive sheet and let module and usage be archived - #1285

Merged
WilcoLouwerse merged 54 commits into
developmentfrom
feat/woo587-cmdb-import-archive-reconciliation-1-reader
Oct 15, 2026
Merged

WilcoLouwerse merged 54 commits into
developmentfrom
feat/woo587-cmdb-import-archive-reconciliation-1-reader

Conversation

@WilcoLouwerse

@WilcoLouwerse WilcoLouwerse commented Oct 12, 2026 •

Copy link
Copy Markdown

What

First of three stacked PRs that replace #1281 (archive or soft-delete applications that left a newer CMDB export). This slice reads the archive sheet; nothing acts on it yet.

  • Spec: OpenSpec change cmdb-import-archive-reconciliation: ADDED REQ-CMDB-015..017, MODIFIED REQ-CMDB-012 and REQ-CMDB-014. The design records why object state is read per lens and from the trash (D9).
  • Schemas: module (0.3.9) and usage (1.5.7) opt in to archiving through x-openregister-archive. Run Force Update after installing.
  • Profile: the import profile names the sheet "Gearchiveerde Applicaties" and the accepted missingRecords modes (keep, archive), readable before the packs are validated.
  • Reader: when the workbook has the archive sheet, it is read under the same bounds as the CMDB sheets, for its APPID column only, and its APPIDs are returned next to the rows.
  • Fixture: topdesk-archived-applications.xlsx, a later export in which APPID 1234 moved to the archive sheet and APPID 2 is on no sheet.

Stack

  1. This PR → development
  2. feat(cmdb-import): archive or soft-delete applications that left the export #1286: the reconciliation in the import service and the endpoint option
  3. feat(cmdb-import): offer the missing-records choice and an Archived quick filter #1287: the import section's choice, the Archived quick filter, e2e and docs

Jira: WOO-587 (sub-task of WOO-586). Plan: WOO-587 plan. Review and merge are tracked in WOO-590.

Review fixes (2026-10-12)

Commit What
388181a3 The live spec names the renamed module-version test (039).
16337d91 An archive-sheet APPID without a cached value is reported (uncachedRows), not dropped; the spec delta, design D3/D5 and task 2 say what follows (archive, never delete — #1286).
11d9a23c One test per bound the archive sheet is read under (part size, row span, referenced-string budget, key-column-only filter, archiveSheetName() null cases), each mutation-checked.
aa3b6648, 70d6f934 Proposal, design and tasks name all three archive-only cases; design D8 describes the Archived tab ignoring facets and search.

Tests

Check Result
PHPUnit CMDB, Settings and Fixtures suites on this slice alone ✅ 313 tests
phpcs / phpmd / phpstan on this slice alone ✅ 0 errors

🤖 Generated with Claude Code

WilcoLouwerse and others added 3 commits October 12, 2026 12:09
… 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>
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 0c0afb5

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 ✅ 1233/1233 · 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.

WilcoLouwerse and others added 7 commits October 12, 2026 13:41
…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>
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>
Comment thread lib/Service/Cmdb/CmdbWorkbookReader.php
Comment thread lib/Service/Cmdb/CmdbWorkbookReader.php
Comment thread openspec/changes/cmdb-import-archive-reconciliation/tasks.md
Comment thread tests/Unit/Service/Cmdb/CmdbWorkbookReaderTest.php
Comment thread lib/Service/Cmdb/CmdbWorkbookReader.php
Comment thread lib/Service/Cmdb/CmdbWorkbookReader.php
Comment thread lib/Settings/cmdb-import/topdesk-profile.json

@WilcoLouwerse WilcoLouwerse left a comment •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE (Thorough) — self-review posted as COMMENT (GitHub blocks self-APPROVE)

No blockers: the archive sheet is read under the CMDB sheets' bounds and the readBounded() refactor returns the same archive array in all three branches. The concerns are about silent gaps (uncached formulas, untested bounds) that matter once #1286 acts on these APPIDs.

Update 2026-10-12: All three concerns were fixed on the branch after posting; the four minors stay open as follow-ups.

🟡 Concerns (3)

  • Uncached archive-sheet formulas vanish without a warning — lib/Service/Cmdb/CmdbWorkbookReader.php:366. 🟡 Concern — the archive sheet drops a formula without a cached value silently, and reports present: true with fewer APPIDs The archive sheet is formula-derived: the new fixture's APPID cell is <f>..</f><v>1234</v> (build-fixtures.py, archive_appid). ✅ Addressed in 16337d91.
  • Archive sheet bounds now apply to imports that keep — lib/Service/Cmdb/CmdbWorkbookReader.php:187. 🟡 Concern — an export that imports today can be refused because of a sheet the admin chose not to act on read() takes no mode, so from this slice on every import, including missingRecords=keep, loads the archive sheet and enforces on it the part-size bound (here), the row span, the referenced-string budget and TOO_MANY_ROWS. ✅ Addressed in cdbec3d3 (#1287).
  • 'Same bounds as the CMDB sheets' is tested for one of five bounds — tests/Unit/Service/Cmdb/CmdbWorkbookReaderTest.php:1005. 🟡 Concern — the totality promise "the archive sheet is read under the same bounds as the CMDB sheets, APPID column only" has one test REQ-CMDB-015, design D3 and the PR body promise that. ✅ Addressed in 11d9a23c.

🟢 Minor (4)

  • tasks.md ticks tasks whose code is in #1286/#1287 — openspec/changes/cmdb-import-archive-reconciliation/tasks.md:37. 🟡 Concern — tasks 3 to 7 are marked implemented and tested in a PR that contains none of that code tasks.md ticks [x] Implement and [x] Test for Task 3 (matching revives, CmdbExportImportService.php, CmdbImportReport.php), Task 4 (reconciliation), Task 5 (controller option), Task 6 (settings section) and Task 7 (fixture, docs; only its Test is open).
  • Referenced-string budget shrinks for workbooks without the archive sheet — lib/Service/Cmdb/CmdbWorkbookReader.php:313. 🟢 Minor — count($profile->readSheetNames()) always counts the archive sheet, also when the workbook has none assertReferencedStringBytes divides maxReferencedStringBytes by sheetCount (CmdbWorkbookBounds.php:207).
  • appIds are raw cell values, not match keys — lib/Service/Cmdb/CmdbWorkbookReader.php:366. 🟢 Minor — archive.appIds holds raw typed cell values; say so, or normalise Read-only probe: cells 7.0, ' 8 ', 'app-9' come back as 7 (int), ' 8 ' and 'app-9'.
  • Profile accepts 'archive' while the endpoint still refuses it — lib/Settings/cmdb-import/topdesk-profile.json:48. 🟢 Minor — heads-up: until #1286 merges, the profile and the endpoint disagree The profile now lists ["keep", "archive"] and missingRecordsModes() reads it, but nothing in lib/ calls missingRecordsModes() on this head (only the unit test does): CmdbExportImportService::supportsMissingRecords() is still $mode === 'keep' and CmdbImportController still answers details.accepted: ['keep'].

✅ Fixed before posting (1)

  • Renamed test is still named by the live spec — fixed in 388181a.

✅ Verified clean

  • readBounded() refactor: the returned archive array matches the pre-refactor version (softwarecatalog 141ea249) for no sheet, a sheet without the key column and a sheet with it — checked by reading and a read-only probe.
  • Schema bumps module 0.3.9 / usage 1.5.7 follow the fragment-version convention and are pinned by TopdeskCmdbFragmentTest and PublicationFieldRulesTest.
  • x-openregister-archive matches OpenRegister's Schema::ARCHIVE_ANNOTATION.

Scope decision: slice 1 of 3 of the closed #1281, 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-031, ADR-032, ADR-037 — all resolve on hydra main.
Jira context: WOO-587 — CMDB-import: gearchiveerde en verdwenen applicaties bij herimport (status: Refinement)

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ de5dedb

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 ✅ 1233/1233 · 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:53 UTC

Download the full PDF report from the workflow artifacts.

WilcoLouwerse and others added 13 commits October 12, 2026 16:54
…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>
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>
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 finished sentence

The sentence the aria-live region announces (and the report heading shows)
only had the row counts, so an import that read 0 rows and archived one
application and deleted one was announced as "0 rows read: 0 created,
0 updated, 0 unchanged" and a screen reader user never heard that
applications were deleted (WCAG 4.1.3).

The sentence now ends with "Applications: ..." listing the non-zero counts,
after a cancel too, and nothing when no application was touched. The docs
mention it.

Review #1287 f5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le APPIDs

The reader now lists the archive-sheet rows whose APPID formula has no
cached value (archive.uncachedRows). The service treats such a sheet like
an absent one for the choice between archive and delete: an application on
no readable sheet may be the one that row holds, so it is archived, never
soft-deleted, and the report carries a warning naming the sheet and the
rows (cut after ten). Applications the sheet does list are archived as
before; keep is unaffected. Without this a workbook saved without cached
values turned every archived application into a deletion, unannounced.

The warning is translated (en, nl) and the OpenAPI description of
missingRecords names the extra case.

Review #1285 f1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e the facet id set

The Applications page narrows its list with `_ids` (the objects a GEMMA facet
or the search matched) and merges the active quick filter's map next to it,
so the tab's `_archived=true` was ANDed with an id set that FacetService
builds from the working set. OpenRegister's default lens leaves archived
objects out of that set, so with a facet selected the Archived tab asked for
live ids that are archived and listed nothing.

The Archived tab's filter now also carries an empty-string `_ids`, which wins
over the page's `_ids` in the merge and is left out of the request. An empty
array would not do: the filter resolver drops it before the merge. The tab
lists the whole archive and ignores the GEMMA selection and the search term;
the docs say so. The vitest runs the package's own filter resolver and query
builder, and pins that the active tab is merged last.

Read in @conduction/nextcloud-vue 2.65.0 (the installed version).

Review #1287 f6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reconciliation listed every usage of the municipality, so a stale
module deleted by hand next to the one a later import created got its own
scope entry next to the working one. With the key on the archive sheet the
trashed pair was restored and archived: a long-deleted copy of the
application appeared in the archive. Every other match path (matchModule,
upsertUsage, findInAnyState) prefers working over archived over trash.

reconciliationScope() now keeps one entry per key through onePerKey(): the
best module state wins, the best usage state breaks a tie, the first
listed the rest. The other entries are left as they are. REQ-CMDB-015, the
design D5 text and task 4 name the duplicate case.

Review #1286 f4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed-row rule testable

The section's tests set `missingRecords` on the component and stubbed the
radio away, so `v-model`, `value`, `name` and `:disabled` on the two radios
were never exercised, no test clicked "Keep them as they are", and the rule
that a deleted application has no link to its module had no test at all.

The jest spec now mounts the section with radio and table stubs that render
what the page does, clicks Keep and reads the posted form, checks the default,
the shared name and the disabled state, and asserts the report table links an
archived or failed row and not a deleted one, and shows no row number for a
reconciliation row. Each test goes red when the wiring or the rule is
mutated. The e2e adds a Keep import that must archive and delete nothing and
a check that the archived application is absent from the Applications page
before the Archived tab; the spec scenario for the choice now points at it.

Review #1287 f8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sheets the import reads

The WORKBOOK_TOO_LARGE hint said the import "reads only" the two CMDB sheets
and told the admin to delete the sheets it does not read; the section's file
help said other sheets are ignored. The import also reads the APPID column of
"Gearchiveerde Applicaties", and an admin who followed the hint would delete
that sheet, so the next import archives and never deletes.

Both strings now name the archive sheet and its APPID column (replacing the
l10n keys in place), and the spec pins the new hint.

Review #1287 f9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REQ-CMDB-015 promises that only this municipality's usages and what its
import created are reconciled, that each application has its own error
boundary, that a cancel before the scope is loaded reads nothing, and that
the match never duplicates (working before archived before deleted). Most
of these branches had no test that a mutation could turn red; the first
one, the consumer re-check on a trash listing that is not filtered by
consumer, guarded the only thing between another municipality's module and
the trash.

Adds tests for: a trashed usage of another consumer; modules with no
external key, a bare prefix or another origin; a delete that OpenRegister
refuses and a restore that throws, each with a following application still
reconciled; more than one page of usages and one chunk of modules; archived
beating deleted and working beating archived in the match; and an archived
match failing its row under keep when the archive services are missing
(design D7). The cancel test now also asserts the trash was not read and
the usages were not paged. Each test was checked by mutating the service
and goes red.

Review #1286 f3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry import

The docs said the import "ignores all other sheets" and that a sheet it does
not read may exceed the part size limit. The archive sheet is read (its APPID
column only) in every import, also with Keep, so it counts against the row
span and part-size limits like a CMDB sheet and can refuse a keep import.

The file section, the row limit, the part-size limit and the WORKBOOK_TOO_LARGE
row now say so, and that deleting the sheet from a copy makes the import
archive and never delete.

Review #1285 f2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The warning has a one-row form and an "in N rows (…)" form that names the
first ten rows; the docs quoted only the first.

Review pre-push q4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ fd8f758

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 ✅ 1239/1239 · 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 16:18 UTC

Download the full PDF report from the workflow artifacts.

WilcoLouwerse and others added 13 commits October 13, 2026 18:44
… document the reader's bounds

Closes the review findings:
- "design D8 describes UI code not in this PR": D8 now says the UI code lands in #1287.
- tasks.md ticked Implement/Test for Tasks 3-7 whose code is not in this PR: unticked, with a status note that they land in #1286 and #1287.
- CmdbWorkbookReader sheet count always includes the archive sheet: documented in the read() docblock (limit / 3 is deliberate).
- appIds are raw cell values: documented as raw, untrimmed and un-normalised; no consumer exists in this PR, so no code change.

Class sweep: docs/features/cmdb-import.md described the referenced-string bound as "once per CMDB sheet"; it now names the archive sheet too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…' into feat/woo587-cmdb-import-archive-reconciliation-2-reconcile
The help under "Applications missing from the export" was a paragraph of
seven lines. It is now one sentence; the full rules, including when
applications are only archived, moved to the section's information
panel.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…able; usages before modules

Closes the review findings:
- "Unreadable CMDB-sheet APPID still soft-deletes": a CMDB-sheet row whose APPID is a formula without a cached value is skipped as empty, so its application looked absent. The run now notes such rows per sheet and, with any, archives instead of soft-deleting and warns naming the CMDB sheet and the rows (reusing the archive-sheet warning). New test testAnUncachedCmdbRowArchivesInsteadOfDeleting.
- "Extras written after the module; no failing-extra test": itemsOf() now orders every usage (own and extra) before every module, as the docblock promises. New test testAFailingExtraUsageFailsTheApplicationBeforeItsModule.
- archiveServices() swallowed the container exception: it is now logged (exception class only) and chained as previous.
- trash() read for missingRecords=keep: kept on purpose (matching is mode independent, a hand-deleted application must be restored, not duplicated); the reason is now in its docblock.

Class sweep: the rule is updated in the service docblocks, openapi.json (missingRecords description), and the change's spec.md (requirement and new scenario), design.md (D3 text, D5 table, risks) and proposal.md. docs/features/cmdb-import.md does not describe archive reconciliation yet, so it holds no copy of the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes the merge artefact from the base fix, which unticked Tasks 3-7 and said their code lands in #1286/#1287:
- Tasks 3, 4 and 5 (matching, reconciliation, controller option) are implemented and tested here: Implement and Test ticked again.
- Tasks 6 and 7 stay unticked: the UI is #1287, and the docs and the e2e run of task 7 are not done.
- The status line now says what is true for this PR.
- Task 4's acceptance criterion also names the CMDB-sheet unreadable APPID case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile' into feat/woo587-cmdb-import-archive-reconciliation-3-ui
…describe what a failed application leaves

Closes the pre-push findings:
- "Fully-empty CMDB row with an uncached APPID stays invisible": the reader dropped a CMDB row whose only content is an APPID formula without a cached value before the service could see it, so its application could still be soft-deleted. read() now returns `uncachedKeyRows` (source sheet => dropped row numbers, collected only when the key column is the uncached one), and the service merges them with the rows that carry `uncached` into the same unreadable-key bookkeeping, so the run archives instead of deleting and warns. New tests: testAnEmptyCmdbRowWithAnUncachedKeyIsListed (reader) and testAFullyEmptyCmdbRowWithAnUncachedKeyArchivesInsteadOfDeleting (service).
- "Failure-branch docblock overstates": the itemsOf() and reconcileApplication() docblocks and design.md D5 and Risks now state the order (own usage, extra usages, module, extra modules) and what a failure leaves: a failing extra usage leaves the own usage changed, that extra visible and the module unchanged, and the row only says failed.

Class sweep: other callers of readRows() are the archive sheet (unchanged, no key filter) and readSourceRows(); the read() docblock names the new key. The spec and proposal speak of "a row of a CMDB sheet" in general and need no change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loudly on a bad lens read

Closes "e2e waits for networkidle, which never settles on Nextcloud
(gate-58)": the archive test now waits for a working application row to be
visible before asserting that the archived application is absent, so the
negative assertion cannot pass on a list that has not rendered. No other
networkidle wait remains in the touched spec (git grep over tests/e2e shows
only comments explaining why it is avoided).

Closes thread 4248187506 (e2e cleanup comment): the header and the helper
doc no longer claim that an interrupted run leaves nothing behind or that
_includeDeleted adds the trash (a soft-deleted row cannot be listed, design
D9); objectsInLens now fails the test on a non-OK response instead of
returning an empty list that made cleanup skip silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ignored

Closes "Archived tab keeps facet sidebar and search active but ignores
them": FacetedCatalogIndexView tracks the active quick-filter tab and shows
a note under the toolbar while a tab whose filter carries the empty _ids
override is active; en and nl strings and the generated l10n js are added.
A mounted view test asserts the note appears on the Archived tab, is gone on
another tab, and goes red with the note removed.

Closes "Archived-tab facet override is tested against a local merge, not the
view": the facet-override test now calls the library's own useSelfFetchList
and reads the fixedFilters getter it hands the list composable, instead of
restating the spread; only the list composable and store are replaced. It
goes red when the _ids override is removed from the manifest. The source
grep test that pinned the merge order is dropped as redundant.

Class sweep: the Services page uses the same view but has no quick filter
with the override, so it shows no note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and unreadable CMDB APPIDs

Makes tasks.md true for this PR after the merge of the reconcile base:
the status line names #1286 for tasks 1-5 and this PR for task 6 (ticked,
with the new Archived-tab note as an acceptance line); task 7 has its
implementation ticked and its Playwright run still open.

docs/features/cmdb-import.md already described archive and soft delete (the
old "left as they are" sentence only remains for the Keep choice); it now
also says that an APPID without a cached value on a CMDB sheet archives
instead of deleting, and that the Archived tab shows a note about the
ignored facets and search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile' into feat/woo587-cmdb-import-archive-reconciliation-3-ui
… tab

The view started with no known tab and only learned of a change from the
tab's click, but it remounts CnIndexPage whenever the facet or search key
changes, which puts the page back on its default tab; the note then showed
over the default list. The view now starts from the tab the library starts
on (first quick filter marked default, else the first) and returns to it
whenever the remount key changes.

Tests: the note follows the default tab at mount, and disappears when a
search change remounts the list while the Archived tab is active; the
remount test goes red without the reset.

The e2e wait for a listed application is scoped to the Applications list
container, so a row of another table cannot satisfy it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reconciliation-3-ui' into the review fixes

# Conflicts:
#	l10n/en.js
#	l10n/en.json
#	l10n/nl.js
#	l10n/nl.json
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 3f23a25

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 ✅ 1239/1239 · 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 17:29 UTC

Download the full PDF report from the workflow artifacts.

@WilcoLouwerse WilcoLouwerse left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE (Standard) — self-review posted as COMMENT (GitHub blocks self-APPROVE)

Nothing left to address: the one new minor and the four open minors from round 1 were fixed on the branch before posting. All 7 threads are resolved. What still stands between this PR and a merge: 1 approval, and the CI run on ae31ac3, which started with this push.

Round Found Status
1 (388181a) 🟡 uncached archive formulas vanished · 🟡 archive bounds applied to keep · 🟡 'same bounds' tested for 1 of 5 · 🟢 tasks.md ticks · 🟢 string budget · 🟢 raw appIds · 🟢 profile vs endpoint 3 🟡 resolved in 16337d9/11d9a23; 3 🟢 resolved in ae31ac3; 1 🟢 needed no change (#1286)
2 (ae31ac3) — this review 🟢 design D8 described UI code from #1287 fixed before posting in ae31ac3
Finding Was Now Where
🟢 tasks.md ticks tasks whose code is in #1286/#1287 🟢 r1 resolved in ae31ac3 thread
🟢 Referenced-string budget shrinks for workbooks without the archive sheet 🟢 r1 resolved in ae31ac3 (documented; docs copy corrected) thread
🟢 appIds are raw cell values, not match keys 🟢 r1 resolved in ae31ac3 (documented) thread
🟢 Profile accepts 'archive' while the endpoint still refuses it 🟢 r1 no change needed: #1286 makes the endpoint accept it thread
🟢 Design D8 describes UI code not in this PR new fixed before posting in ae31ac3 openspec/changes/cmdb-import-archive-reconciliation/design.md:118
Details of this round

Scope decision: delta 388181a…70d6f93 (4 commits, 7 files, 281 changed lines), plus the fix commit ae31ac3 written in this run.

✅ Fixed before posting (1)

  • Design D8 describes UI code not in this PR — ae31ac3: D8 now says the quick filters, the _ids override and the src/icons.js entry land in #1287.

Verified at 70d6f93: 16337d9's $unseen branch is on the live path and covered by testAnUncachedArchiveKeyIsListedNotDropped. The archive-only rule reads the same in proposal, design, tasks and spec.
Gates: CI quality / Hydra Gates ✅ on 70d6f93. CI on ae31ac3 was still running when this verdict was posted. ae31ac3 changes docblock and Markdown text only. phpcs on CmdbWorkbookReader.php is clean; PHPUnit was not run for it.
Jira context: WOO-587, CMDB import: archived and vanished applications on re-import.

…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
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ fc2e137

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 ✅ 1274/1274 · 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:31 UTC

Download the full PDF report from the workflow artifacts.

WilcoLouwerse and others added 4 commits October 15, 2026 10:37
…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>
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/stackiq @ 9df500c

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 ✅ 1281/1281 · 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 09:09 UTC

Download the full PDF report from the workflow artifacts.

WilcoLouwerse added a commit that referenced this pull request Oct 15, 2026
…' 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>
@WilcoLouwerse
WilcoLouwerse merged commit 0506cd3 into development Oct 15, 2026
49 checks passed
@WilcoLouwerse
WilcoLouwerse deleted the feat/woo587-cmdb-import-archive-reconciliation-1-reader branch October 15, 2026 09:24
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