Skip to content

Commit ef177f1

Browse files
nesonoclaude
andcommitted
Add design document for configurable document types (#224)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 62d9b67 commit ef177f1

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Design: Configurable Document Types for FIRE (Issue #224)
2+
3+
## Context
4+
5+
FIRE currently hardcodes three document types (`.sysreq.md`, `.swreq.md`, `.regreq.md`)
6+
with validation rules embedded in Python Pydantic models. Systems engineering teams need
7+
many more document types (`.handbook.md`, `.ipsra.md`, `.opman.md`, etc.) each with
8+
different mandatory/optional fields. The issue asks for a consumer-configurable system
9+
where document formats are defined in YAML, validated by FIRE tooling via Bazel, and
10+
auto-generated into a FORMAT_SPECIFICATION.md usable as LLM context.
11+
12+
## Design: YAML Config Schema
13+
14+
A `fire_config.yaml` in the consumer's repo defines field types and document types:
15+
16+
```yaml
17+
fire_config_version: 1
18+
19+
field_definitions:
20+
sil:
21+
display_name: "SIL"
22+
type: enum
23+
values:
24+
[
25+
"ASIL-A",
26+
"ASIL-B",
27+
"ASIL-C",
28+
"ASIL-D",
29+
"SIL-1",
30+
"SIL-2",
31+
"SIL-3",
32+
"SIL-4",
33+
"DAL-A",
34+
"DAL-B",
35+
"DAL-C",
36+
"DAL-D",
37+
"DAL-E",
38+
"QM",
39+
]
40+
allow_todo: true
41+
description: "Safety Integrity Level (ISO 26262, IEC 61508, DO-178C/DO-254, QM)"
42+
43+
sec:
44+
display_name: "Sec"
45+
type: bool
46+
allow_todo: true
47+
description: "Security relevance flag"
48+
49+
version:
50+
display_name: "Version"
51+
type: int
52+
min_value: 1
53+
allow_todo: false
54+
description: "Requirement version, positive integer >= 1"
55+
56+
parent:
57+
display_name: "Parent"
58+
type: parent_link
59+
allow_todo: true
60+
allow_multiple: true
61+
description: "Markdown link(s) to parent requirement(s)"
62+
63+
document_types:
64+
sysreq:
65+
suffix: ".sysreq.md"
66+
display_name: "System Requirement"
67+
description: "High-level system requirements"
68+
required_fields: [sil, sec, version]
69+
optional_fields: [parent]
70+
71+
swreq:
72+
suffix: ".swreq.md"
73+
display_name: "Software Requirement"
74+
description: "Implementation-level software requirements"
75+
required_fields: [sil, sec, version]
76+
optional_fields: [parent]
77+
78+
regreq:
79+
suffix: ".regreq.md"
80+
display_name: "Regulatory Requirement"
81+
description: "Regulatory obligations (SIL/Sec optional)"
82+
required_fields: [version]
83+
optional_fields: [sil, sec, parent]
84+
```
85+
86+
Consumers extend this by adding their own types:
87+
88+
```yaml
89+
handbook:
90+
suffix: ".handbook.md"
91+
display_name: "Handbook Entry"
92+
description: "Operational handbook entries"
93+
required_fields: [version]
94+
optional_fields: []
95+
```
96+
97+
## Key Architectural Decisions
98+
99+
- **`field_definitions`** are reusable across document types (sil is identical for sysreq and swreq)
100+
- **Field types**: `enum`, `bool`, `int`, `parent_link` — matching existing Pydantic validators. Extensible later with `string`, `float`, `regex`
101+
- **Default config** ships with FIRE and matches current hardcoded behavior — backwards compatible with no consumer config
102+
- **Dynamic Pydantic models** built at runtime from config, preserving error reporting quality and `extra: "forbid"` behavior
103+
104+
## Files to Create
105+
106+
| File | Purpose |
107+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
108+
| `fire/starlark/config_models.py` | Pydantic models for config schema (`FireConfig`, `FieldDefinition`, `DocumentType`) + `load_config()` + embedded default |
109+
| `fire/starlark/config_models_test.py` | Tests for config parsing and validation |
110+
| `fire/starlark/dynamic_requirement_model.py` | Factory: config -> Pydantic BaseModel subclass at runtime |
111+
| `fire/starlark/dynamic_requirement_model_test.py` | Tests proving dynamic models match static ones |
112+
| `fire/starlark/default_fire_config.yaml` | Default config file shipped with FIRE |
113+
| `fire/starlark/generate_format_spec.py` | Reads config, produces FORMAT_SPECIFICATION.md |
114+
| `fire/starlark/generate_format_spec_test.py` | Tests for spec generation |
115+
| `fire/starlark/format_spec.bzl` | Bazel rule `generate_format_specification()` |
116+
117+
## Files to Modify
118+
119+
| File | Change |
120+
| -------------------------------------------- | ---------------------------------------------------------------------------------------- |
121+
| `fire/starlark/validate_cross_references.py` | `_metadata_model_for_file()` -> config-driven suffix lookup; `main()` accepts `--config` |
122+
| `fire/starlark/requirements.bzl` | `requirement_library()` gains optional `config` attr, passed to Python script |
123+
| `fire/starlark/release_report.py` | `main()` accepts `--config`; passes to metadata parsing |
124+
| `fire/starlark/reports.bzl` | `release_report` rule gains optional `config` attr |
125+
| `fire/starlark/BUILD.bazel` | New build targets for new files |
126+
| `FORMAT_SPECIFICATION.md` | Replaced by auto-generated output |
127+
128+
## Bazel Integration
129+
130+
```text
131+
fire_config.yaml ─┬──> requirement_library(config=":fire_config")
132+
│ └─> validate_cross_references.py --config fire_config.yaml
133+
134+
├──> release_report(config=":fire_config")
135+
│ └─> release_report.py --config fire_config.yaml
136+
137+
└──> generate_format_specification(config=":fire_config", out="FORMAT_SPEC.md")
138+
└─> generate_format_spec.py --config fire_config.yaml
139+
```
140+
141+
Config is `attr.label(allow_single_file=[".yaml",".yml"], default=None)`. When None, Python scripts use the embedded default.
142+
143+
## Implementation Phases
144+
145+
### Phase 1: Config Models (no behavior change)
146+
147+
1. Create `config_models.py` with Pydantic models for config schema
148+
2. Create `default_fire_config.yaml` matching current behavior
149+
3. Add `load_config()` that loads YAML or returns default
150+
4. Tests: config parses correctly, invalid configs rejected
151+
152+
### Phase 2: Dynamic Model Factory (no behavior change)
153+
154+
1. Create `dynamic_requirement_model.py` — builds Pydantic model from config
155+
2. Tests: dynamic models produce identical validation to static `RequirementMetadata` and `RegulatoryRequirementMetadata` for all existing test cases
156+
157+
### Phase 3: Wire Config into Validation
158+
159+
1. Modify `validate_cross_references.py`: config-driven `_metadata_model_for_file()` and `--config` arg
160+
2. Modify `requirements.bzl`: optional `config` attr
161+
3. Modify `release_report.py` and `reports.bzl`: optional `config` attr
162+
4. Tests: existing tests still pass, new test with custom document type
163+
164+
### Phase 4: Format Specification Generator
165+
166+
1. Create `generate_format_spec.py` and `format_spec.bzl`
167+
2. Replace hand-written `FORMAT_SPECIFICATION.md` with auto-generated output
168+
3. Tests: generated output covers all document types and fields
169+
170+
### Phase 5: Documentation and Examples
171+
172+
1. Update README.md with config section
173+
2. Add example with custom document type
174+
175+
## Verification
176+
177+
- All existing tests pass unchanged (backwards compatibility)
178+
- New unit tests for config parsing, dynamic model generation, format spec generation
179+
- Integration test: add custom document type to `integration_test/`, validate it works end-to-end
180+
- Verify generated FORMAT_SPECIFICATION.md is equivalent to current hand-written one

0 commit comments

Comments
 (0)