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