feat(firmware): validate configuration and migrate sensor-driven flight APIs

Validate configuration through its owning schemas and keep board
declarations separate from shared F722 resource ownership. Bound USB
transitions, isolate transport failures and retain restart-only activation.

Migrate navigation and flight consumers with the sensor and schema
contracts. Require explicit installation data and synchronous output
authority; board motor outputs remain inhibited.

Validation: assembled-prefix host tests, strict Clippy, platform/USB tests
and both ARM release builds. Final-source coverage and scoped Miri/Kani
evidence are retained separately from hardware qualification.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent fb1b8af2d1
commit f9a61a086f
211 files changed
+45657 -12740

No files matched your search

@@ -290,3 +290,91 @@ fn recursive_tree_rejects_duplicate_display_keys() {
Err(issue) if issue.rule() == ValidationRule::Inconsistent
));
}
#[derive(Config)]
struct FloatRules {
#[config(id = 1, key = "finite", unit = "", finite)]
finite: f32,
#[config(id = 2, key = "nonzero", unit = "", nonzero)]
nonzero: f64,
#[config(id = 3, key = "lower", unit = "", range(min = 0.0))]
lower: f32,
#[config(id = 4, key = "upper", unit = "", range(max = 1.0))]
upper: f64,
#[config(id = 5, key = "free", unit = "")]
free: f64,
#[config(id = 6, key = "nonzero32", unit = "", nonzero)]
nonzero32: f32,
}
#[test]
fn scalar_rules_preserve_local_repair_and_float_domains() {
let mut values = FloatRules {
finite: f32::NAN,
nonzero: 0.0,
lower: f32::NAN,
upper: f64::NAN,
free: f64::NAN,
nonzero32: 0.0,
};
// Every other field is invalid. A local repair must still succeed.
values
.set_field(FieldId::new(1), ConfigValue::F32(1.0))
.unwrap();
for (id, value, rule) in [
(
1,
ConfigValue::F32(f32::INFINITY),
ValidationRule::NonFinite,
),
(2, ConfigValue::F64(0.0), ValidationRule::Zero),
(2, ConfigValue::F64(-0.0), ValidationRule::Zero),
(3, ConfigValue::F32(f32::NAN), ValidationRule::Unordered),
(4, ConfigValue::F64(f64::NAN), ValidationRule::Unordered),
(6, ConfigValue::F32(0.0), ValidationRule::Zero),
(6, ConfigValue::F32(-0.0), ValidationRule::Zero),
] {
let id = FieldId::new(id);
let before = values.field_value(id);
assert_eq!(
values.set_field(id, value),
Err(FieldWriteError::Violation(ConfigViolation::field(id, rule)))
);
assert_eq!(values.field_value(id), before);
}
// Neither nonzero nor a one-sided range implies finite.
for (id, value) in [
(2, ConfigValue::F64(f64::from_bits(0xfff0_0000_0000_0042))),
(3, ConfigValue::F32(f32::INFINITY)),
(4, ConfigValue::F64(f64::NEG_INFINITY)),
(5, ConfigValue::F64(f64::from_bits(0x7ff8_0000_0000_1234))),
(6, ConfigValue::F32(f32::INFINITY)),
] {
values.set_field(FieldId::new(id), value).unwrap();
assert_eq!(values.field_value(FieldId::new(id)), Some(value));
}
values.validate().unwrap();
}
#[test]
fn rule_metadata_changes_fingerprints() {
let descriptor = FloatRules::DESCRIPTORS[0];
assert!(descriptor.finite);
assert!(!descriptor.nonzero);
assert!(FloatRules::DESCRIPTORS[1].nonzero);
let original = kaptan_config::validation::__schema_id(&[descriptor]);
assert_ne!(
original,
kaptan_config::validation::__schema_id(&[kaptan_config::FieldDescriptor {
finite: false,
..descriptor
}])
);
assert_ne!(
original,
kaptan_config::validation::__schema_id(&[kaptan_config::FieldDescriptor {
nonzero: true,
..descriptor
}])
);
}