diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2485d..8992b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- **LS-M-5 status corrected to `fixed`** (`safety/stpa/loss-scenarios.yaml`, + `meld-core/src/merger.rs`). The multiply-instantiated-module + silent-corruption hazard was already mitigated by detection-and- + reject (`Merger::check_no_duplicate_instantiations`, invoked + unconditionally at the top of the merge pipeline, plus an + independent resolver-side guard), but the loss-scenario entry + still read `status: open`. Clean-room verification confirmed the + corrupt-output path is unreachable. Added the gate-convention + regression test `ls_m_5_multiply_instantiated_module_rejected` + and flipped the entry to `fixed` with a `fix:` block. NOTE: this + documents the *rejection* mitigation; full per-instance index- + space *support* (fusing a module instantiated twice into two + independent function/memory/table spaces) remains a backlog + forward feature and is not required to close the hazard. + ## [0.15.0] - 2026-05-28 ### Added diff --git a/meld-core/src/merger.rs b/meld-core/src/merger.rs index 308e1ef..b677249 100644 --- a/meld-core/src/merger.rs +++ b/meld-core/src/merger.rs @@ -4649,6 +4649,46 @@ mod tests { } } + /// LS-M-5 gate-convention regression: a component that + /// instantiates the same core module twice must be rejected at + /// merge time with `DuplicateModuleInstantiation`, never silently + /// mis-merged. This pins the detection-and-reject mitigation that + /// closes the LS-M-5 silent-corruption hazard. (Named to satisfy + /// the LS-N verification gate's `ls___*` convention; + /// `test_duplicate_module_instantiation_rejected` below predates + /// the convention and is kept as-is.) + #[test] + fn ls_m_5_multiply_instantiated_module_rejected() { + let comp = make_component_with_instances(vec![ + crate::parser::ComponentInstance { + index: 0, + kind: crate::parser::InstanceKind::Instantiate { + module_idx: 2, + args: vec![], + }, + }, + crate::parser::ComponentInstance { + index: 1, + kind: crate::parser::InstanceKind::Instantiate { + module_idx: 2, // same module instantiated again + args: vec![], + }, + }, + ]); + let err = Merger::check_no_duplicate_instantiations(&[comp]) + .expect_err("multiply-instantiated module must be rejected"); + match err { + Error::DuplicateModuleInstantiation { + component_idx, + module_idx, + } => { + assert_eq!(component_idx, 0); + assert_eq!(module_idx, 2); + } + other => panic!("expected DuplicateModuleInstantiation, got {other:?}"), + } + } + #[test] fn test_duplicate_module_instantiation_rejected() { let comp = make_component_with_instances(vec![ diff --git a/safety/stpa/loss-scenarios.yaml b/safety/stpa/loss-scenarios.yaml index 0bf9886..ac9b40b 100644 --- a/safety/stpa/loss-scenarios.yaml +++ b/safety/stpa/loss-scenarios.yaml @@ -2478,8 +2478,33 @@ loss-scenarios: instantiation order. The component model spec allows multiple instantiations of the same module (with different import wiring), but the merger's index-space merging model does not account for this. - status: open + status: fixed priority: critical + fix: > + Mitigated by detection-and-reject, the same pattern used for + LS-P-11 (DuplicateModuleExport). `Merger::check_no_duplicate_instantiations` + (`meld-core/src/merger.rs`) walks every component's instances + and returns `Error::DuplicateModuleInstantiation { component_idx, + module_idx }` the moment a `module_idx` reappears in the + instantiate set; it is invoked unconditionally at the top of the + merge pipeline (merger.rs ~909) before any index-space merging + runs, so the corrupt-output path is unreachable. The resolver + has an independent guard at resolver.rs ~2222 for the + instance-graph path. The silent-corruption hazard [H-1, H-2, H-3] + is therefore eliminated: a multiply-instantiated module produces + a loud build-time error, never a mis-merged module. Confirmed by + `meld-core::merger::tests::ls_m_5_multiply_instantiated_module_rejected` + (gate-convention regression test), plus the pre-existing + `test_duplicate_module_instantiation_rejected`, + `test_single_instantiation_accepted`, `test_no_instances_accepted` + (merger) and `test_multiply_instantiated_module_rejected` + (resolver). NOTE: this *rejects* multiply-instantiated modules + rather than *supporting* them. Full per-instance index-space + support (so a shared utility module instantiated twice fuses + correctly with two independent function/memory/table spaces) + remains a forward feature on the backlog — it is a large merger + refactor and is NOT required to close this safety hazard, since + rejection already prevents the unsafe output. - id: LS-M-6 title: component-provenance section mis-attributes fused functions