407 lines
13 KiB
Markdown
407 lines
13 KiB
Markdown
# 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';
|
||
|
||
<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.
|
||
- [ ] 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.
|