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,87 @@
|
|
|
1
|
+
# Plan for Hardware Aspects of Certification
|
|
2
|
+
|
|
3
|
+
**Item:** Autopilot Mode Logic FPGA (complex CPLD/FPGA-based LRU function)
|
|
4
|
+
**Design assurance level:** B (from hazardous failure condition)
|
|
5
|
+
**AEH class:** complex
|
|
6
|
+
**Certification basis:** FAR/CS-25
|
|
7
|
+
**Status:** draft-for-review
|
|
8
|
+
|
|
9
|
+
## 1. Scope
|
|
10
|
+
|
|
11
|
+
This plan covers the airborne electronic hardware item Autopilot Mode Logic FPGA (complex CPLD/FPGA-based LRU function). FPGA implementation of the autopilot flight mode selection and engage logic hosted in the flight guidance computer LRU.
|
|
12
|
+
The item is complex AEH per DO-254, technology CPLD/FPGA programmable logic.
|
|
13
|
+
|
|
14
|
+
## 2. Design assurance level and failure-condition basis
|
|
15
|
+
|
|
16
|
+
The hardware design assurance level is B based on the hazardous failure condition (system safety reference FHA-FGS-001 / PSSA rev C).
|
|
17
|
+
|
|
18
|
+
## 3. Hardware life cycle and life cycle data
|
|
19
|
+
|
|
20
|
+
The waterfall life cycle is used. The hardware life cycle data set for this level is:
|
|
21
|
+
|
|
22
|
+
- plan for hardware aspects of certification (PHAC)
|
|
23
|
+
- hardware requirements standards
|
|
24
|
+
- hardware design standards
|
|
25
|
+
- hardware validation plan
|
|
26
|
+
- hardware verification plan
|
|
27
|
+
- hardware configuration management plan
|
|
28
|
+
- hardware process assurance plan
|
|
29
|
+
- hardware requirements data
|
|
30
|
+
- hardware design data
|
|
31
|
+
- hardware verification data and results
|
|
32
|
+
- hardware life cycle environment configuration index
|
|
33
|
+
- hardware configuration index (HCI)
|
|
34
|
+
- hardware process assurance records
|
|
35
|
+
- hardware accomplishment summary (HAS)
|
|
36
|
+
|
|
37
|
+
## 4. Hardware design assurance environment
|
|
38
|
+
|
|
39
|
+
- Development tools: HDL synthesis tool, static timing analyzer
|
|
40
|
+
- Verification tools: HDL simulator, requirements-based test harness, code coverage tool
|
|
41
|
+
- Tool qualification: criteria-based per DO-254 tool assessment
|
|
42
|
+
|
|
43
|
+
## 5. Requirements capture and traceability
|
|
44
|
+
|
|
45
|
+
- Requirements under review: 12 (allocated 10, derived 2).
|
|
46
|
+
- Allocated requirements traced upward: 10 of 10 (complete).
|
|
47
|
+
- Derived requirements justified: 2 of 2 (complete).
|
|
48
|
+
- Capture readiness score: 0.83 (ready).
|
|
49
|
+
- Hardware requirements are reviewed for completeness, correctness, and verifiability with unique identifiers and trace links; vague wording fails the review.
|
|
50
|
+
|
|
51
|
+
## 6. Design and implementation
|
|
52
|
+
|
|
53
|
+
- HDL languages: VHDL.
|
|
54
|
+
- Target device: FPGA, flight guidance computer LRU.
|
|
55
|
+
- Conceptual design and detailed design data are produced for complex AEH and reviewed against the requirements.
|
|
56
|
+
|
|
57
|
+
## 7. Verification strategy
|
|
58
|
+
|
|
59
|
+
- Verification methods: analysis, review, test.
|
|
60
|
+
- Requirements-based coverage ratio: 0.98 (level B).
|
|
61
|
+
- Verification independence: independent.
|
|
62
|
+
- Hardware/software integration evidence ties the item to the software it hosts.
|
|
63
|
+
|
|
64
|
+
## 8. Configuration management
|
|
65
|
+
|
|
66
|
+
The hardware configuration management plan defines baselines, change control (ECR/ECO), and the hardware configuration index (HCI). Class 1 changes (form/fit/function, safety effect, or complex hardware) require baseline update, reverification, and independent review.
|
|
67
|
+
|
|
68
|
+
## 9. Process assurance
|
|
69
|
+
|
|
70
|
+
Hardware process assurance monitors conformity of the life cycle data and records objective evidence of process compliance.
|
|
71
|
+
|
|
72
|
+
## 10. Certification liaison and compliance
|
|
73
|
+
|
|
74
|
+
Certification authority liaison, means of compliance, and the acceptance basis (AC 20-152A) are addressed with the airworthiness authority. Previously developed hardware, when used, is assessed under the PDS process.
|
|
75
|
+
|
|
76
|
+
## 11. Objectives summary
|
|
77
|
+
|
|
78
|
+
- planning
|
|
79
|
+
- requirements
|
|
80
|
+
- design
|
|
81
|
+
- verification
|
|
82
|
+
- cm
|
|
83
|
+
- process-assurance
|
|
84
|
+
- cert-liaison
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
*Generated by Aero Agent Roles do254-hardware-engineer core (2026-09-05). DRAFT for human hardware certification engineer review. Not an approval document.*
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test the do254-hardware-engineer evidence bundle + profile + dispatch.
|
|
3
|
+
|
|
4
|
+
Verifies (offline, no network):
|
|
5
|
+
- build --bundle emits evidence/{model,gates,provenance}.json
|
|
6
|
+
- bundle JSON parses, model carries the PHAC numbers, gates all_pass
|
|
7
|
+
- skill dispatch cross-checks the bound do254 leaf logics against the
|
|
8
|
+
core when AeroSkills is present (all agree)
|
|
9
|
+
- standalone (no AeroSkills) exits 0 with cross_checked=false
|
|
10
|
+
- build --profile prepends the program-context header and records the
|
|
11
|
+
customer in model.json (PROFILE-SCHEMA.md)
|
|
12
|
+
- malformed profile fails loudly with exit 1
|
|
13
|
+
- cli check PASSes the built PHAC
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import unittest
|
|
22
|
+
|
|
23
|
+
ROLE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
CLI = os.path.join(ROLE_DIR, "cli.py")
|
|
25
|
+
# repo root is 4 up from tests/ (tests -> role -> roles -> repo)
|
|
26
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
|
27
|
+
os.path.dirname(os.path.abspath(__file__)))))
|
|
28
|
+
PROFILE = os.path.join(REPO_ROOT, "profiles", "example-airframer.json")
|
|
29
|
+
EXPECTED_CUSTOMER = "Example Airframers Ltd"
|
|
30
|
+
|
|
31
|
+
AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
|
|
32
|
+
HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_build(outdir, env_extra=None, extra_args=None):
|
|
36
|
+
env = dict(os.environ)
|
|
37
|
+
if env_extra:
|
|
38
|
+
env.update(env_extra)
|
|
39
|
+
args = [sys.executable, CLI, "build", "--out",
|
|
40
|
+
os.path.join(outdir, "phac.md"), "--bundle"]
|
|
41
|
+
if extra_args:
|
|
42
|
+
args.extend(extra_args)
|
|
43
|
+
return subprocess.run(args, capture_output=True, text=True, env=env)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestDo254BundleProfile(unittest.TestCase):
|
|
47
|
+
|
|
48
|
+
def setUp(self):
|
|
49
|
+
self.tmp = tempfile.mkdtemp()
|
|
50
|
+
|
|
51
|
+
def tearDown(self):
|
|
52
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
53
|
+
|
|
54
|
+
def _model(self):
|
|
55
|
+
return json.load(open(os.path.join(self.tmp, "evidence",
|
|
56
|
+
"model.json")))
|
|
57
|
+
|
|
58
|
+
def test_bundle_emits_three_files(self):
|
|
59
|
+
r = run_build(self.tmp)
|
|
60
|
+
self.assertEqual(r.returncode, 0, r.stderr[-500:])
|
|
61
|
+
ev = os.path.join(self.tmp, "evidence")
|
|
62
|
+
for f in ("model.json", "gates.json", "provenance.json"):
|
|
63
|
+
self.assertTrue(os.path.exists(os.path.join(ev, f)), f + " missing")
|
|
64
|
+
|
|
65
|
+
def test_model_has_phac_content(self):
|
|
66
|
+
r = run_build(self.tmp)
|
|
67
|
+
self.assertEqual(r.returncode, 0)
|
|
68
|
+
m = self._model()
|
|
69
|
+
self.assertEqual(m["role"], "do254-hardware-engineer")
|
|
70
|
+
self.assertEqual(m["deliverable_type"],
|
|
71
|
+
"Plan for Hardware Aspects of Certification")
|
|
72
|
+
self.assertIn("assurance_level", m)
|
|
73
|
+
self.assertIn("aeh_class", m)
|
|
74
|
+
self.assertEqual(m["aeh_class"], "complex")
|
|
75
|
+
self.assertEqual(m.get("status"), "draft-for-review")
|
|
76
|
+
|
|
77
|
+
def test_gates_all_pass_exit_zero(self):
|
|
78
|
+
r = run_build(self.tmp)
|
|
79
|
+
g = json.load(open(os.path.join(self.tmp, "evidence", "gates.json")))
|
|
80
|
+
self.assertTrue(g["all_pass"])
|
|
81
|
+
self.assertEqual(g["exit_code"], 0)
|
|
82
|
+
self.assertGreaterEqual(len(g["gates"]), 5)
|
|
83
|
+
|
|
84
|
+
def test_dispatch_crosschecks_when_skills_present(self):
|
|
85
|
+
if not HAS_SKILLS:
|
|
86
|
+
self.skipTest("AeroSkills not present")
|
|
87
|
+
r = run_build(self.tmp)
|
|
88
|
+
p = json.load(open(os.path.join(self.tmp, "evidence",
|
|
89
|
+
"provenance.json")))
|
|
90
|
+
self.assertTrue(p["cross_checked"])
|
|
91
|
+
# all four bound leaves dispatch at least one logic cross-check
|
|
92
|
+
leaves = {row["leaf"] for row in p["skills"]}
|
|
93
|
+
for leaf in ("avionics/do254/hardware-planning",
|
|
94
|
+
"avionics/do254/verification",
|
|
95
|
+
"avionics/do254/configuration-management",
|
|
96
|
+
"avionics/do254/requirements-capture"):
|
|
97
|
+
self.assertIn(leaf, leaves, f"no dispatch row for {leaf}")
|
|
98
|
+
for row in p["skills"]:
|
|
99
|
+
self.assertTrue(row["dispatched"])
|
|
100
|
+
self.assertTrue(row["agrees"], "core/skill disagree: %s" % row)
|
|
101
|
+
|
|
102
|
+
def test_standalone_honest_no_skills(self):
|
|
103
|
+
r = run_build(self.tmp, env_extra={"AEROSKILLS_DEV": "/nonexistent"})
|
|
104
|
+
self.assertEqual(r.returncode, 0, r.stderr[-500:])
|
|
105
|
+
p = json.load(open(os.path.join(self.tmp, "evidence",
|
|
106
|
+
"provenance.json")))
|
|
107
|
+
self.assertFalse(p["cross_checked"])
|
|
108
|
+
self.assertEqual(len(p["skills"]), 0)
|
|
109
|
+
g = json.load(open(os.path.join(self.tmp, "evidence", "gates.json")))
|
|
110
|
+
self.assertTrue(g["all_pass"])
|
|
111
|
+
|
|
112
|
+
def test_profile_prepends_context_and_records_customer(self):
|
|
113
|
+
if not os.path.exists(PROFILE):
|
|
114
|
+
self.skipTest("profiles/example-airframer.json not present")
|
|
115
|
+
r = run_build(self.tmp, extra_args=["--profile", PROFILE])
|
|
116
|
+
self.assertEqual(r.returncode, 0, r.stderr[-500:])
|
|
117
|
+
md = open(os.path.join(self.tmp, "phac.md")).read()
|
|
118
|
+
self.assertTrue(md.startswith("## Program context"),
|
|
119
|
+
"profile context header must lead the PHAC")
|
|
120
|
+
self.assertIn("Customer: " + EXPECTED_CUSTOMER, md)
|
|
121
|
+
m = self._model()
|
|
122
|
+
self.assertEqual(m["profile"]["customer"], EXPECTED_CUSTOMER)
|
|
123
|
+
self.assertEqual(m["profile"]["authority"], "FAA")
|
|
124
|
+
# profiled PHAC still passes the gate-check command
|
|
125
|
+
c = subprocess.run(
|
|
126
|
+
[sys.executable, CLI, "check", "--file",
|
|
127
|
+
os.path.join(self.tmp, "phac.md"), "--dal", "B"],
|
|
128
|
+
capture_output=True, text=True)
|
|
129
|
+
self.assertEqual(c.returncode, 0, c.stdout)
|
|
130
|
+
self.assertIn("RESULT: PASS", c.stdout)
|
|
131
|
+
|
|
132
|
+
def test_malformed_profile_fails_loudly(self):
|
|
133
|
+
bad = os.path.join(self.tmp, "bad-profile.json")
|
|
134
|
+
with open(bad, "w") as f:
|
|
135
|
+
json.dump({"program": "no customer here"}, f)
|
|
136
|
+
r = run_build(self.tmp, extra_args=["--profile", bad])
|
|
137
|
+
self.assertEqual(r.returncode, 1)
|
|
138
|
+
self.assertIn("error: bad profile", r.stdout)
|
|
139
|
+
|
|
140
|
+
def test_check_still_works(self):
|
|
141
|
+
r = run_build(self.tmp)
|
|
142
|
+
self.assertEqual(r.returncode, 0)
|
|
143
|
+
c = subprocess.run(
|
|
144
|
+
[sys.executable, CLI, "check", "--file",
|
|
145
|
+
os.path.join(self.tmp, "phac.md"), "--dal", "B"],
|
|
146
|
+
capture_output=True, text=True)
|
|
147
|
+
self.assertEqual(c.returncode, 0, c.stdout)
|
|
148
|
+
self.assertIn("RESULT: PASS", c.stdout)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
unittest.main()
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test do254_hardware_core: the executable engine of the DO-254 role.
|
|
3
|
+
|
|
4
|
+
Proves the role can DO its job standalone (no AeroSkills needed):
|
|
5
|
+
DAL determination, AEH classification, verification expectations,
|
|
6
|
+
configuration management, requirement traceability, PHAC generation,
|
|
7
|
+
and evidence-gate checks.
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
14
|
+
"..", "core"))
|
|
15
|
+
from do254_hardware_core import ( # noqa: E402
|
|
16
|
+
SEVERITY_TO_DAL, build_phac, capture_readiness, check_phac,
|
|
17
|
+
check_phac_markdown, classify_aeh, classify_derived, cm_actions,
|
|
18
|
+
coverage_adequate, coverage_ratio, example_item,
|
|
19
|
+
example_phac_markdown, hci_entry, hw_change_class,
|
|
20
|
+
independence_required, life_cycle_data, planning_artifacts,
|
|
21
|
+
render_phac_markdown, req_issues, traceability_summary,
|
|
22
|
+
verification_complete, verification_methods_for,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestCoreDomainRules(unittest.TestCase):
|
|
27
|
+
|
|
28
|
+
def test_severity_to_dal_mapping(self):
|
|
29
|
+
self.assertEqual(SEVERITY_TO_DAL["catastrophic"], "A")
|
|
30
|
+
self.assertEqual(SEVERITY_TO_DAL["hazardous"], "B")
|
|
31
|
+
self.assertEqual(SEVERITY_TO_DAL["major"], "C")
|
|
32
|
+
self.assertEqual(SEVERITY_TO_DAL["minor"], "D")
|
|
33
|
+
self.assertEqual(SEVERITY_TO_DAL["no-safety-effect"], "E")
|
|
34
|
+
|
|
35
|
+
def test_aeh_classification_complex(self):
|
|
36
|
+
# programmable logic / internal state / not fully verifiable from
|
|
37
|
+
# top data / safety significant => complex (full process)
|
|
38
|
+
self.assertEqual(classify_aeh(True, False, True, False), "complex")
|
|
39
|
+
self.assertEqual(classify_aeh(False, True, True, False), "complex")
|
|
40
|
+
self.assertEqual(classify_aeh(False, False, False, False), "complex")
|
|
41
|
+
self.assertEqual(classify_aeh(False, False, True, True), "complex")
|
|
42
|
+
self.assertEqual(classify_aeh(False, False, True, False), "simple")
|
|
43
|
+
|
|
44
|
+
def test_planning_artifacts(self):
|
|
45
|
+
self.assertIn("phac", planning_artifacts("complex"))
|
|
46
|
+
self.assertNotIn("detailed-design", planning_artifacts("simple"))
|
|
47
|
+
self.assertIn("verification", planning_artifacts("simple"))
|
|
48
|
+
|
|
49
|
+
def test_verification_methods(self):
|
|
50
|
+
self.assertEqual(verification_methods_for("complex", "B"),
|
|
51
|
+
{"test", "analysis", "review"})
|
|
52
|
+
self.assertEqual(verification_methods_for("simple", "B"),
|
|
53
|
+
{"review"})
|
|
54
|
+
|
|
55
|
+
def test_coverage_ratios(self):
|
|
56
|
+
self.assertEqual(coverage_ratio("A"), 0.98)
|
|
57
|
+
self.assertEqual(coverage_ratio("B"), 0.98)
|
|
58
|
+
self.assertEqual(coverage_ratio("C"), 0.95)
|
|
59
|
+
self.assertEqual(coverage_ratio("D"), 0.95)
|
|
60
|
+
self.assertIsNone(coverage_ratio("E"))
|
|
61
|
+
|
|
62
|
+
def test_independence(self):
|
|
63
|
+
self.assertTrue(independence_required("A"))
|
|
64
|
+
self.assertTrue(independence_required("B"))
|
|
65
|
+
self.assertFalse(independence_required("C"))
|
|
66
|
+
self.assertFalse(independence_required("D"))
|
|
67
|
+
|
|
68
|
+
def test_coverage_adequate(self):
|
|
69
|
+
self.assertTrue(coverage_adequate("A", 0.99))
|
|
70
|
+
self.assertFalse(coverage_adequate("A", 0.97))
|
|
71
|
+
self.assertTrue(coverage_adequate("E", 0.0)) # no target
|
|
72
|
+
|
|
73
|
+
def test_verification_complete(self):
|
|
74
|
+
self.assertTrue(verification_complete(
|
|
75
|
+
["test", "analysis", "review"], ["test", "review"]))
|
|
76
|
+
self.assertFalse(verification_complete(["review"], ["test"]))
|
|
77
|
+
|
|
78
|
+
def test_life_cycle_scope(self):
|
|
79
|
+
self.assertGreater(len(life_cycle_data("A")),
|
|
80
|
+
len(life_cycle_data("D")))
|
|
81
|
+
self.assertIn("plan for hardware aspects of certification (PHAC)",
|
|
82
|
+
life_cycle_data("B"))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestConfigManagementRules(unittest.TestCase):
|
|
86
|
+
|
|
87
|
+
def test_change_class_1_conditions(self):
|
|
88
|
+
# functional change => class 1
|
|
89
|
+
self.assertEqual(hw_change_class(
|
|
90
|
+
{"hardware_class": "simple", "safety_effect": "none",
|
|
91
|
+
"functional_change": True})["class"], 1)
|
|
92
|
+
# safety effect alone forces class 1
|
|
93
|
+
self.assertEqual(hw_change_class(
|
|
94
|
+
{"hardware_class": "simple", "safety_effect": "minor",
|
|
95
|
+
"functional_change": False})["class"], 1)
|
|
96
|
+
# complex hardware => class 1
|
|
97
|
+
self.assertEqual(hw_change_class(
|
|
98
|
+
{"hardware_class": "complex", "safety_effect": "none",
|
|
99
|
+
"functional_change": False})["class"], 1)
|
|
100
|
+
|
|
101
|
+
def test_change_class_2(self):
|
|
102
|
+
self.assertEqual(hw_change_class(
|
|
103
|
+
{"hardware_class": "simple", "safety_effect": "none",
|
|
104
|
+
"functional_change": False})["class"], 2)
|
|
105
|
+
|
|
106
|
+
def test_cm_actions_map(self):
|
|
107
|
+
c1 = cm_actions(1)
|
|
108
|
+
self.assertTrue(c1["reverification_required"])
|
|
109
|
+
self.assertTrue(c1["independent_review"])
|
|
110
|
+
c2 = cm_actions(2)
|
|
111
|
+
self.assertFalse(c2["reverification_required"])
|
|
112
|
+
self.assertFalse(c2["independent_review"])
|
|
113
|
+
self.assertTrue(c2["ecr_required"]) # documented ECR still required
|
|
114
|
+
|
|
115
|
+
def test_hci_entry_format(self):
|
|
116
|
+
self.assertEqual(hci_entry("HW-A", "Rev B", "BL-1"),
|
|
117
|
+
"HW-A Rev B BL-1")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestRequirementsRules(unittest.TestCase):
|
|
121
|
+
|
|
122
|
+
def test_vague_wording_flagged(self):
|
|
123
|
+
self.assertIn("vague", req_issues(
|
|
124
|
+
{"id": "R1", "text": "The item shall be suitable for flight.",
|
|
125
|
+
"traceable": True}))
|
|
126
|
+
self.assertNotIn("vague", req_issues(
|
|
127
|
+
{"id": "R2", "text": "The item shall assert the output.",
|
|
128
|
+
"traceable": True}))
|
|
129
|
+
|
|
130
|
+
def test_missing_id_and_trace(self):
|
|
131
|
+
issues = req_issues({"id": "", "text": "Req without id.",
|
|
132
|
+
"traceable": False})
|
|
133
|
+
self.assertIn("missing-id", issues)
|
|
134
|
+
self.assertIn("not-traceable", issues)
|
|
135
|
+
|
|
136
|
+
def test_derived_classification(self):
|
|
137
|
+
self.assertEqual(classify_derived(True), "allocated")
|
|
138
|
+
self.assertEqual(classify_derived(False), "derived")
|
|
139
|
+
|
|
140
|
+
def test_capture_readiness(self):
|
|
141
|
+
reqs = [
|
|
142
|
+
{"id": "R1", "text": "clean requirement one", "traceable": True},
|
|
143
|
+
{"id": "R2", "text": "clean requirement two", "traceable": True},
|
|
144
|
+
{"id": "R3", "text": "vague and adequate wording",
|
|
145
|
+
"traceable": True},
|
|
146
|
+
]
|
|
147
|
+
ready, score = capture_readiness(reqs)
|
|
148
|
+
self.assertAlmostEqual(score, 2 / 3)
|
|
149
|
+
self.assertFalse(ready) # below 0.7 threshold
|
|
150
|
+
|
|
151
|
+
def test_traceability_summary(self):
|
|
152
|
+
item = example_item()
|
|
153
|
+
t = traceability_summary(item.requirements)
|
|
154
|
+
self.assertEqual(t["total"], 12)
|
|
155
|
+
self.assertEqual(t["allocated"], 10)
|
|
156
|
+
self.assertEqual(t["derived"], 2)
|
|
157
|
+
self.assertTrue(t["allocated_traced_complete"])
|
|
158
|
+
self.assertTrue(t["derived_justified_complete"])
|
|
159
|
+
self.assertTrue(t["ready"])
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestPhacBuilder(unittest.TestCase):
|
|
163
|
+
|
|
164
|
+
def test_example_level_b_complex(self):
|
|
165
|
+
model = build_phac(example_item())
|
|
166
|
+
self.assertEqual(model["assurance_level"], "B")
|
|
167
|
+
self.assertEqual(model["aeh_class"], "complex")
|
|
168
|
+
self.assertEqual(model["coverage_ratio"], 0.98)
|
|
169
|
+
self.assertEqual(model["status"], "draft-for-review")
|
|
170
|
+
|
|
171
|
+
def test_phac_has_required_sections(self):
|
|
172
|
+
md = example_phac_markdown()
|
|
173
|
+
for sec in ["## 1. Scope", "## 5. Requirements capture",
|
|
174
|
+
"## 7. Verification strategy",
|
|
175
|
+
"## 8. Configuration management",
|
|
176
|
+
"## 11. Objectives summary"]:
|
|
177
|
+
self.assertIn(sec, md)
|
|
178
|
+
self.assertIn("not an approval", md.lower())
|
|
179
|
+
|
|
180
|
+
def test_core_gates_all_pass(self):
|
|
181
|
+
model = build_phac(example_item())
|
|
182
|
+
gates = check_phac(model)
|
|
183
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
184
|
+
|
|
185
|
+
def test_markdown_gates_all_pass(self):
|
|
186
|
+
md = example_phac_markdown()
|
|
187
|
+
gates = check_phac_markdown(md, "B")
|
|
188
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
189
|
+
|
|
190
|
+
def test_standalone_no_skills_repo(self):
|
|
191
|
+
# Core must work with no AeroSkills present: set env to a bad path.
|
|
192
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
193
|
+
model = build_phac(example_item())
|
|
194
|
+
self.assertIn(model["assurance_level"], "ABCDE")
|
|
195
|
+
md = render_phac_markdown(model)
|
|
196
|
+
self.assertGreater(len(md), 1000)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
unittest.main()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Role test: DO-254 Airborne Electronic Hardware 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 complex CPLD/FPGA LRU item produces
|
|
9
|
+
a complete PHAC skeleton with no blank sections.
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
|
|
17
|
+
os.path.abspath(__file__))))) # repo root (4 up from tests/)
|
|
18
|
+
# Bound-skill resolution requires the Aero Agent Skills checkout. It is a
|
|
19
|
+
# cross-repo dev check: when the skills repo is absent (fresh public clone),
|
|
20
|
+
# skip resolution rather than fail - the role's bound list is verified in CI.
|
|
21
|
+
AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
|
|
22
|
+
HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
23
|
+
ROLE_DIR = os.path.join(ROLES_REPO, "roles", "do254-hardware-engineer")
|
|
24
|
+
PHAC_TEMPLATE = os.path.join(ROLE_DIR, "templates", "phac-template.md")
|
|
25
|
+
|
|
26
|
+
EXPECTED_BOUND = [
|
|
27
|
+
"avionics/do254/hardware-planning",
|
|
28
|
+
"avionics/do254/requirements-capture",
|
|
29
|
+
"avionics/do254/verification",
|
|
30
|
+
"avionics/do254/configuration-management",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
EXPECTED_STAGES = [
|
|
34
|
+
"Hardware planning", "Requirements capture", "Verification strategy",
|
|
35
|
+
"Configuration management", "Process assurance",
|
|
36
|
+
"Certification liaison",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_role_md():
|
|
41
|
+
p = os.path.join(ROLE_DIR, "ROLE.md")
|
|
42
|
+
if not os.path.exists(p):
|
|
43
|
+
return None
|
|
44
|
+
return open(p).read()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestDo254HardwareRole(unittest.TestCase):
|
|
48
|
+
|
|
49
|
+
def test_bound_skills_resolve_in_aeroskills(self):
|
|
50
|
+
if not HAS_SKILLS:
|
|
51
|
+
self.skipTest("Aero Agent Skills checkout not present "
|
|
52
|
+
"(cross-repo dev check)")
|
|
53
|
+
for leaf in EXPECTED_BOUND:
|
|
54
|
+
sk = os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")
|
|
55
|
+
self.assertTrue(
|
|
56
|
+
os.path.exists(sk),
|
|
57
|
+
f"bound skill not found in aero-agent-skills: {leaf}")
|
|
58
|
+
# every bound leaf ships a logic file the CLI can dispatch
|
|
59
|
+
scripts = os.path.join(AEROSKILLS, "skills", leaf, "scripts")
|
|
60
|
+
logic = [f for f in os.listdir(scripts)
|
|
61
|
+
if f.endswith("_logic.py")]
|
|
62
|
+
self.assertTrue(logic, f"bound leaf lacks logic file: {leaf}")
|
|
63
|
+
|
|
64
|
+
def test_bound_skills_listed_in_frontmatter(self):
|
|
65
|
+
role = read_role_md()
|
|
66
|
+
self.assertIsNotNone(role)
|
|
67
|
+
for leaf in EXPECTED_BOUND:
|
|
68
|
+
self.assertIn(leaf, role, f"missing in ROLE.md skills_bound: {leaf}")
|
|
69
|
+
|
|
70
|
+
def test_workflow_stages_deterministic(self):
|
|
71
|
+
role = read_role_md()
|
|
72
|
+
idx = [role.find(stage) for stage in EXPECTED_STAGES]
|
|
73
|
+
self.assertTrue(all(i >= 0 for i in idx), "a stage is missing")
|
|
74
|
+
self.assertEqual(idx, sorted(idx), "stage order not deterministic")
|
|
75
|
+
|
|
76
|
+
def test_deliverable_template_complete(self):
|
|
77
|
+
if not os.path.exists(PHAC_TEMPLATE):
|
|
78
|
+
self.fail("phac-template.md missing")
|
|
79
|
+
text = open(PHAC_TEMPLATE).read()
|
|
80
|
+
for n in range(1, 12):
|
|
81
|
+
self.assertTrue(
|
|
82
|
+
re.search(rf"^## {n}\. ", text, re.M),
|
|
83
|
+
f"section {n} missing")
|
|
84
|
+
self.assertIn("DRAFT", text)
|
|
85
|
+
low = text.lower()
|
|
86
|
+
self.assertIn("not an approval", low)
|
|
87
|
+
self.assertIn("document", low)
|
|
88
|
+
self.assertNotIn("___", text) # zero blanks
|
|
89
|
+
|
|
90
|
+
def test_forbidden_lines_present(self):
|
|
91
|
+
role = read_role_md()
|
|
92
|
+
for phrase in ["issue certification approval", "regulatory sign-off",
|
|
93
|
+
"reproduce proprietary", "sign_off_required: true"]:
|
|
94
|
+
self.assertIn(phrase, role, f"missing boundary: {phrase}")
|
|
95
|
+
|
|
96
|
+
def test_sources_register_exists(self):
|
|
97
|
+
p = os.path.join(ROLE_DIR, "SOURCES.md")
|
|
98
|
+
self.assertTrue(os.path.exists(p), "SOURCES.md missing for role")
|
|
99
|
+
|
|
100
|
+
def test_core_engine_exists(self):
|
|
101
|
+
"""100% standard: every role ships an executable core + cli."""
|
|
102
|
+
for f in ["core", "cli.py"]:
|
|
103
|
+
self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, f)),
|
|
104
|
+
f"{f} missing - role is not executable")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
unittest.main()
|