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,99 @@
1
+ # Concept Design Package - RegionalJet-1500 (100 pax / 1500 nm / M0.78)
2
+
3
+ **Document type:** Concept Design Package
4
+ **Status:** draft-for-review
5
+ **Aircraft class:** transport (class-I conceptual sizing)
6
+ **Generated:** 2026-09-05
7
+
8
+ ## 1. Requirements and design space
9
+
10
+ | Requirement | Value |
11
+ |---|---|
12
+ | Passengers | 100 pax |
13
+ | Payload | 20,000 lb (100 pax x 200 lb + baggage, class-I) |
14
+ | Design range | 1,500 nm |
15
+ | Cruise | M0.78 at 10,668 m (FL350), 450 kt TAS |
16
+ | Takeoff field length | 1,829 m (6,001 ft) |
17
+ | Engines | 2 turbofans |
18
+
19
+ Constraint diagram (matching chart, T/W vs W/S; SI inputs, sea-level ISA for takeoff, cruise at FL350):
20
+
21
+ | Constraint | Rule | Value at design W/S |
22
+ |---|---|---|
23
+ | Stall (max W/S) | W/S = 0.5*rho*CLmax*VS^2 | 5,512 N/m^2 (115.1 psf) |
24
+ | Takeoff | T/W = 1.21*(W/S)/(rho*g*CLmax*s_TO) | 0.1404 |
25
+ | Climb | T/W = 1/LD + gamma (OEI -> installed, x n/(n-1)) | 0.2147 |
26
+ | Cruise | T/W = q*CD0/(W/S) + k*(W/S)/q | 0.0569 |
27
+
28
+ **Selected design point:** W/S = 5,100 N/m^2 (106.5 psf), T/W = 0.2147 (binding constraint: climb).
29
+ **Feasible:** True - the point lies on the lower bound of the feasible region (stall limit not exceeded).
30
+ Feasible-region boundary samples (W/S N/m^2 -> minimum T/W): 1,378 -> 0.2147, 1,895 -> 0.2147, 2,412 -> 0.2147, 2,929 -> 0.2147, 3,445 -> 0.2147, 3,962 -> 0.2147, 4,479 -> 0.2147, 4,996 -> 0.2147, 5,512 -> 0.2147.
31
+
32
+ ## 2. Sizing mission
33
+
34
+ Mission profile (segments in flight order; each burns from the weight remaining after earlier segments, Breguet range/endurance models):
35
+
36
+ | Segment | Fuel burned | Fraction of MTOW |
37
+ |---|---|---|
38
+ | Taxi | 300 lb | 0.2% of MTOW |
39
+ | Takeoff | 900 lb | 0.7% of MTOW |
40
+ | Climb | 6,000 lb | 5.0% of MTOW |
41
+ | Cruise (1,500 nm @ 450 kt) | 30,057 lb | 25.0% of MTOW |
42
+ | Descent | 750 lb | 0.6% of MTOW |
43
+
44
+ | **Block fuel** | **38,007 lb** | **31.6%** |
45
+ | Block time | 4.19 hr | |
46
+ | Landing weight | 82,292 lb | |
47
+ | Reserve (hold45_5pct: 45 min hold at 1500 ft + 5% trip) | 3,932 lb | |
48
+ | **Required fuel (mission + reserves)** | **41,939 lb** | **34.9% of MTOW** |
49
+
50
+ ## 3. Configuration (from the design point)
51
+
52
+ - Wing loading W/S: 5,100 N/m^2 (106.5 psf) - design choice, 92.5% of the stall-limited maximum.
53
+ - Wing area S = W/(W/S) = 104.9 m^2 (1,129 ft^2); aspect ratio 9.5; span b = 31.6 m.
54
+ - Drag model: CD0 = 0.018, e = 0.80, k = 1/(pi*e*AR) = 0.0419; L/D_max = 18.2 (consistent with the mission cruise L/D of 18).
55
+ - Engines: 2 x ~12,912 lbf class sea-level static thrust (see Section 5).
56
+ - 3-view reference geometry from the OpenVSP stage (initial layout) feeds the class-II mass build-up; not required for the class-I sizing closure.
57
+
58
+ ## 4. Mass statement (class-I, balances to converged MTOW)
59
+
60
+ | Group | Weight (lb) | % MTOW |
61
+ |---|---|---|
62
+ | Operating empty weight (class-I fit 0.485 x MTOW) | 58,345 | 48.5% |
63
+ | Design payload (100 pax x 200 lb + cargo) | 20,000 | 16.6% |
64
+ | Fuel (mission + reserves, hold45_5pct) | 41,939 | 34.9% |
65
+ | **Maximum takeoff weight (converged)** | **120,298** | **100.0%** |
66
+
67
+ - Balance check (empty + payload + fuel vs MTOW): PASS within 1%.
68
+ - Empty-weight fraction 0.485 inside the transport class-I band 0.42-0.55: PASS.
69
+ - Weight-growth context: conceptual phase growth allowance 10% applies to the detailed mass budget roll-up (mass-budget stage).
70
+ - CG envelope and critical loading conditions require the 3-view layout and the mass-properties stage (component arms); the class-I closure above bounds the balance (fuel + payload = 61,939 lb available below MTOW - OEW = 61,954 lb).
71
+
72
+ ## 5. MTOW convergence and engine size check
73
+
74
+ Fuel-fraction sizing iteration (W0 = payload / (1 - W_e/W0 - W_f/W0), class-I empty fraction 0.485):
75
+
76
+ | Iteration | Guess (lb) | Fuel fraction | New MTOW (lb) | Rel. change |
77
+ |---|---|---|---|---|
78
+ | 1 | 100,000 | 0.3591 | 128,259 | 28.26% |
79
+ | 2 | 128,259 | 0.3454 | 117,944 | 8.04% |
80
+ | 3 | 117,944 | 0.3496 | 120,955 | 2.55% |
81
+ | 4 | 120,955 | 0.3483 | 120,007 | 0.78% |
82
+ | 5 | 120,007 | 0.3487 | 120,298 | 0.24% |
83
+
84
+ **Converged MTOW = 120,298 lb (54,566 kg)** after 5 iterations (tolerance 0.5%): CONVERGED.
85
+
86
+ - Installed sea-level thrust: 25,824 lbf total = 12,912 lbf per engine (T/W = 0.215).
87
+ - Engine weight estimate: ~2,582 lb per engine at engine T/W = 5.0 (4-6 typical band).
88
+ - Top-of-climb margin at 10,668 m (thrust lapse sigma^0.7 vs W/(L/D)): 1.81 - PASS (>= 1.0).
89
+ - Engine-out second-segment gradient is carried on the installed basis: T/W = (n/(n-1)) x (1/LD + gamma) with LD = 12 and gamma = 0.024 (FAR 25.121(b)(1) two-engine value, 2 engines); the engine-sizing stage re-checks OEI climb, hot day, and takeoff derate against the catalogue engine.
90
+
91
+ ## 6. Feasibility notes and next stages
92
+
93
+ - Class-I results are FEASIBILITY, not detailed design: the converged MTOW, the W/S-T/W design point, and the mass statement close the sizing loop within the stated class-I model accuracy.
94
+ - Structure/propulsion/systems mass split requires the class-II component build-up (weight-estimation, engine-sizing, fuselage/wing/tail-sizing leaves); the conceptual growth allowance (10%) is the margin policy for that roll-up.
95
+ - Payload-range, cost (parametric DOC/LCC), and CG envelope are produced by the dedicated stages (payload-range-diagram, parametric-cost, cg-envelope) once the class-II breakdown exists.
96
+
97
+ ---
98
+
99
+ *DRAFT - conceptual feasibility for human design review. Not a production release and not an approval document. Conceptual results are class-I estimates; every configuration choice must trace to the trade data before detailed design.*
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """Test aircraft_design_core: the executable engine of the aircraft
3
+ conceptual design engineer role.
4
+
5
+ Proves the role can DO its job standalone (no AeroSkills needed):
6
+ constraint-analysis T/W rules, mission fuel-fraction models, class-I
7
+ empty-weight fit, MTOW convergence within 0.5%, concept package
8
+ generation, and evidence-gate checks.
9
+ """
10
+ import math
11
+ import os
12
+ import sys
13
+ import unittest
14
+
15
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "core"))
16
+ from aircraft_design_core import ( # noqa: E402
17
+ EMPTY_WEIGHT_FRACTION_BANDS, breguet_cruise_fuel_fraction,
18
+ breguet_loiter_fuel_fraction, build_concept, chain_mission, check_concept,
19
+ check_concept_markdown, check_empty_weight_fraction, climb_constraint_tw,
20
+ constraint_tw, cruise_constraint_tw, empty_weight_fraction,
21
+ example_item, example_concept_markdown, render_concept_markdown,
22
+ segment_fuel_fraction, size_mtow, stall_max_ws, takeoff_constraint_tw,
23
+ tow_estimate, weight_breakdown_ok,
24
+ )
25
+
26
+ G = 9.80665
27
+ RHO_SL = 1.225
28
+
29
+
30
+ class TestConstraintAnalysis(unittest.TestCase):
31
+ """Matching-chart rules must match the AeroSkills leaf formulas."""
32
+
33
+ def test_stall_max_ws(self):
34
+ # W/S = 0.5*rho*CLmax*VS^2: 0.5*1.225*2.5*60^2
35
+ self.assertAlmostEqual(stall_max_ws(60.0, 2.5), 0.5 * 1.225 * 2.5 * 3600.0)
36
+ with self.assertRaises(ValueError):
37
+ stall_max_ws(-1.0, 2.5)
38
+
39
+ def test_takeoff_constraint(self):
40
+ # T/W = 1.21*(W/S)/(rho*g*CLmax*s_TO) with clean SI inputs
41
+ ws, rho, cl, s_to = 4000.0, 1.225, 2.0, 1500.0
42
+ expected = 1.21 * ws / (rho * G * cl * s_to)
43
+ self.assertAlmostEqual(takeoff_constraint_tw(ws, rho, cl, s_to), expected)
44
+ # dispatcher agrees with the direct leaf call
45
+ self.assertAlmostEqual(
46
+ constraint_tw("takeoff", W_S=ws, rho=rho, CLmax=cl, s_TO=s_to),
47
+ expected)
48
+ with self.assertRaises(ValueError):
49
+ takeoff_constraint_tw(-5.0, rho, cl, s_to)
50
+
51
+ def test_climb_constraint(self):
52
+ # T/W = 1/LD + gamma
53
+ self.assertAlmostEqual(climb_constraint_tw(20.0, 0.03), 1.0 / 20.0 + 0.03)
54
+ # OEI second-segment on the installed basis: x n/(n-1)
55
+ raw = constraint_tw("climb", LD=12.0, gamma=0.024)
56
+ inst = constraint_tw("climb", LD=12.0, gamma=0.024, engines=2)
57
+ self.assertAlmostEqual(raw, 1.0 / 12.0 + 0.024)
58
+ self.assertAlmostEqual(inst, 2.0 * raw)
59
+ with self.assertRaises(ValueError):
60
+ constraint_tw("climb", LD=12.0, gamma=0.024, engines=1)
61
+
62
+ def test_cruise_constraint_shape(self):
63
+ # q*CD0/W_S + k*W_S/q is U-shaped in W/S with its minimum at
64
+ # W/S* = q*sqrt(CD0/k); takeoff grows with W/S, so high W/S values
65
+ # are takeoff-bound while very low W/S values are cruise-bound.
66
+ v, rho, cd0, k = 231.3, 0.3796, 0.018, 0.0419
67
+ q = 0.5 * rho * v * v
68
+ ws_opt = q * math.sqrt(cd0 / k)
69
+ vals = [cruise_constraint_tw(w, v, rho, cd0, k)
70
+ for w in (ws_opt * 0.7, ws_opt, ws_opt * 1.3)]
71
+ self.assertLess(vals[1], vals[0])
72
+ self.assertLess(vals[1], vals[2])
73
+ with self.assertRaises(ValueError):
74
+ cruise_constraint_tw(-1.0, v, rho, cd0, k)
75
+ with self.assertRaises(ValueError):
76
+ constraint_tw("bogus", W_S=4000.0)
77
+
78
+ def test_design_point_on_feasible_boundary(self):
79
+ item = example_item()
80
+ model = build_concept(item)
81
+ dp = model["design_point"]
82
+ # chosen W/S below the stall limit and inside the feasible region
83
+ self.assertLessEqual(dp["ws_nm2"], dp["ws_stall_max_nm2"])
84
+ self.assertTrue(dp["feasible"])
85
+ # required T/W equals the max over the constraint curves at that W/S
86
+ max_curve = max(dp["curves"][name]["tw_at_design"]
87
+ for name in dp["curves"])
88
+ self.assertAlmostEqual(dp["tw_required"], max_curve, places=6)
89
+ self.assertIn(dp["binding_constraint"], dp["curves"])
90
+ # the example has a real, defensible regional-jet design point
91
+ self.assertGreater(dp["tw_required"], 0.10)
92
+ self.assertLess(dp["tw_required"], 0.35)
93
+
94
+
95
+ class TestMissionFuelFractions(unittest.TestCase):
96
+ """Segment fuel models must match the sizing-mission-profile leaf."""
97
+
98
+ def test_breguet_cruise_fraction_matches_doc_case(self):
99
+ # AeroSkills worked example: 3000 nm, V 450 kt, TSFC 0.6, L/D 18,
100
+ # W0 150000 lb burns 69088.9 lb on the cruise leg -> fraction 0.46059
101
+ frac = breguet_cruise_fuel_fraction(3000.0, 450.0, 0.6, 18.0)
102
+ self.assertAlmostEqual(frac * 150000.0, 69088.9, delta=1.0)
103
+
104
+ def test_loiter_endurance_fraction(self):
105
+ # hold 0.75 hr at TSFC 0.5, L/D 15: 1 - exp(-0.75*0.5/15)
106
+ frac = breguet_loiter_fuel_fraction(0.75, 0.5, 15.0)
107
+ self.assertAlmostEqual(frac, 1.0 - math.exp(-0.75 * 0.5 / 15.0))
108
+
109
+ def test_segment_fuel_fraction_dispatch(self):
110
+ # absolute-flow segments scale with the start weight (W0-dependent)
111
+ f1 = segment_fuel_fraction("taxi", {"fuel_flow": 1200.0, "time": 0.25},
112
+ 120000.0)
113
+ self.assertAlmostEqual(f1, 300.0 / 120000.0)
114
+ # climb fraction model returns the fraction directly
115
+ self.assertAlmostEqual(
116
+ segment_fuel_fraction("climb", {"fraction": 0.01}, 120000.0), 0.01)
117
+ # cruise dispatches to Breguet range
118
+ fc = segment_fuel_fraction("cruise", {"R_nm": 1500.0, "V_kt": 450.0,
119
+ "TSFC": 0.6, "LD": 18.0},
120
+ 120000.0)
121
+ self.assertAlmostEqual(fc, 1.0 - math.exp(-1500.0 / (450.0 * 0.6 * 18.0)))
122
+ with self.assertRaises(ValueError):
123
+ segment_fuel_fraction("weird", {}, 120000.0)
124
+
125
+ def test_mission_chains_weights_and_balances_fractions(self):
126
+ item = example_item()
127
+ mission = chain_mission(item, 150000.0)
128
+ # weight chains: block fuel < start weight, landing weight positive
129
+ self.assertGreater(mission["block_fuel_lb"], 0.0)
130
+ self.assertGreater(mission["landing_weight_lb"], 0.0)
131
+ # required fuel = block + reserve; reserve = hold + 5% of trip
132
+ self.assertAlmostEqual(mission["required_fuel_lb"],
133
+ mission["block_fuel_lb"] + mission["reserve_fuel_lb"])
134
+ # per-segment fractions of MTOW sum to the block-fuel fraction
135
+ total_frac = sum(s["fraction_of_mtow"] for s in mission["segments"])
136
+ self.assertAlmostEqual(total_frac,
137
+ mission["block_fuel_lb"] / 150000.0)
138
+ # segment types in the expected order
139
+ types = [s["type"] for s in mission["segments"]]
140
+ self.assertEqual(types, ["taxi", "takeoff", "climb", "cruise", "descent"])
141
+
142
+
143
+ class TestEmptyFractionAndTow(unittest.TestCase):
144
+ """Class-I empty-weight fit and the tow-estimation leaf parity."""
145
+
146
+ def test_empty_weight_fraction_band_and_fit(self):
147
+ self.assertEqual(EMPTY_WEIGHT_FRACTION_BANDS["transport"], (0.42, 0.55))
148
+ # representative fraction is the transport band midpoint
149
+ self.assertAlmostEqual(empty_weight_fraction("transport"), 0.485)
150
+ with self.assertRaises(ValueError):
151
+ empty_weight_fraction("airship")
152
+
153
+ def test_empty_fraction_in_band_check(self):
154
+ in_band, band, frac = check_empty_weight_fraction(58300.0, 120000.0,
155
+ "transport")
156
+ self.assertTrue(in_band)
157
+ self.assertEqual(band, (0.42, 0.55))
158
+ self.assertAlmostEqual(frac, 58300.0 / 120000.0)
159
+ # out-of-band case fails
160
+ in_band2, _, _ = check_empty_weight_fraction(80000.0, 120000.0, "transport")
161
+ self.assertFalse(in_band2)
162
+
163
+ def test_tow_estimate_leaf(self):
164
+ # W0 = payload/(1 - empty - fuel)
165
+ self.assertAlmostEqual(tow_estimate(20000.0, 0.485, 0.35),
166
+ 20000.0 / (1.0 - 0.485 - 0.35))
167
+ with self.assertRaises(ValueError):
168
+ tow_estimate(20000.0, 0.6, 0.5) # fractions reach 1.0
169
+
170
+ def test_weight_breakdown_balances(self):
171
+ self.assertTrue(weight_breakdown_ok(58000.0, 42000.0, 20000.0, 120000.0))
172
+ self.assertFalse(weight_breakdown_ok(60000.0, 42000.0, 20000.0, 120000.0))
173
+
174
+
175
+ class TestMTOWConvergence(unittest.TestCase):
176
+ """The sizing loop must genuinely iterate and converge within 0.5%."""
177
+
178
+ def test_mtow_converges_within_tolerance(self):
179
+ item = example_item()
180
+ sizing = size_mtow(item)
181
+ self.assertTrue(sizing["converged"])
182
+ self.assertEqual(sizing["tolerance"], 0.005)
183
+ self.assertGreaterEqual(len(sizing["iterations"]), 2)
184
+ # final successive estimates within the 0.5% band
185
+ last = sizing["iterations"][-1]
186
+ self.assertLess(last["rel_change"], 0.005)
187
+ # MTOW dominates payload and matches a 100-pax regional jet class
188
+ self.assertGreater(sizing["mtow_lb"], 3.0 * sizing["payload_lb"])
189
+ self.assertGreater(sizing["mtow_lb"], 90000.0)
190
+ self.assertLess(sizing["mtow_lb"], 160000.0)
191
+
192
+ def test_fuel_fraction_is_weight_dependent(self):
193
+ # fixed-fuel segments make the fuel fraction fall as W0 grows
194
+ f_small = chain_mission(example_item(), 80000.0)["fuel_fraction"]
195
+ f_large = chain_mission(example_item(), 160000.0)["fuel_fraction"]
196
+ self.assertLess(f_large, f_small)
197
+
198
+ def test_breakdown_balances_at_convergence(self):
199
+ sizing = size_mtow(example_item())
200
+ self.assertTrue(weight_breakdown_ok(
201
+ sizing["empty_weight_lb"], sizing["required_fuel_lb"],
202
+ sizing["payload_lb"], sizing["mtow_lb"], tol=0.01))
203
+
204
+ def test_converges_from_a_different_guess(self):
205
+ item = example_item()
206
+ item.initial_guess_lb = 250000.0
207
+ sizing = size_mtow(item)
208
+ self.assertTrue(sizing["converged"])
209
+ # converges to the same fixed point regardless of the start guess
210
+ base = size_mtow(example_item())["mtow_lb"]
211
+ self.assertAlmostEqual(sizing["mtow_lb"], base, delta=base * 0.005)
212
+
213
+
214
+ class TestConceptPackageAndGates(unittest.TestCase):
215
+
216
+ def test_build_concept_model_complete(self):
217
+ model = build_concept(example_item())
218
+ for key in ("document_type", "status", "item", "requirement",
219
+ "design_point", "mission", "sizing", "engine",
220
+ "mass_statement"):
221
+ self.assertIn(key, model)
222
+ ms = model["mass_statement"]
223
+ self.assertEqual(model["status"], "draft-for-review")
224
+ self.assertTrue(model["sizing"]["empty_in_band"])
225
+ # mass statement carries the three rows and a total
226
+ self.assertEqual(len(ms["rows"]), 3)
227
+ self.assertAlmostEqual(ms["total_lb"], model["sizing"]["mtow_lb"])
228
+ # engine check: 2 engines, positive thrust, top-of-climb margin >= 1
229
+ self.assertEqual(model["engine"]["n_engines"], 2)
230
+ self.assertGreater(model["engine"]["toc_margin"], 1.0)
231
+ self.assertTrue(model["engine"]["toc_ok"])
232
+
233
+ def test_core_gates_all_pass(self):
234
+ model = build_concept(example_item())
235
+ gates = check_concept(model)
236
+ self.assertTrue(gates["all_pass"], gates)
237
+
238
+ def test_markdown_gates_all_pass_and_no_blanks(self):
239
+ md = example_concept_markdown()
240
+ self.assertGreater(len(md), 3000)
241
+ self.assertNotIn("___", md)
242
+ gates = check_concept_markdown(md)
243
+ self.assertTrue(gates["all_pass"], gates)
244
+
245
+ def test_render_contains_key_numbers(self):
246
+ md = example_concept_markdown()
247
+ model = build_concept(example_item())
248
+ s = model["sizing"]
249
+ dp = model["design_point"]
250
+ # converged MTOW, W/S-T/W point, and the mass statement are present
251
+ self.assertIn(f"{s['mtow_lb']:,.0f}", md)
252
+ self.assertIn(f"{dp['ws_nm2']:,.0f} N/m^2", md)
253
+ self.assertIn(f"{dp['tw_required']:.4f}", md)
254
+ self.assertIn("CONVERGED", md.upper())
255
+ self.assertIn("not an approval", md.lower())
256
+
257
+ def test_standalone_no_skills_repo(self):
258
+ # Core must work with no AeroSkills present and no local paths.
259
+ os.environ["AEROSKILLS_DEV"] = "/nonexistent"
260
+ model = build_concept(example_item())
261
+ md = render_concept_markdown(model)
262
+ self.assertTrue(model["sizing"]["converged"])
263
+ self.assertGreater(len(md), 3000)
264
+ gates = check_concept(model)
265
+ self.assertTrue(gates["all_pass"], gates)
266
+
267
+
268
+ if __name__ == "__main__":
269
+ unittest.main()
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ """Role test: aircraft-design-engineer."""
3
+ import os
4
+ import re
5
+ import unittest
6
+
7
+ ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
8
+ os.path.abspath(__file__)))))
9
+ AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
10
+ HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
11
+ ROLE_DIR = os.path.join(ROLES_REPO, "roles", "aircraft-design-engineer")
12
+ TEMPLATE = os.path.join(ROLE_DIR, "templates", "concept-design-template.md")
13
+
14
+ EXPECTED_BOUND = [
15
+ "vehicle-design/conceptual/constraint-analysis",
16
+ "vehicle-design/conceptual/sizing-mission-profile",
17
+ "vehicle-design/conceptual/payload-range-diagram",
18
+ "vehicle-design/conceptual/tow-estimation",
19
+ "vehicle-design/conceptual/openvsp-geometry",
20
+ "vehicle-design/sizing/ws-tw-trade",
21
+ "vehicle-design/sizing/weight-estimation",
22
+ "vehicle-design/sizing/engine-sizing",
23
+ "vehicle-design/sizing/fuselage-sizing",
24
+ "vehicle-design/sizing/wing-planform-sizing",
25
+ "vehicle-design/sizing/tail-sizing",
26
+ "vehicle-design/sizing/landing-gear-sizing",
27
+ "vehicle-design/sizing/control-surface-sizing",
28
+ "vehicle-design/sizing/fuel-tank-sizing",
29
+ "vehicle-design/sizing/battery-sizing",
30
+ "vehicle-design/sizing/environmental-control-sizing",
31
+ "vehicle-design/sizing/electrical-wire-sizing",
32
+ "vehicle-design/sizing/apu-fuel-burn-sizing",
33
+ "vehicle-design/sizing/bleed-air-system-sizing",
34
+ "vehicle-design/sizing/fire-protection-sizing",
35
+ "vehicle-design/sizing/ice-protection-sizing",
36
+ "vehicle-design/sizing/hydraulic-system-sizing",
37
+ "vehicle-design/sizing/landing-gear-retraction-sizing",
38
+ "vehicle-design/mass-properties/mass-budget",
39
+ "vehicle-design/mass-properties/cg-envelope",
40
+ "vehicle-design/mass-properties/inertia-estimation",
41
+ "vehicle-design/mdo/design-of-experiments",
42
+ "vehicle-design/mdo/multidisciplinary-optimization",
43
+ "vehicle-design/mdo/surrogate-modeling",
44
+ "vehicle-design/cost-estimation/parametric-cost",
45
+ "vehicle-design/cost-estimation/operating-cost",
46
+ "vehicle-design/cost-estimation/life-cycle-cost",
47
+ "vehicle-design/structures-integration/wing-box-sizing",
48
+ "vehicle-design/structures-integration/fuselage-skin-stringer"
49
+ ]
50
+
51
+ STAGES = [
52
+ "1. Requirements framing",
53
+ "2. Sizing mission",
54
+ "3. Initial layout",
55
+ "4. W/S vs T/W trade",
56
+ "5. Weight iteration",
57
+ "6. Component sizing",
58
+ "7. Subsystem sizing",
59
+ "8. Mass + CG",
60
+ "9. MDO exploration",
61
+ "10. Payload-range",
62
+ "11. Cost",
63
+ "12. Structures check",
64
+ "13. Package"
65
+ ]
66
+
67
+
68
+ def role_text():
69
+ p = os.path.join(ROLE_DIR, "ROLE.md")
70
+ return open(p).read() if os.path.exists(p) else None
71
+
72
+
73
+ class TestAircraftDesignEngineerRole(unittest.TestCase):
74
+
75
+ def test_bound_skills_resolve(self):
76
+ if not HAS_SKILLS:
77
+ self.skipTest("skills checkout absent (cross-repo dev check)")
78
+ for leaf in EXPECTED_BOUND:
79
+ self.assertTrue(
80
+ os.path.exists(os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")),
81
+ f"bound skill missing: {leaf}")
82
+
83
+ def test_stages_ordered(self):
84
+ role = role_text()
85
+ self.assertIsNotNone(role)
86
+ wf = role[role.find("## Workflow"):role.find("## Evidence gates")]
87
+ idx = [wf.find(s) for s in STAGES]
88
+ self.assertTrue(all(i >= 0 for i in idx), "stage missing in workflow")
89
+ self.assertEqual(idx, sorted(idx), "stage order not deterministic")
90
+
91
+ def test_template_present(self):
92
+ self.assertTrue(os.path.exists(TEMPLATE), "deliverable template missing")
93
+
94
+ def test_boundaries(self):
95
+ role = role_text()
96
+ self.assertIsNotNone(role)
97
+ for phrase in ["sign_off_required: true", "forbidden"]:
98
+ self.assertIn(phrase, role)
99
+
100
+ def test_sources(self):
101
+ self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, "SOURCES.md")),
102
+ "SOURCES.md missing")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -0,0 +1,122 @@
1
+ ---
2
+ type: role
3
+ name: airworthiness-compliance-engineer
4
+ title: "Airworthiness Compliance Engineer"
5
+ status: draft
6
+ domain: systems-engineering-safety
7
+ deliverable_type: "compliance checklist / certification matrix"
8
+ standards_bound:
9
+ - id: far-25
10
+ tier: TIER-1
11
+ reference-only: false
12
+ - id: cs-25
13
+ tier: TIER-1
14
+ reference-only: false
15
+ skills_bound:
16
+ - avionics/far-cs25/airworthiness
17
+ - avionics/far-cs25/special-conditions
18
+ - systems-engineering-safety/certification/certification-basis
19
+ - systems-engineering-safety/certification/equivalent-level-of-safety
20
+ - systems-engineering-safety/certification/means-of-compliance
21
+ - systems-engineering-safety/certification/mmel-development
22
+ - systems-engineering-safety/continued-airworthiness/airworthiness-directive-compliance
23
+ - systems-engineering-safety/continued-airworthiness/type-certificate-data-sheet
24
+ - systems-engineering-safety/continued-airworthiness/in-service-safety-assessment
25
+ tools_allowed: [stdlib, offline-file-processing]
26
+ forbidden:
27
+ - "declare a compliance finding"
28
+ - "claim certification approval"
29
+ - "assert applicability without the certification basis"
30
+ sign_off_required: true
31
+ license: Apache-2.0
32
+ compatibility: "agentskills.io ROLE.md; any SKILL.md host"
33
+ metadata:
34
+ version: 0.1.0
35
+ author: Aero Agent Roles
36
+ skills_release: "v1.3.0+"
37
+ ---
38
+
39
+ # Airworthiness Compliance Engineer
40
+
41
+ ## Role identity
42
+
43
+ Drives the certification compliance workflow: determine the applicable
44
+ certification basis (FAR/CS), select means of compliance per regulation,
45
+ map each regulation to the compliance document and status, and produce
46
+ the compliance checklist (the certification matrix). Use when a project
47
+ needs to show how it will comply with airworthiness regulations. Do NOT
48
+ use for design engineering, or when no cert basis exists.
49
+
50
+ ## Deliverable contract
51
+
52
+ 1. **Compliance checklist / certification matrix** — per regulation:
53
+ applicability determination, selected means of compliance, the
54
+ associated compliance document(s), status of the finding, responsible
55
+ owner. Original skeleton per templates/compliance-matrix-template.md.
56
+ 2. **Certification basis summary** — the regs + special conditions +
57
+ equivalent level of safety findings that apply.
58
+ 3. **Gap report** — open items for the human compliance engineer.
59
+
60
+ ## Workflow (stages → bound skills)
61
+
62
+ | Stage | Loads skill | Produces |
63
+ |---|---|---|
64
+ | 1. Certification basis | certification/certification-basis | regs that apply, TC/STC context |
65
+ | 2. Special conditions | far-cs25/special-conditions | special conditions list |
66
+ | 3. ELOS | certification/equivalent-level-of-safety | ELOS findings |
67
+ | 4. Means of compliance | certification/means-of-compliance | MoC per reg |
68
+ | 5. Airworthiness mapping | far-cs25/airworthiness | reg → MoC → document map |
69
+ | 6. MMEL | certification/mmel-development | MMEL applicability |
70
+ | 7. Continued airworthiness | continued-airworthiness/airworthiness-directive-compliance + type-certificate-data-sheet + in-service-safety-assessment | AD/TCDS/in-service inputs |
71
+ | 8. Compliance matrix | (all above) | the deliverable |
72
+
73
+ ## Evidence gates
74
+
75
+ - Stage 1 done = certification basis list cites the actual reg text
76
+ (FAR/CS are TIER-1 public-domain — quotable with citation).
77
+ - Stage 5 done = every applicable reg has a chosen MoC + document.
78
+ - FINAL = matrix complete; statuses are PROPOSED (not found); nothing
79
+ marked "compliant" without evidence.
80
+
81
+ ## Boundary / forbidden
82
+
83
+ - NEVER declare a compliance finding — the authority/designee does.
84
+ - NEVER claim approval.
85
+ - FAR/CS are public domain — quote with citation, but keep paraphrase
86
+ preferred per standards-map.
87
+
88
+ ## Verification
89
+
90
+ The role runs STANDALONE: `core/airworthiness_core.py` is an executable
91
+ engine that determines regulation applicability (aircraft type + change
92
+ scope), selects the means of compliance per regulation (real MOC
93
+ vocabulary: analysis, test, inspection, safety assessment, similarity),
94
+ computes severity/DAL context, BUILDS the compliance matrix, and
95
+ gate-checks deliverables. No AeroSkills checkout required.
96
+
97
+ Run the role:
98
+ ```bash
99
+ python3 cli.py build --out matrix.md # example item (FAR-25 STC)
100
+ python3 cli.py build --out matrix-cs.md --jurisdiction EASA # CS-25 basis variant
101
+ python3 cli.py check --file matrix.md # gate-check a matrix
102
+ ```
103
+
104
+ Tests:
105
+ - tests/test_airworthiness_core.py (20 tests): domain rules (severity →
106
+ DAL, 25.1309 safety assessment applicability, MOC suitability),
107
+ applicability logic (transport vs other categories, change-scope
108
+ screening), MOC selection (FAR-25 vocabulary + CS-25 MOC scheme),
109
+ matrix builder completeness (real regs, no blank fields), gates
110
+ pass/fail, and standalone (AEROSKILLS_DEV=/nonexistent).
111
+ - tests/test_role_airworthiness.py: bound-skill resolution (skips if the
112
+ skills repo is absent) + workflow + template present + boundaries.
113
+
114
+ Bound skills in Aero Agent Skills deepen individual stages when the
115
+ library is present (certification basis, means of compliance, ELOS,
116
+ airworthiness mapping); the core engine does not depend on them.
117
+
118
+ ## Compliance
119
+
120
+ - far-25 / cs-25 TIER-1 (public-domain regulations); standards-map gated:
121
+ false — quotable with citation.
122
+ - SOURCES.md records acquisition/extraction/verification status.
@@ -0,0 +1,16 @@
1
+ # SOURCES.md - Airworthiness Compliance Engineer
2
+
3
+ Standards and documents this role references. Summary-not-copy: see
4
+ [STANDARDS.md](../../STANDARDS.md) for the full rule and purchase links.
5
+
6
+ | Source | Role use | Gated |
7
+ |---|---|---|
8
+ | 14 CFR Part 21 | certification procedures (TC/STC/TSO) | false |
9
+ | 14 CFR Part 25 | airworthiness standards for transport category | false |
10
+ | EASA CS-25 | EASA certification specifications | false |
11
+ | EASA Part 21 (Easy Access Rules) | EASA certification procedures | false |
12
+ | SAE ARP4754A | development assurance context (shared) | true |
13
+
14
+ FAR and CS are public-domain regulations, quotable with citation. The
15
+ compliance matrix template in `templates/` is an original structure; rows
16
+ map regulation to means of compliance to evidence document.