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

+74
View File
@@ -0,0 +1,74 @@
# Product vision
## Product
**ItWorx Knowledge Hub** is a self-hosted knowledge, documentation and project
intelligence platform for one owner and trusted collaborators.
It replaces the user's current LeafWiki workflow and becomes the durable home for:
- school notes and course material;
- certification notes;
- technical reference documentation;
- infrastructure documentation;
- troubleshooting knowledge;
- operational runbooks;
- project dossiers, decisions and progress;
- imported attachments and supporting files.
## Problem
The current knowledge is primarily wiki-shaped, while project knowledge is spread across
repositories, status notes, deployment details and personal memory. A conventional wiki
can store those texts, but it does not provide enough structure to answer questions such
as:
- What is the current verified state of a project?
- Why was an architectural decision made?
- Which runbook was last validated?
- Which documents refer to an outdated system?
- What changed since a previous project milestone?
- Which content came from the legacy LeafWiki import?
The new platform must preserve the speed and durability of Markdown while making project
and operational knowledge explicit, searchable and auditable.
## Vision statement
> A private, reliable and recoverable knowledge hub that combines a fast technical wiki
> with structured project intelligence, and that can later serve external tools through
> a secure API without depending on those tools.
## Product principles
1. **Knowledge remains useful without AI.**
2. **Markdown remains portable and readable.**
3. **Structure is added where it creates real retrieval value.**
4. **User data is never silently overwritten or discarded.**
5. **Imports, revisions and backups are reversible and verifiable.**
6. **Search respects permissions at every layer.**
7. **The UI optimizes reading, writing and retrieval—not decoration.**
8. **The first production topology stays operationally simple.**
9. **External automation uses stable contracts and least privilege.**
10. **A future MCP server remains an independent service.**
## Primary success outcomes
- The owner can migrate representative LeafWiki content without source modification or
lost pages, links and attachments.
- School and project documentation can be found in seconds.
- Projects have one reliable current-state view and a traceable history.
- Accidental edits and deletions can be recovered.
- A complete backup can be restored into a clean environment.
- The system runs predictably on Unraid.
- A separate future MCP service can retrieve compact, permission-filtered context without
scraping the UI or reading the database directly.
## Product identity
- Product name: **ItWorx Knowledge Hub**
- Primary language: Dutch (`nl-BE`)
- Technical identifiers and API field names: English
- Suggested hostname: `knowledge.example.com` (replace it with an operator-controlled
HTTPS hostname when configuring the reverse proxy).
- Default local host port: `1240`
+96
View File
@@ -0,0 +1,96 @@
# Scope and non-goals
## In scope for the first complete release
### Knowledge management
- Hierarchical pages and collections.
- Markdown editing and safe rendering.
- Stable internal links.
- Tags, backlinks, favorites and recent pages.
- Templates and typed document metadata.
- Draft recovery and optimistic-concurrency conflict handling.
- Immutable revisions, comparison and restoration.
- Trash, retention and controlled purge.
- Permission-aware full-text and fuzzy title search.
- Attachments with deduplication and integrity hashes.
- Durable Markdown/project export.
### Structured project knowledge
- Project dossiers and lifecycle status.
- Current-state summary.
- Technology and external-link metadata.
- Project timeline entries.
- Milestones.
- Linked project documents.
- Architecture Decision Records.
- Runbook profiles and validation dates.
- Compact project-context assembly.
### Migration and operations
- Read-only LeafWiki discovery.
- Deterministic preflight.
- Idempotent import.
- Validation report and rollback by import.
- PostgreSQL migrations.
- Backup, retention, clean restore and integrity validation.
- Health endpoints, structured logs and audit trail.
- Docker Compose deployment on Unraid.
- Reverse-proxy documentation.
### Integration surface
- Versioned REST API.
- OpenAPI description.
- Scoped service accounts.
- Revocable one-time-display API tokens.
- Permission-filtered search, pages, projects and context.
- Controlled write endpoints with optimistic concurrency.
- Transactional outbox.
## Explicit non-goals
The first release does **not** include:
- an MCP server or MCP transport;
- an embedded chatbot or autonomous AI agent;
- automatic code execution or shell execution from runbooks;
- live repository write access;
- automatic Gitea synchronization;
- public multi-tenant SaaS hosting;
- real-time collaborative editing;
- native mobile applications;
- a marketplace or plugin runtime;
- arbitrary user-defined server-side scripts;
- Elasticsearch, Redis, Kafka, Kubernetes or microservices;
- public anonymous publishing by default;
- a full Notion-style relational database builder;
- spreadsheet, whiteboard or presentation editing;
- OCR of imported documents;
- secrets management or password-vault behavior.
## Deferred candidates
These may be considered only after release evidence and a new milestone/ADR:
- a separate MCP server;
- read-only Gitea document synchronization;
- semantic search with embeddings;
- public share links with expiry;
- additional workspaces;
- WebAuthn/passkeys;
- external identity providers;
- scheduled page review reminders;
- offline-first editing;
- document-level approval workflows.
## Scope-change rule
A new feature cannot displace a P0 acceptance criterion. It must be:
1. added to the backlog;
2. assigned a priority and milestone;
3. evaluated for security, migration and operational impact;
4. accepted through an ADR when it changes a core boundary.
+121
View File
@@ -0,0 +1,121 @@
# Non-functional requirements
## Reliability and integrity
- Published content, revisions, audit and outbox changes are transactionally consistent.
- A failed request cannot leave a partially published page.
- Attachment references cannot point to missing committed blobs without a detectable
integrity violation.
- Imports are idempotent and reversible by import ID.
- Backups are considered successful only after manifest and hash validation.
- Restore is tested into a clean target.
- Database migrations are forward-tested from every supported released schema.
## Performance targets
Targets are measured on a modest x86-64 Unraid host with SSD-backed PostgreSQL after warm
startup, using the documented synthetic dataset.
| Operation | Dataset | Target |
|---|---:|---:|
| Application shell first authenticated response | normal | p95 < 1.0 s |
| Page API read | 100,000 pages | p95 < 300 ms |
| Search first page | 100,000 pages / 1,000,000 revisions | p95 < 750 ms |
| Lazy tree child load | 10,000-node collection | p95 < 300 ms |
| Page publish without attachment | normal | p95 < 500 ms |
| Project context bundle | 10,000 project-linked records total | p95 < 800 ms |
| API health liveness | normal | p95 < 100 ms |
The UI should remain responsive under a 4× CPU slowdown profile. Performance regressions
must be measured; arbitrary timeout increases are not fixes.
## Capacity targets
Initial validated design target:
- 100,000 current pages;
- 1,000,000 page revisions;
- 10,000 projects;
- 1,000,000 project updates/relations combined;
- 100,000 attachment references;
- 500 GiB attachment storage, constrained by host capacity;
- 20 concurrent interactive sessions;
- 10 service accounts.
These are validation ceilings, not expected normal usage.
## Availability
- Single-host design; no false high-availability claim.
- Graceful shutdown and database connection draining.
- Readiness becomes false when mandatory dependencies are unavailable.
- Liveness remains independent of transient PostgreSQL unavailability.
- Background jobs use leases/idempotency to recover after restart.
- No required outbound internet dependency for normal operation.
## Security
- Private by default.
- Least privilege.
- Secure session cookies and CSRF protection.
- Strong password policy and lockout.
- Scoped, revocable service tokens.
- Authorization at use-case/query boundaries.
- Safe Markdown and attachment handling.
- Security headers.
- Sensitive-data log redaction.
- Non-root production container.
- Dependency, secret and container scans in release gate.
- Threat model maintained in `docs/26-threat-model.md`.
## Accessibility
- WCAG 2.2 AA target for implemented flows.
- Keyboard-complete operation.
- Visible focus.
- Semantic landmarks and headings.
- Dialog focus trap/return.
- Sufficient contrast.
- 200% zoom and narrow viewport without loss of function.
- Reduced-motion support.
- Automated axe coverage plus manual keyboard/screen-reader spot checks.
## Compatibility
Supported clients for release validation:
- current stable Chromium-based desktop browser;
- current stable Firefox desktop;
- representative Android Chromium viewport;
- touch and keyboard interaction.
The server supports Linux containers on x86-64. ARM64 support is desirable when the
dependency chain and CI can verify it.
## Maintainability
- Modular-monolith dependency rules enforced by tests.
- Strict nullability and TypeScript.
- Warnings as errors.
- Versioned API.
- Documented migrations and ADRs.
- One high-level verification command.
- No hidden manual production build steps.
- Current-state and evidence documents updated per milestone.
## Observability
- Structured JSON logs in production.
- Correlation/request ID.
- Health status and bounded system metrics.
- Audit trail separate from diagnostic logs.
- No user page bodies or attachment contents in normal logs.
- Background-job state visible to administrators.
## Localization and timezone
- Primary UI locale: `nl-BE`.
- Store instants in UTC.
- Render dates/times in the configured installation timezone, default
`Europe/Brussels`.
- Store locale-independent status values and translate in the UI.
+203
View File
@@ -0,0 +1,203 @@
# System architecture
## Decision summary
The first release is a **modular monolith** deployed as one application container plus
PostgreSQL.
The production application contains:
- ASP.NET Core API and authentication;
- compiled React static assets;
- application/use-case modules;
- background hosted services;
- filesystem attachment adapter;
- import/export and backup orchestration.
This topology minimizes operational overhead on Unraid while retaining internal
boundaries that can be tested and evolved.
## Context
```mermaid
flowchart LR
User[Browser user] -->|HTTPS| Proxy[Reverse proxy]
Proxy --> App[ItWorx Knowledge Hub]
ApiClient[Scoped API client] -->|HTTPS /api/v1| Proxy
App --> Db[(PostgreSQL)]
App --> Files[(Attachments / imports / backups)]
Leaf[Read-only LeafWiki snapshot] -->|Read-only import mount| App
FutureMCP[Future separate MCP server] -.->|HTTPS /api/v1| Proxy
```
The future MCP server is shown only as an external client. It is not built or deployed by
this repository.
## Production containers
```text
knowledge-hub
postgres
```
Development may run the React Vite server separately for fast refresh. Production serves
its built files through ASP.NET Core.
## Logical projects
```text
ItWorx.KnowledgeHub.Domain
Pure domain concepts and invariants
ItWorx.KnowledgeHub.Application
Use cases, authorization requirements, ports and orchestration
ItWorx.KnowledgeHub.Contracts
Stable request/response and event contracts
ItWorx.KnowledgeHub.Infrastructure
EF Core, PostgreSQL, files, hashing, import adapters, jobs
ItWorx.KnowledgeHub.Api
HTTP, auth/session transport, middleware, static SPA hosting
src/frontend
React application
tests/backend/*
tests/e2e
```
Allowed dependency direction:
```mermaid
flowchart TD
Api --> Application
Api --> Contracts
Api --> Infrastructure
Infrastructure --> Application
Infrastructure --> Domain
Infrastructure --> Contracts
Application --> Domain
Application --> Contracts
```
Forbidden:
- Domain → Application/Infrastructure/API/UI
- Application → Infrastructure/API/UI
- Contracts → Infrastructure/API/UI
- Frontend → database/filesystem
Architecture tests must enforce this.
## Application modules
Initial module boundaries:
- Identity
- Workspaces
- Knowledge
- Search
- Attachments
- Projects
- Decisions
- Runbooks
- Imports
- Exports
- Audit
- Integration
- Operations
Modules may share infrastructure composition but must not mutate each other's tables
through ad hoc DbContext access. Cross-module operations use application contracts and a
single transaction boundary where required.
## Data flow: publish page
```mermaid
sequenceDiagram
participant UI
participant API
participant App as Application
participant DB as PostgreSQL
participant Outbox
UI->>API: PUT page + If-Match
API->>App: PublishPage command
App->>DB: Load page + authorization
App->>App: Validate version, Markdown and links
App->>DB: Update current page
App->>DB: Insert immutable revision
App->>DB: Replace derived link/search records
App->>DB: Insert audit event
App->>Outbox: Insert page.updated event
DB-->>App: Commit
App-->>API: DTO + new ETag
API-->>UI: 200 OK
```
## Data flow: attachment upload
Uploads use a bounded streaming pipeline:
1. authenticate and authorize;
2. stream to a quarantined temporary file with a hard size limit;
3. calculate SHA-256;
4. inspect signature and type;
5. reject active/unsafe inline behavior;
6. atomically move to content-addressed storage;
7. commit blob/reference metadata;
8. schedule safe preview generation where supported;
9. remove temporary data on failure.
No ordinary upload is executed or automatically extracted.
## Data flow: import
1. Discover source in read-only mode.
2. Fingerprint all relevant source items.
3. Select a versioned adapter.
4. Produce a preflight plan with conflicts/ambiguities.
5. Persist the plan.
6. Execute in bounded transactional batches with import provenance.
7. Resolve stable internal relations.
8. Validate counts, tree, links and file hashes.
9. Re-fingerprint the source.
10. Mark complete only when source is unchanged and validation passes.
## Background jobs
Use database-backed jobs/leases rather than a mandatory external broker.
Initial job types:
- search projection repair;
- attachment preview;
- orphan blob cleanup;
- import execution and validation;
- export generation;
- backup creation and verification;
- retention purge;
- outbox dispatch/retention;
- integrity scan.
Jobs require idempotency, bounded retries, backoff, cancellation and administrator-visible
status.
## Configuration
Configuration precedence:
1. secure mounted secret/file where applicable;
2. environment variables;
3. `appsettings.Production.json`;
4. compiled safe defaults.
Database credentials and key material are not stored in repository configuration.
## Future evolution
Possible later extraction candidates are backup/export workers or an MCP adapter, but only
after measured pressure. The domain and API boundaries must not assume extraction is
required.
+325
View File
@@ -0,0 +1,325 @@
# Domain model
## Installation and workspace
### Installation
Represents system-wide configuration and bootstrap state.
Invariants:
- exactly one installation record;
- owner bootstrap can complete only once;
- configured schema/product version is traceable;
- primary locale and timezone are validated.
### Workspace
The first release exposes one active workspace, but content rows carry `WorkspaceId` to
make authorization boundaries explicit.
Fields:
- ID
- Name
- Slug
- CreatedAt
- Settings version
- ArchivedAt (future-safe)
### Membership
Connects an interactive user to a workspace role:
- Reader
- Editor
- Administrator
Invariants:
- at least one enabled administrator exists;
- disabled users cannot act through existing sessions;
- service accounts are not memberships and receive no implicit role.
## Knowledge
### Page
The current published state and tree location.
Core fields:
- ID / WorkspaceId
- ParentPageId
- SortKey
- Title
- Slug
- Markdown
- DocumentType
- DocumentStatus
- CurrentRevisionNumber
- ConcurrencyVersion
- CreatedBy / UpdatedBy
- CreatedAt / UpdatedAt / PublishedAt
- TrashedAt / PurgeAfter
Invariants:
- title is non-empty and bounded;
- parent is in the same workspace;
- a page cannot parent itself or create a cycle;
- current revision number increases monotonically;
- ordinary changes cannot mutate immutable revisions;
- trashed pages are excluded from normal navigation/search.
### PageRevision
Immutable snapshot of semantically relevant page state:
- Page identity and revision number
- Title, slug and Markdown
- Parent/order snapshot
- Document type/status
- typed metadata snapshot
- author/source/change summary
- created timestamp
- content hash
Restoring creates a new revision.
### Draft
User-private mutable work based on a specific page revision:
- User / Page or draft key
- Base revision/concurrency version
- Markdown and metadata
- UpdatedAt / ExpiresAt
A draft never silently publishes.
### PageLink
Derived relation from an authored internal link:
- SourcePageId
- target stable PageId when resolved
- original target text
- display label
- occurrence/anchor information
- resolution state
Derived records can be rebuilt from revisions.
### PageRelation
Explicit semantic relationship:
- Source / Target
- relation type
- label
- creator/time
### Tag and PageTag
Tags are case-insensitively unique per workspace. Display casing is preserved.
### Favorite and RecentVisit
User-specific convenience records. They never grant content access.
## Attachments
### AttachmentBlob
Physical deduplicated content:
- SHA-256 key
- byte length
- detected media type
- storage state
- created timestamp
- integrity status
- preview state
Blob paths are derived only from the hash.
### AttachmentReference
Logical attachment:
- ID / WorkspaceId
- Blob hash
- Original filename
- Description / alt text / caption
- Uploader
- created/trashed timestamps
### AttachmentUsage
Connects a reference to a page, project update or other supported owner.
Deleting a usage does not remove shared content. Orphan cleanup follows retention.
## Projects
### Project
- ID / WorkspaceId
- Name / Slug
- Summary
- Lifecycle status
- Priority
- Start/target/completion dates
- Repository/live/documentation links
- Current-state Markdown
- Owner
- Created/updated metadata
- concurrency version
### ProjectDocument
Connects a project to a page with a role:
- Overview
- Architecture
- Deployment
- Testing
- Troubleshooting
- Security
- Release
- Other
### ProjectUpdate
Immutable chronological update:
- project
- timestamp and author/source
- category
- title
- summary Markdown
- verification status
- optional commit/release/issue/URL
- linked pages/decisions/milestones
- correction/supersession link
Corrections append; they do not rewrite historical audit.
### ProjectMilestone
Named project milestone, distinct from repository build milestones:
- state
- target/completion date
- summary
- order
- linked updates/pages
### TechnologyTag
Normalized project technology classification.
## Decisions
### DecisionRecord
Typed profile linked to one canonical page:
- ID / ProjectId / PageId
- Decision number
- Status
- DecisionDate
- SupersedesDecisionId
- SupersededByDecisionId
- Summary
The page holds durable Markdown sections. The profile holds queryable lifecycle metadata.
Invariants:
- accepted numbering is unique per project;
- supersession cannot cycle;
- a superseded record identifies its replacement;
- decision history is not deleted during ordinary project deletion.
## Runbooks
### RunbookProfile
Typed profile linked to a canonical page:
- ID / PageId
- Owner
- Risk level
- LastValidatedAt / ValidatedBy
- validation status
- expected section anchors
The page's Markdown must contain or explicitly mark:
- Purpose
- Scope
- Prerequisites
- Steps
- Validation
- Rollback
- Risks
The platform displays and validates structure; it never executes steps.
## Imports
### ImportJob
- ID / adapter/version
- source fingerprint and safe descriptor
- status
- preflight plan version
- started/completed timestamps
- actor
- counts and validation outcome
- rollback status
### ImportItem
One discovered source item and its target mapping:
- stable source identity/hash
- source kind/path descriptor
- target entity ID
- planned action
- status
- conflict/diagnostic data
All imported entities carry import provenance sufficient for scoped rollback.
## Audit and integration
### AuditEvent
Append-only security and content action record:
- actor kind and ID
- source (web, API, import, system)
- action
- target type/ID
- safe summary
- correlation ID
- timestamp
- network/user-agent metadata under retention policy
Page bodies, passwords and token secrets are excluded.
### OutboxMessage
Transactional integration event:
- ID
- event type/version
- aggregate identity
- safe payload
- occurred/available time
- processing attempts/status
The initial product may use it for cache/index repair and future integrations.
+256
View File
@@ -0,0 +1,256 @@
# Database schema
This document defines the intended relational shape. EF Core migrations are the
executable source once implemented. Names may be refined in M0/M1 through an ADR, but
constraints and invariants must remain.
## Conventions
- PostgreSQL `uuid` primary keys generated by the application or database.
- `timestamptz` for instants.
- UTC application semantics.
- lowercase snake_case database names.
- explicit foreign keys and delete behavior.
- `citext` or normalized columns for case-insensitive unique values.
- `jsonb` only for versioned, non-core metadata and import diagnostics.
- integer/bigint monotonic revision numbers where scoped.
- byte/string concurrency token exposed as ETag.
- no database credentials or secrets in data migrations.
## Identity and workspace
### installation
- `id` singleton key
- `instance_id` uuid unique
- `product_version`
- `schema_version`
- `primary_locale`
- `time_zone`
- `setup_completed_at`
- timestamps / concurrency version
### users
Use ASP.NET Core Identity-compatible tables with application extensions:
- display name
- enabled state
- last login
- security stamp
- created/updated timestamps
### workspaces
- `id`
- `name`
- normalized `slug`
- settings version
- timestamps
- archived timestamp
Unique: normalized slug.
### workspace_memberships
- workspace/user composite uniqueness
- role
- timestamps
Constraint: valid role enum. Application invariant: final admin cannot be removed.
## Knowledge tables
### pages
Important columns:
```text
id uuid primary key
workspace_id uuid not null
parent_page_id uuid null
sort_key text not null
title varchar(300) not null
normalized_title varchar(300) not null
slug varchar(320) not null
markdown text not null
document_type smallint not null
document_status smallint not null
current_revision bigint not null
content_hash char(64) not null
concurrency_version bigint not null
created_by / updated_by uuid
created_at / updated_at / published_at timestamptz
trashed_at / purge_after timestamptz null
search_vector tsvector generated or maintained
```
Indexes:
- `(workspace_id, parent_page_id, sort_key, id)`
- `(workspace_id, slug)`
- `(workspace_id, updated_at desc, id)`
- partial active-page indexes where `trashed_at is null`
- GIN `search_vector`
- GIN/GiST trigram on normalized title
- optional path/ancestor closure only if measured need justifies it
Cycle prevention is primarily application-controlled and integration-tested. A recursive
query validates moves transactionally.
### page_revisions
Unique `(page_id, revision_number)`. Store immutable snapshots and content hash.
Indexes:
- page/revision descending
- workspace/created timestamp
- author/time where audit UI needs it
No cascade delete during ordinary content lifecycle.
### drafts
Unique `(workspace_id, user_id, draft_key)`. Draft content is private to the author.
### page_links
Unique identity includes source page, occurrence key and source revision. Index target
page for backlinks and unresolved text for maintenance.
### page_relations
Unique `(source_page_id, target_page_id, relation_type)` unless multiple labels are
explicitly supported.
### tags / page_tags
- normalized tag unique per workspace
- composite join primary key
- indexes for tag-filter search
### favorites / recent_visits
Composite user/content uniqueness. Recent visits use upserted last-view time and bounded
retention.
## Attachments
### attachment_blobs
Primary key is 64-character lowercase SHA-256.
Columns include:
- length
- detected media type
- storage/preview/integrity states
- created/verified timestamps
- reference count cache only if transactionally maintained
### attachment_references
Stable logical ID, workspace, blob hash, safe original filename and descriptive metadata.
### attachment_usages
Polymorphic ownership must not use an unconstrained arbitrary string alone. Prefer
separate nullable constrained columns or typed join tables for page/project update usage.
The selected design requires an ADR.
Indexes support owner lookup and orphan discovery.
## Projects
### projects
Unique normalized project slug per workspace. Index lifecycle status and last update.
### project_documents
Unique `(project_id, page_id, role)`.
### project_updates
Ordered by `(occurred_at desc, id desc)`. Immutable content with correction/supersession
links.
### project_milestones
Unique name or sequence per project; stable sort key.
### technology_tags / project_technology_tags
Normalized unique technology name per workspace.
## Decisions and runbooks
### decision_records
- unique page profile
- unique accepted decision number per project
- self-referencing supersession constraints
- indexes by project/status/date
### runbook_profiles
- unique page profile
- risk/validation metadata
- review/validation indexes
## Imports
### import_jobs
Unique source fingerprint plus invocation identity does not automatically prevent a new
deliberate import, but idempotency keys prevent duplicate execution.
### import_items
Unique `(import_job_id, source_identity)`. Index target identities and status.
### import_provenance
Links imported target rows to job/item. This table is the authoritative rollback scope.
## Audit, jobs and outbox
### audit_events
Append-only, indexed by workspace/time, actor/time, target/time and correlation ID.
Partitioning is deferred until measured volume warrants it.
### background_jobs
Durable state, lease owner/expiry, attempt count, schedule, progress and safe diagnostics.
### outbox_messages
Index unprocessed `(available_at, id)`. Retention after successful dispatch is
configurable.
## Search implementation
Use the PostgreSQL `simple` text-search configuration initially because documents mix
Dutch, English, code, commands and product names. Maintain normalized searchable text
from:
- title;
- stripped Markdown text;
- tags;
- selected structured metadata.
Use trigram matching for titles/slugs/tags and explicit ranking weights. Permission
filters must be part of the query, not a post-filter after result selection.
## Migration requirements
Every migration must:
- be deterministic;
- be tested from the previous released schema;
- avoid silent data truncation;
- include data backfill verification where needed;
- document locks/expected duration for large tables;
- have a tested application rollback strategy even when SQL downgrade is unsafe.
+240
View File
@@ -0,0 +1,240 @@
# API contract
The API is built for the web client and trusted scoped automation. A future separate MCP
server consumes the same API.
The draft OpenAPI document is in `docs/api/openapi.yaml`. Implementation and contract
tests must keep it current.
## Conventions
- Base path: `/api/v1`
- JSON: UTF-8, camelCase
- IDs: opaque UUID strings
- Times: RFC 3339 UTC
- Errors: `application/problem+json` (RFC 7807)
- Pagination: opaque cursor
- Concurrency: strong ETag plus `If-Match`
- Correlation: accept/return `X-Correlation-ID`
- Idempotency: `Idempotency-Key` for retryable create/append endpoints
- Locale: server returns invariant values plus localized UI copy remains client-side
## Authentication
### Browser
Secure cookie session plus anti-forgery token for mutations.
### Service account
```http
Authorization: Bearer ikh_<public-id>_<secret>
```
The full token is shown once. The server stores:
- service account ID;
- token public ID/prefix;
- SHA-256 or keyed verifier of a cryptographically random 256-bit secret;
- created/last-used/expiry/revoked timestamps;
- scopes and optional allowed network policy.
For controlled curation, `pages:read` authorizes page detail, roots, children,
revisions and backlinks; `pages:write` authorizes create, update, move and reversible
trash. Browser callers retain their role-based cookie policy. Update and move accept an
optional `Idempotency-Key` in addition to mandatory `If-Match`, so bounded automation
can retry without duplicating a successful mutation.
The API never returns or logs the secret.
## Core scopes
```text
knowledge:read
knowledge:search
pages:read
pages:write
projects:read
projects:write
project-updates:append
decisions:read
decisions:write
runbooks:read
attachments:read
attachments:write
imports:read
imports:execute
audit:read
system:read
```
Scopes never bypass workspace/content authorization.
## Resource endpoints
### System
- `GET /system/info`
- `GET /health` is outside `/api/v1` and contains no private details
### Search
- `GET /search?q=&types=&tags=&projectId=&cursor=&limit=`
Returns permission-filtered hits and facets. No inaccessible counts or snippets.
### Pages
- `GET /pages/{id}`
- `GET /pages/{id}/children`
- `GET /pages/{id}/relations`
- `GET /pages/{id}/revisions`
- `GET /pages/{id}/revisions/{revision}`
- `POST /pages`
- `PUT /pages/{id}` with `If-Match`
- `POST /pages/{id}/move` with `If-Match`
- `POST /pages/{id}/trash` with `If-Match`
- `POST /pages/{id}/restore`
- `POST /pages/{id}/restore-revision`
Page responses include:
- stable ID;
- current title/slug/breadcrumb;
- raw Markdown when scope permits;
- typed metadata;
- current revision;
- ETag;
- stable source citation object.
### Projects
- `GET /projects`
- `GET /projects/{id}`
- `POST /projects`
- `PUT /projects/{id}` with `If-Match`
- `GET /projects/{id}/updates`
- `POST /projects/{id}/updates`
- `GET /projects/{id}/decisions`
- `GET /projects/{id}/context`
### Project context
`GET /projects/{id}/context` returns a bounded context bundle:
```json
{
"project": {
"id": "...",
"name": "Ludarium",
"status": "active",
"summary": "...",
"currentStateMarkdown": "...",
"updatedAt": "..."
},
"recentUpdates": [],
"activeMilestones": [],
"decisions": [],
"runbooks": [],
"documents": [],
"knownIssues": [],
"citations": [],
"truncation": {
"truncated": false,
"omittedCounts": {}
}
}
```
Requirements:
- default bounded item counts;
- explicit `updatedAt` and source IDs;
- deterministic ordering;
- permission filtering before assembly;
- maximum serialized size;
- no secrets, raw attachment bodies or private filesystem paths;
- truncation metadata rather than silent omission.
### RAG publication manifest
`GET /rag/publication-manifest?space=&cursor=&limit=` — a single, deliberately narrow
read-only endpoint for RAGcore's n8n sync, reusing the existing `pages:read` scope (no
new scope). There is no general `GET /pages` enumeration in this API — only
`/pages/roots` plus a tree walk — so this is the one reliable way an external consumer
discovers the full curated publication set. Selection and shape are entirely driven by
the `rag:publish`/`rag:space:`/`rag:allow-deprecated`/`locale:` tag convention; see
`docs/32-rag-publication-convention.md` for the full convention and
`docs/api/openapi.yaml`'s `/rag/publication-manifest` path for the exact response shape.
### Attachments
The implemented v1 routes accept either the normal cookie role or the matching
least-privilege service-account scope: `attachments:read` for listing/content and
`attachments:write` for upload. `pages:write` also authorizes move and the reversible
`POST /pages/{id}/trash` operation so an importer can roll back only the pages from
its own recorded batch; it does not grant permanent purge access.
- `POST /attachments` streaming multipart
- `GET /attachments/{id}/metadata`
- `GET /attachments/{id}/content`
- `DELETE /attachments/{id}` logical delete
Range requests may be supported after authorization. Unsafe types download as attachment.
### Imports
Administrative API only:
- create discovery/preflight;
- retrieve plan/report;
- execute accepted plan;
- validate;
- rollback.
Source paths are configured server-side; clients cannot supply arbitrary unrestricted
host paths.
### Service accounts
Administrative browser/API endpoints create, list, rotate and revoke accounts/tokens.
Creation/rotation returns the secret once.
## Error types
At minimum:
- validation error;
- authentication required;
- forbidden;
- not found (without existence leakage);
- concurrency conflict;
- idempotency conflict;
- rate limited;
- unsupported media type;
- payload too large;
- import preflight conflict;
- dependency unavailable.
Every problem includes a stable machine-readable `type` and correlation ID.
## Rate limits
Separate policies:
- login/recovery/bootstrap;
- service-token authentication failures;
- search;
- attachment upload;
- import/backup operations;
- general API.
Limits are configurable and return standards-compatible retry guidance.
## Versioning policy
- Additive compatible fields may appear within v1.
- Clients must ignore unknown fields.
- Breaking changes require `/api/v2` or a documented transition.
- Outbox event payloads carry their own schema version.
- The future MCP server may never query the database directly.
+132
View File
@@ -0,0 +1,132 @@
# Search and indexing
## Goals
Search must find mixed Dutch, English and technical content quickly without leaking
inaccessible titles, snippets, facets or counts.
The first release uses PostgreSQL full-text search and `pg_trgm`. Embeddings and external
search services are out of scope.
## Indexed sources
### Page document
Weighted fields:
- A: title
- B: tags, document type, breadcrumb labels
- C: stripped Markdown headings and body
- D: selected metadata such as course/module and attachment descriptions
### Project document
- name and summary;
- current state;
- status and technologies;
- project updates;
- linked decision summaries;
- linked document titles.
### Attachment metadata
Only safe metadata is indexed:
- original filename;
- description;
- caption;
- detected media type.
Attachment body extraction and OCR are deferred.
## Text normalization
- Store original content unchanged.
- Build a normalized search projection.
- Use PostgreSQL `simple` configuration initially to avoid damaging code, commands,
product names and bilingual terms through aggressive stemming.
- Normalize Unicode and case for fuzzy title matching.
- Strip Markdown syntax without concatenating unrelated tokens.
- Keep code identifiers searchable.
- Never index hidden secrets or operator-only source paths.
## Query behavior
1. Parse and bound the query.
2. Apply workspace, visibility, trash and permission filters.
3. Generate workspace- and permission-filtered full-text candidates.
4. If exact candidates exist, rank those first and skip workspace-wide fuzzy work; otherwise use the indexed `pg_trgm` title predicate at the measured 0.2 threshold for typo-only searches.
5. Rank with title/exact phrase/recency weights.
6. Generate snippets only from permitted candidates.
7. Return deterministic `(rank, updatedAt, id)` cursor ordering.
Exact title and exact quoted phrase matches outrank fuzzy matches.
This exact-first fallback is intentional: it keeps common-term latency bounded while preserving typo tolerance. The threshold is applied with PostgreSQL `SET LOCAL` in the request transaction; see ADR-020 and the 2026-08-01 production audit.
## Filters
- document type;
- document status;
- collection/root;
- tag;
- project;
- author/editor;
- changed date range;
- content kind;
- stale/review state.
Facets are calculated from the permitted candidate population.
## Index consistency
Publishing a page updates its current search projection transactionally where practical.
Background repair handles:
- parser/version changes;
- project context rollups;
- bulk import;
- failed projection updates;
- full reindex.
Every projection stores a source version/hash. Administrators can see lag and repair it.
## Permission safety
Search authorization is part of SQL selection. It is not acceptable to:
1. retrieve the top global results;
2. filter inaccessible rows in application memory;
3. return fewer results or leaked facets.
Tests must include private pages whose titles and rare terms would reveal their existence.
## Scale verification
Synthetic data must include:
- 100,000 current pages;
- mixed Dutch/English prose;
- code and command blocks;
- 1,000,000 revisions not all included in current-content search;
- tags and project links;
- deliberately inaccessible content;
- typo and phrase queries.
Capture:
- p50/p95/p99 latency;
- query plans;
- index sizes;
- result correctness;
- no-leak assertions.
## Search API safeguards
- query length bound;
- result limit cap;
- cursor expiry/versioning;
- rate limit;
- cancellation/timeout;
- safe highlight tags generated by server or client—not arbitrary HTML;
- generic not-found/forbidden behavior where existence is sensitive.
+157
View File
@@ -0,0 +1,157 @@
# Markdown and linking
## Canonical content
The canonical human-authored body of a page is Markdown stored in PostgreSQL and included
in immutable revisions. HTML is derived and disposable.
Full exports use Markdown plus a manifest/frontmatter representation. Database metadata
remains authoritative during normal operation.
## Supported syntax
Initial profile:
- CommonMark;
- headings;
- emphasis and strikethrough;
- ordered/unordered lists;
- task lists;
- block quotes;
- fenced code blocks with language;
- inline code;
- tables;
- links;
- safe images through attachment references;
- footnotes when the selected parser supports deterministic export;
- Mermaid fenced blocks.
Raw HTML is disabled by default.
### Managed import regions
Imported and derived pages may store four exact control tokens in canonical Markdown:
```text
<!-- managed-source:start -->
<!-- managed-source:end -->
<!-- user-notes:start -->
<!-- user-notes:end -->
```
These tokens remain in PostgreSQL and immutable revisions so synchronization can replace
only the managed source region and preserve user notes byte-exact. The renderer removes
only these four exact tokens from a transient render copy, replacing them with line
boundaries before Markdown parsing. Stored Markdown is never normalized by rendering.
Unknown comments, HTML, scripts and event handlers are not recognized as controls and
remain subject to the normal raw-HTML-disabled and allowlist-sanitized pipeline.
## Internal wiki links
Author syntax:
```text
[[Target title]]
[[Target title|Visible label]]
```
The editor autocomplete may insert an invisible stable marker or structured editor
metadata, but exported Markdown must remain human-readable.
At publish time, each link records:
- source page/revision;
- authored target text;
- optional display text;
- resolved stable target ID;
- resolution state;
- occurrence or source range where practical.
Resolution order:
1. explicit stable target marker;
2. exact title in the same collection/subtree;
3. exact title in the workspace;
4. unique normalized match;
5. unresolved or ambiguous.
The UI must ask the editor to resolve ambiguity before or after publishing; it must not
silently choose an arbitrary page.
## Slugs
- Derived from title for readability.
- Unique only where required by routing policy; stable page ID prevents identity loss.
- Old canonical URLs redirect using ID.
- Slugs contain no authorization meaning.
- Import preserves the legacy path separately as provenance.
## Heading anchors
Heading IDs are deterministic within a rendered revision. Duplicate headings receive
stable suffixes. Links to headings use page ID plus anchor.
A heading rename may break an authored anchor; the system should detect this as a link
maintenance warning but need not maintain a permanent heading identity in v1.
## Rendering safety
- Parse using a fixed configured extension set.
- Do not pass raw HTML through.
- Remove only the four documented import control tokens from render input; never expose
them in reader views, snippets or revision previews.
- Sanitize generated HTML with an allowlist.
- Rewrite external links with safe `rel` attributes.
- Reject `javascript:`, `data:` and unsafe protocols except explicitly generated safe
image data where controlled.
- Render Mermaid with strict security and sanitized labels.
- Never allow Mermaid directives to access network/filesystem.
- Syntax highlighting is client-side or server-side without executing code.
- Cap nesting, token count and rendered size to resist pathological input.
## Attachments in Markdown
Use a stable application URI:
```text
![Alt text](attachment:550e8400-e29b-41d4-a716-446655440000 "Caption")
```
The renderer resolves it through authorized metadata/content endpoints. Exports rewrite it
to a deterministic relative file path and include the attachment.
Ordinary external image hotlinking is disabled by default for privacy. The editor may
store an external hyperlink instead.
## Draft and revision behavior
The draft stores raw Markdown and typed metadata. Preview never changes published
relations or search.
Publish:
1. validates size and parser limits;
2. parses internal links and attachment references;
3. authorizes all referenced private resources;
4. creates current state and immutable revision;
5. updates derived relations/search;
6. writes audit/outbox;
7. returns the new ETag.
## Export frontmatter
Export may include:
```yaml
id: 550e8400-e29b-41d4-a716-446655440000
title: PostgreSQL back-up herstellen
documentType: runbook
status: active
tags:
- PostgreSQL
- Back-up
updatedAt: 2026-07-31T18:00:00Z
```
Importers must not trust privileged fields such as author IDs, permissions or executable
paths from frontmatter.
+203
View File
@@ -0,0 +1,203 @@
# Authentication and security design
## Threat posture
The service is private and self-hosted but must assume:
- the reverse-proxy endpoint may become internet reachable;
- an authenticated low-privilege account can be malicious;
- imported Markdown and files are untrusted;
- database or backup theft is possible;
- API tokens can leak;
- container compromise must not automatically expose the host.
The complete threat register is in `docs/26-threat-model.md`.
## First-run owner setup
First-run setup is intentionally available without a separate installation code. The
endpoint remains a tightly bounded one-time operation:
1. Expose only that setup is required.
2. Require the new workspace and owner credentials over the setup form.
3. Protect the mutation with CSRF validation and a setup-specific rate limit.
4. Atomically create installation, workspace, owner and settings.
5. Permanently disable first-run registration after successful completion.
6. Audit successful completion without recording passwords or tokens.
The setup endpoint must not be exposed through an internet-facing reverse proxy before
owner setup is complete. Complete setup over the trusted LAN or HTTPS endpoint, then
configure the reverse proxy and create the first backup.
## Password authentication
Use ASP.NET Core Identity or an equivalent reviewed implementation.
Policy:
- minimum 12 characters by default;
- allow password-manager generated long passphrases;
- maximum input bound high enough for passphrases;
- no arbitrary composition rules;
- check against a small bundled common-password denylist where feasible without an
outbound service;
- adaptive, versioned password hashing;
- transparent rehash on successful login when policy changes;
- generic authentication failure;
- exponential/threshold lockout;
- session/security stamp invalidation after password reset or account disable.
## Operator-issued recovery
No email provider is required.
1. Administrator or local operator issues a single-use recovery token.
2. Token has at least 256 bits of entropy, a short expiry and one-use state.
3. Store only a verifier.
4. Deliver through an operator-visible channel.
5. Prefer a URL fragment so the raw token never enters access logs:
`https://knowledge.example/#/recover/<token>`.
6. Client posts the token body to the API.
7. Successful reset consumes the token and invalidates sessions.
8. Record safe audit metadata.
For the final administrator, provide a documented container operator command that creates
a recovery token without database surgery.
## Browser sessions
- Secure cookie in HTTPS production.
- HttpOnly.
- SameSite=Lax or stricter based on tested flows.
- Bounded idle and absolute lifetime.
- Session rotation after authentication/privilege change.
- Anti-forgery token on all cookie-authenticated mutations.
- No auth tokens in localStorage.
- Logout invalidates server-side session/security state where implemented.
- Concurrent session view/revocation is desirable in M1.
## Authorization
Authorization is workspace- and resource-aware.
- Reader: view/search permitted content.
- Editor: reader plus content/project creation and editing.
- Administrator: editor plus users, imports, backup, retention, audit and service accounts.
- Service account: scopes plus explicitly assigned workspace/content access; no role
inheritance.
Rules are enforced in Application use cases and query specifications. Endpoint attributes
alone are insufficient.
Tests cover:
- unauthenticated access;
- role escalation;
- object-ID substitution;
- cross-workspace access even though UI exposes one workspace;
- trashed/private content;
- attachment and revision access;
- project context aggregation;
- disabled users and revoked tokens.
## Service-account tokens
Format contains a non-secret public ID/prefix and a random secret. Store only a verifier.
- Show once.
- Optional expiry.
- Multiple independently revocable tokens per account.
- Named purpose and last-used time.
- Explicit scopes.
- Immediate revocation.
- Constant-time verification.
- Rate limit failed token authentication.
- Never accept token in query string.
- Never log the authorization header.
A database compromise should not make offline token recovery feasible because secrets are
uniform random 256-bit values.
## CSRF, CORS and origins
- Browser API is same-origin in production.
- CORS disabled unless a specific trusted integration requires it.
- Anti-forgery for cookie mutation.
- Bearer-token calls do not use cookies.
- Validate configured public/base URL.
- Proxy headers accepted only from configured trusted proxy networks.
- Host filtering enabled.
## Content security
Recommended production headers:
- Content-Security-Policy with no unsafe inline script;
- `frame-ancestors 'none'` unless explicitly required;
- `object-src 'none'`;
- `base-uri 'self'`;
- `form-action 'self'`;
- HSTS at reverse proxy after HTTPS validation;
- X-Content-Type-Options;
- Referrer-Policy;
- Permissions-Policy.
Rendered Markdown disallows raw HTML and unsafe URLs. Mermaid uses strict mode. Uploads
are not served as executable same-origin content.
## Secrets and encryption
- Secrets enter through mounted secret files or environment variables.
- ASP.NET data-protection keys persist outside the read-only root filesystem.
- Production data-protection keys should be protected with an operator-managed secret
where supported.
- Backup encryption is operator-configurable and must not rely on a key stored only inside
the backup.
- API tokens, passwords and bootstrap/recovery secrets are never recoverable from the DB.
- Do not pretend application-level encryption protects against a fully compromised
running application; document the threat boundary.
## Logging and privacy
Never log:
- passwords;
- raw tokens;
- cookies/session IDs;
- CSRF tokens;
- page bodies by default;
- attachment content;
- full recovery/bootstrap links;
- database connection strings.
Use safe IDs, correlation IDs and redacted structured properties. Audit retention and
diagnostic-log retention are separate.
## Rate limits and abuse controls
At minimum isolate policies for:
- setup/login/recovery;
- failed bearer authentication;
- global search;
- upload;
- import/export/backup;
- expensive project-context requests.
Return a generic response that does not confirm account/content existence.
## Security verification
Release requires:
- dependency vulnerability scan;
- secret scan;
- container scan;
- authorization matrix tests;
- CSRF tests;
- XSS corpus;
- malicious upload/path corpus;
- session/token lifecycle tests;
- security header test;
- non-root/read-only container evidence;
- independent security review.
+132
View File
@@ -0,0 +1,132 @@
# Attachments
## Goals
- Preserve school and project files.
- Avoid filename/path vulnerabilities.
- Deduplicate identical content.
- Maintain authorization and provenance.
- Detect corruption.
- Never execute uploaded content.
## Storage model
Physical blobs are addressed by lowercase SHA-256:
```text
/var/lib/knowledge-hub/attachments/ab/cd/<full-sha256>
```
The physical path never contains the user's filename. A logical attachment reference
stores the original display filename and metadata in PostgreSQL.
Temporary uploads use a separate same-filesystem quarantine directory so a validated file
can be atomically moved.
## Upload pipeline
1. Authenticate and authorize.
2. Enforce request and streaming limits.
3. Sanitize display filename; reject path separators/control characters.
4. Stream once to quarantine while calculating SHA-256.
5. Verify actual byte length.
6. Detect known signatures.
7. Compare declared type, extension and detected type.
8. Apply policy:
- safe raster image: eligible for inline preview;
- PDF/Office/text/archive/config: download by default;
- HTML/SVG/script/executable: download-only or reject according to policy;
- unknown: `application/octet-stream`, download-only.
9. Atomically commit blob and logical reference.
10. Generate preview asynchronously where supported.
11. Remove quarantine data on every failure/cancellation.
Default maximum: 100 MiB per file, configurable by administrator. The reverse proxy,
Kestrel and application limits must agree.
## Supported initial categories
- PNG, JPEG, GIF, WebP;
- PDF;
- plain text and common configuration files;
- Office documents;
- common archives as opaque downloads;
- other opaque files by administrator policy.
SVG is not rendered inline in v1. Archives are not extracted as ordinary attachments.
## Serving
Every content request re-authorizes the logical reference.
Headers:
- `X-Content-Type-Options: nosniff`;
- safe `Content-Disposition`;
- bounded cache policy for private content;
- CSP/sandbox where inline documents are later enabled;
- no user-controlled response headers.
Inline rendering is initially restricted to known safe raster images. Range requests may
be added for large downloads with authorization tests.
## Deduplication
Two logical references may point to one blob.
- Deduplication is global inside the installation storage, but authorization always uses
the logical reference.
- A caller cannot discover whether a matching private blob already exists.
- Content hash is not sufficient permission to retrieve.
- Concurrent uploads with the same SHA-256 serialize the blob lookup, atomic commit
and database insert. Every upload receives its own logical reference while exactly
one physical blob is retained; uploads with different hashes remain concurrent.
- Blob cleanup occurs only when no active or retained reference remains.
- Reference counts are derived or transactionally maintained and periodically verified.
## Image metadata
Logical references may store:
- alt text;
- caption;
- width/height;
- rotation-normalized preview;
- uploader and upload time.
Strip unnecessary metadata from generated previews. Preserve the original bytes as the
blob unless policy explicitly rejects them.
## Integrity
- New blobs are verified after atomic move.
- Scheduled sampling/full scan recalculates hashes.
- Missing/corrupt blobs mark references degraded and alert administrators.
- Backups include a blob manifest and hashes.
- Restore validates before enabling normal write traffic.
## Deletion
Deleting an attachment use/reference is logical first. Retention protects accidental
deletion. Orphan physical blobs are cleaned in a background job after the configured
window and a final transactional reference check.
## Security tests
Include:
- double extensions;
- path traversal names;
- null/control characters;
- oversized/chunked uploads;
- declared/detected MIME mismatch;
- HTML/SVG stored XSS;
- polyglot samples where feasible;
- executable/script files;
- authorization ID substitution;
- interrupted upload cleanup;
- duplicate content;
- concurrent duplicate uploads;
- malicious archive names without extraction;
- race between deletion and download;
- corruption detection.
+129
View File
@@ -0,0 +1,129 @@
# Revisions and audit
## Distinction
**Revisions** preserve content state and support comparison/restoration.
**Audit events** preserve security- and operation-relevant attribution.
They are related but not interchangeable.
## Page revisions
A revision is immutable and includes:
- page ID;
- monotonically increasing revision number;
- title and slug;
- parent/order snapshot;
- Markdown;
- typed metadata snapshot;
- content hash;
- author/source;
- change summary;
- timestamp.
The current `pages` row is a projection of the newest published revision plus operational
fields.
### When to create a revision
- content publish;
- title/slug change;
- document type/status change;
- structural move when it changes meaning/breadcrumb;
- restore;
- import;
- API mutation.
Purely personal favorite/recent state does not create a page revision.
### Restore
Restoring revision N creates revision N+1 (or later) containing that snapshot. It does not
delete intervening history. The restore action itself is audited.
### Diff
Provide:
- line-based Markdown diff;
- metadata field diff;
- safe rendered comparison where useful;
- author/time/change summary.
Do not render removed unsafe HTML as active content.
## Project history
Project lifecycle changes and current-state mutations create an immutable project-state
revision or equivalent history record. Timeline entries are immutable; corrections append
a linked correction.
ADR supersession and runbook validation are explicitly audited.
## Audit event requirements
Audit events are append-only application records:
- actor kind: user, service account, import, system;
- actor ID and safe display snapshot;
- source channel;
- action;
- target type and stable ID;
- workspace;
- result;
- correlation ID;
- timestamp;
- safe changed-field names/summary;
- optional network/client metadata under policy.
No page body diff, password, token or raw attachment content is stored in the audit event.
## Atomicity
A published mutation succeeds only when current state, revision, audit and required
outbox record commit together. A failed transaction creates none of them.
Authentication failures may need audit/security logging outside a content transaction,
with rate limiting and privacy controls.
## Retention
- Revisions: retained by default until explicit administrative policy.
- Audit: configurable long retention, minimum recommended one year for a personal system.
- Diagnostic logs: much shorter operational retention.
- Trashed content: configurable grace period.
- Purge: explicit, audited and blocked while legal/operational holds exist (future-ready).
## Audit access
Administrator only by default. Search/filter:
- time range;
- actor;
- action;
- target;
- source;
- correlation ID;
- result.
Audit export excludes sensitive request data.
## Integrity and tamper evidence
PostgreSQL permissions prevent ordinary application paths from updating/deleting audit
rows. A future hash chain may be added, but v1 must not claim tamper-proof logging against
a database administrator. Backups and restricted DB roles provide practical protection.
## Tests
- concurrent publish produces exactly one accepted revision;
- restore appends;
- failed mutation creates no partial audit/revision;
- service-account attribution;
- import attribution;
- disabled user attribution remains readable;
- audit UI authorization;
- sensitive-field redaction;
- retention and purge boundaries.
+170
View File
@@ -0,0 +1,170 @@
# Project hub
## Purpose
Project dossiers turn scattered project notes into a reliable current-state and history
view while keeping long-form documents as normal wiki pages.
A project is not a duplicate folder. It is a structured aggregate linked to pages,
decisions, runbooks, updates and milestones.
## Project fields
Required:
- name;
- slug;
- concise summary;
- lifecycle status;
- priority;
- current-state Markdown;
- owner;
- created/updated timestamps.
Optional:
- start/target/completion dates;
- repository URL;
- live URL;
- documentation URL;
- issue tracker URL;
- technologies;
- icon/image;
- visibility classification;
- external identifiers.
URLs are validated and never used for server-side fetches in v1.
## Lifecycle
```text
Concept → Planned → Active ↔ Blocked
↘ Maintenance
Any non-archived state → Paused
Active/Maintenance → Completed
Any state → Archived
```
Transitions are flexible enough for personal projects but recorded in project history.
Archived projects remain searchable by explicit filter and API permission.
## Project overview
The page contains:
1. status and summary;
2. current verified state;
3. primary links and technologies;
4. active milestones;
5. recent project updates;
6. accepted/current decisions;
7. important runbooks;
8. linked architecture/deployment/testing/troubleshooting pages;
9. known issues/documentation warnings;
10. revision/history access.
The default view is concise. It does not render every full linked document.
## Project documents
A page can be linked with a role:
- Overview
- Current state
- Architecture
- Deployment
- Infrastructure
- Security
- Testing
- Troubleshooting
- Release
- User documentation
- Other
One page may be linked to multiple projects where meaningful. The page remains canonical;
the project stores the relationship, not a copy.
## Project updates
Categories:
- Progress
- Feature
- Fix
- Deployment
- Validation
- Release
- Decision
- Risk
- Blocker
- Documentation
- Maintenance
An update includes title, bounded summary Markdown, occurred time, actor/source,
verification status and optional references:
- commit SHA;
- release/tag;
- issue identifier;
- URL;
- page;
- decision;
- milestone;
- attachment.
Verification status:
- Reported
- Verified
- Superseded
The UI must distinguish reported agent/user claims from validated evidence.
## Project milestones
Project-specific milestones have:
- name;
- order;
- state;
- target date;
- completion date;
- summary;
- success criteria;
- linked updates/documents.
They do not reuse repository build milestone IDs.
## Project context bundle
The bounded context API is designed for external retrieval:
- project identity/status/summary/current state;
- configurable number of recent verified updates;
- active milestones;
- accepted decisions;
- validated runbooks;
- selected document summaries and citations;
- known blockers;
- explicit truncation information.
It must not synthesize facts that are not stored. Summaries are stored/maintained fields,
not automatic LLM output in v1.
## Dashboard behavior
Active projects show:
- name/status;
- one-line current state;
- last verified update;
- stale/documentation warning;
- direct open action.
Cards use consistent heights and alignment without hiding essential content.
## Deletion/archive
Projects archive by default. Deletion cannot delete linked pages. Timeline, decision and
audit history remains recoverable. A privileged purge path requires explicit dependency
review.
+106
View File
@@ -0,0 +1,106 @@
# Runbooks and Architecture Decision Records
## Architecture Decision Records
ADRs provide durable reasoning, not only outcomes.
### Required sections
- Context
- Decision criteria
- Options considered
- Decision
- Consequences
- Validation
### Metadata
- stable ID;
- project;
- human-readable decision number;
- status;
- decision date;
- supersedes/superseded-by;
- owner/author;
- linked page;
- tags.
### Lifecycle
- Proposed
- Accepted
- Rejected
- Superseded
- Withdrawn
An accepted ADR can be superseded only by another accepted ADR. Links are bidirectional
and cycle-free.
Editing the ADR page creates revisions. Changing lifecycle metadata is audited and
revisioned. Historical decisions remain readable.
## Runbooks
A runbook is a human-executed procedure. The platform never runs its commands.
### Required sections
1. Purpose
2. Scope
3. Prerequisites
4. Steps
5. Validation
6. Rollback
7. Risks/notes
### Metadata
- owner;
- risk: low/medium/high/critical;
- system/project tags;
- last validated time and validator;
- review due time;
- estimated duration (optional);
- maintenance-window requirement (optional);
- runbook status.
### Checklist behavior
Markdown task lists may be used as a transient execution checklist, but checking boxes in
a normal read session must not silently modify the canonical runbook.
A future “run session” can create an execution copy, but that is deferred. In v1 the UI
may provide a local/non-persistent checklist mode with an explicit warning.
### Validation
Marking a runbook validated requires:
- permission;
- current revision;
- validator;
- time;
- optional evidence/comment.
A new content revision makes prior validation stale unless the change is explicitly
classified non-procedural and policy allows it.
### Safety
- Commands render as text/code only.
- No terminal bridge.
- No secret interpolation.
- No copy button may include hidden text.
- Sensitive-value placeholders are visibly marked.
- High/critical runbooks emphasize prerequisites, validation and rollback.
## Templates
Canonical templates live in `product/templates/`. The application may seed editable
copies, but template version/provenance is retained.
## API behavior
ADRs and runbooks are retrievable through their own typed endpoints and through page
resources. The context API returns bounded summaries and stable page citations rather
than silently parsing arbitrary headings at request time.
+281
View File
@@ -0,0 +1,281 @@
# LeafWiki migration
## Objective
Migrate the owner's existing LeafWiki knowledge into ItWorx Knowledge Hub without
modifying the source and without silently losing or overwriting content.
LeafWiki versions and deployments may differ. The importer must be discovery-driven and
adapter-versioned rather than assuming one undocumented fixed schema.
## Safety contract
- Source mount is read-only at the operating-system/container level.
- SQLite files are opened with read-only/immutable semantics.
- No source file is renamed, normalized, repaired or touched.
- The importer never writes cache, lock, journal or temporary files beside the source.
- A complete source fingerprint is captured before and after.
- Import writes only to an isolated target workspace/database.
- Every target record is attributable to an import ID and source item.
- Re-running an accepted plan is idempotent.
- Rollback removes only that import's creations/changes according to the recorded plan.
- Conflicts are explicit; no silent overwrite.
## Supported source discovery
Inventory recursively:
- Markdown files;
- SQLite databases, detected by header rather than extension only;
- images and other assets;
- JSON/YAML/config manifests;
- directory hierarchy;
- symlinks, which are recorded but not followed outside the source root;
- unknown files.
For SQLite, inspect read-only:
- SQLite version and pragmas safe to read;
- tables/views/indexes;
- columns and foreign keys;
- row counts;
- candidate content, tree, tag, revision and attachment tables;
- application version markers where present.
The bundled `tools/leafwiki-inspector` provides a neutral first inventory. It is not the
final importer.
## Adapter architecture
```text
IImportSource
Discover()
Fingerprint()
SelectAdapter()
ILeafWikiAdapter
Analyze()
BuildPlan()
ReadItem()
LeafWikiFilesystemMarkdownAdapter
LeafWikiSqliteAdapter_<known-version>
LeafWikiHybridAdapter_<known-version>
```
A known adapter must validate its expected signature before reading semantic columns. An
unknown signature produces a preflight blocker, not guessed mappings.
A filesystem-first adapter can import Markdown hierarchy even when DB enrichment is
unknown, but the preflight must clearly state which tags/revisions/metadata would be
omitted and require explicit acceptance.
### Implemented adapters
| Adapter name | Applies when | Recovers |
| --- | --- | --- |
| `synthetic-sqlite` | a SQLite file with the recognized synthetic schema | pages and hierarchy from the database |
| `leafwiki-filesystem-v5` | `schema.json` declares version 5 (or, absent that file, Markdown carries `leafwiki_id` frontmatter) | titles from `leafwiki_title` (YAML-escape decoded), directory hierarchy, the author's sibling order from each directory's `.order.json`, and assets |
| `filesystem-markdown` | any source containing Markdown | hierarchy from directory structure only; titles from `title:` frontmatter, first heading, or filename |
The catalog tries them in that order and takes the first that reports it applies, so a
version-validated adapter is always preferred over the signature-free fallback, and an
unrecognized LeafWiki version falls back rather than being guessed at
(`docs/adr/ADR-032-leafwiki-v5-import-fidelity.md`).
### Target-side transformations
An import writes Markdown that must work in *this* application, so two changes are made
to the imported copy — never to the source:
- an asset link whose bytes were genuinely imported (`](/assets/<page-id>/<file>)`)
becomes `](/api/v1/attachments/<reference-id>/content)`; an asset that was rejected or
unreadable keeps its original link and is reported as a diagnostic;
- a leading level-1 heading is dropped when it exactly repeats the page title, because
this application renders the title above the body and would otherwise show it twice.
A heading that differs from the title is content and is kept.
## Source fingerprint
A deterministic manifest includes:
- normalized relative path;
- item type;
- byte length;
- SHA-256;
- symlink target descriptor without traversal;
- for SQLite: schema fingerprint and selected row-count summary.
Sort entries ordinally and hash the manifest. Do not include source absolute paths in
user-facing logs unless administrator-only and redacted.
## Preflight plan
Preflight produces:
- adapter and version;
- source fingerprint;
- discovered counts;
- proposed target hierarchy;
- title/slug mapping;
- Markdown encoding/line-ending diagnostics;
- link-resolution plan;
- attachment mapping and hashes;
- tags/revision metadata availability;
- duplicate/conflict classifications;
- unsupported/ambiguous items;
- estimated target changes;
- deterministic plan hash.
No target mutation occurs during preflight.
## Content mapping
### Pages and hierarchy
Preference order:
1. authoritative LeafWiki tree metadata from a validated adapter;
2. Markdown directory hierarchy;
3. explicit operator mapping.
Title sources:
1. validated metadata;
2. frontmatter title;
3. first H1;
4. filename.
Keep original relative path and legacy ID as provenance.
### Markdown
- Preserve original Markdown bytes as an import artifact/hash.
- Decode according to detected/declared encoding; report replacements.
- Convert only syntax with a deterministic documented rule.
- Preserve unknown constructs as fenced/raw text rather than discard them.
- Convert internal links after all page identities are mapped.
- Record unresolved/ambiguous links.
### Tags
Import normalized tags when the adapter can prove their relation. Preserve original
display value.
### Revisions
When reliable legacy revisions exist:
- import them in chronological order;
- preserve source timestamp/author label as provenance;
- mark source attribution;
- never assign legacy author labels to current user identities without explicit mapping.
If revisions cannot be safely reconstructed, import current content only and report it.
### Attachments
- Hash and validate each asset.
- Map references after page identities exist.
- Reject path traversal.
- Preserve safe original filename and source path provenance.
- Deduplicate through normal attachment storage.
- Report missing references and unreferenced assets.
## Conflict policy
Classifications:
- no target conflict;
- exact duplicate by source provenance/hash;
- same title/path, different content;
- same attachment hash;
- ambiguous link;
- unsupported source item.
Default actions:
- exact rerun duplicate: skip idempotently;
- same hash attachment: reuse blob, create intended logical reference;
- title/path conflict: create under an explicit import namespace or require plan choice;
- existing user page: never overwrite;
- ambiguous link: leave unresolved and report.
## Execution
- Persist the accepted immutable plan.
- Use bounded transactions/checkpoints to avoid one giant lock.
- Ensure each item is idempotent.
- Create provenance with the target row.
- Defer cross-links until page mapping exists.
- Allow cancellation between batches.
- Resume only when source and plan fingerprints still match.
- On failure, keep a diagnosable job state; do not mark complete.
## Validation
At minimum compare:
- discovered vs planned vs imported/skipped/failed counts;
- hierarchy/root/subtree mapping;
- current page content hashes after deterministic conversion;
- link resolved/unresolved counts;
- tags;
- attachment bytes/hashes and references;
- revision counts when imported;
- target searchability;
- representative rendered pages;
- source fingerprint before/after.
A validation mismatch blocks completion.
## Rollback
Rollback uses import provenance and the accepted plan:
- remove imported relations;
- remove imported current entities when they were created by the job;
- preserve any subsequent user-edited target by moving it to a review/conflict state
rather than deleting it;
- release attachment references and clean blobs only through normal retention;
- append audit records;
- never touch pre-existing target content;
- never touch source.
Test full rollback and partial-failure rollback.
## Real migration procedure
1. Update and back up the existing LeafWiki.
2. Create an immutable/read-only snapshot.
3. Keep LeafWiki running normally during dry runs.
4. Run inspector and preflight.
5. Resolve all blockers.
6. Import into a non-production target.
7. Validate representative school pages, links, files and search with the user.
8. Rehearse rollback.
9. Schedule cutover.
10. Place LeafWiki read-only.
11. Capture a final snapshot/fingerprint.
12. Run final import or delta strategy supported by the adapter.
13. Validate and take a Knowledge Hub backup.
14. Keep LeafWiki read-only through the rollback window.
15. Retire only after explicit user sign-off.
## Synthetic fixtures
Tests must cover:
- filesystem-only Markdown tree;
- duplicate titles;
- nested pages;
- UTF-8 and special characters;
- wiki links and unresolved links;
- attachments and missing attachments;
- SQLite database with known synthetic schema;
- unknown SQLite schema;
- symlink escape attempt;
- corrupted database/file;
- rerun idempotency;
- rollback after user edit;
- source hash immutability.
+136
View File
@@ -0,0 +1,136 @@
# Backup and restore
## Principle
A backup is not considered valid until it is internally verified, and the backup system is
not considered production-ready until a clean-target restore is tested.
## Backup contents
A full backup set contains:
1. PostgreSQL logical dump in a supported restorable format.
2. Attachment blobs.
3. Optional generated exports required by policy.
4. Application configuration manifest without secrets.
5. Product, schema and container/image versions.
6. File inventory with sizes and SHA-256 hashes.
7. Backup job metadata and timestamps.
8. Encryption metadata when operator-managed encryption is enabled.
Do not include runtime session secrets or plaintext API tokens.
## Consistency strategy
Preferred v1 approach:
- create a PostgreSQL consistent logical dump;
- snapshot/copy immutable content-addressed attachment blobs;
- capture attachment metadata and blob manifest from the same defined backup window;
- validate that every active DB blob reference exists in the manifest;
- record files added after the cutoff for the next backup.
Because committed blobs are immutable, exact transaction coordination is simpler than for
mutable files. Deletions use retention, preventing a blob from disappearing during backup.
## Backup workflow
1. Acquire a backup job/lease.
2. Record application/schema version and cutoff.
3. Run PostgreSQL dump with failure-on-error.
4. Inventory referenced attachment hashes.
5. copy/link/archive required blobs without following unsafe paths;
6. create manifest;
7. verify dump readability and every file hash;
8. optionally encrypt with an operator-supplied mechanism;
9. atomically publish completed backup;
10. apply retention only after a verified new backup exists;
11. record audit and administrator-visible result.
A failed backup never replaces the last known-good backup.
## Retention defaults
Configurable starting policy:
- 7 daily;
- 4 weekly;
- 6 monthly;
- at least one manually protected backup.
Retention must account for available storage and never delete an in-progress restore
source.
## Restore workflow
Restore is deliberately separate from normal application startup.
1. Select backup.
2. Verify manifest, hashes, version compatibility and encryption.
3. Create clean PostgreSQL database and empty target directories.
4. Restore database.
5. Restore attachment blobs by hash.
6. Start the recorded/current compatible application in maintenance mode.
7. Apply supported migrations.
8. Run integrity checks:
- FK/schema;
- user/membership;
- page/revision counts;
- current content hashes;
- link counts;
- project counts;
- attachment references vs blobs;
- import/audit consistency.
9. Run authenticated smoke tests.
10. Produce restore report.
11. Promote target only through an explicit operator step.
Never test restore by overwriting the only production instance.
## Backup encryption
The application may integrate an operator-supplied encryption command or library after
threat review. Requirements:
- key is not stored only inside the backup;
- command arguments do not expose secrets in process listings where avoidable;
- restore can verify authentication/integrity before extraction;
- failure is explicit;
- documentation includes key-loss consequences.
Unencrypted backups must be clearly labeled and protected by host/storage controls.
## Disaster recovery targets
Initial targets, to be confirmed on real hardware:
- RPO: last successful scheduled daily backup by default.
- RTO: documented single-host restore, target under 2 hours for typical personal dataset,
excluding transfer of very large attachments.
Do not claim an RTO until a measured production-like rehearsal exists.
## Tests
### Trusted-intake pilot recovery
Before enabling intake, capture a verified backup and restore it into an isolated,
empty target. Record the source commit, image digest, migration range, backup ID,
table counts and attachment hashes. A rollback of intake starts by setting
`IngestionProposal:Enabled=false` and revoking the separate client's service
account. Keep proposals, source references, attachment usages and audit history;
disabling the feature is not a retention-cleanup instruction. Published attachments
remain referenced by their pages. Restore a pre-release backup only after an
isolated restore proves it is necessary and the operator has assessed newer writes.
- create backup with representative data;
- verify manifest;
- corrupt dump and blob tests;
- clean-target restore;
- compare logical counts and hashes;
- restore older supported schema then migrate;
- missing key/encrypted backup failure;
- interrupted backup cleanup;
- retention policy;
- no secret inclusion;
- large attachment set.
+145
View File
@@ -0,0 +1,145 @@
# Observability
## Goals
Operators must be able to answer:
- Is the application alive and ready?
- Is PostgreSQL available?
- Are migrations compatible?
- Are background jobs progressing?
- Did an import, backup or integrity scan fail?
- Is search projection current?
- Are attachment files missing or corrupt?
- Which release and schema are running?
- What request/job correlation identifies a failure?
Observability must not expose private knowledge or secrets.
## Health endpoints
### `/health/live`
Answers only whether the process can respond and has not entered a fatal internal state.
It must not fail because PostgreSQL is temporarily unavailable.
### `/health/ready`
Checks required runtime dependencies:
- compatible database/schema;
- database connectivity;
- writable required storage;
- data-protection key access;
- critical startup jobs complete;
- no maintenance lock preventing normal traffic.
Return minimal public details. Detailed diagnostics require administrator authentication.
### `/api/v1/system/info`
Permissioned information:
- product/version/commit;
- schema version;
- uptime;
- configured locale/timezone;
- storage/job/search status;
- feature flags;
- safe dependency versions.
## Logging
Production uses structured JSON to stdout/stderr for container collection.
Core properties:
- timestamp;
- level;
- event ID/name;
- message template;
- correlation/request ID;
- actor kind and safe ID where authenticated;
- route/status/duration;
- job/import/backup ID;
- exception type and safe stack in operator logs.
Never log page content, passwords, tokens, cookies, authorization headers, connection
strings or attachment bytes.
Use explicit redaction and tests for sensitive property names.
## Audit vs logs
- Diagnostic logs help operate and debug.
- Audit events prove who did what to protected resources.
Do not rely on transient container logs as the audit trail.
## Metrics
Expose bounded metrics to administrators and optionally a protected scrape endpoint:
- request count/duration/status;
- active sessions (approximate, non-identifying);
- DB pool saturation;
- job queue counts and oldest age;
- import/backup outcomes;
- search projection lag;
- attachment storage bytes/count;
- integrity failures;
- login failure/lockout counters without username labels.
Avoid high-cardinality labels such as page ID, user ID or query text.
## Tracing
Use correlation IDs across HTTP, application commands and jobs. OpenTelemetry-compatible
instrumentation is acceptable but no external collector is required.
## Background-job visibility
Admin UI shows:
- type;
- state;
- queued/started/completed time;
- progress counts;
- attempts;
- next retry;
- cancellation availability;
- safe failure summary;
- correlation ID.
Raw stack traces remain in operator logs, not ordinary UI.
## Alerts
The initial system provides persistent in-app administrator alerts for:
- repeated backup failure;
- failed restore rehearsal;
- import validation failure;
- storage unwritable/low;
- missing/corrupt blobs;
- incompatible schema;
- stuck critical job;
- token/security event requiring attention.
External alert integrations are deferred.
## Retention
### Trusted-intake signals
Record proposal count, processing-stage duration, review duration and publication
count with only bounded `stage`, `outcome` and `problem_type` labels. Correlation
IDs connect requests to safe operational events. Never put source bodies, source
URLs, filenames, bearer tokens, credentials or private paths in metrics, logs or
release evidence. Alert on sustained failed proposals, stuck review state and
publication failures without exposing proposal content.
- Configure log rotation at Docker/Unraid level.
- Keep audit retention in the application/database.
- Prune metrics/job detail according to policy.
- Preserve final import/backup/restore reports longer than ordinary job logs.
+204
View File
@@ -0,0 +1,204 @@
# UI/UX design system
## Design direction
ItWorx Knowledge Hub should feel like a focused professional technical workspace:
- calm;
- compact without being cramped;
- legible for long documents;
- fast;
- consistent;
- trustworthy;
- restrained in motion and decoration.
It should not look like a generic admin template or an overloaded analytics dashboard.
## Brand direction
Use the ItWorx identity with two controlled accents:
- a clear light blue for navigation/knowledge actions;
- one consistent green for positive/project accents.
The application must also work in monochrome/high-contrast contexts. Semantic states
(error, warning, success) may use distinct accessible colors but must not redefine the
brand.
Exact tokens are implemented and contrast-tested in M1. Avoid hardcoded colors inside
components.
## Typography
- UI: modern highly legible sans-serif.
- Code: reliable monospace.
- Long-form body target: 70–85 characters per line.
- Default body size at least 16 CSS px.
- Headings use a restrained scale and predictable spacing.
- Do not use all-caps for long labels.
Prefer system or openly licensed bundled/web-safe fonts. The product must not require an
external font CDN.
## Spacing and density
Use a 4 px base grid with common increments 4/8/12/16/24/32.
Two density modes may be considered later; v1 has one balanced default. Tree rows and
buttons must remain large enough for touch targets.
## Layout tokens
- Header: approximately 56 px.
- Left navigation: 280 px default, 224–420 px resizable.
- Context panel: 300 px default, collapsible.
- Document max line width independent of center pane width.
- Mobile breakpoint based on usable layout, not device names.
- Avoid horizontal page scrolling except deliberate code/table containers.
## Core components
- App shell
- Navigation tree
- Breadcrumbs
- Global search/command palette
- Page header
- Document renderer
- Markdown editor and split preview
- Table of contents
- Metadata panel
- Backlink/relations list
- Tag input
- Status badge
- Project card/list
- Timeline
- ADR state/lineage
- Runbook structure/validation
- Revision list/diff
- Attachment uploader/list
- Import plan/report
- Data table with cursor pagination
- Dialog, sheet and confirmation
- Toast plus persistent error summary
- Skeleton/loading indicator
- Empty state
- Conflict-resolution panel
Build shared primitives; avoid route-specific visual inventions.
## Navigation tree behavior
- Lazy load children.
- Expand/collapse with keyboard.
- Maintain expanded state per user/device.
- Support drag/move only when accessible alternatives exist.
- Context menu has an explicit button, not right-click only.
- Current page is indicated semantically and visually.
- Long titles truncate with accessible full-title access.
- Moving a subtree shows destination and impact before commit.
## Editor
Desktop modes:
- write;
- split;
- preview.
Mobile modes are separate tabs, never a squeezed split view.
Editor requirements:
- clear draft/published state;
- last autosave time;
- save/publish action;
- change summary;
- outline navigation;
- internal-link chooser;
- upload insertion;
- accessible keyboard shortcuts;
- conflict banner and merge flow;
- no toolbar that steals focus unpredictably.
A command palette can accelerate formatting and page creation but is not required to
complete basic tasks.
## Feedback states
### Loading
Use stable skeletons for page/tree/list layouts. Do not block the entire shell for one
panel.
### Empty
Explain what belongs here and provide one relevant action.
### Error
- state what failed;
- preserve user input;
- show retry or recovery;
- include correlation ID in expandable technical details;
- do not expose stack traces.
### Conflict
Distinguish:
- server version;
- local draft;
- common/base revision.
Never present “last save wins” as success.
### Success
Use subtle confirmation. Do not rely only on transient toast for important operations such
as import, backup or token creation.
## Responsive behavior
### Desktop
Three-pane reading where space allows.
### Tablet/narrow desktop
Context panel becomes a sheet. Navigation can collapse.
### Mobile
- one pane;
- bottom/top actions within thumb reach;
- no hover-only controls;
- editor toolbar scrolls or groups;
- dialogs avoid overflowing viewport/keyboard;
- tables transform or use contained horizontal scrolling.
## Dark mode
- user-selectable plus system preference;
- no flash of wrong theme;
- syntax highlighting and Mermaid remain readable;
- images/diagrams retain borders where needed;
- contrast verified independently.
## Motion
- brief functional transitions only;
- respect `prefers-reduced-motion`;
- no parallax, continuous decorative movement or animation blocking input;
- loading indicators must not cause layout shift.
## Content tone
Dutch UI copy is direct and professional:
- “Pagina maken”
- “Wijzigingen publiceren”
- “Versieconflict”
- “Import controleren”
- “Back-up herstellen”
Avoid anthropomorphic AI wording because AI is not part of the product.
+106
View File
@@ -0,0 +1,106 @@
# Accessibility
## Standard
Target WCAG 2.2 AA for all implemented user flows.
Automated tests are necessary but not sufficient. Each milestone with UI changes includes
manual keyboard review; M8 includes representative screen-reader checks.
## Global requirements
- Semantic HTML first.
- One logical `h1` per page view.
- Landmarks for header, navigation, main and complementary content.
- Skip link.
- Visible focus meeting contrast/area requirements.
- Logical DOM and tab order.
- No keyboard trap.
- 200% zoom without loss of function.
- Reflow at 320 CSS px width except genuinely two-dimensional content.
- Minimum target size or adequate spacing.
- Text contrast and non-text contrast.
- Status not conveyed by color alone.
- Accessible names for icon-only controls.
- Form labels, instructions and error association.
- Live regions only for concise relevant updates.
- Reduced motion.
- Language set to `nl-BE`, with code/foreign fragments where appropriate.
## Navigation tree
Implement the WAI-ARIA tree pattern only if the team can meet its complete keyboard model.
A nested disclosure/list pattern is acceptable and often simpler.
Required operations:
- Tab enters/leaves the tree predictably.
- Arrow or disclosure controls expand/collapse.
- Current page is announced.
- Actions are reachable without context menu/right-click.
- Drag-and-drop has move-dialog/keyboard equivalent.
- Loading more children is announced.
## Editor
- Text area/editor exposes a usable label and instructions.
- Toolbar controls have names and pressed state.
- Shortcuts do not conflict with browser/assistive technology and are documented.
- Preview mode is a distinct region.
- Heading outline is navigable.
- Autosave and publish state are announced without excessive interruption.
- Validation errors link to the relevant metadata/editor location.
- Conflict UI explains versions and preserves text.
- Markdown source remains available; no inaccessible rich-editor-only content.
## Markdown rendering
- Heading hierarchy is validated/warned.
- Tables have accessible overflow and author guidance for headers.
- Code blocks have language labels where known.
- Copy action is labeled and reports success.
- Images require alt text or explicit decorative status.
- Mermaid diagrams require an author-provided text description or alternative.
- Link text must be understandable in context.
- Deprecated/warning notices use semantic status patterns.
## Dialogs and sheets
- Initial focus is intentional.
- Focus is trapped only while modal.
- Escape closes when safe.
- Focus returns to trigger.
- Title/description relationships are correct.
- Destructive confirmation names the target and consequence.
- Token/recovery secret dialogs prevent accidental loss and provide copy confirmation.
## Search and dynamic results
- Search input has label and clear action.
- Result count/loading changes are announced politely.
- Keyboard focus does not jump on every keystroke.
- Filters are reachable and stateful.
- Highlighting is not the only relevance cue.
- Empty/no-results states are announced.
## Testing matrix
Automated:
- axe-core on primary routes and important dialogs;
- eslint accessibility rules;
- component semantics where stable;
- Playwright keyboard flows;
- color-token contrast tests where practical.
Manual at M8:
- keyboard-only complete journeys;
- Chromium with a screen reader on login, tree, page read/edit, search, project, import
report and backup status;
- 200% zoom;
- reduced motion;
- dark/light mode;
- mobile touch and software keyboard.
Every exception requires documented user impact and an accepted remediation plan.
+208
View File
@@ -0,0 +1,208 @@
# Testing strategy
## Test pyramid and ownership
### Domain/unit tests
Fast deterministic tests for:
- domain invariants;
- status transitions;
- slug/title normalization;
- link parsing;
- import planning rules;
- token/password-independent utility behavior;
- context bounding;
- retention rules.
### Application tests
Use cases with controlled ports/fakes where appropriate:
- authorization requirements;
- commands/queries;
- conflict behavior;
- audit/outbox intent;
- validation and error types.
### PostgreSQL integration tests
Use Testcontainers with the supported PostgreSQL major/minor family:
- EF migrations from empty and previous schema fixtures;
- constraints and transactionality;
- authorization-aware queries;
- search ranking/leakage;
- import idempotency/rollback;
- job leasing;
- backup metadata/integrity logic;
- optimistic concurrency.
SQLite is not an acceptable substitute for application persistence tests. SQLite is used
only as a legacy import source fixture.
### Frontend component tests
Vitest and Testing Library:
- component behavior and semantics;
- forms/validation;
- loading/error/conflict states;
- navigation tree interactions;
- API contract adapters;
- draft state;
- project/ADR/runbook views.
### Browser end-to-end tests
Playwright against the real app and PostgreSQL:
- first-run setup;
- login/logout/recovery;
- create/edit/move/trash/restore page;
- internal links and backlinks;
- revision compare/restore;
- search and filters;
- attachment upload/download;
- synthetic import and report;
- project/ADR/runbook flow;
- service-account lifecycle through admin UI;
- backup status/restore rehearsal entry points;
- desktop and mobile;
- axe checks.
### Operational tests
- production image build;
- non-root identity;
- read-only root filesystem;
- required mount permissions;
- health/readiness behavior;
- restart recovery;
- database migration rehearsal;
- full backup/clean restore;
- source immutability;
- upgrade from prior release.
## Test projects
Intended structure:
```text
tests/backend/
ItWorx.KnowledgeHub.Domain.Tests
ItWorx.KnowledgeHub.Application.Tests
ItWorx.KnowledgeHub.Architecture.Tests
ItWorx.KnowledgeHub.IntegrationTests
tests/e2e/
Playwright tests and fixtures
tools/leafwiki-inspector/tests/
Inspector unit tests
```
## Test data
Use deterministic builders and synthetic fixtures.
Never use:
- real LeafWiki data;
- real emails/passwords;
- production URLs/tokens;
- copied private project documents.
Synthetic fixture names should make their fictional nature clear.
## Database lifecycle
- One PostgreSQL Testcontainer per test collection/suite, with isolated database/schema
per test where needed.
- Apply real migrations.
- Reset state using a safe test-only reset strategy.
- Parallelize only after proving isolation.
- Capture container logs on failure.
- Pin the test image to the supported PostgreSQL version family.
## Time and IDs
- Inject `TimeProvider`.
- Use deterministic or recorded generated IDs.
- Avoid sleeps; wait on observable state with bounded timeouts.
- Control locale/timezone in tests.
## Security test corpus
- stored/reflected Markdown XSS;
- unsafe links;
- Mermaid injection;
- CSRF;
- session fixation/invalidation;
- role/scope escalation;
- object-ID substitution;
- search leakage;
- upload signatures/path names/polyglots;
- import traversal/symlinks/corrupt DB;
- secret logging;
- service-token revoke/rotation.
## Scale suites
Separate normal CI and heavier release suites.
Normal CI:
- representative hierarchy;
- hundreds/thousands of pages;
- fast search correctness.
Release:
- 100,000 pages;
- 1,000,000 revisions;
- large project/update relations;
- attachment manifest scale;
- query plans and p95 timings.
Store generator seed and environment details with results.
## Flake policy
- No blind retry loops.
- One infrastructure retry may be configured only for a documented external startup race.
- A flaky product test blocks release until root cause is fixed or an explicit accepted
issue proves it is not product risk.
- Quarantined/skipped tests require owner, reason and expiry.
## Coverage
Coverage percentage is diagnostic, not the completion definition. Critical branches need
explicit tests regardless of total percentage.
Minimum release expectations:
- all authorization policies covered;
- all domain invariants covered;
- all API error contracts covered;
- all migration/restore destructive boundaries covered;
- primary user journeys covered end to end.
## Verification entry points
`node scripts/verify.mjs` orchestrates:
- structural/bundle validation;
- format/lint/type check;
- .NET build/tests;
- frontend tests;
- Python inspector tests;
- optional E2E;
- optional container/release gates.
Flags are documented in the script. CI uses the same entry point.
## Evidence
Record exact commands, commit SHA, versions and counts in `EVIDENCE.md`. Generated test
reports live in ignored `artifacts/`, with stable summary references.
+162
View File
@@ -0,0 +1,162 @@
# MCP integration — separate service
## Boundary
This repository does **not** implement an MCP server.
The integration boundary is:
```text
Codex / Claude / another MCP client
↓ MCP transport
Separate ItWorx Knowledge MCP service
↓ HTTPS + scoped bearer token
ItWorx Knowledge Hub /api/v1
```
The MCP service has no direct PostgreSQL, attachment-volume or internal filesystem access.
## Document and repository knowledge workflow
The separate `ITWorx_MCP_Hub` repository owns the MCP protocol and attachment
extraction. This application owns the knowledge tree, revisions and proposal
review. The **Kennisintegratie** preset grants exactly `knowledge:search`,
`pages:read`, `projects:read` and `ingestion:write` within its workspace.
Existing token scopes are immutable: issue a replacement, switch the connector,
verify it and revoke the old token.
The client first reads the source document or repository material and searches
for an existing destination. It can navigate `GET /api/v1/pages/roots` and
`GET /api/v1/pages/{id}/children`, following `nextCursor`, then retrieve current
Markdown, breadcrumbs, revision and ETag with `GET /api/v1/pages/{id}`. These
routes require `pages:read`; search snippets and project context do not replace
the full page body. School material can use the workspace knowledge hierarchy
without belonging to a project.
Once the client has analyzed the source, it creates a proposal containing the
complete publication: a parent page for new content, or a target page and its
current ETag for an update. Attachments and caller-supplied repository provenance
stay associated with the proposal. Source contents are data, not instructions.
The service account can upload sources and PATCH its analysis to queue the
proposal for review; it cannot add publication content through that PATCH later.
Approval and publication remain human actions in `/knowledge/intake`. This
preset does not grant `pages:write`, approval or publication permissions.
Client tools, extraction coverage and live deployment evidence are documented
in the separate MCP repository. Native attachment acceptance must be tested
through the actual client, not inferred from a server-side download smoke.
## Why the boundary matters
- The wiki remains useful and secure without MCP.
- Protocol and SDK upgrades do not destabilize the knowledge platform.
- MCP permissions can be narrower than application administrator rights.
- Transport-facing risk is isolated.
- Other API clients can reuse the same contracts.
- Audit attribution can identify the MCP service account.
## API capabilities required before MCP work
Read:
- search permitted knowledge;
- retrieve page and selected revision;
- retrieve related pages/backlinks;
- list/get projects;
- retrieve bounded project context;
- retrieve project updates/decisions/runbooks;
- retrieve attachment metadata/content when scoped.
Controlled write:
- create page;
- update page with ETag;
- append project update;
- create decision;
- update project status;
- create runbook.
Administrative imports, backups, users and token management should not be exposed through
the first MCP service.
## Citation model
Every retrievable knowledge item includes a stable citation object:
```json
{
"kind": "page",
"id": "uuid",
"revision": 42,
"title": "PostgreSQL back-up herstellen",
"canonicalPath": "/knowledge/uuid/postgresql-back-up-herstellen",
"updatedAt": "2026-07-31T18:00:00Z"
}
```
Project context carries citations for each summarized source. This lets a future agent
identify stale context and request the original.
## Context bounds
The API, not the MCP adapter, enforces safe defaults:
- maximum updates/decisions/documents;
- maximum body/snippet bytes;
- deterministic newest/important ordering;
- permission filtering;
- explicit truncation and omitted counts;
- optional `since`/revision parameters;
- no attachment bytes in context bundles;
- no secrets or operational private paths.
## Write safety
Future MCP writes use application API controls:
- dedicated service account;
- minimum scopes;
- `If-Match`;
- idempotency key;
- validation;
- revision and audit;
The same boundary is used by the Canvas curation tooling: inventory needs only
`pages:read` plus `attachments:read`; curation adds `pages:write`. Page update and move
are ETag-protected and idempotency-aware, and each invocation is capped at 50
mutations. No browser cookie or administrator token is embedded in tooling state.
- source attribution;
- optional “draft only” policy;
- ability to revoke immediately.
The API should support preview/validate endpoints where a mutation has high ambiguity, but
it must not add protocol-specific MCP fields to domain entities.
## Outbox events
Stable versioned events may later let the separate MCP service invalidate caches:
- `page.created.v1`
- `page.updated.v1`
- `page.trashed.v1`
- `project.updated.v1`
- `project-update.appended.v1`
- `decision.created.v1`
The initial outbox need not expose a public webhook. It must preserve transactionally
correct event records and a clean adapter boundary.
## Security expectations for future MCP project
Not implemented here, but the API design must support:
- read-only default token;
- separate write token/account;
- rate limits;
- IP/network policy where configured;
- full audit source;
- token rotation;
- no database access;
- no generic shell/filesystem tools;
- no arbitrary SQL;
- no hidden administrator bypass.
+89
View File
@@ -0,0 +1,89 @@
# Threat model
## Assets
- User credentials and sessions.
- Wiki and project content.
- Revisions and audit attribution.
- Attachments.
- API service tokens.
- LeafWiki source snapshot.
- Database and backups.
- Data-protection and backup keys.
- Host filesystem and Docker socket.
- Availability and integrity of the Unraid deployment.
## Trust boundaries
1. Browser ↔ reverse proxy.
2. Reverse proxy ↔ application.
3. API client ↔ application.
4. Application ↔ PostgreSQL.
5. Application ↔ writable data volumes.
6. Application ↔ read-only import source.
7. Backup storage.
8. Container ↔ Unraid host.
## Threat register
| ID | Threat | Primary controls | Required verification |
|---|---|---|---|
| T01 | First-run owner registration race | atomic single-use setup; CSRF; setup rate limit | concurrent setup integration test |
| T02 | Password brute force/account enumeration | adaptive hashing; generic errors; lockout; rate limit | auth abuse tests |
| T03 | Session theft/fixation | secure cookie; rotation; bounded life; security stamp | session lifecycle tests |
| T04 | CSRF mutation | same-origin; anti-forgery; SameSite | CSRF integration tests |
| T05 | Horizontal/vertical authorization bypass | application-layer policies; scoped queries | role/object substitution matrix |
| T06 | Search metadata leakage | permission filter before ranking/facets/snippets | rare-term private-page tests |
| T07 | Stored XSS in Markdown/Mermaid | no raw HTML; sanitizer; strict Mermaid; CSP | XSS corpus/browser test |
| T08 | Malicious attachment execution | signature validation; download-only policy; nosniff; authorization | upload/serving corpus |
| T09 | Path traversal/symlink escape | hash paths; normalized bounded import root; no unsafe following | traversal fixture tests |
| T10 | LeafWiki source mutation | RO mount; immutable SQLite; before/after fingerprint | source hash proof |
| T11 | Import overwrite/data loss | preflight; explicit conflicts; provenance; rollback | conflict/idempotency/rollback tests |
| T12 | API token theft/database recovery | random 256-bit secret; verifier only; revoke/expiry; no logs | token lifecycle/log tests |
| T13 | Concurrency overwrite | ETag/If-Match; drafts; conflict UI | concurrent edit E2E |
| T14 | Backup theft/tamper | storage controls; optional authenticated encryption; manifest hashes | corrupt/encrypted backup tests |
| T15 | Restore of incompatible/corrupt data | compatibility checks; clean-target restore; integrity checks | restore matrix |
| T16 | Secret leakage through logs/support bundle | structured redaction; safe exports | secret canary scan |
| T17 | Container-to-host escalation | non-root; read-only root; dropped caps; no Docker socket | image/runtime inspection |
| T18 | SSRF through project links/import metadata | no server fetch of user URLs; bounded local roots | SSRF negative tests |
| T19 | Denial of service via parser/search/upload | size/complexity bounds; timeouts; rate limits; streaming | resource-limit tests |
| T20 | Audit/history tampering through normal app | append-only paths; restricted DB behavior; immutable revisions | mutation/permission tests |
| T21 | Service account scope escalation | explicit scopes plus content auth; no admin bypass | API scope matrix |
| T22 | Outbox/job duplicate side effects | idempotency; leases; attempt state | restart/retry tests |
| T23 | Unsafe database migration | backup; compatibility gates; rehearsal | upgrade/rollback tests |
| T24 | Sensitive content in external image requests | external image hotlink disabled by default | renderer/network tests |
## Abuse cases
### Malicious reader guesses page IDs
Response must not distinguish inaccessible from nonexistent where doing so leaks
existence. Search, attachments, revisions and context use the same resource policy.
### Malicious editor uploads HTML disguised as PNG
The server streams, checks signature and applies detected-type policy. It does not render
the file inline. Filename and declared MIME are not trusted.
### Imported Markdown contains scripts and unsafe URLs
Raw source is preserved for provenance, but normal rendering uses the safe Markdown
profile. Import validation includes renderer security tests.
### Stolen service token
Administrator can identify token prefix/last use and revoke it. Token cannot log into the
browser, exceed scopes or recover the owner password. Mutations remain attributable.
### Compromised app container
The container runs non-root, lacks Docker socket and host-wide mounts, and has a read-only
root filesystem. Database/files remain at risk within granted access; backups and host
security are still required. Do not claim container isolation eliminates this risk.
## Review cadence
- Update when trust boundaries, auth, uploads, imports, backup or deployment change.
- Perform focused review per milestone.
- Perform independent full review in M8.
- Record accepted residual risk through ADR or release notes.
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

@@ -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.