Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
94e826b
docs(openspec): connections-catalogue-pages, connection index, detail…
rubenvdlinde Sep 27, 2026
113ecb3
docs(openspec): connections-diagram-and-graph-export, application map…
rubenvdlinde Sep 27, 2026
7d1f849
docs(openspec): connections-api-catalogue, record the APIs an applica…
rubenvdlinde Sep 27, 2026
7f6c7a6
docs(openspec): connections-derived-dependencies, suggest known and c…
rubenvdlinde Sep 27, 2026
7850227
docs(openspec): landscape-application-page, correct keys, usages, con…
rubenvdlinde Sep 27, 2026
2e19d75
docs(openspec): landscape-usage-registration, usage pages, owners and…
rubenvdlinde Sep 27, 2026
40505d7
docs(openspec): landscape-application-components, partOf relation and…
rubenvdlinde Sep 27, 2026
6ae331f
docs(openspec): landscape-change-entry-type, change type through Open…
rubenvdlinde Sep 27, 2026
98e910c
docs(openspec): landscape-move-between-organisations, transfer chosen…
rubenvdlinde Sep 27, 2026
281174a
docs(openspec): landscape-dependent-field-options, dependent option t…
rubenvdlinde Sep 27, 2026
207678f
docs(openspec): landscape-completeness-score, OpenRegister quality ru…
rubenvdlinde Sep 27, 2026
1a4de2f
docs(openspec): landscape-owner-attestation, confirmation rounds for …
rubenvdlinde Sep 27, 2026
8ff4a82
docs(openspec): landscape-ai-system-inventory, AI systems with AI Act…
rubenvdlinde Sep 27, 2026
d6e1f26
chore(parity): OpenSpec-pass decisions and matrix states for the firs…
rubenvdlinde Sep 27, 2026
988815a
docs(openspec): landscape-ai-system-inventory, FRIA as a reference fi…
rubenvdlinde Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openspec/changes/connections-api-catalogue/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-27
55 changes: 55 additions & 0 deletions openspec/changes/connections-api-catalogue/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Design: connections-api-catalogue

Read at development `49e65cb4`.

## Context

Stackiq's catalogue schemas live in `lib/Settings/softwarecatalogus_register.json`. Per ADR-037 a change adds its schema in a fragment under `lib/Settings/register.d/`, which `SettingsService::loadSettings()` deep-merges into the monolith at load (`lib/Service/SettingsService.php:1653-1680`). Lists append in that merge (`deepMergeConfig`, :7338), so a fragment can add a schema to the register's list.

## D1. A new schema in a fragment

File `lib/Settings/register.d/application-interfaces.json` with:

- `components.schemas.applicationInterface`, schema.org type `WebAPI`:

| property | type | notes |
|---|---|---|
| `name` | string, required | |
| `shortDescription`, `longDescription` | string, markdown for the long one | |
| `module` | `$ref` module, required | the application that offers the API, `inversedBy: interfaces` |
| `style` | enum `REST`, `SOAP`, `GraphQL`, `event or message`, `file`, `other` | facetable |
| `version` | string | |
| `specificationUrl` | string, format uri | an OpenAPI, WSDL or AsyncAPI document |
| `documentationUrl` | string, format uri | |
| `standardVersions` | array of `$ref` element, `queryParams: gemmaType=standaardversie` | the same picker as `connection.standardVersions` |
| `status` | enum `in development`, `in use`, `end of support`, `withdrawn` | the connection's values, with an `x-openregister-lifecycle` on those exact values |
| `publicationDate`, `depublicationDate` | date-time | |

- `authorization` copied from `module` (`register.json` module schema): organisation-scoped read for `aanbod-beheerder`, read for `gebruik-beheerder`, public read after `publicationDate`; create and update for the groups that may edit a module.
- `components.registers.stackiq.schemas: ["applicationInterface"]` and `components.registers.stackiq.configuration.schemas.applicationInterface: { "magicMapping": true, "autoCreateTable": true }`, like every other catalogue schema (`register.json:853` onwards).
- On `connection`, a new optional property `interface` (`$ref` applicationInterface, `x-relation-filter` on `module` equal to the connection's `moduleB`), added through the same fragment.

Rejected: a `subtype` value on `connection`. An API exists before anyone connects to it, has its own version and specification, and serves many connections; LeanIX models it as its own fact sheet for that reason.

## D2. Pages

In `src/manifest.d/application-interfaces.json`:

- `Apis`, route `/apis`, `type: index`, schema `applicationInterface`, columns name, module, style, version, status; `filterMenu: true`.
- `ApiDetail`, route `/apis/:id`, `type: detail`: data widget, files, related (connections that name it), history tab.
- A menu child `APIs` under the `Modules` (Applications) entry, next to Connections from `connections-catalogue-pages`. No new top-level entry (ADR-097).

On `ModuleDetail` (`src/manifest.json:491`) an `object-list` widget `md-apis` with filter `{ "module": "@objectId" }`, `rowRoute: ApiDetail`, and `allowCreate: true` with the application prefilled.

## Declarative versus imperative

All declarative: a schema with a lifecycle and relations, manifest pages, one object-list widget (ADR-031). No PHP.

## Seed data

`lib/Settings/stackiq_mock_register.json` gains two demo APIs on one demo application: a REST API with a specification URL pointing at a placeholder `https://example.org/openapi.json`, and an event API.

## Risks

- `ModuleDetail` also changes in `connections-catalogue-pages` and `landscape-application-page`; the widgets stack below each other.
- A schema added through a fragment has never been tried for a brand-new schema in this app (the two existing fragments modify schemas). Task 1 proves the merge with a unit test before any page work.
42 changes: 42 additions & 0 deletions openspec/changes/connections-api-catalogue/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
kind: code
depends_on:
- connections-catalogue-pages
---

# Record the APIs an application exposes

## Summary

A supplier or an information manager records the APIs an application offers, next to that application: the style, the version, where the specification lives, the standards it follows and its status. A connection can name the API it calls. The application page lists its APIs, and an APIs list shows them across the catalogue.

## Why

Row from the stackiq matrix:

- `stackiq:conn-api-catalogue`, "Keep the APIs an application exposes in the catalogue next to the application." Rated no. SAP LeanIX rates yes: https://help.sap.com/docs/leanix/ea/interface-modeling-guidelines, interface subtype "API ... APIs provide functionalities accessible to external applications", related to the providing application. The row sits in the product's core area (connections), which is why it is built with one competitor.

No tender, feature request or roadmap row names it.

## What stackiq has today

- No schema describes an API. The register's catalogue schemas are listed at `lib/Settings/softwarecatalogus_register.json:817` onwards (sector, suite, module, catalogService, vulnerability, contactPerson, organization, usage, catalogContract, connection, software-review, compliancy, moduleVersion, sbomComponent, bioMeasure).
- `connection.type` has the value `api` (`register.json:3565` schema), but it only labels the transport of one connection; it says nothing about the API itself.
- Standards live as GEMMA elements with `gemmaType` standaard and standaardversie; `module.standardVersions` and `connection.standardVersions` already point at them.

## What this change builds

1. A schema `applicationInterface` (title "API") in a register fragment: name, descriptions, the providing application, style, version, specification URL, documentation URL, standard versions, status and publication dates.
2. An optional `interface` field on `connection`, so a connection names the API it calls.
3. An APIs list page and an API detail page, reached under Applications in the menu.
4. An APIs section on the application page, with an Add button that fills in the application.

## Out of scope

- A developer portal: keys, subscriptions, a try-it console. The matrix category says stackiq is not a developer portal; integriq's open change `access-developer-portal-and-subscriptions` covers that for its gateway.
- Importing APIs from an OpenAPI file or a gateway (integriq's `gateway-openapi-import-and-publish` covers publishing through integriq).
- Checking an API against the NLGov REST API design rules (integriq's `gateway-api-design-rules-check`).

## Risks

- Suppliers and municipalities may both register the same API. The detail page shows the providing application and its supplier, and `operations-record-reconciliation` covers merging duplicates.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# application-interfaces specification

**Status**: proposed
**Scope**: stackiq
**OpenSpec changes**:
- connections-api-catalogue

## Purpose

The catalogue records the APIs an application offers, next to the application, so an architect sees what can be connected to and how. Matrix row `stackiq:conn-api-catalogue`.

## ADDED Requirements

### Requirement: REQ-AIF-001 A user can record an API that an application offers

Stackiq SHALL store an API as an `applicationInterface` object with a name, the providing application, a style, a version, a specification URL, a documentation URL, the standard versions it follows and a status. The status SHALL move through `in development`, `in use`, `end of support` and `withdrawn` by declared transitions.

#### Scenario: A supplier adds a REST API to its application
@e2e tests/e2e/workflows/application-interfaces.spec.ts

- **GIVEN** a supplier with edit rights on application X
- **WHEN** they open the page of application X, click Add in the APIs section and save name "Zaken API", style `REST`, version `1.2` and a specification URL
- **THEN** the APIs section of application X lists "Zaken API" with style REST and version 1.2
- **AND** the APIs list at `/apis` shows it too

### Requirement: REQ-AIF-002 The application page lists its APIs

The application page `ModuleDetail` SHALL list the APIs whose providing application is that application, and each row SHALL open the API's detail page.

#### Scenario: An architect checks what an application offers
@e2e tests/e2e/workflows/application-interfaces.spec.ts

- **GIVEN** application X offers a REST API and an event API
- **WHEN** an architect opens the page of application X
- **THEN** the APIs section lists both APIs with their style and status

### Requirement: REQ-AIF-003 A connection can name the API it calls

The `connection` schema SHALL carry an optional `interface` field that points at an API of the connection's application B, and the API's detail page SHALL list the connections that name it.

#### Scenario: An information manager links a connection to an API
@e2e exclude The field is a related-object picker in the library form; tests/Unit/Settings/ApplicationInterfaceFragmentTest.php asserts the property and its relation filter.

- **GIVEN** a connection from application A to application X, and X offers "Zaken API"
- **WHEN** the information manager sets the connection's API to "Zaken API"
- **THEN** the detail page of "Zaken API" lists that connection
35 changes: 35 additions & 0 deletions openspec/changes/connections-api-catalogue/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Tasks: connections-api-catalogue

## Implementation tasks

### Task 1: The applicationInterface schema
- **spec_ref**: openspec/changes/connections-api-catalogue/specs/application-interfaces/spec.md#requirement-req-aif-001-a-user-can-record-an-api-that-an-application-offers
- **files**: `lib/Settings/register.d/application-interfaces.json`, `lib/Settings/stackiq_mock_register.json`
- **acceptance_criteria**:
- GIVEN the merged register WHEN it is imported THEN the stackiq register lists applicationInterface and its table exists
- GIVEN a connection WHEN its interface field is set THEN it holds an API of the connection's application B
- [ ] Implement
- [ ] Test (PHPUnit `tests/Unit/Settings/ApplicationInterfaceFragmentTest.php`: the merged register carries the schema, the register list entry and the lifecycle on enum values)

### Task 2: APIs pages and the application page section
- **spec_ref**: openspec/changes/connections-api-catalogue/specs/application-interfaces/spec.md#requirement-req-aif-002-the-application-page-lists-its-apis
- **files**: `src/manifest.d/application-interfaces.json`, `src/manifest.json` (ModuleDetail), `l10n/en.json`, `l10n/nl.json`
- **acceptance_criteria**:
- GIVEN an application with two APIs WHEN its page opens THEN the APIs section lists both
- GIVEN the APIs list WHEN the user filters on style REST THEN only REST APIs remain
- [ ] Implement
- [ ] Test (Playwright `tests/e2e/workflows/application-interfaces.spec.ts`)

### Task 3: Documentation
- **spec_ref**: openspec/changes/connections-api-catalogue/specs/application-interfaces/spec.md#requirement-req-aif-001-a-user-can-record-an-api-that-an-application-offers
- **files**: `docs/features/application-interfaces.md`, `docs/images/application-apis.png`
- **acceptance_criteria**:
- GIVEN the docs site WHEN a reader opens APIs THEN it explains how to record an API and link a connection to it, with a screenshot
- [ ] Implement
- [ ] Test (docs build, screenshot with Playwright)

## Verification

- `openspec validate connections-api-catalogue --type change --strict` passes.
- `composer check:strict` and `npm run lint` pass; the PHPUnit and Playwright cases above pass.
- English and Dutch strings for every new label (ADR-005); docs with a screenshot (ADR-010).
2 changes: 2 additions & 0 deletions openspec/changes/connections-catalogue-pages/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-27
59 changes: 59 additions & 0 deletions openspec/changes/connections-catalogue-pages/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Design: connections-catalogue-pages

Read at development `49e65cb4`. Every path and line below was opened for this design.

## Context

A connection (`koppeling`) is a catalogue object: an application A talks to an application B, or to a national provision, over a transport, in a direction. The schema is complete (`lib/Settings/softwarecatalogus_register.json:3565`) and the ArchiMate import fills it, but the app shows it nowhere. The pages are declarative manifest pages over OpenRegister (ADR-001, ADR-024), so no PHP controller or service is added.

## D1. Pages live in a manifest fragment

New file `src/manifest.d/connections.json` (ADR-037), merged at build like `src/manifest.d/connection-registry.json`. It holds:

- `Koppelingen`, route `/koppelingen`, `type: index`, `register: @resolve:voorzieningen_register`, `schema: connection`. Columns: `name`, `type`, `status`, `moduleA`, `moduleB`, `nonMunicipalProvision`, `dataExchangeDirection`. `filterMenu: true`, so the table header lists the values of every enum column as toggleable filters (`CnIndexPage.vue:2236` in `@conduction/nextcloud-vue` 2.57.1). `quickFilters` on status: All, In use, In development, End of support, Withdrawn, the way `Contracten` does it (`src/manifest.json:540`).
- `KoppelingDetail`, route `/koppelingen/:id`, `type: detail`. Widgets: `kp-data` (type `data`, all visible fields), `kp-files` (files integration, the schema already allows files), `kp-related` (type `related`), and a History tab in the sidebar like every other detail page. `lifecycleActions` on, so `CnLifecycleActions` (`CnDetailPage.vue:158`) offers release, sunset and withdraw once D3 lands.

Rejected: adding the pages to `src/manifest.json` directly. That file is already 1,000+ lines, and ADR-037 puts a change's pages in its own fragment so two changes do not conflict on one file.

## D2. The application page gets two connection lists

`ModuleDetail` (`src/manifest.json:491`) gains two `object-list` widgets:

| id | filter | title |
|---|---|---|
| `md-connections-out` | `{ "moduleA": "@objectId" }` | Connections from this application |
| `md-connections-in` | `{ "moduleB": "@objectId" }` | Connections to this application |

Both set `rowRoute: KoppelingDetail`, `viewAllRoute: Koppelingen` with the same filter in `viewAllQuery`, and columns `type`, `status`, the other side of the connection. `CnObjectListWidget` takes one filter object with AND semantics (`CnObjectListWidget.vue:503`), so one list with "A or B" is not possible declaratively; two lists also say which way the data flows.

Rejected: a custom widget that calls `GET /api/koppelingen-gebruik/{uuid}` (`AangebodenGebruikController.php:208`). That endpoint is public, mixes usages into the answer, and would add the first caller of a custom endpoint where OpenRegister's own list already serves the need (ADR-022).

## D3. Register fixes in the same change

All in `lib/Settings/softwarecatalogus_register.json`, schema `connection`:

1. **Lifecycle states.** Replace the Dutch states in `x-openregister-lifecycle` (:3926) with the enum values: initial `in development`, final `withdrawn`, transitions release (`in development` to `in use`), sunset (`in use` to `end of support`), withdraw (`in use`, `end of support` to `withdrawn`). The rows already hold the English values (`lib/Repair/RenameDutchCatalogValues.php:87-90`).
2. **Picker.** Change `objectConfiguration.queryParams` on `nonMunicipalProvision` (:3720) to `gemmaType=Buitengemeentelijke voorziening`, the spelling the GEMMA model uses.
3. **Name template.** `objectNameField` names `gegevensuitwisselingRichting` and `buitengemeentelijkVoorziening`, keys the schema renamed to `dataExchangeDirection` and `nonMunicipalProvision`, and maps `AnaarB`, `BnaarA`, `bi-directioneel` where the enum holds `AtoB`, `BtoA`, `bi-directional`. Rewrite it on the current keys and values, so a connection reads "Application A to Application B" in lists and pickers.
4. **Facets.** Set `facetable: true` on `type`, `status` and `dataExchangeDirection`, so the index page can count and filter them.
5. **Version.** Bump the `connection` schema version to 0.3.2 and the register version, and add a changelog line. The register changelog entry 2.4.4 (`register.json:7`) records why: OpenRegister skips an import whose deployed version is not lower, and its content check ignores `configuration`.

## D4. Menu

Add `Connections` as a child of the `Modules` (Applications) menu entry, in the fragment. `CnAppNav` supports one level of `children[]` (`CnAppNav.vue:6`). ADR-097 decision 1 caps the main menu at six top-level entries and asks an amendment for more. Stackiq already carries fifteen, so this change adds none.

Rejected: a top-level `Connections` entry. It would need an ADR-097 amendment that this change has no standing to make.

## Declarative versus imperative

Everything here is declarative: manifest pages, `object-list` widgets, the schema's own lifecycle and facets (ADR-031). No service, controller or route is added. Access follows the schema's existing `authorization` block (`register.json:3565` area): organisation-scoped read and public read after `publicationDate`.

## Seed data

No new schema. The demo register `lib/Settings/stackiq_mock_register.json` gains three connections (an API between two demo applications, a file transfer, and one to a national provision) so the pages show rows on a fresh install.

## Risks

- `ModuleDetail` is also edited by `landscape-application-page`. Both add rows to the same grid. The second change to land moves its widgets below the first.
- Existing connections whose `nonMunicipalProvision` points at an element still resolve; only the picker's query changes.
- A connection readable by the public group shows on the public frontend already; the pages add no new read path.
Loading
Loading