aero-agent-roles 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/NOTICE +18 -0
  2. package/README.md +49 -0
  3. package/bin/aero-agent-roles.js +127 -0
  4. package/lib/catalog.js +97 -0
  5. package/lib/install.js +48 -0
  6. package/lib/mcp.js +199 -0
  7. package/manifest.json +516 -0
  8. package/package.json +44 -0
  9. package/roles/aerodynamics-engineer/ROLE.md +153 -0
  10. package/roles/aerodynamics-engineer/SOURCES.md +12 -0
  11. package/roles/aerodynamics-engineer/cli.py +143 -0
  12. package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
  13. package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
  14. package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
  15. package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
  16. package/roles/aircraft-design-engineer/ROLE.md +138 -0
  17. package/roles/aircraft-design-engineer/SOURCES.md +12 -0
  18. package/roles/aircraft-design-engineer/cli.py +152 -0
  19. package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
  20. package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
  21. package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
  22. package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
  23. package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
  24. package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
  25. package/roles/airworthiness-compliance-engineer/cli.py +146 -0
  26. package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
  27. package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
  28. package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
  29. package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
  30. package/roles/as9100-quality-auditor/ROLE.md +128 -0
  31. package/roles/as9100-quality-auditor/SOURCES.md +16 -0
  32. package/roles/as9100-quality-auditor/cli.py +136 -0
  33. package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
  34. package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
  35. package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
  36. package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
  37. package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
  38. package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
  39. package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
  40. package/roles/data-bus-avionics-engineer/cli.py +311 -0
  41. package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
  42. package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
  43. package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
  44. package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
  45. package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
  46. package/roles/do160-environmental-engineer/ROLE.md +136 -0
  47. package/roles/do160-environmental-engineer/SOURCES.md +30 -0
  48. package/roles/do160-environmental-engineer/cli.py +147 -0
  49. package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
  50. package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
  51. package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
  52. package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
  53. package/roles/do178c-cert-engineer/ROLE.md +120 -0
  54. package/roles/do178c-cert-engineer/SOURCES.md +16 -0
  55. package/roles/do178c-cert-engineer/cli.py +129 -0
  56. package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
  57. package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
  58. package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
  59. package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
  60. package/roles/do254-hardware-engineer/ROLE.md +127 -0
  61. package/roles/do254-hardware-engineer/SOURCES.md +19 -0
  62. package/roles/do254-hardware-engineer/cli.py +308 -0
  63. package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
  64. package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
  65. package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
  66. package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
  67. package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
  68. package/roles/engineering-analysis-engineer/ROLE.md +154 -0
  69. package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
  70. package/roles/engineering-analysis-engineer/cli.py +151 -0
  71. package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
  72. package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
  73. package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
  74. package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
  75. package/roles/flight-management-engineer/ROLE.md +160 -0
  76. package/roles/flight-management-engineer/SOURCES.md +30 -0
  77. package/roles/flight-management-engineer/cli.py +258 -0
  78. package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
  79. package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
  80. package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
  81. package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
  82. package/roles/flight-mechanics-engineer/ROLE.md +162 -0
  83. package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
  84. package/roles/flight-mechanics-engineer/cli.py +149 -0
  85. package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
  86. package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
  87. package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
  88. package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
  89. package/roles/flight-test-engineer/ROLE.md +137 -0
  90. package/roles/flight-test-engineer/SOURCES.md +24 -0
  91. package/roles/flight-test-engineer/cli.py +135 -0
  92. package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
  93. package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
  94. package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
  95. package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
  96. package/roles/flight-test-performance-engineer/ROLE.md +165 -0
  97. package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
  98. package/roles/flight-test-performance-engineer/cli.py +319 -0
  99. package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
  100. package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
  101. package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
  102. package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
  103. package/roles/gnc-engineer/ROLE.md +141 -0
  104. package/roles/gnc-engineer/SOURCES.md +35 -0
  105. package/roles/gnc-engineer/cli.py +141 -0
  106. package/roles/gnc-engineer/core/gnc_core.py +1074 -0
  107. package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
  108. package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
  109. package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
  110. package/roles/guidance-engineer/ROLE.md +156 -0
  111. package/roles/guidance-engineer/SOURCES.md +43 -0
  112. package/roles/guidance-engineer/cli.py +280 -0
  113. package/roles/guidance-engineer/core/guidance_core.py +820 -0
  114. package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
  115. package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
  116. package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
  117. package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
  118. package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
  119. package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
  120. package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
  121. package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
  122. package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
  123. package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
  124. package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
  125. package/roles/ndt-engineer/ROLE.md +157 -0
  126. package/roles/ndt-engineer/SOURCES.md +18 -0
  127. package/roles/ndt-engineer/cli.py +316 -0
  128. package/roles/ndt-engineer/core/ndt_core.py +1590 -0
  129. package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
  130. package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
  131. package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
  132. package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
  133. package/roles/numerical-analysis-engineer/ROLE.md +158 -0
  134. package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
  135. package/roles/numerical-analysis-engineer/cli.py +414 -0
  136. package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
  137. package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
  138. package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
  139. package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
  140. package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
  141. package/roles/propulsion-engineer/ROLE.md +163 -0
  142. package/roles/propulsion-engineer/SOURCES.md +15 -0
  143. package/roles/propulsion-engineer/cli.py +168 -0
  144. package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
  145. package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
  146. package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
  147. package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
  148. package/roles/space-systems-engineer/ROLE.md +161 -0
  149. package/roles/space-systems-engineer/SOURCES.md +49 -0
  150. package/roles/space-systems-engineer/cli.py +173 -0
  151. package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
  152. package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
  153. package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
  154. package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
  155. package/roles/state-estimation-engineer/ROLE.md +154 -0
  156. package/roles/state-estimation-engineer/SOURCES.md +27 -0
  157. package/roles/state-estimation-engineer/cli.py +326 -0
  158. package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
  159. package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
  160. package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
  161. package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
  162. package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
  163. package/roles/structures-loads-engineer/ROLE.md +171 -0
  164. package/roles/structures-loads-engineer/SOURCES.md +44 -0
  165. package/roles/structures-loads-engineer/cli.py +219 -0
  166. package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
  167. package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
  168. package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
  169. package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
  170. package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
@@ -0,0 +1,761 @@
1
+ #!/usr/bin/env python3
2
+ """do254_hardware_core.py - DO-254 Airborne Electronic Hardware Engineer core.
3
+
4
+ This is the role's ENGINE: given an airborne electronic hardware item's
5
+ project facts it determines the hardware design assurance level (DAL A-E
6
+ from the function failure condition), classifies the item simple vs
7
+ complex AEH, computes verification expectations (methods, coverage
8
+ ratios, independence), configuration management depth, requirement
9
+ traceability health, and BUILDS the Plan for Hardware Aspects of
10
+ Certification (PHAC) content. It also gate-checks deliverables.
11
+ Standalone: no external repo needed.
12
+
13
+ Domain rules encoded here are public process knowledge as practiced in
14
+ the bound AeroSkills do254 leaves (hardware-planning, verification,
15
+ configuration-management, requirements-capture) and standard
16
+ certification practice summarized in FAA AC 20-152A. DO-254 text is
17
+ never reproduced - the PHAC structure is an original synthesis.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import re
23
+ from dataclasses import dataclass, field
24
+ from datetime import date
25
+
26
+ def _today() -> str:
27
+ import os
28
+ from datetime import date
29
+ return os.environ.get("ROLE_GEN_DATE", date.today().isoformat())
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Domain tables (public process knowledge)
33
+ # ---------------------------------------------------------------------------
34
+
35
+ # Hardware design assurance level from failure-condition severity
36
+ # (FAR 25.1309 / CS-25.1309 severity categories -> DAL mapping used in
37
+ # DO-254 certification practice; identical severity-to-level logic as
38
+ # the software side).
39
+ SEVERITY_TO_DAL = {
40
+ "catastrophic": "A",
41
+ "hazardous": "B",
42
+ "major": "C",
43
+ "minor": "D",
44
+ "no-safety-effect": "E",
45
+ }
46
+ DALS = ("A", "B", "C", "D", "E")
47
+
48
+ # DO-254 verification logic: requirements-based test coverage ratio by
49
+ # level: 0.98 at levels A/B, 0.95 at levels C/D (as encoded in the bound
50
+ # avionics/do254/verification leaf). Level E (no safety effect) is
51
+ # typically outside DO-254 scope: no coverage target.
52
+ COVERAGE_RATIO = {"A": 0.98, "B": 0.98, "C": 0.95, "D": 0.95, "E": None}
53
+
54
+ # Verification independence by DAL: independent verification expected at
55
+ # the higher hardware design assurance levels (A/B) per the bound
56
+ # avionics/do254/verification leaf.
57
+ INDEPENDENT = {"A": True, "B": True, "C": False, "D": False, "E": False}
58
+
59
+ # Life cycle data set by DAL: the hardware life cycle data items a PHAC
60
+ # must name. Levels with more rigor require the full set; E (no safety
61
+ # effect) is a documented exclusion. Original synthesis of the standard
62
+ # plan-of-attack categories, not reproduced text.
63
+ LIFE_CYCLE_DATA = {
64
+ "A": [
65
+ "plan for hardware aspects of certification (PHAC)",
66
+ "hardware requirements standards",
67
+ "hardware design standards",
68
+ "hardware validation plan",
69
+ "hardware verification plan",
70
+ "hardware configuration management plan",
71
+ "hardware process assurance plan",
72
+ "hardware requirements data",
73
+ "hardware design data",
74
+ "hardware verification data and results",
75
+ "hardware life cycle environment configuration index",
76
+ "hardware configuration index (HCI)",
77
+ "hardware process assurance records",
78
+ "hardware accomplishment summary (HAS)",
79
+ ],
80
+ "B": [
81
+ "plan for hardware aspects of certification (PHAC)",
82
+ "hardware requirements standards",
83
+ "hardware design standards",
84
+ "hardware validation plan",
85
+ "hardware verification plan",
86
+ "hardware configuration management plan",
87
+ "hardware process assurance plan",
88
+ "hardware requirements data",
89
+ "hardware design data",
90
+ "hardware verification data and results",
91
+ "hardware life cycle environment configuration index",
92
+ "hardware configuration index (HCI)",
93
+ "hardware process assurance records",
94
+ "hardware accomplishment summary (HAS)",
95
+ ],
96
+ "C": [
97
+ "plan for hardware aspects of certification (PHAC)",
98
+ "hardware requirements standards",
99
+ "hardware design standards",
100
+ "hardware verification plan",
101
+ "hardware configuration management plan",
102
+ "hardware requirements data",
103
+ "hardware design data",
104
+ "hardware verification data and results",
105
+ "hardware configuration index (HCI)",
106
+ "hardware process assurance records",
107
+ ],
108
+ "D": [
109
+ "plan for hardware aspects of certification (PHAC)",
110
+ "hardware requirements data",
111
+ "hardware design data",
112
+ "hardware verification plan",
113
+ "hardware verification data and results",
114
+ "hardware configuration index (HCI)",
115
+ "hardware process assurance records",
116
+ ],
117
+ "E": [
118
+ "hardware requirements data",
119
+ "hardware design data",
120
+ "hardware verification data and results",
121
+ ],
122
+ }
123
+
124
+ # Verification objectives by DAL: objective families expected per level
125
+ # (public practice: planning/verification/CM/process assurance scale with
126
+ # the level; A/B add independence and certification liaison).
127
+ VERIFICATION_FAMILIES = {
128
+ "A": ["planning", "requirements", "design", "verification", "cm",
129
+ "process-assurance", "cert-liaison"],
130
+ "B": ["planning", "requirements", "design", "verification", "cm",
131
+ "process-assurance", "cert-liaison"],
132
+ "C": ["planning", "requirements", "design", "verification", "cm",
133
+ "process-assurance"],
134
+ "D": ["planning", "requirements", "design", "verification", "cm"],
135
+ "E": [],
136
+ }
137
+
138
+ # Hardware change classes (DO-254 CM practice, bound
139
+ # configuration-management leaf): class 1 = formal ECR/ECO with baseline
140
+ # update + reverification + independent review; class 2 = documented
141
+ # lighter review.
142
+ CHANGE_CLASS_1_RATIONALE = ("form/fit/function change or safety effect "
143
+ "present or complex hardware")
144
+ CHANGE_CLASS_2_RATIONALE = ("no form/fit/function change, no safety effect, "
145
+ "simple hardware")
146
+
147
+
148
+ @dataclass
149
+ class HardwareItem:
150
+ """Project facts the role needs to build the PHAC."""
151
+ item_name: str
152
+ description: str = ""
153
+ failure_condition: str = "major" # severity category
154
+ system_safety_ref: str = "" # FHA/PSSA reference
155
+ # AEH classification inputs (bound hardware-planning leaf semantics):
156
+ has_programmable_logic: bool = False
157
+ has_internal_state: bool = False
158
+ fully_verifiable_from_top_data: bool = True
159
+ safety_significant: bool = False
160
+ technology: str = "" # CPLD / FPGA / ASIC / ...
161
+ life_cycle_model: str = "waterfall"
162
+ hdl_languages: list = field(default_factory=lambda: ["VHDL"])
163
+ target_device: str = ""
164
+ development_tools: list = field(default_factory=list)
165
+ verification_tools: list = field(default_factory=list)
166
+ tool_qualification_approach: str = "criteria-based per DO-254"
167
+ previously_developed: bool = False
168
+ pds_origin_standard: str = ""
169
+ certification_basis: str = "FAR/CS-25"
170
+ requirements: list = field(default_factory=list)
171
+
172
+ def assurance_level(self) -> str:
173
+ return SEVERITY_TO_DAL.get(self.failure_condition.lower(), "E")
174
+
175
+ def aeh_class(self) -> str:
176
+ return classify_aeh(
177
+ has_programmable_logic=self.has_programmable_logic,
178
+ has_internal_state=self.has_internal_state,
179
+ fully_verifiable_from_top_data=self.fully_verifiable_from_top_data,
180
+ safety_significant=self.safety_significant)
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # AEH classification (grounded in avionics/do254/hardware-planning leaf)
185
+ # ---------------------------------------------------------------------------
186
+
187
+ _COMPLEX_ARTIFACTS = ("phac", "requirements-capture", "conceptual-design",
188
+ "detailed-design", "verification", "configuration-management",
189
+ "process-assurance")
190
+ _SIMPLE_ARTIFACTS = ("hardware-plan", "verification", "configuration-management")
191
+
192
+
193
+ def classify_aeh(has_programmable_logic, has_internal_state,
194
+ fully_verifiable_from_top_data, safety_significant) -> str:
195
+ """Return 'complex' or 'simple' for a DO-254 AEH item.
196
+
197
+ Conservative planning default: when in doubt, treat the item as
198
+ complex so the full design assurance process applies (programmable
199
+ logic, internal state, or inability to verify behavior from top-level
200
+ data alone, or safety-significant items).
201
+ """
202
+ if has_programmable_logic or has_internal_state:
203
+ return "complex"
204
+ if not fully_verifiable_from_top_data:
205
+ return "complex"
206
+ if safety_significant:
207
+ return "complex"
208
+ return "simple"
209
+
210
+
211
+ def planning_artifacts(classification: str) -> list:
212
+ """Planning artifact set per AEH class; unknown class -> ValueError."""
213
+ if classification == "complex":
214
+ return list(_COMPLEX_ARTIFACTS)
215
+ if classification == "simple":
216
+ return list(_SIMPLE_ARTIFACTS)
217
+ raise ValueError("unknown AEH classification: %r" % (classification,))
218
+
219
+
220
+ # ---------------------------------------------------------------------------
221
+ # Verification rules (grounded in avionics/do254/verification leaf)
222
+ # ---------------------------------------------------------------------------
223
+
224
+ def _check_dal(dal: str) -> None:
225
+ if dal not in DALS:
226
+ raise ValueError("invalid DAL %r" % (dal,))
227
+
228
+
229
+ def verification_methods_for(aeh_class: str, dal: str) -> set:
230
+ """Verification methods for a DO-254 hardware item: complex AEH uses
231
+ test, analysis, and review; simple AEH uses reduced verification
232
+ (review). Unknown classes or levels outside A-D raise ValueError."""
233
+ if aeh_class not in ("simple", "complex"):
234
+ raise ValueError("unknown AEH class: %r" % (aeh_class,))
235
+ if dal not in ("A", "B", "C", "D"):
236
+ raise ValueError("invalid hardware design assurance level: %r" % (dal,))
237
+ if aeh_class == "complex":
238
+ return {"test", "analysis", "review"}
239
+ return {"review"}
240
+
241
+
242
+ def independence_required(dal: str) -> bool:
243
+ """Independent verification is expected at the higher hardware design
244
+ assurance levels (A/B)."""
245
+ _check_dal(dal)
246
+ return INDEPENDENT[dal]
247
+
248
+
249
+ def coverage_ratio(dal: str) -> float | None:
250
+ """Requirements-based test coverage ratio by level (None at E)."""
251
+ _check_dal(dal)
252
+ return COVERAGE_RATIO[dal]
253
+
254
+
255
+ def coverage_adequate(dal: str, measured_pct: float) -> bool:
256
+ """True when measured requirements-based coverage meets the level ratio."""
257
+ ratio = coverage_ratio(dal)
258
+ if ratio is None:
259
+ return True
260
+ return measured_pct >= ratio
261
+
262
+
263
+ def verification_complete(methods_used, required_methods) -> bool:
264
+ """True when every required verification method is present in the
265
+ methods used (extra methods do not fail the check)."""
266
+ return set(required_methods).issubset(set(methods_used))
267
+
268
+
269
+ def hwsw_integration_evidence(present: bool) -> bool:
270
+ """Whether hardware/software integration evidence is available for the
271
+ item (plain boolean pass-through)."""
272
+ return bool(present)
273
+
274
+
275
+ def structural_methods_text(dal: str) -> str:
276
+ """Plain-language verification expectation for the level."""
277
+ cov = coverage_ratio(dal)
278
+ indep = independence_required(dal)
279
+ indep_txt = "independent" if indep else "may be conducted by the developer"
280
+ if cov is None:
281
+ return "No requirements-based coverage target (no safety effect)."
282
+ return ("Requirements-based coverage ratio %.2f; verification %s."
283
+ % (cov, indep_txt))
284
+
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # Configuration management (grounded in avionics/do254/configuration-management)
288
+ # ---------------------------------------------------------------------------
289
+
290
+ VALID_HARDWARE_CLASSES = ("simple", "complex")
291
+ VALID_SAFETY_EFFECTS = ("none", "minor", "major", "hazardous", "catastrophic")
292
+
293
+
294
+ def hw_change_class(change: dict) -> dict:
295
+ """Classify a DO-254 hardware change as class 1 or class 2.
296
+
297
+ change: dict with keys hardware_class ('simple'/'complex'),
298
+ safety_effect ('none'/'minor'/'major'/'hazardous'/'catastrophic'),
299
+ functional_change (bool). Class 1 when form/fit/function changes,
300
+ safety effect present, or hardware is complex.
301
+ """
302
+ if not isinstance(change, dict):
303
+ raise ValueError("change must be a dict")
304
+ hw = change.get("hardware_class")
305
+ se = change.get("safety_effect")
306
+ fc = change.get("functional_change")
307
+ if hw not in VALID_HARDWARE_CLASSES:
308
+ raise ValueError("hardware_class must be simple or complex, got %r" % hw)
309
+ if se not in VALID_SAFETY_EFFECTS:
310
+ raise ValueError("safety_effect must be one of %s, got %r"
311
+ % (", ".join(VALID_SAFETY_EFFECTS), se))
312
+ if not isinstance(fc, bool):
313
+ raise ValueError("functional_change must be a bool, got %r" % fc)
314
+ if fc or se != "none" or hw == "complex":
315
+ return {"class": 1, "rationale": CHANGE_CLASS_1_RATIONALE}
316
+ return {"class": 2, "rationale": CHANGE_CLASS_2_RATIONALE}
317
+
318
+
319
+ def cm_actions(change_class_num: int) -> dict:
320
+ """Map a change class to required configuration management actions."""
321
+ if change_class_num == 1:
322
+ return {"baseline_update": True, "ecr_required": True,
323
+ "reverification_required": True, "independent_review": True}
324
+ if change_class_num == 2:
325
+ return {"baseline_update": True, "ecr_required": True,
326
+ "reverification_required": False, "independent_review": False}
327
+ raise ValueError("change class must be 1 or 2, got %r" % (change_class_num,))
328
+
329
+
330
+ def hci_entry(item: str, revision: str, baseline: str) -> str:
331
+ """Format one hardware configuration index line: 'item rev baseline'."""
332
+ if not isinstance(item, str) or not item.strip():
333
+ raise ValueError("item must be a non-empty string")
334
+ if not isinstance(revision, str) or not revision.strip():
335
+ raise ValueError("revision must be a non-empty string")
336
+ if not isinstance(baseline, str) or not baseline.strip():
337
+ raise ValueError("baseline must be a non-empty string")
338
+ return "%s %s %s" % (item.strip(), revision.strip(), baseline.strip())
339
+
340
+
341
+ # ---------------------------------------------------------------------------
342
+ # Requirements capture (grounded in avionics/do254/requirements-capture)
343
+ # ---------------------------------------------------------------------------
344
+
345
+ VAGUE_TERMS = ("suitable", "adequate", "approximately", "etc", "as required",
346
+ "or better", "and so on", "reasonable")
347
+
348
+
349
+ def req_issues(requirement: dict) -> list:
350
+ """List of issue flags for one requirement mapping.
351
+
352
+ Recognized flags: missing-id, empty-text, vague, not-traceable."""
353
+ if not isinstance(requirement, dict):
354
+ raise ValueError("requirement must be a mapping")
355
+ issues = []
356
+ rid = requirement.get("id") or ""
357
+ text = requirement.get("text") or ""
358
+ if not str(rid).strip():
359
+ issues.append("missing-id")
360
+ if not str(text).strip():
361
+ issues.append("empty-text")
362
+ lower = str(text).lower()
363
+ if any(term in lower for term in VAGUE_TERMS):
364
+ issues.append("vague")
365
+ if not requirement.get("traceable"):
366
+ issues.append("not-traceable")
367
+ return issues
368
+
369
+
370
+ def classify_derived(has_higher_level_source: bool) -> str:
371
+ """'derived' when there is no direct higher-level source, else 'allocated'."""
372
+ return "allocated" if has_higher_level_source else "derived"
373
+
374
+
375
+ def capture_readiness(requirements: list) -> tuple:
376
+ """(ready, score) fraction of requirements with no issues.
377
+
378
+ Raises ValueError on an empty list. Ready means score >= 0.7
379
+ (project-defined threshold)."""
380
+ if not requirements:
381
+ raise ValueError("requirements list must not be empty")
382
+ clean = sum(1 for req in requirements if not req_issues(req))
383
+ score = clean / float(len(requirements))
384
+ return (score >= 0.7, score)
385
+
386
+
387
+ def traceability_summary(requirements: list) -> dict:
388
+ """Traceability health of a hardware requirements set.
389
+
390
+ Returns counts of allocated (upward-traced) vs derived requirements,
391
+ derived requirements lacking a justification, and a per-requirement
392
+ issue list for the design review. Derived requirements are added
393
+ during design or safety analysis with no direct higher-level source;
394
+ they must be identified and justified and they count for verification.
395
+ """
396
+ allocated = 0
397
+ allocated_traced = 0
398
+ derived = 0
399
+ derived_justified = 0
400
+ issues_by_id = {}
401
+ for req in requirements:
402
+ issues = req_issues(req)
403
+ if classify_derived(bool(req.get("source"))) == "allocated":
404
+ allocated += 1
405
+ if req.get("traceable"):
406
+ allocated_traced += 1
407
+ else:
408
+ derived += 1
409
+ if req.get("justification"):
410
+ derived_justified += 1
411
+ if issues:
412
+ issues_by_id[req.get("id", "?")] = issues
413
+ total = len(requirements)
414
+ readiness = capture_readiness(requirements)
415
+ return {
416
+ "total": total,
417
+ "allocated": allocated,
418
+ "allocated_traced": allocated_traced,
419
+ "derived": derived,
420
+ "derived_justified": derived_justified,
421
+ "issue_count": sum(len(v) for v in issues_by_id.values()),
422
+ "issues_by_id": issues_by_id,
423
+ "readiness_score": round(readiness[1], 4),
424
+ "ready": readiness[0],
425
+ "allocated_traced_complete": (allocated > 0 and
426
+ allocated_traced == allocated),
427
+ "derived_justified_complete": derived == 0 or derived_justified == derived,
428
+ }
429
+
430
+
431
+ # ---------------------------------------------------------------------------
432
+ # PHAC builder: produces the actual deliverable content
433
+ # ---------------------------------------------------------------------------
434
+
435
+ def build_phac(item: HardwareItem) -> dict:
436
+ """Build the complete PHAC content model from project facts."""
437
+ dal = item.assurance_level()
438
+ aeh_class = item.aeh_class()
439
+ methods = sorted(verification_methods_for(aeh_class, dal)
440
+ if dal in ("A", "B", "C", "D") else [])
441
+ return {
442
+ "document_type": "Plan for Hardware Aspects of Certification",
443
+ "status": "draft-for-review",
444
+ "item": item.item_name,
445
+ "item_description": item.description,
446
+ "certification_basis": item.certification_basis,
447
+ "assurance_level": dal,
448
+ "severity_source": item.failure_condition,
449
+ "system_safety_reference": item.system_safety_ref,
450
+ "aeh_class": aeh_class,
451
+ "life_cycle_model": item.life_cycle_model,
452
+ "technology": item.technology,
453
+ "life_cycle_data": life_cycle_data(dal),
454
+ "coverage_ratio": coverage_ratio(dal),
455
+ "verification_methods": methods,
456
+ "independence": independence_required(dal),
457
+ "independence_text": ("independent" if independence_required(dal)
458
+ else "may be conducted by the developer"),
459
+ "hwsw_integration": hwsw_integration_evidence(True),
460
+ "planning_artifacts": planning_artifacts(aeh_class),
461
+ "hdl_languages": item.hdl_languages,
462
+ "target_device": item.target_device,
463
+ "development_tools": item.development_tools,
464
+ "verification_tools": item.verification_tools,
465
+ "tool_qualification_approach": item.tool_qualification_approach,
466
+ "previously_developed": item.previously_developed,
467
+ "pds_origin_standard": item.pds_origin_standard,
468
+ "objectives": VERIFICATION_FAMILIES[dal],
469
+ "traceability": traceability_summary(item.requirements or []),
470
+ "generated": _today(),
471
+ }
472
+
473
+
474
+ def life_cycle_data(dal: str) -> list:
475
+ _check_dal(dal)
476
+ return LIFE_CYCLE_DATA[dal]
477
+
478
+
479
+ def render_phac_markdown(model: dict) -> str:
480
+ """Render the PHAC content model as the deliverable markdown document."""
481
+ t = model["traceability"]
482
+ if model["coverage_ratio"] is not None:
483
+ coverage_line = ("- Requirements-based coverage ratio: "
484
+ f"{model['coverage_ratio']:.2f} "
485
+ f"(level {model['assurance_level']}).")
486
+ else:
487
+ coverage_line = ("- Requirements-based coverage ratio: none "
488
+ "(no safety effect).")
489
+ objectives = model["objectives"] or ["none (no safety effect)"]
490
+ lines = [
491
+ "# Plan for Hardware Aspects of Certification",
492
+ "",
493
+ f"**Item:** {model['item']}",
494
+ f"**Design assurance level:** {model['assurance_level']} "
495
+ f"(from {model['severity_source']} failure condition)",
496
+ f"**AEH class:** {model['aeh_class']}",
497
+ f"**Certification basis:** {model['certification_basis']}",
498
+ f"**Status:** {model['status']}",
499
+ "",
500
+ "## 1. Scope",
501
+ "",
502
+ f"This plan covers the airborne electronic hardware item "
503
+ f"{model['item']}."
504
+ + (f" {model['item_description']}" if model['item_description'] else ""),
505
+ f"The item is {model['aeh_class']} AEH per DO-254, technology "
506
+ f"{model['technology'] or 'to be defined'}.",
507
+ "",
508
+ "## 2. Design assurance level and failure-condition basis",
509
+ "",
510
+ f"The hardware design assurance level is {model['assurance_level']} "
511
+ f"based on the {model['severity_source']} failure condition"
512
+ + (f" (system safety reference {model['system_safety_reference']})"
513
+ if model['system_safety_reference'] else "") + ".",
514
+ "",
515
+ "## 3. Hardware life cycle and life cycle data",
516
+ "",
517
+ f"The {model['life_cycle_model']} life cycle is used. The hardware "
518
+ "life cycle data set for this level is:",
519
+ "",
520
+ *[f"- {d}" for d in model["life_cycle_data"]],
521
+ "",
522
+ "## 4. Hardware design assurance environment",
523
+ "",
524
+ f"- Development tools: {', '.join(model['development_tools']) or 'to be defined'}",
525
+ f"- Verification tools: {', '.join(model['verification_tools']) or 'to be defined'}",
526
+ f"- Tool qualification: {model['tool_qualification_approach']}",
527
+ "",
528
+ "## 5. Requirements capture and traceability",
529
+ "",
530
+ f"- Requirements under review: {t['total']} "
531
+ f"(allocated {t['allocated']}, derived {t['derived']}).",
532
+ f"- Allocated requirements traced upward: "
533
+ f"{t['allocated_traced']} of {t['allocated']} "
534
+ f"({'complete' if t['allocated_traced_complete'] else 'INCOMPLETE'}).",
535
+ f"- Derived requirements justified: {t['derived_justified']} of "
536
+ f"{t['derived']} "
537
+ f"({'complete' if t['derived_justified_complete'] else 'INCOMPLETE'}).",
538
+ f"- Capture readiness score: {t['readiness_score']:.2f} "
539
+ f"({'ready' if t['ready'] else 'NOT READY'}).",
540
+ "- Hardware requirements are reviewed for completeness, "
541
+ "correctness, and verifiability with unique identifiers and "
542
+ "trace links; vague wording fails the review.",
543
+ "",
544
+ "## 6. Design and implementation",
545
+ "",
546
+ f"- HDL languages: {', '.join(model['hdl_languages']) or 'to be defined'}.",
547
+ f"- Target device: {model['target_device'] or 'to be defined'}.",
548
+ "- Conceptual design and detailed design data are produced for "
549
+ "complex AEH and reviewed against the requirements.",
550
+ "",
551
+ "## 7. Verification strategy",
552
+ "",
553
+ f"- Verification methods: {', '.join(model['verification_methods']) or 'reduced (level E)'}.",
554
+ coverage_line,
555
+ f"- Verification independence: {model['independence_text']}.",
556
+ "- Hardware/software integration evidence ties the item to the "
557
+ "software it hosts.",
558
+ "",
559
+ "## 8. Configuration management",
560
+ "",
561
+ "The hardware configuration management plan defines baselines, "
562
+ "change control (ECR/ECO), and the hardware configuration index "
563
+ "(HCI). Class 1 changes (form/fit/function, safety effect, or "
564
+ "complex hardware) require baseline update, reverification, and "
565
+ "independent review.",
566
+ "",
567
+ "## 9. Process assurance",
568
+ "",
569
+ "Hardware process assurance monitors conformity of the life cycle "
570
+ "data and records objective evidence of process compliance.",
571
+ "",
572
+ "## 10. Certification liaison and compliance",
573
+ "",
574
+ "Certification authority liaison, means of compliance, and the "
575
+ "acceptance basis (AC 20-152A) are addressed with the airworthiness "
576
+ "authority. Previously developed hardware, when used, is assessed "
577
+ "under the PDS process.",
578
+ "",
579
+ "## 11. Objectives summary",
580
+ "",
581
+ *[f"- {o}" for o in objectives],
582
+ "",
583
+ "---",
584
+ f"*Generated by Aero Agent Roles do254-hardware-engineer core "
585
+ f"({model['generated']}). DRAFT for human hardware certification "
586
+ "engineer review. Not an approval document.*",
587
+ ]
588
+ return "\n".join(lines)
589
+
590
+
591
+ # ---------------------------------------------------------------------------
592
+ # Evidence gate checkers: verify a deliverable meets the role's gates
593
+ # ---------------------------------------------------------------------------
594
+
595
+ GATE_CHECKS = {
596
+ "level_identified": "assurance_level is a single valid DAL letter",
597
+ "aeh_classified": "item classified simple or complex AEH",
598
+ "life_cycle_data_present": "life cycle data set is non-empty and "
599
+ "level-appropriate",
600
+ "coverage_stated": "requirements-based coverage ratio present",
601
+ "methods_stated": "verification methods stated for the class/level",
602
+ "traceability_ready": "allocated traced, derived justified, readiness "
603
+ "score at or above 0.7",
604
+ "sign_off_honest": "document is marked draft-for-review, not approval",
605
+ }
606
+
607
+
608
+ def check_phac(model: dict) -> dict:
609
+ """Run the evidence gates against a PHAC model. Pass/fail per gate."""
610
+ dal = model.get("assurance_level", "")
611
+ t = model.get("traceability", {})
612
+ results = {
613
+ "level_identified": dal in DALS,
614
+ "aeh_classified": model.get("aeh_class") in ("simple", "complex"),
615
+ "life_cycle_data_present": bool(model.get("life_cycle_data")),
616
+ "coverage_stated": isinstance(model.get("coverage_ratio"), (int, float))
617
+ or model.get("coverage_ratio") is None,
618
+ "methods_stated": isinstance(model.get("verification_methods"), list),
619
+ "traceability_ready": (bool(t.get("allocated_traced_complete"))
620
+ and bool(t.get("derived_justified_complete"))
621
+ and bool(t.get("ready"))),
622
+ "sign_off_honest": model.get("status") == "draft-for-review",
623
+ }
624
+ results["all_pass"] = all(results.values())
625
+ return results
626
+
627
+
628
+ def check_phac_markdown(md_text: str, dal: str) -> dict:
629
+ """Gate-check the rendered markdown deliverable."""
630
+ low = md_text.lower()
631
+ level_pat = re.compile(r"design assurance level[:*\s]*" + dal.lower() + r"\b")
632
+ checks = {
633
+ "has_title": "plan for hardware aspects of certification" in low,
634
+ "has_level": bool(level_pat.search(low)),
635
+ "has_class": ("aeh class" in low),
636
+ "has_coverage": "coverage ratio" in low,
637
+ "has_independence": "independence" in low,
638
+ "has_draft_marker": "draft" in low,
639
+ "has_not_approval": "not an approval" in low,
640
+ }
641
+ checks["all_pass"] = all(checks.values())
642
+ return checks
643
+
644
+
645
+ def validate_deliverable_text(md_text: str, dal: str) -> dict:
646
+ """Public entry point used by gate tooling: check a PHAC document."""
647
+ return check_phac_markdown(md_text, dal)
648
+
649
+
650
+ # ---------------------------------------------------------------------------
651
+ # Example project (for tests and worked-example generation)
652
+ # ---------------------------------------------------------------------------
653
+
654
+ def example_requirements() -> list:
655
+ """A realistic requirements set for a CPLD/FPGA-based LRU function."""
656
+ return [
657
+ {"id": "HR-001",
658
+ "text": "The autopilot mode logic FPGA shall select the active "
659
+ "flight mode from the mode-select discretes within 50 ms "
660
+ "of the mode change command.",
661
+ "traceable": True, "source": "SYS-REQ-112"},
662
+ {"id": "HR-002",
663
+ "text": "The FPGA shall assert the autopilot engage output only "
664
+ "when all engage interlocks are satisfied.",
665
+ "traceable": True, "source": "SYS-REQ-113"},
666
+ {"id": "HR-003",
667
+ "text": "The FPGA shall de-assert the engage output within 20 ms "
668
+ "of any disengage interlock opening.",
669
+ "traceable": True, "source": "SYS-REQ-114"},
670
+ {"id": "HR-004",
671
+ "text": "The FPGA shall latch the last valid mode state across a "
672
+ "power interruption of up to 10 ms.",
673
+ "traceable": True, "source": "SYS-REQ-115"},
674
+ {"id": "HR-005",
675
+ "text": "The FPGA shall present the active mode on the ARINC 429 "
676
+ "output bus within one output frame.",
677
+ "traceable": True, "source": "SYS-REQ-116"},
678
+ {"id": "HR-006",
679
+ "text": "The FPGA shall ignore mode-select inputs while the "
680
+ "engage interlock is open.",
681
+ "traceable": True, "source": "SYS-REQ-113"},
682
+ {"id": "HR-007",
683
+ "text": "The FPGA shall drive the watchdog output with a pulse "
684
+ "train of period 10 ms plus-or-minus 1 ms while the "
685
+ "internal sequencer is running.",
686
+ "traceable": True, "source": "SYS-REQ-117"},
687
+ {"id": "HR-008",
688
+ "text": "The FPGA shall reset to the disengaged state on power-up "
689
+ "or on watchdog time-out.",
690
+ "traceable": True, "source": "SYS-REQ-114"},
691
+ {"id": "HR-009",
692
+ "text": "The FPGA shall debounce the mode-select discretes over "
693
+ "a 5 ms window before accepting a state change.",
694
+ "traceable": True, "source": "SYS-REQ-112"},
695
+ {"id": "HR-010",
696
+ "text": "The FPGA shall report internal status on the built-in "
697
+ "test output word every 100 ms.",
698
+ "traceable": True, "source": "SYS-REQ-118"},
699
+ # Derived requirement: added during detailed design, justified.
700
+ {"id": "HR-011",
701
+ "text": "The FPGA shall implement a one-hot state encoding for "
702
+ "the mode sequencer to prevent multiple-mode lockup.",
703
+ "traceable": False, "source": "",
704
+ "justification": "One-hot encoding chosen during detailed design "
705
+ "to guarantee single-mode arbitration (safety "
706
+ "analysis of mode lockup)."},
707
+ # Derived requirement: added from safety analysis, justified.
708
+ {"id": "HR-012",
709
+ "text": "The FPGA shall provide a hardware mode-select voting "
710
+ "scheme across the two mode-select discretes.",
711
+ "traceable": False, "source": "",
712
+ "justification": "Dual-discrete voting added following the "
713
+ "FPGA-level FMEA to protect against a stuck "
714
+ "discrete failure."},
715
+ ]
716
+
717
+
718
+ def example_item() -> HardwareItem:
719
+ """Reference item: a complex CPLD/FPGA-based LRU function."""
720
+ return HardwareItem(
721
+ item_name="Autopilot Mode Logic FPGA (complex CPLD/FPGA-based "
722
+ "LRU function)",
723
+ description="FPGA implementation of the autopilot flight mode "
724
+ "selection and engage logic hosted in the flight "
725
+ "guidance computer LRU.",
726
+ failure_condition="hazardous",
727
+ system_safety_ref="FHA-FGS-001 / PSSA rev C",
728
+ has_programmable_logic=True,
729
+ has_internal_state=True,
730
+ fully_verifiable_from_top_data=False,
731
+ safety_significant=True,
732
+ technology="CPLD/FPGA programmable logic",
733
+ life_cycle_model="waterfall",
734
+ hdl_languages=["VHDL"],
735
+ target_device="FPGA, flight guidance computer LRU",
736
+ development_tools=["HDL synthesis tool", "static timing analyzer"],
737
+ verification_tools=["HDL simulator", "requirements-based test "
738
+ "harness", "code coverage tool"],
739
+ tool_qualification_approach="criteria-based per DO-254 tool "
740
+ "assessment",
741
+ certification_basis="FAR/CS-25",
742
+ requirements=example_requirements(),
743
+ )
744
+
745
+
746
+ def example_phac_markdown() -> str:
747
+ return render_phac_markdown(build_phac(example_item()))
748
+
749
+
750
+ if __name__ == "__main__":
751
+ item = example_item()
752
+ model = build_phac(item)
753
+ md = render_phac_markdown(model)
754
+ print(f"ASSURANCE LEVEL: {model['assurance_level']}")
755
+ print(f"AEH CLASS: {model['aeh_class']}")
756
+ print(f"COVERAGE RATIO: {model['coverage_ratio']:.2f}")
757
+ print(f"INDEPENDENCE: {model['independence_text']}")
758
+ print(f"LIFE CYCLE DATA: {len(model['life_cycle_data'])} items")
759
+ print(f"TRACEABILITY: {model['traceability']}")
760
+ print(f"GATES: {check_phac(model)}")
761
+ print(f"RENDERED: {len(md)} chars")