A scaffolded project builds its guest and its IDL from two separate crates with two separate lockfiles, both pinned to the same floating ref:
# methods/guest/Cargo.toml (init.rs:719)
spel-framework = { git = "{spel_git}", branch = "main" }
# examples/Cargo.toml (init.rs:810, :812)
spel-framework = { git = "{spel_git}", branch = "main" }
spel = { git = "{spel_git}", branch = "main" }
spel init emits the same ref for both, so they agree at scaffold time. But because they are independent crates with independent lockfiles on a moving branch, they can resolve to different commits — locked at different times, one updated by cargo update and not the other, or one vendored.
Why it matters
make idl runs cargo run --bin generate_idl in examples/, so the committed IDL is produced by that crate's framework version, while the deployed guest is built by methods/guest's. When the two drift, the IDL describes a different program than the one that ships — silently. No error, no warning.
Observed while reviewing #257: with examples/ resolved to an older spel-framework than the guest, make idl emitted an IDL missing the entire extension surface — a contributed instruction absent, and an injected account missing from a gated instruction's account list. The guest had both. A client generated from that IDL would omit the instruction entirely and build the gated call with the wrong account list, which fails on chain.
The extension mechanism makes the consequence severe, but the shape is general: any framework change affecting IDL emission can diverge this way.
Suggested fixes
Cheapest first:
- Make the IDL producer assert its framework version matches the guest's, and fail loudly when it does not. This catches every cause of drift, including ones not listed here.
- Pin by commit rather than branch at scaffold time (resolve the branch to a rev in
spel init), so both crates are pinned to the same immutable commit.
- At minimum, document that the two pins must stay in lockstep and that editing one alone silently corrupts the IDL.
Related: the same silent-wrong-artifact shape as #260 (wallet config schema written under a key serde ignores).
A scaffolded project builds its guest and its IDL from two separate crates with two separate lockfiles, both pinned to the same floating ref:
spel initemits the same ref for both, so they agree at scaffold time. But because they are independent crates with independent lockfiles on a moving branch, they can resolve to different commits — locked at different times, one updated bycargo updateand not the other, or one vendored.Why it matters
make idlrunscargo run --bin generate_idlinexamples/, so the committed IDL is produced by that crate's framework version, while the deployed guest is built bymethods/guest's. When the two drift, the IDL describes a different program than the one that ships — silently. No error, no warning.Observed while reviewing #257: with
examples/resolved to an olderspel-frameworkthan the guest,make idlemitted an IDL missing the entire extension surface — a contributed instruction absent, and an injected account missing from a gated instruction's account list. The guest had both. A client generated from that IDL would omit the instruction entirely and build the gated call with the wrong account list, which fails on chain.The extension mechanism makes the consequence severe, but the shape is general: any framework change affecting IDL emission can diverge this way.
Suggested fixes
Cheapest first:
spel init), so both crates are pinned to the same immutable commit.Related: the same silent-wrong-artifact shape as #260 (wallet config schema written under a key serde ignores).