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,281 @@
1
+ #!/usr/bin/env python3
2
+ """Role test: DO-160G Environmental Qualification Engineer.
3
+
4
+ Verifies (offline, no network):
5
+ 1. Every skill bound in ROLE.md frontmatter RESOLVES to a real leaf in
6
+ the aero-agent-skills repo (the pinned skills_release band).
7
+ 2. The workflow stage order is deterministic and complete.
8
+ 3. Evidence-gate smoke: the example LRU produces a complete
9
+ qualification report with no blank sections.
10
+ 4. When the skills checkout is present, the core's numeric anchors are
11
+ CROSS-CHECKED against the bound leaves' own *_logic.py files.
12
+ 5. cli.py build --bundle works (evidence JSON written, gates pass).
13
+ """
14
+ import importlib.util
15
+ import json
16
+ import os
17
+ import re
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+ import unittest
22
+
23
+ ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
24
+ os.path.abspath(__file__))))) # repo root (4 up from tests/)
25
+ AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
26
+ HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
27
+ ROLE_DIR = os.path.join(ROLES_REPO, "roles", "do160-environmental-engineer")
28
+ TEMPLATE = os.path.join(ROLE_DIR, "templates",
29
+ "environmental-qualification-plan-template.md")
30
+
31
+ EXPECTED_BOUND = [
32
+ "avionics/do160/environmental-qualification",
33
+ "avionics/do160/lightning-protection",
34
+ "avionics/do160/electrostatic-discharge",
35
+ "avionics/do160/power-input",
36
+ "avionics/do160/radio-frequency-susceptibility",
37
+ "avionics/do160/radio-frequency-emissions",
38
+ ]
39
+
40
+ EXPECTED_STAGES = [
41
+ "Environmental scope", "Lightning plan", "ESD levels",
42
+ "Power input quality", "RF immunity", "RF emissions",
43
+ ]
44
+
45
+
46
+ def read_role_md():
47
+ p = os.path.join(ROLE_DIR, "ROLE.md")
48
+ if not os.path.exists(p):
49
+ return None
50
+ return open(p).read()
51
+
52
+
53
+ def load_leaf_logic(leaf):
54
+ """Import a bound leaf's *_logic.py (first one found). Returns module
55
+ or None when the leaf or its logic file is unavailable."""
56
+ logic_dir = os.path.join(AEROSKILLS, "skills", leaf, "scripts")
57
+ if not os.path.isdir(logic_dir):
58
+ return None
59
+ for f in sorted(os.listdir(logic_dir)):
60
+ if f.endswith("_logic.py"):
61
+ path = os.path.join(logic_dir, f)
62
+ spec = importlib.util.spec_from_file_location(
63
+ leaf.replace("/", "_"), path)
64
+ mod = importlib.util.module_from_spec(spec)
65
+ try:
66
+ spec.loader.exec_module(mod)
67
+ except Exception:
68
+ continue
69
+ return mod
70
+ return None
71
+
72
+
73
+ class TestDo160Role(unittest.TestCase):
74
+
75
+ def test_bound_skills_resolve_in_aeroskills(self):
76
+ if not HAS_SKILLS:
77
+ self.skipTest("Aero Agent Skills checkout not present "
78
+ "(cross-repo dev check)")
79
+ for leaf in EXPECTED_BOUND:
80
+ sk = os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")
81
+ self.assertTrue(os.path.exists(sk),
82
+ f"bound skill not found in aero-agent-skills: "
83
+ f"{leaf}")
84
+
85
+ def test_bound_skills_listed_in_frontmatter(self):
86
+ role = read_role_md()
87
+ self.assertIsNotNone(role)
88
+ for leaf in EXPECTED_BOUND:
89
+ self.assertIn(leaf, role, f"missing in ROLE.md skills_bound: "
90
+ f"{leaf}")
91
+
92
+ def test_workflow_stages_deterministic(self):
93
+ role = read_role_md()
94
+ idx = [role.find(stage) for stage in EXPECTED_STAGES]
95
+ self.assertTrue(all(i >= 0 for i in idx), "a stage is missing")
96
+ self.assertEqual(idx, sorted(idx),
97
+ "stage order not deterministic")
98
+
99
+ def test_deliverable_template_complete(self):
100
+ self.assertTrue(os.path.exists(TEMPLATE),
101
+ "environmental-qualification-plan-template.md missing")
102
+ text = open(TEMPLATE, encoding="utf-8").read()
103
+ # all 9 numbered sections present
104
+ for n in range(1, 10):
105
+ self.assertTrue(re.search(rf"^## {n}\. ", text, re.M),
106
+ f"section {n} missing")
107
+ # zero blank fillers
108
+ self.assertNotIn("___", text)
109
+ low = text.lower()
110
+ self.assertIn("draft", low)
111
+ self.assertIn("not an approval", low)
112
+ self.assertIn("15 kv air discharge", low)
113
+
114
+ def test_forbidden_lines_present(self):
115
+ role = read_role_md()
116
+ for phrase in ["issue qualification approval", "regulatory sign-off",
117
+ "reproduce proprietary", "sign_off_required: true"]:
118
+ self.assertIn(phrase, role, f"missing boundary: {phrase}")
119
+
120
+ def test_sources_register_exists(self):
121
+ p = os.path.join(ROLE_DIR, "SOURCES.md")
122
+ self.assertTrue(os.path.exists(p), "SOURCES.md missing for role")
123
+
124
+ def test_core_engine_exists(self):
125
+ for f in ["core", "cli.py"]:
126
+ self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, f)),
127
+ f"{f} missing - role is not executable")
128
+
129
+ def test_cli_build_bundle_exits_zero(self):
130
+ """cli build --bundle -> exit 0, evidence JSON valid, gates pass."""
131
+ with tempfile.TemporaryDirectory() as td:
132
+ out = os.path.join(td, "eqpr.md")
133
+ r = subprocess.run([sys.executable, os.path.join(ROLE_DIR, "cli.py"),
134
+ "build", "--out", out, "--bundle"],
135
+ capture_output=True, text=True)
136
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
137
+ self.assertTrue(os.path.exists(out))
138
+ ev = os.path.join(td, "evidence")
139
+ for f in ["model.json", "gates.json", "provenance.json"]:
140
+ self.assertTrue(os.path.exists(os.path.join(ev, f)),
141
+ f"evidence/{f} missing")
142
+ gates = json.load(open(os.path.join(ev, "gates.json")))
143
+ self.assertTrue(gates["all_pass"])
144
+ self.assertEqual(gates["exit_code"], 0)
145
+ model = json.load(open(os.path.join(ev, "model.json")))
146
+ self.assertIn("temperature_category", model)
147
+ prov = json.load(open(os.path.join(ev, "provenance.json")))
148
+ self.assertEqual(prov["role"], "do160-environmental-engineer")
149
+ # no local paths in the bundle
150
+ blob = json.dumps(model) + json.dumps(prov)
151
+ self.assertNotIn("/Users/", blob)
152
+ self.assertNotIn(AEROSKILLS if not AEROSKILLS.startswith("~")
153
+ else "/nonexistent", blob)
154
+
155
+ def test_cli_build_standalone_bad_skills_env(self):
156
+ """AEROSKILLS_DEV=/nonexistent -> core still builds correctly."""
157
+ env = dict(os.environ)
158
+ env["AEROSKILLS_DEV"] = "/nonexistent"
159
+ with tempfile.TemporaryDirectory() as td:
160
+ out = os.path.join(td, "eqpr.md")
161
+ r = subprocess.run([sys.executable, os.path.join(ROLE_DIR, "cli.py"),
162
+ "build", "--out", out],
163
+ capture_output=True, text=True, env=env)
164
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
165
+ text = open(out).read()
166
+ self.assertIn("draft", text.lower())
167
+ self.assertIn("not an approval", text.lower())
168
+
169
+ def test_cli_check_gate(self):
170
+ with tempfile.TemporaryDirectory() as td:
171
+ out = os.path.join(td, "eqpr.md")
172
+ subprocess.run([sys.executable, os.path.join(ROLE_DIR, "cli.py"),
173
+ "build", "--out", out],
174
+ capture_output=True, text=True, check=True)
175
+ r = subprocess.run([sys.executable,
176
+ os.path.join(ROLE_DIR, "cli.py"),
177
+ "check", "--file", out, "--category", "B1"],
178
+ capture_output=True, text=True)
179
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
180
+ self.assertIn("RESULT: PASS", r.stdout)
181
+
182
+
183
+ class TestDo160LeafCrossCheck(unittest.TestCase):
184
+ """Core numeric anchors vs the bound leaves' logic files (dev check,
185
+ skipped when the skills checkout is absent)."""
186
+
187
+ @classmethod
188
+ def setUpClass(cls):
189
+ if not HAS_SKILLS:
190
+ raise unittest.SkipTest("Aero Skills checkout not present")
191
+
192
+ def _core(self):
193
+ sys.path.insert(0, os.path.join(ROLE_DIR, "core"))
194
+ import do160_environmental_core as core
195
+ return core
196
+
197
+ def test_esd_leaf_agrees(self):
198
+ mod = load_leaf_logic("avionics/do160/electrostatic-discharge")
199
+ core = self._core()
200
+ self.assertIsNotNone(mod)
201
+ self.assertAlmostEqual(
202
+ mod.category_test_level_kv("A"),
203
+ core.esd_category_test_level_kv("A"), places=6)
204
+ self.assertAlmostEqual(
205
+ mod.stored_energy_joules(150.0, 15.0),
206
+ core.esd_stored_energy_joules(150.0, 15.0), places=9)
207
+ self.assertAlmostEqual(mod.peak_current_amps(15.0),
208
+ core.esd_peak_current_amps(15.0), places=6)
209
+ self.assertAlmostEqual(
210
+ mod.rc_time_constant_ns(330.0, 150.0),
211
+ core.esd_rc_time_constant_ns(330.0, 150.0), places=6)
212
+ self.assertEqual(mod.pass_verdict(True, True),
213
+ core.esd_pass_verdict(True, True))
214
+
215
+ def test_lightning_leaf_agrees(self):
216
+ mod = load_leaf_logic("avionics/do160/lightning-protection")
217
+ core = self._core()
218
+ self.assertIsNotNone(mod)
219
+ self.assertEqual(mod.test_level_in_range(3),
220
+ core.lightning_level_valid(3))
221
+ self.assertEqual(mod.waveform_supported("C"),
222
+ core.lightning_waveform_valid("C"))
223
+ self.assertEqual(mod.pass_verdict(False, False, False),
224
+ core.lightning_pass_verdict(False, False, False))
225
+
226
+ def test_power_input_leaf_agrees(self):
227
+ mod = load_leaf_logic("avionics/do160/power-input")
228
+ core = self._core()
229
+ self.assertIsNotNone(mod)
230
+ self.assertAlmostEqual(mod.sag_depth_percent(28.0, 21.0),
231
+ core.sag_depth_percent(28.0, 21.0), places=6)
232
+ self.assertAlmostEqual(mod.surge_height_percent(28.0, 32.2),
233
+ core.surge_height_percent(28.0, 32.2),
234
+ places=6)
235
+ ok1, d1, p1 = mod.transient_check(80.0, 20.0, 100.0, 25.0)
236
+ ok2, d2, p2 = core.transient_check(80.0, 20.0, 100.0, 25.0)
237
+ self.assertEqual((ok1, d1, p1), (ok2, d2, p2))
238
+ self.assertAlmostEqual(mod.ripple_percent(29.0, 27.0, 28.0),
239
+ core.ripple_percent(29.0, 27.0, 28.0),
240
+ places=6)
241
+
242
+ def test_rf_susceptibility_leaf_agrees(self):
243
+ mod = load_leaf_logic("avionics/do160/radio-frequency-susceptibility")
244
+ core = self._core()
245
+ self.assertIsNotNone(mod)
246
+ self.assertAlmostEqual(
247
+ mod.required_amp_power_for_test(100.0, 3.0, 3.0, 3.0, 6.0),
248
+ core.required_amp_power_for_test(100.0, 3.0, 3.0, 3.0, 6.0),
249
+ delta=1.0)
250
+ self.assertAlmostEqual(mod.cs114_limit_dbu_a("B"),
251
+ core.cs114_limit_dbu_a("B"), places=6)
252
+ m1, o1 = mod.margin_check_dbu(60.0, 65.7)
253
+ m2, o2 = core.margin_check_dbu(60.0, 65.7)
254
+ self.assertAlmostEqual(m1, m2, places=6)
255
+ self.assertEqual(o1, o2)
256
+
257
+ def test_rf_emissions_leaf_agrees(self):
258
+ mod = load_leaf_logic("avionics/do160/radio-frequency-emissions")
259
+ core = self._core()
260
+ self.assertIsNotNone(mod)
261
+ self.assertAlmostEqual(mod.ce102_limit_db(150e3, "A"),
262
+ core.ce102_limit_db(150e3, "A"), places=6)
263
+ self.assertAlmostEqual(mod.re102_limit_db(100e6, "A"),
264
+ core.re102_limit_db(100e6, "A"), places=6)
265
+ self.assertEqual(mod.worst_case_frequency([50e3, 150e3], [8.0, 5.0])[0],
266
+ core.worst_case_frequency([50e3, 150e3],
267
+ [8.0, 5.0])[0])
268
+
269
+ def test_environmental_leaf_agrees(self):
270
+ mod = load_leaf_logic("avionics/do160/environmental-qualification")
271
+ core = self._core()
272
+ self.assertIsNotNone(mod)
273
+ self.assertEqual(mod.section_name(25), core.section_name(25))
274
+ self.assertEqual(mod.temperature_category_range("B1"),
275
+ core.temperature_category_range("B1"))
276
+ self.assertEqual(mod.matrix_complete(sorted(core.SECTIONS), "B1"),
277
+ core.matrix_complete(sorted(core.SECTIONS), "B1"))
278
+
279
+
280
+ if __name__ == "__main__":
281
+ unittest.main()
@@ -0,0 +1,120 @@
1
+ ---
2
+ type: role
3
+ name: do178c-cert-engineer
4
+ title: "DO-178C Software Certification Engineer"
5
+ status: draft
6
+ domain: avionics
7
+ deliverable_type: "certification plan + verification evidence set"
8
+ standards_bound:
9
+ - id: do-178c
10
+ tier: TIER-2
11
+ reference-only: true
12
+ skills_bound:
13
+ - avionics/do178c/planning
14
+ - avionics/do178c/development
15
+ - avionics/do178c/verification
16
+ - avionics/do178c/software-testing
17
+ - avionics/do178c/configuration-management
18
+ - avionics/do178c/tool-qualification
19
+ - avionics/do178c/previously-developed-software
20
+ - avionics/do178c/data-control-coupling-analysis
21
+ - avionics/do178c/airworthiness-liaison
22
+ tools_allowed: [stdlib, offline-file-processing]
23
+ forbidden:
24
+ - "issue certification approval"
25
+ - "claim regulatory sign-off"
26
+ - "reproduce proprietary RTCA/EUROCAE text"
27
+ - "assert DAL without evidence"
28
+ sign_off_required: true
29
+ license: Apache-2.0
30
+ compatibility: "agentskills.io ROLE.md; any SKILL.md host"
31
+ metadata:
32
+ version: 0.1.0
33
+ author: Aero Agent Roles
34
+ skills_release: "v1.3.0+"
35
+ ---
36
+
37
+ # DO-178C Software Certification Engineer
38
+
39
+ ## Role identity
40
+
41
+ This role drives the DO-178C software certification workflow for an
42
+ airborne software item: from planning through development, verification,
43
+ configuration management, and the artifacts that support the Plan for
44
+ Software Aspects of Certification (PSAC). Use when a project must produce
45
+ DO-178C-compliant software evidence. Do NOT use for general software
46
+ engineering (non-airborne), hardware-only items (see DO-254 role), or
47
+ when no cert basis exists.
48
+
49
+ ## Deliverable contract
50
+
51
+ The role produces:
52
+ 1. **PSAC (Plan for Software Aspects of Certification)** — original
53
+ skeleton per templates/psac-template.md: software level (DAL), life
54
+ cycle data, standards, tool qualification approach, verification
55
+ strategy, liaison with airworthiness.
56
+ 2. **Verification evidence set** — requirements-based test coverage
57
+ summary, data/control coupling analysis results, configuration
58
+ index, accomplishment summary input.
59
+ 3. **Gap report** — what is missing before the human cert engineer
60
+ signs.
61
+
62
+ ## Workflow (stages → bound skills)
63
+
64
+ | Stage | Loads skill | Produces |
65
+ |---|---|---|
66
+ | 1. Planning | do178c/planning | PSAC draft, DAL determination input, life cycle data list |
67
+ | 2. Dev framework | do178c/development | development standards mapping, code/data standards inputs |
68
+ | 3. Verification strategy | do178c/verification | verification methods, coverage targets per level |
69
+ | 4. Test evidence | do178c/software-testing | requirements-based test coverage summary |
70
+ | 5. CM | do178c/configuration-management | configuration index input, change control notes |
71
+ | 6. Tool qualification | do178c/tool-qualification | tool qualification level + approach |
72
+ | 7. PDS check | do178c/previously-developed-software | PDS assessment input |
73
+ | 8. Coupling analysis | do178c/data-control-coupling-analysis | data/control coupling results |
74
+ | 9. Airworthiness liaison | do178c/airworthiness-liaison | liaison summary, open items for the cert authority |
75
+
76
+ ## Evidence gates
77
+
78
+ - Stage 1 done = DAL identified WITH evidence (system safety output),
79
+ PSAC skeleton filled.
80
+ - Stage 4 done = every requirement mapped to a test with pass criteria.
81
+ - Stage 8 done = coupling analysis executed with results recorded.
82
+ - FINAL = every section of the deliverable has a source (skill + standard
83
+ reference); nothing asserted without a checkable basis.
84
+
85
+ ## Boundary / forbidden
86
+
87
+ - NEVER issue certification approval — the DER/regulator signs.
88
+ - NEVER reproduce DO-178C text (proprietary, RTCA/EUROCAE). Summaries and
89
+ original structure only.
90
+ - NEVER assert a DAL without the system-safety evidence trail.
91
+ - Output is a DRAFT for human review — mark it draft.
92
+
93
+ ## Verification
94
+
95
+ The role runs STANDALONE: `core/do178c_core.py` is an executable engine
96
+ that determines DAL from failure severity, computes coverage targets,
97
+ structural coverage, independence, life cycle data, BUILDS the PSAC, and
98
+ gate-checks deliverables. No AeroSkills checkout required.
99
+
100
+ Run the role:
101
+ ```bash
102
+ python3 cli.py build --out psac.md # example item (DAL B)
103
+ python3 cli.py build --failure-condition catastrophic # DAL A variant
104
+ python3 cli.py check --file psac.md --dal B # gate-check a PSAC
105
+ ```
106
+
107
+ Tests:
108
+ - tests/test_do178c_core.py (11 tests): domain rules + PSAC builder +
109
+ gates + standalone (no skills repo)
110
+ - tests/test_role_do178c.py: bound-skill resolution (skips if the skills
111
+ repo is absent) + workflow + template present + boundaries
112
+
113
+ Bound skills in Aero Agent Skills deepen individual stages when the
114
+ library is present (verification logic, CM, tool qualification); the
115
+ core engine does not depend on them.
116
+
117
+ ## Compliance
118
+
119
+ - do-178c TIER-2 reference-only per standards-map; no verbatim.
120
+ - SOURCES.md records acquisition/extraction/verification status.
@@ -0,0 +1,16 @@
1
+ # SOURCES.md - DO-178C Certification 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
+ | RTCA DO-178C / ED-12C | software life cycle, objectives, DAL-specific evidence | true |
9
+ | RTCA DO-330 | tool qualification approach | true |
10
+ | SAE ARP4754A | development assurance context (shared with systems safety) | true |
11
+ | FAA AC 20-115D | FAA acceptance of DO-178C methods | false |
12
+ | EASA AMC 20-115C | EASA acceptance of ED-12C methods | false |
13
+
14
+ The PSAC and verification templates in `templates/` are original
15
+ structures informed by public-domain guidance (AC 20-115D, AMC 20-115C)
16
+ and summary process knowledge. No proprietary text is reproduced.
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ """cli.py - run the DO-178C Certification Engineer role.
3
+
4
+ Usage:
5
+ python3 cli.py build --out <file.md> # build PSAC for the example item
6
+ python3 cli.py check --file <file.md> # gate-check an existing PSAC
7
+
8
+ The role ENGINE (core/do178c_core.py) does the work standalone; bound
9
+ skills in Aero Agent Skills deepen individual stages when present.
10
+ """
11
+ import argparse
12
+ import os
13
+ import sys
14
+
15
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "core"))
16
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
17
+ "..", "..", "scripts"))
18
+ import do178c_core as core # noqa: E402
19
+ import evidence # noqa: E402
20
+
21
+ ROLE_SLUG = "do178c-cert-engineer"
22
+
23
+
24
+ def _provenance() -> dict:
25
+ """Provenance for the PSAC bundle: role core, no skill dispatch."""
26
+ return {
27
+ "role": ROLE_SLUG,
28
+ "core": {
29
+ "file": "core/do178c_core.py",
30
+ "functions": ["build_psac", "check_psac", "render_psac_markdown"],
31
+ "version": "0.1.0",
32
+ },
33
+ "skills": [],
34
+ "cross_checked": False,
35
+ "disclaimer": "DRAFT for human review. Not an approval document.",
36
+ }
37
+
38
+
39
+ def cmd_build(args):
40
+ item = core.example_item()
41
+ # allow overriding the failure condition from CLI for different DALs
42
+ if args.failure_condition:
43
+ item.failure_condition = args.failure_condition
44
+ item.system_safety_ref = args.safety_ref or item.system_safety_ref
45
+ model = core.build_psac(item)
46
+ md = core.render_psac_markdown(model)
47
+
48
+ # program profile (customer tailoring): load + apply context header
49
+ profile = None
50
+ try:
51
+ profile = evidence.load_profile(args.profile)
52
+ except ValueError as e:
53
+ print(f"error: bad profile: {e}")
54
+ return 1
55
+ if profile:
56
+ model["profile"] = {
57
+ "customer": profile.get("customer"),
58
+ "program": profile.get("program"),
59
+ "basis": profile.get("basis"),
60
+ "authority": profile.get("authority"),
61
+ "der": profile.get("der"),
62
+ "document_prefix": profile.get("document_prefix"),
63
+ "revision": profile.get("revision"),
64
+ }
65
+ md = evidence.profile_header_block(profile) + md
66
+
67
+ gates = core.check_psac(model)
68
+ if args.out:
69
+ with open(args.out, "w") as f:
70
+ f.write(md)
71
+ print(f"built PSAC (level {model['software_level']}) -> {args.out}")
72
+ if profile:
73
+ print(f"profile: {profile.get('customer')} / "
74
+ f"{profile.get('program')}")
75
+ print(f"gates: all_pass={gates['all_pass']} {gates}")
76
+ if args.bundle:
77
+ paths = evidence.write_bundle(
78
+ args.out, ROLE_SLUG,
79
+ "Plan for Software Aspects of Certification",
80
+ model, gates, _provenance())
81
+ print(f"bundle: model={paths['model']}")
82
+ print(f" gates={paths['gates']}")
83
+ print(f" provenance={paths['provenance']}")
84
+ else:
85
+ print(md)
86
+ return 0 if gates["all_pass"] else 1
87
+
88
+
89
+ def cmd_check(args):
90
+ if not os.path.exists(args.file):
91
+ print(f"file not found: {args.file}")
92
+ return 1
93
+ md = open(args.file).read()
94
+ # determine level from the doc if not given
95
+ dal = (args.dal or "B").upper()
96
+ gates = core.check_psac_markdown(md, dal)
97
+ for name, ok in gates.items():
98
+ if name != "all_pass":
99
+ print(f"{'PASS' if ok else 'FAIL'} {name}")
100
+ print(f"RESULT: {'PASS' if gates['all_pass'] else 'FAIL'}")
101
+ return 0 if gates["all_pass"] else 1
102
+
103
+
104
+ def main():
105
+ p = argparse.ArgumentParser(description="DO-178C Certification Engineer role")
106
+ sub = p.add_subparsers(dest="cmd", required=True)
107
+
108
+ b = sub.add_parser("build")
109
+ b.add_argument("--out", default="", help="output file (default: stdout)")
110
+ b.add_argument("--failure-condition", default="", help="hazardous/major/minor/...")
111
+ b.add_argument("--safety-ref", default="", help="system safety reference")
112
+ b.add_argument("--bundle", action="store_true",
113
+ help="emit evidence/{model,gates,provenance}.json")
114
+ b.add_argument("--profile", default="",
115
+ help="program profile JSON (customer tailoring, "
116
+ "docs/PROFILE-SCHEMA.md)")
117
+ b.set_defaults(fn=cmd_build)
118
+
119
+ c = sub.add_parser("check")
120
+ c.add_argument("--file", required=True, help="PSAC markdown file to gate-check")
121
+ c.add_argument("--dal", default="", help="expected software level")
122
+ c.set_defaults(fn=cmd_check)
123
+
124
+ args = p.parse_args()
125
+ sys.exit(args.fn(args))
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()