13 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
.ymlextension. - Keep one record per file.
- Keep one
index.ymlin 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
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.
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:indexdeterministically rebuilds collection and relationship indexes.pnpm data:validatevalidates schemas, IDs, files, and relationship integrity.pnpm data:checkregenerates 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 infeature_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:
- reads
data/database.ymland all collection indexes; - parses the referenced
.ymlrecords; - validates records and relationships;
- constructs typed entity maps and reverse indexes;
- publishes one immutable client data module with
actions.createData; - 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:
EpicTreeEpicDetailsFeatureDetailsFeatureTasksRequirementDetailsRequirementsForFeatureArchitectureDecisionDetailsArchitectureDecisionsForFeatureRequirementFeatureMatrixDecisionFeatureMatrixTraceabilityMatrix
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:
- Add or edit the authoritative record in its dedicated folder.
- Preserve existing stable IDs.
- Run
pnpm data:index. - Review source and derived index changes together.
- Run
pnpm data:validate. - Run
pnpm data:check. - Run the Docusaurus typecheck and production build.
- Exercise the affected React view in a browser.
- Commit records and indexes together.
- 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
.ymlfiles. - 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.