@@ -166,30 +166,38 @@ fn bundle_runtime() {
166166 let runtime_resource = build_runtime ( ) ;
167167 let runtime_bytes = fs:: read ( & runtime_resource) . expect ( "Failed to read runtime binary" ) ;
168168
169- // Compute SHA256 hash
169+ assert ! (
170+ !runtime_bytes. is_empty( ) ,
171+ "analysis runtime binary is empty: {runtime_resource:?}"
172+ ) ;
173+
174+ let out_dir = env:: var_os ( "OUT_DIR" ) . unwrap ( ) ;
175+
176+ // Snapshot the runtime binary into this crate's OUT_DIR and embed THAT copy.
177+ //
178+ // The hash below is computed from `runtime_bytes`, while the embedded bytes
179+ // come from `include_bytes!` evaluated when this crate is compiled — two
180+ // reads at different times. The build path of `runtime_resource` lives in a
181+ // shared target directory that other cargo invocations (clippy, the napi
182+ // typedef pass, repeated `just build`/`just test` runs) can rebuild, and the
183+ // guest binary is not bit-for-bit reproducible. If that file changed between
184+ // hashing and the `include_bytes!` compile, the embedded bytes would no
185+ // longer match the stored hash and the runtime integrity check would fail.
186+ //
187+ // Writing a private snapshot into OUT_DIR — which nothing else ever touches —
188+ // and pointing `include_bytes!` at it guarantees the embedded bytes are
189+ // exactly the bytes we hashed, as a single atomic pair.
190+ let embedded_path = Path :: new ( & out_dir) . join ( "analysis-runtime.bin" ) ;
191+ fs:: write ( & embedded_path, & runtime_bytes) . expect ( "Failed to write runtime snapshot" ) ;
192+
193+ // Compute SHA256 hash of the same in-memory buffer we just embedded.
170194 use sha2:: { Digest , Sha256 } ;
171195 let mut hasher = Sha256 :: new ( ) ;
172196 hasher. update ( & runtime_bytes) ;
173197 let hash = hasher. finalize ( ) ;
174198 let hash_hex = hex:: encode ( hash) ;
175199
176- let out_dir = env:: var_os ( "OUT_DIR" ) . unwrap ( ) ;
177- let out_dir = Path :: new ( & out_dir) ;
178-
179- // Stage the runtime binary into OUT_DIR and embed that copy rather than the
180- // live build-target binary. `include_bytes!` is expanded by rustc when the
181- // host crate is compiled, which happens after this build script runs. If we
182- // pointed `include_bytes!` at the mutable target-dir binary, a later rebuild
183- // of the runtime (e.g. by clippy or a subsequent `cargo build` that does not
184- // re-trigger this script) could change those bytes while the recorded
185- // ANALYSIS_RUNTIME_SHA256 stays stale, producing a `.node` whose embedded
186- // bytes and integrity hash disagree. The OUT_DIR copy is written here, in
187- // lock-step with the hash, and is only ever touched by this build script, so
188- // the embedded bytes and hash can never desync.
189- let embedded_path = out_dir. join ( "analysis-runtime.bin" ) ;
190- fs:: write ( & embedded_path, & runtime_bytes) . expect ( "Failed to stage runtime binary in OUT_DIR" ) ;
191-
192- let dest_path = out_dir. join ( "host_resource.rs" ) ;
200+ let dest_path = Path :: new ( & out_dir) . join ( "host_resource.rs" ) ;
193201 let contents = format ! (
194202 r#"pub(crate) static ANALYSIS_RUNTIME: &[u8] = include_bytes!({embedded_path:?});
195203pub(crate) const ANALYSIS_RUNTIME_SHA256: &str = "{hash_hex}";"#
0 commit comments