diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-25 12:07:58 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-25 12:07:58 +0300 |
| commit | 7c7e91c857cbf8e5c2bf17a8d2d94e78177315f5 (patch) | |
| tree | 2abca6f46362b380557632263a83b8bd3e616148 | |
| parent | 7c3b3a53f51a87e7d6f28ed26173aa4c8e5b6957 (diff) | |
| download | fparkan-7c7e91c857cbf8e5c2bf17a8d2d94e78177315f5.tar.xz fparkan-7c7e91c857cbf8e5c2bf17a8d2d94e78177315f5.zip | |
build(ci): fail closed on shader provenance
| -rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
| -rw-r--r-- | Cargo.lock | 420 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/manifest.json | 2 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.frag.spv | bin | 500 -> 500 bytes | |||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.vert.spv | bin | 1012 -> 1012 bytes | |||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi.rs | 8 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/tests.rs | 6 | ||||
| -rw-r--r-- | xtask/Cargo.toml | 2 | ||||
| -rw-r--r-- | xtask/src/main.rs | 311 |
9 files changed, 733 insertions, 18 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ca3066..a91fad1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,6 +112,8 @@ jobs: echo "DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH" >> "$GITHUB_ENV" - name: Install cargo-deny run: cargo install cargo-deny --version 0.19.9 --locked + - name: Verify shader provenance + run: cargo xtask shader-provenance - name: Run canonical CI gate run: cargo xtask ci - name: Run native Vulkan smoke @@ -107,6 +107,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -188,22 +194,38 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.3.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" dependencies = [ "serde", - "serde_core", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 2.0.18", + "toml 0.8.23", + "unicode-xid", + "url", ] [[package]] name = "cargo_metadata" -version = "0.23.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868" dependencies = [ "camino", "cargo-platform", + "cargo-util-schemas", "semver", "serde", "serde_json", @@ -351,6 +373,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "dlib" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -387,6 +420,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -440,6 +484,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] name = "fparkan-animation" version = "0.1.0" @@ -790,6 +843,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -925,6 +1081,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -995,6 +1157,15 @@ dependencies = [ ] [[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] name = "num_enum" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1245,6 +1416,15 @@ dependencies = [ ] [[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] name = "owned_ttf_parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1312,12 +1492,21 @@ dependencies = [ ] [[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -1479,6 +1668,28 @@ dependencies = [ ] [[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1513,6 +1724,15 @@ dependencies = [ [[package]] name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" @@ -1595,6 +1815,12 @@ dependencies = [ ] [[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1612,6 +1838,17 @@ dependencies = [ ] [[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1677,6 +1914,28 @@ dependencies = [ ] [[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] name = "toml" version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1684,7 +1943,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", - "serde_spanned", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -1693,6 +1952,15 @@ dependencies = [ [[package]] name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" @@ -1711,6 +1979,20 @@ dependencies = [ [[package]] name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" @@ -1731,6 +2013,12 @@ dependencies = [ ] [[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] name = "toml_writer" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1759,6 +2047,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1771,6 +2065,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2142,6 +2460,9 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] [[package]] name = "winnow" @@ -2159,6 +2480,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2223,7 +2550,30 @@ dependencies = [ "fparkan-corpus", "serde", "serde_json", - "toml", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] @@ -2247,6 +2597,60 @@ dependencies = [ ] [[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/adapters/fparkan-render-vulkan/shaders/manifest.json b/adapters/fparkan-render-vulkan/shaders/manifest.json index ce0fa85..ee61039 100644 --- a/adapters/fparkan-render-vulkan/shaders/manifest.json +++ b/adapters/fparkan-render-vulkan/shaders/manifest.json @@ -1 +1 @@ -{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"1e57f14d193fc61457c0749081c452ad25669998913107df12f3ccc3c33e0341","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":253,"sha256":"9023b1cc856c98ecd21755596c4e9d1e62cc63e1787f8c43ada2101544e8d0d1","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"23e1d3d9d32e7f7ec0b9ca87f8b86be8f8363c7eb5d745fc5a157cb8433eb138"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"f19e74d001d07fb537d4b0f9e621f9b8bc40eeb68816130220853abea6bd4445","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":125,"sha256":"6efe2c9716ae845c471ecbaac2c83e56a17a37dc017dd63f0a05f0d9161f44ba","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"f09342c22d58c8768151ab8579e54e49af586434a4005d16a24e816d881a64f0"}],"manifest_hash":"20fb84fb6edbd6897e2ea3c2ec3a6db3826a84b46c4efb69027c1cfc0119ccf2"} +{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"1e57f14d193fc61457c0749081c452ad25669998913107df12f3ccc3c33e0341","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":253,"sha256":"4d3ceca7b42ebc971d831b0a0d816457397bd9aeda47fb8d44c4b1aeaa5e7ba0","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"23e1d3d9d32e7f7ec0b9ca87f8b86be8f8363c7eb5d745fc5a157cb8433eb138"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"f19e74d001d07fb537d4b0f9e621f9b8bc40eeb68816130220853abea6bd4445","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":125,"sha256":"5a7441be03cd3c25d557268b2e58d5aa50504c87bffcb4c3fd7cbcf007db0b96","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"f09342c22d58c8768151ab8579e54e49af586434a4005d16a24e816d881a64f0"}],"manifest_hash":"11e3feb65200ebd2ac87b7e776e9c6433a5da9d71a651bfadea89a51be17ff05"}
\ No newline at end of file diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv Binary files differindex c5d57ee..95f7c37 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv +++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv Binary files differindex 04321ea..efa9481 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index 386cb68..ad65e81 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -86,8 +86,8 @@ const VALIDATION_LAYER_NAME: &str = "VK_LAYER_KHRONOS_validation"; pub(crate) const SPIRV_MAGIC: u32 = 0x0723_0203; pub(crate) const SPIRV_VERSION_1_0: u32 = 0x0001_0000; pub(crate) const TRIANGLE_VERTEX_SHADER_WORDS: &[u32] = &[ - SPIRV_MAGIC, - SPIRV_VERSION_1_0, + 0x0723_0203, + 0x0001_0300, 0x0008_000b, 0x0000_0021, 0x0000_0000, @@ -341,8 +341,8 @@ pub(crate) const TRIANGLE_VERTEX_SHADER_WORDS: &[u32] = &[ 0x0001_0038, ]; pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[ - SPIRV_MAGIC, - SPIRV_VERSION_1_0, + 0x0723_0203, + 0x0001_0300, 0x0008_000b, 0x0000_0013, 0x0000_0000, diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs index b04ce8c..9c927a8 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs @@ -612,7 +612,7 @@ fn triangle_shader_manifest_hashes_are_stable() { assert_eq!(report.modules[0].word_count, 253); assert_eq!( report.modules[0].sha256, - "9023b1cc856c98ecd21755596c4e9d1e62cc63e1787f8c43ada2101544e8d0d1" + "4d3ceca7b42ebc971d831b0a0d816457397bd9aeda47fb8d44c4b1aeaa5e7ba0" ); assert_eq!(report.modules[0].descriptor_sets, 0); assert_eq!(report.modules[0].push_constant_bytes, 0); @@ -627,11 +627,11 @@ fn triangle_shader_manifest_hashes_are_stable() { assert!(!report.modules[0].interface_hash.is_empty()); assert_eq!( report.modules[1].sha256, - "6efe2c9716ae845c471ecbaac2c83e56a17a37dc017dd63f0a05f0d9161f44ba" + "5a7441be03cd3c25d557268b2e58d5aa50504c87bffcb4c3fd7cbcf007db0b96" ); assert_eq!( report.manifest_hash, - "20fb84fb6edbd6897e2ea3c2ec3a6db3826a84b46c4efb69027c1cfc0119ccf2" + "11e3feb65200ebd2ac87b7e776e9c6433a5da9d71a651bfadea89a51be17ff05" ); } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index f3f3621..86dfe13 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -7,7 +7,7 @@ repository.workspace = true [dependencies] fparkan-corpus = { path = "../crates/fparkan-corpus" } -cargo_metadata = "0.23" +cargo_metadata = "0.21.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.9" diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e1e5970..735ec94 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -29,6 +29,7 @@ use std::fmt; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; const CORPORA_MANIFEST_ENV: &str = "FPARKAN_CORPORA_MANIFEST"; const PART1_ROOT_ENV: &str = "FPARKAN_CORPUS_PART1_ROOT"; @@ -36,6 +37,7 @@ const PART2_ROOT_ENV: &str = "FPARKAN_CORPUS_PART2_ROOT"; const CI_ACCEPTANCE_ROADMAP: &str = "fixtures/acceptance/stage_0_roadmap.md"; const CI_ACCEPTANCE_COVERAGE: &str = "fixtures/acceptance/coverage.tsv"; const CI_ACCEPTANCE_REPORT: &str = "target/fparkan/acceptance/stage-0-audit.json"; +const SHADER_MANIFEST_REPORT: &str = "adapters/fparkan-render-vulkan/shaders/manifest.json"; const STAGE_PACKAGE_MANIFEST: &str = "fixtures/acceptance/stage_packages.toml"; const SUPPLY_CHAIN_POLICY_CONFIG: &str = "deny.toml"; const REQUIRED_NATIVE_SMOKE_PLATFORMS: &[&str] = &["macos"]; @@ -73,6 +75,7 @@ fn run(args: &[String]) -> Result<(), String> { [cmd] if cmd == "ci" => { run_cargo_fmt_check()?; run_policy(Path::new("."))?; + run_shader_provenance_verification()?; cargo(&["test", "--workspace", "--all-targets", "--all-features", "--locked"])?; cargo(&[ "clippy", @@ -95,6 +98,7 @@ fn run(args: &[String]) -> Result<(), String> { Ok(()) } [cmd] if cmd == "policy" => run_policy(Path::new(".")), + [cmd] if cmd == "shader-provenance" => run_shader_provenance_verification(), [cmd, subcmd, rest @ ..] if cmd == "acceptance" && subcmd == "report" => { let options = parse_acceptance_options(rest)?; run_acceptance_report(&options) @@ -129,7 +133,7 @@ fn run(args: &[String]) -> Result<(), String> { Ok(()) } _ => Err( - "usage: cargo xtask ci | policy | acceptance report --suite synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] [--out <path>] | acceptance audit [--roadmap <path>] [--coverage <path>] [--out <path>] [--strict] | native-smoke audit --dir <path> | package --target <triple> --app viewer|game|headless|cli | test synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] | corpus baseline --root <path>" + "usage: cargo xtask ci | policy | shader-provenance | acceptance report --suite synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] [--out <path>] | acceptance audit [--roadmap <path>] [--coverage <path>] [--out <path>] [--strict] | native-smoke audit --dir <path> | package --target <triple> --app viewer|game|headless|cli | test synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] | corpus baseline --root <path>" .to_string(), ), } @@ -191,6 +195,285 @@ fn run_cargo_fmt_check() -> Result<(), String> { } } +fn run_shader_provenance_verification() -> Result<(), String> { + let manifest_path = workspace_relative_path(SHADER_MANIFEST_REPORT); + let manifest = load_shader_manifest(&manifest_path)?; + let compiler_path = resolve_required_tool("FPARKAN_GLSLANG_VALIDATOR", &manifest.compiler)?; + let validator_path = resolve_required_tool("FPARKAN_SPIRV_VAL", &manifest.validator)?; + + verify_tool_metadata(&compiler_path, &manifest.compiler)?; + verify_tool_metadata(&validator_path, &manifest.validator)?; + + let out_dir = shader_provenance_output_dir(); + if out_dir.exists() { + fs::remove_dir_all(&out_dir).map_err(|err| format!("{}: {err}", out_dir.display()))?; + } + fs::create_dir_all(&out_dir).map_err(|err| format!("{}: {err}", out_dir.display()))?; + + for module in &manifest.modules { + verify_shader_module(&manifest, module, &compiler_path, &validator_path, &out_dir)?; + } + Ok(()) +} + +fn load_shader_manifest(path: &Path) -> Result<ShaderManifestJson, String> { + let text = fs::read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?; + let manifest = serde_json::from_str::<ShaderManifestJson>(&text) + .map_err(|err| format!("{}: invalid shader manifest JSON: {err}", path.display()))?; + if manifest.modules.is_empty() { + return Err(format!( + "{}: shader manifest must describe at least one module", + path.display() + )); + } + Ok(manifest) +} + +fn resolve_required_tool( + env_var: &str, + manifest: &ShaderToolManifestJson, +) -> Result<PathBuf, String> { + let requested = std::env::var(env_var).unwrap_or_else(|_| manifest.name.clone()); + resolve_tool_path(&requested).ok_or_else(|| { + format!( + "required shader tool {} is unavailable (set {env_var} to override path)", + manifest.name + ) + }) +} + +fn resolve_tool_path(tool: &str) -> Option<PathBuf> { + let candidate = Path::new(tool); + if candidate.components().count() > 1 { + return candidate.is_file().then(|| candidate.to_path_buf()); + } + let output = Command::new("which").arg(tool).output().ok()?; + if !output.status.success() { + return None; + } + let resolved = String::from_utf8(output.stdout).ok()?; + let resolved = resolved.trim(); + (!resolved.is_empty()).then(|| PathBuf::from(resolved)) +} + +fn verify_tool_metadata(path: &Path, manifest: &ShaderToolManifestJson) -> Result<(), String> { + let actual_name = path + .file_name() + .and_then(|value| value.to_str()) + .ok_or_else(|| format!("{}: invalid tool filename", path.display()))?; + if actual_name != manifest.name { + return Err(format!( + "{}: tool name mismatch, expected {}, found {}", + path.display(), + manifest.name, + actual_name + )); + } + let actual_version = tool_version(path)?; + if actual_version != manifest.version { + return Err(format!( + "{}: tool version mismatch, expected {:?}, found {:?}", + path.display(), + manifest.version, + actual_version + )); + } + let actual_sha256 = sha256_file(path)?; + if actual_sha256 != manifest.binary_sha256 { + return Err(format!( + "{}: tool SHA-256 mismatch, expected {}, found {}", + path.display(), + manifest.binary_sha256, + actual_sha256 + )); + } + Ok(()) +} + +fn tool_version(path: &Path) -> Result<String, String> { + let output = Command::new(path) + .arg("--version") + .output() + .map_err(|err| format!("{} --version: {err}", path.display()))?; + if !output.status.success() { + return Err(format!( + "{} --version exited with {}", + path.display(), + output.status + )); + } + let stdout = String::from_utf8(output.stdout) + .map_err(|err| format!("{} --version: invalid UTF-8: {err}", path.display()))?; + stdout + .lines() + .find(|line| !line.trim().is_empty()) + .map(str::trim) + .map(|line| { + line.strip_prefix("Glslang Version: ") + .unwrap_or(line) + .to_string() + }) + .ok_or_else(|| format!("{} --version returned no version line", path.display())) +} + +fn verify_shader_module( + manifest: &ShaderManifestJson, + module: &ShaderModuleManifestJson, + compiler_path: &Path, + validator_path: &Path, + out_dir: &Path, +) -> Result<(), String> { + let source_path = workspace_relative_path(&module.source_path); + let checked_in_spirv_path = workspace_relative_path(&module.spirv_path); + let generated_spirv_path = out_dir.join(format!("{}.spv", module.name)); + + let source_sha256 = sha256_file(&source_path)?; + if source_sha256 != module.source_sha256 { + return Err(format!( + "{}: source SHA-256 mismatch, expected {}, found {}", + source_path.display(), + module.source_sha256, + source_sha256 + )); + } + + let checked_in_spirv_sha256 = sha256_file(&checked_in_spirv_path)?; + if checked_in_spirv_sha256 != module.sha256 { + return Err(format!( + "{}: checked-in SPIR-V SHA-256 mismatch, expected {}, found {}", + checked_in_spirv_path.display(), + module.sha256, + checked_in_spirv_sha256 + )); + } + + compile_shader_module( + compiler_path, + &manifest.target_env, + module, + &source_path, + &generated_spirv_path, + )?; + validate_shader_module(validator_path, &manifest.target_env, &generated_spirv_path)?; + + let generated_spirv_sha256 = sha256_file(&generated_spirv_path)?; + if generated_spirv_sha256 != module.sha256 { + return Err(format!( + "{}: generated SPIR-V SHA-256 mismatch, expected {}, found {}", + generated_spirv_path.display(), + module.sha256, + generated_spirv_sha256 + )); + } + Ok(()) +} + +fn compile_shader_module( + compiler_path: &Path, + target_env: &str, + module: &ShaderModuleManifestJson, + source_path: &Path, + output_path: &Path, +) -> Result<(), String> { + let stage = glslang_stage(&module.stage).ok_or_else(|| { + format!( + "{}: unsupported shader stage {:?}", + source_path.display(), + module.stage + ) + })?; + let output = Command::new(compiler_path) + .args(["-V", "--target-env", target_env, "-S", stage, "-e"]) + .arg(&module.entry_point) + .arg(source_path) + .arg("-o") + .arg(output_path) + .output() + .map_err(|err| { + format!( + "{}: shader compile failed to start: {err}", + source_path.display() + ) + })?; + if !output.status.success() { + return Err(format!( + "{}: shader compile failed:\n{}{}", + source_path.display(), + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + Ok(()) +} + +fn validate_shader_module( + validator_path: &Path, + target_env: &str, + module_path: &Path, +) -> Result<(), String> { + let output = Command::new(validator_path) + .args(["--target-env", target_env]) + .arg(module_path) + .output() + .map_err(|err| { + format!( + "{}: shader validation failed to start: {err}", + module_path.display() + ) + })?; + if !output.status.success() { + return Err(format!( + "{}: shader validation failed:\n{}{}", + module_path.display(), + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + Ok(()) +} + +fn glslang_stage(stage: &str) -> Option<&'static str> { + match stage { + "vertex" => Some("vert"), + "fragment" => Some("frag"), + _ => None, + } +} + +fn sha256_file(path: &Path) -> Result<String, String> { + for command in [&["shasum", "-a", "256"][..], &["sha256sum"][..]] { + let mut process = Command::new(command[0]); + process.args(&command[1..]).arg(path); + let Ok(output) = process.output() else { + continue; + }; + if !output.status.success() { + continue; + } + let stdout = String::from_utf8(output.stdout) + .map_err(|err| format!("{}: invalid checksum output: {err}", path.display()))?; + if let Some(sum) = stdout.split_whitespace().next() { + return Ok(sum.to_string()); + } + } + Err(format!( + "{}: could not compute SHA-256 (tried shasum and sha256sum)", + path.display() + )) +} + +fn shader_provenance_output_dir() -> PathBuf { + let nonce = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + workspace_root_path() + .join("target") + .join("fparkan") + .join("shader-provenance") + .join(format!("{}-{}", std::process::id(), nonce)) +} + fn run_cargo_deny() -> Result<(), String> { validate_supply_chain_policy_config(&workspace_relative_path(SUPPLY_CHAIN_POLICY_CONFIG))?; let cargo_deny = std::env::var_os("CARGO_DENY").unwrap_or_else(|| "cargo-deny".into()); @@ -1374,6 +1657,32 @@ struct NativeSmokeAuditOptions { dir: PathBuf, } +#[derive(Debug, Deserialize)] +struct ShaderManifestJson { + target_env: String, + compiler: ShaderToolManifestJson, + validator: ShaderToolManifestJson, + modules: Vec<ShaderModuleManifestJson>, +} + +#[derive(Debug, Deserialize)] +struct ShaderToolManifestJson { + name: String, + version: String, + binary_sha256: String, +} + +#[derive(Debug, Deserialize)] +struct ShaderModuleManifestJson { + name: String, + stage: String, + entry_point: String, + source_path: String, + source_sha256: String, + spirv_path: String, + sha256: String, +} + fn parse_test_options(args: &[String], default_root: PathBuf) -> Result<TestOptions, String> { let mut options = TestOptions { stage: Stage::All, |
