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.
- package/NOTICE +18 -0
- package/README.md +49 -0
- package/bin/aero-agent-roles.js +127 -0
- package/lib/catalog.js +97 -0
- package/lib/install.js +48 -0
- package/lib/mcp.js +199 -0
- package/manifest.json +516 -0
- package/package.json +44 -0
- package/roles/aerodynamics-engineer/ROLE.md +153 -0
- package/roles/aerodynamics-engineer/SOURCES.md +12 -0
- package/roles/aerodynamics-engineer/cli.py +143 -0
- package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
- package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
- package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
- package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
- package/roles/aircraft-design-engineer/ROLE.md +138 -0
- package/roles/aircraft-design-engineer/SOURCES.md +12 -0
- package/roles/aircraft-design-engineer/cli.py +152 -0
- package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
- package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
- package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
- package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
- package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
- package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
- package/roles/airworthiness-compliance-engineer/cli.py +146 -0
- package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
- package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
- package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
- package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
- package/roles/as9100-quality-auditor/ROLE.md +128 -0
- package/roles/as9100-quality-auditor/SOURCES.md +16 -0
- package/roles/as9100-quality-auditor/cli.py +136 -0
- package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
- package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
- package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
- package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
- package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
- package/roles/data-bus-avionics-engineer/cli.py +311 -0
- package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
- package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
- package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
- package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
- package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
- package/roles/do160-environmental-engineer/ROLE.md +136 -0
- package/roles/do160-environmental-engineer/SOURCES.md +30 -0
- package/roles/do160-environmental-engineer/cli.py +147 -0
- package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
- package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
- package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
- package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
- package/roles/do178c-cert-engineer/ROLE.md +120 -0
- package/roles/do178c-cert-engineer/SOURCES.md +16 -0
- package/roles/do178c-cert-engineer/cli.py +129 -0
- package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
- package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
- package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
- package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
- package/roles/do254-hardware-engineer/ROLE.md +127 -0
- package/roles/do254-hardware-engineer/SOURCES.md +19 -0
- package/roles/do254-hardware-engineer/cli.py +308 -0
- package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
- package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
- package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
- package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
- package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
- package/roles/engineering-analysis-engineer/ROLE.md +154 -0
- package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
- package/roles/engineering-analysis-engineer/cli.py +151 -0
- package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
- package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
- package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
- package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
- package/roles/flight-management-engineer/ROLE.md +160 -0
- package/roles/flight-management-engineer/SOURCES.md +30 -0
- package/roles/flight-management-engineer/cli.py +258 -0
- package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
- package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
- package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
- package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
- package/roles/flight-mechanics-engineer/ROLE.md +162 -0
- package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
- package/roles/flight-mechanics-engineer/cli.py +149 -0
- package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
- package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
- package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
- package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
- package/roles/flight-test-engineer/ROLE.md +137 -0
- package/roles/flight-test-engineer/SOURCES.md +24 -0
- package/roles/flight-test-engineer/cli.py +135 -0
- package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
- package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
- package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
- package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
- package/roles/flight-test-performance-engineer/ROLE.md +165 -0
- package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
- package/roles/flight-test-performance-engineer/cli.py +319 -0
- package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
- package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
- package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
- package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
- package/roles/gnc-engineer/ROLE.md +141 -0
- package/roles/gnc-engineer/SOURCES.md +35 -0
- package/roles/gnc-engineer/cli.py +141 -0
- package/roles/gnc-engineer/core/gnc_core.py +1074 -0
- package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
- package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
- package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
- package/roles/guidance-engineer/ROLE.md +156 -0
- package/roles/guidance-engineer/SOURCES.md +43 -0
- package/roles/guidance-engineer/cli.py +280 -0
- package/roles/guidance-engineer/core/guidance_core.py +820 -0
- package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
- package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
- package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
- package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
- package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
- package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
- package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
- package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
- package/roles/ndt-engineer/ROLE.md +157 -0
- package/roles/ndt-engineer/SOURCES.md +18 -0
- package/roles/ndt-engineer/cli.py +316 -0
- package/roles/ndt-engineer/core/ndt_core.py +1590 -0
- package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
- package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
- package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
- package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
- package/roles/numerical-analysis-engineer/ROLE.md +158 -0
- package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
- package/roles/numerical-analysis-engineer/cli.py +414 -0
- package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
- package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
- package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
- package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
- package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
- package/roles/propulsion-engineer/ROLE.md +163 -0
- package/roles/propulsion-engineer/SOURCES.md +15 -0
- package/roles/propulsion-engineer/cli.py +168 -0
- package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
- package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
- package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
- package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
- package/roles/space-systems-engineer/ROLE.md +161 -0
- package/roles/space-systems-engineer/SOURCES.md +49 -0
- package/roles/space-systems-engineer/cli.py +173 -0
- package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
- package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
- package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
- package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
- package/roles/state-estimation-engineer/ROLE.md +154 -0
- package/roles/state-estimation-engineer/SOURCES.md +27 -0
- package/roles/state-estimation-engineer/cli.py +326 -0
- package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
- package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
- package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
- package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
- package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
- package/roles/structures-loads-engineer/ROLE.md +171 -0
- package/roles/structures-loads-engineer/SOURCES.md +44 -0
- package/roles/structures-loads-engineer/cli.py +219 -0
- package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
- package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
- package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
- package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
- 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()
|