diff --git a/.claude/commands/test.md b/.claude/commands/test.md index 621add25..50749714 100644 --- a/.claude/commands/test.md +++ b/.claude/commands/test.md @@ -5,7 +5,7 @@ category: Testing tags: [testing, softwarecatalogus, newman, playwright, persona] --- -Base directory for this skill: /home/rubenlinde/nextcloud-docker-dev/workspace/server/apps-extra/softwarecatalog +Base directory for this skill: /home/rubenlinde/nextcloud-docker-dev/workspace/server/apps-extra/stackiq # Test Softwarecatalogus — Orchestrator @@ -57,33 +57,33 @@ When running API tests, use Newman CLI: which newman || npm install -g newman newman-reporter-htmlextra # Run setup first (creates test data) -newman run softwarecatalog/postman/softwarecatalogus-tests.json \ - -e softwarecatalog/postman/environment-local.json \ +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 softwarecatalog/postman/softwarecatalogus-tests.json \ - -e softwarecatalog/postman/environment-local.json \ +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ --reporters cli,htmlextra \ - --reporter-htmlextra-export softwarecatalog/test-results/api/report.html 2>&1 + --reporter-htmlextra-export stackiq/test-results/api/report.html 2>&1 # Run a specific folder -newman run softwarecatalog/postman/softwarecatalogus-tests.json \ - -e softwarecatalog/postman/environment-local.json \ +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 softwarecatalog/postman/softwarecatalogus-tests.json \ - -e softwarecatalog/postman/environment-local.json \ +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 `softwarecatalog/test-results/api/results.md`. +Write API results to `stackiq/test-results/api/results.md`. --- @@ -200,7 +200,7 @@ After EVERY page navigation, check network performance: - Search queries with >5 active filters ### Acceptance Criteria -Before testing each issue, read its detailed acceptance criteria in `softwarecatalog/issues.md`. Each issue has specific, testable acceptance criteria with checkboxes. Use these to determine status: +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 @@ -217,7 +217,7 @@ CMS pages (privacy, terms, FAQ, disclaimer) are managed in the **OpenCatalogi** ### RBAC Reference The authoritative RBAC rules are defined in the register JSON configuration: -- **File**: `softwarecatalog/lib/Settings/softwarecatalogus_register.json` +- **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) @@ -261,7 +261,7 @@ After all testing is complete, agents **MUST** clean up any objects they created ### Rules - **READ ONLY on GitHub issues** — NEVER update, close, or comment on issues -- Write test results ONLY to local files in `softwarecatalog/test-results/` +- 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) @@ -289,10 +289,10 @@ Run the setup script to create test organizations, contact persons, user account ```bash # Local (default): -bash softwarecatalog/test-setup.sh +bash stackiq/test-setup.sh # Custom environment: -BACKEND_URL="{BACKEND}" ADMIN_USER="{ADMIN_USER}" ADMIN_PASS="{ADMIN_PASS}" bash softwarecatalog/test-setup.sh +BACKEND_URL="{BACKEND}" ADMIN_USER="{ADMIN_USER}" ADMIN_PASS="{ADMIN_PASS}" bash stackiq/test-setup.sh ``` This script creates: @@ -334,7 +334,7 @@ Run the Postman/Newman API test suite. This covers all `[API]`-tagged acceptance which newman || npm install -g newman newman-reporter-htmlextra ``` -**After Newman completes**, parse the output and write results to `softwarecatalog/test-results/api/results.md`: +**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`) @@ -366,7 +366,7 @@ Note: All 7 browsers are used. The bezoeker uses browser-6 (does not need headed You are a testing agent for the GEMMA Softwarecatalogus. Read and follow the instructions in the skill file at: -softwarecatalog/.claude/skills/test-{persona}.md +stackiq/.claude/skills/test-{persona}.md This file contains your persona details, login credentials, test scope, and the list of issues to test. @@ -418,7 +418,7 @@ Your persona is linked to a proper organization (not Default Organisation): Organization-specific features (wizards, filters, dashboards) should work for your persona's org type. ### RBAC Reference -The authoritative RBAC rules are in `softwarecatalog/lib/Settings/softwarecatalogus_register.json`. +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. @@ -523,11 +523,11 @@ After completing all tests, you MUST clean up any objects you created during wiz **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 softwarecatalog/issues.md. +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: softwarecatalog/test-results/{persona}/results-authenticated.md +Write your results to: stackiq/test-results/{persona}/results-authenticated.md Use this format: - Header with persona name, date, environment, login used @@ -557,20 +557,20 @@ If any agent fails (crashes, doesn't write results), log the failure and continu After all tests complete (or in `summary-only` mode), read all result files and generate a summary. **Read these files** (if they exist): -- `softwarecatalog/test-results/api/results.md` (API test results) -- `softwarecatalog/test-results/leverancier/results-authenticated.md` -- `softwarecatalog/test-results/gemeente/results-authenticated.md` -- `softwarecatalog/test-results/security-officer/results-authenticated.md` -- `softwarecatalog/test-results/functioneel-beheerder/results-authenticated.md` -- `softwarecatalog/test-results/samenwerking/results-authenticated.md` -- `softwarecatalog/test-results/architectuur-expert/results-authenticated.md` -- `softwarecatalog/test-results/bezoeker/results-public.md` +- `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**: `softwarecatalog/test-results/README.md` +**Write the summary to**: `stackiq/test-results/README.md` ### Summary Report Format @@ -707,11 +707,11 @@ 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: `softwarecatalog/test-results/README.md` +- 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 `softwarecatalog/website/docs/backlog.md`. +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) @@ -730,7 +730,7 @@ When the argument starts with `issues`, this workflow processes open IGS issues ### Step 7: Build Issue List -Read `softwarecatalog/aanvullende-informatie.md` to get the full list of open issues with their categories. +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 @@ -756,10 +756,10 @@ Your task is to process the following open issues and prepare a GitHub reply com For EACH issue number in your list: ### 1. Read the issue -Read `softwarecatalog/issues/{number}.md` for the full description, comments, and images. +Read `stackiq/issues/{number}.md` for the full description, comments, and images. ### 2. Determine the category -Look up the issue in `softwarecatalog/aanvullende-informatie.md` to find its category (Bug, Datakwaliteit, Tekstueel, Wens, Nog te bepalen). +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 @@ -767,11 +767,11 @@ Look up the issue in `softwarecatalog/aanvullende-informatie.md` to find its cat 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 `softwarecatalog/lib/Settings/softwarecatalogus_register.json` +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 `softwarecatalog/data/` +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 @@ -783,7 +783,7 @@ Look up the issue in `softwarecatalog/aanvullende-informatie.md` to find its cat 4. Use Template D from aanvullende-informatie.md **Wens issues:** -1. Read `softwarecatalog/issues.md` to confirm this is outside the original PvE scope +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 @@ -793,11 +793,11 @@ Look up the issue in `softwarecatalog/aanvullende-informatie.md` to find its cat 3. Follow that category's procedure ### 4. Write the reply -Save the prepared reply as: `softwarecatalog/reacties/{number}.md` +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: `softwarecatalog/reacties/screenshots/{number}-{description}.png` +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). @@ -808,16 +808,16 @@ For issues requiring authenticated access, log in as admin ({ADMIN_USER}/{ADMIN_ For public-facing issues, test without logging in. ## Data Files -CSV import data is in `softwarecatalog/data/`: +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: `softwarecatalog/data/GEMMA release.xml` +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 softwarecatalog/reacties/ +- 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 ``` @@ -827,14 +827,14 @@ GEMMA AMEF model: `softwarecatalog/data/GEMMA release.xml` Wait for all issue agents to complete. Create the output directory if needed: ```bash -mkdir -p softwarecatalog/reacties/screenshots +mkdir -p stackiq/reacties/screenshots ``` ### Step 10: Generate Issues Summary -After all agents complete, read all files in `softwarecatalog/reacties/` and generate a summary. +After all agents complete, read all files in `stackiq/reacties/` and generate a summary. -**Write to**: `softwarecatalog/reacties/README.md` +**Write to**: `stackiq/reacties/README.md` ```markdown # IGS Issues — Voorbereide Reacties diff --git a/.claude/commands/update.md b/.claude/commands/update.md index 4794fd8b..388d4de5 100644 --- a/.claude/commands/update.md +++ b/.claude/commands/update.md @@ -10,7 +10,7 @@ tags: [testing, softwarecatalogus, sync, issues, acceptance-criteria] 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**: `softwarecatalog/` +**Local directory**: `stackiq/` **Input**: Optional argument after `/swc:update`: - No argument → incremental sync (changes since last run) @@ -24,7 +24,7 @@ Synchronize GitHub issues from `VNG-Realisatie/Softwarecatalogus` into local fil ### Step 1: Read last-update timestamp -Read `softwarecatalog/.last-update`. This file contains a single ISO 8601 timestamp (e.g., `2026-03-04T12:00:00Z`). +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) @@ -60,7 +60,7 @@ Skip the list query. Fetch each specified issue individually in Step 4. For each issue in the result set: -- **NEW**: No file exists at `softwarecatalog/issues/{number}.md` +- **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 @@ -83,7 +83,7 @@ gh issue view {NUMBER} --repo VNG-Realisatie/Softwarecatalogus \ ### Step 5: Write individual issue files -Write/overwrite `softwarecatalog/issues/{number}.md` using the **established format**: +Write/overwrite `stackiq/issues/{number}.md` using the **established format**: ```markdown # #{number} — {title} @@ -112,7 +112,7 @@ Write/overwrite `softwarecatalog/issues/{number}.md` using the **established for ... ``` -**Formatting rules** (match existing files in `softwarecatalog/issues/`): +**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) @@ -124,7 +124,7 @@ Write/overwrite `softwarecatalog/issues/{number}.md` using the **established for ### Step 6: Read and parse current issues.md -Read `softwarecatalog/issues.md`. Understand its structure: +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 @@ -230,7 +230,7 @@ For issues that changed to CLOSED: ### Step 12: Update Postman collection for new [API] criteria -Read `softwarecatalog/postman/softwarecatalogus-tests.json` (Postman v2.1 format). +Read `stackiq/postman/softwarecatalogus-tests.json` (Postman v2.1 format). For each new issue with [API]-tagged criteria, determine the target folder: @@ -304,10 +304,10 @@ For each [API] criterion, create a Postman request item: ```bash python3 -c " import json -with open('softwarecatalog/postman/softwarecatalogus-tests.json', 'r') as f: +with open('stackiq/postman/softwarecatalogus-tests.json', 'r') as f: collection = json.load(f) # ... add new items to the appropriate folder ... -with open('softwarecatalog/postman/softwarecatalogus-tests.json', 'w') as f: +with open('stackiq/postman/softwarecatalogus-tests.json', 'w') as f: json.dump(collection, f, indent='\t', ensure_ascii=False) " ``` @@ -320,13 +320,13 @@ Determine which persona(s) should test each new issue: | Label / content | Primary persona | Skill file | |----------------|----------------|------------| -| "Aanbod", vendor features | leverancier | `softwarecatalog/.claude/skills/test-leverancier.md` | -| "Gebruik" (municipality) | gemeente | `softwarecatalog/.claude/skills/test-gemeente.md` | -| "Gebruik" (collaboration) | samenwerking | `softwarecatalog/.claude/skills/test-samenwerking.md` | -| "Zoeken" (unauthenticated) | bezoeker | `softwarecatalog/.claude/skills/test-bezoeker.md` | -| "Referentiearchitectuur" | architectuur-expert | `softwarecatalog/.claude/skills/test-architectuur-expert.md` | -| Security, privacy, RBAC | security-officer | `softwarecatalog/.claude/skills/test-security-officer.md` | -| Admin, CMS, config | functioneel-beheerder | `softwarecatalog/.claude/skills/test-functioneel-beheerder.md` | +| "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: ``` @@ -339,7 +339,7 @@ Also add brief testing instructions for the new issue in the "Detailed Testing I ### Step 14: Update aanvullende-informatie.md -Read `softwarecatalog/aanvullende-informatie.md`. Update: +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 @@ -350,9 +350,9 @@ Read `softwarecatalog/aanvullende-informatie.md`. Update: ### Step 15: Write timestamp -Write the current UTC time as ISO 8601 to `softwarecatalog/.last-update`: +Write the current UTC time as ISO 8601 to `stackiq/.last-update`: ```bash -date -u +"%Y-%m-%dT%H:%M:%SZ" > softwarecatalog/.last-update +date -u +"%Y-%m-%dT%H:%M:%SZ" > stackiq/.last-update ``` ### Step 16: Present summary @@ -385,12 +385,12 @@ Output a structured summary to the user: {list of closed issue numbers and titles} ### Files Modified -- softwarecatalog/issues.md -- softwarecatalog/issues/{numbers}.md -- softwarecatalog/postman/softwarecatalogus-tests.json (if API tests added) -- softwarecatalog/.claude/skills/test-{persona}.md (list which ones) -- softwarecatalog/aanvullende-informatie.md -- softwarecatalog/.last-update +- 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 @@ -410,8 +410,8 @@ If the user chooses to test: **API tests**: Run Newman for only the affected folders: ```bash -newman run softwarecatalog/postman/softwarecatalogus-tests.json \ - -e softwarecatalog/postman/environment-local.json \ +newman run stackiq/postman/softwarecatalogus-tests.json \ + -e stackiq/postman/environment-local.json \ --folder "{affected-folder-name}" \ --reporters cli 2>&1 ``` @@ -420,7 +420,7 @@ 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 `softwarecatalog/test-results/{persona}/results-authenticated.md` +- Write results to `stackiq/test-results/{persona}/results-authenticated.md` **Both**: Run API first, then browser. @@ -430,10 +430,10 @@ After testing completes, if any tests FAIL, ask the user: | Option | Label | Description | |--------|-------|-------------| -| 1 | **Yes, fix them** | I'll investigate the failures and implement fixes in the softwarecatalog app code | +| 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 `softwarecatalog/` app. After fixing, re-run the affected tests to verify. +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. --- @@ -444,10 +444,10 @@ If the user wants fixes: read the test results, identify the root causes, and im - **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 `softwarecatalog/` only +- All output goes to LOCAL files in `stackiq/` only ### Other rules -- All file writes go to `softwarecatalog/` only — NEVER write to `Softwarecatalogus/` +- 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]` 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 `