Files
itworx-knowledge-hub-Public/docs/08-database-schema.md
T

257 lines
6.3 KiB
Markdown

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