Files
documentation-docusaurus-3d…/references/local-yaml-data-database.md
T

12 KiB

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

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:

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

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.

id: feature-001
title: Display project lifecycle state
status: in-progress
epic_id: epic-001
summary: >
  Display the current lifecycle state and supporting evidence.
owners:
  - root-at-skic

Task

A Task belongs to one Feature. The Task owns that relationship through feature_id.

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.

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.

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.

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.

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:

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:

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:

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;
  • 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:

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:

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.

---
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';

<EpicTree />

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