Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/verification-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
| SR-28 | Attestation configuration completeness | partial | `attestation::tests::test_sr28_config_completeness` | — |
| SR-29 | Attestation serialization round-trip | partial | `attestation::tests::test_sr29_attestation_round_trip`<br>`attestation::tests::test_to_bytes_returns_result` | — |
| SR-30 | Attestation output hash integrity | partial | `attestation::tests::test_sr30_output_hash_integrity` | — |
| SR-31 | Multiply-instantiated module detection | verified | `merger::tests::ls_m_5_multiply_instantiated_module_rejected` | — |
| SR-31 | Multiply-instantiated module detection (SharedMemory/Auto reject; MultiMemory supported via SR-55) | verified | `multiply_instantiated_runtime::shared_memory_and_auto_reject_multiply_instantiated` (live fuse path)<br>`merger::tests::ls_m_5_multiply_instantiated_module_rejected` (isolated reject) | #364 |
| SR-32 | Stackful P3 lifting mode emission | implemented | `p3_async::tests::stackful_intrinsic_signatures_pinned`<br>`p3_async::tests::stackful_lift_is_async_without_callback`<br>`adapter::fact::tests::sr32_has_callback_export_detects_companion`<br>`adapter::fact::tests::sr32_stackful_emitter_handles_no_shim_with_default_results`<br>`adapter::fact::tests::sr32_stackful_emitter_shape_pins_call_drop_globalget` | — |
| SR-33 | Cross-component `stream<T>` fusion at merge time | verified | `p3_bridge_runtime::ls_st_1_round_trip_local_stream_never_crosses_host`<br>`p3_bridge_runtime::cross_memory_chain_preserves_data_across_distinct_memories`<br>`p3_bridge_runtime::backpressure_partial_write_then_drain_then_resume`<br>`p3_bridge_runtime::ls_st_1_eof_only_after_writer_drop_and_drain`<br>`p3_bridge_runtime::slot_exhaustion_falls_back_to_host_stream` | — |
| SR-34 | Static `stream<T>` validation | implemented | `meld-core/src/p3_stream.rs::tests::ls_r_11_stream_typed_import_with_mismatched_roles_raises`<br>`meld-core/src/p3_stream.rs::tests::ls_r_11_per_edge_lift_export_mismatch_raises`<br>`meld-core/src/p3_stream.rs::tests::ls_r_11_sync_only_connection_with_unrelated_streams_does_not_raise` | — |
Expand Down
53 changes: 32 additions & 21 deletions meld-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,27 +410,6 @@ impl Fuser {
return Err(Error::NoComponents);
}

// RFC-46 Q1 (ADR-7 path-H inc 3): normalize multiply-instantiated core
// modules into distinct module identities *before* resolve/merge, so each
// instantiation is allocated independent functions/memory/tables/globals
// via the merger's per-module machinery (already proven correct for N
// distinct modules) — no shared mutable state (H-1), no (component,
// module)-keyed map overwrites. After this pass no module is instantiated
// more than once, so the `DuplicateModuleInstantiation` reject in the
// resolver/merger becomes an unreachable backstop. Idempotent, so a
// repeated fuse (after another `add_component`) is safe.
let mut duplicated_modules = 0usize;
for component in &mut self.components {
duplicated_modules +=
crate::core_instance_topology::expand_multiply_instantiated_modules(component);
}
if duplicated_modules > 0 {
log::info!(
"core-instance topology: duplicated {duplicated_modules} multiply-instantiated \
core module(s) into distinct identities (RFC-46 Q1)"
);
}

// Restore the originally-requested strategy before resolving, so a
// repeated fuse (e.g. after another `add_component`) re-derives the
// resolution from the CURRENT component set instead of reusing a
Expand All @@ -440,6 +419,38 @@ impl Fuser {
.get_or_insert((self.config.memory_strategy, self.config.address_rebasing));
self.config.memory_strategy = requested_strategy;
self.config.address_rebasing = requested_rebasing;

// RFC-46 Q1 (ADR-7 path-H inc 3): normalize multiply-instantiated core
// modules into distinct module identities *before* resolve/merge, so each
// instantiation is allocated independent functions/memory/tables/globals
// via the merger's per-module machinery (already proven correct for N
// distinct modules) — no shared mutable state (H-1), no (component,
// module)-keyed map overwrites. After this pass no module is instantiated
// more than once, so the `DuplicateModuleInstantiation` reject becomes an
// unreachable backstop. Idempotent, so a repeated fuse is safe.
//
// #364: support is gated to **explicit `MultiMemory`** — the only case the
// differential execution oracle (`multiply_instantiated_runtime`) proves
// keeps per-instance state independent (each instance gets its OWN memory).
// Under `SharedMemory` the instances would share one linear memory and the
// independence is *unverified* (SR-55 note); under `Auto` the strategy is
// resolved implicitly, which functional-safety builds must not rely on
// (ADR-4). In both cases we skip normalization and let the
// `DuplicateModuleInstantiation` reject fire (SR-31) rather than ship an
// unverified transform.
let mut duplicated_modules = 0usize;
if requested_strategy == MemoryStrategy::MultiMemory {
for component in &mut self.components {
duplicated_modules +=
crate::core_instance_topology::expand_multiply_instantiated_modules(component);
}
if duplicated_modules > 0 {
log::info!(
"core-instance topology: duplicated {duplicated_modules} multiply-instantiated \
core module(s) into distinct identities (RFC-46 Q1, MultiMemory)"
);
}
}
if self.config.memory_strategy == MemoryStrategy::Auto {
self.resolve_auto_memory_strategy();
let result = if self.config.memory_strategy == MemoryStrategy::SharedMemory {
Expand Down
22 changes: 22 additions & 0 deletions meld-core/tests/multiply_instantiated_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,25 @@ fn two_instances_keep_independent_memory_and_data_segments() {
assert_eq!(bump1.call(&mut store, ()).unwrap(), 8, "bump1 #3");
assert_eq!(bump2.call(&mut store, ()).unwrap(), 7, "bump2 #2");
}

/// #364: multiply-instantiated support is gated to the execution-verified
/// MultiMemory case. Under SharedMemory (independence unverified) and Auto
/// (implicit — ADR-4), meld must REJECT rather than ship an unverified transform.
#[test]
fn shared_memory_and_auto_reject_multiply_instantiated() {
for ms in [MemoryStrategy::SharedMemory, MemoryStrategy::Auto] {
let component = multiply_instantiated_component();
let mut cfg = base_config();
cfg.memory_strategy = ms;
let mut fuser = Fuser::new(cfg);
fuser.add_component_named(&component, Some("dup")).unwrap();
let err = fuser.fuse_with_stats().err().unwrap_or_else(|| {
panic!("{ms:?}: multiply-instantiated must be rejected (unverified), but it fused")
});
let msg = format!("{err}");
assert!(
msg.contains("more than once") || msg.contains("multiply-instantiated"),
"{ms:?}: expected DuplicateModuleInstantiation, got: {msg}"
);
}
}
26 changes: 22 additions & 4 deletions safety/requirements/safety-requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -858,14 +858,27 @@ artifacts:
- type: mitigates
target: LS-M-5
fields:
implementation: meld-core/src/merger.rs
implementation:
- meld-core/src/merger.rs
- meld-core/src/lib.rs
verification-method: test
verification-description: >
Test with component that instantiates the same core module
twice; verify merger returns an error
Verified on the LIVE fuse path (#364): `multiply_instantiated_runtime::
shared_memory_and_auto_reject_multiply_instantiated` fuses a component
instantiating one core module twice under SharedMemory and Auto and
asserts a DuplicateModuleInstantiation rejection — exercising
`fuse_with_stats`, not the merger reject in isolation. The unit-level
`merger::tests::ls_m_5_multiply_instantiated_module_rejected` still checks
the reject function directly. SCOPE (#364): the reject is the live
behavior only for SharedMemory/Auto, where per-instance independence is
unverified; under explicit MultiMemory the modules are SUPPORTED (SR-55,
execution-verified) — lib.rs gates the core-instance normalization to
MultiMemory so this reject fires elsewhere rather than shipping an
unverified transform.
references:
- "BA RFC #46 discussion: cfallin on multiply-instantiated modules"
- "safety/stpa/rfc46-comparative-analysis.md section 4 Q1"
- "https://github.com/pulseengine/meld/issues/364"

# ==========================================================================
# Roadmap requirements v0.8 → v0.11 (SR-32..SR-36)
Expand Down Expand Up @@ -2045,7 +2058,12 @@ artifacts:
the merger's index-remapping machinery already proven correct for N
distinct modules. When active, this SUPERSEDES the SR-31 reject for the
supported cases; SR-31's fail-fast remains for any residual meld cannot
duplicate soundly.
duplicate soundly. SCOPE (#364): support is gated to explicit
**MultiMemory** — the only strategy under which the execution oracle proves
per-instance independence (each instance gets its OWN linear memory). Under
SharedMemory (shared linear memory → independence unverified) and Auto
(implicit strategy, ADR-4) the normalization is NOT applied and the SR-31
reject fires instead.
status: verified
tags: [rfc46, adr7, merger, topology]
links:
Expand Down
11 changes: 10 additions & 1 deletion safety/requirements/sw-verifications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,16 @@ artifacts:
type: sw-verification
title: "Verification of SR-31: Multiply-instantiated module detection"
description: >
Verifies SR-31 via: tests `merger::tests::ls_m_5_multiply_instantiated_module_rejected`.
Verifies SR-31 on the LIVE fuse path via
`multiply_instantiated_runtime::shared_memory_and_auto_reject_multiply_instantiated`
— a component instantiating one core module 2× is REJECTED
(DuplicateModuleInstantiation) under SharedMemory and Auto, exercising
`fuse_with_stats` (not the merger reject in isolation). Complemented by the
unit-level `merger::tests::ls_m_5_multiply_instantiated_module_rejected`.
NOTE (#364): under explicit MultiMemory the reject is intentionally NOT
the behavior — multiply-instantiated modules are SUPPORTED there (SR-55,
execution-verified independent state); the SR-31 reject scope is
SharedMemory/Auto (unverified independence), gated in lib.rs.
status: verified
fields:
method: automated-test
Expand Down
Loading