Model resolution and lifetime repair
Goal: A YAML I²C device has identical identity, configuration and behavior directly on a controller or behind a mux; loading edited runtime parts does not retain their allocations forever.
Authorization: The user approved the architecture review and requested implementation on 2026-09-18.
Architecture: Resolve manifest overrides before embedded descriptors and Rust fallbacks. Topology validation and tree construction share the leaf resolver. Dynamic descriptors and metadata belong to their runtime owners; static built-in kits remain reusable. Preserve unsupported-type errors and explicit override requirements.
Stack: Rust, existing descriptor schema, Cargo integration tests, native and wasm builds.
Ownership contract
Runtime-authored descriptors must not enter a process-lifetime interning table.
PeripheralKit::metadata() and SimInput::input_channels() borrow their owner;
discovery snapshots clone their channel descriptions when they must outlive it.
Metadata strings and tables use Cow so built-in literals remain borrowed while
runtime YAML owns its allocations. This changes Rust API ownership, not the
serialized manifest/discovery schema. Rust callers that previously copied an
InputChannel must clone it; callers inspecting metadata should borrow fields.
The built-in registry may own a fixed, lazily initialized collection of kits,
but loading or editing a manifest must not grow that collection.
Resolver
- [x] Establish baseline with
cargo test -p labwired-core --test i2c_mux_tca9548a --test part_pack_contract --test i2c_factory_kit_coverage(36 passed). - [x] Add
crates/core/tests/yaml_mux_resolution.rs: every embedded I²C descriptor and manifest-carried sensors must validate and execute behind nested muxes. Check configuration seeding, identity, overrides, wrong transports and address validation. - [x] Run the original eight regression cases before production changes (eight failed); confirm the two additional address/mux override cases fail before fixing them.
- [x] Update
crates/core/src/peripherals/components/i2c_factory.rsto use descriptor discovery instead of the per-device YAML allowlist; share leaf resolution with topology validation. - [x] Ensure
crates/core/src/bus/part_pack.rsuses the same configuration seeding as built-ins and preserves override precedence. Move noise-knob seeding into the shared model method after reproducing its failure behind a mux. - [x] Run existing mux, pack, factory and migrated-device contract tests: 163 tests passed across 20 integration suites, including all 13 new resolver regressions and the unchanged catalog-byte comparison.
Runtime ownership
- [x] Trace static kit/channel contracts and write an allocation-retention regression for repeated distinct runtime packs (all 12 tested constructor cases retained memory before the fix).
- [x] Remove permanent interning for dynamic packs and ensure channels/metadata have an owned lifetime. All core unit/integration test targets compile after adapting Rust consumers.
- [x] Verify drop/reload behavior, edited definitions with the same type, discovery and input routing: the allocation regression passes with zero retained bytes across every measured constructor/bus case, including failed attachment.
Verification and handoff
- [x] Review spec compliance and code quality independently (both inspections passed; runtime verification remains required).
- [x] Run formatting, relevant core suites, and wasm compilation; report exact results and pre-existing warnings below.
- [x] Record remaining product work separately: richer drafting/driver onboarding, fidelity upgrades, provenance-based coverage, and performance work. Do not claim those delivered by a resolver repair.
Remaining product work (not delivered here)
- Expand drafting from basic register skeletons into rules/timers/output bindings with a driver-backed verification loop.
- Upgrade device fidelity where YAML migration currently preserves stubs (for example BMP280 stimulus and BMI270 FIFO/interrupt/time behavior).
- Replace filename-derived YAML/Rust coverage counts with runtime provenance.
- Continue the independent real-time/browser and RISC-V execution workstreams.
This repair neither changes hardware-validation claims nor proves simulator wall-clock performance. It does not update the app's core submodule pin or deploy browser assets.
Verification evidence
cargo check -j 2 -p labwired-core --tests: passed (all core test targets compile).- Targeted runtime verification: 163 tests passed in 20 integration suites. This includes 13 mux-resolution regressions, zero retained allocations across the measured runtime reload/drop cases, and byte-identical catalog JSON against the unchanged committed fixture.
cargo clippy -j 2 -p labwired-core -p labwired-cli --all-targets -- -D warnings: passed after removing one needless borrow in the new allocation test.- After that test-only correction, the allocation test and all 13 resolver regressions were rerun together: 14 passed, zero failures.
cargo fmt --all -- --checkandgit diff --check: passed.cargo check -j 2 -p labwired-wasm --target wasm32-unknown-unknown: passed. Six warnings remain in unchanged virtual Wi-Fi and GDB stub code (unused native-only imports and a dead constant on the wasm target).
Cargo checks/tests above use local command-line overrides
--config 'profile.dev.package.labwired-core.opt-level=0' and
--config 'profile.dev.package.labwired-core.debug=0' to limit build cost on a
busy host. No repository build profiles were changed. These are correctness
checks, not release-mode performance measurements.