From 24212bd2a12c58226eb09f9369ec87b9a037b545 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 18 Aug 2026 15:57:07 +0200 Subject: [PATCH 01/43] =?UTF-8?q?docs(openspec):=20change=20proposals=20?= =?UTF-8?q?=E2=80=94=20adopt-integration-leaves=20mcp-full-action-surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artifacts only. Every task box is unticked; nothing here is wired to anything yet, and each change is picked up by `/opsx-apply` when it is scheduled. Committed because these were sitting UNTRACKED in the shared checkout across ten apps at once. An untracked directory is one file-sweep away from being swept into an unrelated commit and one branch switch away from being lost, and these carry the design reasoning rather than just a title. --- .../adopt-integration-leaves/.openspec.yaml | 2 + .../adopt-integration-leaves/design.md | 110 +++++++++ .../adopt-integration-leaves/proposal.md | 96 ++++++++ .../specs/catalog-integration-leaves/spec.md | 144 ++++++++++++ .../changes/adopt-integration-leaves/tasks.md | 80 +++++++ .../mcp-full-action-surface/.openspec.yaml | 2 + .../changes/mcp-full-action-surface/design.md | 212 ++++++++++++++++++ .../mcp-full-action-surface/proposal.md | 120 ++++++++++ .../specs/mcp-tool-surface/spec.md | 191 ++++++++++++++++ .../changes/mcp-full-action-surface/tasks.md | 113 ++++++++++ 10 files changed, 1070 insertions(+) create mode 100644 openspec/changes/adopt-integration-leaves/.openspec.yaml create mode 100644 openspec/changes/adopt-integration-leaves/design.md create mode 100644 openspec/changes/adopt-integration-leaves/proposal.md create mode 100644 openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md create mode 100644 openspec/changes/adopt-integration-leaves/tasks.md create mode 100644 openspec/changes/mcp-full-action-surface/.openspec.yaml create mode 100644 openspec/changes/mcp-full-action-surface/design.md create mode 100644 openspec/changes/mcp-full-action-surface/proposal.md create mode 100644 openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md create mode 100644 openspec/changes/mcp-full-action-surface/tasks.md diff --git a/openspec/changes/adopt-integration-leaves/.openspec.yaml b/openspec/changes/adopt-integration-leaves/.openspec.yaml new file mode 100644 index 00000000..95672402 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-18 diff --git a/openspec/changes/adopt-integration-leaves/design.md b/openspec/changes/adopt-integration-leaves/design.md new file mode 100644 index 00000000..be99cf97 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/design.md @@ -0,0 +1,110 @@ +# Design — adopt-integration-leaves + +## 1. Leaf-per-schema mapping and why each pairing is the right one + +| Schema | Leaf | Grounding (real fields / services) | +|---|---|---| +| `contactPerson` | `contacts` | `contactsUid` — "Verwijzing (UID) naar de Nextcloud-contactpersoon in het adresboek (OCP\Contacts\IManager)". The catalogue record is explicitly only the ROLE; identity lives in NC Contacts. The leaf makes the vCard (display name, email, avatar) visible and linkable on `ContactpersoonDetail` instead of a bare UID string. | +| `organization` | `contacts` | `organization.contactsUid` — same convention, "contactpersoon van het type organisatie". | +| `contract` | `calendar` | `contract.startDate` / `contract.endDate` ("De einddatum van het contract (indien van toepassing)"). End dates drive renewal planning; the leaf gives every contract a Meetings/Events tab plus the synced end-date event (section 3). | +| `moduleVersion` | `calendar` | `dateEndSupport` ("Startdatum einde ondersteuning") and `dateWithdrawn`. `dateEndSupport` is machine-maintained by the EOL sync (`eolSource`, `eolUpdatedOn` — "Alleen gezet door de EOL-matcher", `EolSyncService::run()`), so the synced event tracks upstream endoflife.date data. | +| `assessment` | `deck` | Reviews carry moderation state (`status` pending/approved/rejected, forced to `pending` server-side by `ReviewService::submit()`, transitioned only by `ModerationService::approve()/reject()` — see `register.d/catalog-ratings.json`). Follow-ups ("moderate this review", "discuss rating 2/10 for module X with the vendor") are card-shaped work; `DeckProvider` supports both link-existing (`{cardId}`) and create-and-link (`{boardId, stackId, title}`). | +| `module` | `bookmarks` | `module.website` is one URL; real applications have docs, changelog, security advisories, pricing pages. `BookmarksProvider` stores links in OR's own `openregister_bookmark_links` table (survives Bookmarks tag edits, caches title/url for the sidebar). | +| `service` | `bookmarks` | `service.website` — same reasoning for supplier service offerings. | + +Leaf ids verified against openregister at HEAD: +`LinkedEntityService::legacyLinkedTypeIds()` = `files`, `mail`, `contacts`, +`notes`, `todos`, `calendar`, `talk`, `deck`; registered +`IntegrationProvider::getId()` values include `contacts`, `calendar`, +`deck`, `bookmarks` (`openregister/lib/Service/Integration/Providers/`). +`LinkedEntityService::validateType()` throws on anything else, and +`Repair/LogDanglingLinkedTypes` logs schemas whose `linkedTypes` name an +unregistered integration — both act as loud guards against a typo in the +fragment. + +## 2. Fragment mechanics — the `contract` array hazard + +`contract` is the ONE schema that already carries `linkedTypes` +(`["decidesk-decisions"]`, in the monolith). ADR-037 fragments are +deep-merged by `SettingsService::loadSettings()` (`deepMergeConfig()`); for +scalar/object keys the merge is a union, but array-of-scalar semantics +(union vs replace) must not be assumed. The fragment therefore declares the +FULL intended array — `["decidesk-decisions", "calendar"]` — which is +correct under either semantic: + +- replace → the merged value is exactly the full array; +- union → `decidesk-decisions` deduplicates, `calendar` is added. + +Task 1.3 verifies the merged output (`/api/settings/load`) contains both +entries exactly once. Losing `decidesk-decisions` would silently break the +ContractApprovalPanel's decision leaf — this is the highest-risk line of the +whole change, hence its own task and scenario. + +## 3. Lifecycle-date calendar sync + +`CalendarProvider` is a read/render surface: it lists CalDAV VEVENTs that +carry `X-OPENREGISTER-*` properties identifying the owning object +(persistence is owned by the Calendar app; creation flows via OR's +`CalendarEventService`). Declaring `calendar` in `linkedTypes` gives the +tab and manual link/create, but nobody will hand-create "contract ends" +events for every contract — so this change adds a thin app-side sync: + +- `lib/Service/LifecycleCalendarService.php` — `syncContract(objectData)` + and `syncModuleVersion(objectData)`; upserts (creates, moves, or + deletes) one all-day linked event per tracked date field via OR's + calendar link path. Event titles are English per fleet convention + (`feedback_english-code`): "Contract ends: {contractNumber}" / + "End of support: {module name} {version}". +- `lib/Listener/LifecycleCalendarListener.php` — subscribes to + OpenRegister's object-saved event for the voorzieningen register, + filters on the `contract` / `moduleVersion` schema slugs resolved + through `SettingsService` (never hard-coded register ids), and delegates + to the service. Deletion of the object removes the linked event (OR's + `ObjectCleanupListener` already unlinks leaf rows; the listener only + needs to handle date-cleared-on-save). +- Idempotency: the event is looked up by its object link + a + deterministic marker (one tracked field = one event), so re-saves and + EOL re-stamps move the single event instead of accumulating duplicates. +- Fail-soft: calendar unavailable (app disabled, no writable calendar) is + logged and never blocks the object save — same graceful-degradation + posture the register's other integrations use. + +**Which calendar?** The events are personal CalDAV objects; the sync runs +in the saving user's session and writes to that user's default calendar +(the same calendar OR's create-event leaf flow targets). A shared +"portfolio calendar" is a legitimate future improvement, deferred — it +needs an ownership/config decision (`declared-config-enforced-nowhere` is +the failure mode to avoid: no config key is introduced here until +something reads it). + +## 4. Deferred (explicitly out of scope) + +- `connection.dateEndSupport` / `dateWithdrawn` — same calendar shape as + `moduleVersion`, deferred until the koppeling detail surface is + reviewed; adding it later is one fragment line + one listener case. +- `compliancy.url` / `evidenceReference` as bookmarks — compliance + evidence is file/reference-shaped and already has `allowFiles: true` + + `evidenceReference`; forcing it into bookmarks would duplicate an + existing surface. +- `usage` deck leaf (TIME-classification review follow-ups via + `timeReviewDate`) — plausible, but the assessment leaf should prove the + pattern first. +- NC Mail (`configuration.linkedTypes: ["mail"]` sidebar target + + `mailObjectTemplate`) — a separate comms-rule discussion; the manifest + `_note`s document a deliberate "comms hard-rule" that email widgets stay + off these detail pages, and this change does not reopen it. + +## 5. Manifest touch-points + +`src/manifest.json` detail pages affected: `ContactpersoonDetail`, +`ContractDetail`, `ModuleDetail`, `Diensten`/`DienstDetail` equivalent, +`ModuleversieDetail`, `ReviewDetail`, `OrganisatieDetail`. The leaf tabs +render from schema `linkedTypes` via the shared detail-page sidebar +(`CnObjectSidebar` — "so the CnObjectSidebar and dashboard widgets can +render a … tab without per-app glue", per `CalendarProvider`'s own +docblock); no per-page widget wiring is expected, but the two `_note` +strings that assert "declares NO email/calendar linkedType" become false +for `contract`/`contactPerson` and MUST be rewritten to describe the new +state, so the next audit doesn't read a stale premise +(`reference_design-system-adoption-silent-failures`: notes that lie are +worse than no notes). diff --git a/openspec/changes/adopt-integration-leaves/proposal.md b/openspec/changes/adopt-integration-leaves/proposal.md new file mode 100644 index 00000000..cb5a77c8 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/proposal.md @@ -0,0 +1,96 @@ +--- +kind: code +depends_on: [] +--- + +# softwarecatalog — adopt OpenRegister integration leaves (contacts, calendar, deck, bookmarks) + +## Why + +OpenRegister ships an app-agnostic integration-leaf registry +(`openregister/lib/Service/Integration/IntegrationRegistry.php` + +`Providers/`): a schema that declares a leaf id in +`configuration.linkedTypes` gets that Nextcloud app's link surface (sidebar +tab / widgets on the object detail page) with zero per-app glue — +`ContactsProvider` (id `contacts`), `CalendarProvider` (`calendar`), +`DeckProvider` (`deck`), `BookmarksProvider` (`bookmarks`) all exist today, +alongside the legacy allow-list ids in +`LinkedEntityService::legacyLinkedTypeIds()` (`files`, `mail`, `contacts`, +`notes`, `todos`, `calendar`, `talk`, `deck`). + +Software Catalog consumes almost none of this. Verified against +`lib/Settings/softwarecatalogus_register.json` at HEAD: + +- `allowFiles: true` on exactly 6 schemas (`suite`, `service`, + `organization`, `usage`, `module`, `compliancy`) — the files leaf. +- `linkedTypes` on exactly one schema: `contract` → + `["decidesk-decisions"]` (the ADR-066 approval projection). +- No schema declares `contacts`, `calendar`, `deck`, or `bookmarks`. + +That leaves four gaps the domain data is already shaped for: + +1. **Contacts** — `contactPerson.contactsUid` is literally "Verwijzing + (UID) naar de Nextcloud-contactpersoon in het adresboek + (`OCP\Contacts\IManager`)", and `organization.contactsUid` mirrors it. + The identity IS a Nextcloud contact by design (the + `ContactpersoonDetail` manifest note says communication happens + "through the linked Nextcloud contact via contactsUid"), yet the detail + page renders no contacts leaf — the vCard link exists only as a bare + string property. +2. **Calendar** — `contract.endDate` ("De einddatum van het contract") and + `moduleVersion.dateEndSupport` ("Startdatum einde ondersteuning", + stamped by the EOL matcher per `eolSource`/`eolUpdatedOn`) are the two + dates portfolio managers plan around, and neither is visible in any + calendar. The `ContractDetail` manifest `_note` even hard-codes the + current state: "The contract schema declares NO email/calendar + linkedType". +3. **Deck** — `assessment` records (reviews, live since the + `catalog-ratings` fragment added `auteur` + moderation `status` + pending/approved/rejected, enforced by `ReviewService::submit()` and + `ModerationService::approve()/reject()`) generate follow-up work + (moderate a pending review, chase a vendor about a bad rating) that has + no task surface. +4. **Bookmarks** — `module.website` ("Een URL naar uw applicatie") and + `service.website` are single URL strings; vendors accumulate more than + one relevant link (docs, changelog, status page, pricing) and today + have nowhere structured to put them. + +## What Changes + +- Add a new ADR-037 register fragment + `lib/Settings/register.d/catalog-integration-leaves.json` (never editing + the `softwarecatalogus_register.json` monolith) that declares + `configuration.linkedTypes`: + - `contactPerson`: `["contacts"]` + - `organization`: `["contacts"]` + - `contract`: `["decidesk-decisions", "calendar"]` — restating the + existing `decidesk-decisions` entry so the merged array is correct + regardless of whether the ADR-037 deep-merge unions or replaces + arrays (verified behaviour recorded in `design.md`). + - `moduleVersion`: `["calendar"]` + - `assessment`: `["deck"]` + - `module`: `["bookmarks"]` + - `service`: `["bookmarks"]` +- Add a lifecycle-date calendar sync (`lib/Service/LifecycleCalendarService.php` + + an OR object-saved listener): when `contract.endDate` or + `moduleVersion.dateEndSupport` is set or changed, upsert a linked + all-day VEVENT through OpenRegister's calendar link path (the same + `X-OPENREGISTER-*`-marked events `CalendarProvider::list()` renders), + so the calendar leaf tab shows the end-of-contract / end-of-support + event without manual linking; remove the event when the date is + cleared. EOL-matcher re-stamps of `dateEndSupport` + (`EolSyncService::run()` → `EolMatcherService`) move the event. +- Update the stale `src/manifest.json` detail-page `_note` prose on + `ContractDetail` and `ContactpersoonDetail` (both currently assert "NO + email/calendar linkedType" as the reason no comms widgets are placed) + and verify the leaf tabs render on the `ContractDetail`, + `ContactpersoonDetail`, `ModuleversieDetail`, `ReviewDetail`, + `ModuleDetail`, and `Diensten` detail surfaces. +- No new leaf providers and no OpenRegister changes: everything consumed + here (`contacts`, `calendar`, `deck`, `bookmarks`) is already a + registered `IntegrationProvider` at openregister HEAD. + +Not BREAKING: purely additive configuration plus one new sync service; no +existing route, response shape, or schema property changes. The +`connection.dateEndSupport` and `compliancy.url` fields are deliberately +out of scope (see `design.md` deferrals). diff --git a/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md b/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md new file mode 100644 index 00000000..813c6ef2 --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/specs/catalog-integration-leaves/spec.md @@ -0,0 +1,144 @@ +## ADDED Requirements + +### Requirement: Contact persons and organisations MUST expose the contacts leaf +The `contactPerson` and `organization` schemas SHALL declare `contacts` in +`configuration.linkedTypes`, so the detail pages render OpenRegister's +contacts leaf (vCard link rows with role, backed by +`openregister_contact_links` + `X-OPENREGISTER-*` vCard properties) for +the identity that today exists only as the bare `contactsUid` string. + +#### Scenario: Contact role detail shows the linked Nextcloud contact +- GIVEN a `contactPerson` object whose `contactsUid` references an existing + address-book contact +- WHEN a user opens `ContactpersoonDetail` (`/contactpersonen/:id`) +- THEN a contacts leaf tab MUST be present in the object sidebar +- AND it MUST list the linked contact with its display name (not the raw UID) +- @e2e Playwright: seed a contactPerson with a linked contact, open the + detail page, assert the contacts tab and the contact's display name + +#### Scenario: Organisation detail offers link-existing contact +- GIVEN an `organization` object with no linked contact +- WHEN a user opens the organisation detail page and uses the contacts leaf +- THEN the leaf MUST offer linking an existing address-book contact +- AND after linking, the contact MUST appear in the leaf list +- @e2e exclude Link-picker flow is owned and e2e-covered by OpenRegister's + integration-contacts suite; this app only declares the linkedType + +### Requirement: Contract end dates and version end-of-support dates MUST surface as calendar leaf events +The `contract` and `moduleVersion` schemas SHALL declare `calendar` in +`configuration.linkedTypes`, and the app SHALL maintain one linked all-day +calendar event per tracked date field — `contract.endDate` and +`moduleVersion.dateEndSupport` — created, moved, and removed by +`LifecycleCalendarService` when the field is set, changed (including EOL +re-stamps by `EolSyncService`/`EolMatcherService`), or cleared. Sync +failures MUST be logged and MUST NOT block the object save. + +#### Scenario: Setting a contract end date creates the leaf event +- GIVEN a contract whose `endDate` is empty +- WHEN a user saves the contract with `endDate = 2027-03-31` +- THEN a linked all-day event on 2027-03-31 MUST exist for that contract +- AND it MUST be listed in the calendar leaf tab on `ContractDetail` +- @e2e Playwright: set an endDate through the contract modal, open the + detail page, assert the calendar tab lists the end-date event + +#### Scenario: EOL matcher re-stamp moves the end-of-support event +- GIVEN a `moduleVersion` with a synced end-of-support event on 2026-12-01 +- WHEN the EOL sync (`EolSyncService::run()`) re-stamps `dateEndSupport` + to 2027-06-01 +- THEN the SAME linked event MUST now be on 2027-06-01 +- AND no duplicate end-of-support event MUST exist for that version +- @e2e exclude Background-job path with an external-feed dependency; + asserted by a PHPUnit test on `LifecycleCalendarService` upsert idempotency + +#### Scenario: Clearing the date removes the event without failing the save +- GIVEN a contract with a synced end-date event +- WHEN the contract is saved with `endDate` cleared +- THEN the save MUST succeed +- AND the linked end-date event MUST be removed +- @e2e exclude Deletion side-effect; asserted by PHPUnit on the listener + +#### Scenario: Calendar unavailable degrades gracefully +- GIVEN the Calendar app is disabled on the instance +- WHEN a contract with an `endDate` is saved +- THEN the save MUST succeed (HTTP 200 on the object write) +- AND the condition MUST be logged, not thrown +- @e2e exclude Requires disabling a server app mid-suite; asserted by + PHPUnit with a throwing calendar-service double + +### Requirement: Assessments MUST expose the deck leaf for follow-up work +The `assessment` schema SHALL declare `deck` in +`configuration.linkedTypes`, so review follow-ups (moderating a `pending` +review, acting on a low rating) can be tracked as Deck cards linked to the +assessment — supporting both `DeckProvider` create payload shapes +(`{cardId}` link-existing and `{boardId, stackId, title}` create-and-link). + +#### Scenario: A pending review gets a follow-up card +- GIVEN an `assessment` with moderation `status = pending` +- WHEN a moderator opens `ReviewDetail` and creates a card from the deck leaf +- THEN a Deck card linked to that assessment MUST be created +- AND the deck leaf tab MUST list it with its board/stack context +- @e2e Playwright: open a seeded pending review, create a card via the + deck leaf, assert it appears in the tab + +### Requirement: Applications and services MUST expose the bookmarks leaf for vendor and documentation links +The `module` and `service` schemas SHALL declare `bookmarks` in +`configuration.linkedTypes`, complementing the single `website` property +each schema carries with a structured, multi-link surface backed by +`openregister_bookmark_links`. + +#### Scenario: An application accumulates documentation links +- GIVEN a `module` object whose `website` property is set +- WHEN a user links two bookmarks (documentation, changelog) via the leaf + on `ModuleDetail` +- THEN both bookmarks MUST be listed in the bookmarks leaf tab with their + cached titles and URLs +- AND the `website` property MUST be unchanged +- @e2e Playwright: link a bookmark on a module detail page and assert the + tab renders title + URL + +#### Scenario: Bookmarks app uninstalled yields an empty leaf, not an error +- GIVEN the Bookmarks app is not installed +- WHEN a user opens `ModuleDetail` +- THEN the page MUST render without error +- AND the bookmarks leaf MUST present an empty/unavailable state +- @e2e exclude Requires uninstalling a server app; covered by + `BookmarksProvider`'s own contract (returns empty list when uninstalled) + +### Requirement: Leaf declarations MUST live in a register fragment and MUST preserve the contract's decidesk leaf +All `linkedTypes` additions SHALL be declared in a new +`lib/Settings/register.d/catalog-integration-leaves.json` fragment +(ADR-037); `lib/Settings/softwarecatalogus_register.json` MUST NOT be +modified. The fragment MUST declare `contract.configuration.linkedTypes` +as the full array `["decidesk-decisions", "calendar"]` so the existing +`decidesk-decisions` entry survives either array-merge semantic, and every +declared leaf id MUST be one that +`LinkedEntityService::validateType()` accepts at openregister HEAD. + +#### Scenario: Merged contract linkedTypes contain both leaves exactly once +- GIVEN the monolith declaring `contract.linkedTypes = ["decidesk-decisions"]` + and this change's fragment applied +- WHEN the merged settings are read (`GET /api/settings/load`) +- THEN `contract.configuration.linkedTypes` MUST contain + `decidesk-decisions` and `calendar`, each exactly once +- @e2e exclude Config-merge assertion; asserted by a PHPUnit test on + `SettingsService::loadSettings()` output + +#### Scenario: No dangling linked type is introduced +- GIVEN the fragment applied on an instance at openregister HEAD +- WHEN the `LogDanglingLinkedTypes` repair step runs +- THEN it MUST report zero schemas whose `linkedTypes` reference an + unregistered integration +- @e2e exclude Repair-step log assertion; verified via occ output in CI + +### Requirement: Stale manifest notes MUST be corrected +The `src/manifest.json` `_note` strings on `ContractDetail` and +`ContactpersoonDetail` that assert the schemas declare "NO email/calendar +linkedType" SHALL be rewritten to describe the post-change state, keeping +the documented comms hard-rule (no email widgets) intact and accurate. + +#### Scenario: Manifest notes no longer contradict the register +- GIVEN this change applied +- WHEN `src/manifest.json` is searched for "declares NO email/calendar linkedType" +- THEN no detail page whose schema now declares `calendar` or `contacts` + MUST carry that assertion +- @e2e exclude Documentation-string assertion; checked by grep in review diff --git a/openspec/changes/adopt-integration-leaves/tasks.md b/openspec/changes/adopt-integration-leaves/tasks.md new file mode 100644 index 00000000..b20cd1be --- /dev/null +++ b/openspec/changes/adopt-integration-leaves/tasks.md @@ -0,0 +1,80 @@ +# Tasks — adopt-integration-leaves + +## 1. Register fragment + +- [ ] 1.1 Add `lib/Settings/register.d/catalog-integration-leaves.json` + declaring `configuration.linkedTypes` on: `contactPerson` + + `organization` (`["contacts"]`), `contract` + (`["decidesk-decisions", "calendar"]` — full array, see design.md §2), + `moduleVersion` (`["calendar"]`), `assessment` (`["deck"]`), `module` + + `service` (`["bookmarks"]`). Validate with `python3 -m json.tool`. +- [ ] 1.2 Cross-check every leaf id against openregister HEAD: + `LinkedEntityService::legacyLinkedTypeIds()` plus + `IntegrationRegistry::listIds()` must accept all of `contacts`, + `calendar`, `deck`, `bookmarks` (they do at time of writing — re-verify + at apply time, the gate suite changes under you). +- [ ] 1.3 PHPUnit on `SettingsService::loadSettings()` merged output: + `contract.configuration.linkedTypes` contains `decidesk-decisions` AND + `calendar` exactly once each (guards the array-merge semantic either way); + all seven schemas carry their declared leaf. +- [ ] 1.4 Re-import on the dev instance (fragment signature change triggers + re-import) and confirm `Repair/LogDanglingLinkedTypes` reports zero + dangling entries. + +## 2. Lifecycle-date calendar sync + +- [ ] 2.1 Add `lib/Service/LifecycleCalendarService.php` with + `syncContract(array $object): void` and + `syncModuleVersion(array $object): void` — upsert one linked all-day + event per tracked field (`contract.endDate`, + `moduleVersion.dateEndSupport`) through OpenRegister's calendar link + path (the `X-OPENREGISTER-*` event surface `CalendarProvider::list()` + renders); move on change, delete on clear, never duplicate (one tracked + field = one event, deterministic marker). +- [ ] 2.2 Add `lib/Listener/LifecycleCalendarListener.php` subscribed to + OpenRegister's object-saved event; filter to the voorzieningen register + and the `contract`/`moduleVersion` schemas resolved via + `SettingsService` (no hard-coded register/schema ids); register the + listener in `lib/AppInfo/Application.php` alongside the existing OR + event listeners. +- [ ] 2.3 Fail-soft: wrap calendar interaction so an unavailable Calendar + app (or no writable calendar) logs a warning and the object save still + succeeds — mirror the graceful-degradation posture of the other + integrations. +- [ ] 2.4 Event titles in English per fleet convention: + `Contract ends: {contractNumber}`, + `End of support: {module name} {version}`. + +## 3. Frontend / manifest + +- [ ] 3.1 Verify the leaf tabs render from schema `linkedTypes` on the + affected detail pages (`ContactpersoonDetail`, `OrganisatieDetail`, + `ContractDetail`, `ModuleversieDetail`, `ReviewDetail`, `ModuleDetail`, + service detail) — expected zero per-page wiring via the shared object + sidebar; if a page suppresses sidebar tabs, wire it there. +- [ ] 3.2 Rewrite the `_note` strings on `ContractDetail` and + `ContactpersoonDetail` in `src/manifest.json` that currently assert + "declares NO email/calendar linkedType" — keep the comms hard-rule (no + email widgets) documented, describe the new calendar/contacts leaves. + +## 4. Tests + +- [ ] 4.1 PHPUnit `LifecycleCalendarServiceTest`: create-on-set, + move-on-change (including a simulated EOL re-stamp of + `dateEndSupport`), delete-on-clear, idempotent double-save, and + fail-soft when the calendar double throws. +- [ ] 4.2 PHPUnit `LifecycleCalendarListenerTest`: fires only for + `contract`/`moduleVersion` saves in the voorzieningen register; ignores + other schemas. +- [ ] 4.3 Playwright: contacts tab on a seeded contactPerson + (display name, not raw UID); calendar tab on a contract after setting + `endDate`; deck card create-and-link on a pending review; bookmark + link + render on a module detail page — per the @e2e-tagged scenarios + (gate-19 traceability). + +## 5. Spec + docs + +- [ ] 5.1 Sync this change's spec delta into + `openspec/specs/catalog-integration-leaves/spec.md` on archive. +- [ ] 5.2 CHANGELOG entry under Unreleased: contacts/calendar/deck/bookmarks + leaf adoption + lifecycle-date calendar sync. diff --git a/openspec/changes/mcp-full-action-surface/.openspec.yaml b/openspec/changes/mcp-full-action-surface/.openspec.yaml new file mode 100644 index 00000000..95672402 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-18 diff --git a/openspec/changes/mcp-full-action-surface/design.md b/openspec/changes/mcp-full-action-surface/design.md new file mode 100644 index 00000000..1b8d3cd3 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/design.md @@ -0,0 +1,212 @@ +# Design — mcp-full-action-surface + +## 1. Positioning against `softwarecatalog-mcp-adoption` + +That change (active, unimplemented, `.openspec.yaml: schema: conduction`, +created 2026-07-13) is the read-only declarative half of this surface. Its +reasoning is kept; its artefacts cannot be applied as written: + +| Its assumption | State at HEAD | Consequence | +|---|---|---| +| Slugs `moduleVersie`, `dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik` | Renamed to `moduleVersion`, `service`, `organization`, `contactPerson`, `connection`, `usage` | Its fragment would deep-merge 8 orphan schemas into the register (ADR-037 creates keys it cannot match) — worse than failing loudly | +| Dutch filter names (`naam`, `aanbieder`, `licentietype`, `standaardGemma`, `afnemer`…) | Properties are English (`name`, `provider`, `licentietype` DOES survive on `module`, but e.g. `naam` → `name`, `standaardGemma` → `standardGemma`, `afnemer` → `consumer`) | `McpAnnotationValidator` would reject — every filter list must be re-derived from the HEAD `properties` maps | +| `kwetsbaarheid`/`beoordeeling` excluded as "dead schemas" | `vulnerability` and `assessment` are live: manifest pages `Kwetsbaarheden`/`KwetsbaarheidDetail`, `Reviews`/`ReviewDetail`; `ReviewService`/`ModerationService`; `register.d/catalog-ratings.json` moderation fields | Both belong in the read surface; `vulnerability` is also the safe derived-write candidate | +| Write tools deferred (`DEFERRED_QUESTIONS`) | The concrete need now exists (hermiq grant model + chat commanding) | This change is the deferred `kind: code` follow-up it named | + +**Disposition:** this change supersedes it. Archive +`softwarecatalog-mcp-adoption` as superseded-by `mcp-full-action-surface` +when this lands; do not apply its fragment first. + +## 2. Architecture + +``` +hermiq agent (default-deny grants, scope × reach, approval gate, audit) + -> OpenRegister /api/mcp (JSON-RPC) / chat facade + -> SchemaDerivedToolProvider <- register.d/mcp-full-action-surface.json (layer A+B) + -> IMcpToolProvider::softwarecatalog <- lib/Mcp/SoftwareCatalogToolProvider.php (layer C) + dispatcher only; per tool: + McpArgumentValidator -> per-object gate -> existing workflow service +``` + +Fleet reference: `decidesk/lib/Mcp/` — `DecideskToolProvider` (dispatcher +with a `TOOL_DESCRIPTORS` constant so unit tests assert the catalogue as a +fixture), `McpArgumentValidator`, `McpMeetingGate` (the single +"load object, prove the caller may touch it" ladder: argument validation → +load → not_found → authorise, auth helpers that return real booleans and +are never wrapped in `catch(\Throwable)`), `McpMeetingScopeResolver`. +Software Catalog ports the shape: `McpContractGate` (wraps the existing +`ContractApprovalService::authorizeSubmit(contractUuid, groupNames, +activeOrgUuid)` — the IDOR guard from `contract-approval-ownership-guard`), +`McpPublicationGate` (wraps `PublicationController::authorizeEntry()` +semantics via `PublicationService::resolveEntry()` + the admin / +`aanbod-beheerder` organisation match), and admin checks via +`IGroupManager::isAdmin()`. **Rule: the MCP layer adds no new authority — +every tool runs exactly the guard its REST twin runs.** + +DI alias, mirroring `decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`: + +```php +$context->registerServiceAlias( + 'OCA\\OpenRegister\\Mcp\\IMcpToolProvider::softwarecatalog', + SoftwareCatalogToolProvider::class +); +``` + +## 3. Layer A — derived read tools (14 schemas × search/get = 28 tools) + +`register.d/mcp-full-action-surface.json`, `configuration.x-openregister-mcp`, +`enabled: true`, verbs `search` + `get`, `scope: "read"`, +`readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, +implicit `reach: user` (hermiq infers `user` for 3-segment `{app}.{schema}.{search|get}` +ids; we declare it anyway — see §5). Filters below are cross-checked +against the HEAD `properties` maps (every name verified present): + +| Schema | search filters (all real properties) | +|---|---| +| `module` | `name`, `type`, `provider`, `licentietype`, `hostingJurisdiction` | +| `moduleVersion` | `module`, `status`, `dateEndSupport` | +| `service` | `name`, `provider` | +| `organization` | `name`, `type`, `status`, `registrationStatus` | +| `contactPerson` | `organization`, `role` | +| `connection` | `type`, `status`, `integrationType`, `provider` | +| `compliancy` | `module`, `standardGemma` | +| `usage` | `consumer`, `provider`, `status`, `module`, `timeClassification` | +| `contract` | `status`, `contractType`, `service`, `usage`, `endDate` | +| `suite` | `name` | +| `vulnerability` | `name`, `cveCode`, `cvssScore`, `modules` | +| `assessment` | `status`, `rating`, `modules`, `usage` | +| `bioMeasure` | `code`, `name`, `bbnLevel` | +| `sbomComponent` | `name`, `moduleVersion`, `purl`, `vexCveIds` | + +Excluded from derivation, reasoning inherited from the superseded change: +`sector` (2-field taxonomy), `element`/`view`/`model`/ +`property-definition`/`relation` (AMEF bulk-import artifacts; `element` +alone has 80+ properties). Note `view` data IS reachable through curated +`listViews`/`getView` provider tools (layer C), which return the enriched +projection the `ViewController` API serves rather than raw AMEF XML. + +## 4. Layer B — derived writes: `vulnerability` only + +`vulnerability.create` (`scope: create`) and `vulnerability.update` +(`scope: update`), both `reach: instance`, `destructiveHint: false`. +Justification: it is the only live schema with (a) no +`x-openregister-lifecycle` state machine, (b) no decidesk projection +fields, (c) no dedicated workflow service — the app's own UI authors it +via generic OR object CRUD, so a derived MCP write matches the app's +existing authority model exactly (OR RBAC at invoke time). `delete` is +withheld (destructive; no current UI story). Every OTHER schema keeps the +superseded change's "no raw writes" rule: `contract.status = Actief` is a +decidesk projection ("softwarecatalog NEVER sets `status = Actief` on its +own authority" — `register.d/contracts-to-decidesk.json`), and +`moduleVersion`/`connection`/`organization`/`usage`/`contract` carry +lifecycle state machines a raw `update` would bypass. + +## 5. Layer C — curated provider tools (grant-matrix table) + +Reach follows `hermiq/openspec/specs/agent-capability-reach/spec.md`: +`self` < `user` < `instance` < `external`; reach = widest principal set an +invocation can AFFECT or DISCLOSE TO; a read that leaves the instance is +`external`; undeclared reach fail-closes to `external`, so every +descriptor declares one explicitly. + +### Read tools (scope: read) + +| Tool id | Delegates to | Reach | Notes | +|---|---|---|---| +| `softwarecatalog.getMyContactProfile` | `ContactpersonenController::getMe` path (`/api/me` resolution) | user | The caller's own contactPerson + organisation context | +| `softwarecatalog.listOffers` | `AanbodService::getAanbod()` | user | Offers pending for the caller's active organisation | +| `softwarecatalog.listOfferedUsages` | `AangebodenGebruikService::getGebruiksWhereAfnemer()` / `getGebruiksWhereDeelnemers()` | user | Usage records offered to / shared with the caller's organisation | +| `softwarecatalog.getPortfolioReport` | `PortfolioReportService::buildReport(organisationUuid)` | user | Caller's organisation only; gate rejects foreign org uuids for non-admins | +| `softwarecatalog.listPendingModerations` | `ModerationService::listPending()` | user | Admin-gated (same as `moderation#pending`) | +| `softwarecatalog.getReviewAggregate` | `ReviewAggregateService` (`review#aggregate`) | user | Public aggregate numbers | +| `softwarecatalog.getContractApprovalConfig` | `ContractApprovalService::isDelegationConfigured()` (`contractApproval#config`) | user | Lets an agent know whether submit tools can work | +| `softwarecatalog.getSbomImportStatus` | `SbomImportService::getStatus(moduleVersionUuid)` | user | Behind `SbomImportService::userCanReadModule()` | +| `softwarecatalog.listViews` / `softwarecatalog.getView` | `ViewService` (`view#getAllViews` / `#getView`) | user | Enriched ArchiMate view projection, incl. enrichment params | +| `softwarecatalog.previewOrganisationMerge` | `MergeOrganisatieService::dryRun(source, target)` | user | Admin-gated; read-only preview of `mergeOrganisations` | +| `softwarecatalog.getEolSyncStatus` | `EolSyncService::getStatus()` | user | Read of last-run metadata only | + +### Write tools (scope as listed; hermiq default-deny, human approval gate) + +| Tool id | Delegates to | Scope | Reach | Why that reach | +|---|---|---|---|---| +| `softwarecatalog.submitContractApproval` | `ContractApprovalService::submitForApproval(uuid, false)` behind `authorizeSubmit()` | update | instance | Raises a decidesk Decision other users see; flips `approvalState` | +| `softwarecatalog.submitContractRenewal` | `submitForApproval(uuid, true)` | update | instance | Same seam, renewal flavour | +| `softwarecatalog.publishObject` | `PublicationService::publish(objectType, uuid)` | update | external | Sets `publicationDate` → anonymous open-data readers see the record; effect leaves the authenticated instance surface | +| `softwarecatalog.depublishObject` | `PublicationService::depublish(objectType, uuid)` | update | external | Withdraws from the public surface — same boundary | +| `softwarecatalog.approveRegistration` | `ModerationService::approve(uuid, type)` | update | instance | Admits an organisation/review; visible to all users | +| `softwarecatalog.rejectRegistration` | `ModerationService::reject(uuid, type)` | update | instance | | +| `softwarecatalog.submitReview` | `ReviewService::submit(payload, subjectType, subjectId)` | create | instance | Forced to `status: pending` server-side; moderators observe it | +| `softwarecatalog.acceptOffer` | `AanbodService::acceptAanbod(aanbodId)` | update | instance | | +| `softwarecatalog.declineOffer` | `AanbodService::denyAanbod(aanbodId)` | delete | instance | REST twin is a DELETE verb | +| `softwarecatalog.claimUsage` | `AangebodenGebruikService::setGebruikSelfToActiveOrg(gebruikId)` | update | instance | | +| `softwarecatalog.declineUsage` | `AangebodenGebruikService::deleteGebruikAsAfnemer(gebruikId)` | delete | instance | | +| `softwarecatalog.grantOrganisationMembership` | `OrganisationMembersController::grant(uuid, userId)` logic (extract to service if needed) | update | instance | Changes another user's permission set | +| `softwarecatalog.revokeOrganisationMembership` | `::revoke(uuid, userId)` logic | update | instance | | +| `softwarecatalog.mergeOrganisations` | `MergeOrganisatieService::execute(source, target, actorUid)` | update | instance | Admin-gated; tombstones the source (`mergedInto`) | +| `softwarecatalog.registerOrganisation` | `IntakeService::submit(payload)` (+ `validate()`) | create | instance | Creates a `pending` registration for moderators | +| `softwarecatalog.importSbom` | `SbomImportService::importForModuleVersie(...)` | create | instance | Content passed inline (SBOM JSON/XML string), not a file upload | +| `softwarecatalog.triggerEolSync` | `EolSyncService::run()` | update | external | Outbound HTTP to endoflife.date — per hermiq's rule, anything issuing external requests is `external` regardless of verb | + +Descriptor hints: every write tool sets `readOnlyHint: false`; +`destructiveHint: true` only on `declineOffer`, `declineUsage`, +`revokeOrganisationMembership`, and `mergeOrganisations` (tombstoning); +`idempotentHint` per delegate semantics (e.g. `publish` idempotent, +`submitReview` not). + +### Named exclusions (auditable "full coverage" boundary) + +| Surface | Why not a tool | +|---|---| +| `settings#*` config get/set (~50 endpoints: general/sync/AMEF/voorzieningen/email/cronjob/user-group config, auto-configure, force-update, clear-cache, debug, heartbeat) | App configuration, not catalogue operation. An agent misconfiguring register bindings can brick the app for everyone; nothing in the PO intent ("command the app from chat") needs it. Deferred, not denied forever. | +| `contactpersonen#convertToUser`, `changePassword`, `disableUser`, `enableUser`, `updateUserGroups` | Identity/credential administration. Password and account-state changes are outside any sane agent grant in v1. | +| `settings#importArchiMate` / `exportArchiMate` / `downloadArchiMate` + progress streaming | File-upload/-download shaped with an async progress protocol; MCP tool-call ergonomics don't fit yet. `importSbom` is included instead because its payload is inline text. | +| `federation#addPeer/removePeer/pull` | Instance-topology administration touching remote instances; needs its own security review before any agent reach. | +| `dashboard#*`, `preferences#*`, `facet#getFacets`, `settings#getObjectsCounts/Statistics` | UI plumbing; derived `search` covers the data need. | + +## 6. Chat scenarios the surface must support (grounded end-to-end) + +1. **"Which contracts expire this quarter?"** → + `softwarecatalog.contract.search` with a `endDate` range filter + (real property: `contract.endDate`, "De einddatum van het contract"); + scope read / reach user — grantable without approval friction. +2. **"Log a vulnerability against application X."** → + `softwarecatalog.module.search {name: X}` then + `softwarecatalog.vulnerability.create {name, cveCode, cvssScore, + modules: [moduleId]}` (all real `vulnerability` properties); write → + default-deny, first use prompts a grant, invocation passes the human + approval gate and lands in the audit trail. +3. **"Submit contract 2025-0042 for renewal approval."** → + `contract.search {contractNumber}` then + `softwarecatalog.submitContractRenewal {contractUuid}`; the gate runs + `ContractApprovalService::authorizeSubmit()` — a caller whose active + organisation doesn't own the contract gets the same 403-equivalent + `forbidden` error the REST path returns, agent or not. + +## 7. Risks / trade-offs + +- [Risk] The superseded change is applied first with Dutch slugs → + orphan schemas polluting the register. Mitigation: proposal recommends + archiving it as superseded; task 1.1 asserts the fragment only names + slugs present in the HEAD monolith (fails the build otherwise). +- [Risk] MCP write tool drifts from its REST twin's guard (an MCP-only + IDOR). Mitigation: the delegation rule is a spec requirement with + per-gate unit tests mirroring `contract-approval-ownership-guard`'s + 403 cases; the gates REUSE the service-level guards rather than + reimplementing them. +- [Trade-off] `OrganisationMembersController::grant/revoke` logic lives + in the controller today; the provider either extracts it into a small + service (preferred, one-time refactor) or is deferred for those two + tools — decided at apply time, recorded in tasks 5.4. +- [Trade-off] No `delete` tools for catalogue records at all (beyond the + decline/revoke workflow verbs). Deliberate: destructive deletes have no + workflow service and no agent story; bias to fewer. + +## 8. Deferred + +- Curated tools over the excluded admin surfaces (config, ArchiMate, + federation) once hermiq has an "operator agent" grant tier. +- `x-openregister-mcp` on a trimmed AMEF projection schema (inherited + deferral). +- `assessment` derived writes — review submission must stay behind + `softwarecatalog.submitReview` so the server-side `pending` forcing and + `auteur` stamping are never bypassed. diff --git a/openspec/changes/mcp-full-action-surface/proposal.md b/openspec/changes/mcp-full-action-surface/proposal.md new file mode 100644 index 00000000..0abbeca4 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/proposal.md @@ -0,0 +1,120 @@ +--- +kind: code +depends_on: [] +--- + +# softwarecatalog — full MCP action surface for hermiq (chat-drivable catalogue) + +## Why + +**Product intent:** every Conduction app should expose MCP tooling for ALL +of its user actions, so any action can in principle be automated by an AI +agent — with the user granting rights per agent, granularly, on hermiq's +two-axis grant model (`scope` × `reach`, default-deny for writes, human +approval gates, audit trail — `hermiq/openspec/specs/agent-tool-governance/` +and `agent-capability-reach/spec.md`). Even without automation, a user +should be able to command the app from chat: "which contracts expire this +quarter?" answered, "submit contract 2025-0042 for renewal" queued behind +an approval gate. + +**Current state (verified at HEAD):** Software Catalog has zero MCP +surface. `grep -rn "IMcpToolProvider\|McpTool\|x-openregister-mcp" lib/ +src/ appinfo/` returns nothing outside openspec prose. The mechanism is +proven elsewhere: decidesk ships the fleet reference implementation +(`decidesk/lib/Mcp/DecideskToolProvider.php` — dispatcher + +`TOOL_DESCRIPTORS` catalogue, `McpArgumentValidator`, `McpMeetingGate` +per-object authorisation, `McpMeetingScopeResolver`), registered via the +DI alias `OCA\OpenRegister\Mcp\IMcpToolProvider::decidesk` +(`decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`), and +OpenRegister derives CRUD tools from `x-openregister-mcp` schema blocks +(`openregister/lib/Mcp/`). + +**Relationship to `softwarecatalog-mcp-adoption` (active change, +2026-07-13, `schema: conduction`):** that change specifies the read-only +half — derived `search`/`get` tools on 9 curated schemas via a +`register.d` fragment — and explicitly defers every write/action tool +("A future `kind: code` change could promote … a `#[McpTool]` once +there's a concrete agent workflow need", its `DEFERRED_QUESTIONS`). This +change is that deferred follow-up, and it also has to correct the ground +under it: **the register was since migrated to English slugs** and +`softwarecatalog-mcp-adoption`'s fragment is written against schema names +that no longer exist. Verified against +`lib/Settings/softwarecatalogus_register.json` at HEAD: the register +contains `module`, `moduleVersion`, `service`, `organization`, +`contactPerson`, `connection`, `compliancy`, `usage`, `contract`, +`suite`, `sector`, `vulnerability`, `assessment`, `bioMeasure`, +`sbomComponent` + the 5 AMEF schemas — there is no `moduleVersie`, +`dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik`, +`kwetsbaarheid`, or `beoordeeling`. Applying that change's JSON as-is +would deep-merge eight ORPHAN schemas into the register (ADR-037 creates +what it cannot match) instead of annotating the real ones. Two of its +exclusions are also stale: `vulnerability` and `assessment` are live +surfaces now (`Kwetsbaarheden`/`KwetsbaarheidDetail` and +`Reviews`/`ReviewDetail` manifest pages; `ReviewService`, +`ModerationService`, the `catalog-ratings` fragment), despite the +monolith's leftover "niet daadwerkelijk gebruikt" description. + +**This change therefore supersedes `softwarecatalog-mcp-adoption`**: it +retains that change's curation reasoning (read-only derived tools, honest +hints, filters cross-checked against real properties, AMEF exclusion, no +raw writes on lifecycle-governed schemas) and re-grounds it on the English +slugs, then adds the full action layer on top. Recommend archiving +`softwarecatalog-mcp-adoption` as superseded when this change lands. + +## What Changes + +1. **Derived read layer (config)** — new + `lib/Settings/register.d/mcp-full-action-surface.json` fragment + declaring `configuration.x-openregister-mcp` (`search` + `get`, + `scope: read`, `readOnlyHint: true`) on 14 schemas: the 9 from the + superseded change under their current slugs (`module`, `moduleVersion`, + `service`, `organization`, `contactPerson`, `connection`, `compliancy`, + `usage`, `contract`) plus `suite`, `vulnerability`, `assessment`, + `bioMeasure`, `sbomComponent` (all now live surfaces). AMEF schemas + (`element`, `view`, `model`, `property-definition`, `relation`) and + `sector` stay excluded — reasoning inherited, see design.md. +2. **Derived write verbs on `vulnerability` only** — `create`/`update` + (`scope` accordingly, `reach: instance`): the one live schema with no + lifecycle state machine, no projection fields, and no workflow + service; the app's own UI writes it through generic OR object CRUD. + Every other schema's writes stay workflow-only (below). +3. **Hand-written provider (code)** — + `lib/Mcp/SoftwareCatalogToolProvider.php` + (`OCA\SoftwareCatalog\Mcp`, implements + `OCA\OpenRegister\Mcp\IMcpToolProvider`), registered under the DI + alias `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog`, tool + ids `softwarecatalog.{toolName}`. Dispatcher-only, decidesk-style: + argument validation (`McpArgumentValidator` port) → per-object + authorisation gate → delegation to the EXISTING workflow service. + 12 curated read tools and 17 write tools covering every real + user-facing workflow action found in `lib/Controller/` + + `lib/Service/` — contract approval/renewal + (`ContractApprovalService::submitForApproval()` behind + `authorizeSubmit()`), publish/depublish (`PublicationService`), + moderation (`ModerationService::listPending/approve/reject`), reviews + (`ReviewService::submit`), offers (`AanbodService::getAanbod/ + acceptAanbod/denyAanbod`), offered-usage claim/decline + (`AangebodenGebruikService`), organisation membership + (`OrganisationMembersController` logic), organisation merge + (`MergeOrganisatieService::dryRun/execute`), intake + (`IntakeService::submit`), SBOM import (`SbomImportService`), EOL sync + (`EolSyncService::run`), portfolio report + (`PortfolioReportService::buildReport`). Full catalogue table with + per-tool `scope` and `reach` in design.md. +4. **Grant-matrix annotations** — every descriptor declares `scope` + (read/create/update/delete) AND `reach` (self/user/instance/external) + from hermiq's closed vocabularies, because hermiq fail-closes an + undeclared reach to `external` (its most-restricted class) and we want + reads grantable at `user` reach. Publication tools are honestly + `reach: external` (they alter the anonymous open-data surface), as is + the EOL sync trigger (outbound HTTP to endoflife.date). +5. **Named exclusions, not silent ones** — admin configuration plumbing + (the ~50 `settings#*` config get/set endpoints, email templates, + cronjob config, user-group config), identity/credential operations + (`contactpersonen#convertToUser/changePassword/disable/enable`), + ArchiMate import/export (file-transfer shaped), and federation peer + management are deliberately NOT tools in this change — each with its + rationale recorded in design.md so the coverage claim is auditable. + +Not BREAKING: purely additive — no existing route, controller, or schema +property changes; REST surface untouched. diff --git a/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md b/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md new file mode 100644 index 00000000..d27bcf8b --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/specs/mcp-tool-surface/spec.md @@ -0,0 +1,191 @@ +## ADDED Requirements + +### Requirement: Software Catalog MUST register a hand-written MCP tool provider +The app SHALL ship `OCA\SoftwareCatalog\Mcp\SoftwareCatalogToolProvider` +implementing `OCA\OpenRegister\Mcp\IMcpToolProvider`, registered under the +DI alias `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog` +(mirroring decidesk's registrar at +`decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`). The +provider MUST be a dispatcher only: it owns the tool catalogue (a constant +descriptor table unit tests can assert as a fixture, per +`DecideskToolProvider::TOOL_DESCRIPTORS`) and routes tool ids to handler +classes; it MUST NOT contain business logic. Every tool id MUST be +namespaced `softwarecatalog.{toolName}`. + +#### Scenario: The provider is discoverable through OpenRegister +- GIVEN this change applied and the app enabled +- WHEN OpenRegister resolves registered `IMcpToolProvider` aliases +- THEN `IMcpToolProvider::softwarecatalog` MUST resolve to + `SoftwareCatalogToolProvider` +- AND its listed tools MUST all carry ids starting with `softwarecatalog.` +- @e2e exclude DI-resolution assertion; asserted by PHPUnit bootstrapping + the container + +### Requirement: Every tool descriptor MUST declare scope and reach from hermiq's closed vocabularies +Every descriptor — derived and curated — SHALL declare `scope` (one of +`read`, `create`, `update`, `delete`) and `reach` (one of `self`, `user`, +`instance`, `external`, per +`hermiq/openspec/specs/agent-capability-reach/spec.md`), plus honest +`readOnlyHint`/`destructiveHint`/`idempotentHint` values. Reach MUST be +declared explicitly (hermiq fail-closes an undeclared reach to +`external`). A tool whose invocation issues an outbound HTTP request +(`softwarecatalog.triggerEolSync` → endoflife.date) or alters the +anonymous open-data surface (`publishObject`/`depublishObject`) MUST +declare `reach: external` regardless of its verb. + +#### Scenario: No descriptor ships without both axes +- GIVEN the provider's descriptor table and the derived-tool fragment +- WHEN every entry is inspected +- THEN each MUST carry a `scope` and a `reach` from the closed vocabularies +- AND no read tool MUST carry `readOnlyHint: false` +- @e2e exclude Descriptor-shape fixture assertion; PHPUnit over the + descriptor constant + +#### Scenario: Publication tools are classified as external reach +- GIVEN the descriptors for `softwarecatalog.publishObject` and + `softwarecatalog.depublishObject` +- WHEN their `reach` is read +- THEN it MUST be `external` +- AND their `scope` MUST be `update` +- @e2e exclude Fixture assertion; PHPUnit + +### Requirement: Read tools MUST be side-effect free and separated from write tools +Curated read tools (`getMyContactProfile`, `listOffers`, +`listOfferedUsages`, `getPortfolioReport`, `listPendingModerations`, +`getReviewAggregate`, `getContractApprovalConfig`, `getSbomImportStatus`, +`listViews`, `getView`, `previewOrganisationMerge`, `getEolSyncStatus`) +SHALL delegate only to read paths of the existing services and MUST NOT +persist anything. `previewOrganisationMerge` MUST delegate to +`MergeOrganisatieService::dryRun()` and MUST NOT be able to reach +`execute()`. + +#### Scenario: Merge preview never mutates +- GIVEN two organisation uuids +- WHEN `softwarecatalog.previewOrganisationMerge` is invoked +- THEN the response MUST contain the dry-run impact summary +- AND no object write MUST occur (asserted via a mocked + `MergeOrganisatieService` expecting `dryRun()` once and `execute()` never) +- @e2e exclude MCP JSON-RPC path; PHPUnit on the handler + +### Requirement: Every write tool MUST delegate to the existing workflow service behind its existing guard +Each write tool SHALL delegate to the named workflow method — +`ContractApprovalService::submitForApproval()`, +`PublicationService::publish()/depublish()`, +`ModerationService::approve()/reject()`, `ReviewService::submit()`, +`AanbodService::acceptAanbod()/denyAanbod()`, +`AangebodenGebruikService::setGebruikSelfToActiveOrg()/deleteGebruikAsAfnemer()`, +organisation-membership grant/revoke, `MergeOrganisatieService::execute()`, +`IntakeService::submit()`, `SbomImportService::importForModuleVersie()`, +`EolSyncService::run()` — and MUST run per-object authorisation before the +delegate, structured as the decidesk ladder (argument validation → load → +not_found → authorise → delegate, per `decidesk/lib/Mcp/McpMeetingGate.php`). +The MCP layer MUST NOT grant authority the REST twin denies: in particular +`submitContractApproval`/`submitContractRenewal` MUST pass +`ContractApprovalService::authorizeSubmit()` and fail closed exactly like +the REST 403 path. Raw object writes on lifecycle-governed schemas +(`contract`, `usage`, `organization`, `moduleVersion`, `connection`) MUST +NOT be exposed as MCP tools. + +#### Scenario: A non-owning caller cannot submit a contract via MCP +- GIVEN a contract owned by organisation A +- AND an authenticated caller whose active organisation is B and who is + not an instance admin +- WHEN `softwarecatalog.submitContractApproval` is invoked for that contract +- THEN the tool MUST return a forbidden error +- AND `ContractApprovalService::submitForApproval()` MUST NOT be invoked +- AND no `DecisionRequestedEvent` MUST be dispatched +- @e2e exclude Mirrors the REST 403 cases of + `contract-approval-ownership-guard`; PHPUnit with mocked dispatcher + +#### Scenario: Review submission cannot bypass moderation +- GIVEN any caller +- WHEN `softwarecatalog.submitReview` is invoked with a payload declaring + `status: approved` +- THEN the persisted assessment MUST have `status: pending` (forced + server-side by `ReviewService::submit()`) +- AND the response MUST reflect the pending state +- @e2e exclude Server-side forcing assertion; PHPUnit on the handler + + service + +#### Scenario: Argument validation precedes authorisation and business logic +- GIVEN an invocation of any curated tool with a missing required argument +- WHEN the provider dispatches it +- THEN the tool MUST return a validation error naming the argument +- AND no service method MUST have been called +- @e2e exclude Validator-ladder assertion; PHPUnit + +### Requirement: Derived read tools MUST cover the 14 live catalogue schemas under their current English slugs +`lib/Settings/register.d/mcp-full-action-surface.json` SHALL declare +`configuration.x-openregister-mcp` with `search` + `get` (`scope: read`, +`readOnlyHint: true`) on exactly: `module`, `moduleVersion`, `service`, +`organization`, `contactPerson`, `connection`, `compliancy`, `usage`, +`contract`, `suite`, `vulnerability`, `assessment`, `bioMeasure`, +`sbomComponent`. Every schema name and every `search.filters` entry MUST +exist in the HEAD `softwarecatalogus_register.json` (`McpAnnotationValidator` +must report zero unknown-filter errors, and the fragment MUST NOT +introduce any schema key absent from the monolith). The AMEF schemas +(`element`, `view`, `model`, `property-definition`, `relation`) and +`sector` MUST NOT be annotated. `lib/Settings/softwarecatalogus_register.json` +MUST NOT be modified. + +#### Scenario: Contracts are searchable by end date from chat +- GIVEN the fragment imported and contracts with `endDate` values in Q4 +- WHEN an agent invokes `softwarecatalog.contract.search` with an + `endDate` range filter for the quarter +- THEN the result MUST contain exactly the contracts whose `endDate` + falls in the range the caller may read under OR RBAC +- @e2e exclude MCP JSON-RPC query; covered by OpenRegister's derived-tool + suite plus an app-side import assertion + +#### Scenario: No orphan schema is merged into the register +- GIVEN the fragment applied +- WHEN the merged register is diffed against the monolith's schema key set +- THEN the set of schema keys MUST be unchanged (annotations only, no new + schemas — in particular none of the retired Dutch slugs `moduleVersie`, + `dienst`, `organisatie`, `contactpersoon`, `koppeling`, `gebruik`, + `kwetsbaarheid`, `beoordeeling`) +- @e2e exclude Config-merge assertion; PHPUnit on + `SettingsService::loadSettings()` + +### Requirement: Derived write verbs MUST exist on vulnerability and nowhere else +The fragment SHALL additionally declare `create` and `update` (with +matching `scope`, `reach: instance`, `readOnlyHint: false`) on the +`vulnerability` schema only — the one live schema with no lifecycle state +machine, no projection fields, and no workflow service. No other schema in +the fragment MUST carry a `create`, `update`, or `delete` verb, and +`vulnerability` MUST NOT carry `delete`. + +#### Scenario: An agent logs a vulnerability against an application +- GIVEN an agent granted `softwarecatalog.vulnerability.create` (a write — + hermiq default-denies it until granted, and the invocation passes the + human approval gate) +- WHEN it invokes the tool with `name`, `cveCode`, `cvssScore`, and + `modules` referencing an existing module id +- THEN a `vulnerability` object MUST be created with those values under + the caller's OR RBAC authority +- AND the invocation MUST appear in hermiq's audit trail +- @e2e exclude Cross-app hermiq grant flow; covered by hermiq's + agent-tool-governance suite; app-side PHPUnit asserts the fragment shape + +#### Scenario: Writes on lifecycle-governed schemas stay impossible +- GIVEN the imported merged register +- WHEN the derived tool list for `softwarecatalog` is enumerated +- THEN no `contract.*`, `usage.*`, `organization.*`, `moduleVersion.*`, + or `connection.*` tool with scope `create`, `update`, or `delete` MUST + exist +- @e2e exclude Tool-listing assertion; import check in CI + +### Requirement: This change supersedes softwarecatalog-mcp-adoption +The change SHALL be applied instead of, never after or alongside, the +`softwarecatalog-mcp-adoption` fragment: that change's +`register.d/softwarecatalog-mcp-adoption.json` (Dutch slugs) MUST NOT be +created, and on landing this change the `softwarecatalog-mcp-adoption` +change MUST be archived as superseded with a pointer to +`mcp-full-action-surface`. + +#### Scenario: The stale fragment never lands +- GIVEN this change applied +- WHEN `lib/Settings/register.d/` is listed +- THEN it MUST contain `mcp-full-action-surface.json` +- AND it MUST NOT contain `softwarecatalog-mcp-adoption.json` +- @e2e exclude File-presence assertion; checked in review/CI diff --git a/openspec/changes/mcp-full-action-surface/tasks.md b/openspec/changes/mcp-full-action-surface/tasks.md new file mode 100644 index 00000000..d0853ea6 --- /dev/null +++ b/openspec/changes/mcp-full-action-surface/tasks.md @@ -0,0 +1,113 @@ +# Tasks — mcp-full-action-surface + +## 1. Derived layer (register fragment) + +- [ ] 1.1 Add `lib/Settings/register.d/mcp-full-action-surface.json`: + `configuration.x-openregister-mcp` with `search`/`get` (`scope: read`, + honest hints, explicit `reach: user`) on the 14 schemas in design.md §3, + plus `create`/`update` (`reach: instance`) on `vulnerability` only. + Assert (script or PHPUnit) that every schema key in the fragment exists + in the HEAD monolith — none of the retired Dutch slugs — and validate + with `python3 -m json.tool`. +- [ ] 1.2 Re-derive every `search.filters` list from the HEAD `properties` + maps (design.md table — every name verified against HEAD at + proposal time; re-verify at apply time); the merged register must pass + `McpAnnotationValidator` with zero unknown-filter errors. +- [ ] 1.3 Agent-facing English `description` prose per verb per schema + (what the LLM reads to choose the tool), reusing the superseded + change's descriptions where the schema survived the rename. +- [ ] 1.4 Import on the dev instance and verify the derived tool listing: + 28 read tools + `vulnerability.create`/`.update`, and no write tool on + any lifecycle-governed schema. + +## 2. Provider skeleton + +- [ ] 2.1 Add `lib/Mcp/SoftwareCatalogToolProvider.php` + (`OCA\SoftwareCatalog\Mcp`, implements + `OCA\OpenRegister\Mcp\IMcpToolProvider`): descriptor constant + (id, name, description, inputSchema, scope, reach, hints) + dispatch + table; no business logic (decidesk `DecideskToolProvider` shape). +- [ ] 2.2 Register the DI alias + `OCA\OpenRegister\Mcp\IMcpToolProvider::softwarecatalog` in + `lib/AppInfo/Application.php` (mirror + `decidesk/lib/AppInfo/Registrar/DomainServiceRegistrar.php:121`). +- [ ] 2.3 Add `lib/Mcp/McpArgumentValidator.php` (port of decidesk's: + typed required/optional argument checking, validation error before any + service call). + +## 3. Authorisation gates + +- [ ] 3.1 Add `lib/Mcp/McpContractGate.php`: load contract → + `not_found` → `ContractApprovalService::authorizeSubmit(contractUuid, + groupNames, activeOrgUuid)`; helpers return real booleans, never + wrapped in `catch(\Throwable)` (decidesk `McpMeetingGate` rules). +- [ ] 3.2 Add `lib/Mcp/McpPublicationGate.php` reusing the + `PublicationController::authorizeEntry()` semantics via + `PublicationService::resolveEntry()` (admin OR owning + `aanbod-beheerder`). +- [ ] 3.3 Admin gates for moderation/merge/EOL/membership tools via + `IGroupManager::isAdmin()` — identical posture to the REST twins. + +## 4. Read tools + +- [ ] 4.1 Implement the curated read handlers (design.md §5 read table): + `getMyContactProfile`, `listOffers`, `listOfferedUsages`, + `getPortfolioReport` (reject foreign org uuid for non-admins), + `listPendingModerations`, `getReviewAggregate`, + `getContractApprovalConfig`, `getSbomImportStatus` + (`userCanReadModule()` gate), `listViews`, `getView`, + `previewOrganisationMerge` (dryRun only), `getEolSyncStatus`. + +## 5. Write tools + +- [ ] 5.1 Contract seam: `submitContractApproval`, `submitContractRenewal` + → `ContractApprovalService::submitForApproval()` behind + `McpContractGate`. +- [ ] 5.2 Publication seam: `publishObject`, `depublishObject` → + `PublicationService::publish()/depublish()` behind + `McpPublicationGate`; descriptors declare `reach: external`. +- [ ] 5.3 Moderation/review/intake: `approveRegistration`, + `rejectRegistration` (`ModerationService`), `submitReview` + (`ReviewService::submit()` — pending forced server-side), + `registerOrganisation` (`IntakeService::validate()` + `submit()`). +- [ ] 5.4 Offers/usages/membership/merge: `acceptOffer`, `declineOffer` + (`AanbodService`), `claimUsage`, `declineUsage` + (`AangebodenGebruikService`), `grantOrganisationMembership`, + `revokeOrganisationMembership` (extract the + `OrganisationMembersController` grant/revoke logic into a small + service, or defer these two tools — record the decision here), + `mergeOrganisations` (`MergeOrganisatieService::execute()`, + admin-gated, `destructiveHint: true`). +- [ ] 5.5 Ops: `importSbom` (`SbomImportService::importForModuleVersie()`, + inline payload), `triggerEolSync` (`EolSyncService::run()`, + `reach: external`). + +## 6. Tests + +- [ ] 6.1 PHPUnit descriptor fixture: every entry has `scope` + `reach` + from the closed vocabularies; reads are `readOnlyHint: true`; + publication + EOL tools are `reach: external`; write set matches + design.md exactly. +- [ ] 6.2 PHPUnit per gate: non-owning caller → forbidden AND delegate + never called AND no `DecisionRequestedEvent` (mirror + `contract-approval-ownership-guard`'s cases at the MCP seam); + owning/admin caller passes through. +- [ ] 6.3 PHPUnit validator ladder: missing/badly-typed argument → + validation error, zero service calls. +- [ ] 6.4 PHPUnit `submitReview` pending-forcing; + `previewOrganisationMerge` never reaches `execute()`. +- [ ] 6.5 PHPUnit fragment/merge: schema key set unchanged after merge + (no orphan Dutch slugs); vulnerability the only schema with write verbs. +- [ ] 6.6 `composer check:strict` clean (PHPCS, PHPMD, Psalm, PHPStan) — + fix pre-existing issues encountered in touched files. + +## 7. Supersession + spec/docs + +- [ ] 7.1 Archive `softwarecatalog-mcp-adoption` as superseded by this + change (pointer in its archive note); its + `register.d/softwarecatalog-mcp-adoption.json` is never created. +- [ ] 7.2 Sync this change's spec delta into + `openspec/specs/mcp-tool-surface/spec.md` on archive. +- [ ] 7.3 CHANGELOG entry under Unreleased: full MCP action surface + (28 derived read tools, vulnerability writes, ~29 curated tools) for + hermiq consumption. From 95ff8c3652c2279ed9bb0e0563dba85644729602 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:02:29 +0000 Subject: [PATCH 02/43] chore(release): 0.1.141-unstable.20260820130046 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 327d81b8..2b99f3e5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820125107 + 0.1.141-unstable.20260820130046 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 58b9fb46..4cab2be9 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820125107", + "version": "0.1.141-unstable.20260820130046", "description": "Software Catalog", "license": { "name": "agpl" From 35a943c2e0cfe554b9064e27d9ec3b5b38b1552c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:20:04 +0000 Subject: [PATCH 03/43] chore(release): 0.1.141-unstable.20260820201847 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 2b99f3e5..5b64bd7e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820130046 + 0.1.141-unstable.20260820201847 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 4cab2be9..367bb591 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820130046", + "version": "0.1.141-unstable.20260820201847", "description": "Software Catalog", "license": { "name": "agpl" From 6a2d1725ceb54203b1537584343f279d676d883e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:40:57 +0000 Subject: [PATCH 04/43] chore(release): 0.1.141-unstable.20260820203911 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5b64bd7e..a7452b9a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820201847 + 0.1.141-unstable.20260820203911 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 367bb591..1a8f7317 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820201847", + "version": "0.1.141-unstable.20260820203911", "description": "Software Catalog", "license": { "name": "agpl" From 5689d7eeb0f2ec600ee2c978d7b6691b0d2258f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:57:37 +0000 Subject: [PATCH 05/43] chore(release): 0.1.141-unstable.20260820205557 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a7452b9a..0a415e45 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820203911 + 0.1.141-unstable.20260820205557 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 1a8f7317..48ac54f1 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820203911", + "version": "0.1.141-unstable.20260820205557", "description": "Software Catalog", "license": { "name": "agpl" From 743ba4b77b6542647cdf67a3957ab2bffe227312 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:22:46 +0000 Subject: [PATCH 06/43] chore(release): 0.1.141-unstable.20260820212102 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 0a415e45..c629c83b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820205557 + 0.1.141-unstable.20260820212102 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 48ac54f1..6758d9a2 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820205557", + "version": "0.1.141-unstable.20260820212102", "description": "Software Catalog", "license": { "name": "agpl" From 4f03776004229ece83c1e1e6d291b9d825abea94 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 20 Aug 2026 23:28:37 +0200 Subject: [PATCH 07/43] fix(manifest): process and audit fields are no longer editable inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CnObjectDataWidget.editable` defaults to TRUE, so every property named in a data widget's `include` list becomes a text box the user can type into. That put lifecycle state and audit stamps — `status`, `lifecycle`, `submittedAt`, `approvedBy`, `openedAt`, `closedAt`, `publishedAt`, `enactedAt` — in front of users as editable fields. These are written by the backend when a transition lands (`TransitionEngine` stamps them through `saveObject()`), so an input for them is a control that can only ever fail or confuse: the guarded path is the lifecycle buttons, and `LifecycleValidationListener` rejects anything that is not a legal transition. Locked with per-field `overrides..editable: false` rather than `editable: false` on the widget: these panels mix process state with fields the user legitimately edits, and a blanket lock would make those read-only too. NOT fixed here: widgets that declare no `include` at all render EVERY schema property, and enumerating their fields in the manifest would drift the moment the schema changes. 52 such widgets fleet-wide expose 124 process fields. Closing those needs a server-side "system-owned" marker, which OpenRegister does not have — `readOnly:true` has no bypass for backend callers and would break the transition that writes the field. Filed as ConductionNL/openregister#2644. Inserted textually, one compact line per widget, so the diff is the change and nothing else: a full JSON re-serialisation reflowed hand-compacted lines and turned this into thousands of lines of churn. A verifier re-parses both files and asserts the only structural difference is the added overrides, and that no non-process field was locked. --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index b190a8d0..299009d1 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -195,7 +195,7 @@ "schema": "organization", "_note": "ADR-062 rollout (round 2): this page did not exist before — organisatie objects (vendors, municipalities, collaborations) were only reachable as index cards with no detail view (codeberg softwarecatalog#76). Vendor/organisation archetype: the organisatie schema itself carries only catalogue-role fields (contactsUid, type, status, registratiestatus, samenwerkingtype, geregistreerdDoor, publicatie dates) because identity (name, website, e-mail, KvK) lives in the linked Nextcloud contact via contactsUid, not on this OR object — so the body leads with those 8 catalogue fields (2-col) rather than name/address. Top-right: a stats-block KPI card counting the organisation's own diensten, modules and contactpersonen (all FK-scoped via aanbieder/organisatie = @objectId) so a reader sees portfolio size at a glance without opening any list. Below: three FK object-lists — Services (dienst.aanbieder) and Applications (module.aanbieder) are the org's supply-side offerings; Contact persons (contactpersoon.organisatie) links to the existing ContactpersoonDetail page. Services still have no dedicated index/detail page (a real fleet gap, follow-up remains). Applications now do — bio-compliance-assessment added Modules/ModuleDetail (BBN level and DPIA tracking needed somewhere to live), so org-modules now carries `rowRoute: ModuleDetail` and a bbnLevel column; org-diensten's rowRoute stays intentionally omitted. An organisation does not communicate itself (contact happens through the linked NC contact), so per the comms hard-rule NO Emails/Meetings widgets appear. Audit trail stays a sidebar tab. Card-click navigation from the Organisaties index required a matching fix in OrganisatieCard.vue (the custom cardComponent never emitted the `click` event CnCardGrid/CnPageRenderer listen on for register+schema route resolution — clicking a card was previously a no-op).", "widgets": [ - { "id": "org-data", "type": "data", "title": "Organisation", "icon": "OfficeBuilding", "content": { "columns": 2, "include": [ "contactsUid", "type", "status", "registrationStatus", "samenwerkingtype", "registeredBy", "publicationDate", "depublicationDate" ] } }, + { "id": "org-data", "type": "data", "title": "Organisation", "icon": "OfficeBuilding", "content": { "columns": 2, "include": [ "contactsUid", "type", "status", "registrationStatus", "samenwerkingtype", "registeredBy", "publicationDate", "depublicationDate" ], "overrides": { "status": { "editable": false }, "registeredBy": { "editable": false } } } }, { "id": "org-stats-services", "type": "stats-block", "title": "Services", "icon": "ChartBar", "content": { "entries": [ { "title": "Services", "register": "@resolve:voorzieningen_register", "schema": "service", "metric": "count", "filter": { "provider": "@objectId" } } ] } }, { "id": "org-stats-applications", "type": "stats-block", "title": "Applications", "icon": "ChartBar", "content": { "entries": [ { "title": "Applications", "register": "@resolve:voorzieningen_register", "schema": "module", "metric": "count", "filter": { "provider": "@objectId" } } ] } }, { "id": "org-stats-contact-persons", "type": "stats-block", "title": "Contact persons", "icon": "ChartBar", "content": { "entries": [ { "title": "Contact persons", "register": "@resolve:voorzieningen_register", "schema": "contactPerson", "metric": "count", "filter": { "organization": "@objectId" } } ] } }, From d0bdc09c06dab946bcb2882f754e0be274a76e8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:44:56 +0000 Subject: [PATCH 08/43] chore(release): 0.1.141-unstable.20260820214311 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index c629c83b..1876dfc2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820212102 + 0.1.141-unstable.20260820214311 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6758d9a2..9a60e73a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820212102", + "version": "0.1.141-unstable.20260820214311", "description": "Software Catalog", "license": { "name": "agpl" From 9a6e9e57d87f7e1884b39decc8e8eb8ac7c63876 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:06:27 +0000 Subject: [PATCH 09/43] chore(release): 0.1.141-unstable.20260820220515 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1876dfc2..02e07ebb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820214311 + 0.1.141-unstable.20260820220515 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 9a60e73a..7bb60eb4 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820214311", + "version": "0.1.141-unstable.20260820220515", "description": "Software Catalog", "license": { "name": "agpl" From 71cb9a0c1f34cb31a1772fbffd56497579f0c765 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:33:12 +0000 Subject: [PATCH 10/43] chore(release): 0.1.141-unstable.20260820223149 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 02e07ebb..3e0b375b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820220515 + 0.1.141-unstable.20260820223149 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7bb60eb4..6cb09eb5 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820220515", + "version": "0.1.141-unstable.20260820223149", "description": "Software Catalog", "license": { "name": "agpl" From 0c1e103cdf6abd34c24caf54b3366482c5b130c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:50:27 +0000 Subject: [PATCH 11/43] chore(release): 0.1.141-unstable.20260820224848 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 3e0b375b..1c87434f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820223149 + 0.1.141-unstable.20260820224848 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6cb09eb5..e0a73e11 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820223149", + "version": "0.1.141-unstable.20260820224848", "description": "Software Catalog", "license": { "name": "agpl" From c74a3a9c8894a7f12f256f135274f8fbeb0096cc Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 01:11:44 +0200 Subject: [PATCH 12/43] build(deps-dev): move the whole stylelint family to 17 as one upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stylelint 15.11.0 -> 17.14.1 with every package that peers on it: @nextcloud/stylelint-config ^2.4.0 -> ^3.2.2 stylelint-config-recommended-scss ^13.1.0 -> ^17.0.1 stylelint-config-recommended-vue ^1.6.1 -> ^2.0.0 postcss-html ^1.8.1 -> ^2.0.0 stylelint-config-html (absent) -> ^2.0.0 None can move alone: vue-config@2 peers 'postcss-html ^2.0.0' and 'stylelint-config-html >=2.0.0'; postcss-html@2 breaks vue-config@1.6.1's own '^1.0.0' peer; scss-config@17 peers 'stylelint ^17'. @nextcloud/stylelint-config is the member that decides it — v2.4.0 still declares indentation / string-quotes / number-leading-zero / selector-list-comma-newline-after, all removed in stylelint 16. Then 10 real errors, in 4 files: 8x word-break: break-word -> overflow-wrap: break-word 1x word-wrap: break-word -> overflow-wrap: break-word 1x clip: rect(0,0,0,0) -> clip-path: inset(50%) Fixed by hand, NOT with --fix. The autofix also rewrites the 86 advisory csstools/use-logical warnings (text-align: left -> start, padding-left -> padding-inline-start, ...) across 28 files. Those change how the UI lays out under RTL and have nothing to do with this bump; openbuild, scholiq and hermiq all carry the same warnings unfixed. A dependency upgrade should not smuggle in a directional-CSS change. Result: 10 errors -> 0, exit 0. The 86 warnings are unchanged and non-blocking. --- package-lock.json | 1379 +++++++---------- package.json | 11 +- src/modals/object/ObjectModal.vue | 4 +- src/modals/object/ViewObject.vue | 10 +- .../sections/ArchiMateImportExport.vue | 4 +- .../settings/sections/ModerationQueue.vue | 2 +- 6 files changed, 609 insertions(+), 801 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ed17dc5..02e8c784 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.1", "@nextcloud/prettier-config": "^1.2.0", - "@nextcloud/stylelint-config": "^2.4.0", + "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/webpack-vue-config": "^7.0.2", "@pinia/testing": "^1.0.2", "@playwright/test": "^1.60.0", @@ -79,11 +79,12 @@ "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", "jsdom": "^29.1.1", - "postcss-html": "^1.8.1", + "postcss-html": "^2.0.0", "prettier": "^3.9.6", - "stylelint": "^15.11.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.6.1", + "stylelint": "^17.14.1", + "stylelint-config-html": "^2.0.0", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^2.0.0", "stylelint-webpack-plugin": "^5.0.1", "ts-jest": "^29.2.3", "ts-loader": "^9.5.1", @@ -127,49 +128,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, "node_modules/@asamuzakjp/dom-selector": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", @@ -2023,6 +1981,50 @@ "node-fetch": "^3.3.0" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@ckpack/vue-color": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@ckpack/vue-color/-/vue-color-1.6.0.tgz", @@ -2354,9 +2356,9 @@ } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", - "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -2370,10 +2372,10 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.4.1" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { @@ -2402,9 +2404,9 @@ } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", - "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -2418,13 +2420,13 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", - "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", + "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", "dev": true, "funding": [ { @@ -2438,17 +2440,40 @@ ], "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.1.tgz", + "integrity": "sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", "dev": true, "funding": [ { @@ -2462,10 +2487,10 @@ ], "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@ctrl/tinycolor": { @@ -4370,6 +4395,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@lezer/common": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", @@ -4954,19 +5003,21 @@ } }, "node_modules/@nextcloud/stylelint-config": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/stylelint-config/-/stylelint-config-2.4.0.tgz", - "integrity": "sha512-S/q/offcs9pwnkjSrnfvsONryCOe6e1lfK2sszN6ZtkYyXvaqi8EbQuuhaGlxCstn9oXwbXfAI6O3Y8lGrjdFg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@nextcloud/stylelint-config/-/stylelint-config-3.2.2.tgz", + "integrity": "sha512-5rr77fGK+zoa8yN+8zR43XbqyuN/yB2wSAy4vKE2F+hgAeOhpUAyChV+pfySDbP20t4s22DHgn7BDiZKsbNE3Q==", "dev": true, "license": "AGPL-3.0-or-later", + "dependencies": { + "stylelint-use-logical": "^2.1.3" + }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.19 || ^22 || ^24" }, "peerDependencies": { - "stylelint": "^15.6.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.1.0" + "stylelint": "^17.9.1", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^1.6.1" } }, "node_modules/@nextcloud/typings": { @@ -6184,6 +6235,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -6597,13 +6661,6 @@ "@types/unist": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -6619,13 +6676,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -8049,16 +8099,6 @@ "node": ">=8" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -8947,6 +8987,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -9014,51 +9078,6 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cancelable-promise": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/cancelable-promise/-/cancelable-promise-4.3.1.tgz", @@ -9504,16 +9523,16 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -9910,56 +9929,6 @@ } } }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -10531,7 +10500,6 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6" } @@ -11126,20 +11094,6 @@ } } }, - "node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", - "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-plugin-vue/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -11807,18 +11761,15 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { @@ -12039,6 +11990,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -12287,16 +12251,6 @@ "node": ">=0.10.0" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -12375,6 +12329,19 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -12482,6 +12449,13 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", @@ -12549,22 +12523,22 @@ "license": "MIT" }, "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -12577,8 +12551,8 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, "node_modules/htmlparser2/node_modules/entities": { @@ -12724,16 +12698,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -12753,6 +12717,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -12763,19 +12738,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -13027,14 +12989,17 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { @@ -15452,9 +15417,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true, "license": "MIT" }, @@ -15723,19 +15688,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/marked": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", @@ -15764,9 +15716,9 @@ } }, "node_modules/mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", "dev": true, "license": "MIT", "funding": { @@ -16013,107 +15965,18 @@ "license": "CC0-1.0" }, "node_modules/meow": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", - "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.2", - "camelcase-keys": "^7.0.0", - "decamelize": "^5.0.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.2", - "read-pkg-up": "^8.0.0", - "redent": "^4.0.0", - "trim-newlines": "^4.0.2", - "type-fest": "^1.2.2", - "yargs-parser": "^20.2.9" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -16685,21 +16548,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/minimizer-webpack-plugin": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", @@ -18158,19 +18006,21 @@ } }, "node_modules/postcss-html": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz", - "integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-2.0.0.tgz", + "integrity": "sha512-f2Rvw5FCollEfVj3wfN7JdQb7n2rNIthW+epw2EByio7M6P7RH0BTj8a/ODHrUXd0cmO7ychb6YniymV93182Q==", "dev": true, "license": "MIT", "dependencies": { - "htmlparser2": "^8.0.0", + "htmlparser2": "^9.1.0", "js-tokens": "^9.0.0", - "postcss": "^8.5.0", - "postcss-safe-parser": "^6.0.0" + "postcss-safe-parser": "^7.0.1" }, "engines": { - "node": "^12 || >=14" + "node": "^22.12 || >=24" + }, + "peerDependencies": { + "postcss": "^8.5.0" } }, "node_modules/postcss-html/node_modules/js-tokens": { @@ -18216,19 +18066,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-scope": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", @@ -18244,19 +18081,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -18280,20 +18104,30 @@ "license": "MIT" }, "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/postcss-scss": { @@ -18324,10 +18158,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz", + "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -18680,6 +18513,26 @@ ], "license": "MIT" }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", @@ -18747,19 +18600,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", @@ -18837,185 +18677,43 @@ "dev": true, "license": "MIT" }, - "node_modules/read-pkg": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", - "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", - "dev": true, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/read-pkg-up": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", - "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", - "dev": true, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", - "dependencies": { - "find-up": "^5.0.0", - "read-pkg": "^6.0.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">= 20.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/redent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", - "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^5.0.0", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10.13.0" } }, "node_modules/regenerate": { @@ -19324,23 +19022,6 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ripemd160": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", @@ -20413,19 +20094,6 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", - "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -20518,13 +20186,6 @@ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "license": "MIT" }, - "node_modules/style-search": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true, - "license": "ISC" - }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -20544,62 +20205,63 @@ } }, "node_modules/stylelint": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", - "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", + "version": "17.14.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.1.tgz", + "integrity": "sha512-xVQwyiuxALUBNB2fBe0tmNemg9KqLtdj3T64mioFDar79B2cU8LIyz+3KL6LdiHs9NkeNfwxpKSaIVOY8f112g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4", - "@csstools/selector-specificity": "^3.0.0", - "balanced-match": "^2.0.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.6", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.2.0", - "css-functions-list": "^3.2.1", - "css-tree": "^2.3.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.1", + "cosmiconfig": "^9.0.2", + "css-functions-list": "^3.3.3", + "css-tree": "^3.2.1", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^7.0.0", + "file-entry-cache": "^11.1.5", "global-modules": "^2.0.0", - "globby": "^11.1.0", + "globby": "^16.2.1", "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^5.2.4", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.29.0", - "mathml-tag-names": "^2.1.3", - "meow": "^10.1.5", - "micromatch": "^4.0.5", + "html-tags": "^5.1.0", + "ignore": "^7.0.5", + "import-meta-resolve": "^4.2.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.1.0", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.28", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.13", + "picocolors": "^1.1.1", + "postcss": "^8.5.16", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.4", "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^3.0.0", + "string-width": "^8.2.1", + "supports-hyperlinks": "^4.5.0", "svg-tags": "^1.0.0", - "table": "^6.8.1", - "write-file-atomic": "^5.0.1" + "table": "^6.9.0", + "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" + "node": ">=20.19.0" } }, "node_modules/stylelint-config-html": { @@ -20620,32 +20282,45 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", - "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=20.19.0" }, "peerDependencies": { - "stylelint": "^15.10.0" + "stylelint": "^17.0.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.1.0.tgz", - "integrity": "sha512-8L5nDfd+YH6AOoBGKmhH8pLWF1dpfY816JtGMePcBqqSsLU+Ysawx44fQSlMOJ2xTfI9yTGpup5JU77c17w1Ww==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-17.0.1.tgz", + "integrity": "sha512-x5DVehzJudcwF0od3sGpgkln2PLLranFE7twwbp7dqDINCyZvwzFkMc6TLhNOvazRiVBJYATQLouJY0xPGB8WA==", "dev": true, "license": "MIT", "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^13.0.0", - "stylelint-scss": "^5.3.0" + "stylelint-config-recommended": "^18.0.0", + "stylelint-scss": "^7.0.0" + }, + "engines": { + "node": ">=20" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^15.10.0" + "stylelint": "^17.0.0" }, "peerDependenciesMeta": { "postcss": { @@ -20654,25 +20329,31 @@ } }, "node_modules/stylelint-config-recommended-vue": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-1.6.1.tgz", - "integrity": "sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-2.0.0.tgz", + "integrity": "sha512-SrGBfxgX+CmxRoFOl6HHhfKrp+6YvGnuiHj/N+/deI310eGNhix8aZOtPHG+OeqB6+5Si5BbjsZiC+kULUO1DQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5", - "stylelint-config-html": ">=1.0.0", - "stylelint-config-recommended": ">=6.0.0" + "semver": "^7.3.5" }, "engines": { - "node": "^12 || >=14" + "node": "^22.12 || >=24" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" + "postcss-html": "^2.0.0", + "stylelint": ">=16.0.0", + "stylelint-config-html": ">=2.0.0", + "stylelint-config-recommended": ">=14.0.0", + "stylelint-config-recommended-scss": ">=14.0.0" + }, + "peerDependenciesMeta": { + "stylelint-config-recommended-scss": { + "optional": true + } } }, "node_modules/stylelint-config-recommended-vue/node_modules/semver": { @@ -20689,20 +20370,63 @@ } }, "node_modules/stylelint-scss": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz", - "integrity": "sha512-4LzLaayFhFyneJwLo0IUa8knuIvj+zF0vBFueQs4e3tEaAMIQX8q5th8ziKkgOavr6y/y9yoBe+RXN/edwLzsQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-7.2.0.tgz", + "integrity": "sha512-6E79Bachv0Iz0gqRUZgdqdXCsiq26DWBWIBNHYtjTmAp3wJu6cp/I37VfW7BPntmh2puF3bY09XWl4HZGrLhzw==", "dev": true, "license": "MIT", "dependencies": { - "known-css-properties": "^0.29.0", + "@csstools/css-calc": "^3.2.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.4", + "@csstools/css-tokenizer": "^4.0.0", + "css-tree": "^3.2.1", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.13", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0" }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^16.8.2 || ^17.0.0" + } + }, + "node_modules/stylelint-scss/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/stylelint-scss/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stylelint-use-logical": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stylelint-use-logical/-/stylelint-use-logical-2.1.3.tgz", + "integrity": "sha512-haPkgxKre+eSqr4IZJnHwNT/9/wICykeFZIaz7rZbe4SohTHkw7vBahMOrZJZpdny/EBVHAcPH2IBeoiUcZWWw==", + "dev": true, + "license": "CC0-1.0", + "engines": { + "node": ">=14.0.0" + }, "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" + "stylelint": ">= 11 < 18" } }, "node_modules/stylelint-webpack-plugin": { @@ -20730,36 +20454,81 @@ "webpack": "^5.0.0" } }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "node_modules/stylelint/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz", - "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.2.0" + "flat-cache": "^6.1.23" + } + }, + "node_modules/stylelint/node_modules/globby": { + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.3.tgz", + "integrity": "sha512-VZX7TV7jmd/pn71vdnLKtgwy1IWqc3KjI9x1/UtPkwoKk5fKrNLY30ltDe3cAM5xruIN7YuuaulFt133jRrKZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylelint/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, + "node_modules/stylelint/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/stylelint/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -20773,18 +20542,63 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/stylelint/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", "dev": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/superjson": { @@ -20813,43 +20627,46 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz", + "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" }, "engines": { - "node": ">=14.18" + "node": ">=20" }, "funding": { "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -21341,19 +21158,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/trim-newlines": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", - "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -22057,6 +21861,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -23655,16 +23472,6 @@ "node": ">=12" } }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", diff --git a/package.json b/package.json index 46441455..612f1355 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@nextcloud/browserslist-config": "^3.0.1", "@nextcloud/eslint-config": "^9.0.1", "@nextcloud/prettier-config": "^1.2.0", - "@nextcloud/stylelint-config": "^2.4.0", + "@nextcloud/stylelint-config": "^3.2.2", "@nextcloud/webpack-vue-config": "^7.0.2", "@pinia/testing": "^1.0.2", "@playwright/test": "^1.60.0", @@ -115,11 +115,12 @@ "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", "jsdom": "^29.1.1", - "postcss-html": "^1.8.1", + "postcss-html": "^2.0.0", "prettier": "^3.9.6", - "stylelint": "^15.11.0", - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-recommended-vue": "^1.6.1", + "stylelint": "^17.14.1", + "stylelint-config-html": "^2.0.0", + "stylelint-config-recommended-scss": "^17.0.1", + "stylelint-config-recommended-vue": "^2.0.0", "stylelint-webpack-plugin": "^5.0.1", "ts-jest": "^29.2.3", "ts-loader": "^9.5.1", diff --git a/src/modals/object/ObjectModal.vue b/src/modals/object/ObjectModal.vue index 9a386038..88f3912c 100644 --- a/src/modals/object/ObjectModal.vue +++ b/src/modals/object/ObjectModal.vue @@ -965,11 +965,11 @@ export default { } .detail-value { - word-break: break-word; + overflow-wrap: break-word; } .sub-detail-value { - word-break: break-word; + overflow-wrap: break-word; font-size: 0.8rem; color: var(--color-text-maxcontrast); } diff --git a/src/modals/object/ViewObject.vue b/src/modals/object/ViewObject.vue index 65442392..d5dcb5b5 100644 --- a/src/modals/object/ViewObject.vue +++ b/src/modals/object/ViewObject.vue @@ -5453,7 +5453,7 @@ export default { text-align: left; align-items: center; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .json-value { @@ -5712,7 +5712,7 @@ export default { min-height: 100px; resize: vertical; white-space: pre-wrap; - word-break: break-word; + overflow-wrap: break-word; overflow-wrap: anywhere; } @@ -5862,7 +5862,7 @@ export default { .viewObjectDialog .viewTable th, .viewObjectDialog .viewTable td { white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .viewObjectDialog .viewTable td.td-labels { @@ -5884,7 +5884,7 @@ export default { .viewObjectDialog .viewTable td.table-row-title { flex: 1; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .short-column { @@ -5900,7 +5900,7 @@ export default { width: 100%; max-width: initial; white-space: normal; - word-break: break-word; + overflow-wrap: break-word; } .table-row-type { diff --git a/src/views/settings/sections/ArchiMateImportExport.vue b/src/views/settings/sections/ArchiMateImportExport.vue index ab98cc8d..182c3b5d 100644 --- a/src/views/settings/sections/ArchiMateImportExport.vue +++ b/src/views/settings/sections/ArchiMateImportExport.vue @@ -1305,7 +1305,7 @@ export default { padding: 0; margin: -1px; overflow: hidden; - clip: rect(0, 0, 0, 0); + clip-path: inset(50%); white-space: nowrap; border: 0; } @@ -2036,7 +2036,7 @@ button.omschrijving-item:disabled { font-weight: 500; color: var(--color-main-text); margin-bottom: 0.5rem; - word-wrap: break-word; + overflow-wrap: break-word; } .error-meta { diff --git a/src/views/settings/sections/ModerationQueue.vue b/src/views/settings/sections/ModerationQueue.vue index 04b229f4..e0d2e2fd 100644 --- a/src/views/settings/sections/ModerationQueue.vue +++ b/src/views/settings/sections/ModerationQueue.vue @@ -333,7 +333,7 @@ export default defineComponent({ .moderation-title { font-weight: 600; - word-break: break-word; + overflow-wrap: break-word; } .moderation-actions { From 4f756706fa0b1551af89a3881ca8fa63a6cab734 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:12:30 +0000 Subject: [PATCH 13/43] chore(release): 0.1.141-unstable.20260820231107 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1c87434f..04cfd4bb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820224848 + 0.1.141-unstable.20260820231107 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index e0a73e11..a805512c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820224848", + "version": "0.1.141-unstable.20260820231107", "description": "Software Catalog", "license": { "name": "agpl" From 3b98ca49e4bb4e07a3fde3c2e325a6f412acfea3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:41:33 +0000 Subject: [PATCH 14/43] chore(release): 0.1.141-unstable.20260820233952 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 04cfd4bb..06e58ae2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820231107 + 0.1.141-unstable.20260820233952 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index a805512c..7a49f544 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820231107", + "version": "0.1.141-unstable.20260820233952", "description": "Software Catalog", "license": { "name": "agpl" From fa4ecea428c731c7d2d70caf8415e23a7abd033f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:03:11 +0000 Subject: [PATCH 15/43] chore(release): 0.1.141-unstable.20260821000125 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 06e58ae2..5abe3e6b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260820233952 + 0.1.141-unstable.20260821000125 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7a49f544..d2942f35 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260820233952", + "version": "0.1.141-unstable.20260821000125", "description": "Software Catalog", "license": { "name": "agpl" From 1ef96a2405247a2fd850dcc99606668b045cc6b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:24:12 +0000 Subject: [PATCH 16/43] chore(release): 0.1.141-unstable.20260821002227 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5abe3e6b..48d1ff69 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821000125 + 0.1.141-unstable.20260821002227 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index d2942f35..0a5f696a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821000125", + "version": "0.1.141-unstable.20260821002227", "description": "Software Catalog", "license": { "name": "agpl" From ed1a773f26836412173d7ab9f4a66b2140973bd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:48:23 +0000 Subject: [PATCH 17/43] chore(release): 0.1.141-unstable.20260821004639 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 48d1ff69..5a395542 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821002227 + 0.1.141-unstable.20260821004639 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 0a5f696a..7e91d294 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821002227", + "version": "0.1.141-unstable.20260821004639", "description": "Software Catalog", "license": { "name": "agpl" From de3924fea6bdf8e47cb45eaf8eea6216e3ea81d0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:02:04 +0000 Subject: [PATCH 18/43] chore(release): 0.1.141-unstable.20260821010037 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5a395542..8d6115ab 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821004639 + 0.1.141-unstable.20260821010037 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 7e91d294..d56aa8be 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821004639", + "version": "0.1.141-unstable.20260821010037", "description": "Software Catalog", "license": { "name": "agpl" From 228c60b97f209f501a3cbf91dca01f18cad10e90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:16:06 +0000 Subject: [PATCH 19/43] chore(release): 0.1.141-unstable.20260821011425 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 8d6115ab..9e21c1f2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821010037 + 0.1.141-unstable.20260821011425 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index d56aa8be..3b3b716c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821010037", + "version": "0.1.141-unstable.20260821011425", "description": "Software Catalog", "license": { "name": "agpl" From 003cdf5134293b41b0903fc02db93c4d3b968976 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:29:33 +0000 Subject: [PATCH 20/43] chore(release): 0.1.141-unstable.20260821012736 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 9e21c1f2..046458c2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821011425 + 0.1.141-unstable.20260821012736 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 3b3b716c..c76e9f39 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821011425", + "version": "0.1.141-unstable.20260821012736", "description": "Software Catalog", "license": { "name": "agpl" From 231bfe22d09d154e2daca6f41c92f8c17bf5cb80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:47:13 +0000 Subject: [PATCH 21/43] chore(release): 0.1.141-unstable.20260821014525 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 046458c2..a8c5f3de 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821012736 + 0.1.141-unstable.20260821014525 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c76e9f39..0788bb27 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821012736", + "version": "0.1.141-unstable.20260821014525", "description": "Software Catalog", "license": { "name": "agpl" From 249246c43140501a1cd1ab3284a80760e686f3e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:56:23 +0000 Subject: [PATCH 22/43] chore(release): 0.1.141-unstable.20260821015458 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index a8c5f3de..6cf92c11 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821014525 + 0.1.141-unstable.20260821015458 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 0788bb27..efe67a63 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821014525", + "version": "0.1.141-unstable.20260821015458", "description": "Software Catalog", "license": { "name": "agpl" From c33308f3debd83309f634f6e751aa4abb1948543 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:05:10 +0000 Subject: [PATCH 23/43] chore(release): 0.1.141-unstable.20260821020326 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 6cf92c11..83a3f2bd 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821015458 + 0.1.141-unstable.20260821020326 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index efe67a63..14b1e210 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821015458", + "version": "0.1.141-unstable.20260821020326", "description": "Software Catalog", "license": { "name": "agpl" From 340ed2acd58c28b5a93897afb97180abb4b0ec56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:24:32 +0000 Subject: [PATCH 24/43] chore(release): 0.1.141-unstable.20260821022249 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 83a3f2bd..d608a76e 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821020326 + 0.1.141-unstable.20260821022249 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 14b1e210..87990c48 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821020326", + "version": "0.1.141-unstable.20260821022249", "description": "Software Catalog", "license": { "name": "agpl" From 7c737b5149b4995efc734c3bf6392a61fc9d7ab2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:44:02 +0000 Subject: [PATCH 25/43] chore(release): 0.1.141-unstable.20260821024223 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d608a76e..ce870203 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821022249 + 0.1.141-unstable.20260821024223 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 87990c48..08120140 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821022249", + "version": "0.1.141-unstable.20260821024223", "description": "Software Catalog", "license": { "name": "agpl" From 6933bbb93d3229e1a306cea5fcfd8f3fb0eda712 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:54:59 +0000 Subject: [PATCH 26/43] chore(release): 0.1.141-unstable.20260821025313 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index ce870203..6453474f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821024223 + 0.1.141-unstable.20260821025313 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 08120140..b6664db8 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821024223", + "version": "0.1.141-unstable.20260821025313", "description": "Software Catalog", "license": { "name": "agpl" From 8569683d666f71a9a500ac69edf6fb340ad34228 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:06:21 +0000 Subject: [PATCH 27/43] chore(release): 0.1.141-unstable.20260821030426 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 6453474f..dd0d0cad 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821025313 + 0.1.141-unstable.20260821030426 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index b6664db8..b32c8fdf 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821025313", + "version": "0.1.141-unstable.20260821030426", "description": "Software Catalog", "license": { "name": "agpl" From 22f0241c13d49e42964480165ec7479275c1bc3d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:19:08 +0000 Subject: [PATCH 28/43] chore(release): 0.1.141-unstable.20260821031723 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index dd0d0cad..d7514931 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821030426 + 0.1.141-unstable.20260821031723 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index b32c8fdf..c1ea6576 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821030426", + "version": "0.1.141-unstable.20260821031723", "description": "Software Catalog", "license": { "name": "agpl" From 91062c29ccd25751cbfc614cda769f4c8d6bcd8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:32:26 +0000 Subject: [PATCH 29/43] chore(release): 0.1.141-unstable.20260821033058 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d7514931..e5be6c05 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821031723 + 0.1.141-unstable.20260821033058 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c1ea6576..19fb3fdf 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821031723", + "version": "0.1.141-unstable.20260821033058", "description": "Software Catalog", "license": { "name": "agpl" From c69f4ee4d0c06e89bc63bcacb3697c703041c010 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:44:04 +0000 Subject: [PATCH 30/43] chore(release): 0.1.141-unstable.20260821034217 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index e5be6c05..55c06172 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821033058 + 0.1.141-unstable.20260821034217 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 19fb3fdf..e7d23dc0 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821033058", + "version": "0.1.141-unstable.20260821034217", "description": "Software Catalog", "license": { "name": "agpl" From 692b859c37058c5074280b335fa87389d65abedf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:55:25 +0000 Subject: [PATCH 31/43] chore(release): 0.1.141-unstable.20260821035333 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 55c06172..5bf435cb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821034217 + 0.1.141-unstable.20260821035333 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index e7d23dc0..370b3fa6 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821034217", + "version": "0.1.141-unstable.20260821035333", "description": "Software Catalog", "license": { "name": "agpl" From c25af32fab3716eb46aa36d90cf889be4380aa27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:06:55 +0000 Subject: [PATCH 32/43] chore(release): 0.1.141-unstable.20260821040505 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 5bf435cb..b7131462 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821035333 + 0.1.141-unstable.20260821040505 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 370b3fa6..c35c629c 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821035333", + "version": "0.1.141-unstable.20260821040505", "description": "Software Catalog", "license": { "name": "agpl" From 5c616934de3fb269401fcc231623d2018fba71d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:18:41 +0000 Subject: [PATCH 33/43] chore(release): 0.1.141-unstable.20260821041656 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b7131462..1b61c57a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821040505 + 0.1.141-unstable.20260821041656 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index c35c629c..88eba791 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821040505", + "version": "0.1.141-unstable.20260821041656", "description": "Software Catalog", "license": { "name": "agpl" From 4ca2fed33f77ced8c03694c1b95a9ec2d4cfcea0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:30:29 +0000 Subject: [PATCH 34/43] chore(release): 0.1.141-unstable.20260821042832 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 1b61c57a..75dd50d9 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821041656 + 0.1.141-unstable.20260821042832 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 88eba791..4fe5a05d 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821041656", + "version": "0.1.141-unstable.20260821042832", "description": "Software Catalog", "license": { "name": "agpl" From 24a0095679b3144de4ddf2a214f7761f3dadd5ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 04:47:34 +0000 Subject: [PATCH 35/43] chore(release): 0.1.141-unstable.20260821044543 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 75dd50d9..82adbd02 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821042832 + 0.1.141-unstable.20260821044543 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 4fe5a05d..6ca5090a 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821042832", + "version": "0.1.141-unstable.20260821044543", "description": "Software Catalog", "license": { "name": "agpl" From 29f588e4bc4bab49cdce8729db894ef0910f03a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:09:34 +0000 Subject: [PATCH 36/43] chore(release): 0.1.141-unstable.20260821050743 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 82adbd02..2db8dba5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821044543 + 0.1.141-unstable.20260821050743 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 6ca5090a..31566bb6 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821044543", + "version": "0.1.141-unstable.20260821050743", "description": "Software Catalog", "license": { "name": "agpl" From 1c0b9cb376ada8c1a09285e19ef9fd2daebd0d6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:26:08 +0000 Subject: [PATCH 37/43] chore(release): 0.1.141-unstable.20260821052417 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 2db8dba5..70c3d5ef 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821050743 + 0.1.141-unstable.20260821052417 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 31566bb6..9ec9d64e 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821050743", + "version": "0.1.141-unstable.20260821052417", "description": "Software Catalog", "license": { "name": "agpl" From 08ed7cf3bbb202a2d9e26fcc3f6462abd401afcd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 05:38:52 +0000 Subject: [PATCH 38/43] chore(release): 0.1.141-unstable.20260821053703 [skip ci] --- appinfo/info.xml | 2 +- openapi.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 70c3d5ef..b6305aa2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie. **Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl. ]]> - 0.1.141-unstable.20260821052417 + 0.1.141-unstable.20260821053703 EUPL-1.2 Conduction SoftwareCatalog diff --git a/openapi.json b/openapi.json index 9ec9d64e..32f1ef47 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "softwarecatalog", - "version": "0.1.141-unstable.20260821052417", + "version": "0.1.141-unstable.20260821053703", "description": "Software Catalog", "license": { "name": "agpl" From bcece50a890e103efe01057c8cacaaaef731dfb8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 14:16:39 +0200 Subject: [PATCH 39/43] chore(deps): refresh the shared Conduction locks (#692) hydra-gates v1.8.1 -> v1.8.2 nc-vue 2.8.2 -> 2.9.2 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ package-lock.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index e5b93042..d38884fd 100644 --- a/composer.lock +++ b/composer.lock @@ -2792,16 +2792,16 @@ }, { "name": "conduction/hydra-gates", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/ConductionNL/.github.git", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a" + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ConductionNL/.github/zipball/8e0e9857e54d6c680e157939e78a468e58d3751a", - "reference": "8e0e9857e54d6c680e157939e78a468e58d3751a", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", + "reference": "3dfcd1e56d27bd06eaa98a9a66e377e7e14fe491", "shasum": "" }, "require": { @@ -2845,9 +2845,9 @@ "support": { "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", "issues": "https://github.com/ConductionNL/.github/issues", - "source": "https://github.com/ConductionNL/.github/tree/v1.8.1" + "source": "https://github.com/ConductionNL/.github/tree/v1.8.2" }, - "time": "2026-08-20T05:07:22+00:00" + "time": "2026-08-20T09:37:12+00:00" }, { "name": "consolidation/annotated-command", diff --git a/package-lock.json b/package-lock.json index 02e8c784..53dc9131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.8.2.tgz", - "integrity": "sha512-kqzqQ2uFyzpHUL6VxzNsfJ5iDOsy/S1iQ9UVn7W0w3CdlVb4RxWz5AFym/onB1eKewF2WtF+9vzBM5CHWsaHTQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.9.2.tgz", + "integrity": "sha512-79AFgzsNiTU9ltg4bEquumR0CFm7b4ed/jW5qEncPPSPWO82HEDwIIe0zm6x38oOegE2ESADiRDn02TW/qXeIQ==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", From 3f6beb9ec173ef4c5cabe5e4052c17047225e29e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 21 Aug 2026 16:02:53 +0200 Subject: [PATCH 40/43] chore(deps): refresh the shared Conduction locks (#694) hydra-gates v1.8.2 -> v1.8.2 nc-vue 2.9.2 -> 2.10.1 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53dc9131..4a7dfbb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.9.2.tgz", - "integrity": "sha512-79AFgzsNiTU9ltg4bEquumR0CFm7b4ed/jW5qEncPPSPWO82HEDwIIe0zm6x38oOegE2ESADiRDn02TW/qXeIQ==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.10.1.tgz", + "integrity": "sha512-4S2X+Bv6mGzQMfxZW8XJheJ8iFis+iJdrl3+hlchYl50qQ77TDWy2xsp9Dog+ggfOikfngzmJseF5kz2MHZt4A==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", From 646c301763d94a730a16c5bd777f5e6307372264 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 02:16:44 +0200 Subject: [PATCH 41/43] =?UTF-8?q?fix(tests):=20opt=20into=20the=20OpenRegi?= =?UTF-8?q?ster=20contract=20=E2=80=94=20fixes=20130=20standalone=20unit?= =?UTF-8?q?=20errors=20(#696)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares this app for ConductionNL/.github#531, which drops `OCA\OpenRegister\Contract\` from conduction/hydra-gates' RUNTIME psr-4 autoload. That prefix is LONGER than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is longest-prefix-wins, so whichever app's autoloader registers first defines OpenRegister's contract for the whole process. IT ALSO FIXES A LIVE PROBLEM HERE, WHICH THE OTHER APPS IN THIS SWEEP DID NOT HAVE. tests/bootstrap-unit.php registers its OpenRegister stubs through a manual spl_autoload_register prefix map covering `OCA\OpenRegister\Db\` and `...\Service\`. Neither covers `...\Contract\`, so the standalone unit suite was already failing on it: before Tests: 715, Assertions: 2573, Errors: 131, Failures: 1, Skipped: 25 after Tests: 715, Assertions: 2993, Errors: 1, Failures: 0, Skipped: 24 The single remaining error is unrelated — Symfony\Component\HttpFoundation\ HeaderUtils is absent from the standalone environment, reached via OCP's DownloadResponse. Added to BOTH bootstraps deliberately: phpunit.xml loads tests/bootstrap.php and phpunit-unit.xml loads tests/bootstrap-unit.php, and both reach code that needs the contract. The full phpunit.xml path cannot be measured outside a Nextcloud tree (its bootstrap fatals on `Class "OC_App" not found`), so that half is verified by CI rather than locally, and is a no-op there while the prefix still exists. interface_exists() is order-independent: it asks whether the interface is RESOLVABLE rather than who registered first. Appending a fallback autoloader does not work, because spl_autoload_register appends relative to registration order and that order across independently loaded apps is what nobody controls. Co-authored-by: Conduction Release Bot --- tests/bootstrap-unit.php | 25 +++++++++++++++++++++++++ tests/bootstrap.php | 26 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/bootstrap-unit.php b/tests/bootstrap-unit.php index c1939566..7001ac87 100644 --- a/tests/bootstrap-unit.php +++ b/tests/bootstrap-unit.php @@ -23,6 +23,31 @@ // Include Composer's autoloader. require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Note the stub prefixes registered just below are `OCA\OpenRegister\Db\` and +// `...\Service\` — neither covers `...\Contract\`, so once hydra-gates stops +// declaring it nothing else in this app resolves it. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // Register OCP/NCU classes from nextcloud/ocp package. // nextcloud/ocp has no autoload section in its composer.json, so we register it manually. spl_autoload_register(function (string $class): void { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4174546e..7783ea29 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,6 +23,32 @@ // Include Composer's autoloader require_once __DIR__ . '/../vendor/autoload.php'; +// THE OpenRegister CONTRACT INTERFACES, OPTED INTO RATHER THAN AUTOLOADED. +// +// conduction/hydra-gates claims `OCA\OpenRegister\Contract\` as a RUNTIME psr-4 +// prefix, so consumers get these interfaces implicitly. That prefix is LONGER +// than openregister's own `OCA\OpenRegister\` -> `lib/`, and PSR-4 is +// longest-prefix-wins, so whichever app's autoloader registers first defines +// OpenRegister's contract for the whole process (ConductionNL/.github#531). +// +// Loaded here, immediately after the autoloader and before the OpenRegister +// stubs below, for the same reason those stubs are loaded early: what is +// declared first wins, and the contract must exist before anything implementing +// it is declared. +// +// interface_exists() is order-independent — it asks whether the interface is +// RESOLVABLE, not who registered first. Appending a fallback autoloader does +// NOT work: spl_autoload_register appends relative to registration order, and +// that order across independently loaded apps is what nobody controls. +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} + // OpenRegister test stubs. The real OCA\OpenRegister\Db\ObjectEntity has // __call magic getters that PHPUnit cannot configure on a mock, so the unit // tests use the explicit stub in tests/Stubs/. It is loaded HERE, BEFORE From 53692a1616a85c55e33e1353989bf79022c95a02 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 02:52:11 +0200 Subject: [PATCH 42/43] =?UTF-8?q?chore(quality):=20migrate=20to=20PHPStan?= =?UTF-8?q?=202=20=E2=80=94=2035=20findings=20to=20zero,=20plus=20a=20miss?= =?UTF-8?q?ing-manager=20bug=20(#697)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(quality): migrate to PHPStan 2 — 35 findings to zero, plus a real bug Bumps `phpstan/phpstan` to ^2.0 and `conduction/hydra-gates` to ^1.8.2, and clears every finding the new major surfaces. ## A missing manager was never detected ContactPersonHandler::setUserManager(): $user = $this->_userManager->get($username); $manager = $this->_userManager->get($managerUsername); if ($user === null || $manager === false) { ...warn and return... } IUserManager::get() returns `?IUser` — it signals "no such user" with NULL and never returns false. So the second half of that guard could never fire: calling setUserManager() with a manager username that does not exist skipped the warning entirely and carried on as if the manager were real. Fixed to `=== null`. PHPStan found it from the other end: inside that branch `$user !== null` was reported as always FALSE, because the only reachable way in was the first clause. ## Dead guards (28) Mostly in the ArchiMate import/export pair, which are near-copies of each other, so nearly every finding came in twos: - `self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false` and `['parallel_processing'] === true` — both are class constants set to true, so neither was ever conditional. - `$identifier !== false`, `$versionId !== false`, `$refCompId !== false`, `$amefKey !== false` — all strings; none can be false. - `is_array($sectionData) === false` — the parameter is declared `array`, so PHP rejects anything else at the call boundary first. - `isset($statistics[$sectionKey]) === false` — the branch above pins $sectionKey to a key $statistics always has. - `if ($section !== 'omschrijving')` with the comment "Skip summary section itself" — `omschrijving` is assigned to $statistics on the line AFTER the loop, so the loop can never see it. - `method_exists($this->archiMateService, '...Optimized')` — the method is declared on the class. - Several `isset() && !== null` pairs and non-nullable-entity null tests. ## One scoped ignore OrganizationSyncService's `if ($contactObject !== null)` is provably true — the code a few lines above already dereferences $contactObject unconditionally. It is left in place because the block it wraps is 243 lines: removing the `if` is a pure re-indentation of a quarter of the method, a large review-hostile diff for zero behaviour change. The comment says so and marks it for the next real edit to that method. ## Verification phpstan 0, phpcs clean, phpmd clean. PHPUnit is NOT part of this evidence: the bootstrap requires a booted Nextcloud (`Class "OC_App" not found`) and cannot run standalone. Checked that this is pre-existing by stashing every change in this commit and re-running — byte-identical failure. CI runs the suite inside the container. * style(quality): satisfy phpcs on the comments this branch added CI's phpcs step runs `--warning-severity=0`, and it failed on every one of the three PRat in this series for the same reason: comments I wrote. Two sniffs: - Squiz.Commenting.InlineComment.NotCapital — many of my new comments open with a lowercase function name ("// find() throws rather than ..."). Rephrased so the first word is a real capitalised word. - Generic.Commenting.DocComment.TagsNotGrouped — the `@param-out` tags I added were interleaved between `@param` tags, splitting the group. Moved below the last `@param`. One of those inserts had also orphaned a continuation line off the `@param` above it; that is rejoined. Where PHPStan genuinely needs a `/** @var */` inline doc-block (which Squiz.Commenting.InlineComment.DocBlock rejects), the line now carries a targeted `phpcs:ignore` naming that sniff and saying why, rather than dropping the annotation and leaving the type wrong. I should have caught this locally. I did run phpcs, but with `--report=summary | tail -3`, which prints only the timing line — so I read an empty tail as "clean" when the error count was two lines above the cut. Re-verified here with CI's exact invocation. --------- Co-authored-by: Conduction Release Bot --- composer.json | 4 +- composer.lock | 23 +++++-- .../OrganisationMembersController.php | 4 +- lib/Controller/SettingsController.php | 8 ++- lib/Service/ArchiMateExportService.php | 2 +- lib/Service/ArchiMateImportService.php | 65 +++++++++---------- lib/Service/ArchiMateService.php | 45 ++++++------- lib/Service/OrganisatieService.php | 18 ++--- lib/Service/OrganizationSyncService.php | 5 ++ lib/Service/SettingsService.php | 19 ++---- .../ContactPersonHandler.php | 6 +- .../SoftwareCatalogue/OrganizationHandler.php | 47 +++++++------- lib/Service/SoftwareCatalogueService.php | 3 +- phpstan.neon | 16 +++++ 14 files changed, 148 insertions(+), 117 deletions(-) diff --git a/composer.json b/composer.json index 289381fa..880aa7be 100644 --- a/composer.json +++ b/composer.json @@ -80,7 +80,7 @@ }, "require-dev": { "conduction/coding-standard": "^1.0", - "conduction/hydra-gates": "^1.0", + "conduction/hydra-gates": "^1.8.2", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", "guzzlehttp/guzzle": "^7.8", @@ -88,7 +88,7 @@ "phpcsstandards/phpcsextra": "^1.4", "phpmd/phpmd": "^2.15", "phpmetrics/phpmetrics": "^2.8", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-latest", "squizlabs/php_codesniffer": "^3.9", diff --git a/composer.lock b/composer.lock index d38884fd..9bfbbdf7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b1b59434167398888c8cfc94e8cf1f7d", + "content-hash": "8ec5b262f02d9a521ddced66fe163f6a", "packages": [ { "name": "adbario/php-dot-notation", @@ -5836,15 +5836,15 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.33", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", - "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -5863,6 +5863,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -5885,7 +5896,7 @@ "type": "github" } ], - "time": "2026-02-28T20:30:03+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/lib/Controller/OrganisationMembersController.php b/lib/Controller/OrganisationMembersController.php index 92fa3893..cc752432 100644 --- a/lib/Controller/OrganisationMembersController.php +++ b/lib/Controller/OrganisationMembersController.php @@ -261,7 +261,9 @@ private function authorizeMaintainer(string $organisationUuid): ?JSONResponse { * * @return \OCA\OpenRegister\Service\OrganisationService The service instance. * - * @throws \Throwable When OpenRegister is unavailable. + * No `@throws`: the body is a plain property read. If OpenRegister is + * unavailable the failure happens in the container while CONSTRUCTING this + * controller, not here. */ private function getOrganisationService(): \OCA\OpenRegister\Service\OrganisationService { return $this->organisationService; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index e3eb3e89..5bc0f6af 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -264,7 +264,7 @@ public function index(): JSONResponse { try { $user = $this->userSession->getUser(); - $isAdmin = $user !== null && $this->groupManager->isAdmin($user->getUID()); + $isAdmin = $this->groupManager->isAdmin($user->getUID()); // Delegate all business logic to service. $data = $this->settingsService->getAllSettings(); @@ -1577,10 +1577,12 @@ private function parseArchiMateFileUpload(): ?array { * @spec openspec/changes/method-decomposition/tasks.md#task-3 */ private function resolveArchiMateMethod(array $options): array { + // No method_exists() probe: ArchiMateService declares + // importArchiMateFileFromPathOptimized(), so only the request parameter + // decides which path runs. $useOptimized = $this->request->getParam('useOptimized', 'true') === 'true'; - $hasOptimized = method_exists($this->archiMateService, 'importArchiMateFileFromPathOptimized'); - if ($useOptimized === true && $hasOptimized === true) { + if ($useOptimized === true) { $this->logger->info('Using OPTIMIZED ArchiMate import method.'); return $this->archiMateService->importArchiMateFileFromPathOptimized($options); } diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index a061ec4b..38d2be00 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -1209,7 +1209,7 @@ private function addObjectDirectlyToXmlWithProperties( $xmlData = $this->cleanObjectDataForXml(object: $object, propDefMap: $propertyDefinitionMap); } - if (is_array($xmlData) === true && empty($xmlData) === false) { + if (empty($xmlData) === false) { if ($sectionName === 'views') { $this->addViewDataToXmlNode(viewNode: $objectNode, viewData: $xmlData); } else { diff --git a/lib/Service/ArchiMateImportService.php b/lib/Service/ArchiMateImportService.php index c7d5fb88..d5e41407 100644 --- a/lib/Service/ArchiMateImportService.php +++ b/lib/Service/ArchiMateImportService.php @@ -368,11 +368,11 @@ public function importArchiMateFileFromPathOptimized(array $options = []): array // PERFORMANCE OPTIMIZATION: Clean up memory after XML parsing. $memoryCleanupTime = 0; - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $memCleanupStart = microtime(true); - $this->cleanupMemory(); - $memoryCleanupTime = microtime(true) - $memCleanupStart; - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. + $memCleanupStart = microtime(true); + $this->cleanupMemory(); + $memoryCleanupTime = microtime(true) - $memCleanupStart; // STEP 2: Extract model identifier. $modelIdStartTime = microtime(true); @@ -1254,7 +1254,7 @@ private function createSectionObject(string $section, string $identifier, array // Fallback: Use AMEF identifier as both ID and extract clean UUID for slug. $objectId = $identifier; // Extract clean UUID from AMEF identifier (remove "id-" prefix if present). - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $slug = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -1735,9 +1735,10 @@ private function saveObjectsInParallelBatches(array $objects, ObjectServiceInter }//end try // Memory cleanup between chunks. - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $this->cleanupMemory(); - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. Flip the constant and the + // compiler will point you back here. + $this->cleanupMemory(); }//end foreach // Store the aggregated result for statistics calculation. @@ -2047,7 +2048,7 @@ private function getAmefRegisterId(): ?int { } // Validate and normalize to positive int. - if ($rawRegisterId !== null && $rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { + if ($rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { $registerId = (int)$rawRegisterId; if ($registerId > 0) { return $registerId; @@ -2549,10 +2550,8 @@ private function findItemsInSection(array $sectionData, string $sectionName): ar // OPTIMIZATION: Removed debug logging from section processing. $items = []; - // Safety check: ensure sectionData is an array. - if (is_array($sectionData) === false) { - return []; - } + // No is_array() safety check: $sectionData is declared array, so PHP + // rejects anything else at the call boundary before this could run. // Get section structure configuration from AMEF config. $config = $this->getSectionStructureConfig(sectionName: $sectionName); @@ -4033,7 +4032,7 @@ private function processStandardVersionRelationship( $standardId = $source; } - if ($versionId !== false && $standardId === true) { + if ($standardId === true) { $stdVersionRelMap[$versionId] = $standardId; } }//end processStandaardVersieRelationship() @@ -4084,7 +4083,7 @@ private function processRelationshipImmediate( $standardId = $source; } - if ($refCompId !== false && $standardId === true) { + if ($standardId === true) { // Initialize arrays if not exists. if (isset($gemmaRelationshipMap[$refCompId]) === false) { $gemmaRelationshipMap[$refCompId] = [ @@ -4800,7 +4799,7 @@ private function transformSectionObjectsBatch( // AMEF identifier becomes slug. } else { // Fallback: extract clean UUID from AMEF identifier for slug. - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $object['@self']['slug'] = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -4809,7 +4808,7 @@ private function transformSectionObjectsBatch( } } else { // No properties to flatten, use AMEF identifier logic. - if ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + if (str_starts_with($identifier, 'id-') === true) { $object['@self']['slug'] = substr($identifier, 3); // Remove "id-" prefix. } else { @@ -4988,7 +4987,9 @@ private function flattenPropertiesBatch(array &$object, array $properties, array continue; } - if ($value !== null && isset($propDefMap[$defRef]) === true) { + // No isset($propDefMap[$defRef]) re-check: the loop above only + // reaches here for a $defRef the map already has. + if ($value !== null) { $propertyName = $propDefMap[$defRef]; $camelCaseName = $this->convertToCamelCase(propertyName: $propertyName); $object[$camelCaseName] = $value; @@ -5022,13 +5023,14 @@ private function flattenPropertiesBatch(array &$object, array $properties, array ); } } else { + // 'mapping_exists' is always true here — the map lookup already + // succeeded, so a null $value is the only way into this branch. $this->logger->warning( - 'Property value is null or mapping missing', + 'Property value is null', [ 'object_id' => $object['identifier'] ?? 'unknown', 'property_def_ref' => $defRef, 'value' => $value, - 'mapping_exists' => isset($propDefMap[$defRef]) === true, ] ); }//end if @@ -5720,10 +5722,8 @@ private function calculateObjectStatistics(array $normalizedData): array { $sectionKey = 'elements'; }//end if - if (isset($statistics[$sectionKey]) === false) { - continue; - // Skip unknown section types. - } + // No "skip unknown section types" guard: the branch above pins + // $sectionKey to a key $statistics always has, so it never fired. // Determine if this object was created, updated, or had errors. $objectId = $object['@self']['id'] ?? $object['identifier'] ?? null; @@ -5809,14 +5809,13 @@ private function calculateObjectStatistics(array $normalizedData): array { 'total_errors' => 0, ]; - foreach ($statistics as $section => $sectionStats) { - if ($section !== 'omschrijving') { - // Skip summary section itself. - $summary['total_objects_created'] += $sectionStats['created']; - $summary['total_objects_updated'] += $sectionStats['updated']; - $summary['total_objects_unchanged'] += $sectionStats['unchanged']; - $summary['total_errors'] += count($sectionStats['errors']); - } + // No "skip the summary section" guard: `omschrijving` is written into + // $statistics on the line AFTER this loop, so the loop can never see it. + foreach ($statistics as $sectionStats) { + $summary['total_objects_created'] += $sectionStats['created']; + $summary['total_objects_updated'] += $sectionStats['updated']; + $summary['total_objects_unchanged'] += $sectionStats['unchanged']; + $summary['total_errors'] += count($sectionStats['errors']); } $statistics['omschrijving'] = $summary; diff --git a/lib/Service/ArchiMateService.php b/lib/Service/ArchiMateService.php index 1a123163..d9376a5d 100644 --- a/lib/Service/ArchiMateService.php +++ b/lib/Service/ArchiMateService.php @@ -642,10 +642,8 @@ private function findItemsInSection(array $sectionData, string $sectionName): ar // OPTIMIZATION: Removed debug logging from section processing. $items = []; - // Safety check: ensure sectionData is an array. - if (is_array($sectionData) === false) { - return []; - } + // No is_array() safety check: $sectionData is declared array, so PHP + // rejects anything else at the call boundary before this could run. // Get section structure configuration from AMEF config. $config = $this->getSectionStructureConfig(sectionName: $sectionName); @@ -974,7 +972,7 @@ private function createSectionObject(string $section, string $identifier, array } elseif (isset($data['Object ID']) === true) { // Check if we have "Object ID" property directly. $slug = $data['Object ID']; - } elseif ($identifier !== false && str_starts_with($identifier, 'id-') === true) { + } elseif (str_starts_with($identifier, 'id-') === true) { // Fallback: extract from identifier (remove "id-" prefix if present). $slug = substr($identifier, 3); } @@ -1027,9 +1025,9 @@ private function saveObjectsToDatabase(array $objects): array { // PERFORMANCE OPTIMIZATION: Use parallel batch processing for large datasets. $batchProcessingStartTime = microtime(true); - if (self::PERFORMANCE_OPTIMIZATIONS['parallel_processing'] === true - && count($objects) > self::PERFORMANCE_OPTIMIZATIONS['batch_size'] - ) { + // PERFORMANCE_OPTIMIZATIONS['parallel_processing'] is a class constant set + // to true, so only the batch-size threshold decides this. + if (count($objects) > self::PERFORMANCE_OPTIMIZATIONS['batch_size']) { $result = $this->saveObjectsInParallelBatches( objects: $objects, objectService: $objectService, @@ -1169,9 +1167,9 @@ private function saveObjectsInParallelBatches(array $objects, ObjectServiceInter }//end try // Memory cleanup between chunks. - if (self::PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] !== false) { - $this->cleanupMemory(); - } + // PERFORMANCE_OPTIMIZATIONS['memory_cleanup'] is a class constant set + // to true, so this was never conditional. + $this->cleanupMemory(); }//end foreach // Store the aggregated result for statistics calculation. @@ -1767,7 +1765,7 @@ private function getAmefRegisterId(): ?int { } // Validate and normalize to positive int. - if ($rawRegisterId !== null && $rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { + if ($rawRegisterId !== '' && is_numeric((string)$rawRegisterId) === true) { $registerId = (int)$rawRegisterId; if ($registerId > 0) { return $registerId; @@ -2258,10 +2256,8 @@ private function calculateObjectStatistics(array $normalizedData): array { // Default fallback. }; - if (isset($statistics[$sectionKey]) === false) { - continue; - // Skip unknown section types. - } + // No "skip unknown section types" guard: the branch above pins + // $sectionKey to a key $statistics always has, so it never fired. // Determine if this object was created, updated, or had errors. $objectId = $object['@self']['id'] ?? $object['identifier'] ?? null; @@ -2346,14 +2342,13 @@ private function calculateObjectStatistics(array $normalizedData): array { 'total_errors' => 0, ]; - foreach ($statistics as $section => $sectionStats) { - if ($section !== 'omschrijving') { - // Skip summary section itself. - $summary['total_objects_created'] += $sectionStats['created']; - $summary['total_objects_updated'] += $sectionStats['updated']; - $summary['total_objects_skipped'] += $sectionStats['skipped']; - $summary['total_errors'] += count($sectionStats['errors']); - } + // No "skip the summary section" guard: `omschrijving` is written into + // $statistics on the line AFTER this loop, so the loop can never see it. + foreach ($statistics as $sectionStats) { + $summary['total_objects_created'] += $sectionStats['created']; + $summary['total_objects_updated'] += $sectionStats['updated']; + $summary['total_objects_skipped'] += $sectionStats['skipped']; + $summary['total_errors'] += count($sectionStats['errors']); } $statistics['omschrijving'] = $summary; @@ -3061,7 +3056,7 @@ private function processRelationshipImmediate( $standardId = $source; } - if ($refCompId !== false && $standardId === true) { + if ($standardId === true) { // Initialize arrays if not exists. if (isset($gemmaRelationshipMap[$refCompId]) === false) { $gemmaRelationshipMap[$refCompId] = [ diff --git a/lib/Service/OrganisatieService.php b/lib/Service/OrganisatieService.php index 8dc86c08..9a1029ac 100644 --- a/lib/Service/OrganisatieService.php +++ b/lib/Service/OrganisatieService.php @@ -106,15 +106,15 @@ public function createOrganisationInOpenRegister(array $objectData): ?object { organizationUuid: $organizationUuid ); - if ($organisationEntity !== null) { - $this->logger->info( - 'OrganisatieService: Successfully created organization entity', - [ - 'organizationUuid' => $organizationUuid, - 'entityId' => $organisationEntity->getId(), - ] - ); - } + // The createOrganisationEntityInternal() helper is declared non-nullable and + // throws on failure — the catch below is the real failure path. + $this->logger->info( + 'OrganisatieService: Successfully created organization entity', + [ + 'organizationUuid' => $organizationUuid, + 'entityId' => $organisationEntity->getId(), + ] + ); return $organisationEntity; } catch (\Exception $e) { diff --git a/lib/Service/OrganizationSyncService.php b/lib/Service/OrganizationSyncService.php index 9b510cfa..d598cf2b 100644 --- a/lib/Service/OrganizationSyncService.php +++ b/lib/Service/OrganizationSyncService.php @@ -1404,6 +1404,11 @@ private function processContactPerson(object $contactPerson, array &$stats): ?st private function updateOrganisationEntityUsers(object $organisationEntity, array $usernames, array &$stats): void { try { $organisationUuid = $organisationEntity->getUuid(); + // OpenRegister is not on the analysis path, so the getUsers() call has no + // resolvable return type and sort() below cannot be checked without + // this. It is a list of usernames. + // phpcs:ignore Squiz.Commenting.InlineComment.DocBlock -- PHPStan only reads @var from a /** */ block. + /** @var array $currentUsers */ $currentUsers = ($organisationEntity->getUsers() ?? []); // Add admin users to ensure they're always included. diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index b08f7d58..d354a501 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -872,7 +872,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int { $amefKey = $amefKeyMap[$objectType] ?? null; - if ($amefKey !== false && isset($decodedAmefConfig[$amefKey]) === true) { + if (isset($decodedAmefConfig[$amefKey]) === true) { $schemaId = $decodedAmefConfig[$amefKey]; if (empty($schemaId) === false) { $result = (int)$schemaId; @@ -920,9 +920,7 @@ public function getSchemaIdForObjectType(string $objectType): ?int { // Only check voorzieningen config if object type exists in the key map. if ($result === null && isset($voorzieningenKeyMap[$objectType]) === true) { $voorzieningenKey = $voorzieningenKeyMap[$objectType]; - if (isset($voorzieningenConfig[$voorzieningenKey]) === true - && $voorzieningenConfig[$voorzieningenKey] !== null - ) { + if (isset($voorzieningenConfig[$voorzieningenKey]) === true) { $result = (int)$voorzieningenConfig[$voorzieningenKey]; } } @@ -5997,11 +5995,10 @@ public function getEmailConfigFocused(): array { */ public function updateEmailConfig(array $config): array { try { - if (isset($config) === true) { - $result = $this->updateEmailSettings(emailSettings: $config); - if ($result['success'] === false) { - return $result; - } + // No isset($config) guard: it is a required, non-nullable parameter. + $result = $this->updateEmailSettings(emailSettings: $config); + if ($result['success'] === false) { + return $result; } return [ @@ -6070,9 +6067,7 @@ public function updateAmefConfig(array $config): array { // Load existing config to allow merging. $existing = $this->getAmefConfig(); - if (is_array($existing) === false) { - $existing = []; - } + // No is_array() fallback: getAmefConfig() is declared to return array. // Determine target register id. if (isset($config['register']) === true) { diff --git a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php index 095f84a1..cae6758a 100644 --- a/lib/Service/SoftwareCatalogue/ContactPersonHandler.php +++ b/lib/Service/SoftwareCatalogue/ContactPersonHandler.php @@ -1454,7 +1454,11 @@ public function setUserManager(string $username, string $managerUsername): void $user = $this->_userManager->get($username); $manager = $this->_userManager->get($managerUsername); - if ($user === null || $manager === false) { + // `$manager === null`, not `=== false`: IUserManager::get() returns + // ?IUser and signals "no such user" with null. Comparing against + // false meant a MISSING MANAGER was never detected — the guard fell + // through and the method carried on as if the manager existed. + if ($user === null || $manager === null) { $this->_logger->warning( 'Cannot set manager - user or manager not found', [ diff --git a/lib/Service/SoftwareCatalogue/OrganizationHandler.php b/lib/Service/SoftwareCatalogue/OrganizationHandler.php index 747e6a4b..25533b53 100644 --- a/lib/Service/SoftwareCatalogue/OrganizationHandler.php +++ b/lib/Service/SoftwareCatalogue/OrganizationHandler.php @@ -437,30 +437,31 @@ public function processContactpersonen(object $organizationObject): array { ); } - if ($contactgegevensObject !== null) { - $processedContacts[] = $contactgegevensObject; - - $actionLogMessage = 'Created new contactgegevens from contactpersoon'; - $actionValue = 'create'; - if ($existingContactgegevens !== null) { - $actionLogMessage = 'Updated existing contactgegevens from contactpersoon'; - $actionValue = 'update'; - } + // No null guard: saveObject() returns a non-nullable + // ObjectEntityInterface and throws on failure, which the + // catch below handles. + $processedContacts[] = $contactgegevensObject; - $this->_logger->info( - $actionLogMessage, - [ - 'organizationId' => $organizationUuid, - // UUID, not getId(): `getId()` is not on - // ObjectEntityInterface (ADR-084), and the UUID is - // the identifier every other log line here carries. - 'contactgegevensId' => $contactgegevensObject->getUuid(), - 'contactpersoonIndex' => $index, - 'email' => $contactgegevensData['email'], - 'action' => $actionValue, - ] - ); - }//end if + $actionLogMessage = 'Created new contactgegevens from contactpersoon'; + $actionValue = 'create'; + if ($existingContactgegevens !== null) { + $actionLogMessage = 'Updated existing contactgegevens from contactpersoon'; + $actionValue = 'update'; + } + + $this->_logger->info( + $actionLogMessage, + [ + 'organizationId' => $organizationUuid, + // UUID, not getId(): `getId()` is not on + // ObjectEntityInterface (ADR-084), and the UUID is + // the identifier every other log line here carries. + 'contactgegevensId' => $contactgegevensObject->getUuid(), + 'contactpersoonIndex' => $index, + 'email' => $contactgegevensData['email'], + 'action' => $actionValue, + ] + ); } catch (\Exception $e) { $this->_logger->error( 'Failed to process contactPerson: ' . $e->getMessage(), diff --git a/lib/Service/SoftwareCatalogueService.php b/lib/Service/SoftwareCatalogueService.php index 45c25c4a..307d78f2 100644 --- a/lib/Service/SoftwareCatalogueService.php +++ b/lib/Service/SoftwareCatalogueService.php @@ -1526,7 +1526,8 @@ private function createOrganisationInOpenRegisterInternal( $this->_logger->info( 'SoftwareCatalogueService: STEP 2 - Checking user context', [ - 'hasUserSession' => $userSession !== null, + // Always true: $userSession is an injected, non-nullable IUserSession. + 'hasUserSession' => true, 'currentUser' => $currentUserValue, 'isAnonymous' => $currentUser === null, ] diff --git a/phpstan.neon b/phpstan.neon index a805d1e8..ecc4397b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -22,3 +22,19 @@ parameters: # class` errors that a bare ignore pattern cannot fix. Analysis-only; # never loaded at runtime or by PHPUnit. - tests/analysis-stubs/decidesk-events.stub.php + + ignoreErrors: + # OrganizationSyncService's `if ($contactObject !== null)` at the top of + # the contact-person loop. saveObject() returns a non-nullable + # ObjectEntityInterface, so the guard is provably true — the code a few + # lines ABOVE it already dereferences $contactObject unconditionally, + # which is the giveaway. + # + # Left in place rather than removed because the block it wraps is 243 + # lines: deleting the `if` is a pure re-indentation of a quarter of the + # method, which is a large, review-hostile diff for zero behaviour + # change. Worth doing when that method is next touched for real. + - + message: '#Strict comparison using !== between OCA\\OpenRegister\\Contract\\ObjectEntityInterface and null will always evaluate to true#' + identifier: notIdentical.alwaysTrue + path: lib/Service/OrganizationSyncService.php From fe821988a5575406f850b60f061efba306c16c59 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 22 Aug 2026 10:24:58 +0200 Subject: [PATCH 43/43] chore(deps): refresh the shared Conduction locks (#706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): refresh the shared Conduction locks hydra-gates v1.8.2 -> v1.9.0 nc-vue 2.10.1 -> 2.11.1 Lock-only: both packages are already declared with caret ranges that permit these versions, so nothing about what this app ACCEPTS changes - only what it currently resolves to. Opened by the weekly fleet shared-dependency bump, because a lock nobody re-resolves is a pin nobody chose. Merging is gated by this repository's own suite, deliberately: taking hydra-gates v1.8.1 added patchObject() to a published interface, which is a load-time fatal for any concrete double that implements it without the method. CI is the only thing that can tell a safe bump from that. * fix(psalm): stub OpenRegister's contract, which v1.9.0 stopped autoloading hydra-gates v1.9.0 removed `OCA\OpenRegister\Contract\` from its runtime psr-4 autoload (ConductionNL/.github#531). The removal was correct — that prefix is longer than openregister's own, so a vendored copy in ANY app defined the contract for the whole process — but it was verified against PHPUnit only. Psalm never runs the test bootstrap; it resolves types through the composer autoload map. So the guarded require added to this app's bootstrap does nothing for it, and 213 UndefinedClass errors appeared for a class the app typehints but does not own. This is the same situation as the decidesk event stubs already in this file: a sibling Nextcloud app supplies the type at runtime, so it is absent from the analysis path and gets declared here. A stub teaches the analyser the shape without putting the class back into the runtime autoloader, which is what caused the original defect. Measured in this checkout on the real v1.9.0: 213 errors before, 0 after — "No errors found!", psalm exit 0. * fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped URLs in this app's metadata — website, bugs, repository and screenshots — as pointing at codeberg.org. GitHub is the only host. Two things changed together, and only one of them is the host: codeberg.org/Conduction/softwarecatalog -> github.com/ConductionNL/stackiq The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the host would have produced github.com/ConductionNL/softwarecatalog, which resolves today purely because GitHub redirects a renamed repo — and stops the moment anyone creates a repo at the old path. Screenshots move to raw.githubusercontent.com rather than a github.com/raw redirect, and every one was fetched rather than assumed: HTTP 200 each. A green gate with dead image URLs would be worse than the finding it silenced. Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this app's last development run, so the PR runs are simply the first measured against it. Pre-existing debt, newly visible. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Conduction Release Bot --- appinfo/info.xml | 18 +++++++++--------- composer.lock | 17 ++++++----------- package-lock.json | 6 +++--- psalm.xml | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b6305aa2..0c68b0f6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -55,20 +55,20 @@ Vrij en open source onder de EUPL-licentie. Conduction SoftwareCatalog - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq organization tools integration - https://codeberg.org/Conduction/softwarecatalog - https://codeberg.org/Conduction/softwarecatalog/issues - https://codeberg.org/Conduction/softwarecatalog + https://github.com/ConductionNL/stackiq + https://github.com/ConductionNL/stackiq/issues + https://github.com/ConductionNL/stackiq - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-dashboard.png - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-applications.png - https://codeberg.org/Conduction/softwarecatalog/raw/branch/main/img/screenshot-connections.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-dashboard.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-applications.png + https://raw.githubusercontent.com/ConductionNL/stackiq/main/img/screenshot-connections.png + +