{
  "format": "odb-inspection/1",
  "executed": false,
  "source": {
    "path": "components/ultrasound/convex-open-image/0.1.1/src/lib.rs",
    "sha256": "499ac1bc5020b389c0185711335204949dceb09726656ee9e8eecda853565aa9",
    "build": "node scripts/build-open-image-wasm.ts --version 0.1.1; npm run components:build",
    "files": [
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/Cargo.toml",
        "sha256": "42db07af0a9058eb117124a0b32de0c013885ce5d349795ac078badb0e0dab2c",
        "text": "[package]\nname = \"probe-open-image\"\nversion = \"0.1.1\"\nedition = \"2024\"\nlicense = \"AGPL-3.0-or-later\"\npublish = false\n[lib]\ncrate-type = [\"rlib\", \"cdylib\"]\n[dependencies]\nodb-convex-decoder = { path = \"../../convex-decoder/1.0.0\", default-features = false }\nserde_json = \"1\"\nsha2 = \"0.10\"\n[profile.release]\nopt-level = 3\nlto = true\nstrip = true\npanic = \"abort\"\n\n[target.'cfg(unix)'.dependencies]\nlibc = \"0.2\"\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/Cargo.lock",
        "sha256": "bb7e510b6c3697bb53ca6d5db2bf1fbce30f4f064c1d9ed1e5d5e4d59d13dc7b",
        "text": "# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\nversion = 4\n\n[[package]]\nname = \"block-buffer\"\nversion = \"0.10.4\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\"\ndependencies = [\n \"generic-array\",\n]\n\n[[package]]\nname = \"cfg-if\"\nversion = \"1.0.5\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600\"\n\n[[package]]\nname = \"cpufeatures\"\nversion = \"0.2.17\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\"\ndependencies = [\n \"libc\",\n]\n\n[[package]]\nname = \"crypto-common\"\nversion = \"0.1.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\"\ndependencies = [\n \"generic-array\",\n \"typenum\",\n]\n\n[[package]]\nname = \"digest\"\nversion = \"0.10.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\"\ndependencies = [\n \"block-buffer\",\n \"crypto-common\",\n]\n\n[[package]]\nname = \"generic-array\"\nversion = \"0.14.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\"\ndependencies = [\n \"typenum\",\n \"version_check\",\n]\n\n[[package]]\nname = \"itoa\"\nversion = \"1.0.18\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682\"\n\n[[package]]\nname = \"libc\"\nversion = \"0.2.191\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a\"\n\n[[package]]\nname = \"memchr\"\nversion = \"2.8.3\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98\"\n\n[[package]]\nname = \"odb-convex-decoder\"\nversion = \"1.0.0\"\ndependencies = [\n \"serde_json\",\n \"sha2\",\n]\n\n[[package]]\nname = \"probe-open-image\"\nversion = \"0.1.1\"\ndependencies = [\n \"libc\",\n \"odb-convex-decoder\",\n \"serde_json\",\n \"sha2\",\n]\n\n[[package]]\nname = \"proc-macro2\"\nversion = \"1.0.107\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9\"\ndependencies = [\n \"unicode-ident\",\n]\n\n[[package]]\nname = \"quote\"\nversion = \"1.0.47\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001\"\ndependencies = [\n \"proc-macro2\",\n]\n\n[[package]]\nname = \"serde\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba\"\ndependencies = [\n \"serde_core\",\n]\n\n[[package]]\nname = \"serde_core\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48\"\ndependencies = [\n \"serde_derive\",\n]\n\n[[package]]\nname = \"serde_derive\"\nversion = \"1.0.229\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"syn\",\n]\n\n[[package]]\nname = \"serde_json\"\nversion = \"1.0.154\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6\"\ndependencies = [\n \"itoa\",\n \"memchr\",\n \"serde\",\n \"serde_core\",\n \"zmij\",\n]\n\n[[package]]\nname = \"sha2\"\nversion = \"0.10.9\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\"\ndependencies = [\n \"cfg-if\",\n \"cpufeatures\",\n \"digest\",\n]\n\n[[package]]\nname = \"syn\"\nversion = \"3.0.9\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"unicode-ident\",\n]\n\n[[package]]\nname = \"typenum\"\nversion = \"1.20.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20\"\n\n[[package]]\nname = \"unicode-ident\"\nversion = \"1.0.27\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563\"\n\n[[package]]\nname = \"version_check\"\nversion = \"0.9.5\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\"\n\n[[package]]\nname = \"zmij\"\nversion = \"1.0.23\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b\"\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/README.md",
        "sha256": "a93fc3de29df0c4a7d128ea40c582148e7617f9fb6b3e7bab1d55b14186fdbe4",
        "text": "# Original UC5-2S reconstruction profile\n\nRelease0.1.1 adds a verified native recording host. The image-profile source\nand shared Rust processing library are unchanged from0.1.0; this is a host\nvalidation change, not a new acquisition or calibration result. Release0.1.0\nremains immutable. Use the0.1.1 native host instead of the earlier example,\nwhich does not verify receive-journal metadata and raw-file hashes.\n\nAn experimental, resource-free image profile implemented in Rust under\nAGPL-3.0-or-later. Native Linux and browser WASM use the same `OpenSession`\nand the shared `odb-convex-decoder` processing library. All processing inputs\nare byte slices in memory. Hosts provide transport, storage and display.\n\nThis component reconstructs already acquired DATA streams. It does not\ninitialize or activate a probe. A complete autonomous acquisition profile\nremains separate work; this release is not a plug-and-scan application.\n\n## Generated profile\n\nThe symmetric 16-tap filter is the binomial polynomial\n`(1 + z^-1)^15 / 2^15`, generated using integer recurrence. Display defaults\nare -8 dB gain, 48 dB dynamic range, flat gain versus sample index,\ntemporal persistence 0.25 and generated identity grayscale. Adaptive compound\nmixing and spatial enhancement are disabled. These are original display\nchoices, not calibrated tissue or transducer parameters.\n\nRendering uses a chosen normalized 60-degree sector with unit radius and\n512 × 512 opaque RGBA pixels. This is not a measurement of the probe's angle\nor depth. The decoder accepts the observed mode0/P896/N32 DATA format,\n1800-word lines and four u16 lanes; no physical sampling rate is inferred.\nThere is no claim of equivalence to another image profile or metric calibration.\n\n## Build and native use\n\nFrom the repository root, with Rust 1.99.0 and its `wasm32-wasip1` target installed:\n\n```sh\nnode scripts/build-open-image-wasm.ts --version 0.1.1\nnode scripts/build-open-image-wasm.ts --version 0.1.1 --check\nnpm run components:build\ncargo test --locked --manifest-path components/ultrasound/convex-open-image/0.1.1/Cargo.toml\n```\n\nThe optional `replay_open` native binary accepts a recorded capture directory\nand a new output directory. It accepts the observed `probe-receive-v1` and\nsealed `probe-receive-memory-v1` convex DATA86/16,384-byte journal profiles.\nIt verifies the declared profile, terminal state, reservation/seal, ordinals,\nfixed raw filenames, per-read status/code/size/SHA256 and byte total. Empty\nand partial timeout prefixes are preserved exactly. Unknown, failed, in-flight\nor discontinuous receive outcomes are rejected rather than concatenated.\nAn explicit final per-read cancellation before I/O, with no result fields,\nterminates the verified prefix without inventing a gap or consuming an orphan\nfile. It is distinct from cancellation after a transfer with an unknown result.\nTheir original archives are not modified; use a tool with an explicit gap\npolicy to investigate such evidence.\n\nBefore creating output or evaluating images, the host snapshots all validated\nDATA into owned memory. Later source-file changes cannot alter those bytes.\nThe journal is limited to16MiB, at most10,000reads, and163,840,000DATA bytes;\nthese are host bounds, not sampling or image-quality claims. Metadata and DSP\nmemory are additional. Files must be regular; symlinks/devices are rejected.\nAfter validation it writes PNGs and hashes, `profile.json`, and `replay.json`\nwith source hashes/counts and processing/completed/failed state. A failed\nevaluation can retain partial outputs but does not gain a completed marker.\nThe output directory must be new. No configuration, bootstrap response or\ndevice access is needed; archived commands are never executed.\n\n```sh\ncargo build --release --locked --manifest-path components/ultrasound/convex-open-image/0.1.1/Cargo.toml --bin replay_open\ncomponents/ultrasound/convex-open-image/0.1.1/target/release/replay_open CAPTURE NEW_OUTPUT\n```\n\nThe snapshot verifies declared host DATA only. It does not authenticate a\ndevice, establish a complete or loss-free physical capture, or calibrate an\nimage. A prepared empty journal can yield zero images without implying an\nacquisition. Browser `.bin`/`.odb-capture`/`.odb-stream` loading remains a\nseparate host path; its USB capabilities and acquisition dependencies do not\nchange in this release.\nBoth WASM builds pin Rust 1.99.0 and normalize repository and Cargo-cache paths.\nThe locked Rust compiler adapter also normalizes Cargo's metadata for the two\nlocal crates, retaining their names, versions, targets and enabled features.\nOther dependency identities and all numerical compiler options are preserved.\nIts source hash selects a separate build cache so an adapter change cannot reuse\nan older compilation. This avoids host-dependent function ordering in the WASM.\nCI tests the shared library and original profile and rebuilds both artifacts.\nBinary identity is\nchecked with the same locked dependencies and Rust toolchain; it is not a claim\nof identical output across different compiler versions.\n\nNative library callers use `OpenSession::new(Display::default())`,\n`push(&[u8])`, `advance(&[u8])` and `finish()`. `Display` accepts bounded finite\ngain, dynamic range, sample-index gain slope and persistence settings.\n\n## Browser use\n\n```ts\nconst decoder = await createProbeDecoder(wasmBytes, undefined, {\n  purpose: \"reconstruction\",\n  continuous: true,\n});\nconst frames = decoder.pushRgba(recordedDataBytes);\n// Feed remaining bytes in order, then call decoder.finishRgba().\n```\n\nInitialization requires explicit reconstruction purpose. Acquisition/default\ninitialization and hardware command binding fail. ABI operations 2/5 return\nPNG batches, 9/10 RGBA batches, 11 encodes an RGBA snapshot and 12 advances\nprocessing histories without rasterizing. No resource file, mounted directory,\nnetwork access or USB import is required. Maximum WASM memory is 256 MiB.\nThe `ultrasound/convex-replay` application selects reconstruction explicitly and\nreads local `.bin`, `.odb-capture` and `.odb-stream` files. The recording source\ndoes not expose USB or execute archived journal commands. Continuous JSONL\nrecordings are read incrementally; legacy JSON containers are limited to96MiB.\nShared processing sources are pinned in `shared-sources.json` and checked before\nbuilding the WASM. The registry also pins this lock file with the component.\n\n## Validation scope\n\nTwo preserved phantom acquisitions provide 45 complete reference frames.\nReplay counts remain 9 and 36; native/WASM RGB differences are bounded by\n2 intensity levels out of 255. Image appearance intentionally differs from\nthe earlier reference profile. Those measurements validate reconstruction\ncontinuity and cross-target consistency, not acoustic safety, calibration or\ndiagnostic performance. No new physical capture was required.\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/src/bin/replay_open.rs",
        "sha256": "062154485111286a755e62913f25b469f84bbdd7248f7f7f4026b3d420830aa4",
        "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\nmod capture;\nuse probe_open_image::{Display, HEIGHT, OpenSession, WIDTH, manifest};\nuse serde_json::json;\nuse sha2::{Digest, Sha256};\nuse std::{\n    env,\n    fs::{self, OpenOptions},\n    io::Write,\n    path::Path,\n};\nfn write_new(path: &Path, bytes: &[u8]) -> Result<(), Box<dyn std::error::Error>> {\n    let mut file = OpenOptions::new().write(true).create_new(true).open(path)?;\n    file.write_all(bytes)?;\n    file.sync_all()?;\n    Ok(())\n}\nfn run(input: &Path, out: &Path) -> Result<(), Box<dyn std::error::Error>> {\n    if out.exists() {\n        return Err(\"Output directory must be new\".into());\n    }\n    // Snapshot and verify ALL declared DATA before creating a result or feeding the engine.\n    let captured = capture::snapshot(input)?;\n    let cfg = Display::default();\n    let mut session = OpenSession::new(cfg)?;\n    fs::create_dir(out)?;\n    let mut audit = json!({\"schema\":\"odb-native-replay/1\",\"state\":\"processing\",\"input\":captured.audit,\n        \"images\":0,\"commands_executed\":0,\"calibrated\":false,\"error\":null});\n    write_new(\n        &out.join(\"replay.json\"),\n        &serde_json::to_vec_pretty(&audit)?,\n    )?;\n    let mut rows = Vec::new();\n    let result = (|| -> Result<(), Box<dyn std::error::Error>> {\n        let mut save =\n            |frames: Vec<odb_convex_decoder::RgbaFrame>| -> Result<(), Box<dyn std::error::Error>> {\n                for f in frames {\n                    let name = format!(\"{:06}.png\", f.sequence);\n                    let png = odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, &f.rgba)?;\n                    write_new(&out.join(&name), &png)?;\n                    rows.push(json!({\"sequence\":f.sequence,\"file\":name,\"sha256\":format!(\"{:x}\",Sha256::digest(&png)),\"rgba_sha256\":format!(\"{:x}\",Sha256::digest(&f.rgba))}));\n                }\n                Ok(())\n            };\n        for bytes in &captured.reads {\n            save(session.push(bytes)?)?;\n        }\n        save(session.finish()?)?;\n        write_new(&out.join(\"images.json\"), &serde_json::to_vec_pretty(&rows)?)?;\n        write_new(\n            &out.join(\"profile.json\"),\n            &serde_json::to_vec_pretty(&manifest(cfg)?)?,\n        )?;\n        Ok(())\n    })();\n    audit[\"images\"] = json!(rows.len());\n    audit[\"state\"] = json!(if result.is_ok() {\n        \"completed\"\n    } else {\n        \"failed\"\n    });\n    if let Err(error) = &result {\n        audit[\"error\"] = json!(error.to_string());\n    }\n    // A crash leaves processing/invalid JSON rather than promoting a prefix to completion.\n    fs::write(out.join(\"replay.json\"), serde_json::to_vec_pretty(&audit)?)?;\n    result?;\n    println!(\n        \"{} images; verified declared DATA snapshot; no configuration or USB\",\n        rows.len()\n    );\n    Ok(())\n}\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n    let a: Vec<_> = env::args().skip(1).collect();\n    if a.len() != 2 {\n        return Err(\"Usage: replay_open archived-capture new-output-dir\".into());\n    }\n    run(Path::new(&a[0]), Path::new(&a[1]))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn invalid_snapshot_creates_no_output_and_existing_results_are_preserved() {\n        let root = std::env::temp_dir().join(format!(\"odb-native-result-{}\", std::process::id()));\n        fs::create_dir(&root).unwrap();\n        let data = root.join(\"capture/receive/data\");\n        fs::create_dir_all(&data).unwrap();\n        fs::write(data.join(\"00001.bin\"), [1u8, 2, 3]).unwrap();\n        let mut v = json!({\"synthetic\":true,\"schema_version\":\"probe-receive-v1\",\"state\":\"cancelled\",\"reads_requested\":2,\"received_bytes\":3,\n            \"plan\":{\"profile\":\"convex-01\",\"channel\":\"data\",\"endpoint\":134,\"transfer_type\":\"bulk\",\"requested_bytes\":16384},\n            \"passive_profile\":{\"profile\":\"convex-01\",\"vid_pid\":\"04b4:4611\",\"profile_match\":true},\n            \"reads\":[{\"ordinal\":1,\"expected_raw_file\":\"00001.bin\",\"raw_file\":\"00001.bin\",\"state\":\"received\",\"code\":0,\"requested_bytes\":16384,\"received_bytes\":3,\"sha256\":\"0000000000000000000000000000000000000000000000000000000000000000\"}]});\n        fs::write(data.join(\"manifest.json\"), serde_json::to_vec(&v).unwrap()).unwrap();\n        let output = root.join(\"result\");\n        assert!(run(&root.join(\"capture\"), &output).is_err());\n        assert!(!output.exists());\n        v[\"reads\"][0][\"sha256\"] = json!(format!(\"{:x}\", Sha256::digest([1u8, 2, 3])));\n        fs::write(data.join(\"manifest.json\"), serde_json::to_vec(&v).unwrap()).unwrap();\n        run(&root.join(\"capture\"), &output).unwrap();\n        let before = fs::read(output.join(\"replay.json\")).unwrap();\n        let audit: serde_json::Value = serde_json::from_slice(&before).unwrap();\n        assert_eq!(audit[\"state\"], \"completed\");\n        assert_eq!(audit[\"images\"], 0);\n        assert_eq!(audit[\"input\"][\"received_bytes\"], 3);\n        assert!(run(&root.join(\"capture\"), &output).is_err());\n        assert_eq!(before, fs::read(output.join(\"replay.json\")).unwrap());\n        fs::remove_dir_all(root).unwrap();\n    }\n}\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/shared-sources.json",
        "sha256": "5d3b280d009c7cb2ddea2f923a36877636a69b03c93b50e2a84d6c2f54212fca",
        "text": "{\n  \"format\": \"odb-source-lock/1\",\n  \"component\": \"ultrasound/convex-decoder@1.0.0\",\n  \"files\": [\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/lib.rs\",\n      \"sha256\": \"93a7d80de814a9fdde3bdc131a8deb3980b3ff7fe399e2e5143bf8064228cfda\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/Cargo.lock\",\n      \"sha256\": \"a89f9d40168858def7e4ee35a1bba63208b8322e3a7ce64270157c2730f44165\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/Cargo.toml\",\n      \"sha256\": \"7f927acd5159c79e9e5cd6e86da164fb52b8ecc0ff9f3676186c7e99899ce91c\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/source-origin.json\",\n      \"sha256\": \"2105fe63c1c9ad1f565f0343e16abc23636a0e5a47b621979de8f140ff78d516\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/admission.rs\",\n      \"sha256\": \"5aba1902cdaa2a8aeed10737896ee186ca8dcbc69d57b4cff92977c093557269\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/archive.rs\",\n      \"sha256\": \"098ebf2b90a99e35d507fc8765184fc1f68631afb96382a2a2138f5104b409f6\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_bfsp.rs\",\n      \"sha256\": \"9c078685c2ba936c640ca1f62d1cf45107e78e7d3040a284f2f60a20ae8d4c4f\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_command.rs\",\n      \"sha256\": \"480fbc33846874ae68b0396cb2913f69f7ec199b90b873060af4946a09cb01bd\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_compound.rs\",\n      \"sha256\": \"a87d04c92d51de21aec911267eb73c4ecccdcac75bef13c362b3b3e4f7967359\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_configuration.rs\",\n      \"sha256\": \"dbdc8df42ad13c65de76e15ca08f767bdce12b79e4667845f94f58902cd61ef8\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_dsc.rs\",\n      \"sha256\": \"1cfa44b12ba6b8b7d8b8f768460ca6e04a475444e0d58c14cae797aa49461e0d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_dtgc.rs\",\n      \"sha256\": \"dcd7fc2a0aaecdbf6c724689343b99fa00ad32f7d1eb7346023133ff32310e18\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_group.rs\",\n      \"sha256\": \"d88d35ce2c5d7b1821f9f305379c11805c0dfb7c3dbdda64da80a07f5c75a7d3\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_lifecycle.rs\",\n      \"sha256\": \"3e0b28199d0e95074fd454d06dbd9f83f309bab0f1a3fab38d7fe703b069175e\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_messages.rs\",\n      \"sha256\": \"aa00a808c67b7daec150fb1fddf6121e9e38a3bf69f9d407b265d2b892bc4db2\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_parameters.rs\",\n      \"sha256\": \"27dcc979d239197a2767d16783450c6c78aef4e89744e14167b965a8c74ea961\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_pipeline.rs\",\n      \"sha256\": \"4fb4925ede9fc66f4fb0292fae144b25822a515b17c60f8567dcb339f33fb11e\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_producers.rs\",\n      \"sha256\": \"41a50d9b9d549088c2caf6e1eff19531ae443df986427bee824190522a08e9f8\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_sri.rs\",\n      \"sha256\": \"74057b9a8cdc0c1012ae40390bb832f05866a07fff2f90234e8ac5c73e9a6d4d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/convex_sri_plan.rs\",\n      \"sha256\": \"fabc74491f2b537dcfb50bc9c66045e4d962b2ca0d1391d596e32b9e1bdea57d\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/framing.rs\",\n      \"sha256\": \"74c4e02ba50a5035517f7b1a7f4023a8ea259af7b92cb0b139e5853d707d653a\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_assets.json\",\n      \"sha256\": \"700e1c948dfb673980c38fba239bed9a3b01bff558f0d56ee17f1e10343fbd0f\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_producer_assets.json\",\n      \"sha256\": \"207ec2321a7c01de1b5462a3e61c0b46f09c9dc9e2d9dbcb3db748186fbdbd0a\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/known_convex_sri_layout.json\",\n      \"sha256\": \"4cafbc69a11b93c19d04d390aa1989d5a241df0d928d027ebe7632bf1921f396\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_dsp.rs\",\n      \"sha256\": \"d711bf2d2221c23f4213a260e2429d2fac3a086d8fc3078d1acad0f8982cca38\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_palette.rs\",\n      \"sha256\": \"0b05f3f2d247222a0f730ee3f361646231c3cb351606bf62adf864788fdf4bc2\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/linear_post.rs\",\n      \"sha256\": \"55bc8d05a021a34ff7da6d9f0aaab3e658a0018e875c78f914d0c8bcbaa069e6\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/rgba_png.rs\",\n      \"sha256\": \"4d91a880687db02ebdaad20b63e28e653af8b15f943a577c34961ef70d3ff8ad\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/resources.rs\",\n      \"sha256\": \"c8dcb773217ae1a6391a6092b902ebedb94bb19691202f24b07e307804aef3e4\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/examples/replay_memory.rs\",\n      \"sha256\": \"2409ba64d2a8470976030b020075799aea4782834104567bd6cdf12c1b17edb3\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/src/selected_profile.rs\",\n      \"sha256\": \"4c0c5b3e451a42a0ce930bdf51d31f031c318f39b66ffd19d8c194b28cb9bf3b\"\n    },\n    {\n      \"path\": \"components/ultrasound/convex-decoder/1.0.0/build/reproducible-rustc.rs\",\n      \"sha256\": \"35ad737748a656197b08b1033df4432bd9a0c444955275e4f9ec68d3c09344c1\"\n    }\n  ]\n}\n"
      },
      {
        "path": "components/ultrasound/convex-open-image/0.1.1/src/bin/capture/mod.rs",
        "sha256": "5a3a66f5aaf1ba09f1a9d79e8f1e3a49b8037aa2d904fa0ff83d58001239128b",
        "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\n//! Native host boundary only: immutable owned DATA snapshot before image evaluation.\nuse serde_json::{Value, json};\nuse sha2::{Digest, Sha256};\nuse std::{\n    fs::{self, OpenOptions},\n    io::Read,\n    path::Path,\n};\n\ntype Result<T> = std::result::Result<T, String>;\nconst TRANSFER: u64 = 16_384;\nconst MAX_READS: u64 = 10_000;\nconst MAX_MANIFEST: u64 = 16 * 1024 * 1024;\n\npub struct Capture {\n    pub reads: Vec<Vec<u8>>,\n    pub audit: Value,\n}\n\nfn regular_bytes(path: &Path, maximum: u64) -> Result<Vec<u8>> {\n    if !fs::symlink_metadata(path)\n        .map_err(|e| e.to_string())?\n        .is_file()\n    {\n        return Err(\"Capture input must be a regular file, not a symlink or device\".into());\n    }\n    let mut options = OpenOptions::new();\n    options.read(true);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt;\n        options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK);\n    }\n    let file = options.open(path).map_err(|e| e.to_string())?;\n    let metadata = file.metadata().map_err(|e| e.to_string())?;\n    if !metadata.is_file() || metadata.len() > maximum {\n        return Err(\"Capture file exceeds its bound or is not regular\".into());\n    }\n    let mut bytes = Vec::new();\n    file.take(maximum + 1)\n        .read_to_end(&mut bytes)\n        .map_err(|e| e.to_string())?;\n    if bytes.len() as u64 > maximum || bytes.len() as u64 != metadata.len() {\n        return Err(\"Capture file grew, shrank or exceeded its bound while reading\".into());\n    }\n    Ok(bytes)\n}\n\nfn unsigned(value: &Value, name: &str) -> Result<u64> {\n    value\n        .as_u64()\n        .ok_or_else(|| format!(\"Missing/invalid {name}\"))\n}\n\nfn memory_seal(v: &Value, count: u64, rows: usize, state: &str) -> Result<()> {\n    let b = &v[\"batch\"];\n    if b[\"first_ordinal\"] != 1\n        || b[\"last_ordinal\"] != count\n        || b[\"persisted_reads\"] != rows\n        || b[\"maximum_retained_buffer_capacity_bytes\"] != 2 * count * TRANSFER\n    {\n        return Err(\"Memory reservation/persisted prefix mismatch\".into());\n    }\n    if state == \"prepared\" && rows == 0 {\n        if b[\"authorization_durable\"] != false\n            || b[\"sealed\"] != false\n            || !b[\"attempted_reads\"].is_null()\n            || v[\"received_bytes\"] != 0\n        {\n            return Err(\"Prepared memory journal claims outcomes\".into());\n        }\n        return Ok(());\n    }\n    let unattempted = if rows as u64 == count {\n        Value::Null\n    } else {\n        json!(rows + 1)\n    };\n    if b[\"sealed\"] != true\n        || b[\"authorization_durable\"] != true\n        || b[\"attempted_reads\"] != rows\n        || b[\"unattempted_from\"] != unattempted\n        || b[\"retained_buffer_capacity_bytes\"]\n            .as_u64()\n            .is_none_or(|n| n > 2 * count * TRANSFER)\n        || v[\"finished_unix_ms\"].as_u64().is_none()\n    {\n        return Err(\n            \"Unsealed memory journal or unknown attempted suffix; continuity not established\"\n                .into(),\n        );\n    }\n    Ok(())\n}\n\n/// A bounded declared host prefix, not an attestation of loss-free device acquisition.\npub fn snapshot(capture: &Path) -> Result<Capture> {\n    let root = capture.canonicalize().map_err(|e| e.to_string())?;\n    for directory in [root.join(\"receive\"), root.join(\"receive/data\")] {\n        if !fs::symlink_metadata(directory)\n            .map_err(|e| e.to_string())?\n            .is_dir()\n        {\n            return Err(\"Receive directories must be real directories\".into());\n        }\n    }\n    let root = root.join(\"receive/data\");\n    let manifest = regular_bytes(&root.join(\"manifest.json\"), MAX_MANIFEST)?;\n    let v: Value = serde_json::from_slice(&manifest).map_err(|e| e.to_string())?;\n    let schema = v[\"schema_version\"]\n        .as_str()\n        .ok_or(\"Missing receive schema\")?;\n    if ![\"probe-receive-v1\", \"probe-receive-memory-v1\"].contains(&schema)\n        || v[\"plan\"][\"profile\"] != \"convex-01\"\n        || v[\"plan\"][\"channel\"] != \"data\"\n        || v[\"plan\"][\"endpoint\"] != 0x86\n        || v[\"plan\"][\"transfer_type\"] != \"bulk\"\n        || v[\"plan\"][\"requested_bytes\"] != TRANSFER\n        || v[\"passive_profile\"][\"profile\"] != \"convex-01\"\n        || v[\"passive_profile\"][\"vid_pid\"] != \"04b4:4611\"\n        || v[\"passive_profile\"][\"profile_match\"] != true\n    {\n        return Err(\"Require the declared UC5-2S convex DATA86/16384 receive profile\".into());\n    }\n    let requested = unsigned(&v[\"reads_requested\"], \"read budget\")?;\n    let rows = v[\"reads\"].as_array().ok_or(\"Missing receive rows\")?;\n    let state = v[\"state\"].as_str().ok_or(\"Missing receive state\")?;\n    if !(1..=MAX_READS).contains(&requested)\n        || rows.len() as u64 > requested\n        || ![\"prepared\", \"cancelled\", \"read_budget_exhausted\"].contains(&state)\n        || (state == \"prepared\" && !rows.is_empty())\n        || (state == \"read_budget_exhausted\" && rows.len() as u64 != requested)\n        || (schema == \"probe-receive-memory-v1\"\n            && state == \"cancelled\"\n            && rows.len() as u64 == requested)\n    {\n        return Err(\n            \"Unknown, failed or nonterminal receive outcome; no continuous replay admitted\".into(),\n        );\n    }\n    if schema == \"probe-receive-memory-v1\" {\n        memory_seal(&v, requested, rows.len(), state)?;\n    }\n    let mut reads = Vec::with_capacity(rows.len());\n    let mut total = 0u64;\n    let mut stream = Sha256::new();\n    let mut cancelled_before_io = None;\n    for (index, row) in rows.iter().enumerate() {\n        let ordinal = index + 1;\n        let name = format!(\"{ordinal:05}.bin\");\n        if row[\"state\"] == \"cancelled_before_io\" {\n            if schema != \"probe-receive-v1\"\n                || state != \"cancelled\"\n                || ordinal != rows.len()\n                || row[\"ordinal\"] != ordinal\n                || row[\"requested_bytes\"] != TRANSFER\n                || row[\"expected_raw_file\"] != name\n                || !row[\"raw_file\"].is_null()\n                || !row[\"received_bytes\"].is_null()\n                || !row[\"code\"].is_null()\n                || !row[\"sha256\"].is_null()\n            {\n                return Err(\n                    \"Only a final, result-free per-read cancellation before I/O is admissible\"\n                        .into(),\n                );\n            }\n            // This is an explicit no-I/O intent, not an unknown transfer or payload.\n            cancelled_before_io = Some(ordinal);\n            continue;\n        }\n        let n = unsigned(&row[\"received_bytes\"], \"received length\")?;\n        let code = match row[\"state\"].as_str() {\n            Some(\"received\") => 0,\n            Some(\"timeout\") => -7,\n            _ => {\n                return Err(format!(\n                    \"Read{ordinal}: unknown/error outcome; never join across a gap\"\n                ));\n            }\n        };\n        let hash = row[\"sha256\"].as_str().ok_or(\"Missing DATA SHA256\")?;\n        if row[\"ordinal\"] != ordinal\n            || row[\"requested_bytes\"] != TRANSFER\n            || row[\"raw_file\"] != name\n            || row[\"expected_raw_file\"] != name\n            || row[\"code\"] != code\n            || n > TRANSFER\n            || hash.len() != 64\n            || !hash\n                .bytes()\n                .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))\n        {\n            return Err(format!(\n                \"Read{ordinal}: ordinal, filename, code, length or hash mismatch\"\n            ));\n        }\n        let bytes = regular_bytes(&root.join(&name), n)?;\n        if bytes.len() as u64 != n || format!(\"{:x}\", Sha256::digest(&bytes)) != hash {\n            return Err(format!(\"Read{ordinal}: stored DATA length/SHA256 mismatch\"));\n        }\n        total += n;\n        stream.update(&bytes);\n        reads.push(bytes);\n    }\n    if v[\"received_bytes\"] != total {\n        return Err(\"Receive byte total mismatch\".into());\n    }\n    let data_reads = reads.len();\n    Ok(Capture {\n        reads,\n        audit: json!({\"schema\":\"odb-native-DATA-snapshot/1\",\n        \"source_schema\":schema,\"source_state\":state,\"manifest_sha256\":format!(\"{:x}\",Sha256::digest(&manifest)),\n        \"DATA_sha256\":format!(\"{:x}\",stream.finalize()),\"received_bytes\":total,\"journal_rows\":rows.len(),\n        \"DATA_reads\":data_reads,\"cancelled_before_io_ordinal\":cancelled_before_io,\n        \"snapshot\":\"owned immutable byte arrays verified before image evaluation\",\n        \"max_DATA_bytes\":MAX_READS*TRANSFER,\"manifest_byte_limit\":MAX_MANIFEST,\n        \"timeout_prefixes_preserved\":true,\"device_identity_attested\":false,\n        \"wire_loss_free\":false,\"physical_capture_complete\":false}),\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::sync::atomic::{AtomicUsize, Ordering};\n    struct Fixture {\n        root: std::path::PathBuf,\n        value: Value,\n    }\n    impl Fixture {\n        fn new(memory: bool) -> Self {\n            static NEXT: AtomicUsize = AtomicUsize::new(0);\n            let root = std::env::temp_dir().join(format!(\n                \"odb-native-capture-{}-{}\",\n                std::process::id(),\n                NEXT.fetch_add(1, Ordering::Relaxed)\n            ));\n            fs::create_dir_all(root.join(\"receive/data\")).unwrap();\n            let mut rows = Vec::new();\n            for (index, bytes) in [vec![], vec![1u8, 2, 3], vec![4u8, 5]]\n                .into_iter()\n                .enumerate()\n            {\n                let name = format!(\"{:05}.bin\", index + 1);\n                fs::write(root.join(\"receive/data\").join(&name), &bytes).unwrap();\n                let timeout = index != 1;\n                rows.push(json!({\"ordinal\":index+1,\"state\":if timeout{\"timeout\"}else{\"received\"},\"code\":if timeout{-7}else{0},\"requested_bytes\":TRANSFER,\"received_bytes\":bytes.len(),\"raw_file\":name,\"expected_raw_file\":name,\"sha256\":format!(\"{:x}\",Sha256::digest(&bytes))}));\n            }\n            let value = json!({\"schema_version\":if memory{\"probe-receive-memory-v1\"}else{\"probe-receive-v1\"},\"state\":\"cancelled\",\"reads_requested\":4,\"received_bytes\":5,\"finished_unix_ms\":1,\n                \"plan\":{\"profile\":\"convex-01\",\"channel\":\"data\",\"endpoint\":134,\"transfer_type\":\"bulk\",\"requested_bytes\":TRANSFER},\n                \"passive_profile\":{\"profile\":\"convex-01\",\"vid_pid\":\"04b4:4611\",\"profile_match\":true},\"reads\":rows,\n                \"batch\":{\"first_ordinal\":1,\"last_ordinal\":4,\"persisted_reads\":3,\"maximum_retained_buffer_capacity_bytes\":8*TRANSFER,\"sealed\":true,\"authorization_durable\":true,\"attempted_reads\":3,\"unattempted_from\":4,\"retained_buffer_capacity_bytes\":2*TRANSFER}});\n            Self { root, value }\n        }\n        fn load(&self) -> Result<Capture> {\n            fs::write(\n                self.root.join(\"receive/data/manifest.json\"),\n                serde_json::to_vec(&self.value).unwrap(),\n            )\n            .unwrap();\n            snapshot(&self.root)\n        }\n    }\n    impl Drop for Fixture {\n        fn drop(&mut self) {\n            let _ = fs::remove_dir_all(&self.root);\n        }\n    }\n    #[test]\n    fn both_journals_preserve_empty_and_partial_timeout_bytes_in_owned_snapshot() {\n        for memory in [false, true] {\n            let f = Fixture::new(memory);\n            let captured = f.load().unwrap();\n            assert_eq!(captured.reads, [vec![], vec![1, 2, 3], vec![4, 5]]);\n            fs::write(f.root.join(\"receive/data/00002.bin\"), b\"changed\").unwrap();\n            assert_eq!(captured.reads[1], [1, 2, 3]);\n            assert_eq!(captured.audit[\"received_bytes\"], 5);\n            assert_eq!(captured.audit[\"physical_capture_complete\"], false);\n        }\n    }\n    #[test]\n    fn explicit_terminal_no_io_is_not_a_gap_and_orphan_files_are_not_consumed() {\n        let mut f = Fixture::new(false);\n        let intent = json!({\"ordinal\":4,\"state\":\"cancelled_before_io\",\"expected_raw_file\":\"00004.bin\",\"requested_bytes\":TRANSFER,\"raw_file\":null,\"received_bytes\":null,\"sha256\":null});\n        f.value[\"reads\"].as_array_mut().unwrap().push(intent);\n        fs::write(f.root.join(\"receive/data/00004.bin\"), [99u8]).unwrap();\n        let result = f.load().unwrap();\n        assert_eq!(result.reads.len(), 3);\n        assert_eq!(result.audit[\"received_bytes\"], 5);\n        assert_eq!(result.audit[\"cancelled_before_io_ordinal\"], 4);\n        f.value[\"reads\"][3][\"received_bytes\"] = json!(1);\n        assert!(f.load().is_err());\n        f.value[\"reads\"][3][\"received_bytes\"] = Value::Null;\n        f.value[\"reads\"][2][\"state\"] = json!(\"cancelled_before_io\");\n        assert!(f.load().is_err());\n    }\n    #[test]\n    fn hash_length_profile_ordinals_and_unknown_outcomes_are_rejected() {\n        for mutation in 0..13 {\n            let mut f = Fixture::new(false);\n            match mutation {\n                0 => f.value[\"reads\"][1][\"sha256\"] = json!(\"0\".repeat(64)),\n                1 => f.value[\"reads\"][1][\"received_bytes\"] = json!(2),\n                2 => f.value[\"reads\"][1][\"ordinal\"] = json!(1),\n                3 => f.value[\"reads\"][1][\"raw_file\"] = json!(\"../outside\"),\n                4 => f.value[\"plan\"][\"endpoint\"] = json!(0x88),\n                5 => f.value[\"reads\"][1][\"state\"] = json!(\"in_flight\"),\n                6 => f.value[\"reads\"][1][\"code\"] = json!(-1),\n                7 => f.value[\"received_bytes\"] = json!(6),\n                8 => f.value[\"state\"] = json!(\"in_progress\"),\n                9 => f.value[\"state\"] = json!(\"failed\"),\n                10 => f.value[\"reads_requested\"] = json!(10_001),\n                11 => f.value[\"passive_profile\"][\"profile_match\"] = json!(false),\n                _ => f.value[\"reads\"][1][\"sha256\"] = json!(\"A\".repeat(64)),\n            }\n            assert!(f.load().is_err(), \"mutation{mutation}\");\n        }\n    }\n    #[test]\n    fn memory_requires_a_consistent_durable_seal_without_unknown_suffix() {\n        for mutation in 0..8 {\n            let mut f = Fixture::new(true);\n            match mutation {\n                0 => f.value[\"batch\"][\"sealed\"] = json!(false),\n                1 => f.value[\"batch\"][\"authorization_durable\"] = json!(false),\n                2 => f.value[\"batch\"][\"attempted_reads\"] = json!(4),\n                3 => f.value[\"batch\"][\"persisted_reads\"] = json!(2),\n                4 => f.value[\"batch\"][\"unattempted_from\"] = Value::Null,\n                5 => f.value[\"batch\"][\"first_ordinal\"] = json!(0),\n                6 => f.value[\"batch\"][\"retained_buffer_capacity_bytes\"] = json!(9 * TRANSFER),\n                _ => f.value[\"finished_unix_ms\"] = Value::Null,\n            }\n            assert!(f.load().is_err(), \"seal mutation{mutation}\");\n        }\n    }\n    #[cfg(unix)]\n    #[test]\n    fn source_symlinks_and_nonregular_inputs_are_never_read() {\n        use std::os::unix::fs::symlink;\n        let f = Fixture::new(false);\n        f.load().unwrap();\n        let raw = f.root.join(\"receive/data/00002.bin\");\n        fs::remove_file(&raw).unwrap();\n        symlink(\"00003.bin\", &raw).unwrap();\n        assert!(f.load().is_err());\n        fs::remove_file(&raw).unwrap();\n        fs::create_dir(&raw).unwrap();\n        assert!(f.load().is_err());\n        fs::remove_dir(&raw).unwrap();\n        fs::write(&raw, [1, 2, 3]).unwrap();\n        let data = f.root.join(\"receive/data\");\n        fs::rename(&data, f.root.join(\"receive/other\")).unwrap();\n        symlink(\"other\", &data).unwrap();\n        assert!(snapshot(&f.root).is_err());\n    }\n}\n"
      }
    ],
    "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\n//! Original display profile for the observed UC5-2S DATA format.\n//! No device configuration, TX, ADC or HV data. No calibration or acoustic units.\nuse odb_convex_decoder::{ImageProfile, Result, RgbaFrame, Session};\nuse serde_json::{Value, json};\nuse std::cell::RefCell;\npub const PROFILE: &str = \"uc5-normalized-binomial-v1\";\npub const WIDTH: usize = 512;\npub const HEIGHT: usize = 512;\n#[derive(Clone, Copy)]\npub struct Display {\n    pub gain_db: f32,\n    pub dynamic_range_db: f32,\n    pub tgc_slope_db: f32,\n    pub persistence: f32,\n}\nimpl Default for Display {\n    fn default() -> Self {\n        // Display choices, not measured tissue attenuation or probe calibration.\n        Self {\n            gain_db: -8.,\n            dynamic_range_db: 48.,\n            tgc_slope_db: 0.,\n            persistence: 0.25,\n        }\n    }\n}\nimpl Display {\n    fn validate(self) -> Result<()> {\n        if !self.gain_db.is_finite()\n            || !(-60. ..=60.).contains(&self.gain_db)\n            || !self.dynamic_range_db.is_finite()\n            || !(12. ..=120.).contains(&self.dynamic_range_db)\n            || !self.tgc_slope_db.is_finite()\n            || !(-60. ..=60.).contains(&self.tgc_slope_db)\n            || !self.persistence.is_finite()\n            || !(0. ..=0.9).contains(&self.persistence)\n        {\n            return Err(\"Display settings out of bounded finite domain\".into());\n        }\n        Ok(())\n    }\n}\n/// Half of the symmetric length16 binomial FIR: coefficients of (1+z^-1)^15/2^15.\n/// Integer construction is exact on native and WASM, with unity DC gain.\npub fn binomial16_half() -> [f32; 8] {\n    let mut c = 1u32;\n    std::array::from_fn(|i| {\n        if i > 0 {\n            c = c * (16 - i as u32) / i as u32;\n        }\n        c as f32 / 32768.\n    })\n}\npub fn configuration() -> Value {\n    // These are observed DATA-format facts; they do not request hardware settings.\n    json!({\"parameters\":{\"A\":2,\"B\":0,\"C\":4,\"D\":4,\"E\":4},\n      \"grouping\":{\"schema_version\":\"convex-b-grouping-settings-v1\",\"b_capacity_bytes\":2097152,\"m_capacity_bytes\":2097152,\"m_line_count\":1,\"bm_header_26\":0,\"bm_header_28\":0,\"epoch_policy\":\"new-organizer-per-observed-epoch-v1\"},\n      \"settings\":{\"schema_version\":\"convex-pipeline-settings-v1\",\n        \"bfsp\":{\"schema_version\":\"convex-bfsp-settings-v1\",\"depth_index\":0,\"selector_index\":0,\"line_words\":1800},\n        \"dtgc\":{\"schema_version\":\"convex-dtgc-settings-v1\",\"bank_selector_0x48\":0,\"gain\":0.,\"dynamic_range\":48.,\"tgc_controls\":[0.,0.,0.,0.,0.,0.]},\n        \"sri\":{\"enhancement\":0,\"correlation_index\":0},\n        \"dsc\":{\"schema_version\":\"convex-dsc-settings-v1\",\"depth_index\":0,\"width\":WIDTH,\"height\":HEIGHT,\"gray_map\":0,\"tint_map\":0},\"max_groups\":128}})\n}\npub fn image_profile(display: Display) -> Result<ImageProfile> {\n    display.validate()?;\n    let mut settings = configuration()[\"settings\"].clone();\n    settings[\"dtgc\"][\"dynamic_range\"] = json!(display.dynamic_range_db);\n    // The rendering sector is a chosen normalized view, NOT a measured scan angle.\n    let theta = std::f32::consts::PI / 3.;\n    let mut parameters = [0u8; 40];\n    for (i, v) in [\n        (2, WIDTH as u32),\n        (3, HEIGHT as u32),\n        (6, 1f32.to_bits()),\n        (7, theta.to_bits()),\n        (8, 1f32.to_bits()),\n        (9, (theta / 2.).cos().to_bits()),\n    ] {\n        parameters[4 * i..4 * i + 4].copy_from_slice(&v.to_le_bytes());\n    }\n    let tgc = std::array::from_fn(|sample| display.tgc_slope_db * (sample as f32 / 1023.));\n    Ok(ImageProfile {\n        settings,\n        bfsp_coefficients: binomial16_half(),\n        // 1800words ->900 ushort4 vectors ->768 valid FIR positions ->3072 upsampled values.\n        // Reduce those3072 values to the core's1024 samples. No physical sampling clock inferred.\n        bfsp_downsample: 3,\n        dtgc_tgc: tgc,\n        // Required core bookkeeping range; this scalar is unused by pixel evaluation.\n        dtgc_depth: 2.,\n        dtgc_gain: display.gain_db,\n        dtgc_dr: 255. / display.dynamic_range_db,\n        // Disable adaptive compound mixing; retain the shared processing/lifecycle implementation.\n        compound_window: 1,\n        compound_coef0: 0.,\n        compound_threshold_a: 0.,\n        compound_threshold_b: 1.,\n        temporal_correlation: display.persistence,\n        dsc_parameters: parameters,\n        display_map: std::array::from_fn(|i| (i / 4) as u8),\n    })\n}\npub fn manifest(display: Display) -> Result<Value> {\n    display.validate()?;\n    Ok(\n        json!({\"profile\":PROFILE,\"license\":\"AGPL-3.0-or-later\",\"purpose\":\"reconstruction-only\",\n        \"input_format\":\"observed mode0/P896/N32, line_words1800, four u16 envelope lanes\",\n        \"filter\":\"(1+z^-1)^15 / 2^15; 16tap binomial, generated by integer recurrence\",\n        \"gain_db\":display.gain_db,\"dynamic_range_db\":display.dynamic_range_db,\n        \"tgc\":\"slope_db * sample_index /1023; default flat\",\"tgc_slope_db\":display.tgc_slope_db,\n        \"temporal_persistence\":display.persistence,\"palette\":\"identity grayscale, generated\",\n        \"geometry\":\"chosen normalized60degree sector, unit radius; no metric scale\",\n        \"alpha\":\"output is opaque; core's fourth LUT component is an intensity index\",\n        \"width\":WIDTH,\"height\":HEIGHT,\"transmit_commands\":0,\"external_resource_files\":0,\n        \"calibrated\":false,\"vendor_profile_equivalence_claimed\":false}),\n    )\n}\npub struct OpenSession {\n    engine: Session,\n}\nimpl OpenSession {\n    pub fn new(display: Display) -> Result<Self> {\n        let profile = image_profile(display)?;\n        let mut conf = configuration();\n        conf[\"settings\"] = profile.settings.clone();\n        let mut engine = Session::from_profile(&conf, profile)?;\n        engine.allow_continuous();\n        Ok(Self { engine })\n    }\n    pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<RgbaFrame>> {\n        let frames = self.engine.push_rgba(bytes)?;\n        Ok(opaque(frames))\n    }\n    pub fn finish(&mut self) -> Result<Vec<RgbaFrame>> {\n        let frames = self.engine.finish_rgba()?;\n        Ok(opaque(frames))\n    }\n    /// Update all processing histories without rasterizing discarded frames.\n    pub fn advance(&mut self, bytes: &[u8]) -> Result<()> {\n        self.engine.advance(bytes)\n    }\n}\nfn opaque(mut frames: Vec<RgbaFrame>) -> Vec<RgbaFrame> {\n    for f in &mut frames {\n        for pixel in f.rgba.as_chunks_mut::<4>().0 {\n            pixel[3] = 255;\n        }\n    }\n    frames\n}\nthread_local! {static ENGINE:RefCell<Option<OpenSession>>=const{RefCell::new(None)};static OUTPUT:RefCell<Vec<u8>>=const{RefCell::new(Vec::new())};}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_builtin_profile() -> u32 {\n    1\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_live_api() -> u32 {\n    1\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_alloc(n: usize) -> *mut u8 {\n    if n > 4194304 {\n        return std::ptr::null_mut();\n    }\n    Box::into_raw(vec![0u8; n].into_boxed_slice()) as *mut u8\n}\n/// # Safety\n/// Pointer/length must describe the exact live allocation returned by odb_alloc.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn odb_free(p: *mut u8, n: usize) {\n    if !p.is_null() {\n        unsafe {\n            drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(p, n)));\n        }\n    }\n}\nfn encode(frames: Vec<RgbaFrame>, png: bool) -> Result<Vec<u8>> {\n    let mut out = (frames.len() as u32).to_le_bytes().to_vec();\n    for f in frames {\n        let b = if png {\n            odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, &f.rgba)?\n        } else {\n            f.rgba\n        };\n        out.extend(f.sequence.to_le_bytes());\n        out.extend((b.len() as u32).to_le_bytes());\n        out.extend(b);\n    }\n    Ok(out)\n}\nfn initialize(bytes: &[u8]) -> Result<Vec<u8>> {\n    let opts: Value = serde_json::from_slice(bytes).map_err(|e| e.to_string())?;\n    let obj = opts\n        .as_object()\n        .ok_or(\"Require explicit reconstruction options\")?;\n    if obj.len() != 1 || obj.get(\"purpose\") != Some(&json!(\"reconstruction\")) {\n        return Err(\"This original profile supports reconstruction only; USB acquisition is not implemented\".into());\n    }\n    let session = OpenSession::new(Display::default())?;\n    ENGINE.with(|s| *s.borrow_mut() = Some(session));\n    let mut v = manifest(Display::default())?;\n    v[\"ready\"] = json!(true);\n    Ok(serde_json::to_vec(&v).unwrap())\n}\n/// # Safety\n/// Pointer/length must identify initialized readable bytes for the duration of the call.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn odb_call(op: u32, p: *const u8, n: usize) -> i32 {\n    if p.is_null() || n > 4194304 {\n        return -1;\n    }\n    let bytes = unsafe { std::slice::from_raw_parts(p, n) };\n    let result = match op {\n        7 | 8 => initialize(bytes),\n        2 | 9 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .push(bytes)\n            })\n            .and_then(|f| encode(f, op == 2)),\n        5 | 10 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .finish()\n            })\n            .and_then(|f| encode(f, op == 5)),\n        11 => odb_convex_decoder::encode_rgba_png(WIDTH, HEIGHT, bytes),\n        12 => ENGINE\n            .with(|s| {\n                s.borrow_mut()\n                    .as_mut()\n                    .ok_or(\"Initialize reconstruction first\")?\n                    .advance(bytes)\n            })\n            .and_then(|()| encode(Vec::new(), false)),\n        4 => Err(\n            \"This original profile cannot produce probe configuration or transmit commands\".into(),\n        ),\n        _ => Err(\"Unsupported reconstruction operation\".into()),\n    };\n    let (status, b) = match result {\n        Ok(b) => (0, b),\n        Err(e) => (-1, e.into_bytes()),\n    };\n    OUTPUT.with(|o| *o.borrow_mut() = b);\n    status\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_output_ptr() -> *const u8 {\n    OUTPUT.with(|o| o.borrow().as_ptr())\n}\n#[unsafe(no_mangle)]\npub extern \"C\" fn odb_output_len() -> usize {\n    OUTPUT.with(|o| o.borrow().len())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn binomial_filter_has_unit_dc_gain_and_zero_nyquist_response() {\n        let half = binomial16_half();\n        let full: Vec<_> = half.iter().chain(half.iter().rev()).copied().collect();\n        assert_eq!(full.iter().sum::<f32>(), 1.);\n        assert_eq!(\n            full.iter()\n                .enumerate()\n                .map(|(i, v)| if i % 2 == 0 { *v } else { -*v })\n                .sum::<f32>(),\n            0.\n        );\n        assert!(full.iter().all(|c| *c > 0.));\n        // Analytic binomial magnitude at omega=pi/2: cos(pi/4)^15.\n        let (re, im) = full\n            .iter()\n            .enumerate()\n            .fold((0f64, 0f64), |(re, im), (n, &c)| {\n                let a = n as f64 * std::f64::consts::FRAC_PI_2;\n                (re + c as f64 * a.cos(), im - c as f64 * a.sin())\n            });\n        assert!((re.hypot(im) - 2f64.powf(-7.5)).abs() < 1e-12);\n    }\n    #[test]\n    fn display_inputs_are_finite_bounded_and_never_activate_hardware() {\n        for invalid in [\n            Display {\n                gain_db: f32::NAN,\n                ..Display::default()\n            },\n            Display {\n                dynamic_range_db: 0.,\n                ..Display::default()\n            },\n            Display {\n                persistence: 1.,\n                ..Display::default()\n            },\n            Display {\n                tgc_slope_db: f32::INFINITY,\n                ..Display::default()\n            },\n        ] {\n            assert!(image_profile(invalid).is_err());\n        }\n        assert!(initialize(br#\"{}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"acquisition\"}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"reconstruction\",\"gain\":0}\"#).is_err());\n        assert!(initialize(br#\"{\"purpose\":\"reconstruction\"}\"#).is_ok());\n        let b = [0u8; 512];\n        let status = unsafe { odb_call(4, b.as_ptr(), b.len()) };\n        assert_eq!(status, -1);\n        OUTPUT.with(|o| assert!(String::from_utf8_lossy(&o.borrow()).contains(\"cannot produce\")));\n    }\n}\n"
  },
  "behavior": "Explicit reconstruction-only initialization; binomial FIR, adjustable native display settings, generated grayscale and normalized sector. No external resource files, hardware configuration, USB or network access. Shared Rust processing library for native/WASM. Recording host requires explicit reconstruction purpose. Shared processing sources are pinned by shared-sources.json. No metric calibration or clinical equivalence claim.",
  "ports": {
    "bytes": {
      "direction": "in",
      "kind": "stream",
      "contract": "odb.usb.bytes/1"
    },
    "frames": {
      "direction": "out",
      "kind": "stream",
      "contract": "odb.video.frame/1"
    }
  },
  "parameters": {},
  "fixtures": [],
  "evidence": [],
  "wasm": {
    "imports": [
      {
        "module": "wasi_snapshot_preview1",
        "name": "environ_get",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "environ_sizes_get",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "fd_write",
        "kind": "function"
      },
      {
        "module": "wasi_snapshot_preview1",
        "name": "proc_exit",
        "kind": "function"
      }
    ],
    "exports": [
      {
        "name": "memory",
        "kind": "memory"
      },
      {
        "name": "odb_alloc",
        "kind": "function"
      },
      {
        "name": "odb_builtin_profile",
        "kind": "function"
      },
      {
        "name": "odb_call",
        "kind": "function"
      },
      {
        "name": "odb_free",
        "kind": "function"
      },
      {
        "name": "odb_output_len",
        "kind": "function"
      },
      {
        "name": "odb_output_ptr",
        "kind": "function"
      },
      {
        "name": "odb_live_api",
        "kind": "function"
      }
    ],
    "signature": "odb_call(i32,i32,i32) -> i32",
    "maximum_memory_bytes": 268435456,
    "bytes": 505744,
    "executed": false
  }
}