Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:37:37 +02:00
commit ad8ad252a9
825 files changed
+124216

No files matched your search

@@ -0,0 +1,119 @@
# Back-ups, herstel en beheer op Unraid
Deze gids beschrijft de M7-functionaliteit: back-ups maken, een back-up herstellen naar
een schone omgeving, en de operationele controles rond een Unraid-implementatie.
Geschreven voor beheerders/operators, niet voor eindgebruikers. Er is bewust geen
scherm in de webapplicatie voor back-upbeheer — dit gebeurt via de API (back-up maken/
lijst tonen) en via een aparte opdrachtregelmodus van dezelfde container-image
(herstellen), nooit via een knop in de draaiende applicatie zelf.
## Een back-up maken
```bash
curl -X POST https://hub.example/api/v1/admin/backups \
-H "X-CSRF-Token: <csrf-token-van-huidige-sessie>" -b cookies.txt
```
Dit maakt synchroon een volledige back-up: elke databasetabel (via een schema-
onafhankelijke binaire kopie, geen externe `pg_dump`-tool nodig) plus elk
content-adresseerbare bijlagebestand, met een `manifest.json` dat per bestand een
SHA-256-hash bevat. Elk bestand wordt na het schrijven opnieuw gehasht en vergeleken
vóór de back-up zichtbaar wordt — een back-up die deze controle niet doorstaat, wordt
nooit gepubliceerd en vervangt dus nooit de laatst bekende goede back-up.
```bash
curl https://hub.example/api/v1/admin/backups -b cookies.txt
```
geeft de lijst van voltooide back-ups terug (nieuwste eerst), met alleen veilige,
niet-herleidbare informatie (geen bestandspaden, geen connectiegegevens).
### Bewaarbeleid
Standaard worden de **14 nieuwste** voltooide back-ups bewaard (instelbaar via
`KnowledgeHub__Backup__RetainedCount`); de allerlaatste back-up wordt nooit verwijderd,
ook niet als dat aantal wordt overschreden. Back-ups blijven op dezelfde schijf/pool
als de applicatie staan — kopieer ze periodiek naar een onafhankelijke opslaglocatie;
een back-up op dezelfde schijf alleen is geen rampenherstel.
## Een back-up herstellen
Herstel is bewust **nooit** bereikbaar via de draaiende applicatie zelf — een draaiende
applicatie die tegelijk verkeer verwerkt én haar eigen database overschrijft, is precies
het scenario dat vermeden moet worden. In plaats daarvan draait dezelfde image in een
aparte, kortstondige opdrachtregelmodus, gericht op een **schone doelomgeving** (nooit
de productie-instantie).
### Stap 1 — doeldatabase voorbereiden (alleen migreren)
```bash
docker run --rm \
--network itworx-knowledge-hub_knowledge-hub-internal \
-e KnowledgeHub__Database__Host=<doel-db-host> \
-e KnowledgeHub__Database__Name=<doel-db> \
-e KnowledgeHub__Database__Username=<gebruiker> \
-e KnowledgeHub__Database__Password=<wachtwoord> \
itworx/knowledge-hub:local \
dotnet ItWorx.KnowledgeHub.Api.dll --migrate-only
```
Gebruik hier bewust `--migrate-only`, niet een gewone opstart: een gewone opstart
maakt als bijwerking meteen de installatierij aan (dezelfde rij die bij een eerste
installatie hoort), waardoor de doeldatabase niet meer "leeg" is tegen de tijd dat
herstel start. `--migrate-only` past uitsluitend het schema toe en sluit daarna direct
af.
### Stap 2 — herstellen
```bash
docker run --rm \
--network itworx-knowledge-hub_knowledge-hub-internal \
-v knowledge-hub-backups:/var/lib/knowledge-hub/backups:ro \
-e KnowledgeHub__Paths__Backups=/var/lib/knowledge-hub/backups \
-v <doel-bijlagen-volume>:/var/lib/knowledge-hub/attachments \
itworx/knowledge-hub:local \
dotnet ItWorx.KnowledgeHub.Api.dll \
--restore \
--backup-id=<back-up-id> \
--target-connection="Host=<doel-db-host>;Port=5432;Database=<doel-db>;Username=<gebruiker>;Password=<wachtwoord>" \
--target-attachments=/var/lib/knowledge-hub/attachments
```
De doeldatabase moet **leeg** zijn — herstel weigert expliciet als er al rijen in
staan, om nooit per ongeluk bestaande gegevens te vermengen met herstelde gegevens.
Het proces rapporteert per tabel het aantal herstelde versus verwachte rijen, het
aantal herstelde bijlagen, en sluit af met `GESLAAGD` of `MISLUKT`
(afsluitcode 0 respectievelijk 1). Een `MISLUKT`-resultaat laat expliciet zien welke
tabel niet overeenkomt — er wordt nooit stilzwijgend een onvolledig herstel als
geslaagd gerapporteerd.
## Opstartgedrag en migraties
Bij elke opstart:
1. De applicatie weigert te starten als de database al een migratie heeft toegepast
die deze build niet kent (bijvoorbeeld na een terugzetting naar een oudere
image-versie tegen een nieuwere database) — dit voorkomt dat de applicatie
onbedoeld tegen een onbekende schemavorm gaat werken.
2. Normale voorwaartse migratie (een oudere database die bijwerkt naar een nieuwere
applicatieversie) blijft het gewone pad en gebeurt automatisch bij opstart.
## Statuscontroles
- `GET /health/live` — altijd anoniem bereikbaar; faalt nooit vanwege een tijdelijk
onbereikbare database. Gebruik dit voor de container-gezondheidscontrole.
- `GET /health/ready` — anoniem bereikbaar maar geeft alleen een minimale status terug
(geen details): controleert databaseverbinding, of alle migraties zijn toegepast, en
of alle geconfigureerde opslagpaden beschrijfbaar zijn.
- `GET /api/v1/system/info` — vereist beheerdersaanmelding. Toont productversie,
schemaversie, commit en uptime — gebruik dit na een herstart/upgrade om te
bevestigen welke versie en welk schema daadwerkelijk draait.
## Logboeken
In productie schrijft de applicatie gestructureerde JSON-logregels naar stdout/stderr,
elk met een correlatie-ID zodat alle logregels van één aanvraag samen te herleiden
zijn. Wachtwoorden, ruwe tokens, cookies, autorisatie-headers, connectiereeksen en
paginainhoud worden nooit gelogd. Logrotatie gebeurt op Docker/Unraid-niveau, niet
binnen de applicatie.
@@ -0,0 +1,82 @@
# Bijlagen en LeafWiki-import
Deze gids beschrijft de M4-functionaliteit: bestanden (bijlagen) toevoegen aan
pagina's, en het overzetten van bestaande LeafWiki-inhoud. Geschreven voor
redacteuren (bijlagen) en de beheerder die een import uitvoert.
## Bijlagen toevoegen aan een pagina
1. Open een pagina en klap **Bijlagen** open, onderaan de pagina.
2. Kies een bestand via **Bijlage toevoegen**.
3. Zodra het uploaden gelukt is, verschijnt het bestand in de lijst met de
bestandsgrootte.
4. Klik op **Kopieer Markdown-verwijzing** en plak die tekst op de gewenste plaats in
de inhoud van de pagina om de bijlage zichtbaar te maken (als afbeelding bij een
afbeeldingsbestand, of als downloadlink voor andere bestanden).
### Wat wordt geweigerd
Om veiligheidsredenen worden sommige bestandstypes nooit geaccepteerd, ongeacht de
bestandsnaam:
- uitvoerbare bestanden (`.exe` en gelijkaardige programma's);
- HTML- en SVG-bestanden (deze kunnen actieve inhoud bevatten).
De toepassing herkent het echte bestandstype aan de inhoud zelf, niet aan de
bestandsextensie — een bestand met een misleidende naam (bijvoorbeeld
`factuur.pdf.exe`) wordt dus wel degelijk herkend en geweigerd.
### Standaardlimiet
Een bijlage mag standaard maximaal 100 MiB groot zijn (instelbaar door de beheerder).
## LeafWiki-import (alleen beheerder, alleen via API)
Er is nog geen importwizard in de webinterface — deze functionaliteit is voor deze
milestone alleen via de API beschikbaar, net zoals gebruikersbeheer dat aanvankelijk
was. De echte import van de eigenaar's LeafWiki-omgeving (M9) volgt de volledige
procedure in `docs/17-leafwiki-migration.md`; dit is de technische samenvatting voor
de synthetische/oefen-import die nu al mogelijk is.
1. **Plaats de bron.** Kopieer de LeafWiki-bronmap (nooit de originele, live map) naar
het gemonteerde volume op het pad ingesteld via `KnowledgeHub:Paths:Imports`
(standaard `/var/lib/knowledge-hub/imports`).
2. **Preflight (verkenning, geen wijzigingen).**
```
POST /api/v1/admin/imports/preflight
{ "sourceRelativePath": "<pad relatief aan de imports-map>", "idempotencyKey": "<unieke sleutel>" }
```
Dit levert een plan op: welke pagina's herkend worden, de voorgestelde
hiërarchie, en eventuele conflicten — zonder dat er iets in de kennisbank
verandert.
3. **Uitvoeren.**
```
POST /api/v1/admin/imports/{jobId}/execute?sourceRelativePath=<zelfde pad>
```
Bestaande pagina's worden nooit overschreven. Een eerder geïmporteerde pagina met
ongewijzigde inhoud wordt bij een herhaalde uitvoering overgeslagen, niet opnieuw
aangemaakt.
4. **Terugdraaien indien nodig.**
```
POST /api/v1/admin/imports/{jobId}/rollback
```
Verwijdert alleen de pagina's die deze import zelf aanmaakte én die nadien niet
door een gebruiker bewerkt zijn. Een pagina die na de import werd bewerkt, blijft
bestaan — bewerkte inhoud gaat nooit verloren door een rollback.
### Wat de import ondersteunt in deze milestone
- Een map met Markdown-bestanden (inclusief geneste submappen, `[[interne links]]`
en bijlagen die door de Markdown-bestanden verwezen worden).
- Een SQLite-database met het specifieke, in deze toepassing gedocumenteerde
synthetische testschema (`synthetic-sqlite-v1`) — bedoeld om de
schema-validatiemechaniek te bewijzen, niet als ondersteuning voor een echte,
onbekende LeafWiki-databaseversie. Een database met een onherkend schema levert
altijd een duidelijke blokkering op in de preflight-stap, nooit een gegokte
interpretatie van de gegevens.
@@ -0,0 +1,29 @@
# Documenten toevoegen via intake
De intake is beschikbaar zodra de beheerder de functie in de serverconfiguratie
heeft ingeschakeld. Meld u aan als redacteur en open **Intake** in het menu.
1. Vul een documenttitel in en kies het documenttype.
2. Kies een PDF-, Word-, Markdown- of tekstbestand van maximaal 100 MB.
3. Voeg bij een PDF of Word-document liefst een korte inhoudelijke toelichting toe.
Bij een klein tekst- of Markdownbestand kan de inhoud automatisch de
conceptpagina vullen.
4. Klik **Document toevoegen**. Het origineel wordt als bijlage opgeslagen en het
voorstel verschijnt in de beoordelingswachtrij.
5. Open **Bekijk voorstel**, controleer de bron, conceptinhoud en bestemming.
Keur het voorstel afzonderlijk goed en bevestig daarna **Publiceren**.
Een mislukte upload kan in hetzelfde formulier worden hervat zonder een tweede
voorstel aan te maken. Na een paginaverversing blijft een onafgewerkt voorstel in
de wachtrij zichtbaar. Het oorspronkelijke bestand blijft bewaard; de toepassing
claimt geen automatische tekstextractie uit PDF of Word en geen AI-analyse van
handmatig toegevoegde bestanden.
## Later toevoegen vanuit ChatGPT
Dezelfde beveiligde `/api/v1/ingestion-proposals`-route is bedoeld voor een
**aparte MCP-service**. Die service kan namens een beperkt serviceaccount een
voorstel en bronbestanden indienen. De browsergebruiker beoordeelt en publiceert
het resultaat in dit platform. De MCP-service, HTTPS-toegang en het bijbehorende
serviceaccount zijn aparte installatie- en verificatiestappen; het inschakelen
van de intake alleen maakt ChatGPT-bijlagen nog niet rechtstreeks beschikbaar.
@@ -0,0 +1,77 @@
# Eerste installatie, aanmelden en wachtwoordherstel
Deze gids beschrijft de M1-functionaliteit: de eenmalige eerste installatie, aanmelden
en afmelden, rolbeheer en operator-herstel van een vergeten wachtwoord. Geschreven voor
de beheerder die de installatie uitvoert.
## Eerste installatie (eenmalig)
Wanneer de toepassing voor het eerst opstart, bestaat er nog geen beheerdersaccount.
Elke bezoeker ziet dan het installatiescherm.
1. **Vul het installatieformulier in:** een naam voor de werkruimte, uw naam,
e-mailadres en een wachtwoord van minimaal 12 tekens.
2. Na een geslaagde installatie bent u automatisch aangemeld als beheerder en kan het
installatiescherm nooit meer opnieuw gebruikt worden.
Er is geen "installatie ongedaan maken"-knop in de toepassing. Dat is opzettelijk: een
volledige nieuwe installatie vereist een operator die de database opnieuw opzet volgens
`docs/18-backup-restore.md` (M7).
## Aanmelden en afmelden
- Meld aan met e-mailadres en wachtwoord op het aanmeldscherm.
- Na 5 mislukte pogingen wordt het account tijdelijk vergrendeld (15 minuten). De
toepassing geeft dezelfde generieke foutmelding voor een onbekend account, een
verkeerd wachtwoord én een vergrendeld account, zodat een aanvaller niet kan afleiden
welk geval van toepassing is — behalve bij een vergrendeling, die wel expliciet als
"te veel pogingen" wordt gemeld zodat een legitieme gebruiker weet dat hij moet
wachten.
- Een sessie duurt maximaal 12 uur, ongeacht activiteit.
- "Afmelden" beëindigt de sessie onmiddellijk.
## Gebruikers en rollen beheren
Alleen een beheerder kan andere gebruikers aanmaken via `POST /api/v1/admin/users` (de
webinterface hiervoor volgt in een latere milestone; de API is al volledig functioneel
en getest). Beschikbare rollen:
| Rol | Rechten |
|---|---|
| Lezer | Bekijken en zoeken van toegestane inhoud. |
| Redacteur | Lezer-rechten plus inhoud aanmaken en bewerken. |
| Beheerder | Redacteur-rechten plus gebruikers, herstelcodes, imports en systeeminstellingen. |
Er moet altijd minstens één ingeschakelde beheerder overblijven: de toepassing weigert
een wijziging die dat zou doorbreken (bijvoorbeeld de laatste beheerder degraderen of
uitschakelen).
Een gebruiker uitschakelen (`enabled: false`) maakt **bestaande** aanmeldsessies van die
gebruiker onmiddellijk ongeldig — niet pas bij een volgende aanmeldpoging.
## Wachtwoord vergeten (operator-herstel)
Er is geen e-mailprovider nodig. Een beheerder geeft een eenmalige herstelcode uit:
1. Beheerder roept `POST /api/v1/admin/users/{userId}/recovery-tokens` aan (via de
webinterface in een latere milestone, of rechtstreeks via de API met een geldige
beheerderssessie).
2. De ruwe herstelcode wordt **precies één keer** getoond, in het antwoord van die
aanroep. Ze wordt nergens anders opgeslagen — alleen een niet-omkeerbare
verificatiewaarde blijft bewaard.
3. Geef de code veilig door aan de gebruiker (bijvoorbeeld persoonlijk of via een
bestaand vertrouwd kanaal — nooit via een onbeveiligd kanaal).
4. De gebruiker dient de code en een nieuw wachtwoord in bij
`POST /api/v1/auth/recovery/redeem`.
5. De code werkt maar één keer en verloopt na 1 uur. Na een geslaagd herstel worden alle
bestaande sessies van die gebruiker ongeldig — de gebruiker moet opnieuw aanmelden
met het nieuwe wachtwoord.
## Wat een gewone gebruiker nooit kan zien
- Of een bepaald e-mailadres al dan niet een account heeft (aanmeldfoutmeldingen zijn
altijd generiek).
- Of de installatie al dan niet is voltooid, verder dan het installatiescherm tonen of
verbergen.
- Inhoud, gebruikers of instellingen van een andere werkruimte (de eerste uitgave
gebruikt één werkruimte, maar de autorisatie is daar al op voorbereid).
@@ -0,0 +1,75 @@
# Operationele runbooks — overzicht
Deze gids wijst naar de bestaande, feitelijk geïmplementeerde runbook-onderwerpen
(`docs/25-release-and-operations.md` "Operational runbooks required by 1.0") en
beschrijft de twee onderwerpen die nog geen eigen gids hadden. Waar een onderwerp niet
of nog niet volledig is geïmplementeerd, staat dat hier expliciet — nooit stilzwijgend
verondersteld.
| Onderwerp | Status | Waar |
| --- | --- | --- |
| Eerste implementatie | Geïmplementeerd en getest | `docs/23-deployment-unraid.md` "First deployment"; `docs/guides/eerste-installatie-en-aanmelden.md` |
| Eigenaarherstel (wachtwoord) | Geïmplementeerd en getest | `docs/guides/eerste-installatie-en-aanmelden.md` (herstel-token) |
| Back-up maken | Geïmplementeerd en getest | `docs/guides/back-ups-herstel-en-unraid-beheer.md` |
| Schoon herstel | Geïmplementeerd en getest | `docs/guides/back-ups-herstel-en-unraid-beheer.md` |
| Applicatie bijwerken | Geïmplementeerd en getest | `docs/23-deployment-unraid.md` "Updates"; schema-driftweigering (M7) |
| Applicatie terugzetten (rollback) | Geïmplementeerd en getest | `docs/23-deployment-unraid.md` "Rollback"; zie ook onderstaande sectie |
| PostgreSQL kleine update | Geïmplementeerd en getest | Onderstaande sectie (nieuw voor M8) |
| PostgreSQL grote upgrade (rehearsal) | **Niet uitgevoerd** | Zie "Bekende beperkingen" hieronder |
| Opslag-/integriteitsstoring | Geïmplementeerd en getest | Onderstaande sectie (nieuw voor M8) |
| Import en rollback | Geïmplementeerd en getest | `docs/guides/bijlagen-en-leafwiki-import.md` |
| Gecompromitteerd service-token | Geïmplementeerd en getest | `docs/guides/service-accounts-en-automatisering.md` (intrekken) |
| Gegevensexport | **Niet geïmplementeerd** | Zie "Bekende beperkingen" hieronder |
| LeafWiki-overstap (cutover) | Ontworpen, niet uitgevoerd tegen echte data | `docs/17-leafwiki-migration.md`; echte overstap is M9-scope |
## PostgreSQL kleine update
Kleine (minor) PostgreSQL-updates binnen dezelfde hoofdversie (18.x) zijn compatibel met
hetzelfde datavolume:
1. Bevestig een recente, geverifieerde back-up (`docs/guides/back-ups-herstel-en-unraid-beheer.md`).
2. Werk de `postgres`-image-tag bij naar de nieuwe kleine versie in `docker-compose.yml`
(bijvoorbeeld `postgres:18.5-bookworm`).
3. `docker compose pull db && docker compose up -d db` — het bestaande datavolume
(`knowledge-hub-db`) wordt hergebruikt; PostgreSQL werkt zelf bij bij het opstarten.
4. Controleer `docker compose ps` (status `healthy`) en start daarna de applicatie
opnieuw op (`docker compose up -d app`).
5. Controleer `GET /health/ready` en een geauthenticeerde `GET /api/v1/system/info`.
Nooit een grote (major) versiesprong op deze manier uitvoeren — zie hieronder.
## Opslag-/integriteitsstoring
Wanneer `GET /health/ready` onbereikbaarheid van de database of een niet-beschrijfbaar
opslagpad rapporteert (`DatabaseHealthCheck`/`StorageHealthCheck`, M7):
1. Controleer `docker compose ps` en `docker logs itworx-knowledge-hub-db-1` /
`itworx-knowledge-hub-app-1` voor de onderliggende fout.
2. Controleer of de onderliggende Unraid-schijf/-pool vol is of in fout-status staat
(buiten het bereik van de applicatie zelf — de applicatie draait immers met een
alleen-lezen hoofdbestandssysteem en kan de hostopslag niet zelf herstellen).
3. Herstel de opslag/host-laag eerst; herstart pas daarna de containers.
4. Als gegevensverlies of -corruptie wordt vermoed: **schrijf niets meer** naar de
database totdat een back-up is bevestigd. Herstel bij twijfel vanaf de laatst
geverifieerde back-up naar een schone doelomgeving
(`docs/guides/back-ups-herstel-en-unraid-beheer.md`) in plaats van de productie-
database rechtstreeks te herstellen met ad-hoc SQL
(`docs/25-release-and-operations.md` "Incident priorities": "Do not 'repair'
production data through undocumented ad hoc SQL").
## Bekende beperkingen
- **Gegevensexport is nog niet geïmplementeerd** als eindgebruikersfunctie. De enige
vorm van "export" die vandaag bestaat, is de back-up zelf
(`docs/guides/back-ups-herstel-en-unraid-beheer.md`), die het volledige datamodel
bevat maar niet bedoeld is als leesbaar exportformaat voor een individuele pagina of
project. Een gerichte exportfunctie (bijvoorbeeld Markdown-export per pagina/project)
is niet gepland vóór een toekomstige milestone met een expliciet acceptatiecriterium.
- **De PostgreSQL major-upgrade-rehearsal is niet uitgevoerd.** Dit vereist een tweede,
oudere PostgreSQL-hoofdversie plus `pg_upgrade`- of dump/restore-oefening, wat buiten
de tijdsbox van dit release-traject valt en geen acuut risico vormt zolang de
applicatie op één vaste hoofdversie (18) blijft draaien. Voer deze oefening uit
vóórdat een major-upgrade daadwerkelijk wordt gepland.
- **De echte LeafWiki-overstap** vereist de echte LeafWiki-momentopname en productie-
Unraid-omgeving van de gebruiker — een expliciete externe blokkade
(`CLAUDE.md` §3), uitgevoerd in M9 zodra beschikbaar.
@@ -0,0 +1,98 @@
# Pagina's schrijven, bewerken en beheren
Deze gids beschrijft de M2-functionaliteit: de kennisboom, pagina's aanmaken en
bewerken, sjablonen, interne links, concepten (autosave) en de prullenbak. Geschreven
voor redacteuren en beheerders die dagelijks inhoud beheren.
## De kennisboom
- De kennisboom staat links in de zijbalk en toont eerst alleen de hoofdpagina's.
Onderliggende pagina's worden pas opgehaald wanneer u een tak openklapt — ook een
boom met tienduizenden pagina's blijft zo direct bruikbaar.
- Klik op een pagina om ze te openen. Klik op het driehoekje om een tak met
subpagina's open of dicht te klappen.
- Op een smal (mobiel) scherm is de zijbalk standaard verborgen. Gebruik de
knop "Navigatie" bovenaan om de kennisboom en de knop "Nieuwe pagina" te openen; ze
sluit automatisch zodra u een pagina kiest, zodat er op mobiel altijd maar één
hoofdvenster tegelijk zichtbaar is.
## Een pagina aanmaken
1. Klik op **Nieuwe pagina** (zijbalk, of via **Subpagina maken** op een bestaande
pagina om er meteen een onderliggende pagina van te maken).
2. Vul een titel in.
3. Kies optioneel een **sjabloon**: Studienotitie, Runbook, Architectuurbeslissing
(ADR) of Probleemoplossing. Een sjabloon vult de inhoud met een bruikbare
startstructuur; u kunt die nadien vrij aanpassen. Kiest u geen sjabloon, dan schrijft
u vrije Markdown-inhoud.
4. Klik op **Pagina publiceren**.
Een nieuwe pagina is direct zichtbaar voor iedereen met leesrechten op die plaats in de
boom.
> **V2-R1-update (2026-08-03):** voegt u vóór de eerste publicatie een bijlage toe aan
> een nieuwe pagina (via de werkbalk of door een bestand in de editor te slepen), dan
> gebeurt dat nu volledig veilig: het bestand wordt meteen definitief opgeslagen en op
> de cursorpositie ingevoegd, en pas bij **Publiceren** wordt de pagina in één stap
> zichtbaar — inclusief alle al toegevoegde bijlagen. Mislukt een upload, dan blijft de
> pagina onzichtbaar en kunt u het gewoon opnieuw proberen; eerder gelukte bijlagen
> gaan niet verloren. Er bestaat dus geen tussentoestand meer met een zichtbare pagina
> en een kapotte bijlagelink. Zie `docs/adr/ADR-025-v2-r1-data-integrity-remediation.md`.
## Een pagina bewerken
1. Open de pagina en klik op **Bewerken**.
2. Pas titel, inhoud, documenttype of status aan.
3. Klik op **Wijzigingen publiceren**.
Elke publicatie verhoogt de revisie van de pagina. De vorige inhoud blijft bewaard —
volledige revisiegeschiedenis met terugzetten volgt in M3.
### Versieconflicten
Als iemand anders de pagina publiceerde terwijl u aan het bewerken was, toont de
toepassing dat expliciet in plaats van de wijziging van de ander stilzwijgend te
overschrijven: u kiest dan tussen **Gebruik gepubliceerde versie** (uw bewerkvenster
wordt bijgewerkt met de nieuwste inhoud) of **Behoud mijn versie (overschrijven)** (uw
tekst wordt alsnog gepubliceerd, bovenop de nieuwste revisie). Niets gaat verloren: uw
getypte tekst blijft zichtbaar totdat u zelf kiest.
## Concepten (automatisch opslaan)
Terwijl u schrijft, wordt uw werk enkele seconden na elke wijziging automatisch
opgeslagen als een persoonlijk concept — zichtbaar alleen voor uzelf, nooit voor andere
gebruikers, en het overschrijft nooit de gepubliceerde pagina. Sluit u de browser per
ongeluk of navigeert u weg, dan biedt de toepassing bij het opnieuw openen van de
bewerkpagina aan om dat concept terug te laden.
## Interne links
Verwijs naar een andere pagina met dubbele blokhaken en de paginatitel:
`[[Titel van de doelpagina]]`. Bij publicatie herkent de toepassing deze verwijzing en
koppelt ze aan de betreffende pagina — niet aan de tekst. Wordt de doelpagina later
hernoemd of verplaatst, dan blijft de link naar dezelfde pagina wijzen.
- Bestaat er geen pagina met die titel, of zijn er meerdere pagina's met dezelfde titel,
dan blijft de tekst gewoon zichtbaar als platte tekst (`[[Titel]]`) in plaats van een
klikbare link — er wordt nooit een kapotte of verkeerde link gemaakt.
- Gebruik `[[Titel|Zichtbare tekst]]` om een andere weergavetekst dan de paginatitel te
tonen.
## Pagina's verplaatsen, ordenen, naar de prullenbak en herstellen
- Een pagina naar de prullenbak verplaatsen kan via **Naar prullenbak** op de
paginaweergave. De pagina verdwijnt uit de kennisboom, maar wordt niet definitief
verwijderd.
- **Herstellen** haalt een pagina terug uit de prullenbak.
- Een pagina in de prullenbak kan niet bewerkt worden totdat ze hersteld is.
- Verplaatsen tussen ouders en herordenen tussen zusterpagina's gebeurt via de API
(`POST /api/v1/pages/{pageId}/move`); een sleep-en-neerzet-interactie in de zijbalk is
nog niet gebouwd en volgt in een latere verfijning.
## Toegankelijkheid
Elke actie in dit hoofdstuk — pagina's aanmaken, bewerken, publiceren, naar de
prullenbak verplaatsen, herstellen en tussen schrijf-/voorbeeldweergave wisselen — is
volledig met het toetsenbord te bedienen en voldoet aan WCAG 2.2 AA (geverifieerd met
axe-core tegen de echte, draaiende toepassing, op zowel een desktop- als een
mobiel-viewport).
@@ -0,0 +1,95 @@
# Projecten, architectuurbeslissingen en runbooks
Deze gids beschrijft de M5-functionaliteit: projectdossiers, architectuurbeslissingen
(ADR's) en runbooks, bereikbaar via **Projecten** in de hoofdnavigatie. Geschreven voor
redacteuren en beheerders.
## Projecten
Een project is geen kopie van bestaande pagina's — het is een gestructureerd dossier
dat verwijst naar pagina's, beslissingen, runbooks, updates en mijlpalen.
1. Klik op **Projecten** in de hoofdnavigatie, daarna op **Nieuw project**.
2. Vul naam, samenvatting, prioriteit en de huidige stand in. Het project start altijd
in status **Concept**.
3. Op de projectpagina ziet u in één overzicht: de huidige stand, gekoppelde pagina's,
beslissingen, mijlpalen en de meest recente updates.
### Status wijzigen
De statusknoppen tonen alleen de overgangen die op dit moment zijn toegestaan (bijvoorbeeld
**Concept → Gepland**, **Actief → Geblokkeerd**). Een project dat is **Gearchiveerd**,
is bewust een eindstation in deze versie; archiveren gebeurt altijd expliciet en is nooit
hetzelfde als verwijderen.
### Pagina's koppelen
Een pagina blijft altijd de canonieke plaats voor de inhoud. Een project koppelt er
alleen naar, met een rol (bijvoorbeeld **Architectuur**, **Beveiliging**,
**Probleemoplossing**). Een pagina ontkoppelen van een project verwijdert nooit de
pagina zelf.
### Updates
Een update is een tijdlijnitem: voortgang, een oplossing, een release, een risico, enz.
Updates worden nooit achteraf gewijzigd — een correctie voegt een nieuwe update toe die
verwijst naar de oorspronkelijke, waarna die oorspronkelijke update automatisch als
**Vervangen** wordt gemarkeerd. De verificatiestatus (**Gemeld** / **Geverifieerd** /
**Vervangen**) maakt altijd duidelijk of een claim al bevestigd is of nog niet.
### Mijlpalen
Mijlpalen zijn projectspecifiek en hebben geen verband met de milestones (M0–M9) van dit
bouwproces zelf. Wijzig de status van een mijlpaal via het keuzemenu naast de mijlpaal.
## Architectuurbeslissingen (ADR's)
Een architectuurbeslissing is een gewone pagina (documenttype
**Architectuurbeslissing**) met een extra statusveld erbovenop, zodat beslissingen
doorzoekbaar, historisch consistent en onderling te koppelen zijn.
1. Klik op het tabblad **Architectuurbeslissingen**, daarna op **Nieuwe beslissing**.
2. Elke nieuwe beslissing start als **Voorgesteld** en krijgt automatisch een
volgnummer (ADR-1, ADR-2, …), per project of, als er geen project gekoppeld is,
werkruimtebreed.
3. Een redacteur kan een voorgestelde beslissing **accepteren**, **afwijzen** of
**intrekken**.
### Een beslissing vervangen
Om een eerder geaccepteerde beslissing te vervangen: maak een nieuwe beslissing aan die
verwijst naar de oude, en accepteer de nieuwe. Zodra de nieuwe beslissing wordt
geaccepteerd, wordt de oude automatisch op **Vervangen** gezet, met een verwijzing in
beide richtingen — zo blijft de volledige lijn van beslissingen zichtbaar en navigeerbaar,
en verdwijnt de oude beslissing nooit.
De inhoud (context, overwegingen, gevolgen) bewerkt u via **Open onderliggende pagina om
inhoud te bewerken** — dat opent de gewone pagina-editor met revisiegeschiedenis, exact
zoals bij elke andere pagina.
## Runbooks
Een runbook is een menselijk uit te voeren procedure. Het platform voert zelf nooit
commando's uit — het toont en valideert alleen de structuur.
1. Klik op het tabblad **Runbooks**, daarna op **Nieuwe runbook**.
2. Kies een risiconiveau (**Laag**/**Gemiddeld**/**Hoog**/**Kritiek**) en geef aan of een
onderhoudsvenster vereist is.
3. Werk de inhoud (doel, vereisten, stappen, validatie, rollback) uit via de gewone
pagina-editor.
### Valideren
Klik op **Valideren tegen huidige revisie** nadat u de procedure hebt gecontroleerd. Dit
registreert wie de validatie deed en tegen welke inhoudsversie. Zodra de onderliggende
pagina daarna opnieuw wordt bewerkt, toont de runbook automatisch een duidelijke
waarschuwing dat de validatie verouderd is — die waarschuwing verdwijnt pas na een
nieuwe validatie tegen de bijgewerkte inhoud.
### Veiligheid
- Commando's worden altijd als platte tekst of code getoond, nooit als uitvoerbare
knop.
- Er is geen terminalkoppeling en geen automatische invulling van geheimen.
- Gevoelige waarden hoort u als plaatshouder te markeren in de tekst zelf (bijvoorbeeld
`<wachtwoord invullen>`), nooit als echte waarde.
@@ -0,0 +1,158 @@
# Service accounts en automatisering
Deze gids beschrijft scoped service accounts en tokens voor geautomatiseerde toegang
tot de API. Beheerders kunnen een tijdelijk, minimaal gemachtigd importtoken via
**Beheer** in de webapplicatie aanmaken en intrekken. De volledige levenscyclus blijft
ook via de API beschikbaar voor gecontroleerde automatisering.
## Wat is een service account?
Een service account is een niet-persoonlijke identiteit voor een geautomatiseerd
systeem (bijvoorbeeld een CI/CD-pipeline of, in de toekomst, een MCP-server) dat via de
API met het platform moet praten zonder een gewone gebruikersaanmelding te gebruiken.
- Een service account heeft één of meer **tokens**. Elk token heeft zijn eigen
**scopes** (rechten), een optionele vervaldatum en kan onafhankelijk van andere
tokens worden ingetrokken.
- Een token bewijst zichzelf met een `Authorization: Bearer <token>`-header, in plaats
van een sessiecookie.
- Scopes verruimen nooit de gewone workspace-rechten — ze beperken alleen wat een reeds
geauthenticeerde aanroep mag proberen. Een token met alleen `knowledge:search` kan
nooit een pagina aanmaken, ongeacht welke rol de aanmakende beheerder zelf heeft.
Service accounts aanmaken, tonen, tokens uitgeven en intrekken kan **alleen** door een
ingelogde beheerder (rol Eigenaar/Beheerder), via een gewone, cookie-geauthenticeerde
aanroep met CSRF-bescherming — nooit door een service account zelf. Een service account
kan dus nooit zijn eigen rechten verruimen of een nieuw token voor zichzelf aanmaken.
## Beschikbare scopes
| Scope | Betekenis |
| --- | --- |
| `knowledge:read` | Pagina's lezen |
| `knowledge:search` | Zoeken in de kennisbank |
| `pages:read` | Individuele pagina's ophalen |
| `pages:write` | Pagina's aanmaken/bewerken |
| `projects:read` | Projectdossiers en projectcontext lezen |
| `projects:write` | Projecten bewerken |
| `project-updates:append` | Een update aan een project toevoegen |
| `decisions:read` | Architectuurbeslissingen lezen |
| `decisions:write` | Architectuurbeslissingen aanmaken |
| `runbooks:read` | Runbooks lezen |
| `runbooks:write` | Runbooks aanmaken |
| `attachments:read` / `attachments:write` | Bijlagen lezen / uploaden |
| `imports:read` / `imports:execute` | Importstatus lezen / een import uitvoeren |
| `audit:read` | Audittrail lezen |
| `system:read` | Systeeminformatie lezen |
Een aanvraag met een onbekende scope wordt geweigerd (HTTP 422) — er is geen manier om
per ongeluk een niet-bestaande of verkeerd gespelde scope aan een token te koppelen.
## Een service account aanmaken
### Via de webapplicatie
1. Meld aan als beheerder en open **Beheer**.
2. Kies onder **Serviceaccounts** een van de drie vaste profielen, afhankelijk van de
taak:
- **Importserviceaccount aanmaken (pagina's en bijlagen)** — uitsluitend
`pages:read`, `pages:write`, `attachments:read` en `attachments:write`. Voor het
generieke Markdown-/bijlage-publicatietraject (`tools/knowledge-ingestion/`).
- **Automatiseringsserviceaccount aanmaken (infrastructuur, projecten,
runbooks)** — uitsluitend `infrastructure:read`, `infrastructure:write`,
`projects:read`, `projects:write`, `project-updates:append`, `runbooks:read` en
`runbooks:write`. Voor het synchroniseren van gecureerde infrastructuurfeiten
(`POST /inventory/curated-package`), het beheren van redactionele
serviceprofielen, en het publiceren van projectdossiers en runbooks.
- **Migratieserviceaccount aanmaken (LeafWiki-import)** — uitsluitend
`imports:read` en `imports:execute`. Voor een eenmalige, gecontroleerde
LeafWiki-migratie (`POST /admin/imports/preflight`/`/execute`/`/rollback`).
Deliberately gescheiden van het automatiseringsprofiel: een token voor dagelijkse
infrastructuur-/projectpublicatie mag nooit ook een migratie kunnen starten.
3. Kopieer het eenmalig getoonde token onmiddellijk naar de automatisering of een
geheimenbeheerder.
4. Trek het token na gebruik in via dezelfde beheerpagina.
Geen van beide vaste profielen dekt elke mogelijke automatisering — voor een taak die
een andere scopecombinatie nodig heeft (bijvoorbeeld alleen zoeken, of alleen
architectuurbeslissingen), gebruikt een beheerder de API hieronder met exact de
benodigde scopes, nooit ruimer dan nodig.
### Via de API
```bash
curl -X POST https://hub.example/api/v1/service-accounts \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <csrf-token-van-huidige-sessie>" \
-b cookies.txt \
-d '{"name":"CI-bot","description":"Leest en doorzoekt de kennisbank vanuit de CI-pipeline.","scopes":["knowledge:search","pages:read"]}'
```
Het antwoord bevat het **ruwe token exact één keer**:
```json
{
"serviceAccountId": "…",
"tokenId": "…",
"rawToken": "ikh_<publieke-id>_<geheim>"
}
```
Bewaar dit token onmiddellijk in een geheimenbeheerder. Het platform slaat nooit de
ruwe waarde op — alleen een cryptografische hash — en kan het dus nadien nooit meer
tonen. Is het token kwijt, dan moet een nieuw token worden uitgegeven en het oude
worden ingetrokken.
## Een service account gebruiken
```bash
curl https://hub.example/api/v1/search?q=backup \
-H "Authorization: Bearer ikh_<publieke-id>_<geheim>"
```
Een aanroep zonder de vereiste scope wordt geweigerd — de mutatie of leesactie vindt
nooit plaats, ook al is het token op zich geldig.
## Tokens roteren
Geef eerst een nieuw token uit, werk de client bij, en trek pas daarna het oude token
in. Een service account is op deze manier nooit tijdelijk onbruikbaar tijdens rotatie:
```bash
curl -X POST https://hub.example/api/v1/service-accounts/<serviceAccountId>/tokens \
-H "Content-Type: application/json" -H "X-CSRF-Token: …" -b cookies.txt \
-d '{"scopes":["knowledge:search","pages:read"]}'
```
## Een token intrekken
```bash
curl -X POST https://hub.example/api/v1/service-accounts/tokens/<tokenId>/revoke \
-H "X-CSRF-Token: …" -b cookies.txt
```
Intrekking is onmiddellijk van kracht: de eerstvolgende aanroep met dat token wordt
geweigerd, zonder wachttijd.
## Herhaalde aanroepen veilig maken (Idempotency-Key)
Een aanmaak- of toevoegaanroep (bijvoorbeeld een nieuwe pagina, beslissing, runbook of
projectupdate) accepteert een optionele `Idempotency-Key`-header. Een netwerkfout
tijdens een eerdere poging kan zo veilig opnieuw worden geprobeerd zonder het risico op
een dubbele pagina of dubbele update:
- Dezelfde sleutel met exact dezelfde aanvraaginhoud: het eerder gegeven antwoord wordt
gewoon opnieuw teruggegeven, zonder de actie nogmaals uit te voeren.
- Dezelfde sleutel met een andere aanvraaginhoud: dit wordt geweigerd (HTTP 409) — er
wordt nooit stilzwijgend iets anders uitgevoerd onder een hergebruikte sleutel.
## Projectcontext voor automatisering
`GET /api/v1/projects/{projectId}/context` geeft een compacte, begrensde momentopname
van een project (huidige stand, recente updates, actieve mijlpalen, beslissingen,
runbooks, documenten) met stabiele citaten (`kind`, `id`, `revisie`, `titel`, pad,
laatst bijgewerkt) in één aanroep — bedoeld voor een toekomstige MCP-server die snel
projectcontext nodig heeft zonder tientallen losse aanroepen. Wanneer er meer items zijn
dan het maximum, toont het antwoord expliciet `truncation.truncated: true` met per
onderdeel het weggelaten aantal — er wordt nooit stilzwijgend informatie weggelaten
zonder dat te melden.
+37
View File
@@ -0,0 +1,37 @@
# Verwijderen van een bronbatch
De API kan een expliciet geïdentificeerde externe bronbatch definitief verwijderen
wanneer die buiten de standaard importjob-rollback is gepubliceerd. Dit is uitsluitend
voor een Administrator en nooit een normale wiki-actie.
## Voorwaarden
1. Maak en herstel-test eerst een verse applicatiebackup naar een afzonderlijke
PostgreSQL-doeldatabase.
2. Stel een reviewed, secretvrije lijst met uitsluitend de eerder vastgelegde
page-ID's én hun gevalideerde SHA-256-contenthash samen. Gebruik geen titel,
pad of zoekresultaat als primaire grens. De API blokkeert elke pagina waarvan
de huidige hash niet meer overeenkomt met het batchbewijs.
3. Roep `POST /api/v1/admin/source-batches/purge/dry-run` aan met de batch-ID,
bronnaam en die lijst.
4. Stop wanneer `safe` onwaar is. Blokkerende gevallen zijn onder meer ontbrekende
of niet-gemarkeerde pagina's, aanvullende bronpagina's buiten de lijst, afgeleide
pagina's zonder betrouwbare koppeling, backlinks vanuit behouden pagina's en
project-/runbookkoppelingen.
5. Gebruik uitsluitend de ongewijzigde `confirmationHash` uit een groene dry-run in
`POST /api/v1/admin/source-batches/purge/execute`.
De execute-call draait de databaseverwijdering transactioneel uit. Attachmentblobs
worden alleen verwijderd als er na die transactie geen references meer bestaan én de
fysieke bytes opnieuw dezelfde SHA-256 opleveren. Een fout bij fysiek opruimen laat
hoogstens een retriable orphan achter; de toepassing verwijdert nooit een blob waar een
behouden database-reference nog naar wijst.
## Validatie na afloop
Controleer met een nieuwe dry-run/leesinventarisatie dat er geen bronpagina's of
technische brontags meer bestaan. Herbouw zoekprojecties door de normale
PostgreSQL-trigger op de verwijdering te laten werken; er is geen tweede zoekindex om
apart te verwijderen. Valideer behouden pagina's, gedeelde attachments, gebruikers,
projecten en runbooks apart. Maak daarna een nieuwe schone backup en test ook die
restore voordat een tijdelijke backup met verwijderde inhoud wordt opgeruimd.
@@ -0,0 +1,58 @@
# Zoeken, revisiegeschiedenis, verwijzingen en labels
Deze gids beschrijft de M3-functionaliteit: zoeken, revisiegeschiedenis bekijken en
herstellen, zien welke pagina's naar een pagina linken, labels, en het definitief
opruimen van de prullenbak. Geschreven voor redacteuren en beheerders.
## Zoeken
Klik op **Zoeken** bovenaan om de zoekpagina te openen.
- Zoeken doorzoekt titels, labels en de inhoud van pagina's. Een exacte titel of
woordgroep weegt zwaarder mee dan een los woord dat toevallig ergens voorkomt.
- Zoeken is tolerant voor kleine typfouten in een titel (bijvoorbeeld
"Trubleshootng" vindt nog steeds "Troubleshooting ...").
- Gebruik het filter **Label** om alleen pagina's met een specifiek label te tonen, of
**Documenttype** om te filteren op bijvoorbeeld enkel runbooks.
- Zoekresultaten tonen nooit pagina's die in de prullenbak staan.
- Een fragment van de gevonden tekst wordt getoond, met de gevonden term
gemarkeerd.
## Labels
- Voeg labels toe aan een pagina via het veld **Labels** in de bewerkpagina, gescheiden
door komma's (bijvoorbeeld `netwerk, archief`).
- Labels worden getoond als chips bovenaan de pagina.
- Klik in de zoekpagina op het filterveld **Label** en typ de naam van een label om
alleen pagina's met dat label te zien.
## Revisiegeschiedenis
Elke publicatie van een pagina bewaart de vorige inhoud als een onveranderlijke
revisie — niets gaat ooit verloren, ook niet wanneer u een pagina hersteld naar een
oudere versie.
1. Open een pagina en klap **Revisiegeschiedenis** open, onderaan de pagina.
2. Klik op een revisie om de inhoud van dat moment te bekijken.
3. Klik op **Deze revisie herstellen** om die inhoud opnieuw te publiceren als de
nieuwste versie. De versie die u zojuist verliet, blijft ook bewaard als revisie —
er wordt nooit geschiedenis overschreven of verwijderd.
## Pagina's die hierheen linken
Klap **Pagina's die hierheen linken** open, onderaan een pagina, om te zien welke
andere pagina's naar deze pagina verwijzen via een `[[interne link]]`. Deze lijst blijft
correct nadat de huidige pagina van titel, locatie in de boom, of URL-pad verandert.
## Prullenbak definitief opruimen (alleen beheerder)
Een pagina die naar de prullenbak is verplaatst, blijft daar minstens 30 dagen bewaard
en is binnen die periode altijd herstelbaar. Na die periode kan een beheerder de
prullenbak definitief opruimen via `POST /api/v1/admin/trash/purge` (de webinterface
hiervoor volgt in een latere milestone; de API is al volledig functioneel en getest).
- Alleen pagina's waarvan de bewaarperiode al verstreken is, worden verwijderd.
- Een pagina die nog binnen de bewaarperiode valt, blijft ongemoeid — er is geen manier
om die termijn via deze aanroep te omzeilen.
- Dit is de enige plaats in de toepassing waar inhoud definitief en onomkeerbaar
verdwijnt; de gewone webinterface heeft hiervoor geen knop.