feat: define local YAML documentation database
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
# 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
|
||||
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`.
|
||||
|
||||
```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`;
|
||||
- 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.
|
||||
- [ ] 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.
|
||||
Reference in New Issue
Block a user