# Local YAML Data Database Use this pattern when structured documentation data must live in the documentation repository, remain reviewable through Git, and drive React components and MDX pages without duplicating entity content inside MDX. ## Contract The source database is a local `data/` tree. It is not a runtime service and it is not a collection of generated MDX pages. - Every database source file uses the `.yml` extension. - Keep one record per file. - Keep one `index.yml` in every leaf collection. - Requirements live only under dedicated requirement folders. - Architectural decisions live under the exact folder `data/architecture-decisions/`. - Stable IDs, not titles or paths, define relationships. - Load and validate data during the Docusaurus build-time phase. - React components consume typed build data; they do not read the filesystem in the browser. - MDX composes components and explanatory prose; it does not duplicate structured record content. A JSON module created by Docusaurus inside `.docusaurus/` is an allowed generated build artifact. It is not a maintained database source and must not be committed as authoritative data. ## Required structure ```text data/ ├── database.yml ├── epics/ │ ├── index.yml │ └── epic-001.yml ├── features/ │ ├── index.yml │ └── feature-001.yml ├── tasks/ │ ├── index.yml │ └── task-001.yml ├── requirements/ │ ├── functional/ │ │ ├── index.yml │ │ └── fr-001.yml │ └── non-functional/ │ ├── index.yml │ └── nfr-001.yml ├── architecture-decisions/ │ ├── index.yml │ └── adr-001.yml └── indexes/ ├── epic-features.yml ├── feature-tasks.yml ├── feature-requirements.yml └── feature-architecture-decisions.yml ``` Do not combine functional requirements, non-functional requirements, architectural decisions, or delivery entities into one catch-all collection. Do not create a generic decision folder. The explicit folders are part of the data contract. `database.yml` declares the database schema version and enabled collections: ```yaml schema_version: 1 collections: - epics - features - tasks - requirements/functional - requirements/non-functional - architecture-decisions ``` ## Stable identifiers and filenames Use lowercase stable IDs with a type prefix and numeric sequence: | Entity | ID example | Filename | |---|---|---| | Epic | `epic-001` | `epic-001.yml` | | Feature | `feature-001` | `feature-001.yml` | | Functional requirement | `fr-001` | `fr-001.yml` | | Non-functional requirement | `nfr-001` | `nfr-001.yml` | | Task | `task-001` | `task-001.yml` | | Architectural decision | `adr-001` | `adr-001.yml` | The filename stem must equal the record's `id`. Never reuse an ID, even after archiving a record. Renaming a title must not change its ID. ## Entity records ### Epic ```yaml id: epic-001 title: Project lifecycle registry status: in-progress summary: > Provide a traceable registry of project lifecycle information. owners: - root-at-skic tags: - governance - lifecycle ``` ### Feature A Feature belongs to one Epic. The Feature owns that relationship through `epic_id`. ```yaml id: feature-001 title: Display project lifecycle state status: in-progress epic_id: epic-001 depends_on_feature_ids: - feature-000 roadmap: start: 2026-08-01 end: 2026-08-21 lane: portal progress: 60 summary: > Display the current lifecycle state and supporting evidence. owners: - root-at-skic ``` `depends_on_feature_ids` is the canonical Feature dependency field. `roadmap` is optional unless the documentation exposes a date-based roadmap. When present, validate ISO dates, require `end` not to precede `start`, require a stable lane, and constrain `progress` to 0–100. React Flow adapters may derive dependency edges and Gantt-like positions from these fields; TSX and MDX must not maintain separate schedules or dependency lists. ### Task A Task belongs to one Feature. The Task owns that relationship through `feature_id`. ```yaml id: task-001 title: Implement the lifecycle state component status: in-backlog feature_id: feature-001 summary: > Render lifecycle status from the local data source. ``` ### Functional requirement Functional requirements remain in `data/requirements/functional/`. A requirement owns its many-to-many Feature mapping through `feature_ids`. ```yaml id: fr-001 type: functional title: Display lifecycle state status: approved statement: > The portal must display the current lifecycle state for every project. feature_ids: - feature-001 - feature-004 ``` ### Non-functional requirement Non-functional requirements remain in `data/requirements/non-functional/` and use the same relationship field. ```yaml id: nfr-001 type: non-functional category: performance title: Resolve documentation data during the static build status: approved statement: > Documentation pages must not require a runtime database service. feature_ids: - feature-001 - feature-002 ``` ### Architectural decision Architectural decisions remain in `data/architecture-decisions/`. A decision owns its many-to-many Feature mapping through `feature_ids`. ```yaml id: adr-001 title: Use local YAML records for structured documentation status: accepted date: 2026-08-14 context: > Structured entities must be reused across several documentation views. decision: > Store authoritative records in local YAML files and resolve them during the static documentation build. consequences: - Documentation changes remain Git-reviewable. - The deployed site does not require a database service. feature_ids: - feature-001 - feature-002 ``` ## Relationship ownership Store each relationship in one canonical direction only: | Relationship | Cardinality | Canonical owner | |---|---:|---| | Epic to Feature | 1:n | Feature stores `epic_id` | | Feature to Task | 1:n | Task stores `feature_id` | | Requirement to Feature | n:m | Requirement stores `feature_ids` | | Architectural decision to Feature | n:m | Architectural decision stores `feature_ids` | Do not also store `feature_ids` on Epics, `task_ids` on Features, or reverse requirement and decision IDs on Features. Generate reverse lookups in `data/indexes/`. This prevents two editable fields from disagreeing about the same relationship. ## Collection indexes Each leaf collection has an `index.yml` containing stable IDs and relative record paths. Sort entries by ID. ```yaml schema_version: 1 items: - id: feature-001 file: feature-001.yml - id: feature-002 file: feature-002.yml ``` Collection indexes are generated and committed. They make reviews explicit and allow tools to discover records without relying on filesystem ordering. ## Relationship indexes Relationship indexes are derived from canonical record fields. For example, `data/indexes/feature-requirements.yml`: ```yaml schema_version: 1 features: feature-001: functional: - fr-001 non_functional: - nfr-001 feature-002: functional: [] non_functional: - nfr-001 ``` And `data/indexes/feature-architecture-decisions.yml`: ```yaml schema_version: 1 features: feature-001: architecture_decisions: - adr-001 feature-002: architecture_decisions: - adr-001 ``` Never edit derived relationship indexes manually. Regenerate them from source records. ## Maintenance commands Every adopting documentation repository must expose these commands: ```text pnpm data:index pnpm data:validate pnpm data:check ``` - `pnpm data:index` deterministically rebuilds collection and relationship indexes. - `pnpm data:validate` validates schemas, IDs, files, and relationship integrity. - `pnpm data:check` regenerates indexes in memory or a temporary directory and fails when committed indexes are stale. Index generation must not add timestamps or machine-specific paths. Running it twice against unchanged records must produce byte-identical output. ## Validation requirements Validation must fail for: - any source database file whose extension is not `.yml`; - a missing collection `index.yml`; - a duplicate record ID; - a filename stem that differs from the record ID; - an unknown entity type, status, or requirement category; - a required field with an empty value; - a dangling `epic_id`, `feature_id`, or entry in `feature_ids`; - a dangling entry in `depends_on_feature_ids`; - an invalid roadmap date, reversed date range, missing lane, or progress outside 0–100; - an index entry whose record is missing; - a record missing from its collection index; - a stale relationship index; - a record placed in the wrong collection; - an architectural decision outside `data/architecture-decisions/`; - a functional or non-functional requirement outside its dedicated folder. Run validation before the Docusaurus build and in Gitea Actions. A successful site build is not sufficient if data validation was skipped. ## Docusaurus build-time loading Browsers cannot safely discover repository files at runtime. Implement a local Docusaurus plugin that: 1. reads `data/database.yml` and all collection indexes; 2. parses the referenced `.yml` records; 3. validates records and relationships; 4. constructs typed entity maps and reverse indexes; 5. publishes one immutable client data module with `actions.createData`; 6. optionally registers generic routes for entity details and traceability views. Keep parsing, validation, and relationship resolution on the Node build side. Do not bundle filesystem APIs or independent YAML parsers into each React component. A minimal plugin shape is: ```ts export default function localYamlDataPlugin() { return { name: 'local-yaml-data', async loadContent() { return loadAndValidateDatabase('data'); }, async contentLoaded({content, actions}) { await actions.createData( 'documentation-database.json', JSON.stringify(content), ); }, }; } ``` Treat the generated module as derived data. Rebuild it from the YAML source on every production build. ## Typed React access Provide one data context and stable hooks rather than letting components know storage paths: ```ts const epic = useEpic('epic-001'); const features = useFeaturesForEpic('epic-001'); const tasks = useTasksForFeature('feature-001'); const requirements = useRequirementsForFeature('feature-001'); const decisions = useArchitectureDecisionsForFeature('feature-001'); const traceability = useFeatureTraceability('feature-001'); ``` Recommended components: - `EpicTree` - `EpicDetails` - `FeatureDetails` - `FeatureTasks` - `RequirementDetails` - `RequirementsForFeature` - `ArchitectureDecisionDetails` - `ArchitectureDecisionsForFeature` - `RequirementFeatureMatrix` - `DecisionFeatureMatrix` - `TraceabilityMatrix` Components receive stable IDs or filter properties. They must render a clear missing-record state rather than silently returning empty output. ## MDX composition MDX pages provide page purpose, explanation, and component placement. They must not copy record fields that already exist in the database. ```mdx --- title: "Explore delivery scope" description: "Inspect Epics, Features, Tasks, requirements, and decisions." --- import EpicTree from '@site/src/components/documentation-data/EpicTree'; import TraceabilityMatrix from '@site/src/components/documentation-data/TraceabilityMatrix'; ``` Use a generic `FeatureDetails` component or generated route for individual Features instead of generating and maintaining one content-heavy MDX file per Feature. ## Change workflow For every data change: 1. Add or edit the authoritative record in its dedicated folder. 2. Preserve existing stable IDs. 3. Run `pnpm data:index`. 4. Review source and derived index changes together. 5. Run `pnpm data:validate`. 6. Run `pnpm data:check`. 7. Run the Docusaurus typecheck and production build. 8. Exercise the affected React view in a browser. 9. Commit records and indexes together. 10. Verify the Gitea Actions run and deployed documentation route. For schema evolution, increment `schema_version`, migrate all records and indexes atomically, and keep the loader's error message explicit about unsupported versions. ## Acceptance checklist - [ ] Database source uses only `.yml` files. - [ ] Requirements are separated into functional and non-functional folders. - [ ] Architectural decisions use exactly `data/architecture-decisions/`. - [ ] Every entity has one stable-ID record file. - [ ] Every leaf collection has a deterministic `index.yml`. - [ ] Canonical relationship fields implement Epic → Feature → Task and n:m mappings to requirements and architectural decisions. - [ ] Feature dependencies and optional roadmap scheduling remain in source YAML and pass referential and date-range validation. - [ ] Reverse indexes are generated, committed, and stale-index checked. - [ ] Referential validation rejects duplicate and dangling IDs. - [ ] Docusaurus loads data at build time. - [ ] React components consume typed hooks rather than parsing source files. - [ ] MDX contains composition and explanatory content rather than duplicate records. - [ ] Data checks, typecheck, production build, browser view, Actions, and deployed route are verified.