# Database Schema (Version 4) This document defines the selected schema direction for V4 document-person relationship expansion. V4 goals are: - extensible role taxonomy, - extensible document type taxonomy, - explicit assertion state (`asserted`, `suggested`), - policy-driven exclusivity, - deterministic migration from V3 links. ## Scope This specification focuses on relationship and document-type governance persistence changes. Existing `Person`, `Source`, `Job`, and `JobSource` core structures remain as in V3 unless explicitly noted. ## New/Expanded Concepts - **Relationship role:** semantic label such as `author`, `recipient`, `mentioned`. - **Assertion state:** whether the link is canonical (`asserted`) or pending review (`suggested`). - **Exclusivity matrix:** configurable role-pair conflicts for same `(document_id, person_id)`. - **Suggestion provenance:** evidence fields enabling review decisions. - **Document type registry:** controlled taxonomy for `Document` classification with stable code identity. ## Selected Model: Role Registry + Separate Suggestion Table This option cleanly separates canonical links from pending suggestions and enables fully data-driven role expansion. ### Tables #### `person_role` | Column | Type | Notes | | --- | --- | --- | | `id` | UUID PK | Stable key | | `code` | TEXT UNIQUE | Canonical role code, for example `author`, `recipient`, `mentioned` | | `label` | TEXT | UI label | | `is_active` | BOOLEAN | Soft-enable/disable role | | `created_at` | TIMESTAMPTZ | Audit timestamp | | `updated_at` | TIMESTAMPTZ | Audit timestamp | #### `document_person` (asserted links only) | Column | Type | Notes | | --- | --- | --- | | `id` | UUID PK | Stable key | | `document_id` | UUID FK | -> `document.id` | | `person_id` | UUID FK | -> `person.id` | | `role_id` | UUID FK | -> `person_role.id` | | `created_at` | TIMESTAMPTZ | Audit timestamp | | `updated_at` | TIMESTAMPTZ | Audit timestamp | Constraints: - `UNIQUE(document_id, person_id, role_id)` #### `document_person_suggestion` | Column | Type | Notes | | --- | --- | --- | | `id` | UUID PK | Stable key | | `document_id` | UUID FK | -> `document.id` | | `person_id` | UUID FK | -> `person.id` | | `role_id` | UUID FK | -> `person_role.id` | | `status` | TEXT | `pending`, `accepted`, `rejected` | | `confidence` | FLOAT NULL | Optional score | | `source_mechanism` | TEXT NULL | For example `rule`, `llm` | | `evidence_ref` | TEXT NULL | Pointer or excerpt ID | | `evidence_span` | JSON NULL | Optional text span payload | | `note` | TEXT NULL | Reviewer note | | `created_at` | TIMESTAMPTZ | Suggestion creation time | | `reviewed_at` | TIMESTAMPTZ NULL | Decision time | | `reviewed_by` | TEXT NULL | Operator identifier | Constraints: - `UNIQUE(document_id, person_id, role_id, status)` with policy for multiple pending rows defined in service layer. - Optional stricter rule: one active pending suggestion per `(document_id, person_id, role_id)`. #### `role_exclusivity` | Column | Type | Notes | | --- | --- | --- | | `id` | UUID PK | Stable key | | `left_role_id` | UUID FK | -> `person_role.id` | | `right_role_id` | UUID FK | -> `person_role.id` | | `created_at` | TIMESTAMPTZ | Audit timestamp | Constraints: - Canonical ordering rule to avoid duplicate pairs (`left_role_id < right_role_id` enforced in service/DB). - `UNIQUE(left_role_id, right_role_id)` Initial seed: - Exclusivity pair: (`author`, `recipient`) ## Deferred Alternative (Not Selected for V4) An enum-based shared table model was considered but is intentionally not selected for V4 because it couples canonical and provisional states in one table and increases invariant complexity. ## Assertion-State Semantics - `asserted`: canonical relationship used for document/person metadata and business logic. - `suggested`: non-canonical proposal requiring explicit review. - Accept action: - creates asserted link (or confirms existing), - marks suggestion `accepted`. - Reject action: - marks suggestion `rejected`. ## Exclusivity Enforcement Policy target: - For a single `(document_id, person_id)`, disallow coexistence of role pairs configured as exclusive. Enforcement layers: 1. Service-level pre-check for clear API errors. 2. Database-level guard where feasible (constraints/triggers or deterministic write path). Initial configured rule: - `author` and `recipient` are exclusive. Enforcement semantics: - Exclusivity is enforced for asserted links. - Pending suggestions may exist even if they would conflict when asserted. - Accepting a suggestion must run exclusivity checks and fail deterministically on conflict. ## Migration From V3 ### Data Mapping - Existing V3 `document_person` rows map to V4 `asserted` semantics. - Existing V3 role values: - `author` -> role `author` - `recipient` -> role `recipient` ### Backfill Steps 1. Seed role rows (`author`, `recipient`, `mentioned`) if using Option A. 2. Migrate current links into asserted table/state. 3. Run conflict scan for exclusivity violations. 4. Apply deterministic conflict policy for any violations. 5. Enable hard enforcement after data passes validation. ## Indexing Guidance Recommended indexes: - `document_person(document_id)` - `document_person(person_id)` - `document_person(role_id)` (Option A) or `document_person(role)` (Option B) - `document_person_suggestion(document_id, status)` (Option A) - `document_person_suggestion(person_id, status)` (Option A) ## Selection Rationale V4 selects role registry plus separate suggestion storage for clearer provenance boundaries, cleaner lifecycle transitions, and long-term extensibility. ## Document Type Registry Model (Selected for V4) V4 applies the same registry governance pattern to document classification. ### Tables #### `document_type` | Column | Type | Notes | | --- | --- | --- | | `id` | UUID PK | Stable key | | `code` | TEXT UNIQUE | Canonical type code, for example `letter`, `diary`, `book`, `postcard` | | `label` | TEXT | UI display label | | `is_active` | BOOLEAN | Soft-enable/disable type | | `sort_order` | INTEGER NULL | Optional UI ordering | | `created_at` | TIMESTAMPTZ | Audit timestamp | | `updated_at` | TIMESTAMPTZ | Audit timestamp | #### `document` update | Column | Type | Notes | | --- | --- | --- | | `document_type_id` | UUID FK NULL | -> `document_type.id` | ### Constraints and Governance - `document_type.code` must be stable and unique. - `document_type.label` may change without changing canonical type identity. - Inactive types remain valid for historical records but are excluded from default create/edit selectors. ### Initial Seeds - Seed baseline type codes from current V3 usage set (for example `letter`, `diary`, `book`, `postcard`, `record`, `memo`) and refine labels as needed. ### Migration and Normalization (Small Corpus) 1. Seed canonical `document_type` rows. 2. Manually assign each existing document (10 total) to a canonical type via `document_type_id`. 3. Resolve any outlier values directly during this one-time pass. 4. Enforce registry-backed write validation after manual assignment is complete. ### Indexing Guidance (Document Type) - `document_type(code)` unique index. - `document(document_type_id)` index. ## Related Local References - [V4 Scope Boundary](scope_boundary_v4.md) - [V4 Requirements](requirements_v4.md) - [V3 Schema](../schema_v3.md)