diff --git a/.claude/commands/test.md b/.claude/commands/test.md new file mode 100644 index 00000000..788dd8aa --- /dev/null +++ b/.claude/commands/test.md @@ -0,0 +1,858 @@ +--- +name: "SWC: Test" +description: Run automated tests for the GEMMA Softwarecatalogus — API tests (Postman/Newman), browser tests (persona agents), issue processing, or all +category: Testing +tags: [testing, softwarecatalogus, newman, playwright, persona] +--- + +Base directory for this skill: /home/rubenlinde/nextcloud-docker-dev/workspace/server/apps-extra/stackiq + +# Test Softwarecatalogus — Orchestrator + +Run automated tests for the GEMMA Softwarecatalogus. Supports four test modes: + +1. **API tests** — Fast, low-cost Newman/Postman tests covering ~327 `[API]`-tagged acceptance criteria via HTTP assertions +2. **Browser tests** — Thorough persona-based browser tests covering ~554 `[UI]`-tagged + ~28 `[HYBRID]`-tagged criteria +3. **Both** — Run API tests first, then browser tests for complete ~909 criteria coverage +4. **Open issues** — Issue-by-issue verification of all 72 open IGS issues, preparing GitHub reply comments with proof + +**Input**: Optional argument after `/swc:test`: +- No argument → ask which test type to run +- `api` → run all API tests (Newman) +- `api:folder-name` → run a specific API test folder (e.g., `api:02 - RBAC & Organization Scoping`) +- `browser` → run all 7 browser persona agents +- `all` → run API tests first, then browser tests +- `issues` → process all open issues (prepare reply comments with proof) +- `issues:15,65,73` → process specific issues by number +- `issues:bug` → process only issues of category Bug +- `issues:datakwaliteit` → process only Datakwaliteit issues +- `issues:tekstueel` → process only Tekstueel issues +- `issues:wens` → process only Wens issues +- Comma-separated persona names → run only those browser agents (e.g., `leverancier,gemeente,bezoeker`) +- `summary-only` → regenerate the summary report from existing results without re-running tests + +**Valid persona names** (for browser tests): `leverancier`, `gemeente`, `security-officer`, `functioneel-beheerder`, `samenwerking`, `architectuur-expert`, `bezoeker` + +**API test folders** (for `api:folder-name`): +| Folder | Issues Covered | +|--------|---------------| +| `00 - Setup` | Test data creation (users, orgs, objects) | +| `01 - Public API & Search` | #85, #144, #315, #343, #344, #345, #346, #440 | +| `02 - RBAC & Organization Scoping` | #105, #300, #307, #394, #414 | +| `03 - Object CRUD` | #6, #65, #73, #365, #382, #400, #437 | +| `04 - Data Migration & Import` | #23, #435 | +| `05 - ArchiMate & Views` | #148, #160, #393, #413 | +| `06 - User Profile & Authentication` | #266, #286, #352, #353, #396 | +| `07 - Export & Reporting` | #15 | +| `08 - Aanbod & Gebruik` | #354, #402, #418, #419, #420 | +| `09 - Data Quality & Naming` | #186, #347, #381, #406, #407, #409 | +| `10 - Glossary & Content` | #155, #332 | + +### API Test Execution + +When running API tests, use Newman CLI: + +```bash +# Install Newman if needed +which newman || npm install -g newman newman-reporter-htmlextra + +# Run setup first (creates test data) +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ + --folder "00 - Setup" --reporters cli 2>&1 | tail -20 + +# Run all test folders +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ + --reporters cli,htmlextra \ + --reporter-htmlextra-export stackiq/test-results/api/report.html 2>&1 + +# Run a specific folder +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ + --folder "{folder-name}" --reporters cli 2>&1 +``` + +**For custom environments**, pass variables: +```bash +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ + --env-var "base_url={BACKEND}" \ + --env-var "admin_user={ADMIN_USER}" \ + --env-var "admin_pass={ADMIN_PASS}" \ + --reporters cli 2>&1 +``` + +Write API results to `stackiq/test-results/api/results.md`. + +--- + +## Step -1: Test Type & Environment Configuration + +### Question 1: Test Type + +If no argument was provided (or argument is empty), ask the user using AskUserQuestion: + +**Question**: "Which tests do you want to run?" +| Option | Label | Description | +|--------|-------|-------------| +| 1 | **API tests (Recommended)** | Fast Newman/Postman tests — ~327 criteria, ~2 min, low cost. Covers all `[API]`-tagged acceptance criteria. | +| 2 | **Browser tests** | Full persona-based browser testing — ~582 criteria, ~30 min, high token cost. Covers `[UI]` and `[HYBRID]` criteria with 7 parallel agents. | +| 3 | **Both** | API tests first, then browser tests — complete ~909 criteria coverage. | +| 4 | **Open issues** | Process open IGS issues — prepare GitHub reply comments with proof. | +| 5 | **Specific API folder** | Run just one API test category (e.g., RBAC, CRUD, Search). | + +If user selects **Specific API folder**, ask which folder (show the API test folders table above). + +### Question 2: Environment + +Ask the user about the target environment using AskUserQuestion: + +**Question**: "Which environment do you want to test against?" +- **Local development (Recommended)** — Frontend: localhost:3000, Backend: localhost:8080, Admin: admin/admin +- **Custom environment** — I'll provide URLs and credentials + +If the user selects **Custom environment**, ask follow-up questions **one at a time**: +1. "What is the frontend URL?" (e.g., `https://softwarecatalogus.accept.opencatalogi.nl`) +2. "What is the backend URL?" (e.g., `https://softwarecatalogus.accept.commonground.nu`) +3. "What are the admin credentials? (format: username:password)" + +Store the resolved values as `{FRONTEND}`, `{BACKEND}`, `{ADMIN_USER}`, `{ADMIN_PASS}`. + +For **Local development**, use: +- `{FRONTEND}` = `http://localhost:3000` +- `{BACKEND}` = `http://localhost:8080` +- `{ADMIN_USER}` = `admin` +- `{ADMIN_PASS}` = `admin` + +Replace all URL references in the shared context and sub-agent prompts with these values. + +--- + +## Shared Context (inject into every sub-agent) + +All sub-agents share this context: + +### Environment + +> **LOCAL TEST ONLY** — All credentials in this file and the persona skill files are for the local development environment only. They do NOT work on production or acceptance environments. + +- **Frontend**: {FRONTEND}/ +- **Backend**: {BACKEND}/ +- **Login URL**: {FRONTEND}/login +- **Backend Admin**: {BACKEND}/ ({ADMIN_USER}:{ADMIN_PASS}) + +### OAS Documentation URLs +These auto-generated OpenAPI specs document the available API endpoints and schemas: +- **Voorzieningen register (id=2)**: {BACKEND}/index.php/apps/openregister/api/registers/2/oas +- **GEMMA/AMEFF register (id=4)**: {BACKEND}/index.php/apps/openregister/api/registers/4/oas + +Use these when testing issues related to API access, OAS documentation, or public API availability (e.g., #85, #148). + +### Login Procedure +1. Navigate to {FRONTEND}/login +2. **Before entering credentials**: Use `browser_evaluate` to run `localStorage.clear()` — this removes stale sessions from previous agents +3. Enter the persona's username and password +4. Verify the dashboard loads after login + +### Screenshot-Based Acceptance Criteria (Image Comparison) +Many issues (especially wizard text/label issues) include **reference screenshots** from PowerPoint presentations showing the EXPECTED text. When an acceptance criterion says "**Image comparison**": +1. **Fetch the reference image** from the GitHub URL in the criterion using `WebFetch` — Claude can read the image +2. **Navigate to the relevant wizard step/page** in the browser +3. **Take a screenshot** of the current UI using `browser_take_screenshot` +4. **Compare visually** — extract text from both the reference image and the live screenshot, then compare labels, titles, tooltips, field names character by character +5. Mark each text element as MATCH or MISMATCH in the results + +The authoritative source document is the PowerPoint attached to issue #329. + +### Console Log Monitoring +After EVERY page navigation and EVERY significant user action (click, form submit, wizard step), check console logs: +1. Call `browser_console_messages` with level `"error"` +2. Record ALL errors in the test results under a **Console Errors** section per issue +3. Ignore known/expected errors (list below) +4. Any unexpected console error is a finding — mark as severity MEDIUM minimum + +**Known/expected errors to ignore:** +- `Failed to load resource: the server responded with a status of 404` for favicon.ico +- `ResizeObserver loop` warnings (browser noise) +- Service worker registration failures in development mode + +### Network Performance Monitoring +After EVERY page navigation, check network performance: +1. Call `browser_network_requests` with `includeStatic: false` +2. For each API call (XHR/fetch), check the response time +3. Flag any call that takes **>500ms** as a **SLOW** call +4. Flag any call that takes **>1000ms** as a **PERFORMANCE_FAIL** +5. Record ALL slow/failed calls in the test results under a **Performance** section + +**Performance thresholds:** +| Response Time | Classification | Action | +|---------------|---------------|--------| +| 0–500ms | OK | No action | +| 500ms–1000ms | SLOW | Record in results, severity LOW | +| >1000ms | PERFORMANCE_FAIL | Record in results, severity MEDIUM | + +**Exceptions (allowed to exceed 1000ms):** +- Initial page load / first navigation after login +- OAS documentation endpoints (`/api/registers/*/oas`) — these generate specs on-the-fly +- Excel/CSV export downloads (`/api/*/export`) +- ArchiMate/AMEFF import/export operations +- Search queries with >5 active filters + +### Acceptance Criteria +Before testing each issue, read its detailed acceptance criteria in `stackiq/issues.md`. Each issue has specific, testable acceptance criteria with checkboxes. Use these to determine status: +- **PASS** = ALL acceptance criteria are met +- **PARTIAL** = Some criteria met, some not +- **FAIL** = Key criteria not met or feature is broken +- **CANNOT_TEST** = Feature not accessible or environment issue prevents testing + +### CMS Page Management +CMS pages (privacy, terms, FAQ, disclaimer) are managed in the **OpenCatalogi** Nextcloud backend app: +- **Pages URL**: {BACKEND}/index.php/apps/opencatalogi/pages# +- **Themes URL**: {BACKEND}/index.php/apps/opencatalogi/themes# +- **IMPORTANT**: The URL pattern is `/apps/opencatalogi/pages#` (NOT `/#/pages`) +- **Features**: Create, edit, delete, copy pages with title, slug, summary, description +- **Public API**: `GET /index.php/apps/opencatalogi/api/pages/{slug}` +- Relevant for issues: #397 (CMS page creation), #332 (front page), themes management + +### RBAC Reference +The authoritative RBAC rules are defined in the register JSON configuration: +- **File**: `stackiq/lib/Settings/softwarecatalogus_register.json` +- Each schema has an `"authorization"` block with `create`, `read`, `update`, `delete` rules +- Rules can be simple group names (e.g., `"public"`, `"gebruik-beheerder"`) or conditional: `{ "group": "aanbod-beheerder", "match": { "_organisation": "$organisation" } }` (only own org's data) + +**Key RBAC rules for testing:** + +| Schema | Public Read | aanbod-beheerder Read | gebruik-beheerder Read | +|--------|------------|----------------------|----------------------| +| **contactpersoon** | NO (but leverancier contact persons ARE expected to be publicly visible via publications) | Own org only | ALL | +| **module** (applicatie) | Only where `geregistreerdDoor: Leverancier` | Own org only | ALL | +| **koppeling** | NO | Own org only | ALL | +| **gebruik** | NO | Own org only | ALL | +| **organisatie** | YES (all) | ALL | ALL | +| **dienst** | YES (all) | ALL | ALL | + +**Important RBAC notes for agents:** +- **Contactpersonen of leveranciers are expected to be publicly visible.** Only gemeente/samenwerking contact persons should be hidden from public view. When testing #394, verify that ONLY leverancier contact persons are exposed — not gemeente ones. +- **Applicatielandschappen page may be visible** to aanbod-beheerder, but should only show applications belonging to their own organization. When testing #105, verify the page shows ONLY own-org data, not that the page itself is blocked. +- When unsure about RBAC, read the register JSON file directly to check the `authorization` block for the relevant schema. + +### Test Data Cleanup (MANDATORY) +After all testing is complete, agents **MUST** clean up any objects they created during wizard walkthroughs and testing. This prevents data contamination that inflates counts and creates false-positive FAIL results in subsequent test runs. + +**Cleanup procedure:** +1. Search for test objects created during the session using the publications API: + ``` + GET {BACKEND}/index.php/apps/opencatalogi/api/publications?_search=Test+Wizard&_limit=50 + GET {BACKEND}/index.php/apps/opencatalogi/api/publications?_search=Test+Koppeling&_limit=50 + ``` +2. For each object found that was created by your persona (check `@self.owner`), delete it: + ``` + DELETE {BACKEND}/index.php/apps/openregister/api/objects/{register}/{schema}/{id} + ``` + Where `register` and `schema` come from the object's `@self` metadata. +3. **Do NOT delete** objects created by the setup script (e.g., "Test Applicatie Leverancier", "Test Dienst Leverancier") — only delete wizard-created duplicates. +4. Record the cleanup in your results file under a "## Test Data Cleanup" section. + +**Objects to clean up (by naming pattern):** +- "Test Wizard *" — any wizard-created test objects +- Objects with your persona's username as `@self.owner` +- Duplicate entries visible in beheer tables that didn't exist before your test + +### Rules +- **READ ONLY on GitHub issues** — NEVER update, close, or comment on issues +- Write test results ONLY to local files in `stackiq/test-results/` +- Take screenshots as evidence where applicable +- **ALWAYS clean up test data** created during wizard walkthroughs (see Test Data Cleanup above) + +--- + +## Persona Registry + +| Key | Skill File | Persona | Role | Organization | +|-----|-----------|---------|------|--------------| +| `leverancier` | `test-leverancier.md` | Jan Pietersen | Aanbod-beheerder (Vendor) | Test Leverancier BV | +| `gemeente` | `test-gemeente.md` | Maria van der Berg | Gebruik-beheerder (Municipality) | Test Gemeente | +| `security-officer` | `test-security-officer.md` | Mark Jansen | Gebruik-beheerder (Security) | Test Gemeente | +| `functioneel-beheerder` | `test-functioneel-beheerder.md` | Peter van Dijk | Admin (Functional Manager) | (Default / admin) | +| `samenwerking` | `test-samenwerking.md` | Linda Bakker | Gebruik-beheerder (Collaboration) | Test Samenwerking | +| `architectuur-expert` | `test-architectuur-expert.md` | Dr. Sarah de Vries | VNG-raadpleger (Architecture) | (Default / VNG) | +| `bezoeker` | `test-bezoeker.md` | Anonymous Visitor | Bezoeker (Unauthenticated) | (none — public) | + +--- + +## Steps + +### Step 0: Environment Setup + +Run the setup script to create test organizations, contact persons, user accounts, and link everything together. Pass the backend URL if using a custom environment: + +```bash +# Local (default): +bash stackiq/test-setup.sh + +# Custom environment: +BACKEND_URL="{BACKEND}" ADMIN_USER="{ADMIN_USER}" ADMIN_PASS="{ADMIN_PASS}" bash stackiq/test-setup.sh +``` + +This script creates: +- 6 Nextcloud user accounts with proper group assignments +- 4 organizations (Test Leverancier BV, Test Gemeente, Test Samenwerking, Test Leverancier 2) +- 4 contact persons linked to their organizations +- Joins each user to their org and sets it as active +- Clears rate limiting / brute force protection + +The script is idempotent — it can be run multiple times safely (existing users/orgs are skipped). + +**Skip this step** if running with `summary-only` argument or if you've already run the setup script in this session. + +### Step 1: Parse Arguments + +Read the argument provided after `/swc:test`: + +- **No argument or empty**: Ask Question 1 (test type) from Step -1, then proceed accordingly +- **`api`** or **`api:folder-name`**: Run Newman API tests (see API Test Execution above) +- **`browser`**: Set `personas` to all 7 +- **`all`**: Run API tests first, then browser tests +- **`issues`**: Run open issues workflow (see Steps 7-10 below) +- **`issues:15,65,73`**: Process only the specified issue numbers +- **`issues:bug`**: Process only open Bug issues +- **`issues:datakwaliteit`**: Process only open Datakwaliteit issues +- **`issues:tekstueel`**: Process only open Tekstueel issues +- **`issues:wens`**: Process only open Wens issues +- **`summary-only`**: Skip to Step 4 (summary generation) +- **Comma-separated persona names**: Parse into list, validate each against the persona registry + +For `issues` mode, skip to **Step 7**. For all other modes, continue with Step 2. + +### Step 1a: Run API Tests (Newman) + +Run the Postman/Newman API test suite. This covers all `[API]`-tagged acceptance criteria. + +**Prerequisites**: Newman must be installed. If not found, install it: +```bash +which newman || npm install -g newman newman-reporter-htmlextra +``` + +**After Newman completes**, parse the output and write results to `stackiq/test-results/api/results.md`: +- Total requests, assertions, passes, failures +- Per-folder pass/fail counts +- Failed test names with issue references (tests are named `#NNN AC: description`) +- Link to HTML report if generated + +**If test mode is `all`**, continue to Step 2 for browser tests. Otherwise skip to Step 4. + +### Step 2: Launch Browser Sub-Agents in Parallel + +For each persona in the `personas` list, launch a Task agent **in parallel** (all in a single message with multiple Task tool calls). Use `subagent_type: "general-purpose"`. + +**Browser assignment per persona** (use these when launching sub-agents): + +| Persona | Browser | +|---------|---------| +| `leverancier` | `browser-1` | +| `gemeente` | `browser-2` | +| `security-officer` | `browser-3` | +| `functioneel-beheerder` | `browser-4` | +| `samenwerking` | `browser-5` | +| `bezoeker` | `browser-6` | +| `architectuur-expert` | `browser-7` | + +Note: All 7 browsers are used. The bezoeker uses browser-6 (does not need headed mode since it's unauthenticated public testing). + +**Sub-agent prompt template** (replace `{persona}` with the persona key and `{browser_num}` with the assigned browser number): + +``` +You are a testing agent for the GEMMA Softwarecatalogus. + +Read and follow the instructions in the skill file at: +stackiq/.claude/skills/test-{persona}.md + +This file contains your persona details, login credentials, test scope, and the list of issues to test. + +## Browser Assignment + +You MUST use browser-{browser_num} for ALL browser operations. Use tools prefixed with `mcp__browser-{browser_num}__`: +- `mcp__browser-{browser_num}__browser_navigate` to navigate +- `mcp__browser-{browser_num}__browser_click` to click +- `mcp__browser-{browser_num}__browser_snapshot` to take snapshots +- `mcp__browser-{browser_num}__browser_evaluate` to run JS +- `mcp__browser-{browser_num}__browser_fill_form` to fill forms +- `mcp__browser-{browser_num}__browser_take_screenshot` for screenshots +- etc. (all tools use the `mcp__browser-{browser_num}__` prefix) + +If your assigned browser errors or is unresponsive, try the next available browser number (skip browser-6 which is headed). + +## Additional Context + +**IMPORTANT**: The skill file uses placeholder variables. Replace them with the values below: +- `{FRONTEND}` → {FRONTEND} +- `{BACKEND}` → {BACKEND} +- `{ADMIN_USER}` → {ADMIN_USER} +- `{ADMIN_PASS}` → {ADMIN_PASS} + +### OAS Documentation URLs +When testing API-related issues (e.g., #85, #148), use these OAS documentation endpoints: +- Voorzieningen register: {BACKEND}/index.php/apps/openregister/api/registers/2/oas +- GEMMA/AMEFF register: {BACKEND}/index.php/apps/openregister/api/registers/4/oas + +### Login Procedure +**For authenticated personas (all except bezoeker):** +1. Use `mcp__browser-{browser_num}__browser_navigate` to go to {FRONTEND}/login +2. IMPORTANT: Before entering credentials, use `mcp__browser-{browser_num}__browser_evaluate` to run: localStorage.clear() + This removes stale sessions from previous tests. +3. Enter your persona's credentials (from the skill file) +4. Verify dashboard loads after login + +**For bezoeker (unauthenticated):** +1. Use `mcp__browser-{browser_num}__browser_navigate` to go to {FRONTEND}/zoeken?_page=1 +2. Use `mcp__browser-{browser_num}__browser_evaluate` to run: localStorage.clear() +3. Do NOT log in — all testing is done as an anonymous visitor + +### Organization Context +Your persona is linked to a proper organization (not Default Organisation): +- Leverancier personas (jan.pietersen) → "Test Leverancier BV" +- Gemeente personas (maria.vanderberg, mark.jansen) → "Test Gemeente" +- Samenwerking personas (linda.bakker) → "Test Samenwerking" +- Admin/VNG personas (peter.vandijk, sarah.devries) → Default Organisation (expected for admin/VNG roles) +Organization-specific features (wizards, filters, dashboards) should work for your persona's org type. + +### RBAC Reference +The authoritative RBAC rules are in `stackiq/lib/Settings/softwarecatalogus_register.json`. +Each schema has an `"authorization"` block. Key rules: +- **contactpersoon**: NOT public, but leverancier contact persons ARE expected to be publicly visible via publications. Only gemeente contact persons should be hidden. +- **module** (applicatie): Public can read only where `geregistreerdDoor: Leverancier`. aanbod-beheerder sees only own org. +- **koppeling**: NOT public. gebruik-beheerder sees all; aanbod-beheerder sees only own org. +- **gebruik**: NOT public. gebruik-beheerder sees all; aanbod-beheerder sees only own org. +- **organisatie**: Public readable by everyone. +When testing RBAC/visibility issues, read the register JSON for the exact rules. + +### CMS Pages +CMS pages (privacy, terms, FAQ, disclaimer) are managed in the OpenCatalogi Nextcloud backend: +- URL: {BACKEND}/index.php/apps/opencatalogi/pages# +- Use this when testing CMS-related issues (#397, #403, #332). + +### Wizard Execution — MANDATORY +**CRITICAL**: Authenticated agents (leverancier, gemeente) MUST execute their wizard flows BEFORE testing individual issues. The skill files contain detailed step-by-step walkthroughs. + +- **Leverancier**: Must complete Applicatie publiceren, Dienst publiceren, and Koppeling publiceren wizards (all steps) +- **Gemeente**: Must complete Applicatie toevoegen wizard (all steps) +- **Both**: Document every wizard step with screenshots, noting field values entered and navigation behavior + +The setup script also pre-creates test objects ("Test Applicatie Leverancier", "Test Dienst Leverancier", "Test Applicatie Gemeente") so beheer tables are never empty. + +### Screenshot-Based Acceptance Criteria (Image Comparison) +When an acceptance criterion in issues.md says "**Image comparison**": +1. Fetch the reference image from the GitHub URL using WebFetch +2. Navigate to the relevant page in the browser +3. Take a screenshot using browser_take_screenshot +4. Compare text from both images — labels, titles, tooltips, field names +5. Mark each text element as MATCH or MISMATCH + +### Console Log Monitoring +After EVERY page navigation and EVERY significant user action (click, form submit, wizard step): +1. Call `browser_console_messages` with level `"error"` +2. Record ALL errors in a **Console Errors** section per issue +3. Ignore these known/expected errors: + - `Failed to load resource: the server responded with a status of 404` for favicon.ico + - `ResizeObserver loop` warnings + - Service worker registration failures in development mode +4. Any unexpected console error is a finding — severity MEDIUM minimum + +### Network Performance Monitoring +After EVERY page navigation: +1. Call `browser_network_requests` with `includeStatic: false` +2. Check response times for all API calls (XHR/fetch) +3. Flag calls >500ms as **SLOW** (severity LOW) +4. Flag calls >1000ms as **PERFORMANCE_FAIL** (severity MEDIUM) + +**Exceptions (allowed to exceed 1000ms):** +- Initial page load / first navigation after login +- OAS documentation endpoints (`/api/registers/*/oas`) +- Excel/CSV export downloads +- ArchiMate/AMEFF import/export +- Search queries with >5 active filters + +At the END of your results file, include a Performance Summary: +``` +## Performance Summary +- Total API calls monitored: {N} +- OK (<500ms): {N} +- SLOW (500ms-1s): {N} +- PERFORMANCE_FAIL (>1s): {N} +- Slowest call: {URL} — {time}ms +``` + +And a Console Errors Summary: +``` +## Console Errors Summary +- Total pages/actions checked: {N} +- Pages with errors: {N} +- Total unique errors: {N} +- Most frequent error: {description} (seen {N} times) +``` + +### Testing Hints for Specific Issues +- **#399 (cross-vendor)**: Public search page → find "Test Applicatie Leverancier 2", click Versies tab, click a version. Verify no error. +- **#375 (SaaS version)**: After wizard, find the created app on `/zoeken?_page=1`, check Versies tab. +- **#105 (RBAC)**: Leverancier only — `/beheer/applicatielandschappen` should show ONLY own org's applications (data scoping, not page visibility). +- **#141 (merge)**: Functioneel-beheerder only — test via Nextcloud backend: OpenRegister → Search/Views → stackiq register → organisatie schema → three-dot menu → Merge. +- **#403 (delete dialog)**: Find a test object in beheer table, click delete, verify dialog text and usage check, click Cancel. +- **#15 (export)**: In beheer table, click Acties → Exporteren → Als CSV/Excel. Verify download. +- **#402 (Edge vs Chrome)**: **SKIP** — untestable (single Chromium engine). + +### Test Data Cleanup (MANDATORY — do this AFTER all testing) +After completing all tests, you MUST clean up any objects you created during wizard walkthroughs: + +1. Search for objects you created: + ```bash + curl -s -u {ADMIN_USER}:{ADMIN_PASS} '{BACKEND}/index.php/apps/opencatalogi/api/publications?_search=Test+Wizard&_limit=50' + ``` + Also search for any other names you used during wizard testing (e.g., your test koppeling names). + +2. For each object where `@self.owner` matches your username, delete it: + ```bash + curl -s -X DELETE -u {ADMIN_USER}:{ADMIN_PASS} '{BACKEND}/index.php/apps/openregister/api/objects/{register}/{schema}/{id}' + ``` + Use the `register`, `schema`, and `id` values from the object's `@self` metadata. + +3. **Do NOT delete** objects created by the setup script: "Test Applicatie Leverancier", "Test Dienst Leverancier", "Test Applicatie Gemeente", "Test Applicatie Leverancier 2". + +4. Add a "## Test Data Cleanup" section to your results file documenting what was deleted. + +**Why this matters:** Without cleanup, wizard re-runs create duplicate entries that cause false FAIL results for count-based issues (#300, #307). + +### Acceptance Criteria +Before testing each issue, read its acceptance criteria from stackiq/issues.md. +The file contains detailed checkboxes for each issue. Use these to determine PASS/FAIL/PARTIAL/CANNOT_TEST. + +### Output Format +Write your results to: stackiq/test-results/{persona}/results-authenticated.md + +Use this format: +- Header with persona name, date, environment, login used +- Summary table: | Issue | Title | Previous Status | Current Status | Severity | +- Per-issue sections with acceptance criteria checkboxes marked [x] or [ ] +- Console Errors subsection per issue (if any errors found) +- Performance notes per issue (if any slow calls) +- Evidence screenshots saved to the same directory +- Performance Summary section at end +- Console Errors Summary section at end + +### Rules +- NEVER update, close, or comment on GitHub issues — READ ONLY +- Write results ONLY to local files in test-results/ +- Take screenshots for evidence +- ALWAYS clean up wizard-created test data after testing (see above) +``` + +### Step 3: Wait for Completion + +Wait for all sub-agent tasks to complete. As each finishes, note its completion status. + +If any agent fails (crashes, doesn't write results), log the failure and continue with the remaining agents. + +### Step 4: Generate Summary Report + +After all tests complete (or in `summary-only` mode), read all result files and generate a summary. + +**Read these files** (if they exist): +- `stackiq/test-results/api/results.md` (API test results) +- `stackiq/test-results/leverancier/results-authenticated.md` +- `stackiq/test-results/gemeente/results-authenticated.md` +- `stackiq/test-results/security-officer/results-authenticated.md` +- `stackiq/test-results/functioneel-beheerder/results-authenticated.md` +- `stackiq/test-results/samenwerking/results-authenticated.md` +- `stackiq/test-results/architectuur-expert/results-authenticated.md` +- `stackiq/test-results/bezoeker/results-public.md` + +For each file, extract: +- Issue number, title, status (PASS/PARTIAL/FAIL/CANNOT_TEST), severity +- Agent/method that tested it (API or persona name) + +**Write the summary to**: `stackiq/test-results/README.md` + +### Summary Report Format + +```markdown +# GEMMA Softwarecatalogus — Test Results Summary + +**Date:** {today's date} +**Environment:** {FRONTEND} (Frontend), {BACKEND} (Backend) +**Method:** {method description — e.g., "API tests (Newman)" or "Browser tests (7 persona agents)" or "Combined API + Browser tests"} + +--- + +## Overall Results + +| Status | Count | Percentage | +|--------|-------|------------| +| **PASS** | {count} | {pct}% | +| **PARTIAL** | {count} | {pct}% | +| **FAIL** | {count} | {pct}% | +| **CANNOT_TEST** | {count} | {pct}% | +| **Total tested** | {count} | — | +| **Not yet tested** | {count} | — | + +--- + +## FAIL Issues (Requires Attention) + +| Issue | Title | Severity | Agent | Summary | +|-------|-------|----------|-------|---------| +| #{num} | {title} | {severity} | {agent} | {one-line summary of failure} | +... + +--- + +## CANNOT_TEST Issues (Blocked) + +| Issue | Title | Agent | Reason | +|-------|-------|-------|--------| +| #{num} | {title} | {agent} | {why it couldn't be tested} | +... + +--- + +## Results by Agent + +### 1. Leverancier — Jan Pietersen +| PASS | PARTIAL | FAIL | CANNOT_TEST | +|------|---------|------|-------------| +| {n} | {n} | {n} | {n} | + +Key findings: {2-3 bullet points} + +### 2. Gemeente — Maria van der Berg +...{repeat for all 7 agents, including Bezoeker — Anonymous Visitor} + +--- + +## Critical Findings + +{List the most important FAIL issues with details — particularly security, privacy, and data integrity issues} + +--- + +## Improvements Since Last Run + +| Issue | Title | Previous | Current | Agent | +|-------|-------|----------|---------|-------| +{issues that improved} + +--- + +## Regressions + +| Issue | Title | Previous | Current | Agent | +|-------|-------|----------|---------|-------| +{issues that got worse} + +--- + +## Performance Overview + +### Aggregate Performance +| Agent | Total Calls | OK (<500ms) | SLOW (500ms-1s) | FAIL (>1s) | Slowest | +|-------|-------------|-------------|-----------------|------------|---------| +| Leverancier | {n} | {n} | {n} | {n} | {url} ({ms}ms) | +| Gemeente | {n} | {n} | {n} | {n} | {url} ({ms}ms) | +...{repeat for all agents} + +### Slowest Endpoints (top 10) +| URL | Time | Agent | Page/Action | +|-----|------|-------|-------------| +| {url} | {ms}ms | {agent} | {context} | +... + +--- + +## Console Errors Overview + +### Aggregate Console Errors +| Agent | Pages Checked | Pages with Errors | Unique Errors | +|-------|--------------|-------------------|---------------| +| Leverancier | {n} | {n} | {n} | +...{repeat for all agents} + +### Most Frequent Errors +| Error | Occurrences | Agents | Severity | +|-------|-------------|--------|----------| +| {error description} | {n} | {agents} | {severity} | +... + +--- + +## Environment Limitations + +{List factors that prevented testing or affected results} + +--- + +## Recommendations + +### Immediate (Security) +{numbered list} + +### High Priority +{numbered list} + +### Before Next Test Run +{numbered list} +``` + +### Step 5: Report to User + +After writing the summary, display a concise overview to the user: +- Total issues tested +- PASS/FAIL/PARTIAL/CANNOT_TEST counts +- Top 3 critical findings +- Link to the full report: `stackiq/test-results/README.md` + +### Step 6: Backlog Suggestions + +After presenting the report, review the test findings for **suggestions and improvements** that are NOT existing GitHub issues but could be valuable. Present these to the user and ask if they should be added to the backlog at `stackiq/website/docs/backlog.md`. + +Examples of backlog-worthy suggestions: +- UX improvements noticed during testing (e.g., inconsistent naming, confusing navigation) +- Accessibility issues not covered by existing issues +- Performance observations that warrant investigation +- Architecture or design decisions that need user group validation +- Missing features that would improve the workflow + +**Format:** Present each suggestion as a numbered list with a short description and source (which agent/issue prompted it). Only add items the user approves. + +--- + +## Open Issues Mode (Steps 7-10) + +When the argument starts with `issues`, this workflow processes open IGS issues one-by-one or in parallel batches, preparing GitHub reply comments with proof. + +### Step 7: Build Issue List + +Read `stackiq/aanvullende-informatie.md` to get the full list of open issues with their categories. + +**Filter based on argument:** +- `issues` → all 72 open issues +- `issues:15,65,73` → only the listed issue numbers +- `issues:bug` → only the 40 open Bug issues +- `issues:datakwaliteit` → only the 11 open Datakwaliteit issues +- `issues:tekstueel` → only the 7 open Tekstueel issues +- `issues:wens` → only the 11 open Wens issues + +### Step 8: Launch Issue Agents in Parallel + +Launch up to **6 sub-agents in parallel** (using `browser-1` through `browser-5` and `browser-7`), each processing a batch of issues. Distribute issues across agents evenly. + +**Sub-agent prompt template** (replace `{issues}` with the comma-separated list, `{browser_num}` with the browser number): + +``` +You are an issue analysis agent for the GEMMA Softwarecatalogus. + +Your task is to process the following open issues and prepare a GitHub reply comment for each: {issues} + +## Workflow per issue + +For EACH issue number in your list: + +### 1. Read the issue +Read `stackiq/issues/{number}.md` for the full description, comments, and images. + +### 2. Determine the category +Look up the issue in `stackiq/aanvullende-informatie.md` to find its category (Bug, Datakwaliteit, Tekstueel, Wens, Nog te bepalen). + +### 3. Investigate based on category + +**Bug issues:** +1. Navigate to the relevant page in the browser (Frontend: {FRONTEND}, Backend: {BACKEND}) +2. Try to reproduce the problem described in the issue +3. Take screenshots showing the current state (whether fixed or still broken) +4. If it involves RBAC, check `stackiq/lib/Settings/softwarecatalogus_register.json` +5. Use the appropriate template from aanvullende-informatie.md (Template A if fixed, Template B if still broken) + +**Datakwaliteit issues:** +1. Read the relevant CSV file(s) from `stackiq/data/` +2. Search for the specific data causing the issue (orphaned references, missing fields, etc.) +3. Count affected records and provide examples +4. Use Template C from aanvullende-informatie.md + +**Tekstueel issues:** +1. Navigate to the page/wizard mentioned in the issue +2. Check if the text has been corrected +3. Take a screenshot as proof +4. Use Template D from aanvullende-informatie.md + +**Wens issues:** +1. Read `stackiq/issues.md` to confirm this is outside the original PvE scope +2. Describe current behavior +3. Use Template E from aanvullende-informatie.md + +**Nog te bepalen issues:** +1. Analyze thoroughly +2. Determine the best-fitting category +3. Follow that category's procedure + +### 4. Write the reply +Save the prepared reply as: `stackiq/reacties/{number}.md` +Include the issue title as an H1 header, the category, and the reply content using the appropriate template. + +### 5. Save screenshots +Save any screenshots to: `stackiq/reacties/screenshots/{number}-{description}.png` + +## Browser Assignment +Use browser-{browser_num} for ALL browser operations (mcp__browser-{browser_num}__* tools). +Before navigating, run localStorage.clear() via browser_evaluate. + +## Login +For issues requiring authenticated access, log in as admin ({ADMIN_USER}/{ADMIN_PASS}) at {FRONTEND}/login. +For public-facing issues, test without logging in. + +## Data Files +CSV import data is in `stackiq/data/`: +- module.csv (applicaties), koppeling.csv, organisatie.csv, contactpersoon.csv +- compliancy.csv, gebruik.csv, gebruik_2.csv, gebruik_3.csv, moduleversie.csv + +GEMMA AMEF model: `stackiq/data/GEMMA release.xml` + +## Rules — CRITICAL +- NEVER update, close, or comment on GitHub issues — this is PREPARATION ONLY +- NEVER post anything to GitHub — all output is LOCAL files for human review +- Write replies ONLY to local files in stackiq/reacties/ +- Take screenshots as evidence +- Do NOT use gh CLI to interact with issues in any way +``` + +### Step 9: Wait and Collect + +Wait for all issue agents to complete. Create the output directory if needed: + +```bash +mkdir -p stackiq/reacties/screenshots +``` + +### Step 10: Generate Issues Summary + +After all agents complete, read all files in `stackiq/reacties/` and generate a summary. + +**Write to**: `stackiq/reacties/README.md` + +```markdown +# IGS Issues — Voorbereide Reacties + +**Datum:** {today's date} +**Totaal verwerkt:** {count} + +## Overzicht + +| # | Issue | Categorie | Status Reactie | Bewijs | +|---|-------|-----------|---------------|--------| +| {num} | {title} | {cat} | Klaar / Concept | {ja/nee} | +... + +## Volgende stappen +1. Review alle reacties in `reacties/{nummer}.md` +2. Pas reacties aan waar nodig +3. Plaats reacties op GitHub issues (handmatig of via gh CLI) +``` + +Report the summary to the user with counts per category and any issues that need manual attention. diff --git a/.claude/commands/update.md b/.claude/commands/update.md new file mode 100644 index 00000000..9bfc2215 --- /dev/null +++ b/.claude/commands/update.md @@ -0,0 +1,454 @@ +--- +name: "SWC: Update" +description: Sync GitHub issues from VNG-Realisatie/Softwarecatalogus, auto-generate acceptance criteria, and update test infrastructure +category: Testing +tags: [testing, softwarecatalogus, sync, issues, acceptance-criteria] +--- + +# Sync Softwarecatalogus Issues & Update Tests + +Synchronize GitHub issues from `VNG-Realisatie/Softwarecatalogus` into local files, auto-generate acceptance criteria, and update both Postman tests and browser test agent skill files. + +**Target repo**: `VNG-Realisatie/Softwarecatalogus` +**Local directory**: `stackiq/` + +**Input**: Optional argument after `/swc:update`: +- No argument → incremental sync (changes since last run) +- `--force` → ignore .last-update, refetch all open issues +- `--dry-run` → show what would change without writing any files +- `--issues 430,442,445` → sync only specific issue numbers + +--- + +## Phase 1: Detect Changes + +### Step 1: Read last-update timestamp + +Read `stackiq/.last-update`. This file contains a single ISO 8601 timestamp (e.g., `2026-03-04T12:00:00Z`). + +- If the file **exists**: use its content as `SINCE_TIMESTAMP` +- If the file **does not exist**: first run. Set `SINCE_TIMESTAMP` to empty (fetch ALL open issues) +- If `--force` was passed: ignore the file, set `SINCE_TIMESTAMP` to empty + +### Step 2: Fetch changed issues from GitHub + +Use the `gh` CLI. **Always use `--repo VNG-Realisatie/Softwarecatalogus`**. + +**Incremental sync** (SINCE_TIMESTAMP is set): +```bash +gh issue list --repo VNG-Realisatie/Softwarecatalogus \ + --state all \ + --json number,title,labels,state,updatedAt \ + --limit 500 \ + --search "updated:>SINCE_TIMESTAMP" +``` + +**First run / force** (SINCE_TIMESTAMP is empty): +```bash +gh issue list --repo VNG-Realisatie/Softwarecatalogus \ + --state all \ + --json number,title,labels,state,updatedAt \ + --limit 500 +``` + +**Specific issues** (`--issues` flag): +Skip the list query. Fetch each specified issue individually in Step 4. + +**Rate limit handling**: If the command fails or returns truncated results, retry with `--limit 100` and paginate. + +### Step 3: Classify each issue + +For each issue in the result set: + +- **NEW**: No file exists at `stackiq/issues/{number}.md` +- **UPDATED**: File exists AND GitHub `updatedAt` is after `SINCE_TIMESTAMP` +- **CLOSED**: Issue `state` is `"closed"` +- **UNCHANGED**: File exists AND not updated since last sync → skip + +Build three lists: `new_issues`, `updated_issues`, `closed_issues`. + +**If `--dry-run`**: Print the classification summary and STOP. Do not write any files. + +--- + +## Phase 2: Update Individual Issue Files + +### Step 4: Fetch full issue data + +For each issue in `new_issues` + `updated_issues`: +```bash +gh issue view {NUMBER} --repo VNG-Realisatie/Softwarecatalogus \ + --json number,title,state,labels,author,createdAt,body,comments +``` + +### Step 5: Write individual issue files + +Write/overwrite `stackiq/issues/{number}.md` using the **established format**: + +```markdown +# #{number} — {title} + +**Status:** {OPEN|CLOSED} | **Labels:** {comma-separated label names} +**Auteur:** @{author.login} | **Datum:** {createdAt as YYYY-MM-DD} +**Link:** https://github.com/VNG-Realisatie/Softwarecatalogus/issues/{number} + +--- + +## Beschrijving + +{issue body — preserve markdown, images, and links as-is} + +--- + +## Reacties ({comment count}) + +### Reactie 1 — @{comment.author.login} ({comment.createdAt as YYYY-MM-DD}) + +{comment body — preserve markdown, images as-is} + +--- + +### Reactie 2 — @{author} ({date}) +... +``` + +**Formatting rules** (match existing files in `stackiq/issues/`): +- Title uses `# #{number} — {title}` (em-dash `—`, not hyphen) +- Status is UPPERCASE: `OPEN` or `CLOSED` +- Preserve HTML image tags from GitHub as-is (don't convert to markdown) +- Include ALL comments, including bot comments + +--- + +## Phase 3: Update issues.md Master File + +### Step 6: Read and parse current issues.md + +Read `stackiq/issues.md`. Understand its structure: +- **Header** (first ~45 lines): date, summary counts, test type legend, recently closed list, new issues list +- **IGS Issues section**: individual `### #{number}: {title}` blocks with acceptance criteria +- **Other Issues section**: table of non-testable issues +- **Distribution table**: issue counts by test step + +### Step 7: Auto-generate acceptance criteria for NEW issues + +For each new issue, analyze the title, body, labels, and comments. Generate structured acceptance criteria. + +**Tag classification — which tag to use:** + +| Content signals | Tag | +|----------------|-----| +| Data fields, API endpoints, CRUD operations, field values, search results, export content, RBAC/permissions, JSON response | **[API]** | +| Layout, styling, labels, button placement, wizard flow, modal appearance, dropdown options, column visibility, text content | **[UI]** | +| Feature that needs both API validation AND visual verification (e.g., "after wizard save, field appears correctly") | **[HYBRID]** | + +**Test Step assignment — based on labels and content:** + +| Label / content keyword | Test Step | +|------------------------|-----------| +| "Aanbod", applicatie wizard, module, versie | Step 7 (applicaties), 8 (diensten), 16 (standaarden) | +| "Gebruik", koppeling, applicatielandschap | Step 10 (beheer gebruik), 11 (koppeling wizard), 17 (benchmarking) | +| "Zoeken", filter, search, facet | Step 14 | +| "Organisatie", organisatiebeheer | Step 3, 6 | +| "Referentiearchitectuur", ArchiMate, AMEFF | Steps 15, 19, 22, 24 | +| "Datamigratie", import, CSV | Step 19 | +| contactpersoon, collega | Step 5 | +| account, profiel, "Mijn Account" | Step 4 or 6 | +| export, Excel, rapportage | Step 13 | +| dashboard, overzicht | Step 2 | +| admin, CMS, pages, configuratie | Step 20 | + +**Acceptance criteria format** (match existing style): + +```markdown +### #{number}: {title} + +**Labels:** {labels} +**Test Step:** Step {N} + +**Summary:** {1-2 sentence English summary of what the issue is about} + +**Acceptance Criteria:** +- [ ] [{TAG}] {Criterion 1 — specific, testable statement} +- [ ] [{TAG}] {Criterion 2} +- [ ] [{TAG}] {Criterion 3} +... + +**Key Context from Comments:** {Brief note about important context from comments, related issues, or workarounds. Include cross-references like "Related to #NNN".} + +--- +``` + +**Criteria generation guidelines:** +- Generate 3-8 criteria per issue (fewer for simple text changes, more for complex features) +- Each criterion must be independently testable (clear PASS/FAIL) +- Start with the most concrete/specific criteria +- If screenshots show expected behavior, add visual comparison criteria +- All criteria start unchecked `- [ ]` +- Cross-reference related issues in the Key Context section + +**Issue classification — IGS vs Other:** +- If the issue has labels like "question", "help wanted", "Conduction ontwikkeling", "Testbevindingen", "Verzamelissue" → add to **Other Issues** table, not IGS section +- If the issue is a testable feature/bug → add to **IGS Issues** section + +### Step 8: Insert new issues into issues.md + +Insert new issue blocks into the IGS Issues section in **numerical order** (sorted by issue number). Place each new block after the last existing issue with a lower number. + +### Step 9: Update existing issues with new requirements + +For each UPDATED issue: +1. Compare the GitHub comments against what's reflected in the existing Key Context section +2. Look for NEW comments that contain: + - New requirements ("moet ook...", "graag ook...", "additional requirement") + - Bug reports within comments + - Scope changes or clarifications +3. If found: add NEW acceptance criteria lines (unchecked `- [ ]`) to the existing issue section +4. Update the "Key Context from Comments" section +5. **NEVER change existing checkbox states** — preserve `[x]` and `[ ]` exactly as-is + +### Step 10: Update the header section + +Update these fields in the issues.md header: +- `**Date:**` → today's date +- `**Total open issues on GitHub:**` → updated count +- `**IGS issues (detailed with acceptance criteria):**` → updated count +- Recently Closed Issues list → add newly closed issue numbers +- New Issues Added list → add new issue numbers with today's date +- Issue Distribution by Test Step table → update counts + +### Step 11: Handle closed issues + +For issues that changed to CLOSED: +- Do NOT remove them from issues.md (historical record) +- Add them to the "Recently Closed Issues" list in the header +- Add `**Status: CLOSED ({date})**` after the title in their IGS section + +--- + +## Phase 4: Update Test Infrastructure + +### Step 12: Update Postman collection for new [API] criteria + +Read `stackiq/postman/softwarecatalogus-tests.json` (Postman v2.1 format). + +For each new issue with [API]-tagged criteria, determine the target folder: + +| Test Step | Postman Folder | +|-----------|---------------| +| Steps 2, 3, 4, 5, 6 | `06 - User Profile & Authentication` | +| Steps 7, 8 | `03 - Object CRUD` | +| Steps 9, 16 | `08 - Aanbod & Gebruik` | +| Steps 10, 11, 17 | `08 - Aanbod & Gebruik` | +| Step 12 | `02 - RBAC & Organization Scoping` | +| Step 13 | `07 - Export & Reporting` | +| Step 14 | `01 - Public API & Search` | +| Steps 15, 19, 22, 24 | `05 - ArchiMate & Views` (or `04 - Data Migration & Import` for import-specific) | +| Step 20 | `10 - Glossary & Content` | +| Step 21 | `09 - Data Quality & Naming` | + +For each [API] criterion, create a Postman request item: + +```json +{ + "name": "#{number} AC{N}: {short criterion description}", + "request": { + "method": "{GET|POST|PATCH|DELETE}", + "header": [ + {"key": "OCS-APIRequest", "value": "true", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} + ], + "url": { + "raw": "{{base_url}}/index.php/apps/openregister/api/objects/stackiq/{schema}", + "host": ["{{base_url}}"], + "path": ["index.php", "apps", "openregister", "api", "objects", "stackiq", "{schema}"] + }, + "auth": { + "type": "basic", + "basic": [ + {"key": "username", "value": "{{admin_user}}", "type": "string"}, + {"key": "password", "value": "{{admin_pass}}", "type": "string"} + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"#{number} AC{N}: {description}\", function() {", + " pm.response.to.have.status(200);", + " var json = pm.response.json();", + " // Add specific assertions based on the criterion", + "});", + "" + ], + "type": "text/javascript" + } + } + ] +} +``` + +**Test assertion patterns** (choose based on criterion type): +- Data presence: `pm.expect(json.results).to.be.an("array")` +- Field existence: `pm.expect(json.results[0]).to.have.property("fieldName")` +- Field value: `pm.expect(json.results[0].fieldName).to.eql("expected")` +- Field not UUID: `pm.expect(json.results[0].fieldName).to.not.match(/^[0-9a-f-]{36}$/)` +- RBAC scoping: compare result counts or check `_organisation` field +- Public access: use `"auth": {"type": "noauth"}` +- Column/field removal: `pm.expect(json.results[0]).to.not.have.property("removedField")` + +**Use `python3` for JSON manipulation** to safely read, modify, and write the collection: +```bash +python3 -c " +import json +with open('stackiq/postman/softwarecatalogus-tests.json', 'r') as f: + collection = json.load(f) +# ... add new items to the appropriate folder ... +with open('stackiq/postman/softwarecatalogus-tests.json', 'w') as f: + json.dump(collection, f, indent='\t', ensure_ascii=False) +" +``` + +Skip this step for issues that only have [UI]-tagged criteria. + +### Step 13: Update persona skill files for new [UI]/[HYBRID] criteria + +Determine which persona(s) should test each new issue: + +| Label / content | Primary persona | Skill file | +|----------------|----------------|------------| +| "Aanbod", vendor features | leverancier | `stackiq/.claude/skills/test-leverancier.md` | +| "Gebruik" (municipality) | gemeente | `stackiq/.claude/skills/test-gemeente.md` | +| "Gebruik" (collaboration) | samenwerking | `stackiq/.claude/skills/test-samenwerking.md` | +| "Zoeken" (unauthenticated) | bezoeker | `stackiq/.claude/skills/test-bezoeker.md` | +| "Referentiearchitectuur" | architectuur-expert | `stackiq/.claude/skills/test-architectuur-expert.md` | +| Security, privacy, RBAC | security-officer | `stackiq/.claude/skills/test-security-officer.md` | +| Admin, CMS, config | functioneel-beheerder | `stackiq/.claude/skills/test-functioneel-beheerder.md` | + +For each persona skill file, find the issues table (format: `| Issue | Title | ... |`) and add the new issue row in numerical order: +``` +| #{number} | {title} | Step {N} | +``` + +If the issue affects multiple personas (e.g., a search bug affects both bezoeker and gemeente), add it to ALL relevant persona files. + +Also add brief testing instructions for the new issue in the "Detailed Testing Instructions" section of the skill file, if one exists. Follow the existing pattern in each file. + +### Step 14: Update aanvullende-informatie.md + +Read `stackiq/aanvullende-informatie.md`. Update: +- The total count in the header +- Add new issues to the appropriate category section +- Note any new functional areas not previously covered + +--- + +## Phase 5: Finalize + +### Step 15: Write timestamp + +Write the current UTC time as ISO 8601 to `stackiq/.last-update`: +```bash +date -u +"%Y-%m-%dT%H:%M:%SZ" > stackiq/.last-update +``` + +### Step 16: Present summary + +Output a structured summary to the user: + +``` +## SWC Update Summary — {date} + +| Category | Count | +|----------|-------| +| New issues synced | {N} | +| Updated issues synced | {N} | +| Closed issues noted | {N} | +| New acceptance criteria added | {N} | +| New Postman API tests added | {N} | +| Persona skill files updated | {N} | + +### New Issues +| # | Title | Labels | Test Step | Tag | +|---|-------|--------|-----------|-----| +| {number} | {title} | {labels} | Step {N} | [API]/[UI]/[HYBRID] | + +### Updated Issues (new criteria added) +| # | Title | New criteria | Reason | +|---|-------|-------------|--------| +| {number} | {title} | {count} | {what changed} | + +### Closed Issues +{list of closed issue numbers and titles} + +### Files Modified +- stackiq/issues.md +- stackiq/issues/{numbers}.md +- stackiq/postman/softwarecatalogus-tests.json (if API tests added) +- stackiq/.claude/skills/test-{persona}.md (list which ones) +- stackiq/aanvullende-informatie.md +- stackiq/.last-update +``` + +### Step 17: Offer test execution + +Ask the user using the **AskUserQuestion tool**: + +**Question**: "Do you want to test the new/updated acceptance criteria?" + +| Option | Label | Description | +|--------|-------|-------------| +| 1 | **API tests** | Run Newman for the Postman folders that received new tests | +| 2 | **Browser tests** | Run affected persona agents to test new [UI]/[HYBRID] criteria | +| 3 | **Both** | API tests first, then browser tests | +| 4 | **Skip** | Don't test now — just save the updates | + +If the user chooses to test: + +**API tests**: Run Newman for only the affected folders: +```bash +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ + --folder "{affected-folder-name}" \ + --reporters cli 2>&1 +``` +Repeat for each folder that received new tests. + +**Browser tests**: Launch the affected persona agents using the same sub-agent pattern from `/swc:test`: +- For each affected persona, launch a Task agent with the sub-agent prompt template from `/swc:test` Step 2 +- BUT limit testing to only the new/updated issues (include a list of specific issue numbers in the prompt) +- Write results to `stackiq/test-results/{persona}/results-authenticated.md` + +**Both**: Run API first, then browser. + +After testing completes, if any tests FAIL, ask the user: + +**Question**: "Some new criteria failed. Do you want me to investigate and fix the issues?" + +| Option | Label | Description | +|--------|-------|-------------| +| 1 | **Yes, fix them** | I'll investigate the failures and implement fixes in the stackiq app code | +| 2 | **No, just report** | Save the test results for later review | + +If the user wants fixes: read the test results, identify the root causes, and implement code fixes in the `stackiq/` app. After fixing, re-run the affected tests to verify. + +--- + +## Rules + +### GitHub: READ ONLY — This is critical +- **NEVER** use `gh issue comment`, `gh issue close`, `gh issue edit`, or any write command +- **NEVER** post comments, update labels, change state, or modify GitHub issues in any way +- **ONLY** use `gh issue list` (to discover) and `gh issue view` (to read) — nothing else +- **NEVER** push changes to any remote repository +- All output goes to LOCAL files in `stackiq/` only + +### Other rules +- All file writes go to `stackiq/` only — NEVER write to `Softwarecatalogus/` +- Preserve existing acceptance criteria checkbox states (`[x]` and `[ ]`) +- Use `python3` for Postman JSON manipulation (not manual text editing) +- When in doubt about tag classification, default to `[HYBRID]` +- When in doubt about persona assignment, assign to `functioneel-beheerder` (broadest scope) diff --git a/.claude/openspec/architecture/adr-001-data-layer.md b/.claude/openspec/architecture/adr-001-data-layer.md new file mode 100644 index 00000000..7d451b92 --- /dev/null +++ b/.claude/openspec/architecture/adr-001-data-layer.md @@ -0,0 +1,223 @@ +- ALL domain data → OpenRegister objects. NO custom Entity/Mapper for domain data. +- App config → `IAppConfig`. NOT OpenRegister. +- Cross-entity references: OpenRegister relations (register+schema+objectId). NO foreign keys. + MUST NOT store foreign keys or embed full objects. + +### Schema standards + +- Schemas: PascalCase, schema.org vocabulary, explicit types + required flags + description field. +- MUST NOT invent custom property names when a schema.org equivalent exists. +- Contact schemas MUST align with vCard properties (fn, email, tel, adr). +- Dutch government fields SHOULD use a mapping layer translating between international standards + and Dutch specs — do not hardcode Dutch field names as primary. +- Schema changes that remove or rename properties are BREAKING. Adding optional properties is non-breaking. + +### Register templates + +- Location: `lib/Settings/{app}_register.json` (OpenAPI 3.0 + `x-openregister` extensions). +- Three template categories: + - **App configuration** — define data models (schemas/registers/views/mappings). + Mark with `x-openregister.type: "application"`. + - **Mock data** — fictional but realistic seed data for dev/test. + Mark with `x-openregister.type: "mock"`. + - **Government standards** — aligned to Dutch API specs (BAG, BRP, KVK, DSO). +- Import mechanism: `ConfigurationService::importFromApp(appId, data, version, force)` → + `ImportHandler::importFromApp()`. Called from repair step or `SettingsLoadService`. +- Idempotency: re-importing with `force: false` MUST NOT create duplicates. Match by slug + using `ObjectService::searchObjects` with `_rbac: false` and `_multitenancy: false`. + Use `version_compare` for skip logic. + +### Seed data + +Apps that store data in OpenRegister are empty on first install. An empty app cannot be +meaningfully tested — there are no objects to view, search, filter, or interact with. +This blocks both automated browser testing and manual QA. The Loadable Register Template +pattern (see Register templates above) already supports seed data via `components.objects[]` +with the `@self` envelope. + +**Requirements:** + +- Every app using OpenRegister MUST include 3-5 realistic objects per schema in + `lib/Settings/{app}_register.json`. +- Use `@self` envelope: `{ "@self": { "register": ..., "schema": ..., "slug": ... }, ...properties }`. + Register/schema MUST match keys; slug is unique human-readable identifier for matching. +- Use general organisation data (municipality, consultancy, travel agency, non-profit) — + NOT context-specific. Varied, realistic field values. +- Mock data quality: real Dutch street names, valid postcodes (`[1-9][0-9]{3}[A-Z]{2}`), + correct municipality/KVK codes, BSNs that pass 11-proef. Fictional but distinguishable from real. +- Cross-register consistency: BRP→BAG, KVK→BAG, DSO→BAG references must be valid. +- Loaded on install alongside schemas via same `importFromApp()` pipeline. +- MUST be idempotent — re-importing skips existing objects matched by slug. + +**In OpenSpec artifacts:** + +- **In design.md**: MUST include a Seed Data section when change introduces/modifies schemas — + define seed objects per schema with concrete field values and related items (files, notes, tasks, contacts). +- **In tasks.md**: MUST include a seed data generation task when change introduces/modifies schemas. + +**Exceptions** (no seed data required): + +- **nldesign** — has no OpenRegister schemas. +- **ExApp sidecar wrappers** (openklant, opentalk, openzaak, valtimo, n8n-nextcloud) — proxy + external services and do not use OpenRegister. +- **nextcloud-vue** — shared library, no seed data applicable. +- Changes that only modify frontend components or non-schema backend logic (e.g., settings, + permissions) do not require seed data. + +**Limitations:** OpenRegister's `ImportHandler` currently supports only flat seed objects. +Related items (files, notes, tasks, contacts) linked through the relation system are tracked +in OpenRegister's pending `seed-related-items` openspec change (see +`openregister/openspec/changes/seed-related-items/`). Until that lands, seed data is limited +to object properties defined in schemas. + +### Deduplication check + +- Before proposing new capability: search `openspec/specs/` and `openregister/lib/Service/` for overlap + with ObjectService, RegisterService, SchemaService, ConfigurationService, and shared Vue components. +- If similar capability exists: MUST reference it and explain why new code is needed rather than extending. +- Proposals duplicating existing functionality without justification MUST be rejected. +- **In design.md**: MUST include a "Reuse Analysis" section listing existing OpenRegister services leveraged. +- **In tasks.md**: MUST include a "Deduplication Check" task verifying no overlap — document findings + even if "no overlap found". + +### Schema migrations + +- Breaking schema changes → new migration in repair step. NEVER modify existing migrations. + +### OpenRegister + @conduction/nextcloud-vue — DO NOT REBUILD + +The platform provides 258+ backend methods and 69+ frontend components. Apps ONLY build +custom logic for domain-specific business rules. Everything below is provided for FREE. + +**CRUD & Data Management** (use ObjectService + CnIndexPage + CnDetailPage): +- Single & bulk create, read, update, delete — `ObjectService.saveObject()`, `deleteObject()` +- List with pagination, sorting, filtering — `ObjectService.findAll()` + `CnDataTable` +- Schema-driven forms — `CnFormDialog` (auto-generates from schema) or `CnAdvancedFormDialog` +- Detail views — `CnDetailPage` with `CnDetailGrid`, `CnDetailCard` sections +- Record merging/deduplication — `ObjectService.mergeObjects()` +- Object locking — `ObjectService.lockObject()` / `unlockObject()` + +**Import & Export** (use ImportService/ExportService + CnMassImportDialog/CnMassExportDialog): +- CSV, Excel, JSON import with intelligent field mapping — `ImportService` +- CSV, Excel, JSON export with column selection — `ExportService` +- Bulk import with validation and progress — `CnMassImportDialog` +- Filtered export with format picker — `CnMassExportDialog` +- NO custom import dialogs, parsers, upload handlers, or export controllers + +**Search & Discovery** (use IndexService + CnFilterBar + CnFacetSidebar): +- Full-text search with field weighting — `IndexService` +- Faceted navigation with counts — `FacetBuilder` + `CnFacetSidebar` +- Semantic search with embeddings — `VectorizationService` +- Hybrid search (keyword + semantic) — automatic +- Search analytics — `SearchTrailService` (popular terms, activity) +- NO custom search endpoints, query builders, or search pages + +**File Management** (use FileService + CnObjectSidebar): +- Upload (single/multipart), download, share links — `FileService` +- File tagging, public/private toggle — `FileService` +- Bulk download as ZIP — `createObjectFilesZip()` +- Text extraction from PDFs/Office docs — `TextExtractionService` +- File tab in object sidebar — `CnObjectSidebar` → `CnFilesTab` +- NO custom file upload components, file controllers, or download handlers + +**Audit & Compliance** (use AuditTrailService + CnObjectSidebar): +- Full change tracking with before/after snapshots — automatic +- Audit trail tab — `CnObjectSidebar` → `CnAuditTrailTab` +- GDPR data subject access requests — `inzageverzoek()`, `verwerkingsregister()` +- Audit export and analytics — `AuditTrailController` +- NO custom audit logging, change tracking, or compliance controllers + +**Dashboard & Analytics** (use CnDashboardPage + CnChartWidget + CnStatsBlock): +- Drag-drop widget dashboard — `CnDashboardPage` with GridStack +- KPI cards — `CnKpiGrid`, `CnStatsBlock`, `CnStatsPanel` +- Charts (line/bar/pie/donut) — `CnChartWidget` (ApexCharts) +- Data tables as widgets — `CnTableWidget` +- Editable data grids — `CnObjectDataWidget` +- NO custom dashboard layouts, chart components, or KPI cards + +**Forms & Dialogs** (use CnFormDialog + schema-driven generation): +- Auto-generated create/edit forms — `CnFormDialog` reads schema → generates fields +- JSON/metadata editing — `CnAdvancedFormDialog` with Properties/Data/Metadata tabs +- Schema editor — `CnSchemaFormDialog` +- Delete/Copy/Mass operations — `CnDeleteDialog`, `CnCopyDialog`, `CnMassDeleteDialog` +- NO custom form components, validation logic, or dialog wrappers + +**Navigation & Pagination** (use CnPagination + CnActionsBar + useListView): +- Pagination control with size selector — `CnPagination` +- Action bar (add, search, toggle views) — `CnActionsBar` +- List state management — `useListView` composable (handles search, filter, sort, page) +- Detail state management — `useDetailView` composable +- NO custom pagination logic, debounced search, or list state management + +**Authorization & RBAC** (use AuthorizationService + PropertyRbacHandler): +- Role-based access control — `AuthorizationService` +- Field-level permissions — `PropertyRbacHandler` +- Object-level restrictions — `PermissionHandler` +- Authorization audit — `AuthorizationAuditService` +- NO custom permission checks, role systems, or access control middleware + +**Webhooks & Events** (use WebhookService): +- Create, test, retry webhooks — `WebhookService` +- CloudEvents format — automatic +- Event subscriptions — selective per schema/action +- NO custom webhook controllers or event dispatchers + +**Notifications & Activity** (use NotificationService + ActivityService): +- Nextcloud notifications — `NotificationService` +- Activity feed — `ActivityService` +- Calendar events — `CalendarEventService` +- Deck/Kanban cards — `DeckCardService` + +**Store & State** (use createObjectStore + plugins): +- Object stores — `createObjectStore(name)` generates Pinia CRUD store +- Store plugins: `auditTrails`, `files`, `lifecycle`, `relations`, `search`, `selection` +- Column/field/filter generation from schema — `columnsFromSchema()`, `fieldsFromSchema()` +- NO custom Pinia stores for CRUD, Vuex, or manual API call management + +**Chat & AI** (use ChatService): +- Multi-turn conversation — `ChatService` +- RAG-based knowledge retrieval — `ContextRetrievalHandler` +- LLM response generation — `ResponseGenerationHandler` + +**Data Retention & Archival** (use ArchivalService): +- Legal hold — `LegalHoldService` +- Destruction schedules — `DestructionService` +- Retention policies — `RetentionService` + +**Semantic & Hybrid Search** (use SolrController + SettingsController): +- Semantic search via vector embeddings — `SettingsController.semanticSearch()` +- Hybrid search (keyword + semantic combined) — `SolrController.hybridSearch()` +- Vector embedding generation — `VectorizationService` +- NO custom search algorithms — configure via OpenRegister settings + +**GraphQL API** (use GraphQLController): +- Query objects across schemas via GraphQL — `GraphQLController.execute()` +- Alternative to REST for complex cross-entity queries + +**Organization / Multi-Tenancy** (use OrganisationController): +- Organization CRUD — `OrganisationController` +- Tenant-scoped data isolation — automatic via `TenantLifecycleService` +- NO custom multi-tenancy logic + +**Task & Workflow Management** (use TasksController + WorkflowEngineController): +- Task creation and tracking — `TasksController` +- Workflow orchestration — `WorkflowEngineRegistry` +- Scheduled workflows — `ScheduledWorkflowController` +- NO custom task/workflow systems + +**Text Extraction** (use FileTextController): +- Extract text from PDFs and Office docs — `TextExtractionService` +- Entity recognition (PII detection) — `EntityRecognitionHandler` +- Content anonymization — automatic + +**Timeline & Stages** (use CnTimelineStages): +- Workflow progression visualization — `CnTimelineStages` component +- Stage tracking with status colors + +### What apps SHOULD build (custom business logic only): +- External API integrations (SAP, Peppol, TenderNed, etc.) +- PDF/document generation with business-specific templates +- Workflow triggers and business rules specific to the domain +- Notification dispatch with app-specific event types +- Custom settings pages with app-specific configuration +- Background jobs for domain-specific processing diff --git a/.claude/openspec/architecture/adr-002-api.md b/.claude/openspec/architecture/adr-002-api.md new file mode 100644 index 00000000..4f956593 --- /dev/null +++ b/.claude/openspec/architecture/adr-002-api.md @@ -0,0 +1,6 @@ +- URL pattern: `/index.php/apps/{app}/api/{resource}` — lowercase plural, hyphens. +- Methods: GET=read, POST=create, PUT=update, DELETE=remove. No custom methods. +- Pagination: support `_page` + `_limit`. Response includes `total`, `page`, `pages`. +- Errors: appropriate HTTP status + `message` field. NO stack traces in responses. +- Auth: Nextcloud built-in only. NO custom login/session/token flows. +- Public endpoints: annotate `#[PublicPage]` + `#[NoCSRFRequired]`. Register CORS OPTIONS route. diff --git a/.claude/openspec/architecture/adr-003-backend.md b/.claude/openspec/architecture/adr-003-backend.md new file mode 100644 index 00000000..82abe764 --- /dev/null +++ b/.claude/openspec/architecture/adr-003-backend.md @@ -0,0 +1,14 @@ +- **Controller → Service → Mapper** (strict 3-layer). Controllers NEVER call mappers directly. +- Controllers: thin (<10 lines/method). Routing + validation + response only. +- Services: ALL business logic. Stateless — no instance state between requests. +- Mappers: DB CRUD only. No business logic. +- DI: constructor injection with `private readonly`. NO `\OC::$server` or static locators. +- Entity setters: POSITIONAL args only. `$e->setName('val')` — NEVER `$e->setName(name: 'val')`. + (`__call` passes `['name' => val]` but `setter()` uses `$args[0]`.) +- Routes: `appinfo/routes.php`. Specific routes BEFORE wildcard `{slug}` routes. +- Config: `IAppConfig` with sensitive flag for secrets. NEVER read DB directly. +- Lifecycle: schema init via repair steps (`IRepairStep`), background via job queue, events via dispatcher. +- **Spec traceability**: every class and public method MUST have `@spec` PHPDoc tag(s) linking to + the OpenSpec change that caused it: `@spec openspec/changes/{name}/tasks.md#task-N`. + Multiple `@spec` tags allowed (code touched by multiple changes). File-level `@spec` in header docblock. + This enables: code → docblock → spec traceability alongside code → git blame → commit → issue → spec. diff --git a/.claude/openspec/architecture/adr-004-frontend.md b/.claude/openspec/architecture/adr-004-frontend.md new file mode 100644 index 00000000..07737117 --- /dev/null +++ b/.claude/openspec/architecture/adr-004-frontend.md @@ -0,0 +1,255 @@ +- **Vue 2 + Pinia + @nextcloud/vue + @conduction/nextcloud-vue**. NO Vuex. Options API only. +- State: Pinia stores in `src/store/modules/`. Use `createObjectStore` for OpenRegister CRUD. +- API calls: `axios` from `@nextcloud/axios` — auto-attaches CSRF token. NEVER raw `fetch()` for mutations. + Loading state with `try/finally`. +- Translations: ALL user-visible strings via `t(appName, 'text')`. NO hardcoded strings. + Translation keys MUST be English — Dutch translations go in `l10n/nl.json`. +- CSS: ONLY Nextcloud CSS variables (`var(--color-primary-element)`, etc.). NO hardcoded colors. + NEVER reference `--nldesign-*` directly — nldesign app handles theming. +- Router: history mode, base `generateUrl('/apps/{app}/')`. Requires matching PHP routes in `routes.php`. + Deep link URL templates MUST match the router mode — use path format (`/apps/{app}/entities/{uuid}`), + NOT hash format (`/apps/{app}/#/entities/{uuid}`). +- OpenRegister dependency: settings returns `openRegisters` (bool) + `isAdmin`. + Show empty state if OR missing. NEVER use `OC.isAdmin` — get from backend. +- NEVER `window.confirm()` or `window.alert()` — use `NcDialog` or `CnFormDialog` (WCAG, theming). +- NEVER read app state from DOM (`document.getElementById`, `dataset`) — use backend API or store. +- NEVER pass server-side data (e.g. app version) via DOM attributes. Use `IInitialState::provideInitialState('key', $value)` in PHP and `loadState('appid', 'key', default)` from `@nextcloud/initial-state` in Vue. DOM data-attributes are not the Nextcloud-idiomatic pattern and break on CSP-hardened instances. +- NEVER add admin settings Vue components (e.g. `AdminRoot.vue`) to the vue-router. Admin settings are registered via `AdminSettings.php` and rendered by Nextcloud's settings framework — adding them to the router makes them publicly accessible as frontend routes, bypassing all server-side access checks. +- NEVER create manual `