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,134 @@
|
|
|
1
|
+
# Avionics Data Bus Loading and Protocol Assessment
|
|
2
|
+
|
|
3
|
+
**Aircraft / program:** EX-100 Reference Example Transport
|
|
4
|
+
**Status:** draft-for-review
|
|
5
|
+
**Protocol basis:** MIL-STD-1553B / ARINC 429 Mark 33 DITS / ARINC 664 Part 7 protocol practice
|
|
6
|
+
|
|
7
|
+
## 1. Scope
|
|
8
|
+
|
|
9
|
+
This assessment budgets every ARINC 429 transmit line, the MIL-STD-1553 bus-controller minor-frame schedule, and the ARINC 664 AFDX virtual-link set of the aircraft bus architecture, and checks received-word conformance (parity, label, word fields) against the interface control documents. Flight deck avionics data bus architecture: ADC, IRS and legacy utility concentrator ARINC 429 transmit lines, a dual-redundant MIL-STD-1553 bus under one bus controller, and a growth AFDX core network.
|
|
10
|
+
|
|
11
|
+
Loading gates: ARINC 429 and MIL-STD-1553 utilization against the 80% design guideline (the leaf practice; ARINC 429 capacity is the link itself), AFDX VL set against the 100 Mbps link.
|
|
12
|
+
|
|
13
|
+
## 2. ARINC 429 bus loading
|
|
14
|
+
|
|
15
|
+
### A429-1 ADC/FMS data (ADC-1 -> FMS-1, EFIS-1, 100 kbps)
|
|
16
|
+
|
|
17
|
+
| Label | Rate (words/s) |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 010 | 25 |
|
|
20
|
+
| 011 | 25 |
|
|
21
|
+
| 036 | 50 |
|
|
22
|
+
| 100 | 50 |
|
|
23
|
+
| 203 | 12.5 |
|
|
24
|
+
| 210 | 25 |
|
|
25
|
+
| 231 | 25 |
|
|
26
|
+
| 320 | 12.5 |
|
|
27
|
+
| 365 | 12.5 |
|
|
28
|
+
| 370 | 12.5 |
|
|
29
|
+
|
|
30
|
+
- Total word rate: **250.00 words/s**
|
|
31
|
+
- Bus load: **9000 bps** (36 bit-times per word)
|
|
32
|
+
- Bus utilization: **9.00%** of 100 kbps (capacity 2777.8 words/s)
|
|
33
|
+
- Capacity verdict: **FITS**; design guideline (80%): **FITS**
|
|
34
|
+
- Headroom to guideline: **71.00%**
|
|
35
|
+
|
|
36
|
+
### A429-2 IRS/FCC data (IRS-1 -> FCC-1, EFIS-1, 100 kbps)
|
|
37
|
+
|
|
38
|
+
| Label | Rate (words/s) |
|
|
39
|
+
|---|---|
|
|
40
|
+
| 320 | 25 |
|
|
41
|
+
| 365 | 25 |
|
|
42
|
+
| 366 | 25 |
|
|
43
|
+
| 367 | 25 |
|
|
44
|
+
| 370 | 25 |
|
|
45
|
+
| 371 | 25 |
|
|
46
|
+
| 372 | 25 |
|
|
47
|
+
| 375 | 12.5 |
|
|
48
|
+
| 376 | 12.5 |
|
|
49
|
+
|
|
50
|
+
- Total word rate: **200.00 words/s**
|
|
51
|
+
- Bus load: **7200 bps** (36 bit-times per word)
|
|
52
|
+
- Bus utilization: **7.20%** of 100 kbps (capacity 2777.8 words/s)
|
|
53
|
+
- Capacity verdict: **FITS**; design guideline (80%): **FITS**
|
|
54
|
+
- Headroom to guideline: **72.80%**
|
|
55
|
+
|
|
56
|
+
### A429-3 fuel/utility (low speed) (UTA-1 -> EICAS-1, 12.5 kbps)
|
|
57
|
+
|
|
58
|
+
| Label | Rate (words/s) |
|
|
59
|
+
|---|---|
|
|
60
|
+
| 250 | 20 |
|
|
61
|
+
| 251 | 20 |
|
|
62
|
+
| 252 | 20 |
|
|
63
|
+
| 253 | 20 |
|
|
64
|
+
| 254 | 20 |
|
|
65
|
+
| 255 | 20 |
|
|
66
|
+
| 256 | 20 |
|
|
67
|
+
| 257 | 20 |
|
|
68
|
+
| 260 | 20 |
|
|
69
|
+
| 261 | 20 |
|
|
70
|
+
| 262 | 20 |
|
|
71
|
+
| 263 | 20 |
|
|
72
|
+
| 264 | 20 |
|
|
73
|
+
|
|
74
|
+
- Total word rate: **260.00 words/s**
|
|
75
|
+
- Bus load: **9360 bps** (36 bit-times per word)
|
|
76
|
+
- Bus utilization: **74.88%** of 12.5 kbps (capacity 347.2 words/s)
|
|
77
|
+
- Capacity verdict: **FITS**; design guideline (80%): **FITS**
|
|
78
|
+
- Headroom to guideline: **5.12%**
|
|
79
|
+
|
|
80
|
+
## 3. MIL-STD-1553 bus loading
|
|
81
|
+
|
|
82
|
+
### 1553-B1 mission bus (BC Mission Computer (MC-1); RTs RT 1 (FMS-1), RT 2 (NAV-1), RT 3 (FCC-1); minor frame 5000 us)
|
|
83
|
+
|
|
84
|
+
| Message | Data words | Wire words | Bus time (us) |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| BCRT | 32 | 34 | 816 |
|
|
87
|
+
| RTBC | 16 | 18 | 432 |
|
|
88
|
+
| BCRT | 16 | 18 | 432 |
|
|
89
|
+
| RTRT | 8 | 11 | 264 |
|
|
90
|
+
| RTBC | 8 | 10 | 240 |
|
|
91
|
+
| BCRT | 8 | 10 | 240 |
|
|
92
|
+
| BCRT | 4 | 6 | 144 |
|
|
93
|
+
| RTRT | 4 | 7 | 168 |
|
|
94
|
+
|
|
95
|
+
- Schedule time: **2736 us** over 5000 us (114 wire words x 24 us)
|
|
96
|
+
- Bus utilization: **54.72%**
|
|
97
|
+
- Budget (80% of frame): 4000 us; headroom 1264 us (25.28%)
|
|
98
|
+
- Verdict: **FITS**
|
|
99
|
+
|
|
100
|
+
## 4. ARINC 664 AFDX network
|
|
101
|
+
|
|
102
|
+
### AFDX core network (growth) (dual independent networks A and B, same VL set)
|
|
103
|
+
|
|
104
|
+
- Virtual links: 14; total bandwidth **10.816 Mbps**
|
|
105
|
+
- Link utilization: **10.82%** of 100 Mbps
|
|
106
|
+
- Link budget verdict: **FITS**
|
|
107
|
+
|
|
108
|
+
| VLs | BAG (ms) | Frame (bytes) | Bandwidth (Mbps) |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| 1 | 4 | 1518 | 3.036 |
|
|
111
|
+
| 1 | 8 | 1518 | 1.518 |
|
|
112
|
+
| 6 | 16 | 1518 | 0.759 |
|
|
113
|
+
| 4 | 32 | 1518 | 0.380 |
|
|
114
|
+
| 2 | 128 | 1518 | 0.095 |
|
|
115
|
+
|
|
116
|
+
- Worst-case latency (1518-byte frame, 2 switch(es) at 150 us): **542.88 us**
|
|
117
|
+
- Jitter: measured 420 us vs budget 500 us -> slack **80.0 us**
|
|
118
|
+
## 5. Protocol conformance checks
|
|
119
|
+
|
|
120
|
+
- ARINC 429 received word 0x60134908: label 010 (8), SDI 1, data field 1234 (BNR 123.4 at LSB 0.1), SSM 3, parity OK
|
|
121
|
+
- Expected label 010 (8): MATCH
|
|
122
|
+
- MIL-STD-1553 command word 16 data words, RT 5 subaddress 12, RT-to-BC: classified **rt-to-bc**, parity OK
|
|
123
|
+
- MIL-STD-1553 status word RT 5: busy=1, terminal_flag=0, parity OK
|
|
124
|
+
|
|
125
|
+
## 6. Findings and observations
|
|
126
|
+
|
|
127
|
+
- **O-1** A429-3 fuel/utility (low speed) sits at 74.88% utilization with only 5.12% headroom to the 80% guideline: growth requires a re-rate or a new line.
|
|
128
|
+
|
|
129
|
+
## 7. Conclusion
|
|
130
|
+
|
|
131
|
+
The bus architecture loading and received-word conformance results above are computed by the data-bus-avionics-engineer core from the bound ARINC 429, MIL-STD-1553 and ARINC 664 AFDX leaf rules (36 bit-times per ARINC 429 word; 24 us per MIL-STD-1553 wire word; frame x 8 / BAG for AFDX VL bandwidth). Numbers are cross-checked against the AeroSkills leaf logic when the skills library is present and recorded in the evidence bundle.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
*Generated by Aero Agent Roles data-bus-avionics-engineer core (2026-09-05). DRAFT for human avionics/data-bus engineer review. Not an approval document.*
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test the evidence bundle + skill-dispatch protocol (PROTOCOL.md v1).
|
|
3
|
+
|
|
4
|
+
Verifies:
|
|
5
|
+
- build --bundle emits evidence/{model,gates,provenance}.json
|
|
6
|
+
- bundle JSON parses, model contains computed bus loading numbers,
|
|
7
|
+
gates all_pass
|
|
8
|
+
- the five bound avionics/data-bus leaf logic modules dispatch and
|
|
9
|
+
cross-check their numbers against the core (agree within 1e-6) when
|
|
10
|
+
AeroSkills is present
|
|
11
|
+
- standalone (no AeroSkills) emits the bundle honestly with
|
|
12
|
+
cross_checked=false
|
|
13
|
+
- build --profile prepends a program context header
|
|
14
|
+
- check gate-checks a built assessment
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
import unittest
|
|
23
|
+
|
|
24
|
+
ROLE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
25
|
+
CLI = os.path.join(ROLE_DIR, "cli.py")
|
|
26
|
+
PROFILE = os.path.normpath(os.path.join(ROLE_DIR, "..", "..", "profiles",
|
|
27
|
+
"example-airframer.json"))
|
|
28
|
+
|
|
29
|
+
AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
|
|
30
|
+
HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
31
|
+
|
|
32
|
+
EXPECTED_LEAVES = [
|
|
33
|
+
"arinc429-protocol", "arinc429-bus-loading", "arinc664-afdx",
|
|
34
|
+
"mil-std-1553", "mil-std-1553-bus-loading",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def run_build(outdir, env_extra=None, extra_args=None):
|
|
39
|
+
env = dict(os.environ)
|
|
40
|
+
if env_extra:
|
|
41
|
+
env.update(env_extra)
|
|
42
|
+
args = [sys.executable, CLI, "build", "--out",
|
|
43
|
+
os.path.join(outdir, "assessment.md"), "--bundle"]
|
|
44
|
+
if extra_args:
|
|
45
|
+
args.extend(extra_args)
|
|
46
|
+
r = subprocess.run(args, capture_output=True, text=True, env=env)
|
|
47
|
+
return r
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestBundleProtocol(unittest.TestCase):
|
|
51
|
+
|
|
52
|
+
def setUp(self):
|
|
53
|
+
self.tmp = tempfile.mkdtemp()
|
|
54
|
+
|
|
55
|
+
def tearDown(self):
|
|
56
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
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_numbers_and_status(self):
|
|
66
|
+
run_build(self.tmp)
|
|
67
|
+
m = json.load(open(os.path.join(self.tmp, "evidence", "model.json")))
|
|
68
|
+
self.assertIn("arinc429", m)
|
|
69
|
+
self.assertIn("m1553", m)
|
|
70
|
+
self.assertIn("afdx", m)
|
|
71
|
+
self.assertEqual(m.get("status"), "draft-for-review")
|
|
72
|
+
self.assertIn("role", m)
|
|
73
|
+
self.assertEqual(m["role"], "data-bus-avionics-engineer")
|
|
74
|
+
# the model must carry the numbers that appear in the markdown
|
|
75
|
+
self.assertAlmostEqual(
|
|
76
|
+
m["m1553"]["schedule"]["utilization_pct"], 54.72, places=4)
|
|
77
|
+
self.assertAlmostEqual(m["afdx"]["utilization_pct"], 10.81575,
|
|
78
|
+
places=4)
|
|
79
|
+
|
|
80
|
+
def test_gates_all_pass_exit_zero(self):
|
|
81
|
+
r = run_build(self.tmp)
|
|
82
|
+
g = json.load(open(os.path.join(self.tmp, "evidence", "gates.json")))
|
|
83
|
+
self.assertTrue(g["all_pass"])
|
|
84
|
+
self.assertEqual(g["exit_code"], 0)
|
|
85
|
+
self.assertGreaterEqual(len(g["gates"]), 3)
|
|
86
|
+
|
|
87
|
+
def test_dispatch_crosschecks_when_skills_present(self):
|
|
88
|
+
if not HAS_SKILLS:
|
|
89
|
+
self.skipTest("AeroSkills not present")
|
|
90
|
+
r = run_build(self.tmp)
|
|
91
|
+
self.assertEqual(r.returncode, 0, r.stderr[-500:])
|
|
92
|
+
p = json.load(open(os.path.join(self.tmp, "evidence",
|
|
93
|
+
"provenance.json")))
|
|
94
|
+
self.assertTrue(p["cross_checked"])
|
|
95
|
+
self.assertEqual(len(p["skills"]), 5)
|
|
96
|
+
leafs = {row["leaf"].rsplit("/", 1)[-1] for row in p["skills"]}
|
|
97
|
+
self.assertEqual(leafs, set(EXPECTED_LEAVES))
|
|
98
|
+
for row in p["skills"]:
|
|
99
|
+
self.assertTrue(row["dispatched"])
|
|
100
|
+
self.assertTrue(row["agrees"],
|
|
101
|
+
"core/skill disagree for %s: %s"
|
|
102
|
+
% (row["function"], row))
|
|
103
|
+
self.assertLessEqual(row["delta"], 1e-6)
|
|
104
|
+
funcs = {row["function"] for row in p["skills"]}
|
|
105
|
+
self.assertIn("vl_bandwidth", funcs)
|
|
106
|
+
self.assertIn("schedule_utilization", funcs)
|
|
107
|
+
|
|
108
|
+
def test_standalone_honest_no_skills(self):
|
|
109
|
+
r = run_build(self.tmp, env_extra={"AEROSKILLS_DEV": "/nonexistent"})
|
|
110
|
+
self.assertEqual(r.returncode, 0)
|
|
111
|
+
p = json.load(open(os.path.join(self.tmp, "evidence",
|
|
112
|
+
"provenance.json")))
|
|
113
|
+
self.assertFalse(p["cross_checked"])
|
|
114
|
+
self.assertEqual(len(p["skills"]), 0)
|
|
115
|
+
|
|
116
|
+
def test_profile_header_applied(self):
|
|
117
|
+
if not os.path.exists(PROFILE):
|
|
118
|
+
self.skipTest("profiles/example-airframer.json not present")
|
|
119
|
+
r = run_build(self.tmp, extra_args=["--profile", PROFILE])
|
|
120
|
+
self.assertEqual(r.returncode, 0, r.stderr[-500:])
|
|
121
|
+
md = open(os.path.join(self.tmp, "assessment.md")).read()
|
|
122
|
+
self.assertIn("Example Airframers Ltd", md)
|
|
123
|
+
self.assertIn("Program context (profile)", md)
|
|
124
|
+
m = json.load(open(os.path.join(self.tmp, "evidence", "model.json")))
|
|
125
|
+
self.assertEqual(m["profile"]["customer"], "Example Airframers Ltd")
|
|
126
|
+
|
|
127
|
+
def test_check_still_works(self):
|
|
128
|
+
run_build(self.tmp)
|
|
129
|
+
md = os.path.join(self.tmp, "assessment.md")
|
|
130
|
+
c = subprocess.run([sys.executable, CLI, "check", "--file", md],
|
|
131
|
+
capture_output=True, text=True)
|
|
132
|
+
self.assertEqual(c.returncode, 0, c.stdout)
|
|
133
|
+
self.assertIn("RESULT: PASS", c.stdout)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
unittest.main()
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test data_bus_avionics_core: the executable engine of the Data Bus role.
|
|
3
|
+
|
|
4
|
+
Proves the role can DO its job standalone (no AeroSkills needed):
|
|
5
|
+
ARINC 429 word decode/build with odd parity, ARINC 429 bus loading %,
|
|
6
|
+
MIL-STD-1553 word encode/decode and message classification, MIL-STD-1553
|
|
7
|
+
BC minor-frame bus loading, AFDX VL bandwidth and link budget, the
|
|
8
|
+
assessment builder + evidence gates, and standalone operation.
|
|
9
|
+
|
|
10
|
+
Real computed anchors (from the bound AeroSkills leaf logic tests):
|
|
11
|
+
- ARINC 429 build_word(8, 1, 1234, 3) == 1611876616 (0x60134908)
|
|
12
|
+
- MIL-STD-1553 encode_command_word(5, 12, 16, 1) == 93316
|
|
13
|
+
- MIL-STD-1553 encode_data_word(0x7FFF) == 262139
|
|
14
|
+
- MIL-STD-1553 encode_status_word(5, busy=1) == 606276
|
|
15
|
+
- AFDX vl_bandwidth(4 ms, 1518) == 3036000.0 bps
|
|
16
|
+
- AFDX 30 x (4 ms, 1518) VLs == 91.08% of the 100 Mbps link
|
|
17
|
+
"""
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import unittest
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
23
|
+
"..", "core"))
|
|
24
|
+
from data_bus_avionics_core import ( # noqa: E402
|
|
25
|
+
a429_bnr_decode, a429_bnr_encode, a429_build_word, a429_decode_word,
|
|
26
|
+
a429_loading_summary, a429_parity_ok, a429_total_word_rate,
|
|
27
|
+
a429_utilization_pct, a429_word_capacity,
|
|
28
|
+
afdx_end_to_end_latency_us, afdx_jitter_slack,
|
|
29
|
+
afdx_largest_bag_for_bandwidth, afdx_link_utilization_pct,
|
|
30
|
+
afdx_transmission_time_us, afdx_vl_bandwidth,
|
|
31
|
+
analyze_architecture, build_assessment, check_assessment,
|
|
32
|
+
check_assessment_markdown, example_assessment_markdown, example_item,
|
|
33
|
+
m1553_classify_message, m1553_decode_command_word,
|
|
34
|
+
m1553_decode_status_word, m1553_encode_command_word,
|
|
35
|
+
m1553_encode_data_word, m1553_encode_status_word, m1553_message_time_us,
|
|
36
|
+
m1553_schedule_utilization, m1553_wire_words, render_assessment_markdown,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
A429_WORD = 1611876616 # 0x60134908: label 010, SDI 1, data 1234
|
|
40
|
+
M1553_CMD = 93316 # RT 5, SA 12, WC 16, T/R 1
|
|
41
|
+
M1553_STATUS = 606276 # RT 5, busy=1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TestArinc429Protocol(unittest.TestCase):
|
|
45
|
+
|
|
46
|
+
def test_word_build_anchor(self):
|
|
47
|
+
self.assertEqual(a429_build_word(8, 1, 1234, 3), A429_WORD)
|
|
48
|
+
self.assertEqual(a429_build_word("010", 1, 1234, 3), A429_WORD)
|
|
49
|
+
|
|
50
|
+
def test_word_decode_fields(self):
|
|
51
|
+
d = a429_decode_word(A429_WORD)
|
|
52
|
+
self.assertEqual(d["label"], 8) # octal 010
|
|
53
|
+
self.assertEqual(d["sdi"], 1)
|
|
54
|
+
self.assertEqual(d["data"], 1234)
|
|
55
|
+
self.assertEqual(d["ssm"], 3)
|
|
56
|
+
self.assertTrue(d["parity_ok"])
|
|
57
|
+
self.assertTrue(a429_parity_ok(A429_WORD))
|
|
58
|
+
|
|
59
|
+
def test_word_round_trip(self):
|
|
60
|
+
w = a429_build_word(0o365, 2, 777, 1)
|
|
61
|
+
d = a429_decode_word(w)
|
|
62
|
+
self.assertEqual((d["label"], d["sdi"], d["data"], d["ssm"]),
|
|
63
|
+
(0o365, 2, 777, 1))
|
|
64
|
+
|
|
65
|
+
def test_bnr_encode_decode(self):
|
|
66
|
+
self.assertEqual(a429_bnr_encode(123.4, 0.1), 1234)
|
|
67
|
+
self.assertAlmostEqual(a429_bnr_decode(1234, 0.1), 123.4, places=6)
|
|
68
|
+
neg = a429_bnr_encode(-12.3, 0.1)
|
|
69
|
+
self.assertEqual(neg, 524165)
|
|
70
|
+
self.assertAlmostEqual(a429_bnr_decode(neg, 0.1), -12.3, places=6)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestArinc429Loading(unittest.TestCase):
|
|
74
|
+
|
|
75
|
+
def test_total_word_rate_and_load(self):
|
|
76
|
+
rates = {"010": 100.0, "011": 100.0}
|
|
77
|
+
self.assertEqual(a429_total_word_rate(rates), 200.0)
|
|
78
|
+
self.assertAlmostEqual(a429_utilization_pct(200.0), 7.2, places=6)
|
|
79
|
+
self.assertAlmostEqual(a429_utilization_pct(200.0, 12500.0),
|
|
80
|
+
57.6, places=6)
|
|
81
|
+
|
|
82
|
+
def test_capacity(self):
|
|
83
|
+
# ~2777.8 words/s at 100 kbps, ~347.2 at 12.5 kbps (36 bit-times)
|
|
84
|
+
self.assertAlmostEqual(a429_word_capacity(100000.0), 2777.7778,
|
|
85
|
+
places=3)
|
|
86
|
+
self.assertAlmostEqual(a429_word_capacity(12500.0), 347.2222,
|
|
87
|
+
places=3)
|
|
88
|
+
|
|
89
|
+
def test_fits_with_headroom(self):
|
|
90
|
+
s = a429_loading_summary({"010": 250.0}, 100000.0)
|
|
91
|
+
self.assertEqual(s["capacity_verdict"], "FITS")
|
|
92
|
+
self.assertEqual(s["guideline_verdict"], "FITS")
|
|
93
|
+
self.assertAlmostEqual(s["utilization_pct"], 9.0, places=6)
|
|
94
|
+
self.assertAlmostEqual(s["headroom_pct"], 71.0, places=6)
|
|
95
|
+
|
|
96
|
+
def test_guideline_violation_not_capacity_over(self):
|
|
97
|
+
# 2500 words/s -> 90% of the 100 kbps link: fits capacity,
|
|
98
|
+
# violates the 80% design guideline.
|
|
99
|
+
s = a429_loading_summary({"010": 2500.0}, 100000.0)
|
|
100
|
+
self.assertAlmostEqual(s["utilization_pct"], 90.0, places=6)
|
|
101
|
+
self.assertEqual(s["capacity_verdict"], "FITS")
|
|
102
|
+
self.assertEqual(s["guideline_verdict"], "VIOLATION")
|
|
103
|
+
self.assertEqual(s["headroom_pct"], 0.0)
|
|
104
|
+
|
|
105
|
+
def test_capacity_over(self):
|
|
106
|
+
# 3100 words/s -> 111.6%: beyond the link itself.
|
|
107
|
+
s = a429_loading_summary({"010": 3100.0}, 100000.0)
|
|
108
|
+
self.assertAlmostEqual(s["utilization_pct"], 111.6, places=6)
|
|
109
|
+
self.assertEqual(s["capacity_verdict"], "OVER")
|
|
110
|
+
|
|
111
|
+
def test_low_speed_line_near_guideline(self):
|
|
112
|
+
# 13 labels x 20 wps = 260 wps -> 74.88% of the 12.5 kbps link
|
|
113
|
+
rates = {oct(0o250 + i)[2:]: 20.0 for i in range(13)}
|
|
114
|
+
s = a429_loading_summary(rates, 12500.0)
|
|
115
|
+
self.assertAlmostEqual(s["total_words_per_s"], 260.0, places=6)
|
|
116
|
+
self.assertAlmostEqual(s["utilization_pct"], 74.88, places=4)
|
|
117
|
+
self.assertEqual(s["guideline_verdict"], "FITS")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestM1553Protocol(unittest.TestCase):
|
|
121
|
+
|
|
122
|
+
def test_command_word_anchor(self):
|
|
123
|
+
self.assertEqual(m1553_encode_command_word(5, 12, 16, 1), M1553_CMD)
|
|
124
|
+
d = m1553_decode_command_word(M1553_CMD)
|
|
125
|
+
self.assertEqual(d["rt_address"], 5)
|
|
126
|
+
self.assertEqual(d["subaddress"], 12)
|
|
127
|
+
self.assertEqual(d["word_count"], 16)
|
|
128
|
+
self.assertEqual(d["transmit_receive"], 1)
|
|
129
|
+
self.assertTrue(d["parity_ok"])
|
|
130
|
+
|
|
131
|
+
def test_data_word_anchor(self):
|
|
132
|
+
self.assertEqual(m1553_encode_data_word(0x7FFF), 262139)
|
|
133
|
+
|
|
134
|
+
def test_status_word_anchor(self):
|
|
135
|
+
self.assertEqual(m1553_encode_status_word(5, busy=1), M1553_STATUS)
|
|
136
|
+
d = m1553_decode_status_word(M1553_STATUS)
|
|
137
|
+
self.assertEqual(d["rt_address"], 5)
|
|
138
|
+
self.assertEqual(d["busy"], 1)
|
|
139
|
+
self.assertTrue(d["parity_ok"])
|
|
140
|
+
|
|
141
|
+
def test_message_classification(self):
|
|
142
|
+
self.assertEqual(m1553_classify_message(5, 12, 16, 1), "rt-to-bc")
|
|
143
|
+
self.assertEqual(m1553_classify_message(5, 12, 16, 0), "bc-to-rt")
|
|
144
|
+
self.assertEqual(m1553_classify_message(31, 8, 8, 0), "broadcast")
|
|
145
|
+
self.assertEqual(m1553_classify_message(3, 0, 17, 0), "mode-code")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestM1553Loading(unittest.TestCase):
|
|
149
|
+
|
|
150
|
+
MESSAGES = [("BCRT", 32), ("RTBC", 16), ("BCRT", 16), ("RTRT", 8),
|
|
151
|
+
("RTBC", 8), ("BCRT", 8), ("BCRT", 4), ("RTRT", 4)]
|
|
152
|
+
|
|
153
|
+
def test_wire_words(self):
|
|
154
|
+
self.assertEqual(m1553_wire_words("BCRT", 16), 18)
|
|
155
|
+
self.assertEqual(m1553_wire_words("RTBC", 32), 34)
|
|
156
|
+
self.assertEqual(m1553_wire_words("RTRT", 8), 11)
|
|
157
|
+
self.assertEqual(m1553_message_time_us("RTRT", 8), 264.0)
|
|
158
|
+
|
|
159
|
+
def test_schedule_utilization_anchor(self):
|
|
160
|
+
s = m1553_schedule_utilization(self.MESSAGES, 5000.0)
|
|
161
|
+
# 114 wire words x 24 us = 2736 us of a 5000 us minor frame
|
|
162
|
+
self.assertEqual(s["total_wire_words"], 114)
|
|
163
|
+
self.assertEqual(s["total_us"], 2736.0)
|
|
164
|
+
self.assertAlmostEqual(s["utilization_pct"], 54.72, places=6)
|
|
165
|
+
self.assertEqual(s["budget_us"], 4000.0)
|
|
166
|
+
self.assertEqual(s["headroom_us"], 1264.0)
|
|
167
|
+
self.assertEqual(s["verdict"], "FITS")
|
|
168
|
+
|
|
169
|
+
def test_schedule_over_budget(self):
|
|
170
|
+
heavy = [("BCRT", 32)] * 6 + [("RTRT", 32)] * 6
|
|
171
|
+
s = m1553_schedule_utilization(heavy, 5000.0)
|
|
172
|
+
self.assertEqual(s["verdict"], "OVER")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class TestAfdx(unittest.TestCase):
|
|
176
|
+
|
|
177
|
+
def test_vl_bandwidth_anchors(self):
|
|
178
|
+
self.assertEqual(afdx_vl_bandwidth(4, 1518), 3036000.0)
|
|
179
|
+
self.assertEqual(afdx_vl_bandwidth(128, 1518), 94875.0)
|
|
180
|
+
|
|
181
|
+
def test_link_utilization(self):
|
|
182
|
+
self.assertAlmostEqual(afdx_link_utilization_pct([(4, 1518)] * 30),
|
|
183
|
+
91.08, places=4)
|
|
184
|
+
with self.assertRaises(ValueError):
|
|
185
|
+
afdx_link_utilization_pct([(4, 1518)] * 33)
|
|
186
|
+
|
|
187
|
+
def test_timing_anchors(self):
|
|
188
|
+
self.assertAlmostEqual(afdx_transmission_time_us(1518), 121.44,
|
|
189
|
+
places=6)
|
|
190
|
+
self.assertAlmostEqual(afdx_end_to_end_latency_us(1518, 2, 150.0),
|
|
191
|
+
542.88, places=4)
|
|
192
|
+
self.assertEqual(afdx_jitter_slack(420.0, 500.0), 80.0)
|
|
193
|
+
self.assertEqual(afdx_jitter_slack(620.0, 500.0), -120.0)
|
|
194
|
+
|
|
195
|
+
def test_largest_bag_for_bandwidth(self):
|
|
196
|
+
self.assertEqual(afdx_largest_bag_for_bandwidth(1e6, 1518), 8)
|
|
197
|
+
with self.assertRaises(ValueError):
|
|
198
|
+
afdx_largest_bag_for_bandwidth(13e6, 1518)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestAssessmentBuilder(unittest.TestCase):
|
|
202
|
+
|
|
203
|
+
def test_example_architecture_loads(self):
|
|
204
|
+
item = example_item()
|
|
205
|
+
results = analyze_architecture(item)
|
|
206
|
+
model = build_assessment(item, results)
|
|
207
|
+
self.assertEqual(len(model["arinc429"]), 3)
|
|
208
|
+
# worked example numbers: 1553 54.72%, AFDX 10.81575%
|
|
209
|
+
self.assertAlmostEqual(
|
|
210
|
+
model["m1553"]["schedule"]["utilization_pct"], 54.72, places=4)
|
|
211
|
+
self.assertAlmostEqual(model["afdx"]["utilization_pct"], 10.81575,
|
|
212
|
+
places=4)
|
|
213
|
+
self.assertFalse(model["afdx"]["oversubscribed"])
|
|
214
|
+
# low-speed line is the loaded one (74.88%) -> observation raised
|
|
215
|
+
self.assertTrue(model["observations"])
|
|
216
|
+
self.assertEqual(model["status"], "draft-for-review")
|
|
217
|
+
|
|
218
|
+
def test_conformance_samples_decode(self):
|
|
219
|
+
item = example_item()
|
|
220
|
+
conf = analyze_architecture(item)["conformance"]
|
|
221
|
+
self.assertEqual(conf["a429_word"]["label"], 8)
|
|
222
|
+
self.assertTrue(conf["a429_word"]["parity_ok"])
|
|
223
|
+
self.assertAlmostEqual(conf["a429_bnr"]["engineering_value"], 123.4,
|
|
224
|
+
places=6)
|
|
225
|
+
self.assertEqual(conf["m1553_command"]["rt_address"], 5)
|
|
226
|
+
self.assertTrue(conf["m1553_command"]["parity_ok"])
|
|
227
|
+
self.assertEqual(conf["m1553_status"]["busy"], 1)
|
|
228
|
+
|
|
229
|
+
def test_deliverable_sections(self):
|
|
230
|
+
md = example_assessment_markdown()
|
|
231
|
+
for sec in ["## 1. Scope", "## 2. ARINC 429 bus loading",
|
|
232
|
+
"## 3. MIL-STD-1553 bus loading",
|
|
233
|
+
"## 4. ARINC 664 AFDX network",
|
|
234
|
+
"## 5. Protocol conformance checks",
|
|
235
|
+
"## 6. Findings and observations"]:
|
|
236
|
+
self.assertIn(sec, md)
|
|
237
|
+
self.assertIn("not an approval", md.lower())
|
|
238
|
+
self.assertGreater(len(md), 3000)
|
|
239
|
+
|
|
240
|
+
def test_core_gates_all_pass(self):
|
|
241
|
+
item = example_item()
|
|
242
|
+
model = build_assessment(item, analyze_architecture(item))
|
|
243
|
+
gates = check_assessment(model)
|
|
244
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
245
|
+
|
|
246
|
+
def test_markdown_gates_all_pass(self):
|
|
247
|
+
md = example_assessment_markdown()
|
|
248
|
+
gates = check_assessment_markdown(md)
|
|
249
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
250
|
+
|
|
251
|
+
def test_standalone_no_skills_repo(self):
|
|
252
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
253
|
+
model = build_assessment(example_item(),
|
|
254
|
+
analyze_architecture(example_item()))
|
|
255
|
+
md = render_assessment_markdown(model)
|
|
256
|
+
self.assertGreater(len(md), 3000)
|
|
257
|
+
self.assertTrue(check_assessment(model)["all_pass"])
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
unittest.main()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Role test: Data Bus / Avionics Network 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 filled template deliverable carries the
|
|
9
|
+
loading numbers, conformance checks, DRAFT and not-an-approval
|
|
10
|
+
markers with zero blank fields.
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
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", "data-bus-avionics-engineer")
|
|
24
|
+
TEMPLATE = os.path.join(ROLE_DIR, "templates", "bus-assessment-template.md")
|
|
25
|
+
|
|
26
|
+
EXPECTED_BOUND = [
|
|
27
|
+
"avionics/data-bus/arinc429-protocol",
|
|
28
|
+
"avionics/data-bus/arinc429-bus-loading",
|
|
29
|
+
"avionics/data-bus/arinc664-afdx",
|
|
30
|
+
"avionics/data-bus/mil-std-1553",
|
|
31
|
+
"avionics/data-bus/mil-std-1553-bus-loading",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
EXPECTED_STAGES = [
|
|
35
|
+
"Architecture inventory",
|
|
36
|
+
"ARINC 429 decode review",
|
|
37
|
+
"ARINC 429 bus loading",
|
|
38
|
+
"MIL-STD-1553 word review",
|
|
39
|
+
"MIL-STD-1553 bus loading",
|
|
40
|
+
"AFDX bandwidth check",
|
|
41
|
+
"Conformance gate",
|
|
42
|
+
"Assessment package",
|
|
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
|
+
class TestDataBusRole(unittest.TestCase):
|
|
54
|
+
|
|
55
|
+
def test_bound_skills_resolve_in_aeroskills(self):
|
|
56
|
+
if not HAS_SKILLS:
|
|
57
|
+
self.skipTest("Aero Agent Skills checkout not present "
|
|
58
|
+
"(cross-repo dev check)")
|
|
59
|
+
for leaf in EXPECTED_BOUND:
|
|
60
|
+
sk = os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")
|
|
61
|
+
self.assertTrue(
|
|
62
|
+
os.path.exists(sk),
|
|
63
|
+
f"bound skill not found in aero-agent-skills: {leaf}")
|
|
64
|
+
logic_dir = os.path.join(AEROSKILLS, "skills", leaf, "scripts")
|
|
65
|
+
self.assertTrue(
|
|
66
|
+
any(f.endswith("_logic.py")
|
|
67
|
+
for f in os.listdir(logic_dir)),
|
|
68
|
+
f"bound skill has no logic file: {leaf}")
|
|
69
|
+
|
|
70
|
+
def test_bound_skills_listed_in_frontmatter(self):
|
|
71
|
+
role = read_role_md()
|
|
72
|
+
self.assertIsNotNone(role)
|
|
73
|
+
for leaf in EXPECTED_BOUND:
|
|
74
|
+
self.assertIn(leaf, role, f"missing in ROLE.md skills_bound: {leaf}")
|
|
75
|
+
|
|
76
|
+
def test_workflow_stages_deterministic(self):
|
|
77
|
+
role = read_role_md()
|
|
78
|
+
# every expected stage appears in order (indexes ascending)
|
|
79
|
+
idx = [role.find(stage) for stage in EXPECTED_STAGES]
|
|
80
|
+
self.assertTrue(all(i >= 0 for i in idx), "a stage is missing")
|
|
81
|
+
self.assertEqual(idx, sorted(idx), "stage order not deterministic")
|
|
82
|
+
|
|
83
|
+
def test_deliverable_template_complete(self):
|
|
84
|
+
if not os.path.exists(TEMPLATE):
|
|
85
|
+
self.fail("bus-assessment-template.md missing")
|
|
86
|
+
text = open(TEMPLATE).read()
|
|
87
|
+
# all 7 numbered sections present, zero blanks
|
|
88
|
+
for n in range(1, 8):
|
|
89
|
+
self.assertTrue(
|
|
90
|
+
re.search(rf"^## {n}\. ", text, re.M),
|
|
91
|
+
f"section {n} missing")
|
|
92
|
+
self.assertNotIn("___", text)
|
|
93
|
+
low = text.lower()
|
|
94
|
+
for marker in ["arinc 429", "mil-std-1553", "afdx", "utilization",
|
|
95
|
+
"parity", "draft", "not an approval", "document"]:
|
|
96
|
+
self.assertIn(marker, low, f"template missing marker: {marker}")
|
|
97
|
+
|
|
98
|
+
def test_forbidden_lines_present(self):
|
|
99
|
+
role = read_role_md()
|
|
100
|
+
for phrase in ["issue bus architecture sign-off",
|
|
101
|
+
"network qualification approval",
|
|
102
|
+
"sign_off_required: true"]:
|
|
103
|
+
self.assertIn(phrase, role, f"missing boundary: {phrase}")
|
|
104
|
+
|
|
105
|
+
def test_sources_register_exists(self):
|
|
106
|
+
p = os.path.join(ROLE_DIR, "SOURCES.md")
|
|
107
|
+
self.assertTrue(os.path.exists(p), "SOURCES.md missing for role")
|
|
108
|
+
|
|
109
|
+
def test_core_engine_exists(self):
|
|
110
|
+
"""100% standard: every role ships an executable core + cli."""
|
|
111
|
+
for f in ["core", "cli.py"]:
|
|
112
|
+
self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, f)),
|
|
113
|
+
f"{f} missing - role is not executable")
|
|
114
|
+
|
|
115
|
+
def test_author_ashfordeou(self):
|
|
116
|
+
role = read_role_md()
|
|
117
|
+
self.assertIn("ashfordeOU", role, "author not set")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
unittest.main()
|