diff --git a/.github/workflows/lean.yml b/.github/workflows/lean.yml new file mode 100644 index 0000000..caa53d1 --- /dev/null +++ b/.github/workflows/lean.yml @@ -0,0 +1,21 @@ +name: Lean Verification + +on: + pull_request: + paths: + - 'tzimtzum/**' + +jobs: + lean-verify: + name: Verify TzimtzumV2 + runs-on: ubuntu-latest + defaults: + run: + working-directory: tzimtzum + steps: + - uses: actions/checkout@v4 + + - uses: jdx/mise-action@v2 + + - name: Build and verify + run: make build diff --git a/.github/workflows/rocq.yml b/.github/workflows/rocq.yml new file mode 100644 index 0000000..cf4bf9b --- /dev/null +++ b/.github/workflows/rocq.yml @@ -0,0 +1,28 @@ +name: Rocq Proofs + +on: + pull_request: + paths: + - 'argus/formal/**' + +jobs: + rocq-proofs: + name: Verify Formal Proofs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: coq-community/docker-coq-action@v1 + with: + custom_image: 'rocq/rocq-prover:9.0' + before_script: | + startGroup "Fix permissions" + sudo chown -R 1000:1000 . + endGroup + script: | + startGroup "Build proofs" + cd argus/formal + coq_makefile -f _CoqProject -o Makefile.coq + make -f Makefile.coq -j2 + endGroup + uninstall: '' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..41362a5 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,38 @@ +name: Rust CI + +on: + pull_request: + +jobs: + rust-ci: + name: Build, Lint & Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: argus + steps: + - uses: actions/checkout@v4 + + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: jdx/mise-action@v2 + + - name: Install Rust components + run: rustup component add rustfmt clippy + + - name: Check formatting + run: cargo fmt --check + + - name: Clippy + run: cargo clippy --workspace -- -D warnings + + - name: Run tests + run: cargo test --workspace + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Security audit + run: cargo audit diff --git a/.gitignore b/.gitignore index d3cc93f..6fcd16b 100644 --- a/.gitignore +++ b/.gitignore @@ -89,10 +89,6 @@ Temporary Items debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/README.md b/README.md new file mode 100644 index 0000000..4bc17e4 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Einsof + +> Work in progress. + +Einsof is a security authorization system for LLM tool execution. It addresses the +[Lethal Trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) -- the +combination of private data, untrusted content, and external communication that enables +data exfiltration through indirect prompt injection. + +The system enforces authorization at tool boundaries so that a confused (prompt-injected) +agent cannot reach egress channels when it carries taint from private data. + +## Components + +### [Tzimtzum](tzimtzum/) -- Formal Specification + +The TzimtzumV2 protocol, written in Lean 4 using the +[Veil](https://github.com/verse-lab/veil) verification framework. All 7 safety properties +and 12 strengthening invariants are verified automatically via push-button SMT + +### [Argus](argus/) -- Rust Implementation + +A tool authorization gateway implementing the TzimtzumV2 protocol. Rust workspace with +10 crates covering the core state machine, policy engine, MCP server management, LLM proxy, +gRPC API, sandboxing, and more. 512+ tests across the workspace. + +### [Formal Proofs](argus/formal/) -- Rocq (Coq) Verification + +Mechanized proofs connecting the Lean specification to the Rust implementation. Three layers: +axioms (data structure interfaces), abstract specification (safety properties), and refinement +proofs (simulation relations and soundness guarantees). + +## Toolchain + +Managed via [mise](https://mise.jdx.dev/): + +| Tool | Version | +|-------|---------| +| Rust | 1.93.0 | +| Lean | 4.27.0 | +| Rocq | 9.0 | + +## License + +MIT diff --git a/argus/Cargo.lock b/argus/Cargo.lock new file mode 100644 index 0000000..39860f7 --- /dev/null +++ b/argus/Cargo.lock @@ -0,0 +1,5552 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "arc-swap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "argus" +version = "0.1.0" +dependencies = [ + "anyhow", + "argus-analysis", + "argus-audit", + "argus-config", + "argus-gateway", + "argus-kernel", + "argus-oracle", + "argus-registry", + "argus-sandbox", + "chrono", + "clap", + "gix", + "reqwest", + "serde", + "serde_json", + "tempfile", + "tokio", + "toml", + "tonic", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "argus-analysis" +version = "0.1.0" +dependencies = [ + "argus-config", + "argus-kernel", + "async-trait", + "content_inspector", + "grep-matcher", + "grep-regex", + "grep-searcher", + "ignore", + "reqwest", + "rig-core", + "schemars 1.2.1", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "toml", + "tracing", +] + +[[package]] +name = "argus-audit" +version = "0.1.0" +dependencies = [ + "argus-kernel", +] + +[[package]] +name = "argus-config" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "toml", +] + +[[package]] +name = "argus-gateway" +version = "0.1.0" +dependencies = [ + "argus-audit", + "argus-kernel", + "argus-oracle", + "argus-registry", + "argus-sandbox", + "bytes", + "futures", + "http-body-util", + "hyper", + "hyper-util", + "libc", + "prost", + "reqwest", + "rig-core", + "rmcp", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tracing", + "uuid", +] + +[[package]] +name = "argus-kernel" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "argus-oracle" +version = "0.1.0" +dependencies = [ + "argus-kernel", + "cedar-policy", + "secretscan", + "serde", + "serde_json", + "tempfile", + "thiserror", + "toml", + "tracing", +] + +[[package]] +name = "argus-registry" +version = "0.1.0" +dependencies = [ + "argus-config", + "chrono", + "hex", + "notify", + "rusqlite", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "toml", + "tracing", + "uuid", +] + +[[package]] +name = "argus-sandbox" +version = "0.1.0" +dependencies = [ + "async-trait", + "secrecy", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "toml", +] + +[[package]] +name = "argus-sandbox-exec" +version = "0.1.0" +dependencies = [ + "argus-sandbox", + "http-body-util", + "hyper", + "hyper-util", + "landlock", + "libc", + "serde_json", + "serial_test", + "tokio", +] + +[[package]] +name = "argus-sentinel" +version = "0.1.0" +dependencies = [ + "argus-kernel", + "serde", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "as-any" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "cfg_aliases", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cedar-policy" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55625387d203085efb7dca8eb594188d586c99f97a83cf577d60bc588b1c705" +dependencies = [ + "cedar-policy-core", + "cedar-policy-formatter", + "itertools", + "linked-hash-map", + "miette", + "ref-cast", + "semver", + "serde", + "serde_json", + "serde_with", + "smol_str", + "thiserror", +] + +[[package]] +name = "cedar-policy-core" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ac16501266418b913a4ee73d1d14542d479c73baf7fccea2542996a2a82fe2" +dependencies = [ + "chrono", + "educe", + "either", + "itertools", + "lalrpop", + "lalrpop-util", + "linked-hash-map", + "linked_hash_set", + "miette", + "nonempty", + "ref-cast", + "regex", + "rustc-literal-escaper", + "serde", + "serde_json", + "serde_with", + "smol_str", + "stacker", + "thiserror", + "unicode-security", +] + +[[package]] +name = "cedar-policy-formatter" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f4b40c3d8a88264578fd9ef17b43ef35ec36e1283ae22930542dff8a6b80ae" +dependencies = [ + "cedar-policy-core", + "itertools", + "logos", + "miette", + "pretty", + "regex", + "smol_str", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "content_inspector" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +dependencies = [ + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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 = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gix" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01237e8d3d78581f71642be8b0c2ae8c0b2b5c251c9c5d9ebbea3c1ea280dce8" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-state", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-actor" +version = "0.35.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "987a51a7e66db6ef4dc030418eb2a42af6b913a79edd8670766122d8af3ba59e" +dependencies = [ + "bstr", + "gix-date", + "gix-utils", + "itoa", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-attributes" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f50d813d5c2ce9463ba0c29eea90060df08e38ad8f34b8a192259f8bce5c078" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-command" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f9c425730a654835351e6da8c3c69ba1804f8b8d4e96d027254151138d5c64" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05050fd6caa6c731fe3bd7f9485b3b520be062d3d139cb2626e052d6c127951" +dependencies = [ + "bstr", + "gix-chunk", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", + "winnow", +] + +[[package]] +name = "gix-config-value" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c489abb061c74b0c3ad790e24a606ef968cebab48ec673d6a891ece7d5aef64" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1c7307e36026b6088e5b12014ffe6d4f509c911ee453e22a7be4003a159c9b" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661245d045aa7c16ba4244daaabd823c562c3e45f1f25b816be2c57ee09f2171" +dependencies = [ + "bstr", + "itoa", + "jiff", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-diff" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9b43e95fe352da82a969f0c84ff860c2de3e724d93f6681fedbcd6c917f252" +dependencies = [ + "bstr", + "gix-hash", + "gix-object", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dccfe3e25b4ea46083916c56db3ba9d1e6ef6dce54da485f0463f9fc0fe1837c" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" +dependencies = [ + "bytes", + "crc32fast", + "flate2", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "prodash", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-filter" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecf004912949bbcf308d71aac4458321748ecb59f4d046830d25214208c471f1" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline-blocking", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-glob" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +dependencies = [ + "gix-hash", + "hashbrown 0.14.5", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae358c3c96660b10abc7da63c06788dfded603e717edbd19e38c6477911b71c8" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.14.5", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-negotiate" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" +dependencies = [ + "bitflags 2.10.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.49.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-odb" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868f703905fdbcfc1bd750942f82419903ecb7039f5288adb5206d6de405e0c9" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d49c55d69c8449f2a0a5a77eb9cbacfebb6b0e2f1215f0fc23a4cb60528a450" +dependencies = [ + "clru", + "gix-chunk", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "memmap2", + "parking_lot", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-packetline" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64286a8b5148e76ab80932e72762dd27ccf6169dd7a134b027c8a262a8262fcf" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-packetline-blocking" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c59c3ad41e68cb38547d849e9ef5ccfc0d00f282244ba1441ae856be54d001" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror", +] + +[[package]] +name = "gix-pathspec" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" +dependencies = [ + "bitflags 2.10.0", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + +[[package]] +name = "gix-prompt" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868e6516dfa16fdcbc5f8c935167d085f2ae65ccd4c9476a4319579d12a69d8d" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c17d78bb0414f8d60b5f952196dc2e47ec320dca885de9128ecdb4a0e38401" +dependencies = [ + "bstr", + "gix-credentials", + "gix-date", + "gix-features", + "gix-hash", + "gix-lock", + "gix-negotiate", + "gix-object", + "gix-ref", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-quote" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" +dependencies = [ + "bstr", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-refspec" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445ed14e3db78e8e79980085e3723df94e1c8163b3ae5bc8ed6a8fe6cf983b42" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" +dependencies = [ + "bstr", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc756b73225bf005ddeb871d1ca7b3c33e2417d0d53e56effa5a36765b52b28" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" +dependencies = [ + "bitflags 2.10.0", + "gix-path", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "gix-shallow" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9a6f6e34d6ede08f522d89e5c7990b4f60524b8ae6ebf8e850963828119ad4" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror", +] + +[[package]] +name = "gix-submodule" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f51472f05a450cc61bc91ed2f62fb06e31e2bbb31c420bc4be8793f26c8b0c1" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" + +[[package]] +name = "gix-transport" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe22ba26d4b65c17879f12b9882eafe65d3c8611c933b272fce2c10f546f59" +dependencies = [ + "base64 0.22.1", + "bstr", + "gix-command", + "gix-credentials", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "reqwest", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" +dependencies = [ + "bitflags 2.10.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a1ad0b04a5718b5cb233e6888e52a9b627846296161d81dcc5eb9203ec84b8" +dependencies = [ + "bstr", + "gix-features", + "gix-path", + "percent-encoding", + "thiserror", + "url", +] + +[[package]] +name = "gix-utils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "gix-worktree" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f1916f8d928268300c977d773dd70a8746b646873b77add0a34876a8c847e9" +dependencies = [ + "bstr", + "gix-attributes", + "gix-features", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] + +[[package]] +name = "gix-worktree-state" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81e31496d034dbdac87535b0b9d4659dbbeabaae1045a0dce7c69b5d16ea7d6" +dependencies = [ + "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", + "thiserror", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep-matcher" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d7b71093325ab22d780b40d7df3066ae4aebb518ba719d38c697a8228a8023" +dependencies = [ + "memchr", +] + +[[package]] +name = "grep-regex" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce0c256c3ad82bcc07b812c15a45ec1d398122e8e15124f96695234db7112ef" +dependencies = [ + "bstr", + "grep-matcher", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep-searcher" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac63295322dc48ebb20a25348147905d816318888e64f531bfc2a2bc0577dc34" +dependencies = [ + "bstr", + "encoding_rs", + "encoding_rs_io", + "grep-matcher", + "log", + "memchr", + "memmap2", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.2", + "web-time", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.10.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph 0.7.1", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + +[[package]] +name = "landlock" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fefd6652c57d68aaa32544a4c0e642929725bdc1fd929367cdeb673ab81088" +dependencies = [ + "enumflags2", + "libc", + "thiserror", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.10.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +dependencies = [ + "serde", +] + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logos" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970" +dependencies = [ + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "serde", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" +dependencies = [ + "serde", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.10.0", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.13.0", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.13.0", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" +dependencies = [ + "arrayvec", + "typed-arena", + "unicode-width 0.2.2", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "process-wrap" +version = "9.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd9713fe2c91c3c85ac388b31b89de339365d2c995146e630b5e0da9d06526a" +dependencies = [ + "futures", + "indexmap 2.13.0", + "nix", + "tokio", + "tracing", + "windows", +] + +[[package]] +name = "prodash" +version = "29.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +dependencies = [ + "log", + "parking_lot", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph 0.8.3", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +dependencies = [ + "bitflags 2.10.0", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "rig-core" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7207790134ee24d87ac3d022c308e1a7c871219d139acf70d13be76c1f6919c5" +dependencies = [ + "as-any", + "async-stream", + "base64 0.22.1", + "bytes", + "eventsource-stream", + "fastrand", + "futures", + "futures-timer", + "glob", + "http", + "mime", + "mime_guess", + "ordered-float", + "pin-project-lite", + "reqwest", + "rmcp", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmcp" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1815dbc06c414d720f8bc1951eccd66bc99efc6376331f1e7093a119b3eb508" +dependencies = [ + "async-trait", + "base64 0.22.1", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "process-wrap", + "rmcp-macros", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f0bc7008fa102e771a76c6d2c9b253be3f2baa5964e060464d038ae1cbc573" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "rusqlite" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" +dependencies = [ + "bitflags 2.10.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-literal-escaper" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be87abb9e40db7466e0681dc8ecd9dcfd40360cb10b4c8fe24a7c4c3669b198" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "secretscan" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb992fd9340ecc63dd03fe5a73fb3d88f955a14410a0f9dd8959729a28744f1" +dependencies = [ + "base64 0.21.7", + "clap", + "colored", + "hex", + "ignore", + "indicatif", + "lazy_static", + "rayon", + "regex", + "serde", + "serde_json", + "url", + "walkdir", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serial_test" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[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 = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "term" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tonic" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tonic-prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.13.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "futures", + "futures-task", + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-security" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50" +dependencies = [ + "unicode-normalization", + "unicode-script", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[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 = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/argus/Cargo.toml b/argus/Cargo.toml new file mode 100644 index 0000000..96a046b --- /dev/null +++ b/argus/Cargo.toml @@ -0,0 +1,95 @@ +[workspace] +resolver = "2" +members = [ + "crates/*", + "argus-cli", + "argus-sandbox-exec", +] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" +repository = "https://github.com/edlon/einsof" + +[workspace.dependencies] +# Internal crates +argus-kernel = { path = "crates/argus-kernel" } +argus-config = { path = "crates/argus-config" } +argus-registry = { path = "crates/argus-registry" } +argus-sandbox = { path = "crates/argus-sandbox", default-features = false } +argus-oracle = { path = "crates/argus-oracle" } +argus-audit = { path = "crates/argus-audit" } +argus-analysis = { path = "crates/argus-analysis" } +argus-gateway = { path = "crates/argus-gateway" } +argus-sentinel = { path = "crates/argus-sentinel" } + +# Shared dependencies +im = { version = "15", features = ["serde"] } +tokio = { version = "1", default-features = false } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +toml = "0.9" +thiserror = "2" +anyhow = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +chrono = { version = "0.4", features = ["serde"] } +sha2 = "0.10" +hex = "0.4" +uuid = { version = "1", features = ["v4", "v7"] } +notify = { version = "8", default-features = false, features = ["macos_kqueue"] } +rusqlite = { version = "0.34", features = ["bundled"] } + +# LLM / Analysis +async-trait = "0.1" +rig-core = { version = "0.29", features = ["rmcp"] } + +# MCP transport (version must match rig-core's rmcp dependency) +rmcp = { version = "0.13", features = ["client", "transport-child-process"] } +schemars = "1" + +# Code search / inspection +ignore = "0.4" +grep-searcher = "0.1" +grep-regex = "0.1" +grep-matcher = "0.1" +content_inspector = "0.2" + +# gRPC +tonic = "0.14" +tonic-prost = "0.14" +prost = "0.14" +tokio-stream = "0.1" + +# HTTP / Proxy +hyper = { version = "1", features = ["server", "http1"] } +hyper-util = { version = "0.1", features = ["tokio"] } +http-body-util = "0.1" +reqwest = { version = "0.12", features = ["json", "stream"] } +bytes = "1" +futures = "0.3" + +# Policy engine +cedar-policy = "4.9" +secretscan = "0.2" + +# Git +gix = { version = "0.72", default-features = false, features = ["blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "worktree-mutation"] } + +# CLI +clap = { version = "4", features = ["derive"] } + +# Sandbox +libc = "0.2" +secrecy = { version = "0.10", features = ["serde"] } +landlock = "0.4" + +# Dev +tempfile = "3" +serial_test = "3" + +[profile.release] +lto = "thin" +codegen-units = 1 +strip = true diff --git a/argus/README.md b/argus/README.md new file mode 100644 index 0000000..7359518 --- /dev/null +++ b/argus/README.md @@ -0,0 +1,83 @@ +# Argus + +> Work in progress. + +Argus is a tool authorization gateway implementing the +[TzimtzumV2](../tzimtzum/) protocol. It acts as a local security daemon and universal +tool execution proxy for LLM agents, enforcing data-flow policies at tool boundaries +to prevent exfiltration via indirect prompt injection. + +## Architecture + +```mermaid +graph TD + CLI[argus-cli] --> GW[argus-gateway] + GW --> Oracle[argus-oracle] + GW --> Audit[argus-audit] + GW --> Sandbox[argus-sandbox] + GW --> Registry[argus-registry] + GW --> Analysis[argus-analysis] + Registry --> Core[argus-core] + Registry --> Config[argus-config] + Analysis --> Core + Analysis --> Config + Kernel[argus-kernel] + + style Kernel stroke-dasharray: 5 5 +``` + +`argus-kernel` is standalone with zero workspace dependencies (rocq-of-rust compatible). + +### Crates + +| Crate | Description | +|-------|-------------| +| **argus-kernel** | Pure functional state machine implementing 9 TzimtzumV2 transitions. Zero workspace dependencies, compatible with [rocq-of-rust](https://github.com/formal-land/rocq-of-rust) extraction. Uses `BTreeMap`/`BTreeSet` exclusively. | +| **argus-gateway** | Main orchestration layer. Owns the kernel, manages MCP servers, handles gRPC serving, and coordinates the LLM API proxy. | +| **argus-oracle** | Policy engine backed by [Cedar](https://www.cedarpolicy.com/). Implements `AuthorizerOracle` for fine-grained tool authorization decisions. | +| **argus-registry** | TOML-based tool registry with git sync, cryptographic attestation, quarantine management, and SQLite persistence. | +| **argus-analysis** | LLM-powered security analysis using [rig-core](https://github.com/0xPlaygrounds/rig). Two-phase agentic pipeline (tool use + structured extraction). | +| **argus-sandbox** | Process sandboxing primitives. Landlock (Linux), seatbelt (macOS). Secret isolation via provider trait. | +| **argus-audit** | Event store and observability. OpenTelemetry integration for audit trails. | +| **argus-config** | Shared configuration types and loading. | +| **argus-core** | Shared domain types, capability catalog, and integrity levels. | +| **argus-cli** | CLI binary (`argus serve`, `argus analyze`). Daemon mode with MCP server lifecycle management. | + +### Three Ingress Faces + +1. **MCP host** -- Argus spawns sandboxed MCP servers and proxies tool calls through the invoke gate +2. **LLM API proxy** -- Transparent HTTP reverse proxy intercepting tool calls in LLM responses +3. **gRPC API** -- Direct access to the 9 kernel transitions for agent frameworks + +### Kernel Transitions + +The core state machine is pure functional -- each transition takes an immutable state and +returns a new state plus an event: + +``` +fn(KernelState, &BackgroundTheory, ...) -> Result<(KernelState, KernelEvent), KernelError> +``` + +The 9 transitions implement the TzimtzumV2 protocol: `register_tool`, `delegate`, +`revoke`, `cascade_revoke`, `invoke_start`, `invoke_complete`, `return_endorsed`, +`return_unendorsed`, `egress_check`. + +## Formal Verification + +The `formal/` directory contains Rocq (Coq) mechanized proofs organized in three layers: + +- **axioms/** -- BTree data structure interfaces, decidability assumptions, oracle behavior +- **spec/** -- Abstract TzimtzumV2 specification with safety property proofs +- **refinement/** -- Simulation relations and soundness proofs connecting spec to implementation + +The `extracted/` directory contains [rocq-of-rust](https://github.com/formal-land/rocq-of-rust) +output from `argus-kernel`, used as reference for the refinement proofs. + +## Building + +```bash +cargo build # debug +cargo build --release # release (thin LTO, stripped) +cargo test # all tests (~512) +cargo clippy --workspace # lint +``` diff --git a/argus/argus-cli/Cargo.toml b/argus/argus-cli/Cargo.toml new file mode 100644 index 0000000..2f660da --- /dev/null +++ b/argus/argus-cli/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "argus" +version.workspace = true +edition.workspace = true +license.workspace = true + +[[bin]] +name = "argus" +path = "src/main.rs" + +[dependencies] +argus-analysis = { workspace = true } +argus-gateway = { workspace = true } +argus-registry = { workspace = true } +argus-kernel = { workspace = true } +argus-oracle = { workspace = true } +argus-audit = { workspace = true } +argus-sandbox = { workspace = true, features = ["runtime"] } +clap = { workspace = true } +gix = { workspace = true } +anyhow = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tonic = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +argus-config = { workspace = true } +chrono = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tempfile = { workspace = true } +toml = { workspace = true } + +[dev-dependencies] +reqwest = { workspace = true } diff --git a/argus/argus-cli/src/config.rs b/argus/argus-cli/src/config.rs new file mode 100644 index 0000000..e32f92e --- /dev/null +++ b/argus/argus-cli/src/config.rs @@ -0,0 +1,287 @@ +use std::collections::HashMap; +use std::path::Path; + +use anyhow::Context; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct DaemonConfig { + pub server: ServerConfig, + pub grpc: Option, + pub proxy: Option, +} + +#[derive(Debug, Deserialize)] +pub struct ServerConfig { + pub registry_path: String, + pub policy_path: String, + #[serde(default = "default_log_level")] + pub log_level: String, + #[serde(default = "default_shutdown_timeout")] + pub shutdown_timeout_secs: u64, +} + +#[derive(Debug, Deserialize)] +pub struct GrpcConfig { + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default = "default_grpc_address")] + pub address: String, +} + +#[derive(Debug, Deserialize)] +pub struct ProxyCfg { + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default = "default_proxy_address")] + pub address: String, + #[serde(default = "default_max_tool_rounds")] + pub max_tool_rounds: usize, + #[serde(default)] + pub upstreams: HashMap, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum UpstreamFormat { + Openai, + Anthropic, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UpstreamEntry { + pub url: String, + pub format: UpstreamFormat, + #[serde(default)] + #[allow(dead_code)] + pub api_key_env: Option, +} + +fn default_log_level() -> String { + "info".into() +} + +fn default_shutdown_timeout() -> u64 { + 5 +} + +fn default_true() -> bool { + true +} + +fn default_grpc_address() -> String { + "127.0.0.1:9090".into() +} + +fn default_proxy_address() -> String { + "127.0.0.1:8080".into() +} + +fn default_max_tool_rounds() -> usize { + 25 +} + +impl DaemonConfig { + pub fn load(path: &Path) -> anyhow::Result { + let content = std::fs::read_to_string(path) + .with_context(|| format!("failed to read config: {}", path.display()))?; + let config: Self = toml::from_str(&content) + .with_context(|| format!("failed to parse config: {}", path.display()))?; + config.validate()?; + Ok(config) + } + + fn validate(&self) -> anyhow::Result<()> { + let grpc_on = self.grpc.as_ref().is_some_and(|g| g.enabled); + let proxy_on = self.proxy.as_ref().is_some_and(|p| p.enabled); + anyhow::ensure!( + grpc_on || proxy_on, + "at least one listener (grpc or proxy) must be enabled" + ); + Ok(()) + } +} + +pub fn expand_tilde(s: &str) -> String { + if let Some(rest) = s.strip_prefix("~/") + && let Ok(home) = std::env::var("HOME") + { + return format!("{home}/{rest}"); + } + s.to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + fn write_config(dir: &Path, content: &str) -> std::path::PathBuf { + let path = dir.join("argus.toml"); + fs::write(&path, content).unwrap(); + path + } + + #[test] + fn parses_full_config() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/registry" +policy_path = "/tmp/policies" + +[grpc] +enabled = true +address = "127.0.0.1:9090" + +[proxy] +enabled = true +address = "127.0.0.1:8080" +max_tool_rounds = 30 + +[proxy.upstreams.openai] +url = "https://api.openai.com" +format = "openai" +api_key_env = "OPENAI_API_KEY" + +[proxy.upstreams.anthropic] +url = "https://api.anthropic.com" +format = "anthropic" +"#, + ); + let config = DaemonConfig::load(&path).unwrap(); + assert_eq!(config.server.registry_path, "/tmp/registry"); + assert_eq!(config.server.policy_path, "/tmp/policies"); + assert!(config.grpc.as_ref().unwrap().enabled); + let proxy = config.proxy.as_ref().unwrap(); + assert_eq!(proxy.max_tool_rounds, 30); + assert_eq!(proxy.upstreams.len(), 2); + } + + #[test] + fn applies_defaults() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/reg" +policy_path = "/tmp/pol" + +[grpc] +"#, + ); + let config = DaemonConfig::load(&path).unwrap(); + assert_eq!(config.server.log_level, "info"); + assert_eq!(config.server.shutdown_timeout_secs, 5); + let grpc = config.grpc.as_ref().unwrap(); + assert!(grpc.enabled); + assert_eq!(grpc.address, "127.0.0.1:9090"); + } + + #[test] + fn validates_no_listeners_enabled() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/reg" +policy_path = "/tmp/pol" + +[grpc] +enabled = false + +[proxy] +enabled = false +"#, + ); + let result = DaemonConfig::load(&path); + assert!(result.is_err()); + let msg = result.unwrap_err().to_string(); + assert!(msg.contains("at least one listener")); + } + + #[test] + fn validates_no_listener_sections() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/reg" +policy_path = "/tmp/pol" +"#, + ); + let result = DaemonConfig::load(&path); + assert!(result.is_err()); + } + + #[test] + fn grpc_only_config_valid() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/reg" +policy_path = "/tmp/pol" + +[grpc] +enabled = true +"#, + ); + let config = DaemonConfig::load(&path).unwrap(); + assert!(config.grpc.is_some()); + assert!(config.proxy.is_none()); + } + + #[test] + fn proxy_only_config_valid() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config( + tmp.path(), + r#" +[server] +registry_path = "/tmp/reg" +policy_path = "/tmp/pol" + +[proxy] +enabled = true + +[proxy.upstreams.openai] +url = "https://api.openai.com" +format = "openai" +"#, + ); + let config = DaemonConfig::load(&path).unwrap(); + assert!(config.grpc.is_none()); + assert!(config.proxy.is_some()); + } + + #[test] + fn invalid_toml_returns_error() { + let tmp = tempfile::tempdir().unwrap(); + let path = write_config(tmp.path(), "this is not valid toml [[["); + assert!(DaemonConfig::load(&path).is_err()); + } + + #[test] + fn missing_file_returns_error() { + assert!(DaemonConfig::load(Path::new("/nonexistent/argus.toml")).is_err()); + } + + #[test] + fn expand_tilde_with_home() { + let result = expand_tilde("~/foo/bar"); + assert!(!result.starts_with('~')); + assert!(result.ends_with("foo/bar")); + } + + #[test] + fn expand_tilde_no_tilde() { + assert_eq!(expand_tilde("/absolute/path"), "/absolute/path"); + } +} diff --git a/argus/argus-cli/src/git.rs b/argus/argus-cli/src/git.rs new file mode 100644 index 0000000..deb486a --- /dev/null +++ b/argus/argus-cli/src/git.rs @@ -0,0 +1,43 @@ +use std::num::NonZeroU32; +use std::path::Path; + +use anyhow::{Context, Result}; +use gix::remote::fetch::Shallow; + +pub fn clone_repo(url: &str, git_ref: Option<&str>, dest: &Path) -> Result<()> { + let mut prepare = gix::prepare_clone(url, dest) + .context("Failed to prepare clone")? + .with_shallow(Shallow::DepthAtRemote( + NonZeroU32::new(1).expect("1 is non-zero"), + )); + + if let Some(r) = git_ref { + prepare = prepare.with_ref_name(Some(r))?; + } + + let (mut checkout, _outcome) = prepare + .fetch_then_checkout(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) + .context("Failed to fetch repository")?; + + checkout + .main_worktree(gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED) + .context("Failed to checkout worktree")?; + + Ok(()) +} + +pub fn head_commit(repo_path: &Path) -> Result { + let repo = gix::open(repo_path).context("Failed to open repository")?; + let id = repo.head_id().context("Failed to resolve HEAD")?; + Ok(id.to_string()) +} + +pub fn resolve_argus_home() -> std::path::PathBuf { + if let Ok(home) = std::env::var("ARGUS_HOME") { + return std::path::PathBuf::from(home); + } + if let Ok(home) = std::env::var("HOME") { + return std::path::PathBuf::from(home).join(".argus"); + } + std::path::PathBuf::from(".argus") +} diff --git a/argus/argus-cli/src/main.rs b/argus/argus-cli/src/main.rs new file mode 100644 index 0000000..66ba01e --- /dev/null +++ b/argus/argus-cli/src/main.rs @@ -0,0 +1,209 @@ +mod config; +mod git; +mod registry_add; +mod registry_check; +mod registry_update; +mod serve; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "argus", about = "Tool Authorization Gateway for LLM APIs")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + Serve { + #[arg(long, default_value = "argus.toml")] + config: String, + }, + Analyze { + path: String, + }, + #[command(subcommand)] + Registry(RegistryCommands), + #[command(subcommand)] + Audit(AuditCommands), +} + +#[derive(Subcommand)] +enum RegistryCommands { + List, + /// Analyze a tool repository and add it to the local registry + Add { + /// Repository URL to analyze + url: String, + /// Pin to a specific git ref (tag, branch, commit) + #[arg(long)] + git_ref: Option, + /// Enable drift tracking for this tool + #[arg(long)] + track: bool, + /// Run analysis without writing files + #[arg(long)] + dry_run: bool, + /// Only produce the analysis report, no TOML entry + #[arg(long)] + report_only: bool, + /// LLM provider (openrouter, ollama, openai, anthropic, gemini) + #[arg(long, default_value = "openrouter")] + provider: String, + /// Model name for the LLM provider + #[arg(long, default_value = "anthropic/claude-sonnet-4")] + model: String, + /// Maximum LLM agent turns + #[arg(long, default_value = "15")] + max_turns: usize, + /// Maximum tokens per LLM response + #[arg(long, default_value = "16384")] + max_tokens: u64, + }, + /// Re-analyze a registered tool and update its registry entry (fetches latest HEAD) + Update { + /// Name of the registered tool to update + name: String, + /// LLM provider (openrouter, ollama, openai, anthropic, gemini) + #[arg(long, default_value = "openrouter")] + provider: String, + /// Model name for the LLM provider + #[arg(long, default_value = "anthropic/claude-sonnet-4")] + model: String, + /// Maximum LLM agent turns + #[arg(long, default_value = "15")] + max_turns: usize, + /// Maximum tokens per LLM response + #[arg(long, default_value = "16384")] + max_tokens: u64, + }, + /// Check tracked tools for upstream drift (compares HEAD vs pinned ref) + Check, + Sync, +} + +#[derive(Subcommand)] +enum AuditCommands { + Query { + #[arg(long)] + agent: Option, + #[arg(long)] + action: Option, + #[arg(long)] + since: Option, + }, + Replay { + #[arg(long)] + from: Option, + }, +} + +fn build_provider_config( + provider: &str, + model: &str, +) -> anyhow::Result { + use argus_config::ExtractionProviderConfig; + match provider { + "openrouter" => Ok(ExtractionProviderConfig::OpenRouter { + model: model.to_string(), + api_key_env: "OPENROUTER_API_KEY".to_string(), + timeout_ms: 30000, + }), + "ollama" => Ok(ExtractionProviderConfig::Ollama { + model: model.to_string(), + base_url: std::env::var("OLLAMA_BASE_URL") + .unwrap_or_else(|_| "http://localhost:11434".to_string()), + timeout_ms: 30000, + }), + "openai" => Ok(ExtractionProviderConfig::OpenAI { + model: model.to_string(), + api_key_env: "OPENAI_API_KEY".to_string(), + timeout_ms: 30000, + }), + "anthropic" => Ok(ExtractionProviderConfig::Anthropic { + model: model.to_string(), + api_key_env: "ANTHROPIC_API_KEY".to_string(), + timeout_ms: 30000, + }), + "gemini" => Ok(ExtractionProviderConfig::Gemini { + model: model.to_string(), + api_key_env: "GEMINI_API_KEY".to_string(), + timeout_ms: 30000, + }), + other => anyhow::bail!( + "Unknown provider: {other}. Use: openrouter, ollama, openai, anthropic, gemini" + ), + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Serve { config } => return serve::run_serve(&config).await, + Commands::Analyze { .. } => println!("argus analyze: not yet implemented"), + Commands::Registry(RegistryCommands::List) => { + println!("argus registry list: not yet implemented") + } + Commands::Registry(RegistryCommands::Add { + url, + git_ref, + track, + dry_run, + report_only, + provider, + model, + max_turns, + max_tokens, + }) => { + let _ = tracing_subscriber::fmt::try_init(); + let provider_config = build_provider_config(&provider, &model)?; + registry_add::run_registry_add(registry_add::RegistryAddOptions { + url, + git_ref, + track, + dry_run, + report_only, + argus_home: git::resolve_argus_home(), + provider: provider_config, + max_turns, + max_tokens, + }) + .await?; + } + Commands::Registry(RegistryCommands::Update { + name, + provider, + model, + max_turns, + max_tokens, + }) => { + let _ = tracing_subscriber::fmt::try_init(); + let provider_config = build_provider_config(&provider, &model)?; + registry_update::run_registry_update(registry_update::RegistryUpdateOptions { + name, + argus_home: git::resolve_argus_home(), + provider: provider_config, + max_turns, + max_tokens, + }) + .await?; + } + Commands::Registry(RegistryCommands::Check) => { + let _ = tracing_subscriber::fmt::try_init(); + registry_check::run_registry_check(git::resolve_argus_home()).await?; + } + Commands::Registry(RegistryCommands::Sync) => { + println!("argus registry sync: not yet implemented") + } + Commands::Audit(AuditCommands::Query { .. }) => { + println!("argus audit query: not yet implemented") + } + Commands::Audit(AuditCommands::Replay { .. }) => { + println!("argus audit replay: not yet implemented") + } + } + Ok(()) +} diff --git a/argus/argus-cli/src/registry_add.rs b/argus/argus-cli/src/registry_add.rs new file mode 100644 index 0000000..0f62186 --- /dev/null +++ b/argus/argus-cli/src/registry_add.rs @@ -0,0 +1,183 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use argus_analysis::{ + InstallDetection, ManifestKind, OrchestratorConfig, RegistryAnalysisReport, RegistryAnalyzer, + ToolContext, audit_dependencies, detect_install, +}; +use argus_config::ExtractionProviderConfig; +use argus_registry::toml_types::InstallConfig; +use argus_registry::{ + AssemblyInput, assemble_mcp_entry, generate_sandbox_config, write_registry_entry, +}; +use tracing::{info, warn}; + +use crate::git; + +pub struct RegistryAddOptions { + pub url: String, + pub git_ref: Option, + pub track: bool, + pub dry_run: bool, + pub report_only: bool, + pub argus_home: PathBuf, + pub provider: ExtractionProviderConfig, + pub max_turns: usize, + pub max_tokens: u64, +} + +pub async fn run_registry_add(opts: RegistryAddOptions) -> Result<()> { + let clone_dir = tempfile::tempdir().context("Failed to create temp directory")?; + let clone_path = clone_dir.path().to_path_buf(); + + let url = opts.url.clone(); + let git_ref = opts.git_ref.clone(); + let path = clone_path.clone(); + let pinned_ref = tokio::task::spawn_blocking(move || -> Result { + info!(url = %url, "Cloning repository"); + git::clone_repo(&url, git_ref.as_deref(), &path)?; + let commit = git::head_commit(&path)?; + info!(pinned_ref = %commit, "Pinned to commit"); + Ok(commit) + }) + .await + .context("Clone task panicked")??; + + let tool_ctx = ToolContext::new(&clone_path)?; + let manifest_kind = tool_ctx.detect_manifest().context( + "No recognized manifest found (package.json, Cargo.toml, pyproject.toml, go.mod)", + )?; + let manifest_content = tool_ctx.read_file(manifest_filename(&manifest_kind), None, None)?; + + info!("Analyzing dependencies"); + let dep_report = audit_dependencies(&manifest_kind, &manifest_content).await; + info!( + total = dep_report.total, + vulnerabilities = dep_report.vulnerabilities.len(), + flagged = dep_report.flagged.len(), + "Dependency audit complete" + ); + + let install_detection = detect_install(&manifest_kind, &manifest_content); + info!( + method = %install_detection.method, + package = %install_detection.package, + "Detected install method" + ); + + info!("Running LLM capability inference"); + let analyzer = RegistryAnalyzer::from_config(&opts.provider) + .context("Failed to initialize LLM provider")? + .with_orchestrator_config(OrchestratorConfig { + max_turns: opts.max_turns, + max_tokens: opts.max_tokens, + }); + let capabilities = analyzer + .infer_capabilities(&clone_path) + .await + .context("Capability inference failed")?; + info!(count = capabilities.len(), "Capabilities inferred"); + + for cap in &capabilities { + info!( + capability = %cap.capability, + evidence_count = cap.evidence.len(), + "Found capability" + ); + } + + let cap_strings: Vec = capabilities.iter().map(|c| c.capability.clone()).collect(); + let sandbox_config = generate_sandbox_config(&cap_strings); + let name = derive_tool_name(&opts.url); + + let report = RegistryAnalysisReport { + tool: name.clone(), + analyzed_at: chrono::Utc::now().to_rfc3339(), + pinned_ref: pinned_ref.clone(), + capabilities: capabilities.clone(), + dependencies: dep_report, + install_detection: install_detection.clone(), + }; + + let report_json = serde_json::to_string_pretty(&report)?; + + if opts.report_only { + println!("{report_json}"); + return Ok(()); + } + + let install_config = to_install_config(&install_detection); + let entry = assemble_mcp_entry(AssemblyInput { + name: name.clone(), + description: format!("Auto-analyzed from {}", opts.url), + repository: opts.url.clone(), + pinned_ref, + track: opts.track, + capabilities: cap_strings, + install: install_config, + sandbox: sandbox_config, + }); + + if opts.dry_run { + let toml_str = toml::to_string_pretty(&entry)?; + println!("{report_json}"); + println!("{toml_str}"); + return Ok(()); + } + + let registry_dir = opts.argus_home.join("local-registry"); + write_registry_entry(®istry_dir, &name, &entry, &report_json)?; + info!( + toml_path = %registry_dir.join("mcps").join(format!("{name}.toml")).display(), + report_path = %registry_dir.join("reports").join(format!("{name}.json")).display(), + "Registry entry written" + ); + + Ok(()) +} + +fn manifest_filename(kind: &ManifestKind) -> &'static str { + match kind { + ManifestKind::PackageJson => "package.json", + ManifestKind::CargoToml => "Cargo.toml", + ManifestKind::PyprojectToml => "pyproject.toml", + ManifestKind::GoMod => "go.mod", + } +} + +fn derive_tool_name(url: &str) -> String { + url.trim_end_matches('/') + .rsplit('/') + .next() + .unwrap_or("unknown") + .trim_end_matches(".git") + .to_string() +} + +fn to_install_config(detection: &InstallDetection) -> InstallConfig { + match detection.method.as_str() { + "npm" => InstallConfig::Npm { + package: detection.package.clone(), + version: detection.version.clone(), + }, + "cargo" => InstallConfig::Cargo { + crate_name: detection.package.clone(), + version: detection.version.clone(), + }, + "pip" => InstallConfig::Pip { + package: detection.package.clone(), + version: detection.version.clone(), + python_version: None, + }, + other => { + warn!( + method = other, + "No native install config for this method, using binary placeholder" + ); + InstallConfig::Binary { + url: format!("TODO: fill in binary URL for {} package", other), + checksum: "TODO: fill in checksum".to_string(), + } + } + } +} diff --git a/argus/argus-cli/src/registry_check.rs b/argus/argus-cli/src/registry_check.rs new file mode 100644 index 0000000..fd1a28c --- /dev/null +++ b/argus/argus-cli/src/registry_check.rs @@ -0,0 +1,79 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use argus_registry::toml_loader::TomlRegistry; +use tracing::{error, info, warn}; + +use crate::git; + +pub async fn run_registry_check(argus_home: PathBuf) -> Result<()> { + let registry_dir = argus_home.join("local-registry"); + let mcps_dir = registry_dir.join("mcps"); + + if !mcps_dir.exists() { + warn!(path = %mcps_dir.display(), "No registry entries found"); + return Ok(()); + } + + let registry = TomlRegistry::load_from_dir(®istry_dir).context("Failed to load registry")?; + + let tracked: Vec<_> = registry + .mcps + .iter() + .filter(|(_, entry)| entry.metadata.track.unwrap_or(false)) + .collect(); + + if tracked.is_empty() { + info!("No tracked tools found, use --track when adding to enable drift detection"); + return Ok(()); + } + + info!(count = tracked.len(), "Checking tracked tools for drift"); + + for (name, entry) in &tracked { + let repo_url = match &entry.metadata.repository { + Some(url) => url, + None => { + warn!(tool = %name, "Skipping tool with no repository URL"); + continue; + } + }; + + let pinned = entry.metadata.pinned_ref.as_deref().unwrap_or("unknown"); + + let clone_dir = tempfile::tempdir()?; + let name_owned = name.to_string(); + let url_owned = repo_url.clone(); + let dest = clone_dir.path().to_path_buf(); + + let head = match tokio::task::spawn_blocking(move || -> Result { + git::clone_repo(&url_owned, None, &dest)?; + git::head_commit(&dest) + }) + .await + .context("Clone task panicked")? + { + Ok(h) => h, + Err(e) => { + error!(tool = %name_owned, error = %e, "Failed to check tool"); + continue; + } + }; + + let short_pinned: String = pinned.chars().take(8).collect(); + let short_head: String = head.chars().take(8).collect(); + + if head == pinned { + info!(tool = %name, commit = %short_pinned, "Up to date"); + } else { + warn!( + tool = %name, + pinned = %short_pinned, + current = %short_head, + "Drift detected, run: argus registry update {name}" + ); + } + } + + Ok(()) +} diff --git a/argus/argus-cli/src/registry_update.rs b/argus/argus-cli/src/registry_update.rs new file mode 100644 index 0000000..9f3fcbf --- /dev/null +++ b/argus/argus-cli/src/registry_update.rs @@ -0,0 +1,56 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use argus_config::ExtractionProviderConfig; +use argus_registry::toml_loader::TomlRegistry; +use tracing::info; + +pub struct RegistryUpdateOptions { + pub name: String, + pub argus_home: PathBuf, + pub provider: ExtractionProviderConfig, + pub max_turns: usize, + pub max_tokens: u64, +} + +pub async fn run_registry_update(opts: RegistryUpdateOptions) -> Result<()> { + let registry_dir = opts.argus_home.join("local-registry"); + let toml_path = registry_dir + .join("mcps") + .join(format!("{}.toml", opts.name)); + + if !toml_path.exists() { + anyhow::bail!( + "No registry entry found for '{}' at {}", + opts.name, + toml_path.display() + ); + } + + let entry = TomlRegistry::load_mcp(&toml_path) + .context(format!("Failed to parse TOML for '{}'", opts.name))?; + + let repo_url = entry + .metadata + .repository + .context("Entry has no repository URL, cannot update")?; + + info!( + tool = %opts.name, + url = %repo_url, + "Re-analyzing tool from latest HEAD" + ); + + crate::registry_add::run_registry_add(crate::registry_add::RegistryAddOptions { + url: repo_url, + git_ref: None, + track: entry.metadata.track.unwrap_or(false), + dry_run: false, + report_only: false, + argus_home: opts.argus_home, + provider: opts.provider, + max_turns: opts.max_turns, + max_tokens: opts.max_tokens, + }) + .await +} diff --git a/argus/argus-cli/src/serve.rs b/argus/argus-cli/src/serve.rs new file mode 100644 index 0000000..2aeee60 --- /dev/null +++ b/argus/argus-cli/src/serve.rs @@ -0,0 +1,555 @@ +use std::collections::{BTreeSet, HashMap}; +use std::path::Path; +use std::sync::Arc; + +use anyhow::Context; +use tokio::sync::RwLock; +use tracing::info; + +use argus_audit::InMemoryEventStore; +use argus_gateway::{ + ArgusGrpcService, GatewayService, LlmFormat, LlmProxy, McpManager, McpServerConfig, + ProxyConfig, UpstreamConfig, +}; +use argus_kernel::{BackgroundTheoryBuilder, CapKind, ConfLevel, EgressKind, ToolId, ToolMetadata}; +use argus_oracle::{CedarAuthorizer, FilePolicySource, MockContentGate}; +use argus_registry::extract_env_defaults; +use argus_registry::toml_loader::TomlRegistry; +use argus_registry::toml_types::{InstallConfig, McpEntry, SandboxNetPolicy}; +use argus_sandbox::{NetPolicy, NoopSandbox, OsSandbox, ProfileBuilder, SandboxProfile}; + +use crate::config::{DaemonConfig, ProxyCfg, UpstreamFormat, expand_tilde}; + +pub async fn run_serve(config_path: &str) -> anyhow::Result<()> { + let config = DaemonConfig::load(Path::new(config_path))?; + + tracing_subscriber::fmt() + .with_env_filter(&config.server.log_level) + .init(); + + info!("starting argus daemon"); + + let registry_path = expand_tilde(&config.server.registry_path); + let toml_registry = load_registry(®istry_path)?; + let bg = build_background_theory(&toml_registry.mcps); + + let policy_path = expand_tilde(&config.server.policy_path); + let policy_dir = Path::new(&policy_path); + if !policy_dir.is_dir() { + tracing::warn!(path = %policy_path, "policy directory not found, creating empty"); + std::fs::create_dir_all(policy_dir) + .with_context(|| format!("failed to create policy dir: {policy_path}"))?; + } + let source = FilePolicySource::new(policy_dir); + let authorizer = CedarAuthorizer::new(&source).context("failed to load Cedar policies")?; + + let content_gate = MockContentGate::default(); + let event_store = InMemoryEventStore::new(); + let gateway = Arc::new(GatewayService::new( + bg, + authorizer, + content_gate, + event_store, + )); + + let sandbox: Box = match OsSandbox::new() { + Ok(s) => { + info!("sandbox enforcement enabled via argus-sandbox-exec"); + Box::new(s) + } + Err(e) => { + tracing::warn!(error = %e, "sandbox wrapper not found, using NoopSandbox"); + Box::new(NoopSandbox) + } + }; + let mcp = Arc::new(RwLock::new(McpManager::new(sandbox))); + let mcp_configs = build_mcp_configs(&toml_registry.mcps); + if !mcp_configs.is_empty() { + let mut mcp_lock = mcp.write().await; + for result in mcp_lock.spawn_all(mcp_configs).await { + match result { + Ok(name) => info!(server = %name, "MCP server ready"), + Err(e) => tracing::warn!(error = %e, "failed to spawn MCP server"), + } + } + } else { + tracing::warn!("no MCP servers configured in registry"); + } + + { + let mcp_lock = mcp.read().await; + for tool_name in mcp_lock.managed_tools() { + if let Err(e) = gateway.register_tool(ToolId::new(&tool_name)).await { + tracing::warn!(tool = %tool_name, error = %e, "failed to register discovered tool"); + } + } + } + + let (shutdown_tx, _) = tokio::sync::watch::channel(false); + let mut handles = Vec::new(); + + if let Some(ref grpc_cfg) = config.grpc + && grpc_cfg.enabled + { + let addr = grpc_cfg.address.parse().context("invalid gRPC address")?; + let grpc_svc = ArgusGrpcService::new(gateway.clone()); + let mut shutdown_rx = shutdown_tx.subscribe(); + handles.push(tokio::spawn(async move { + info!("gRPC server listening on {addr}"); + tonic::transport::Server::builder() + .add_service(grpc_svc.into_server()) + .serve_with_shutdown(addr, async move { + let _ = shutdown_rx.changed().await; + }) + .await + .ok(); + })); + } + + if let Some(ref proxy_cfg) = config.proxy + && proxy_cfg.enabled + { + let proxy_config = build_proxy_config(proxy_cfg); + let proxy = Arc::new(LlmProxy::new(gateway.clone(), mcp.clone(), proxy_config)); + let listener = tokio::net::TcpListener::bind(&proxy_cfg.address) + .await + .context("failed to bind proxy address")?; + let mut shutdown_rx = shutdown_tx.subscribe(); + handles.push(tokio::spawn(async move { + tokio::select! { + _ = proxy.serve(listener) => {} + _ = shutdown_rx.changed() => { + info!("LLM proxy shutting down"); + } + } + })); + } + + info!("argus daemon ready"); + tokio::signal::ctrl_c().await.ok(); + info!("shutdown signal received"); + + let _ = shutdown_tx.send(true); + + let timeout = std::time::Duration::from_secs(config.server.shutdown_timeout_secs); + let _ = tokio::time::timeout(timeout, async { + for h in handles { + let _ = h.await; + } + }) + .await; + + mcp.write().await.shutdown().await; + + info!("argus daemon stopped"); + Ok(()) +} + +fn load_registry(path: &str) -> anyhow::Result { + let registry_dir = Path::new(path); + if !registry_dir.is_dir() { + tracing::warn!(path = %path, "registry directory not found, starting with empty registry"); + return Ok(TomlRegistry::empty()); + } + TomlRegistry::load_from_dir(registry_dir).context("failed to load registry") +} + +fn parse_cap_kind(s: &str) -> Option { + let base = s.split('(').next().unwrap_or(s); + match base { + "filesystem:read" => Some(CapKind::FilesystemRead), + "filesystem:write" => Some(CapKind::FilesystemWrite), + "filesystem:delete" => Some(CapKind::FilesystemDelete), + "network:egress" => Some(CapKind::NetworkEgress), + "network:ingress" => Some(CapKind::NetworkIngress), + "execution:shell" => Some(CapKind::ExecutionShell), + "execution:code" => Some(CapKind::ExecutionCode), + "credentials" => Some(CapKind::Credentials), + "system:info" => Some(CapKind::SystemInfo), + "system:modify" => Some(CapKind::SystemModify), + "clipboard" => Some(CapKind::Clipboard), + "browser:navigate" => Some(CapKind::BrowserNavigate), + "database:read" => Some(CapKind::DatabaseRead), + "database:write" => Some(CapKind::DatabaseWrite), + "ipc" => Some(CapKind::Ipc), + _ => None, + } +} + +fn cap_to_egress(cap: &CapKind) -> Option { + match cap { + CapKind::NetworkEgress => Some(EgressKind::NetworkExternal), + CapKind::NetworkIngress => Some(EgressKind::NetworkInternal), + CapKind::FilesystemWrite | CapKind::FilesystemDelete => Some(EgressKind::FilesystemWrite), + CapKind::Ipc => Some(EgressKind::Ipc), + _ => None, + } +} + +fn build_background_theory(mcps: &HashMap) -> argus_kernel::BackgroundTheory { + let mut builder = BackgroundTheoryBuilder::new(); + for (name, entry) in mcps { + let capabilities: BTreeSet = entry + .metadata + .capabilities + .iter() + .filter_map(|s| parse_cap_kind(s)) + .collect(); + + let egress: BTreeSet = capabilities.iter().filter_map(cap_to_egress).collect(); + + let metadata = ToolMetadata { + capabilities, + egress, + conf_floor: ConfLevel::Public, + endorsed: matches!(entry.metadata.integrity.as_str(), "verified" | "proven"), + }; + builder.register_tool(ToolId::new(name), metadata); + } + builder.build() +} + +fn build_mcp_configs(mcps: &HashMap) -> Vec { + let mut configs = Vec::new(); + for (name, entry) in mcps { + let (command, args) = match &entry.install { + InstallConfig::Npm { package, version } => { + let mut args = vec!["-y".to_string(), format!("{package}@{version}")]; + args.extend(entry.args.default.clone()); + ("npx".to_string(), args) + } + _ => { + tracing::warn!(server = %name, "unsupported install type for auto-spawn, skipping"); + continue; + } + }; + + let mut daemon_env = HashMap::new(); + let missing: Vec<_> = entry + .env + .iter() + .filter_map(|(k, v)| match std::env::var(k) { + Ok(val) => { + daemon_env.insert(k.clone(), val); + None + } + Err(_) if v.required => Some(k.as_str()), + Err(_) => None, + }) + .collect(); + if !missing.is_empty() { + tracing::warn!(server = %name, missing = ?missing, "skipping due to missing required env vars"); + continue; + } + + let env: Vec<(String, String)> = daemon_env + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + let sandbox_profile = build_sandbox_profile(name, entry, &daemon_env); + + configs.push(McpServerConfig { + name: name.clone(), + command, + args, + env, + sandbox_profile, + sidecars: entry.sidecars.clone(), + }); + } + configs +} + +fn build_sandbox_profile( + name: &str, + entry: &McpEntry, + daemon_env: &HashMap, +) -> SandboxProfile { + let mut builder = ProfileBuilder::new(); + + let mut resolved = extract_env_defaults(&entry.env); + resolved.extend(daemon_env.iter().map(|(k, v)| (k.clone(), v.clone()))); + if !resolved.is_empty() { + builder = builder.resolved_env(resolved); + } + + if let Some(cfg) = &entry.sandbox { + builder = builder + .extra_read(cfg.extra_read.clone()) + .extra_write(cfg.extra_write.clone()); + + let policy = cfg.net_policy.as_ref().map(|net| match net { + SandboxNetPolicy::Blocked => NetPolicy::Blocked, + SandboxNetPolicy::Egress => NetPolicy::EgressOnly, + SandboxNetPolicy::EgressDomains(domains) => { + NetPolicy::EgressWithDomains(domains.clone()) + } + }); + if let Some(policy) = policy { + builder = builder.net_policy(policy); + } + } + + builder.build().unwrap_or_else(|e| { + tracing::error!(server = %name, error = %e, "sandbox profile construction failed, using default"); + SandboxProfile::default() + }) +} + +fn build_proxy_config(proxy_cfg: &ProxyCfg) -> ProxyConfig { + let upstreams = proxy_cfg + .upstreams + .iter() + .map(|(name, entry)| UpstreamConfig { + name: name.clone(), + url: entry.url.clone(), + format: match entry.format { + UpstreamFormat::Openai => LlmFormat::OpenAi, + UpstreamFormat::Anthropic => LlmFormat::Anthropic, + }, + }) + .collect(); + + ProxyConfig { + max_tool_rounds: proxy_cfg.max_tool_rounds, + upstreams, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use argus_registry::toml_types::{McpArgs, McpMetadata}; + + fn test_mcp_entry( + name: &str, + integrity: &str, + caps: &[&str], + install: InstallConfig, + ) -> McpEntry { + McpEntry { + metadata: McpMetadata { + name: name.into(), + description: String::new(), + repository: None, + source: "community".into(), + integrity: integrity.into(), + capabilities: caps.iter().map(|s| (*s).into()).collect(), + schema_hash: None, + pinned_ref: None, + track: None, + }, + install, + env: HashMap::new(), + args: McpArgs::default(), + sandbox: None, + sidecars: vec![], + } + } + + fn npm_install(package: &str, version: &str) -> InstallConfig { + InstallConfig::Npm { + package: package.into(), + version: version.into(), + } + } + + #[test] + fn parse_cap_kind_basic() { + assert_eq!( + parse_cap_kind("filesystem:read"), + Some(CapKind::FilesystemRead) + ); + assert_eq!( + parse_cap_kind("network:egress"), + Some(CapKind::NetworkEgress) + ); + assert_eq!(parse_cap_kind("credentials"), Some(CapKind::Credentials)); + assert_eq!(parse_cap_kind("ipc"), Some(CapKind::Ipc)); + } + + #[test] + fn parse_cap_kind_with_scope() { + assert_eq!( + parse_cap_kind("filesystem:read(/tmp/**)"), + Some(CapKind::FilesystemRead) + ); + assert_eq!( + parse_cap_kind("network:egress(api.github.com)"), + Some(CapKind::NetworkEgress) + ); + assert_eq!( + parse_cap_kind("execution:shell(git, ls)"), + Some(CapKind::ExecutionShell) + ); + } + + #[test] + fn parse_cap_kind_unknown() { + assert_eq!(parse_cap_kind("unknown:capability"), None); + assert_eq!(parse_cap_kind(""), None); + } + + #[test] + fn parse_cap_kind_all_15_variants() { + let mappings = [ + ("filesystem:read", CapKind::FilesystemRead), + ("filesystem:write", CapKind::FilesystemWrite), + ("filesystem:delete", CapKind::FilesystemDelete), + ("network:egress", CapKind::NetworkEgress), + ("network:ingress", CapKind::NetworkIngress), + ("execution:shell", CapKind::ExecutionShell), + ("execution:code", CapKind::ExecutionCode), + ("credentials", CapKind::Credentials), + ("system:info", CapKind::SystemInfo), + ("system:modify", CapKind::SystemModify), + ("clipboard", CapKind::Clipboard), + ("browser:navigate", CapKind::BrowserNavigate), + ("database:read", CapKind::DatabaseRead), + ("database:write", CapKind::DatabaseWrite), + ("ipc", CapKind::Ipc), + ]; + for (s, expected) in mappings { + assert_eq!(parse_cap_kind(s), Some(expected), "failed for {s}"); + } + } + + #[test] + fn build_background_theory_from_registry() { + let mut mcps = HashMap::new(); + mcps.insert( + "fs-server".to_string(), + test_mcp_entry( + "fs-server", + "observed", + &["filesystem:read", "filesystem:write"], + npm_install("@mcp/fs", "1.0.0"), + ), + ); + mcps.insert( + "net-server".to_string(), + test_mcp_entry( + "net-server", + "verified", + &["network:egress"], + npm_install("@mcp/net", "2.0.0"), + ), + ); + + let bg = build_background_theory(&mcps); + + let fs_meta = bg.tool_metadata(&ToolId::new("fs-server")).unwrap(); + assert!(fs_meta.capabilities.contains(&CapKind::FilesystemRead)); + assert!(fs_meta.capabilities.contains(&CapKind::FilesystemWrite)); + assert!(fs_meta.egress.contains(&EgressKind::FilesystemWrite)); + assert!(!fs_meta.endorsed); + + let net_meta = bg.tool_metadata(&ToolId::new("net-server")).unwrap(); + assert!(net_meta.capabilities.contains(&CapKind::NetworkEgress)); + assert!(net_meta.egress.contains(&EgressKind::NetworkExternal)); + assert!(net_meta.endorsed); + } + + #[test] + fn build_background_theory_empty_registry() { + let mcps = HashMap::new(); + let bg = build_background_theory(&mcps); + assert!(bg.tool_metadata(&ToolId::new("anything")).is_none()); + } + + #[test] + fn build_mcp_configs_npm_only() { + let mut mcps = HashMap::new(); + mcps.insert( + "npm-server".to_string(), + test_mcp_entry( + "npm-server", + "observed", + &[], + npm_install("@mcp/test", "1.0.0"), + ), + ); + mcps.insert( + "binary-server".to_string(), + test_mcp_entry( + "binary-server", + "observed", + &[], + InstallConfig::Binary { + url: "https://example.com/bin".into(), + checksum: "sha256:abc".into(), + }, + ), + ); + + let configs = build_mcp_configs(&mcps); + assert_eq!(configs.len(), 1); + assert_eq!(configs[0].name, "npm-server"); + assert_eq!(configs[0].command, "npx"); + assert_eq!(configs[0].args[0], "-y"); + assert_eq!(configs[0].args[1], "@mcp/test@1.0.0"); + } + + #[test] + fn build_mcp_configs_includes_default_args() { + let mut mcps = HashMap::new(); + let mut entry = test_mcp_entry( + "with-args", + "observed", + &[], + npm_install("@mcp/test", "1.0.0"), + ); + entry.args = McpArgs { + default: vec!["/home/user/docs".into()], + }; + mcps.insert("with-args".to_string(), entry); + + let configs = build_mcp_configs(&mcps); + assert_eq!(configs[0].args.len(), 3); + assert_eq!(configs[0].args[2], "/home/user/docs"); + } + + #[test] + fn build_proxy_config_maps_upstreams() { + let proxy_cfg = ProxyCfg { + enabled: true, + address: "127.0.0.1:8080".into(), + max_tool_rounds: 20, + upstreams: HashMap::from([ + ( + "openai".into(), + crate::config::UpstreamEntry { + url: "https://api.openai.com".into(), + format: UpstreamFormat::Openai, + api_key_env: None, + }, + ), + ( + "anthropic".into(), + crate::config::UpstreamEntry { + url: "https://api.anthropic.com".into(), + format: UpstreamFormat::Anthropic, + api_key_env: None, + }, + ), + ]), + }; + + let config = build_proxy_config(&proxy_cfg); + assert_eq!(config.max_tool_rounds, 20); + assert_eq!(config.upstreams.len(), 2); + let openai = config + .upstreams + .iter() + .find(|u| u.name == "openai") + .unwrap(); + assert_eq!(openai.format, LlmFormat::OpenAi); + let anthropic = config + .upstreams + .iter() + .find(|u| u.name == "anthropic") + .unwrap(); + assert_eq!(anthropic.format, LlmFormat::Anthropic); + } +} diff --git a/argus/argus-cli/tests/serve_integration.rs b/argus/argus-cli/tests/serve_integration.rs new file mode 100644 index 0000000..15d9c4b --- /dev/null +++ b/argus/argus-cli/tests/serve_integration.rs @@ -0,0 +1,223 @@ +use std::fs; +use std::net::TcpListener; +use std::time::Duration; + +use tempfile::TempDir; + +fn find_free_port() -> u16 { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.local_addr().unwrap().port() +} + +fn cargo_bin_path() -> std::path::PathBuf { + std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join("argus") +} + +struct TestDaemon { + grpc_port: u16, + proxy_port: u16, + child: Option, + _tmp: TempDir, +} + +impl TestDaemon { + fn start(grpc_enabled: bool, proxy_enabled: bool) -> Self { + let tmp = TempDir::new().unwrap(); + let grpc_port = find_free_port(); + let proxy_port = find_free_port(); + + let registry_dir = tmp.path().join("registry"); + fs::create_dir_all(®istry_dir).unwrap(); + + let policy_dir = tmp.path().join("policies"); + fs::create_dir_all(&policy_dir).unwrap(); + fs::write( + policy_dir.join("default.cedar"), + r#"permit(principal, action, resource);"#, + ) + .unwrap(); + + let config_content = format!( + "[server]\n\ + registry_path = \"{registry}\"\n\ + policy_path = \"{policy}\"\n\ + log_level = \"warn\"\n\ + shutdown_timeout_secs = 2\n\ + \n\ + [grpc]\n\ + enabled = {grpc_enabled}\n\ + address = \"127.0.0.1:{grpc_port}\"\n\ + \n\ + [proxy]\n\ + enabled = {proxy_enabled}\n\ + address = \"127.0.0.1:{proxy_port}\"\n\ + max_tool_rounds = 5\n", + registry = registry_dir.display(), + policy = policy_dir.display(), + ); + + let config_path = tmp.path().join("argus.toml"); + fs::write(&config_path, config_content).unwrap(); + + let child = std::process::Command::new(cargo_bin_path()) + .arg("serve") + .arg("--config") + .arg(&config_path) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("failed to spawn argus daemon"); + + TestDaemon { + grpc_port, + proxy_port, + child: Some(child), + _tmp: tmp, + } + } + + fn proxy_url(&self, path: &str) -> String { + format!("http://127.0.0.1:{}{}", self.proxy_port, path) + } + + fn shutdown(&mut self) { + if let Some(mut child) = self.child.take() { + let _ = child.kill(); + let _ = child.wait(); + } + } +} + +impl Drop for TestDaemon { + fn drop(&mut self) { + self.shutdown(); + } +} + +async fn wait_for_port(port: u16, max_wait: Duration) -> bool { + let start = std::time::Instant::now(); + while start.elapsed() < max_wait { + if tokio::net::TcpStream::connect(format!("127.0.0.1:{port}")) + .await + .is_ok() + { + return true; + } + tokio::time::sleep(Duration::from_millis(50)).await; + } + false +} + +fn port_is_closed(port: u16) -> bool { + TcpListener::bind(format!("127.0.0.1:{port}")).is_ok() +} + +#[tokio::test] +async fn daemon_starts_and_binds_grpc_port() { + let mut daemon = TestDaemon::start(true, false); + assert!( + wait_for_port(daemon.grpc_port, Duration::from_secs(10)).await, + "gRPC port did not open" + ); + daemon.shutdown(); +} + +#[tokio::test] +async fn daemon_starts_and_binds_proxy_port() { + let mut daemon = TestDaemon::start(false, true); + assert!( + wait_for_port(daemon.proxy_port, Duration::from_secs(10)).await, + "proxy port did not open" + ); + daemon.shutdown(); +} + +#[tokio::test] +async fn daemon_starts_both_listeners() { + let mut daemon = TestDaemon::start(true, true); + let grpc_ready = wait_for_port(daemon.grpc_port, Duration::from_secs(10)); + let proxy_ready = wait_for_port(daemon.proxy_port, Duration::from_secs(10)); + let (grpc_ok, proxy_ok) = tokio::join!(grpc_ready, proxy_ready); + assert!(grpc_ok, "gRPC port did not open"); + assert!(proxy_ok, "proxy port did not open"); + daemon.shutdown(); +} + +#[tokio::test] +async fn daemon_shuts_down_cleanly() { + let mut daemon = TestDaemon::start(true, false); + assert!(wait_for_port(daemon.grpc_port, Duration::from_secs(10)).await); + let port = daemon.grpc_port; + daemon.shutdown(); + tokio::time::sleep(Duration::from_millis(200)).await; + assert!(port_is_closed(port), "port should be freed after shutdown"); +} + +#[tokio::test] +async fn proxy_returns_401_without_agent_header() { + let mut daemon = TestDaemon::start(false, true); + assert!(wait_for_port(daemon.proxy_port, Duration::from_secs(10)).await); + + let client = reqwest::Client::new(); + let resp = client + .post(daemon.proxy_url("/v1/chat/completions")) + .json(&serde_json::json!({"model": "test", "messages": []})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 401); + daemon.shutdown(); +} + +#[tokio::test] +async fn proxy_returns_404_for_unknown_path() { + let mut daemon = TestDaemon::start(false, true); + assert!(wait_for_port(daemon.proxy_port, Duration::from_secs(10)).await); + + let client = reqwest::Client::new(); + let resp = client + .post(daemon.proxy_url("/v2/unknown")) + .json(&serde_json::json!({})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 404); + daemon.shutdown(); +} + +#[tokio::test] +async fn daemon_starts_with_os_sandbox_initialization() { + // Validates that OsSandbox::new() (or NoopSandbox fallback) doesn't + // prevent daemon startup. The sandbox provider is initialized inside + // run_serve() before binding listeners. + let mut daemon = TestDaemon::start(true, false); + assert!( + wait_for_port(daemon.grpc_port, Duration::from_secs(10)).await, + "daemon should start with sandbox provider initialized" + ); + daemon.shutdown(); +} + +#[tokio::test] +async fn invalid_config_exits_nonzero() { + let tmp = TempDir::new().unwrap(); + let config_path = tmp.path().join("bad.toml"); + fs::write(&config_path, "not valid toml [[[").unwrap(); + + let output = std::process::Command::new(cargo_bin_path()) + .arg("serve") + .arg("--config") + .arg(&config_path) + .output() + .expect("failed to run argus"); + + assert!(!output.status.success()); +} diff --git a/argus/argus-sandbox-exec/Cargo.toml b/argus/argus-sandbox-exec/Cargo.toml new file mode 100644 index 0000000..875e5b4 --- /dev/null +++ b/argus/argus-sandbox-exec/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "argus-sandbox-exec" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +argus-sandbox = { workspace = true, features = ["types"] } +serde_json = { workspace = true } +libc = { workspace = true } + +[dev-dependencies] +serial_test = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +landlock = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "macros", "signal"] } +hyper = { workspace = true, features = ["server", "http1", "client"] } +hyper-util = { workspace = true } +http-body-util = { workspace = true } diff --git a/argus/argus-sandbox-exec/src/linux.rs b/argus/argus-sandbox-exec/src/linux.rs new file mode 100644 index 0000000..58421ee --- /dev/null +++ b/argus/argus-sandbox-exec/src/linux.rs @@ -0,0 +1,150 @@ +use argus_sandbox::{NetPolicy, SandboxProfile}; +use landlock::{ + ABI, Access, AccessFs, AccessNet, PathBeneath, PathFd, Ruleset, RulesetAttr, RulesetCreatedAttr, +}; + +mod proxy; + +pub fn enforce(profile: &SandboxProfile, cmd_args: &[String]) -> Result<(), String> { + match &profile.net_policy { + NetPolicy::EgressWithDomains(domains) if !domains.is_empty() => { + enforce_with_proxy(profile, cmd_args, domains) + } + _ => enforce_landlock_only(profile), + } +} + +fn enforce_landlock_only(profile: &SandboxProfile) -> Result<(), String> { + let abi = ABI::V5; + + let mut ruleset = Ruleset::default() + .handle_access(AccessFs::from_all(abi)) + .map_err(|e| format!("landlock fs setup: {e}"))?; + + ruleset = match &profile.net_policy { + NetPolicy::Blocked => ruleset + .handle_access(AccessNet::from_all(abi)) + .map_err(|e| format!("landlock net setup: {e}"))?, + NetPolicy::EgressOnly => ruleset + .handle_access(AccessNet::BindTcp) + .map_err(|e| format!("landlock net setup: {e}"))?, + NetPolicy::EgressWithDomains(_) => unreachable!(), + }; + + let mut created = ruleset + .create() + .map_err(|e| format!("landlock create: {e}"))?; + + let read_access = AccessFs::Execute | AccessFs::ReadFile | AccessFs::ReadDir; + for path in &profile.allowed_read_paths { + if let Ok(fd) = PathFd::new(path) { + created = created + .add_rule(PathBeneath::new(fd, read_access)) + .map_err(|e| format!("landlock read rule for {}: {e}", path.display()))?; + } + } + + let write_access = read_access + | AccessFs::WriteFile + | AccessFs::MakeDir + | AccessFs::MakeReg + | AccessFs::MakeSym + | AccessFs::Refer + | AccessFs::Truncate + | AccessFs::RemoveFile + | AccessFs::RemoveDir; + for path in &profile.allowed_write_paths { + if let Ok(fd) = PathFd::new(path) { + created = created + .add_rule(PathBeneath::new(fd, write_access)) + .map_err(|e| format!("landlock write rule for {}: {e}", path.display()))?; + } + } + + let status = created + .restrict_self() + .map_err(|e| format!("landlock restrict_self: {e}"))?; + + match status.ruleset { + landlock::RulesetStatus::FullyEnforced => {} + landlock::RulesetStatus::PartiallyEnforced => { + return Err("landlock only partially enforced (kernel too old?)".into()); + } + landlock::RulesetStatus::NotEnforced => { + return Err("landlock not enforced (kernel does not support landlock)".into()); + } + } + + let ret = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) }; + if ret != 0 { + return Err(format!( + "prctl(PR_SET_NO_NEW_PRIVS) failed: {}", + std::io::Error::last_os_error() + )); + } + + Ok(()) +} + +fn enforce_with_proxy( + profile: &SandboxProfile, + cmd_args: &[String], + domains: &[String], +) -> Result<(), String> { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .map_err(|e| format!("tokio runtime: {e}"))?; + + rt.block_on(async { + let mut proxy_server = proxy::ProxyServer::new(domains.to_vec()); + proxy_server.start().await?; + + let http_sock = proxy_server.http_socket_path().display().to_string(); + let proxy_url = format!("http://unix:{http_sock}:"); + + let mut bwrap_args = vec![ + "bwrap".to_string(), + "--clearenv".to_string(), + "--unshare-net".to_string(), + "--bind".to_string(), + "/".to_string(), + "/".to_string(), + "--ro-bind".to_string(), + http_sock.clone(), + http_sock, + ]; + + for var in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"] { + bwrap_args.push("--setenv".to_string()); + bwrap_args.push(var.to_string()); + bwrap_args.push(proxy_url.clone()); + } + + for (key, value) in &profile.resolved_env { + bwrap_args.push("--setenv".to_string()); + bwrap_args.push(key.clone()); + bwrap_args.push(value.clone()); + } + + bwrap_args.push("--".to_string()); + bwrap_args.extend_from_slice(cmd_args); + + let status = tokio::process::Command::new(&bwrap_args[0]) + .args(&bwrap_args[1..]) + .status() + .await + .map_err(|e| format!("failed to spawn bwrap: {e}"))?; + + proxy_server.shutdown(); + + if status.success() { + Ok(()) + } else { + Err(format!( + "sandboxed process exited with status: {}", + status.code().unwrap_or(-1) + )) + } + }) +} diff --git a/argus/argus-sandbox-exec/src/main.rs b/argus/argus-sandbox-exec/src/main.rs new file mode 100644 index 0000000..cab1804 --- /dev/null +++ b/argus/argus-sandbox-exec/src/main.rs @@ -0,0 +1,230 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; + +use argus_sandbox::SandboxProfile; + +const SYSTEM_BASELINE_VARS: &[&str] = &[ + "PATH", + "HOME", + "TMPDIR", + "USER", + "LANG", + "LC_ALL", + "LC_CTYPE", + "LC_MESSAGES", + "LC_COLLATE", + "LC_MONETARY", + "LC_NUMERIC", + "LC_TIME", + "TERM", +]; + +fn main() -> ExitCode { + let args: Vec = std::env::args().collect(); + + let (profile_path, cmd_args) = match parse_args(&args) { + Ok(parsed) => parsed, + Err(e) => { + eprintln!("argus-sandbox-exec: {e}"); + return ExitCode::from(2); + } + }; + + let profile = match read_and_delete_profile(&profile_path) { + Ok(p) => p, + Err(e) => { + eprintln!("argus-sandbox-exec: failed to read profile: {e}"); + return ExitCode::from(2); + } + }; + + if let Err(e) = enforce_and_exec(&profile, &cmd_args) { + eprintln!("argus-sandbox-exec: {e}"); + return ExitCode::from(1); + } + + unreachable!("exec should have replaced this process") +} + +fn parse_args(args: &[String]) -> Result<(PathBuf, Vec), String> { + let mut profile_path = None; + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--profile" => { + i += 1; + profile_path = Some(PathBuf::from( + args.get(i).ok_or("--profile requires a path argument")?, + )); + } + "--" => { + i += 1; + break; + } + other => return Err(format!("unknown argument: {other}")), + } + i += 1; + } + + let profile = profile_path.ok_or("--profile is required")?; + let cmd_args: Vec = args[i..].to_vec(); + if cmd_args.is_empty() { + return Err("no command specified after --".into()); + } + Ok((profile, cmd_args)) +} + +fn read_and_delete_profile(path: &Path) -> Result { + let contents = std::fs::read_to_string(path).map_err(|e| e.to_string())?; + std::fs::remove_file(path).ok(); + serde_json::from_str(&contents).map_err(|e| e.to_string()) +} + +fn enforce_and_exec(profile: &SandboxProfile, cmd_args: &[String]) -> Result<(), String> { + scrub_and_inject_env(profile); + + #[cfg(target_os = "macos")] + { + seatbelt::enforce(profile)?; + } + + #[cfg(target_os = "linux")] + { + linux::enforce(profile, cmd_args)?; + } + + exec_command(cmd_args) +} + +fn scrub_and_inject_env(profile: &SandboxProfile) { + let baseline: HashMap = SYSTEM_BASELINE_VARS + .iter() + .filter_map(|&var| std::env::var(var).ok().map(|v| (var.to_string(), v))) + .collect(); + + // SAFETY: argus-sandbox-exec is single-threaded (runs before execvp replaces the process). + // No other threads exist to observe env mutations. + unsafe { + for (key, _) in std::env::vars() { + std::env::remove_var(&key); + } + + for (key, val) in &baseline { + std::env::set_var(key, val); + } + + if let Some(original_path) = baseline.get("PATH") { + let restricted = restrict_path(original_path); + std::env::set_var("PATH", restricted); + } + + for (key, val) in &profile.resolved_env { + std::env::set_var(key, val); + } + } +} + +fn restrict_path(original: &str) -> String { + let allowed_prefixes = ["/usr/bin", "/usr/local/bin", "/bin", "/usr/sbin"]; + original + .split(':') + .filter(|entry| { + allowed_prefixes + .iter() + .any(|prefix| entry.starts_with(prefix)) + }) + .collect::>() + .join(":") +} + +fn exec_command(cmd_args: &[String]) -> Result<(), String> { + use std::ffi::CString; + + let program = CString::new(cmd_args[0].as_bytes()).map_err(|e| e.to_string())?; + let c_args: Vec = cmd_args + .iter() + .map(|a| CString::new(a.as_bytes()).map_err(|e| e.to_string())) + .collect::, _>>()?; + + let c_arg_ptrs: Vec<*const libc::c_char> = c_args + .iter() + .map(|a| a.as_ptr()) + .chain(std::iter::once(std::ptr::null())) + .collect(); + + unsafe { + libc::execvp(program.as_ptr(), c_arg_ptrs.as_ptr()); + } + + Err(format!( + "execvp failed: {}", + std::io::Error::last_os_error() + )) +} + +#[cfg(target_os = "macos")] +mod seatbelt; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(test)] +mod tests { + use super::*; + use serial_test::serial; + + #[test] + fn system_baseline_vars_are_defined() { + assert!(SYSTEM_BASELINE_VARS.contains(&"PATH")); + assert!(SYSTEM_BASELINE_VARS.contains(&"HOME")); + assert!(SYSTEM_BASELINE_VARS.contains(&"TMPDIR")); + assert!(!SYSTEM_BASELINE_VARS.contains(&"ANTHROPIC_API_KEY")); + } + + #[test] + #[serial] + fn scrub_env_removes_all_non_baseline() { + let original: Vec<(String, String)> = std::env::vars().collect(); + + // SAFETY: test is serialized via #[serial], no concurrent threads + unsafe { std::env::set_var("TEST_LEAK_VAR", "should_not_survive") }; + + let mut resolved = HashMap::new(); + resolved.insert("MY_VAR".to_string(), "my_value".to_string()); + + let profile = SandboxProfile { + resolved_env: resolved, + ..Default::default() + }; + + scrub_and_inject_env(&profile); + + assert!( + std::env::var("TEST_LEAK_VAR").is_err(), + "leaked env var survived scrub" + ); + assert_eq!(std::env::var("MY_VAR").unwrap(), "my_value"); + + // Restore + unsafe { + for (key, _) in std::env::vars() { + std::env::remove_var(&key); + } + for (k, v) in &original { + std::env::set_var(k, v); + } + } + } + + #[test] + fn restrict_path_filters_non_standard() { + let input = "/usr/bin:/usr/local/bin:/home/user/.cargo/bin:/bin:/opt/sketchy"; + let result = restrict_path(input); + assert!(result.contains("/usr/bin")); + assert!(result.contains("/usr/local/bin")); + assert!(result.contains("/bin")); + assert!(!result.contains(".cargo")); + assert!(!result.contains("sketchy")); + } +} diff --git a/argus/argus-sandbox-exec/src/proxy/http_connect.rs b/argus/argus-sandbox-exec/src/proxy/http_connect.rs new file mode 100644 index 0000000..f79f536 --- /dev/null +++ b/argus/argus-sandbox-exec/src/proxy/http_connect.rs @@ -0,0 +1,141 @@ +use std::sync::Arc; + +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::{Method, Request, Response}; +use hyper_util::rt::TokioIo; +use tokio::io; +use tokio::net::{TcpStream, UnixListener}; + +use argus_sandbox::domain_matches_any; + +pub async fn serve( + listener: UnixListener, + domains: Arc>, + mut shutdown_rx: tokio::sync::watch::Receiver, +) { + loop { + tokio::select! { + accept = listener.accept() => { + match accept { + Ok((stream, _)) => { + let domains = domains.clone(); + tokio::spawn(async move { + let io = TokioIo::new(stream); + let svc = service_fn(move |req| { + let domains = domains.clone(); + async move { handle_request(req, &domains).await } + }); + if let Err(e) = http1::Builder::new() + .preserve_header_case(true) + .serve_connection(io, svc) + .with_upgrades() + .await + { + eprintln!("argus-proxy: connection error: {e}"); + } + }); + } + Err(e) => { + eprintln!("argus-proxy: accept error: {e}"); + } + } + } + _ = shutdown_rx.changed() => { + if *shutdown_rx.borrow() { + break; + } + } + } + } +} + +async fn handle_request( + req: Request, + domains: &[String], +) -> Result>, hyper::Error> { + if req.method() == Method::CONNECT { + let authority = req.uri().authority().map(|a| a.to_string()); + if let Some(ref authority) = authority { + let (host, port) = parse_authority(authority); + if domain_matches_any(domains, &host) { + tokio::spawn(async move { + match hyper::upgrade::on(req).await { + Ok(upgraded) => { + let target = format!("{host}:{port}"); + if let Err(e) = tunnel(upgraded, &target).await { + eprintln!("argus-proxy: tunnel to {target} failed: {e}"); + } + } + Err(e) => { + eprintln!("argus-proxy: upgrade failed: {e}"); + } + } + }); + + Ok(Response::new(http_body_util::Empty::new())) + } else { + eprintln!("argus-proxy: blocked connection to {authority}"); + let mut resp = Response::new(http_body_util::Empty::new()); + *resp.status_mut() = hyper::StatusCode::FORBIDDEN; + Ok(resp) + } + } else { + let mut resp = Response::new(http_body_util::Empty::new()); + *resp.status_mut() = hyper::StatusCode::BAD_REQUEST; + Ok(resp) + } + } else { + let mut resp = Response::new(http_body_util::Empty::new()); + *resp.status_mut() = hyper::StatusCode::METHOD_NOT_ALLOWED; + Ok(resp) + } +} + +fn parse_authority(authority: &str) -> (String, u16) { + if let Some(colon_pos) = authority.rfind(':') { + let host = &authority[..colon_pos]; + let port = authority[colon_pos + 1..].parse().unwrap_or(443); + (host.to_string(), port) + } else { + (authority.to_string(), 443) + } +} + +async fn tunnel( + upgraded: hyper::upgrade::Upgraded, + target: &str, +) -> Result<(), Box> { + let mut upstream = TcpStream::connect(target).await?; + let mut client = TokioIo::new(upgraded); + + let (_, _) = io::copy_bidirectional(&mut client, &mut upstream).await?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_authority_with_port() { + let (host, port) = parse_authority("api.github.com:443"); + assert_eq!(host, "api.github.com"); + assert_eq!(port, 443); + } + + #[test] + fn parse_authority_without_port() { + let (host, port) = parse_authority("api.github.com"); + assert_eq!(host, "api.github.com"); + assert_eq!(port, 443); + } + + #[test] + fn parse_authority_with_custom_port() { + let (host, port) = parse_authority("example.com:8080"); + assert_eq!(host, "example.com"); + assert_eq!(port, 8080); + } +} diff --git a/argus/argus-sandbox-exec/src/proxy/mod.rs b/argus/argus-sandbox-exec/src/proxy/mod.rs new file mode 100644 index 0000000..77f0512 --- /dev/null +++ b/argus/argus-sandbox-exec/src/proxy/mod.rs @@ -0,0 +1,57 @@ +mod http_connect; + +use std::path::PathBuf; +use std::sync::Arc; + +use tokio::net::UnixListener; + +pub struct ProxyServer { + domains: Arc>, + http_socket_path: PathBuf, + shutdown_tx: Option>, +} + +impl ProxyServer { + pub fn new(domains: Vec) -> Self { + let pid = std::process::id(); + Self { + domains: Arc::new(domains), + http_socket_path: PathBuf::from(format!("/tmp/argus-proxy-{pid}-http.sock")), + shutdown_tx: None, + } + } + + pub fn http_socket_path(&self) -> &PathBuf { + &self.http_socket_path + } + + pub async fn start(&mut self) -> Result<(), String> { + let _ = std::fs::remove_file(&self.http_socket_path); + + let http_listener = UnixListener::bind(&self.http_socket_path) + .map_err(|e| format!("bind HTTP proxy socket: {e}"))?; + + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); + self.shutdown_tx = Some(shutdown_tx); + + let domains = self.domains.clone(); + tokio::spawn(async move { + http_connect::serve(http_listener, domains, shutdown_rx).await; + }); + + Ok(()) + } + + pub fn shutdown(&mut self) { + if let Some(tx) = self.shutdown_tx.take() { + let _ = tx.send(true); + } + let _ = std::fs::remove_file(&self.http_socket_path); + } +} + +impl Drop for ProxyServer { + fn drop(&mut self) { + self.shutdown(); + } +} diff --git a/argus/argus-sandbox-exec/src/seatbelt.rs b/argus/argus-sandbox-exec/src/seatbelt.rs new file mode 100644 index 0000000..58c12b2 --- /dev/null +++ b/argus/argus-sandbox-exec/src/seatbelt.rs @@ -0,0 +1,198 @@ +use argus_sandbox::{NetPolicy, SandboxProfile}; + +pub fn enforce(profile: &SandboxProfile) -> Result<(), String> { + let sbpl = generate_sbpl(profile); + apply_sandbox(&sbpl) +} + +fn generate_sbpl(profile: &SandboxProfile) -> String { + let mut rules: Vec = [ + "(version 1)", + "(deny default)", + "(allow process-exec)", + "(allow process-fork)", + "(allow sysctl-read)", + "(allow mach-lookup)", + "(allow signal (target self))", + "(allow system-socket)", + "(allow file-read-metadata)", + "(allow file-read-data (literal \"/\"))", + ] + .iter() + .map(|s| (*s).into()) + .collect(); + + for path in &profile.allowed_read_paths { + let p = path.display(); + if path.extension().is_some() && !path.is_dir() { + rules.push(format!("(allow file-read* (literal \"{p}\"))")); + } else { + rules.push(format!("(allow file-read* (subpath \"{p}\"))")); + } + } + + for path in &profile.allowed_write_paths { + let p = path.display(); + rules.push(format!("(allow file-write* (subpath \"{p}\"))")); + rules.push(format!("(allow file-read* (subpath \"{p}\"))")); + } + + const ALLOW_DNS: &str = "(allow network* (local udp \"*:53\"))"; + const DENY_INBOUND: &str = "(deny network-inbound)"; + + match &profile.net_policy { + NetPolicy::Blocked => {} + NetPolicy::EgressOnly => { + rules.push("(allow network-outbound)".into()); + rules.push(ALLOW_DNS.into()); + rules.push(DENY_INBOUND.into()); + } + NetPolicy::EgressWithDomains(domains) => { + if !domains.is_empty() { + let combined: String = domains + .iter() + .map(|d| domain_to_seatbelt_regex(d)) + .collect::>() + .join(" "); + rules.push(format!( + "(allow network-outbound (remote tcp (require-any {combined})))" + )); + rules.push(ALLOW_DNS.into()); + } + rules.push(DENY_INBOUND.into()); + } + } + + rules.join("\n") +} + +fn domain_to_seatbelt_regex(domain: &str) -> String { + if let Some(suffix) = domain.strip_prefix("*.") { + let escaped = regex_escape(suffix); + format!("(regex #\"^.+\\.{escaped}$\")") + } else { + let escaped = regex_escape(domain); + format!("(regex #\"^{escaped}$\")") + } +} + +fn regex_escape(s: &str) -> String { + s.replace('.', "\\.") +} + +fn apply_sandbox(sbpl: &str) -> Result<(), String> { + use std::ffi::CString; + use std::ptr; + + let profile_cstr = CString::new(sbpl).map_err(|e| format!("invalid SBPL: {e}"))?; + let mut errorbuf: *mut libc::c_char = ptr::null_mut(); + + let result = unsafe { sandbox_init(profile_cstr.as_ptr(), 0, &mut errorbuf) }; + + if result != 0 { + let err = if !errorbuf.is_null() { + let msg = unsafe { std::ffi::CStr::from_ptr(errorbuf) } + .to_string_lossy() + .into_owned(); + unsafe { sandbox_free_error(errorbuf) }; + msg + } else { + "unknown sandbox_init error".into() + }; + return Err(format!("sandbox_init failed: {err}")); + } + + Ok(()) +} + +unsafe extern "C" { + fn sandbox_init( + profile: *const libc::c_char, + flags: u64, + errorbuf: *mut *mut libc::c_char, + ) -> libc::c_int; + fn sandbox_free_error(errorbuf: *mut libc::c_char); +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + #[test] + fn sbpl_starts_with_deny_default() { + let profile = SandboxProfile::default(); + let sbpl = generate_sbpl(&profile); + assert!(sbpl.contains("(deny default)")); + } + + #[test] + fn sbpl_includes_read_paths() { + let profile = SandboxProfile { + allowed_read_paths: vec![PathBuf::from("/usr/lib"), PathBuf::from("/tmp/data")], + ..Default::default() + }; + let sbpl = generate_sbpl(&profile); + assert!(sbpl.contains("(allow file-read* (subpath \"/usr/lib\"))")); + assert!(sbpl.contains("(allow file-read* (subpath \"/tmp/data\"))")); + } + + #[test] + fn sbpl_includes_write_paths_with_read() { + let profile = SandboxProfile { + allowed_write_paths: vec![PathBuf::from("/tmp/output")], + ..Default::default() + }; + let sbpl = generate_sbpl(&profile); + assert!(sbpl.contains("(allow file-write* (subpath \"/tmp/output\"))")); + assert!(sbpl.contains("(allow file-read* (subpath \"/tmp/output\"))")); + } + + #[test] + fn sbpl_blocked_network_has_no_allow() { + let profile = SandboxProfile { + net_policy: NetPolicy::Blocked, + ..Default::default() + }; + let sbpl = generate_sbpl(&profile); + assert!(!sbpl.contains("network-outbound")); + } + + #[test] + fn sbpl_egress_only_allows_outbound() { + let profile = SandboxProfile { + net_policy: NetPolicy::EgressOnly, + ..Default::default() + }; + let sbpl = generate_sbpl(&profile); + assert!(sbpl.contains("(allow network-outbound)")); + assert!(sbpl.contains("(deny network-inbound)")); + } + + #[test] + fn sbpl_egress_with_domains_uses_regex() { + let profile = SandboxProfile { + net_policy: NetPolicy::EgressWithDomains(vec![ + "api.github.com".into(), + "*.openai.com".into(), + ]), + ..Default::default() + }; + let sbpl = generate_sbpl(&profile); + assert!(sbpl.contains("(regex #\"^api\\.github\\.com$\")")); + assert!(sbpl.contains("(regex #\"^.+\\.openai\\.com$\")")); + } + + #[test] + fn domain_to_regex_exact() { + let r = domain_to_seatbelt_regex("api.github.com"); + assert_eq!(r, "(regex #\"^api\\.github\\.com$\")"); + } + + #[test] + fn domain_to_regex_wildcard() { + let r = domain_to_seatbelt_regex("*.openai.com"); + assert_eq!(r, "(regex #\"^.+\\.openai\\.com$\")"); + } +} diff --git a/argus/argus-sandbox-exec/tests/enforcement_test.rs b/argus/argus-sandbox-exec/tests/enforcement_test.rs new file mode 100644 index 0000000..8dc3c52 --- /dev/null +++ b/argus/argus-sandbox-exec/tests/enforcement_test.rs @@ -0,0 +1,224 @@ +use std::path::PathBuf; +use std::process::Command; + +use argus_sandbox::{NetPolicy, SandboxProfile}; + +fn wrapper_binary() -> PathBuf { + let path = PathBuf::from(env!("CARGO_BIN_EXE_argus-sandbox-exec")); + assert!( + path.exists(), + "wrapper binary not found at {}", + path.display() + ); + path +} + +fn write_profile(profile: &SandboxProfile) -> PathBuf { + let dir = std::env::temp_dir(); + let path = dir.join(format!( + "test-profile-{}-{}.json", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::write(&path, serde_json::to_string(profile).unwrap()).unwrap(); + path +} + +#[test] +fn wrapper_rejects_missing_profile_arg() { + let output = Command::new(wrapper_binary()) + .arg("--") + .arg("echo") + .arg("hello") + .output() + .unwrap(); + assert!(!output.status.success()); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(stderr.contains("--profile is required")); +} + +#[test] +fn wrapper_rejects_missing_command() { + let profile = SandboxProfile::default(); + let path = write_profile(&profile); + let output = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .output() + .unwrap(); + assert!(!output.status.success()); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(stderr.contains("no command specified")); + std::fs::remove_file(&path).ok(); +} + +#[test] +fn wrapper_rejects_unknown_argument() { + let output = Command::new(wrapper_binary()) + .arg("--unknown") + .output() + .unwrap(); + assert!(!output.status.success()); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(stderr.contains("unknown argument")); +} + +#[test] +fn wrapper_reads_and_deletes_profile() { + let profile = SandboxProfile::default(); + let path = write_profile(&profile); + assert!(path.exists()); + + let _ = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .arg("/bin/echo") + .arg("test") + .output(); + + assert!(!path.exists(), "profile should be deleted after reading"); +} + +#[cfg(target_os = "macos")] +mod macos_tests { + use serial_test::serial; + + use super::*; + + #[test] + #[serial] + fn seatbelt_allows_permitted_read() { + let profile = SandboxProfile { + allowed_read_paths: vec![ + PathBuf::from("/usr/lib"), + PathBuf::from("/usr/share"), + PathBuf::from("/dev/null"), + PathBuf::from("/bin"), + PathBuf::from("/usr/bin"), + PathBuf::from("/private/var"), + PathBuf::from("/System/Library"), + ], + net_policy: NetPolicy::Blocked, + ..Default::default() + }; + let path = write_profile(&profile); + let output = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .arg("/bin/cat") + .arg("/dev/null") + .output() + .unwrap(); + assert!( + output.status.success(), + "expected success, stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + #[test] + #[serial] + fn seatbelt_blocks_network_when_blocked() { + let profile = SandboxProfile { + allowed_read_paths: vec![ + PathBuf::from("/usr"), + PathBuf::from("/bin"), + PathBuf::from("/dev"), + PathBuf::from("/private"), + PathBuf::from("/etc"), + ], + net_policy: NetPolicy::Blocked, + ..Default::default() + }; + let path = write_profile(&profile); + let output = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .arg("/usr/bin/curl") + .arg("--max-time") + .arg("2") + .arg("https://httpbin.org/get") + .output() + .unwrap(); + assert!( + !output.status.success(), + "network should be blocked, stdout: {}", + String::from_utf8_lossy(&output.stdout) + ); + } + + #[test] + #[serial] + #[ignore = "requires network access, may be flaky in CI"] + fn seatbelt_allows_egress_only() { + let profile = SandboxProfile { + allowed_read_paths: vec![ + PathBuf::from("/usr"), + PathBuf::from("/bin"), + PathBuf::from("/dev"), + PathBuf::from("/private"), + PathBuf::from("/etc"), + PathBuf::from("/System"), + PathBuf::from("/Library"), + ], + net_policy: NetPolicy::EgressOnly, + ..Default::default() + }; + let path = write_profile(&profile); + let output = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .arg("/usr/bin/curl") + .arg("--max-time") + .arg("5") + .arg("https://httpbin.org/get") + .output() + .unwrap(); + assert!( + output.status.success(), + "egress should be allowed, stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + #[test] + #[serial] + fn seatbelt_blocks_unpermitted_read() { + let profile = SandboxProfile { + allowed_read_paths: vec![ + PathBuf::from("/usr/lib"), + PathBuf::from("/bin"), + PathBuf::from("/usr/bin"), + ], + net_policy: NetPolicy::Blocked, + ..Default::default() + }; + let path = write_profile(&profile); + let output = Command::new(wrapper_binary()) + .arg("--profile") + .arg(path.to_str().unwrap()) + .arg("--") + .arg("/bin/cat") + .arg("/etc/passwd") + .output() + .unwrap(); + assert!( + !output.status.success(), + "reading /etc/passwd should be blocked" + ); + } +} + +#[cfg(target_os = "linux")] +mod linux_tests { + // Linux Landlock enforcement tests require a kernel with Landlock ABI v5. + // These will be implemented when CI has a suitable Linux environment. +} diff --git a/argus/argus_kernel.v b/argus/argus_kernel.v new file mode 100644 index 0000000..1a5714e --- /dev/null +++ b/argus/argus_kernel.v @@ -0,0 +1,9 @@ +Require Export crates.argus-kernel.src.traits. +Require Export crates.argus-kernel.src.capability. +Require Export crates.argus-kernel.src.types. +Require Export crates.argus-kernel.src.background. +Require Export crates.argus-kernel.src.event. +Require Export crates.argus-kernel.src.kernel. +Require Export crates.argus-kernel.src.state. +Require Export crates.argus-kernel.src.error. +Require Export crates.argus-kernel.src.transitions. diff --git a/argus/crates/argus-analysis/Cargo.toml b/argus/crates/argus-analysis/Cargo.toml new file mode 100644 index 0000000..71e1f79 --- /dev/null +++ b/argus/crates/argus-analysis/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "argus-analysis" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Security analysis and capability inference for Argus" + +[dependencies] +argus-config.workspace = true +argus-kernel.workspace = true +async-trait.workspace = true +rig-core.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +tokio = { workspace = true, features = ["fs"] } +tracing.workspace = true +ignore.workspace = true +grep-searcher.workspace = true +grep-regex.workspace = true +grep-matcher.workspace = true +content_inspector.workspace = true +reqwest.workspace = true +toml.workspace = true + +[dev-dependencies] +tempfile.workspace = true +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/argus/crates/argus-analysis/src/analyzer.rs b/argus/crates/argus-analysis/src/analyzer.rs new file mode 100644 index 0000000..220a14b --- /dev/null +++ b/argus/crates/argus-analysis/src/analyzer.rs @@ -0,0 +1,469 @@ +use crate::error::{AnalysisError, Result, client_error, get_env_var}; +use crate::orchestrator::{ + OrchestratorConfig, OrchestratorResult, ResultExtract, build_orchestrator, + build_result_extractor, build_user_prompt, +}; +use crate::sandbox::ToolContext; +use crate::types::{AnalysisReport, FindingSeverity, InferredCapability, SecurityFinding}; +use argus_config::ExtractionProviderConfig; +use async_trait::async_trait; +use rig::client::{CompletionClient, Nothing}; +use rig::completion::{CompletionModel, Prompt}; +use rig::providers::{anthropic, gemini, ollama, openai, openrouter}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::Instant; +use tracing::{debug, info}; + +#[async_trait] +trait AnalyzeOrchestrate: Send + Sync { + async fn orchestrate( + &self, + ctx: Arc, + config: &OrchestratorConfig, + ) -> Result; +} + +struct OrchestrateImpl { + model: M, +} + +#[async_trait] +impl AnalyzeOrchestrate for OrchestrateImpl +where + M: CompletionModel + Clone + Send + Sync + 'static, +{ + async fn orchestrate( + &self, + ctx: Arc, + config: &OrchestratorConfig, + ) -> Result { + let target_path = ctx.target_root().to_string_lossy().into_owned(); + let (agent, max_turns) = build_orchestrator(self.model.clone(), ctx, config); + let user_prompt = build_user_prompt(&target_path); + + info!(max_turns, "Starting orchestrator agent"); + let response = agent + .prompt(&user_prompt) + .multi_turn(max_turns) + .await + .map_err(|e| AnalysisError::LlmInference(e.to_string()))?; + + let extractor = build_result_extractor(self.model.clone()); + extractor + .extract(&response) + .await + .map_err(AnalysisError::LlmInference) + } +} + +pub struct Analyzer { + inner: Arc, + config: OrchestratorConfig, +} + +impl std::fmt::Debug for Analyzer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Analyzer").finish_non_exhaustive() + } +} + +macro_rules! make_orchestrate { + ($client:expr, $model:expr) => { + Arc::new(OrchestrateImpl { + model: $client.completion_model($model), + }) + }; +} + +impl Analyzer { + pub fn new(config: &ExtractionProviderConfig) -> Result { + let inner: Arc = match config { + ExtractionProviderConfig::OpenRouter { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: openrouter::Client = + openrouter::Client::new(&api_key).map_err(|e| client_error("OpenRouter", e))?; + let completion_model = client.completion_model(model).with_strict_tools(); + Arc::new(OrchestrateImpl { + model: completion_model, + }) + } + ExtractionProviderConfig::Ollama { + model, base_url, .. + } => { + let client: ollama::Client = ollama::Client::builder() + .api_key(Nothing) + .base_url(base_url) + .build() + .map_err(|e| client_error("Ollama", e))?; + make_orchestrate!(client, model) + } + ExtractionProviderConfig::OpenAI { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: openai::Client = + openai::Client::new(&api_key).map_err(|e| client_error("OpenAI", e))?; + make_orchestrate!(client, model) + } + ExtractionProviderConfig::Anthropic { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: anthropic::Client = anthropic::Client::builder() + .api_key(&api_key) + .anthropic_version(anthropic::completion::ANTHROPIC_VERSION_LATEST) + .build() + .map_err(|e| client_error("Anthropic", e))?; + make_orchestrate!(client, model) + } + ExtractionProviderConfig::Gemini { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: gemini::Client = + gemini::Client::new(&api_key).map_err(|e| client_error("Gemini", e))?; + make_orchestrate!(client, model) + } + }; + + Ok(Self { + inner, + config: OrchestratorConfig::default(), + }) + } + + pub fn with_orchestrator_config(mut self, config: OrchestratorConfig) -> Self { + self.config = config; + self + } + + pub async fn analyze(&self, path: &Path) -> Result { + if !path.exists() { + return Err(AnalysisError::InvalidPath(format!( + "Path does not exist: {}", + path.display() + ))); + } + + let start = Instant::now(); + let mut report = AnalysisReport::new(path.to_path_buf()); + + let root = if path.is_file() { + path.parent() + .ok_or_else(|| { + AnalysisError::InvalidPath("Cannot determine parent directory".to_string()) + })? + .to_path_buf() + } else { + path.to_path_buf() + }; + + let ctx = Arc::new(ToolContext::new(&root)?); + + let entries = ctx.list_directory(".")?; + if entries.is_empty() { + report + .warnings + .push("No source code found to analyze".to_string()); + return Ok(report); + } + + info!("Running orchestrator analysis on {}", path.display()); + match self.inner.orchestrate(ctx, &self.config).await { + Ok(result) => apply_result(&mut report, result), + Err(e) => { + debug!(error = %e, "Orchestrator analysis failed"); + report.warnings.push(format!("Analysis failed: {}", e)); + } + } + + report.metadata.duration_ms = start.elapsed().as_millis() as u64; + Ok(report) + } +} + +fn apply_result(report: &mut AnalysisReport, result: OrchestratorResult) { + report.inferred_capabilities = result + .capabilities + .into_iter() + .map(|c| InferredCapability { + name: c.name, + confidence: c.confidence, + evidence: c.evidence, + }) + .collect(); + + report.security_findings = result + .security_findings + .into_iter() + .map(|f| SecurityFinding { + severity: parse_severity(&f.severity), + category: f.category, + file: f.file, + line: f.line, + evidence: f.evidence, + explanation: f.explanation, + }) + .collect(); + + report.warnings = result.warnings; + report.metadata.files_explored = result + .files_explored + .into_iter() + .map(PathBuf::from) + .collect(); + report.metadata.specialists_invoked = vec!["security".to_string(), "capability".to_string()]; +} + +fn parse_severity(s: &str) -> FindingSeverity { + match s.to_lowercase().as_str() { + "critical" => FindingSeverity::Critical, + "high" => FindingSeverity::High, + "medium" => FindingSeverity::Medium, + _ => FindingSeverity::Low, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::orchestrator::OrchestratorResult; + use crate::specialists::{CapabilityEntryResult, SecurityFindingEntry}; + use argus_config::ExtractionProviderConfig; + + #[test] + fn parse_severity_all_levels() { + assert_eq!(parse_severity("low"), FindingSeverity::Low); + assert_eq!(parse_severity("medium"), FindingSeverity::Medium); + assert_eq!(parse_severity("high"), FindingSeverity::High); + assert_eq!(parse_severity("critical"), FindingSeverity::Critical); + } + + #[test] + fn parse_severity_case_insensitive() { + assert_eq!(parse_severity("HIGH"), FindingSeverity::High); + assert_eq!(parse_severity("Critical"), FindingSeverity::Critical); + } + + #[test] + fn parse_severity_unknown_defaults_to_low() { + assert_eq!(parse_severity("unknown"), FindingSeverity::Low); + assert_eq!(parse_severity(""), FindingSeverity::Low); + } + + #[test] + fn apply_result_maps_capabilities() { + let mut report = AnalysisReport::new(PathBuf::from("/test")); + let result = OrchestratorResult { + capabilities: vec![CapabilityEntryResult { + name: "filesystem_read".to_string(), + confidence: 0.9, + evidence: "fs.readFile".to_string(), + }], + security_findings: vec![], + warnings: vec![], + files_explored: vec![], + }; + + apply_result(&mut report, result); + assert_eq!(report.inferred_capabilities.len(), 1); + assert_eq!(report.inferred_capabilities[0].name, "filesystem_read"); + assert_eq!(report.inferred_capabilities[0].confidence, 0.9); + } + + #[test] + fn apply_result_maps_security_findings() { + let mut report = AnalysisReport::new(PathBuf::from("/test")); + let result = OrchestratorResult { + capabilities: vec![], + security_findings: vec![SecurityFindingEntry { + severity: "high".to_string(), + category: "command_injection".to_string(), + file: "index.js".to_string(), + line: Some(42), + evidence: "exec(input)".to_string(), + explanation: "User input flows to exec".to_string(), + }], + warnings: vec![], + files_explored: vec![], + }; + + apply_result(&mut report, result); + assert_eq!(report.security_findings.len(), 1); + assert_eq!(report.security_findings[0].severity, FindingSeverity::High); + assert_eq!(report.security_findings[0].category, "command_injection"); + assert_eq!(report.security_findings[0].line, Some(42)); + } + + #[test] + fn apply_result_maps_metadata() { + let mut report = AnalysisReport::new(PathBuf::from("/test")); + let result = OrchestratorResult { + capabilities: vec![], + security_findings: vec![], + warnings: vec!["danger".to_string()], + files_explored: vec!["src/main.js".to_string(), "package.json".to_string()], + }; + + apply_result(&mut report, result); + assert_eq!(report.warnings, vec!["danger"]); + assert_eq!(report.metadata.files_explored.len(), 2); + assert_eq!( + report.metadata.specialists_invoked, + vec!["security", "capability"] + ); + } + + #[test] + fn apply_result_handles_empty() { + let mut report = AnalysisReport::new(PathBuf::from("/test")); + let result = OrchestratorResult { + capabilities: vec![], + security_findings: vec![], + warnings: vec![], + files_explored: vec![], + }; + + apply_result(&mut report, result); + assert!(report.inferred_capabilities.is_empty()); + assert!(report.security_findings.is_empty()); + } + + #[tokio::test] + async fn analyze_rejects_missing_path() { + let config = ExtractionProviderConfig::Ollama { + model: "llama3.2:3b".to_string(), + base_url: "http://localhost:11434".to_string(), + timeout_ms: 5000, + }; + let analyzer = Analyzer::new(&config).unwrap(); + let result = analyzer.analyze(Path::new("/nonexistent/path/12345")).await; + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), AnalysisError::InvalidPath(_))); + } + + #[tokio::test] + async fn analyze_with_mock_orchestrator() { + struct MockOrchestrate; + + #[async_trait] + impl AnalyzeOrchestrate for MockOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result { + Ok(OrchestratorResult { + capabilities: vec![CapabilityEntryResult { + name: "network_egress".to_string(), + confidence: 0.8, + evidence: "fetch() call".to_string(), + }], + security_findings: vec![], + warnings: vec![], + files_explored: vec!["index.js".to_string()], + }) + } + } + + let analyzer = Analyzer { + inner: Arc::new(MockOrchestrate), + config: OrchestratorConfig::default(), + }; + + let temp_dir = tempfile::TempDir::new().unwrap(); + std::fs::write( + temp_dir.path().join("index.js"), + "fetch('http://example.com')", + ) + .unwrap(); + let report = analyzer.analyze(temp_dir.path()).await.unwrap(); + + assert_eq!(report.inferred_capabilities.len(), 1); + assert_eq!(report.inferred_capabilities[0].name, "network_egress"); + } + + #[tokio::test] + async fn analyze_handles_orchestrator_failure() { + struct FailingOrchestrate; + + #[async_trait] + impl AnalyzeOrchestrate for FailingOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result { + Err(AnalysisError::LlmInference("model timeout".to_string())) + } + } + + let analyzer = Analyzer { + inner: Arc::new(FailingOrchestrate), + config: OrchestratorConfig::default(), + }; + + let temp_dir = tempfile::TempDir::new().unwrap(); + std::fs::write(temp_dir.path().join("main.py"), "import os").unwrap(); + let report = analyzer.analyze(temp_dir.path()).await.unwrap(); + + assert!(report.warnings.iter().any(|w| w.contains("model timeout"))); + assert!(report.inferred_capabilities.is_empty()); + } + + #[test] + fn new_with_ollama_config() { + let config = ExtractionProviderConfig::Ollama { + model: "llama3.2:3b".to_string(), + base_url: "http://localhost:11434".to_string(), + timeout_ms: 5000, + }; + let analyzer = Analyzer::new(&config); + assert!(analyzer.is_ok()); + } + + #[test] + fn new_fails_without_env_var() { + let config = ExtractionProviderConfig::OpenAI { + model: "gpt-4".to_string(), + api_key_env: "NONEXISTENT_KEY_99999".to_string(), + timeout_ms: 5000, + }; + let result = Analyzer::new(&config); + assert!(result.is_err()); + } + + #[test] + fn with_orchestrator_config_overrides_defaults() { + let config = ExtractionProviderConfig::Ollama { + model: "llama3.2:3b".to_string(), + base_url: "http://localhost:11434".to_string(), + timeout_ms: 5000, + }; + let analyzer = + Analyzer::new(&config) + .unwrap() + .with_orchestrator_config(OrchestratorConfig { + max_turns: 30, + max_tokens: 100_000, + }); + assert_eq!(analyzer.config.max_turns, 30); + assert_eq!(analyzer.config.max_tokens, 100_000); + } + + #[test] + fn analyzer_debug_format() { + let config = ExtractionProviderConfig::Ollama { + model: "llama3.2:3b".to_string(), + base_url: "http://localhost:11434".to_string(), + timeout_ms: 5000, + }; + let analyzer = Analyzer::new(&config).unwrap(); + let debug_str = format!("{:?}", analyzer); + assert!(debug_str.contains("Analyzer")); + } +} diff --git a/argus/crates/argus-analysis/src/delegation.rs b/argus/crates/argus-analysis/src/delegation.rs new file mode 100644 index 0000000..f7b9e8d --- /dev/null +++ b/argus/crates/argus-analysis/src/delegation.rs @@ -0,0 +1,369 @@ +use crate::specialists::{ + CapabilityExtractionResult, SECURITY_PREAMBLE, SecurityExtractionResult, + build_capability_prompt, build_capability_system_prompt, build_security_prompt, +}; +use async_trait::async_trait; +use rig::completion::{CompletionModel, ToolDefinition}; +use rig::extractor::ExtractorBuilder; +use rig::tool::Tool; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::Arc; + +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +pub struct DelegationError(String); + +#[async_trait] +pub(crate) trait SecurityExtract: Send + Sync { + async fn extract(&self, prompt: &str) -> Result; +} + +#[async_trait] +impl SecurityExtract for rig::extractor::Extractor +where + M: CompletionModel + Send + Sync, +{ + async fn extract(&self, prompt: &str) -> Result { + rig::extractor::Extractor::extract(self, prompt) + .await + .map_err(|e| e.to_string()) + } +} + +#[async_trait] +pub(crate) trait CapabilityExtract: Send + Sync { + async fn extract(&self, prompt: &str) -> Result; +} + +#[async_trait] +impl CapabilityExtract for rig::extractor::Extractor +where + M: CompletionModel + Send + Sync, +{ + async fn extract(&self, prompt: &str) -> Result { + rig::extractor::Extractor::extract(self, prompt) + .await + .map_err(|e| e.to_string()) + } +} + +pub fn build_security_extractor(model: M) -> impl SecurityExtract { + ExtractorBuilder::::new(model) + .preamble(SECURITY_PREAMBLE) + .build() +} + +pub fn build_capability_extractor(model: M) -> impl CapabilityExtract { + ExtractorBuilder::::new(model) + .preamble(&build_capability_system_prompt()) + .build() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileEntry { + pub path: String, + pub content: String, +} + +fn into_tuples(files: Vec) -> Vec<(String, String)> { + files.into_iter().map(|f| (f.path, f.content)).collect() +} + +#[derive(Deserialize)] +pub struct DelegateSecurityArgs { + pub files: Vec, + pub context: String, +} + +pub struct DelegateSecurityReviewTool { + pub(crate) extractor: Arc, +} + +impl Tool for DelegateSecurityReviewTool { + const NAME: &'static str = "delegate_security_review"; + type Error = DelegationError; + type Args = DelegateSecurityArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "delegate_security_review".to_string(), + description: "Delegate security analysis to a specialist. Provide the relevant source files you've read and context notes about what you observed. Returns structured security findings.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "files": { + "type": "array", + "description": "Source files to analyze", + "items": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "File path" }, + "content": { "type": "string", "description": "File content" } + }, + "required": ["path", "content"] + } + }, + "context": { + "type": "string", + "description": "Context notes: what the tool does, entry points, data flow observations, concerning patterns" + } + }, + "required": ["files", "context"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let prompt = build_security_prompt(&into_tuples(args.files), &args.context); + + let result = self + .extractor + .extract(&prompt) + .await + .map_err(|e| DelegationError(format!("Security analysis failed: {e}")))?; + + serde_json::to_string_pretty(&result).map_err(|e| DelegationError(e.to_string())) + } +} + +#[derive(Deserialize)] +pub struct DelegateCapabilityArgs { + pub files: Vec, + pub context: String, +} + +pub struct DelegateCapabilityInferenceTool { + pub(crate) extractor: Arc, +} + +impl Tool for DelegateCapabilityInferenceTool { + const NAME: &'static str = "delegate_capability_inference"; + type Error = DelegationError; + type Args = DelegateCapabilityArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "delegate_capability_inference".to_string(), + description: "Delegate capability inference to a specialist. Provide source files and context. Returns inferred capabilities with confidence scores.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "files": { + "type": "array", + "description": "Source files to analyze", + "items": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "File path" }, + "content": { "type": "string", "description": "File content" } + }, + "required": ["path", "content"] + } + }, + "context": { + "type": "string", + "description": "Context notes: what the tool does, key observations about its behavior" + } + }, + "required": ["files", "context"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let prompt = build_capability_prompt(&into_tuples(args.files), &args.context); + + let result = self + .extractor + .extract(&prompt) + .await + .map_err(|e| DelegationError(format!("Capability inference failed: {e}")))?; + + serde_json::to_string_pretty(&result).map_err(|e| DelegationError(e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn delegate_security_args_deserializes() { + let json = r#"{ + "files": [{"path": "index.js", "content": "exec()"}], + "context": "Server with exec" + }"#; + let args: DelegateSecurityArgs = serde_json::from_str(json).unwrap(); + assert_eq!(args.files.len(), 1); + assert_eq!(args.files[0].path, "index.js"); + assert_eq!(args.context, "Server with exec"); + } + + #[test] + fn delegate_capability_args_deserializes() { + let json = r#"{ + "files": [{"path": "main.py", "content": "import os"}], + "context": "Reads env vars" + }"#; + let args: DelegateCapabilityArgs = serde_json::from_str(json).unwrap(); + assert_eq!(args.files.len(), 1); + assert_eq!(args.files[0].path, "main.py"); + } + + #[test] + fn delegate_security_tool_has_correct_name() { + assert_eq!(DelegateSecurityReviewTool::NAME, "delegate_security_review"); + } + + #[test] + fn delegate_capability_tool_has_correct_name() { + assert_eq!( + DelegateCapabilityInferenceTool::NAME, + "delegate_capability_inference" + ); + } + + #[test] + fn file_entry_serializes() { + let entry = FileEntry { + path: "test.js".to_string(), + content: "code".to_string(), + }; + let json = serde_json::to_string(&entry).unwrap(); + assert!(json.contains("test.js")); + assert!(json.contains("code")); + } + + #[test] + fn delegate_security_args_with_multiple_files() { + let json = r#"{ + "files": [ + {"path": "a.js", "content": "exec()"}, + {"path": "b.js", "content": "spawn()"} + ], + "context": "Two files with dangerous calls" + }"#; + let args: DelegateSecurityArgs = serde_json::from_str(json).unwrap(); + assert_eq!(args.files.len(), 2); + assert_eq!(args.files[1].path, "b.js"); + } + + #[tokio::test] + async fn delegate_security_tool_definition_is_valid() { + struct FakeExtractor; + #[async_trait] + impl SecurityExtract for FakeExtractor { + async fn extract(&self, _prompt: &str) -> Result { + Ok(SecurityExtractionResult { findings: vec![] }) + } + } + + let tool = DelegateSecurityReviewTool { + extractor: Arc::new(FakeExtractor), + }; + let def = tool.definition(String::new()).await; + assert_eq!(def.name, "delegate_security_review"); + assert!(!def.description.is_empty()); + } + + #[tokio::test] + async fn delegate_capability_tool_definition_is_valid() { + struct FakeExtractor; + #[async_trait] + impl CapabilityExtract for FakeExtractor { + async fn extract(&self, _prompt: &str) -> Result { + Ok(CapabilityExtractionResult { + capabilities: vec![], + warnings: vec![], + }) + } + } + + let tool = DelegateCapabilityInferenceTool { + extractor: Arc::new(FakeExtractor), + }; + let def = tool.definition(String::new()).await; + assert_eq!(def.name, "delegate_capability_inference"); + assert!(!def.description.is_empty()); + } + + fn mock_security_finding() -> crate::specialists::SecurityFindingEntry { + crate::specialists::SecurityFindingEntry { + severity: "high".to_string(), + category: "command_injection".to_string(), + file: "index.js".to_string(), + line: Some(1), + evidence: "exec()".to_string(), + explanation: "Dangerous".to_string(), + } + } + + #[tokio::test] + async fn delegate_security_calls_extractor() { + struct MockExtractor; + #[async_trait] + impl SecurityExtract for MockExtractor { + async fn extract(&self, prompt: &str) -> Result { + assert!(prompt.contains("index.js")); + assert!(prompt.contains("exec()")); + Ok(SecurityExtractionResult { + findings: vec![mock_security_finding()], + }) + } + } + + let tool = DelegateSecurityReviewTool { + extractor: Arc::new(MockExtractor), + }; + let result = tool + .call(DelegateSecurityArgs { + files: vec![FileEntry { + path: "index.js".to_string(), + content: "exec()".to_string(), + }], + context: "test".to_string(), + }) + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.contains("command_injection")); + } + + #[tokio::test] + async fn delegate_capability_calls_extractor() { + struct MockExtractor; + #[async_trait] + impl CapabilityExtract for MockExtractor { + async fn extract(&self, prompt: &str) -> Result { + assert!(prompt.contains("main.py")); + Ok(CapabilityExtractionResult { + capabilities: vec![crate::specialists::CapabilityEntryResult { + name: "filesystem_read".to_string(), + confidence: 0.9, + evidence: "open() call".to_string(), + }], + warnings: vec![], + }) + } + } + + let tool = DelegateCapabilityInferenceTool { + extractor: Arc::new(MockExtractor), + }; + let result = tool + .call(DelegateCapabilityArgs { + files: vec![FileEntry { + path: "main.py".to_string(), + content: "open('file.txt')".to_string(), + }], + context: "reads files".to_string(), + }) + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.contains("filesystem_read")); + } +} diff --git a/argus/crates/argus-analysis/src/dep_audit.rs b/argus/crates/argus-analysis/src/dep_audit.rs new file mode 100644 index 0000000..855c4a1 --- /dev/null +++ b/argus/crates/argus-analysis/src/dep_audit.rs @@ -0,0 +1,281 @@ +use serde::{Deserialize, Serialize}; + +use crate::registry_types::{DependencyReport, DependencyVulnerability, FlaggedDependency}; +use crate::sandbox::ManifestKind; + +#[derive(Debug, Serialize)] +struct OsvQuery { + package: OsvPackage, +} + +#[derive(Debug, Serialize)] +struct OsvPackage { + name: String, + ecosystem: String, +} + +#[derive(Debug, Deserialize)] +struct OsvResponse { + #[serde(default)] + vulns: Vec, +} + +#[derive(Debug, Deserialize)] +struct OsvVuln { + id: String, + summary: Option, + #[serde(default)] + database_specific: Option, +} + +#[derive(Debug, Deserialize)] +struct OsvDatabaseSpecific { + severity: Option, +} + +const OSV_API_URL: &str = "https://api.osv.dev/v1/query"; + +fn ecosystem_for_manifest(kind: &ManifestKind) -> &'static str { + match kind { + ManifestKind::PackageJson => "npm", + ManifestKind::CargoToml => "crates.io", + ManifestKind::PyprojectToml => "PyPI", + ManifestKind::GoMod => "Go", + } +} + +#[derive(Debug, Clone)] +pub struct ParsedDependency { + pub name: String, + pub version: Option, +} + +pub fn parse_npm_dependencies(content: &str) -> Vec { + let parsed: serde_json::Value = match serde_json::from_str(content) { + Ok(v) => v, + Err(_) => return vec![], + }; + + let mut deps = Vec::new(); + for section in ["dependencies", "devDependencies"] { + if let Some(obj) = parsed.get(section).and_then(|v| v.as_object()) { + for (name, version) in obj { + deps.push(ParsedDependency { + name: name.clone(), + version: version.as_str().map(|s| s.to_string()), + }); + } + } + } + deps +} + +pub fn parse_cargo_dependencies(content: &str) -> Vec { + let parsed: toml::Value = match toml::from_str(content) { + Ok(v) => v, + Err(_) => return vec![], + }; + + let mut deps = Vec::new(); + for section in ["dependencies", "dev-dependencies", "build-dependencies"] { + if let Some(table) = parsed.get(section).and_then(|v| v.as_table()) { + for (name, value) in table { + let version = match value { + toml::Value::String(v) => Some(v.clone()), + toml::Value::Table(t) => t + .get("version") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + _ => None, + }; + deps.push(ParsedDependency { + name: name.clone(), + version, + }); + } + } + } + deps +} + +pub fn parse_dependencies(kind: &ManifestKind, content: &str) -> Vec { + match kind { + ManifestKind::PackageJson => parse_npm_dependencies(content), + ManifestKind::CargoToml => parse_cargo_dependencies(content), + _ => vec![], + } +} + +const FLAGGED_PATTERNS: &[(&str, &str)] = &[ + ("child_process", "Can execute arbitrary system commands"), + ("node-pty", "Provides pseudo-terminal access"), + ("puppeteer", "Browser automation with full page access"), + ("playwright", "Browser automation with full page access"), + ("keytar", "Access to OS credential store"), + ("node-keychain", "Access to OS credential store"), +]; + +pub fn flag_suspicious_packages(deps: &[ParsedDependency]) -> Vec { + deps.iter() + .filter_map(|dep| { + FLAGGED_PATTERNS + .iter() + .find(|(pattern, _)| dep.name.contains(pattern)) + .map(|(_, reason)| FlaggedDependency { + package: dep.name.clone(), + reason: reason.to_string(), + }) + }) + .collect() +} + +pub async fn query_osv( + client: &reqwest::Client, + package: &str, + ecosystem: &str, +) -> std::result::Result, reqwest::Error> { + let query = OsvQuery { + package: OsvPackage { + name: package.to_string(), + ecosystem: ecosystem.to_string(), + }, + }; + + let response: OsvResponse = client + .post(OSV_API_URL) + .json(&query) + .send() + .await? + .json() + .await?; + + Ok(response + .vulns + .into_iter() + .map(|v| DependencyVulnerability { + package: package.to_string(), + id: v.id, + severity: v + .database_specific + .and_then(|d| d.severity) + .unwrap_or_else(|| "unknown".to_string()), + description: v.summary.unwrap_or_default(), + }) + .collect()) +} + +pub async fn audit_dependencies( + manifest_kind: &ManifestKind, + manifest_content: &str, +) -> DependencyReport { + let deps = parse_dependencies(manifest_kind, manifest_content); + let ecosystem = ecosystem_for_manifest(manifest_kind); + let flagged = flag_suspicious_packages(&deps); + + let client = reqwest::Client::new(); + let mut vulnerabilities = Vec::new(); + + for dep in &deps { + match query_osv(&client, &dep.name, ecosystem).await { + Ok(vulns) => vulnerabilities.extend(vulns), + Err(e) => { + tracing::warn!(package = %dep.name, error = %e, "OSV query failed"); + } + } + } + + DependencyReport { + total: deps.len(), + vulnerabilities, + flagged, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_npm_deps_from_package_json() { + let content = r#"{ + "dependencies": { + "express": "^4.18.2", + "@anthropic-ai/sdk": "^0.1.0" + }, + "devDependencies": { + "typescript": "^5.0.0" + } + }"#; + let deps = parse_npm_dependencies(content); + assert_eq!(deps.len(), 3); + assert!(deps.iter().any(|d| d.name == "express")); + assert!(deps.iter().any(|d| d.name == "@anthropic-ai/sdk")); + assert!(deps.iter().any(|d| d.name == "typescript")); + } + + #[test] + fn parse_cargo_deps_from_cargo_toml() { + let content = r#" +[dependencies] +serde = "1.0" +tokio = { version = "1", features = ["full"] } + +[dev-dependencies] +tempfile = "3" +"#; + let deps = parse_cargo_dependencies(content); + assert_eq!(deps.len(), 3); + assert!( + deps.iter() + .any(|d| d.name == "serde" && d.version.as_deref() == Some("1.0")) + ); + assert!( + deps.iter() + .any(|d| d.name == "tokio" && d.version.as_deref() == Some("1")) + ); + } + + #[test] + fn parse_empty_manifest_returns_empty() { + assert!(parse_npm_dependencies("{}").is_empty()); + assert!(parse_npm_dependencies("invalid json").is_empty()); + assert!(parse_cargo_dependencies("").is_empty()); + } + + #[test] + fn flag_suspicious_npm_packages() { + let deps = vec![ + ParsedDependency { + name: "express".to_string(), + version: None, + }, + ParsedDependency { + name: "child_process".to_string(), + version: None, + }, + ParsedDependency { + name: "puppeteer".to_string(), + version: None, + }, + ParsedDependency { + name: "lodash".to_string(), + version: None, + }, + ]; + let flagged = flag_suspicious_packages(&deps); + assert_eq!(flagged.len(), 2); + assert!(flagged.iter().any(|f| f.package == "child_process")); + assert!(flagged.iter().any(|f| f.package == "puppeteer")); + } + + #[test] + fn ecosystem_mapping() { + assert_eq!(ecosystem_for_manifest(&ManifestKind::PackageJson), "npm"); + assert_eq!( + ecosystem_for_manifest(&ManifestKind::CargoToml), + "crates.io" + ); + assert_eq!(ecosystem_for_manifest(&ManifestKind::PyprojectToml), "PyPI"); + assert_eq!(ecosystem_for_manifest(&ManifestKind::GoMod), "Go"); + } +} diff --git a/argus/crates/argus-analysis/src/error.rs b/argus/crates/argus-analysis/src/error.rs new file mode 100644 index 0000000..2364b18 --- /dev/null +++ b/argus/crates/argus-analysis/src/error.rs @@ -0,0 +1,24 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AnalysisError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("LLM inference error: {0}")] + LlmInference(String), + #[error("Parse error: {0}")] + Parse(String), + #[error("Invalid path: {0}")] + InvalidPath(String), +} + +pub type Result = std::result::Result; + +pub fn client_error(provider: &str, e: E) -> AnalysisError { + AnalysisError::LlmInference(format!("{} client: {}", provider, e)) +} + +pub fn get_env_var(name: &str) -> Result { + std::env::var(name) + .map_err(|_| AnalysisError::LlmInference(format!("Environment variable {} not set", name))) +} diff --git a/argus/crates/argus-analysis/src/install_detect.rs b/argus/crates/argus-analysis/src/install_detect.rs new file mode 100644 index 0000000..ecaeb1b --- /dev/null +++ b/argus/crates/argus-analysis/src/install_detect.rs @@ -0,0 +1,148 @@ +use crate::registry_types::InstallDetection; +use crate::sandbox::ManifestKind; + +pub fn detect_install(kind: &ManifestKind, content: &str) -> InstallDetection { + match kind { + ManifestKind::PackageJson => detect_npm_install(content), + ManifestKind::CargoToml => detect_cargo_install(content), + ManifestKind::PyprojectToml => detect_pip_install(content), + ManifestKind::GoMod => detect_go_install(content), + } +} + +fn detect_npm_install(content: &str) -> InstallDetection { + let parsed: serde_json::Value = match serde_json::from_str(content) { + Ok(v) => v, + Err(_) => serde_json::Value::Object(Default::default()), + }; + let name = parsed + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or("unknown") + .to_string(); + let version = parsed + .get("version") + .and_then(|v| v.as_str()) + .unwrap_or("0.0.0") + .to_string(); + + InstallDetection { + method: "npm".to_string(), + package: name, + version, + source: "package.json".to_string(), + } +} + +fn detect_cargo_install(content: &str) -> InstallDetection { + let parsed: toml::Value = match toml::from_str(content) { + Ok(v) => v, + Err(_) => toml::Value::Table(Default::default()), + }; + let package = parsed.get("package").and_then(|v| v.as_table()); + let name = package + .and_then(|p| p.get("name")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown") + .to_string(); + let version = package + .and_then(|p| p.get("version")) + .and_then(|v| v.as_str()) + .unwrap_or("0.0.0") + .to_string(); + + InstallDetection { + method: "cargo".to_string(), + package: name, + version, + source: "Cargo.toml".to_string(), + } +} + +fn detect_pip_install(content: &str) -> InstallDetection { + let parsed: toml::Value = match toml::from_str(content) { + Ok(v) => v, + Err(_) => toml::Value::Table(Default::default()), + }; + let project = parsed.get("project").and_then(|v| v.as_table()); + let name = project + .and_then(|p| p.get("name")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown") + .to_string(); + let version = project + .and_then(|p| p.get("version")) + .and_then(|v| v.as_str()) + .unwrap_or("0.0.0") + .to_string(); + + InstallDetection { + method: "pip".to_string(), + package: name, + version, + source: "pyproject.toml".to_string(), + } +} + +fn detect_go_install(content: &str) -> InstallDetection { + let module_line = content + .lines() + .find(|l| l.starts_with("module ")) + .and_then(|l| l.strip_prefix("module ")) + .unwrap_or("unknown"); + + InstallDetection { + method: "go".to_string(), + package: module_line.trim().to_string(), + version: "0.0.0".to_string(), + source: "go.mod".to_string(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn detect_npm_from_package_json() { + let content = r#"{"name": "@thedotmack/claude-mem", "version": "1.2.3"}"#; + let result = detect_install(&ManifestKind::PackageJson, content); + assert_eq!(result.method, "npm"); + assert_eq!(result.package, "@thedotmack/claude-mem"); + assert_eq!(result.version, "1.2.3"); + assert_eq!(result.source, "package.json"); + } + + #[test] + fn detect_cargo_from_cargo_toml() { + let content = "[package]\nname = \"my-tool\"\nversion = \"0.5.0\"\n"; + let result = detect_install(&ManifestKind::CargoToml, content); + assert_eq!(result.method, "cargo"); + assert_eq!(result.package, "my-tool"); + assert_eq!(result.version, "0.5.0"); + } + + #[test] + fn detect_pip_from_pyproject() { + let content = "[project]\nname = \"my-python-tool\"\nversion = \"2.0.0\"\n"; + let result = detect_install(&ManifestKind::PyprojectToml, content); + assert_eq!(result.method, "pip"); + assert_eq!(result.package, "my-python-tool"); + assert_eq!(result.version, "2.0.0"); + } + + #[test] + fn detect_go_from_go_mod() { + let content = "module github.com/user/tool\n\ngo 1.21\n"; + let result = detect_install(&ManifestKind::GoMod, content); + assert_eq!(result.method, "go"); + assert_eq!(result.package, "github.com/user/tool"); + } + + #[test] + fn handles_malformed_manifest_gracefully() { + let result = detect_install(&ManifestKind::PackageJson, "not json"); + assert_eq!(result.method, "npm"); + assert_eq!(result.package, "unknown"); + } +} diff --git a/argus/crates/argus-analysis/src/lib.rs b/argus/crates/argus-analysis/src/lib.rs new file mode 100644 index 0000000..61b4be1 --- /dev/null +++ b/argus/crates/argus-analysis/src/lib.rs @@ -0,0 +1,30 @@ +mod analyzer; +mod delegation; +mod dep_audit; +mod error; +mod install_detect; +mod orchestrator; +mod registry_analysis; +mod registry_extraction; +mod registry_types; +mod sandbox; +mod specialists; +mod tools; +mod types; + +pub use analyzer::Analyzer; +pub use dep_audit::{ParsedDependency, audit_dependencies, parse_dependencies}; +pub use error::{AnalysisError, Result}; +pub use install_detect::detect_install; +pub use orchestrator::{OrchestratorConfig, OrchestratorResult}; +pub use registry_analysis::RegistryAnalyzer; +pub use registry_extraction::RegistryCapabilityResult; +pub use registry_types::{ + CapabilityFinding, DependencyReport, DependencyVulnerability, Evidence, FlaggedDependency, + InstallDetection, RegistryAnalysisReport, +}; +pub use sandbox::{DirEntry, ManifestKind, SearchResult, ToolContext}; +pub use types::{ + AnalysisMetadata, AnalysisReport, DependencyFinding, FindingSeverity, InferredCapability, + SecurityFinding, +}; diff --git a/argus/crates/argus-analysis/src/orchestrator.rs b/argus/crates/argus-analysis/src/orchestrator.rs new file mode 100644 index 0000000..2837042 --- /dev/null +++ b/argus/crates/argus-analysis/src/orchestrator.rs @@ -0,0 +1,323 @@ +use crate::delegation::{ + DelegateCapabilityInferenceTool, DelegateSecurityReviewTool, build_capability_extractor, + build_security_extractor, +}; +use crate::sandbox::ToolContext; +use crate::specialists::{CapabilityEntryResult, SecurityFindingEntry, format_capability_list}; +use crate::tools::{ListDirectoryTool, ReadFileTool, ReadManifestTool, SearchCodeTool}; +use async_trait::async_trait; +use rig::agent::{Agent, AgentBuilder}; +use rig::completion::CompletionModel; +use rig::extractor::ExtractorBuilder; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct OrchestratorConfig { + pub max_turns: usize, + pub max_tokens: u64, +} + +impl Default for OrchestratorConfig { + fn default() -> Self { + Self { + max_turns: 15, + max_tokens: 16384, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(description = "Complete analysis result synthesized by the orchestrator")] +pub struct OrchestratorResult { + #[schemars(description = "Inferred capabilities with confidence scores and evidence")] + pub capabilities: Vec, + #[schemars(description = "Security vulnerabilities found during analysis")] + pub security_findings: Vec, + #[schemars(description = "Warnings or concerns about the analysis")] + pub warnings: Vec, + #[schemars(description = "Files that were explored during analysis")] + pub files_explored: Vec, +} + +const RESULT_EXTRACTOR_PREAMBLE: &str = r#"You are a structured data extractor. Given a security analysis report, extract all findings into the structured format. + +Extract every capability, security finding, all warnings, and the complete list of files explored. Be thorough and do not omit any findings from the report."#; + +#[async_trait] +pub(crate) trait ResultExtract: Send + Sync { + async fn extract(&self, text: &str) -> Result; +} + +#[async_trait] +impl ResultExtract for rig::extractor::Extractor +where + M: CompletionModel + Send + Sync, +{ + async fn extract(&self, text: &str) -> Result { + rig::extractor::Extractor::extract(self, text) + .await + .map_err(|e| e.to_string()) + } +} + +pub fn build_result_extractor(model: M) -> impl ResultExtract { + ExtractorBuilder::::new(model) + .preamble(RESULT_EXTRACTOR_PREAMBLE) + .build() +} + +pub fn system_prompt() -> String { + format!( + r#"You are a security analysis orchestrator. Your job is to thoroughly analyze a tool or MCP server for security risks and capabilities. + +You have filesystem tools to explore the codebase: +- list_directory: List files in a directory +- read_file: Read a file's content (supports offset/limit for large files) +- search_code: Regex search across all source files +- read_manifest: Read the project manifest (package.json, Cargo.toml, etc.) + +Once you understand the codebase, delegate to specialist agents: +- delegate_security_review: Analyze files for security vulnerabilities +- delegate_capability_inference: Infer capabilities + +When delegating, always provide: +1. The actual file contents you've read (not just paths) +2. Context notes explaining what you found (entry points, data flow, concerning patterns) + +Strategy: +1. Read the manifest to understand what the tool does and its dependencies +2. List the root directory to see the project structure +3. Read entry points and follow imports to understand the architecture +4. Search for dangerous patterns: exec, eval, spawn, fetch, fs.write, child_process, subprocess, etc. +5. Delegate to the security specialist with the most relevant files and your observations +6. Delegate to the capability specialist with key files and context +7. After both specialists respond, write a thorough summary of your complete findings + +Known capability taxonomy: +{capabilities} + +Your final message must be a thorough summary covering: +- Every capability detected with its confidence level and supporting evidence +- Every security finding with severity, file location, and explanation +- Any warnings or concerns +- A complete list of all files you explored"#, + capabilities = format_capability_list(), + ) +} + +pub fn build_user_prompt(target_path: &str) -> String { + format!( + "Analyze the tool/MCP at: {target_path}\n\n\ + Explore the codebase, understand its structure and purpose, \ + then delegate to specialist agents for detailed security and capability analysis. \ + Synthesize their results into your final assessment." + ) +} + +pub fn build_orchestrator( + model: M, + ctx: Arc, + config: &OrchestratorConfig, +) -> (Agent, usize) +where + M: CompletionModel + Clone + 'static, +{ + let security_extractor = Arc::new(build_security_extractor(model.clone())); + let capability_extractor = Arc::new(build_capability_extractor(model.clone())); + + let agent = AgentBuilder::new(model) + .preamble(&system_prompt()) + .max_tokens(config.max_tokens) + .tool(ListDirectoryTool { ctx: ctx.clone() }) + .tool(ReadFileTool { ctx: ctx.clone() }) + .tool(SearchCodeTool { ctx: ctx.clone() }) + .tool(ReadManifestTool { ctx }) + .tool(DelegateSecurityReviewTool { + extractor: security_extractor, + }) + .tool(DelegateCapabilityInferenceTool { + extractor: capability_extractor, + }) + .build(); + + (agent, config.max_turns) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn orchestrator_config_defaults() { + let config = OrchestratorConfig::default(); + assert_eq!(config.max_turns, 15); + assert_eq!(config.max_tokens, 16384); + } + + #[test] + fn orchestrator_config_custom_values() { + let config = OrchestratorConfig { + max_turns: 30, + max_tokens: 100_000, + }; + assert_eq!(config.max_turns, 30); + assert_eq!(config.max_tokens, 100_000); + } + + #[test] + fn system_prompt_covers_exploration_tools() { + let prompt = system_prompt(); + assert!(prompt.contains("list_directory")); + assert!(prompt.contains("read_file")); + assert!(prompt.contains("search_code")); + assert!(prompt.contains("read_manifest")); + } + + #[test] + fn system_prompt_covers_delegation_tools() { + let prompt = system_prompt(); + assert!(prompt.contains("delegate_security_review")); + assert!(prompt.contains("delegate_capability_inference")); + } + + #[test] + fn system_prompt_includes_capability_catalog() { + let prompt = system_prompt(); + assert!(prompt.contains("filesystem_read")); + assert!(prompt.contains("network_egress")); + assert!(prompt.contains("execution_shell")); + } + + #[test] + fn system_prompt_describes_expected_output() { + let prompt = system_prompt(); + assert!(prompt.contains("capability")); + assert!(prompt.contains("security finding")); + assert!(prompt.contains("files you explored")); + } + + #[test] + fn user_prompt_includes_target_path() { + let prompt = build_user_prompt("/path/to/tool"); + assert!(prompt.contains("/path/to/tool")); + } + + #[test] + fn user_prompt_mentions_delegation() { + let prompt = build_user_prompt("/test"); + assert!(prompt.contains("delegate")); + assert!(prompt.contains("specialist")); + } + + #[test] + fn orchestrator_result_deserializes() { + let json = r#"{ + "capabilities": [ + {"name": "filesystem_read", "confidence": 0.9, "evidence": "fs.readFile call"} + ], + "security_findings": [ + { + "severity": "high", + "category": "command_injection", + "file": "index.js", + "line": 42, + "evidence": "exec(input)", + "explanation": "User input flows to exec" + } + ], + "warnings": ["Uses deprecated API"], + "files_explored": ["index.js", "package.json"] + }"#; + let result: OrchestratorResult = serde_json::from_str(json).unwrap(); + assert_eq!(result.capabilities.len(), 1); + assert_eq!(result.capabilities[0].name, "filesystem_read"); + assert_eq!(result.security_findings.len(), 1); + assert_eq!(result.security_findings[0].category, "command_injection"); + assert_eq!(result.warnings.len(), 1); + assert_eq!(result.files_explored.len(), 2); + } + + #[test] + fn orchestrator_result_empty() { + let json = r#"{ + "capabilities": [], + "security_findings": [], + "warnings": [], + "files_explored": [] + }"#; + let result: OrchestratorResult = serde_json::from_str(json).unwrap(); + assert!(result.capabilities.is_empty()); + assert!(result.security_findings.is_empty()); + } + + #[test] + fn orchestrator_result_serializes_roundtrip() { + let result = OrchestratorResult { + capabilities: vec![CapabilityEntryResult { + name: "network_egress".to_string(), + confidence: 0.85, + evidence: "fetch() call".to_string(), + }], + security_findings: vec![], + warnings: vec!["No tests found".to_string()], + files_explored: vec!["src/main.ts".to_string()], + }; + let json = serde_json::to_string(&result).unwrap(); + let deserialized: OrchestratorResult = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.capabilities[0].name, "network_egress"); + } + + #[test] + fn orchestrator_result_schema_generated() { + let schema = schemars::schema_for!(OrchestratorResult); + let json = serde_json::to_string(&schema).unwrap(); + assert!(json.contains("capabilities")); + assert!(json.contains("security_findings")); + assert!(json.contains("files_explored")); + } + + #[test] + fn result_extractor_preamble_describes_task() { + assert!(RESULT_EXTRACTOR_PREAMBLE.contains("extract")); + assert!(RESULT_EXTRACTOR_PREAMBLE.contains("structured")); + } + + #[tokio::test] + async fn result_extract_trait_with_mock() { + struct MockExtractor; + #[async_trait] + impl ResultExtract for MockExtractor { + async fn extract(&self, text: &str) -> Result { + assert!(text.contains("test input")); + Ok(OrchestratorResult { + capabilities: vec![], + security_findings: vec![], + warnings: vec![], + files_explored: vec![], + }) + } + } + + let extractor = MockExtractor; + let result = extractor.extract("test input").await.unwrap(); + assert!(result.capabilities.is_empty()); + } + + #[tokio::test] + async fn result_extract_trait_error_propagation() { + struct FailingExtractor; + #[async_trait] + impl ResultExtract for FailingExtractor { + async fn extract(&self, _text: &str) -> Result { + Err("extraction failed".to_string()) + } + } + + let extractor = FailingExtractor; + let result = extractor.extract("anything").await; + assert!(result.is_err()); + assert!(result.unwrap_err().contains("extraction failed")); + } +} diff --git a/argus/crates/argus-analysis/src/registry_analysis.rs b/argus/crates/argus-analysis/src/registry_analysis.rs new file mode 100644 index 0000000..75db870 --- /dev/null +++ b/argus/crates/argus-analysis/src/registry_analysis.rs @@ -0,0 +1,402 @@ +use std::path::Path; +use std::sync::Arc; + +use argus_config::ExtractionProviderConfig; +use async_trait::async_trait; +use rig::agent::AgentBuilder; +use rig::client::{CompletionClient, Nothing}; +use rig::completion::CompletionModel; +use rig::extractor::ExtractorBuilder; +use rig::providers::{anthropic, gemini, ollama, openai, openrouter}; +use tracing::info; + +use crate::error::{AnalysisError, Result, client_error, get_env_var}; +use crate::orchestrator::OrchestratorConfig; +use crate::registry_extraction::RegistryCapabilityResult; +use crate::registry_types::{CapabilityFinding, Evidence}; +use crate::sandbox::ToolContext; +use crate::tools::{ListDirectoryTool, ReadFileTool, ReadManifestTool, SearchCodeTool}; + +const REGISTRY_SYSTEM_PROMPT: &str = r#"You are a security analyst evaluating an MCP tool for inclusion in a curated registry. + +Your job is to determine exactly what capabilities this tool requires to operate. Analyze the source code to find every: + +1. **File system access**: What paths does it read from or write to? Look for fs operations, database files, config directories. +2. **Network access**: What domains/endpoints does it connect to? Look for HTTP clients, API SDKs, WebSocket connections. +3. **Network listening**: Does it bind to any ports? Look for server creation, express/http.listen, bind calls. +4. **Process execution**: Does it spawn child processes? Look for exec, spawn, child_process, Command. +5. **Secret/credential usage**: What API keys or tokens does it need? Look for env var reads, config file key references. + +For each capability, use the scoped capability format: +- filesystem:read() +- filesystem:write() +- network:egress() +- network:listen() +- process:exec() +- secret:env() + +For EVERY capability, you MUST provide evidence: the exact file, line number, code snippet, and reasoning. + +Use the exploration tools to read the manifest first, then entry points, then search for patterns like 'http', 'fetch', 'fs.', 'exec', 'spawn', 'listen', 'bind', 'env', 'secret', 'key', 'token'. + +Be thorough. Missing a capability means the sandbox will block the tool at runtime."#; + +const REGISTRY_EXTRACTOR_PREAMBLE: &str = r#"You are a structured data extractor. Given a security analysis report about an MCP tool, extract all inferred capabilities with their evidence into the structured format. + +Be thorough: extract every capability mentioned with full evidence chains. Each capability must have at least one evidence entry with file path, line number, code snippet, and reasoning."#; + +fn build_registry_user_prompt(target_path: &str) -> String { + format!( + "Analyze the tool source code at: {target_path}\n\n\ + Explore the codebase systematically:\n\ + 1. Read the manifest (package.json, Cargo.toml, etc.)\n\ + 2. List the root directory structure\n\ + 3. Read entry points and main modules\n\ + 4. Search for network, filesystem, process, and secret patterns\n\ + 5. Provide your complete findings with evidence for each capability." + ) +} + +#[async_trait] +pub(crate) trait RegistryCapabilityExtract: Send + Sync { + async fn extract(&self, text: &str) -> std::result::Result; +} + +#[async_trait] +impl RegistryCapabilityExtract for rig::extractor::Extractor +where + M: CompletionModel + Send + Sync, +{ + async fn extract(&self, text: &str) -> std::result::Result { + rig::extractor::Extractor::extract(self, text) + .await + .map_err(|e| e.to_string()) + } +} + +fn build_registry_extractor(model: M) -> impl RegistryCapabilityExtract { + ExtractorBuilder::::new(model) + .preamble(REGISTRY_EXTRACTOR_PREAMBLE) + .build() +} + +#[async_trait] +trait RegistryAnalyzeOrchestrate: Send + Sync { + async fn orchestrate( + &self, + ctx: Arc, + config: &OrchestratorConfig, + ) -> Result>; +} + +struct RegistryOrchestrateImpl { + model: M, +} + +#[async_trait] +impl RegistryAnalyzeOrchestrate for RegistryOrchestrateImpl +where + M: CompletionModel + Clone + Send + Sync + 'static, +{ + async fn orchestrate( + &self, + ctx: Arc, + config: &OrchestratorConfig, + ) -> Result> { + use rig::completion::Prompt; + + let target_path = ctx.target_root().to_string_lossy().into_owned(); + + let agent = AgentBuilder::new(self.model.clone()) + .preamble(REGISTRY_SYSTEM_PROMPT) + .max_tokens(config.max_tokens) + .tool(ListDirectoryTool { ctx: ctx.clone() }) + .tool(ReadFileTool { ctx: ctx.clone() }) + .tool(SearchCodeTool { ctx: ctx.clone() }) + .tool(ReadManifestTool { ctx }) + .build(); + + let user_prompt = build_registry_user_prompt(&target_path); + + info!( + max_turns = config.max_turns, + "Starting registry analysis orchestrator" + ); + let response = agent + .prompt(&user_prompt) + .multi_turn(config.max_turns) + .await + .map_err(|e| AnalysisError::LlmInference(e.to_string()))?; + + let extractor = build_registry_extractor(self.model.clone()); + let result = extractor + .extract(&response) + .await + .map_err(AnalysisError::LlmInference)?; + + Ok(result + .capabilities + .into_iter() + .map(|c| CapabilityFinding { + capability: c.capability, + evidence: c + .evidence + .into_iter() + .map(|e| Evidence { + file: e.file, + line: e.line, + snippet: e.snippet, + reasoning: e.reasoning, + }) + .collect(), + }) + .collect()) + } +} + +pub struct RegistryAnalyzer { + inner: Arc, + config: OrchestratorConfig, +} + +impl std::fmt::Debug for RegistryAnalyzer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RegistryAnalyzer").finish_non_exhaustive() + } +} + +macro_rules! make_registry_orchestrate { + ($client:expr, $model:expr) => { + Arc::new(RegistryOrchestrateImpl { + model: $client.completion_model($model), + }) + }; +} + +impl RegistryAnalyzer { + pub fn new(model: M) -> Self + where + M: CompletionModel + Clone + Send + Sync + 'static, + { + Self { + inner: Arc::new(RegistryOrchestrateImpl { model }), + config: OrchestratorConfig::default(), + } + } + + pub fn from_config(config: &ExtractionProviderConfig) -> Result { + let inner: Arc = match config { + ExtractionProviderConfig::OpenRouter { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: openrouter::Client = + openrouter::Client::new(&api_key).map_err(|e| client_error("OpenRouter", e))?; + let completion_model = client.completion_model(model).with_strict_tools(); + Arc::new(RegistryOrchestrateImpl { + model: completion_model, + }) + } + ExtractionProviderConfig::Ollama { + model, base_url, .. + } => { + let client: ollama::Client = ollama::Client::builder() + .api_key(Nothing) + .base_url(base_url) + .build() + .map_err(|e| client_error("Ollama", e))?; + make_registry_orchestrate!(client, model) + } + ExtractionProviderConfig::OpenAI { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: openai::Client = + openai::Client::new(&api_key).map_err(|e| client_error("OpenAI", e))?; + make_registry_orchestrate!(client, model) + } + ExtractionProviderConfig::Anthropic { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: anthropic::Client = anthropic::Client::builder() + .api_key(&api_key) + .anthropic_version(anthropic::completion::ANTHROPIC_VERSION_LATEST) + .build() + .map_err(|e| client_error("Anthropic", e))?; + make_registry_orchestrate!(client, model) + } + ExtractionProviderConfig::Gemini { + model, api_key_env, .. + } => { + let api_key = get_env_var(api_key_env)?; + let client: gemini::Client = + gemini::Client::new(&api_key).map_err(|e| client_error("Gemini", e))?; + make_registry_orchestrate!(client, model) + } + }; + + Ok(Self { + inner, + config: OrchestratorConfig::default(), + }) + } + + pub fn with_orchestrator_config(mut self, config: OrchestratorConfig) -> Self { + self.config = config; + self + } + + pub async fn infer_capabilities(&self, source_dir: &Path) -> Result> { + if !source_dir.exists() { + return Err(AnalysisError::InvalidPath(format!( + "Path does not exist: {}", + source_dir.display() + ))); + } + let ctx = Arc::new(ToolContext::new(source_dir)?); + self.inner.orchestrate(ctx, &self.config).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn registry_system_prompt_covers_capability_types() { + assert!(REGISTRY_SYSTEM_PROMPT.contains("filesystem:read")); + assert!(REGISTRY_SYSTEM_PROMPT.contains("network:egress")); + assert!(REGISTRY_SYSTEM_PROMPT.contains("process:exec")); + assert!(REGISTRY_SYSTEM_PROMPT.contains("secret:env")); + } + + #[test] + fn registry_user_prompt_includes_target_path() { + let prompt = build_registry_user_prompt("/path/to/tool"); + assert!(prompt.contains("/path/to/tool")); + } + + #[test] + fn registry_user_prompt_describes_exploration_strategy() { + let prompt = build_registry_user_prompt("/test"); + assert!(prompt.contains("manifest")); + assert!(prompt.contains("entry points")); + assert!(prompt.contains("Search for")); + } + + #[tokio::test] + async fn infer_capabilities_rejects_missing_path() { + struct MockOrchestrate; + #[async_trait] + impl RegistryAnalyzeOrchestrate for MockOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result> { + Ok(vec![]) + } + } + + let analyzer = RegistryAnalyzer { + inner: Arc::new(MockOrchestrate), + config: OrchestratorConfig::default(), + }; + + let result = analyzer + .infer_capabilities(Path::new("/nonexistent/path/99999")) + .await; + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), AnalysisError::InvalidPath(_))); + } + + #[tokio::test] + async fn infer_capabilities_with_mock_orchestrator() { + struct MockOrchestrate; + #[async_trait] + impl RegistryAnalyzeOrchestrate for MockOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result> { + Ok(vec![CapabilityFinding { + capability: "network:egress(api.anthropic.com)".to_string(), + evidence: vec![Evidence { + file: "src/client.ts".to_string(), + line: 10, + snippet: "new Anthropic()".to_string(), + reasoning: "Creates API client".to_string(), + }], + }]) + } + } + + let analyzer = RegistryAnalyzer { + inner: Arc::new(MockOrchestrate), + config: OrchestratorConfig::default(), + }; + + let temp_dir = tempfile::TempDir::new().unwrap(); + std::fs::write(temp_dir.path().join("index.js"), "// placeholder").unwrap(); + + let caps = analyzer.infer_capabilities(temp_dir.path()).await.unwrap(); + assert_eq!(caps.len(), 1); + assert_eq!(caps[0].capability, "network:egress(api.anthropic.com)"); + assert_eq!(caps[0].evidence.len(), 1); + } + + #[tokio::test] + async fn infer_capabilities_handles_orchestrator_failure() { + struct FailingOrchestrate; + #[async_trait] + impl RegistryAnalyzeOrchestrate for FailingOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result> { + Err(AnalysisError::LlmInference("timeout".to_string())) + } + } + + let analyzer = RegistryAnalyzer { + inner: Arc::new(FailingOrchestrate), + config: OrchestratorConfig::default(), + }; + + let temp_dir = tempfile::TempDir::new().unwrap(); + std::fs::write(temp_dir.path().join("main.py"), "import os").unwrap(); + + let result = analyzer.infer_capabilities(temp_dir.path()).await; + assert!(result.is_err()); + } + + #[test] + fn registry_analyzer_with_config() { + struct MockOrchestrate; + #[async_trait] + impl RegistryAnalyzeOrchestrate for MockOrchestrate { + async fn orchestrate( + &self, + _ctx: Arc, + _config: &OrchestratorConfig, + ) -> Result> { + Ok(vec![]) + } + } + + let analyzer = RegistryAnalyzer { + inner: Arc::new(MockOrchestrate), + config: OrchestratorConfig::default(), + } + .with_orchestrator_config(OrchestratorConfig { + max_turns: 30, + max_tokens: 32768, + }); + + assert_eq!(analyzer.config.max_turns, 30); + assert_eq!(analyzer.config.max_tokens, 32768); + } +} diff --git a/argus/crates/argus-analysis/src/registry_extraction.rs b/argus/crates/argus-analysis/src/registry_extraction.rs new file mode 100644 index 0000000..cf8d016 --- /dev/null +++ b/argus/crates/argus-analysis/src/registry_extraction.rs @@ -0,0 +1,73 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(inline)] +pub struct RegistryEvidenceEntry { + #[schemars(description = "Relative file path where the capability was observed")] + pub file: String, + #[schemars(description = "Line number in the file")] + pub line: usize, + #[schemars(description = "Relevant code snippet")] + pub snippet: String, + #[schemars(description = "Why this code implies the capability")] + pub reasoning: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(inline)] +pub struct RegistryCapabilityEntry { + #[schemars( + description = "Scoped capability string, e.g. 'filesystem:read(~/.config/)' or 'network:egress(api.github.com)'" + )] + pub capability: String, + #[schemars(description = "Evidence from source code supporting this inference")] + pub evidence: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(description = "Result of analyzing a tool's source code for registry capabilities")] +pub struct RegistryCapabilityResult { + #[schemars(description = "Inferred capabilities with evidence")] + pub capabilities: Vec, + #[schemars(description = "Security concerns or warnings")] + pub warnings: Vec, + #[schemars(description = "Files explored during analysis")] + pub files_explored: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn registry_capability_result_has_valid_schema() { + let schema = schemars::schema_for!(RegistryCapabilityResult); + let json = serde_json::to_string_pretty(&schema).unwrap(); + assert!( + !json.contains("\"$ref\""), + "Schema must be inlined, no $ref" + ); + } + + #[test] + fn registry_capability_result_round_trips() { + let result = RegistryCapabilityResult { + capabilities: vec![RegistryCapabilityEntry { + capability: "network:egress(api.anthropic.com)".to_string(), + evidence: vec![RegistryEvidenceEntry { + file: "src/client.ts".to_string(), + line: 10, + snippet: "fetch('https://api.anthropic.com')".to_string(), + reasoning: "Direct HTTP call to Anthropic API".to_string(), + }], + }], + warnings: vec!["Broad file system access".to_string()], + files_explored: vec!["src/client.ts".to_string()], + }; + let json = serde_json::to_string(&result).unwrap(); + let deser: RegistryCapabilityResult = serde_json::from_str(&json).unwrap(); + assert_eq!(deser.capabilities.len(), 1); + assert_eq!(deser.warnings.len(), 1); + } +} diff --git a/argus/crates/argus-analysis/src/registry_types.rs b/argus/crates/argus-analysis/src/registry_types.rs new file mode 100644 index 0000000..85ce5a8 --- /dev/null +++ b/argus/crates/argus-analysis/src/registry_types.rs @@ -0,0 +1,124 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Evidence { + pub file: String, + pub line: usize, + pub snippet: String, + pub reasoning: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CapabilityFinding { + pub capability: String, + pub evidence: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DependencyVulnerability { + pub package: String, + pub id: String, + pub severity: String, + pub description: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FlaggedDependency { + pub package: String, + pub reason: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DependencyReport { + pub total: usize, + pub vulnerabilities: Vec, + pub flagged: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstallDetection { + pub method: String, + pub package: String, + pub version: String, + pub source: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RegistryAnalysisReport { + pub tool: String, + pub analyzed_at: String, + pub pinned_ref: String, + pub capabilities: Vec, + pub dependencies: DependencyReport, + pub install_detection: InstallDetection, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn capability_finding_serializes_to_json() { + let finding = CapabilityFinding { + capability: "network:egress(api.anthropic.com)".to_string(), + evidence: vec![Evidence { + file: "src/services/compress.ts".to_string(), + line: 42, + snippet: "const client = new Anthropic({apiKey})".to_string(), + reasoning: "Instantiates Anthropic SDK client".to_string(), + }], + }; + let json = serde_json::to_string(&finding).unwrap(); + let deser: CapabilityFinding = serde_json::from_str(&json).unwrap(); + assert_eq!(deser.capability, "network:egress(api.anthropic.com)"); + assert_eq!(deser.evidence.len(), 1); + assert_eq!(deser.evidence[0].line, 42); + } + + #[test] + fn registry_analysis_report_round_trips() { + let report = RegistryAnalysisReport { + tool: "claude-mem".to_string(), + analyzed_at: "2026-03-02T12:00:00Z".to_string(), + pinned_ref: "abc123".to_string(), + capabilities: vec![], + dependencies: DependencyReport { + total: 0, + vulnerabilities: vec![], + flagged: vec![], + }, + install_detection: InstallDetection { + method: "npm".to_string(), + package: "@thedotmack/claude-mem".to_string(), + version: "1.0.0".to_string(), + source: "package.json".to_string(), + }, + }; + let json = serde_json::to_string_pretty(&report).unwrap(); + let deser: RegistryAnalysisReport = serde_json::from_str(&json).unwrap(); + assert_eq!(deser.tool, "claude-mem"); + assert_eq!(deser.install_detection.method, "npm"); + } + + #[test] + fn dependency_report_with_vulnerabilities() { + let report = DependencyReport { + total: 28, + vulnerabilities: vec![DependencyVulnerability { + package: "lodash".to_string(), + id: "CVE-2021-23337".to_string(), + severity: "high".to_string(), + description: "Prototype pollution".to_string(), + }], + flagged: vec![FlaggedDependency { + package: "@anthropic-ai/claude-agent-sdk".to_string(), + reason: "Network-capable SDK".to_string(), + }], + }; + let json = serde_json::to_string(&report).unwrap(); + let deser: DependencyReport = serde_json::from_str(&json).unwrap(); + assert_eq!(deser.total, 28); + assert_eq!(deser.vulnerabilities.len(), 1); + assert_eq!(deser.flagged.len(), 1); + } +} diff --git a/argus/crates/argus-analysis/src/sandbox.rs b/argus/crates/argus-analysis/src/sandbox.rs new file mode 100644 index 0000000..ebaf17c --- /dev/null +++ b/argus/crates/argus-analysis/src/sandbox.rs @@ -0,0 +1,386 @@ +use content_inspector::{ContentType, inspect}; +use grep_regex::RegexMatcher; +use grep_searcher::sinks::UTF8; +use grep_searcher::{BinaryDetection, SearcherBuilder}; +use ignore::WalkBuilder; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +const DEFAULT_MAX_FILE_SIZE: usize = 102_400; +const DEFAULT_MAX_RESULTS: usize = 50; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DirEntry { + pub name: String, + pub size: u64, + pub is_dir: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchResult { + pub file: String, + pub line: u32, + pub text: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ManifestKind { + PackageJson, + CargoToml, + PyprojectToml, + GoMod, +} + +pub struct ToolContext { + target_root: PathBuf, + max_file_size: usize, + max_results: usize, +} + +impl ToolContext { + pub fn new(root: &Path) -> std::io::Result { + let target_root = root.canonicalize()?; + Ok(Self { + target_root, + max_file_size: DEFAULT_MAX_FILE_SIZE, + max_results: DEFAULT_MAX_RESULTS, + }) + } + + pub fn with_max_file_size(mut self, max_file_size: usize) -> Self { + self.max_file_size = max_file_size; + self + } + + pub fn target_root(&self) -> &Path { + &self.target_root + } + + pub fn resolve_path(&self, rel: &str) -> std::io::Result { + if rel.starts_with('/') { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "absolute paths not allowed", + )); + } + + let joined = self.target_root.join(rel); + let resolved = joined.canonicalize()?; + + if !resolved.starts_with(&self.target_root) { + return Err(std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "path escapes target root", + )); + } + + Ok(resolved) + } + + pub fn list_directory(&self, rel: &str) -> std::io::Result> { + let dir = self.resolve_path(rel)?; + let mut entries = Vec::new(); + + for result in std::fs::read_dir(&dir)? { + let entry = result?; + let name = entry.file_name().to_string_lossy().to_string(); + + if name.starts_with('.') { + continue; + } + + let metadata = entry.metadata()?; + entries.push(DirEntry { + name, + size: metadata.len(), + is_dir: metadata.is_dir(), + }); + } + + entries.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(entries) + } + + pub fn read_file( + &self, + rel: &str, + offset: Option, + limit: Option, + ) -> std::io::Result { + let path = self.resolve_path(rel)?; + + let raw = std::fs::read(&path)?; + if inspect(&raw) == ContentType::BINARY { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "binary file detected", + )); + } + + let full_content = String::from_utf8(raw) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + + let sliced = slice_lines(&full_content, offset, limit); + + if sliced.len() > self.max_file_size { + let mut truncated = sliced[..self.max_file_size].to_string(); + truncated.push_str("\n... [truncated]"); + Ok(truncated) + } else { + Ok(sliced) + } + } + + pub fn search_code( + &self, + pattern: &str, + glob_filter: Option<&str>, + ) -> std::io::Result> { + let matcher = RegexMatcher::new(pattern) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()))?; + + let mut builder = WalkBuilder::new(&self.target_root); + builder.hidden(true).git_ignore(true).git_global(false); + + if let Some(glob) = glob_filter { + let mut types_builder = ignore::types::TypesBuilder::new(); + types_builder.add("custom", glob).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()) + })?; + types_builder.select("custom"); + let types = types_builder.build().map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidInput, e.to_string()) + })?; + builder.types(types); + } + + let mut results = Vec::new(); + let mut searcher = SearcherBuilder::new() + .binary_detection(BinaryDetection::quit(0)) + .build(); + + for entry in builder.build().flatten() { + if results.len() >= self.max_results { + break; + } + + let path = entry.path(); + if !path.is_file() { + continue; + } + + let rel_path = path + .strip_prefix(&self.target_root) + .unwrap_or(path) + .to_string_lossy() + .to_string(); + + let remaining = self.max_results - results.len(); + let mut file_results = Vec::new(); + + let _ = searcher.search_path( + &matcher, + path, + UTF8(|line_num, line| { + if file_results.len() < remaining { + file_results.push(SearchResult { + file: rel_path.clone(), + line: line_num as u32, + text: line.trim().to_string(), + }); + } + Ok(true) + }), + ); + + results.extend(file_results); + } + + Ok(results) + } + + pub fn detect_manifest(&self) -> Option { + let checks = [ + ("package.json", ManifestKind::PackageJson), + ("Cargo.toml", ManifestKind::CargoToml), + ("pyproject.toml", ManifestKind::PyprojectToml), + ("go.mod", ManifestKind::GoMod), + ]; + + for (file, kind) in checks { + if self.target_root.join(file).exists() { + return Some(kind); + } + } + + None + } +} + +fn slice_lines(content: &str, offset: Option, limit: Option) -> String { + if offset.is_none() && limit.is_none() { + return content.to_string(); + } + + let lines: Vec<&str> = content.lines().collect(); + let start = offset.unwrap_or(0); + if start >= lines.len() { + return String::new(); + } + + let end = limit + .map(|l| (start + l).min(lines.len())) + .unwrap_or(lines.len()); + + lines[start..end].join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + + fn setup_test_dir() -> tempfile::TempDir { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("index.js"), "console.log('hello');").unwrap(); + std::fs::create_dir(dir.path().join("src")).unwrap(); + std::fs::write(dir.path().join("src/main.js"), "export default {};").unwrap(); + std::fs::write(dir.path().join("package.json"), r#"{"name":"test"}"#).unwrap(); + std::fs::create_dir(dir.path().join(".git")).unwrap(); + std::fs::write(dir.path().join(".git/HEAD"), "ref: refs/heads/main").unwrap(); + dir + } + + #[test] + fn resolve_path_within_root() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert!(ctx.resolve_path("index.js").is_ok()); + assert!(ctx.resolve_path("src/main.js").is_ok()); + } + + #[test] + fn resolve_path_rejects_traversal() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert!(ctx.resolve_path("../../../etc/passwd").is_err()); + assert!(ctx.resolve_path("/etc/passwd").is_err()); + } + + #[test] + fn list_directory_skips_hidden() { + let dir = setup_test_dir(); + std::fs::create_dir(dir.path().join("node_modules")).unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let entries = ctx.list_directory(".").unwrap(); + assert!(!entries.iter().any(|e| e.name == ".git")); + assert!(entries.iter().any(|e| e.name == "index.js")); + assert!(entries.iter().any(|e| e.name == "src")); + assert!(entries.iter().any(|e| e.name == "node_modules")); + } + + #[test] + fn list_directory_sorted() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let entries = ctx.list_directory(".").unwrap(); + let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + let mut sorted = names.clone(); + sorted.sort(); + assert_eq!(names, sorted); + } + + #[test] + fn read_file_returns_content() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let content = ctx.read_file("index.js", None, None).unwrap(); + assert!(content.contains("console.log")); + } + + #[test] + fn read_file_truncates_large() { + let dir = setup_test_dir(); + let large = "x\n".repeat(100_000); + std::fs::write(dir.path().join("big.js"), &large).unwrap(); + let ctx = ToolContext::new(dir.path()) + .unwrap() + .with_max_file_size(1024); + let content = ctx.read_file("big.js", None, None).unwrap(); + assert!(content.len() < large.len()); + assert!(content.contains("[truncated]")); + } + + #[test] + fn read_file_rejects_binary_by_content() { + let dir = setup_test_dir(); + std::fs::write(dir.path().join("image.png"), [0u8; 100]).unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert!(ctx.read_file("image.png", None, None).is_err()); + } + + #[test] + fn read_file_rejects_binary_disguised_as_text() { + let dir = setup_test_dir(); + let mut binary_content = b"looks like javascript\n".to_vec(); + binary_content.extend_from_slice(&[0u8; 50]); + binary_content.extend_from_slice(b"more text"); + std::fs::write(dir.path().join("sneaky.js"), &binary_content).unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert!(ctx.read_file("sneaky.js", None, None).is_err()); + } + + #[test] + fn read_file_with_offset_and_limit() { + let dir = setup_test_dir(); + std::fs::write( + dir.path().join("lines.txt"), + "line1\nline2\nline3\nline4\nline5\n", + ) + .unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let content = ctx.read_file("lines.txt", Some(1), Some(2)).unwrap(); + assert!(content.contains("line2")); + assert!(content.contains("line3")); + assert!(!content.contains("line1")); + assert!(!content.contains("line4")); + } + + #[test] + fn search_code_finds_pattern() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let results = ctx.search_code("console", None).unwrap(); + assert!(!results.is_empty()); + assert!(results[0].file.contains("index.js")); + } + + #[test] + fn search_code_skips_hidden_dirs() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + let results = ctx.search_code("refs/heads", None).unwrap(); + assert!(results.is_empty()); + } + + #[test] + fn detect_manifest_finds_package_json() { + let dir = setup_test_dir(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert_eq!(ctx.detect_manifest(), Some(ManifestKind::PackageJson)); + } + + #[test] + fn detect_manifest_finds_cargo_toml() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("Cargo.toml"), "[package]").unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert_eq!(ctx.detect_manifest(), Some(ManifestKind::CargoToml)); + } + + #[test] + fn detect_manifest_none_for_empty() { + let dir = tempfile::TempDir::new().unwrap(); + let ctx = ToolContext::new(dir.path()).unwrap(); + assert_eq!(ctx.detect_manifest(), None); + } +} diff --git a/argus/crates/argus-analysis/src/specialists.rs b/argus/crates/argus-analysis/src/specialists.rs new file mode 100644 index 0000000..7a4844e --- /dev/null +++ b/argus/crates/argus-analysis/src/specialists.rs @@ -0,0 +1,272 @@ +use argus_kernel::CapKind; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub const SECURITY_PREAMBLE: &str = r#"You are a security specialist analyzing source code for vulnerabilities and risks. + +Focus on: +- Injection vectors: command injection (exec, spawn, system), SQL injection, XSS, template injection +- Dangerous data flows: user input reaching exec/eval/spawn, unsanitized path construction +- Credential exposure: hardcoded secrets, API keys in source, insecure storage +- File system risks: path traversal, arbitrary file write, symlink attacks +- Network risks: SSRF, open redirects, insecure TLS configuration +- Deserialization: unsafe deserialization of untrusted data (pickle, yaml.load, JSON.parse with eval) +- Privilege escalation: setuid, capability manipulation, sudo invocation + +For each finding, provide: +- Severity: low, medium, high, or critical +- Category: a short label (e.g. "command_injection", "path_traversal", "hardcoded_secret") +- The exact file and line where the issue occurs +- Evidence: the specific code snippet +- Explanation: why this is a risk and how it could be exploited + +Be precise. Only report findings you have concrete evidence for."#; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(description = "Result of security analysis on source code")] +pub struct SecurityExtractionResult { + #[schemars(description = "List of security findings with severity, evidence, and explanation")] + pub findings: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(inline)] +pub struct SecurityFindingEntry { + #[schemars(description = "Severity: low, medium, high, or critical")] + pub severity: String, + #[schemars(description = "Short category label (e.g. command_injection, path_traversal)")] + pub category: String, + #[schemars(description = "File path where the finding occurs")] + pub file: String, + #[schemars(description = "Line number if known")] + pub line: Option, + #[schemars(description = "The specific code snippet as evidence")] + pub evidence: String, + #[schemars(description = "Why this is a risk and how it could be exploited")] + pub explanation: String, +} + +fn build_file_prompt(header: &str, files: &[(String, String)], context: &str) -> String { + let mut prompt = String::from(header); + prompt.push_str("\n\n"); + + if !context.is_empty() { + prompt.push_str(&format!("Context from orchestrator: {context}\n\n")); + } + + for (path, content) in files { + prompt.push_str(&format!("=== {path} ===\n{content}\n\n")); + } + + prompt +} + +pub fn build_security_prompt(files: &[(String, String)], context: &str) -> String { + build_file_prompt( + "Analyze these source files for security vulnerabilities.", + files, + context, + ) +} + +pub fn format_capability_list() -> String { + CapKind::all() + .iter() + .map(|k: &CapKind| format!("- {}", k.as_str())) + .collect::>() + .join("\n") +} + +pub fn build_capability_system_prompt() -> String { + format!( + r#"You are a capability analysis specialist inferring what permissions a tool or MCP requires. + +Analyze the provided source code and determine what capabilities the tool needs. + +Capability taxonomy: +{capabilities} + +For each capability: +- Provide a confidence score (0.0-1.0) based on how certain the evidence is +- Cite specific code as evidence"#, + capabilities = format_capability_list(), + ) +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(description = "Result of capability inference from source code")] +pub struct CapabilityExtractionResult { + #[schemars(description = "List of inferred capabilities with confidence and evidence")] + pub capabilities: Vec, + #[schemars(description = "Security warnings or concerns")] + pub warnings: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(inline)] +pub struct CapabilityEntryResult { + #[schemars( + description = "Capability name from the taxonomy (e.g. filesystem_read, network_egress)" + )] + pub name: String, + #[schemars(description = "Confidence score from 0.0 to 1.0")] + pub confidence: f32, + #[schemars(description = "Code evidence supporting this capability")] + pub evidence: String, +} + +pub fn build_capability_prompt(files: &[(String, String)], context: &str) -> String { + build_file_prompt( + "Analyze these source files to infer required capabilities.", + files, + context, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn security_extraction_result_deserializes() { + let json = r#"{ + "findings": [ + { + "severity": "high", + "category": "command_injection", + "file": "index.js", + "line": 47, + "evidence": "exec(req.body.cmd)", + "explanation": "User input passed to exec without sanitization" + } + ] + }"#; + let result: SecurityExtractionResult = serde_json::from_str(json).unwrap(); + assert_eq!(result.findings.len(), 1); + assert_eq!(result.findings[0].category, "command_injection"); + assert_eq!(result.findings[0].severity, "high"); + assert_eq!(result.findings[0].line, Some(47)); + } + + #[test] + fn security_extraction_empty_findings() { + let json = r#"{"findings": []}"#; + let result: SecurityExtractionResult = serde_json::from_str(json).unwrap(); + assert!(result.findings.is_empty()); + } + + #[test] + fn capability_extraction_result_deserializes() { + let json = r#"{ + "capabilities": [ + {"name": "network_egress", "confidence": 0.9, "evidence": "fetch() call in api.js:12"} + ], + "warnings": ["Makes external API calls to unknown domains"] + }"#; + let result: CapabilityExtractionResult = serde_json::from_str(json).unwrap(); + assert_eq!(result.capabilities.len(), 1); + assert_eq!(result.capabilities[0].name, "network_egress"); + assert_eq!(result.warnings.len(), 1); + } + + #[test] + fn capability_extraction_empty() { + let json = r#"{ + "capabilities": [], + "warnings": [] + }"#; + let result: CapabilityExtractionResult = serde_json::from_str(json).unwrap(); + assert!(result.capabilities.is_empty()); + } + + #[test] + fn security_prompt_includes_context_and_files() { + let files = vec![ + ("index.js".to_string(), "exec(input)".to_string()), + ("util.js".to_string(), "spawn('sh')".to_string()), + ]; + let prompt = build_security_prompt(&files, "Express server with exec calls"); + assert!(prompt.contains("index.js")); + assert!(prompt.contains("exec(input)")); + assert!(prompt.contains("util.js")); + assert!(prompt.contains("spawn('sh')")); + assert!(prompt.contains("Express server")); + } + + #[test] + fn security_prompt_handles_empty_context() { + let files = vec![("main.py".to_string(), "os.system(cmd)".to_string())]; + let prompt = build_security_prompt(&files, ""); + assert!(!prompt.contains("Context from orchestrator")); + assert!(prompt.contains("main.py")); + } + + #[test] + fn capability_prompt_includes_context_and_files() { + let files = vec![( + "main.py".to_string(), + "import os\nos.environ['KEY']".to_string(), + )]; + let prompt = build_capability_prompt(&files, "Python script reading env vars"); + assert!(prompt.contains("main.py")); + assert!(prompt.contains("import os")); + assert!(prompt.contains("Python script")); + } + + #[test] + fn capability_prompt_handles_empty_context() { + let files = vec![("lib.rs".to_string(), "use std::fs;".to_string())]; + let prompt = build_capability_prompt(&files, ""); + assert!(!prompt.contains("Context from orchestrator")); + assert!(prompt.contains("lib.rs")); + } + + #[test] + fn security_preamble_covers_key_categories() { + assert!(SECURITY_PREAMBLE.contains("command injection")); + assert!(SECURITY_PREAMBLE.contains("path traversal")); + assert!(SECURITY_PREAMBLE.contains("SSRF")); + assert!(SECURITY_PREAMBLE.contains("deserialization")); + assert!(SECURITY_PREAMBLE.contains("Credential")); + } + + #[test] + fn capability_system_prompt_uses_kernel_capkinds() { + let prompt = build_capability_system_prompt(); + assert!(prompt.contains("filesystem_read")); + assert!(prompt.contains("filesystem_write")); + assert!(prompt.contains("network_egress")); + assert!(prompt.contains("execution_shell")); + assert!(prompt.contains("credentials")); + assert!(prompt.contains("database_read")); + } + + #[test] + fn format_capability_list_contains_all_capkinds() { + let list = format_capability_list(); + for kind in CapKind::all() { + assert!( + list.contains(kind.as_str()), + "Missing capability: {}", + kind.as_str() + ); + } + } + + #[test] + fn security_finding_entry_schema_generated() { + let schema = schemars::schema_for!(SecurityExtractionResult); + let json = serde_json::to_string(&schema).unwrap(); + assert!(json.contains("findings")); + assert!(json.contains("severity")); + assert!(json.contains("category")); + } + + #[test] + fn capability_extraction_schema_generated() { + let schema = schemars::schema_for!(CapabilityExtractionResult); + let json = serde_json::to_string(&schema).unwrap(); + assert!(json.contains("capabilities")); + assert!(json.contains("warnings")); + } +} diff --git a/argus/crates/argus-analysis/src/tools.rs b/argus/crates/argus-analysis/src/tools.rs new file mode 100644 index 0000000..5635b83 --- /dev/null +++ b/argus/crates/argus-analysis/src/tools.rs @@ -0,0 +1,371 @@ +use crate::sandbox::ToolContext; +use rig::completion::ToolDefinition; +use rig::tool::Tool; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::Arc; + +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +pub struct ToolError(String); + +impl From for ToolError { + fn from(e: std::io::Error) -> Self { + Self(e.to_string()) + } +} + +impl From for ToolError { + fn from(e: serde_json::Error) -> Self { + Self(e.to_string()) + } +} + +#[derive(Deserialize)] +pub struct ListDirectoryArgs { + pub path: String, +} + +pub struct ListDirectoryTool { + pub ctx: Arc, +} + +impl Tool for ListDirectoryTool { + const NAME: &'static str = "list_directory"; + type Error = ToolError; + type Args = ListDirectoryArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "list_directory".to_string(), + description: "List files and directories at a path relative to the target root. Returns names, sizes, and whether each entry is a directory.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Relative path to list (use '.' for root)" + } + }, + "required": ["path"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let entries = self.ctx.list_directory(&args.path)?; + Ok(serde_json::to_string_pretty(&entries)?) + } +} + +#[derive(Deserialize)] +pub struct ReadFileArgs { + pub path: String, + pub offset: Option, + pub limit: Option, +} + +pub struct ReadFileTool { + pub ctx: Arc, +} + +impl Tool for ReadFileTool { + const NAME: &'static str = "read_file"; + type Error = ToolError; + type Args = ReadFileArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "read_file".to_string(), + description: "Read the contents of a file. Supports optional line offset and limit for pagination. Binary files are rejected.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Relative path to the file" + }, + "offset": { + "type": "integer", + "description": "Line number to start reading from (0-indexed)" + }, + "limit": { + "type": "integer", + "description": "Maximum number of lines to return" + } + }, + "required": ["path"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + Ok(self.ctx.read_file(&args.path, args.offset, args.limit)?) + } +} + +#[derive(Deserialize)] +pub struct SearchCodeArgs { + pub pattern: String, + pub glob: Option, +} + +pub struct SearchCodeTool { + pub ctx: Arc, +} + +impl Tool for SearchCodeTool { + const NAME: &'static str = "search_code"; + type Error = ToolError; + type Args = SearchCodeArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "search_code".to_string(), + description: "Search for a regex pattern across all source files. Returns matching file paths, line numbers, and matching text. Optionally filter by glob pattern.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regex pattern to search for (e.g. 'exec\\(', 'eval', 'require\\(')" + }, + "glob": { + "type": "string", + "description": "Optional glob to filter files (e.g. '*.js', '*.py')" + } + }, + "required": ["pattern"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + let results = self.ctx.search_code(&args.pattern, args.glob.as_deref())?; + Ok(serde_json::to_string_pretty(&results)?) + } +} + +#[derive(Deserialize)] +pub struct ReadManifestArgs {} + +#[derive(Serialize)] +struct ManifestOutput { + kind: String, + content: String, +} + +pub struct ReadManifestTool { + pub ctx: Arc, +} + +impl Tool for ReadManifestTool { + const NAME: &'static str = "read_manifest"; + type Error = ToolError; + type Args = ReadManifestArgs; + type Output = String; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: "read_manifest".to_string(), + description: "Detect and read the project manifest (package.json, Cargo.toml, pyproject.toml, or go.mod). Returns the manifest type and its contents.".to_string(), + parameters: json!({ + "type": "object", + "properties": {}, + }), + } + } + + async fn call(&self, _args: Self::Args) -> Result { + use crate::sandbox::ManifestKind; + + let kind = self + .ctx + .detect_manifest() + .ok_or_else(|| ToolError("No manifest file found".to_string()))?; + + let filename = match kind { + ManifestKind::PackageJson => "package.json", + ManifestKind::CargoToml => "Cargo.toml", + ManifestKind::PyprojectToml => "pyproject.toml", + ManifestKind::GoMod => "go.mod", + }; + + let content = self.ctx.read_file(filename, None, None)?; + let output = ManifestOutput { + kind: filename.to_string(), + content, + }; + + Ok(serde_json::to_string_pretty(&output)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sandbox::ToolContext; + + #[test] + fn list_dir_tool_has_correct_name() { + assert_eq!(ListDirectoryTool::NAME, "list_directory"); + } + + #[test] + fn read_file_tool_has_correct_name() { + assert_eq!(ReadFileTool::NAME, "read_file"); + } + + #[test] + fn search_code_tool_has_correct_name() { + assert_eq!(SearchCodeTool::NAME, "search_code"); + } + + #[test] + fn read_manifest_tool_has_correct_name() { + assert_eq!(ReadManifestTool::NAME, "read_manifest"); + } + + #[tokio::test] + async fn list_dir_tool_calls_sandbox() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("test.js"), "hello").unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ListDirectoryTool { ctx }; + let result = tool + .call(ListDirectoryArgs { + path: ".".to_string(), + }) + .await; + assert!(result.is_ok()); + assert!(result.unwrap().contains("test.js")); + } + + #[tokio::test] + async fn read_file_tool_calls_sandbox() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("test.js"), "console.log('hi');").unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ReadFileTool { ctx }; + let result = tool + .call(ReadFileArgs { + path: "test.js".to_string(), + offset: None, + limit: None, + }) + .await; + assert!(result.is_ok()); + assert!(result.unwrap().contains("console.log")); + } + + #[tokio::test] + async fn search_code_tool_calls_sandbox() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("test.js"), "exec('rm -rf')").unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = SearchCodeTool { ctx }; + let result = tool + .call(SearchCodeArgs { + pattern: "exec".to_string(), + glob: None, + }) + .await; + assert!(result.is_ok()); + assert!(result.unwrap().contains("exec")); + } + + #[tokio::test] + async fn read_manifest_tool_finds_package_json() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write( + dir.path().join("package.json"), + r#"{"name":"test","version":"1.0.0"}"#, + ) + .unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ReadManifestTool { ctx }; + let result = tool.call(ReadManifestArgs {}).await; + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.contains("package.json")); + assert!(output.contains("test")); + } + + #[tokio::test] + async fn read_manifest_tool_returns_error_when_no_manifest() { + let dir = tempfile::TempDir::new().unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ReadManifestTool { ctx }; + let result = tool.call(ReadManifestArgs {}).await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn list_dir_tool_rejects_traversal() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write(dir.path().join("test.js"), "hello").unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ListDirectoryTool { ctx }; + let result = tool + .call(ListDirectoryArgs { + path: "../../etc".to_string(), + }) + .await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn read_file_tool_with_offset_and_limit() { + let dir = tempfile::TempDir::new().unwrap(); + std::fs::write( + dir.path().join("lines.txt"), + "line1\nline2\nline3\nline4\nline5\n", + ) + .unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + let tool = ReadFileTool { ctx }; + let result = tool + .call(ReadFileArgs { + path: "lines.txt".to_string(), + offset: Some(1), + limit: Some(2), + }) + .await; + assert!(result.is_ok()); + let content = result.unwrap(); + assert!(content.contains("line2")); + assert!(content.contains("line3")); + assert!(!content.contains("line1")); + } + + #[tokio::test] + async fn tool_definitions_have_descriptions() { + let dir = tempfile::TempDir::new().unwrap(); + let ctx = Arc::new(ToolContext::new(dir.path()).unwrap()); + + let list_def = ListDirectoryTool { ctx: ctx.clone() } + .definition(String::new()) + .await; + assert_eq!(list_def.name, "list_directory"); + assert!(!list_def.description.is_empty()); + + let read_def = ReadFileTool { ctx: ctx.clone() } + .definition(String::new()) + .await; + assert_eq!(read_def.name, "read_file"); + assert!(!read_def.description.is_empty()); + + let search_def = SearchCodeTool { ctx: ctx.clone() } + .definition(String::new()) + .await; + assert_eq!(search_def.name, "search_code"); + assert!(!search_def.description.is_empty()); + + let manifest_def = ReadManifestTool { ctx }.definition(String::new()).await; + assert_eq!(manifest_def.name, "read_manifest"); + assert!(!manifest_def.description.is_empty()); + } +} diff --git a/argus/crates/argus-analysis/src/types.rs b/argus/crates/argus-analysis/src/types.rs new file mode 100644 index 0000000..8f714bd --- /dev/null +++ b/argus/crates/argus-analysis/src/types.rs @@ -0,0 +1,139 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum FindingSeverity { + Low, + Medium, + High, + Critical, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SecurityFinding { + pub severity: FindingSeverity, + pub category: String, + pub file: String, + pub line: Option, + pub evidence: String, + pub explanation: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DependencyFinding { + pub package: String, + pub severity: FindingSeverity, + pub advisory: Option, + pub recommendation: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct AnalysisMetadata { + pub files_explored: Vec, + pub orchestrator_turns: u32, + pub specialists_invoked: Vec, + pub total_llm_calls: u32, + pub duration_ms: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InferredCapability { + pub name: String, + pub confidence: f32, + pub evidence: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct AnalysisReport { + pub path: PathBuf, + pub inferred_capabilities: Vec, + pub security_findings: Vec, + pub dependency_findings: Vec, + pub warnings: Vec, + pub approved: bool, + pub metadata: AnalysisMetadata, +} + +impl AnalysisReport { + pub fn new(path: PathBuf) -> Self { + Self { + path, + ..Default::default() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn inferred_capability_serializes() { + let cap = InferredCapability { + name: "filesystem_write".to_string(), + confidence: 0.95, + evidence: "Uses fs.writeFile".to_string(), + }; + let json = serde_json::to_string(&cap).unwrap(); + assert!(json.contains("filesystem_write")); + assert!(json.contains("0.95")); + } + + #[test] + fn security_finding_serializes() { + let finding = SecurityFinding { + severity: FindingSeverity::High, + category: "command_injection".to_string(), + file: "index.js".to_string(), + line: Some(47), + evidence: "exec(user_input)".to_string(), + explanation: "User input flows to exec".to_string(), + }; + let json = serde_json::to_string(&finding).unwrap(); + assert!(json.contains("command_injection")); + assert!(json.contains("47")); + } + + #[test] + fn dependency_finding_serializes() { + let finding = DependencyFinding { + package: "lodash".to_string(), + severity: FindingSeverity::Medium, + advisory: Some("CVE-2021-23337".to_string()), + recommendation: "Upgrade to 4.17.21".to_string(), + }; + let json = serde_json::to_string(&finding).unwrap(); + assert!(json.contains("lodash")); + assert!(json.contains("CVE-2021-23337")); + } + + #[test] + fn finding_severity_ordering() { + assert!(FindingSeverity::Critical > FindingSeverity::High); + assert!(FindingSeverity::High > FindingSeverity::Medium); + assert!(FindingSeverity::Medium > FindingSeverity::Low); + } + + #[test] + fn analysis_metadata_default() { + let meta = AnalysisMetadata::default(); + assert_eq!(meta.orchestrator_turns, 0); + assert!(meta.files_explored.is_empty()); + assert!(meta.specialists_invoked.is_empty()); + } + + #[test] + fn report_with_security_findings() { + let mut report = AnalysisReport::new(PathBuf::from("/test")); + report.security_findings.push(SecurityFinding { + severity: FindingSeverity::Critical, + category: "rce".to_string(), + file: "main.js".to_string(), + line: None, + evidence: "eval()".to_string(), + explanation: "Remote code execution".to_string(), + }); + assert!(!report.security_findings.is_empty()); + } +} diff --git a/argus/crates/argus-audit/Cargo.toml b/argus/crates/argus-audit/Cargo.toml new file mode 100644 index 0000000..3a826cb --- /dev/null +++ b/argus/crates/argus-audit/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "argus-audit" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +argus-kernel = { workspace = true } diff --git a/argus/crates/argus-audit/src/lib.rs b/argus/crates/argus-audit/src/lib.rs new file mode 100644 index 0000000..8a5d9cd --- /dev/null +++ b/argus/crates/argus-audit/src/lib.rs @@ -0,0 +1,3 @@ +mod memory; + +pub use memory::InMemoryEventStore; diff --git a/argus/crates/argus-audit/src/memory.rs b/argus/crates/argus-audit/src/memory.rs new file mode 100644 index 0000000..f4ce1b2 --- /dev/null +++ b/argus/crates/argus-audit/src/memory.rs @@ -0,0 +1,106 @@ +use std::sync::{Mutex, MutexGuard, PoisonError}; + +use argus_kernel::{EventStore, KernelError, KernelEvent}; + +fn lock_poisoned(_: PoisonError) -> KernelError { + KernelError::EventStoreError("lock poisoned".into()) +} + +pub struct InMemoryEventStore { + events: Mutex>, +} + +impl Default for InMemoryEventStore { + fn default() -> Self { + Self::new() + } +} + +impl InMemoryEventStore { + pub fn new() -> Self { + Self { + events: Mutex::new(Vec::new()), + } + } + + fn lock(&self) -> Result>, KernelError> { + self.events.lock().map_err(lock_poisoned) + } + + pub fn snapshot(&self) -> Result, KernelError> { + Ok(self.lock()?.clone()) + } +} + +impl EventStore for InMemoryEventStore { + fn append(&self, event: &KernelEvent) -> Result<(), KernelError> { + self.lock()?.push(event.clone()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use argus_kernel::{KernelAction, ToolId}; + + fn make_event(seq: u64, tool_name: &str) -> KernelEvent { + KernelEvent::new( + seq, + KernelAction::RegisterTool { + tool: ToolId::new(tool_name), + }, + ) + } + + #[test] + fn append_and_snapshot() { + let store = InMemoryEventStore::new(); + store.append(&make_event(1, "tool-a")).unwrap(); + let snap = store.snapshot().unwrap(); + assert_eq!(snap.len(), 1); + assert_eq!(snap[0].sequence, 1); + } + + #[test] + fn preserves_ordering() { + let store = InMemoryEventStore::new(); + store.append(&make_event(1, "first")).unwrap(); + store.append(&make_event(2, "second")).unwrap(); + store.append(&make_event(3, "third")).unwrap(); + let snap = store.snapshot().unwrap(); + assert_eq!(snap.len(), 3); + assert_eq!(snap[0].sequence, 1); + assert_eq!(snap[2].sequence, 3); + } + + #[test] + fn snapshot_is_a_clone() { + let store = InMemoryEventStore::new(); + store.append(&make_event(1, "a")).unwrap(); + let snap = store.snapshot().unwrap(); + store.append(&make_event(2, "b")).unwrap(); + assert_eq!(snap.len(), 1); + assert_eq!(store.snapshot().unwrap().len(), 2); + } + + #[test] + fn concurrent_appends() { + use std::sync::Arc; + use std::thread; + + let store = Arc::new(InMemoryEventStore::new()); + let mut handles = vec![]; + for i in 0..10 { + let s = Arc::clone(&store); + handles.push(thread::spawn(move || { + s.append(&make_event(i, &format!("tool-{i}"))).unwrap(); + })); + } + for h in handles { + h.join().unwrap(); + } + assert_eq!(store.snapshot().unwrap().len(), 10); + } +} diff --git a/argus/crates/argus-config/Cargo.toml b/argus/crates/argus-config/Cargo.toml new file mode 100644 index 0000000..87aada0 --- /dev/null +++ b/argus/crates/argus-config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "argus-config" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +toml = { workspace = true } +thiserror = { workspace = true } diff --git a/argus/crates/argus-config/src/extraction.rs b/argus/crates/argus-config/src/extraction.rs new file mode 100644 index 0000000..b0a90f3 --- /dev/null +++ b/argus/crates/argus-config/src/extraction.rs @@ -0,0 +1,151 @@ +use serde::{Deserialize, Serialize}; + +const fn default_max_turns() -> usize { + 15 +} + +const fn default_max_tokens() -> u64 { + 16384 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractionConfig { + pub enabled: bool, + pub cache_ttl_seconds: u64, + pub mode: ExtractionMode, + pub provider: ExtractionProviderConfig, + #[serde(default = "default_max_turns")] + pub max_turns: usize, + #[serde(default = "default_max_tokens")] + pub max_tokens: u64, +} + +impl Default for ExtractionConfig { + fn default() -> Self { + Self { + enabled: false, + cache_ttl_seconds: 3600, + mode: ExtractionMode::DryRun, + provider: ExtractionProviderConfig::default(), + max_turns: default_max_turns(), + max_tokens: default_max_tokens(), + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ExtractionMode { + #[default] + DryRun, + Enforce, +} + +fn default_extraction_timeout() -> u64 { + 5000 +} + +fn default_ollama_base_url() -> String { + "http://localhost:11434".to_string() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ExtractionProviderConfig { + OpenRouter { + model: String, + api_key_env: String, + #[serde(default = "default_extraction_timeout")] + timeout_ms: u64, + }, + Ollama { + model: String, + #[serde(default = "default_ollama_base_url")] + base_url: String, + #[serde(default = "default_extraction_timeout")] + timeout_ms: u64, + }, + OpenAI { + model: String, + api_key_env: String, + #[serde(default = "default_extraction_timeout")] + timeout_ms: u64, + }, + Anthropic { + model: String, + api_key_env: String, + #[serde(default = "default_extraction_timeout")] + timeout_ms: u64, + }, + Gemini { + model: String, + api_key_env: String, + #[serde(default = "default_extraction_timeout")] + timeout_ms: u64, + }, +} + +impl Default for ExtractionProviderConfig { + fn default() -> Self { + Self::Ollama { + model: "llama3.2:3b".to_string(), + base_url: default_ollama_base_url(), + timeout_ms: default_extraction_timeout(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extraction_config_defaults_to_disabled() { + let config = ExtractionConfig::default(); + assert!(!config.enabled); + assert_eq!(config.mode, ExtractionMode::DryRun); + assert_eq!(config.max_turns, 15); + assert_eq!(config.max_tokens, 16384); + } + + #[test] + fn extraction_mode_serializes_kebab_case() { + let mode = ExtractionMode::DryRun; + let json = serde_json::to_string(&mode).unwrap(); + assert_eq!(json, "\"dry-run\""); + } + + #[test] + fn extraction_provider_serializes_with_type_tag() { + let provider = ExtractionProviderConfig::Ollama { + model: "llama3.2:3b".to_string(), + base_url: "http://localhost:11434".to_string(), + timeout_ms: 5000, + }; + let json = serde_json::to_string(&provider).unwrap(); + assert!(json.contains("\"type\":\"ollama\"")); + } + + #[test] + fn openai_provider_serializes_correctly() { + let provider = ExtractionProviderConfig::OpenAI { + model: "gpt-4o-mini".to_string(), + api_key_env: "OPENAI_API_KEY".to_string(), + timeout_ms: 5000, + }; + let json = serde_json::to_string(&provider).unwrap(); + assert!(json.contains("\"type\":\"openai\"")); + assert!(json.contains("\"model\":\"gpt-4o-mini\"")); + } + + #[test] + fn anthropic_provider_serializes_correctly() { + let provider = ExtractionProviderConfig::Anthropic { + model: "claude-3-haiku-20240307".to_string(), + api_key_env: "ANTHROPIC_API_KEY".to_string(), + timeout_ms: 5000, + }; + let json = serde_json::to_string(&provider).unwrap(); + assert!(json.contains("\"type\":\"anthropic\"")); + } +} diff --git a/argus/crates/argus-config/src/lib.rs b/argus/crates/argus-config/src/lib.rs new file mode 100644 index 0000000..97ca304 --- /dev/null +++ b/argus/crates/argus-config/src/lib.rs @@ -0,0 +1,139 @@ +mod extraction; + +use serde::{Deserialize, Serialize}; + +pub use extraction::{ExtractionConfig, ExtractionMode, ExtractionProviderConfig}; + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum OperationalMode { + #[default] + Development, + Production { + strict: bool, + }, +} + +impl Serialize for OperationalMode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + match self { + OperationalMode::Development => serializer.serialize_str("development"), + OperationalMode::Production { strict } => { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("mode", "production")?; + map.serialize_entry("strict", strict)?; + map.end() + } + } + } +} + +impl<'de> Deserialize<'de> for OperationalMode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{MapAccess, Visitor}; + + struct OperationalModeVisitor; + + impl<'de> Visitor<'de> for OperationalModeVisitor { + type Value = OperationalMode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter + .write_str("\"development\" or {\"mode\": \"production\", \"strict\": bool}") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "development" => Ok(OperationalMode::Development), + _ => Err(E::custom(format!("unknown mode: {}", value))), + } + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut mode: Option = None; + let mut strict: Option = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "mode" => mode = Some(map.next_value()?), + "strict" => strict = Some(map.next_value()?), + _ => { + let _: serde::de::IgnoredAny = map.next_value()?; + } + } + } + + match mode.as_deref() { + Some("production") => Ok(OperationalMode::Production { + strict: strict.unwrap_or(false), + }), + Some("development") => Ok(OperationalMode::Development), + Some(other) => { + Err(serde::de::Error::custom(format!("unknown mode: {}", other))) + } + None => Err(serde::de::Error::missing_field("mode")), + } + } + } + + deserializer.deserialize_any(OperationalModeVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn operational_mode_defaults_to_development() { + let mode = OperationalMode::default(); + assert!(matches!(mode, OperationalMode::Development)); + } + + #[test] + fn operational_mode_serializes_correctly() { + let dev = OperationalMode::Development; + let json = serde_json::to_string(&dev).unwrap(); + assert_eq!(json, "\"development\""); + + let prod = OperationalMode::Production { strict: true }; + let json = serde_json::to_string(&prod).unwrap(); + assert!(json.contains("\"production\"")); + assert!(json.contains("\"strict\":true")); + } + + #[test] + fn operational_mode_deserializes_from_string() { + let mode: OperationalMode = serde_json::from_str("\"development\"").unwrap(); + assert!(matches!(mode, OperationalMode::Development)); + } + + #[test] + fn operational_mode_deserializes_from_map() { + let json = r#"{"mode": "production", "strict": true}"#; + let mode: OperationalMode = serde_json::from_str(json).unwrap(); + assert!(matches!(mode, OperationalMode::Production { strict: true })); + } + + #[test] + fn operational_mode_strict_defaults_to_false() { + let json = r#"{"mode": "production"}"#; + let mode: OperationalMode = serde_json::from_str(json).unwrap(); + assert!(matches!( + mode, + OperationalMode::Production { strict: false } + )); + } +} diff --git a/argus/crates/argus-gateway/Cargo.toml b/argus/crates/argus-gateway/Cargo.toml new file mode 100644 index 0000000..844b858 --- /dev/null +++ b/argus/crates/argus-gateway/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "argus-gateway" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +argus-kernel = { workspace = true } +argus-registry = { workspace = true } +argus-sandbox = { workspace = true, features = ["runtime"] } +rig-core = { workspace = true } +rmcp = { workspace = true } +tonic = { workspace = true } +tonic-prost = { workspace = true } +prost = { workspace = true } +tokio = { workspace = true, features = ["sync", "process", "net", "time", "io-util", "rt"] } +tokio-stream = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +tracing = { workspace = true } +thiserror = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true } +http-body-util = { workspace = true } +reqwest = { workspace = true } +bytes = { workspace = true } +futures = { workspace = true } +uuid = { workspace = true } + +[target.'cfg(unix)'.dependencies] +libc = { workspace = true } + +[dev-dependencies] +argus-oracle = { workspace = true } +argus-audit = { workspace = true } +rmcp = { workspace = true, features = ["server", "macros", "transport-io"] } +schemars = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "time"] } + +[build-dependencies] +tonic-prost-build = "0.14" diff --git a/argus/crates/argus-gateway/build.rs b/argus/crates/argus-gateway/build.rs new file mode 100644 index 0000000..8be0e5c --- /dev/null +++ b/argus/crates/argus-gateway/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_prost_build::configure().compile_protos(&["proto/argus/v1/gateway.proto"], &["proto"])?; + Ok(()) +} diff --git a/argus/crates/argus-gateway/examples/echo_mcp_server.rs b/argus/crates/argus-gateway/examples/echo_mcp_server.rs new file mode 100644 index 0000000..0539ec1 --- /dev/null +++ b/argus/crates/argus-gateway/examples/echo_mcp_server.rs @@ -0,0 +1,69 @@ +use rmcp::handler::server::{tool::ToolRouter, wrapper::Parameters}; +use rmcp::model::*; +use rmcp::schemars; +use rmcp::serde; +use rmcp::service::ServiceExt; +use rmcp::transport::io::stdio; +use rmcp::{ErrorData as McpError, tool, tool_handler, tool_router}; + +#[derive(Clone)] +struct EchoServer { + tool_router: ToolRouter, +} + +impl EchoServer { + fn new() -> Self { + Self { + tool_router: Self::tool_router(), + } + } +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +struct EchoParams { + message: String, +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +struct AddParams { + a: f64, + b: f64, +} + +#[tool_router] +impl EchoServer { + #[tool(description = "Echoes the input message back")] + fn echo(&self, Parameters(params): Parameters) -> Result { + Ok(CallToolResult::success(vec![Content::text(params.message)])) + } + + #[tool(description = "Adds two numbers")] + fn add(&self, Parameters(params): Parameters) -> Result { + Ok(CallToolResult::success(vec![Content::text( + (params.a + params.b).to_string(), + )])) + } +} + +#[tool_handler] +impl rmcp::ServerHandler for EchoServer { + fn get_info(&self) -> ServerInfo { + ServerInfo { + protocol_version: ProtocolVersion::V_2024_11_05, + capabilities: ServerCapabilities::builder().enable_tools().build(), + server_info: Implementation { + name: "echo-test-server".into(), + version: "0.1.0".into(), + ..Default::default() + }, + instructions: Some("A test echo MCP server for integration tests".into()), + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let service = EchoServer::new().serve(stdio()).await?; + service.waiting().await?; + Ok(()) +} diff --git a/argus/crates/argus-gateway/proto/argus/v1/gateway.proto b/argus/crates/argus-gateway/proto/argus/v1/gateway.proto new file mode 100644 index 0000000..6c69830 --- /dev/null +++ b/argus/crates/argus-gateway/proto/argus/v1/gateway.proto @@ -0,0 +1,137 @@ +syntax = "proto3"; +package argus.v1; + +service ArgusGateway { + rpc RegisterTool(RegisterToolRequest) returns (EventResponse); + rpc Delegate(DelegateRequest) returns (EventResponse); + rpc GrantCapability(GrantCapabilityRequest) returns (EventResponse); + rpc Revoke(RevokeRequest) returns (EventResponse); + rpc CascadeRevoke(CascadeRevokeRequest) returns (EventResponse); + rpc InvokeStart(InvokeStartRequest) returns (EventResponse); + rpc InvokeComplete(InvokeCompleteRequest) returns (EventResponse); + rpc ReturnEndorsed(ReturnEndorsedRequest) returns (EventResponse); + rpc ReturnUnendorsed(ReturnUnendorsedRequest) returns (EventResponse); + rpc SentinelElevateTaint(SentinelElevateTaintRequest) returns (EventResponse); + rpc StateSnapshot(StateSnapshotRequest) returns (StateSnapshotResponse); +} + +enum CapKind { + CAP_KIND_UNSPECIFIED = 0; + CAP_KIND_FILESYSTEM_READ = 1; + CAP_KIND_FILESYSTEM_WRITE = 2; + CAP_KIND_FILESYSTEM_DELETE = 3; + CAP_KIND_NETWORK_EGRESS = 4; + CAP_KIND_NETWORK_INGRESS = 5; + CAP_KIND_EXECUTION_SHELL = 6; + CAP_KIND_EXECUTION_CODE = 7; + CAP_KIND_CREDENTIALS = 8; + CAP_KIND_SYSTEM_INFO = 9; + CAP_KIND_SYSTEM_MODIFY = 10; + CAP_KIND_CLIPBOARD = 11; + CAP_KIND_BROWSER_NAVIGATE = 12; + CAP_KIND_DATABASE_READ = 13; + CAP_KIND_DATABASE_WRITE = 14; + CAP_KIND_IPC = 15; +} + +enum ConfLevel { + CONF_LEVEL_UNSPECIFIED = 0; + CONF_LEVEL_PUBLIC = 1; + CONF_LEVEL_INTERNAL = 2; + CONF_LEVEL_SENSITIVE = 3; + CONF_LEVEL_RESTRICTED = 4; +} + +enum KernelActionKind { + KERNEL_ACTION_UNSPECIFIED = 0; + KERNEL_ACTION_REGISTER_TOOL = 1; + KERNEL_ACTION_DELEGATE = 2; + KERNEL_ACTION_GRANT_CAPABILITY = 3; + KERNEL_ACTION_REVOKE = 4; + KERNEL_ACTION_CASCADE_REVOKE = 5; + KERNEL_ACTION_INVOKE_START = 6; + KERNEL_ACTION_INVOKE_COMPLETE = 7; + KERNEL_ACTION_RETURN_ENDORSED = 8; + KERNEL_ACTION_RETURN_UNENDORSED = 9; + KERNEL_ACTION_SENTINEL_ELEVATE_TAINT = 10; +} + +message RegisterToolRequest { + string tool_id = 1; +} + +message DelegateRequest { + string grantor = 1; + string grantee = 2; +} + +message GrantCapabilityRequest { + string parent = 1; + string child = 2; + CapKind capability = 3; +} + +message RevokeRequest { + string parent = 1; + string target = 2; +} + +message CascadeRevokeRequest { + string child = 1; + string parent = 2; +} + +message InvokeStartRequest { + string agent_id = 1; + string tool_id = 2; + string invocation_id = 3; +} + +message InvokeCompleteRequest { + string agent_id = 1; + string invocation_id = 2; +} + +message ReturnEndorsedRequest { + string child = 1; + string parent = 2; +} + +message ReturnUnendorsedRequest { + string child = 1; + string parent = 2; +} + +message SentinelElevateTaintRequest { + string agent_id = 1; + ConfLevel level = 2; +} + +message StateSnapshotRequest {} + +message EventResponse { + KernelActionKind action = 1; + uint64 sequence = 2; +} + +message CapabilitySet { + repeated CapKind capabilities = 1; +} + +message ConfLevelSet { + repeated ConfLevel levels = 1; +} + +message InvocationIdSet { + repeated string invocation_ids = 1; +} + +message StateSnapshotResponse { + repeated string active_agents = 1; + map agent_parents = 2; + map agent_capabilities = 3; + map taint_levels = 4; + map in_flight = 5; + map invocation_tool = 6; + repeated string registered_tools = 7; +} diff --git a/argus/crates/argus-gateway/src/error.rs b/argus/crates/argus-gateway/src/error.rs new file mode 100644 index 0000000..d8eeabc --- /dev/null +++ b/argus/crates/argus-gateway/src/error.rs @@ -0,0 +1,76 @@ +use argus_kernel::KernelError; +use argus_sandbox::SandboxError; + +#[derive(Debug, thiserror::Error)] +pub enum GatewayError { + #[error(transparent)] + Kernel(#[from] KernelError), + + #[error("MCP error: {0}")] + Mcp(#[from] McpError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ProxyError { + #[error("missing X-Argus-Agent header")] + MissingAgentHeader, + + #[error("agent not found: {0}")] + AgentNotFound(String), + + #[error("unsupported path: {0}")] + UnsupportedPath(String), + + #[error("no upstream configured for format")] + NoUpstream, + + #[error("upstream request failed: {0}")] + UpstreamRequest(#[from] reqwest::Error), + + #[error("max tool rounds ({0}) exceeded")] + MaxRoundsExceeded(usize), + + #[error("invalid request body: {0}")] + InvalidBody(String), + + #[error("gateway: {0}")] + Gateway(#[from] GatewayError), + + #[error("json: {0}")] + Json(#[from] serde_json::Error), + + #[error("upstream returned error: {status}")] + UpstreamStatus { status: u16, body: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum McpError { + #[error("failed to spawn MCP server '{name}': {source}")] + SpawnFailed { + name: String, + source: std::io::Error, + }, + + #[error("MCP server '{name}' initialization failed: {reason}")] + InitFailed { name: String, reason: String }, + + #[error("no server registered for tool '{0}'")] + NoServerForTool(String), + + #[error("tool call to '{tool}' on server '{server}' failed: {reason}")] + CallFailed { + server: String, + tool: String, + reason: String, + }, + + #[error("sidecar '{sidecar}' for MCP server '{mcp}' failed: {reason}")] + SidecarFailed { + mcp: String, + sidecar: String, + reason: String, + }, + + #[error(transparent)] + Sandbox(#[from] SandboxError), +} diff --git a/argus/crates/argus-gateway/src/grpc.rs b/argus/crates/argus-gateway/src/grpc.rs new file mode 100644 index 0000000..6a0d35d --- /dev/null +++ b/argus/crates/argus-gateway/src/grpc.rs @@ -0,0 +1,574 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use argus_kernel::{ + AgentId, AuthorizerOracle, CapKind, ConfLevel, ContentGateOracle, EventStore, InvocationId, + KernelAction, KernelError, KernelEvent, KernelState, ToolId, +}; +use tonic::{Request, Response, Status}; + +use crate::{GatewayError, GatewayService}; + +pub mod proto { + tonic::include_proto!("argus.v1"); +} + +pub use proto::argus_gateway_server::ArgusGatewayServer; + +// --- Enum conversions --- + +impl From for proto::CapKind { + fn from(k: CapKind) -> Self { + match k { + CapKind::FilesystemRead => Self::FilesystemRead, + CapKind::FilesystemWrite => Self::FilesystemWrite, + CapKind::FilesystemDelete => Self::FilesystemDelete, + CapKind::NetworkEgress => Self::NetworkEgress, + CapKind::NetworkIngress => Self::NetworkIngress, + CapKind::ExecutionShell => Self::ExecutionShell, + CapKind::ExecutionCode => Self::ExecutionCode, + CapKind::Credentials => Self::Credentials, + CapKind::SystemInfo => Self::SystemInfo, + CapKind::SystemModify => Self::SystemModify, + CapKind::Clipboard => Self::Clipboard, + CapKind::BrowserNavigate => Self::BrowserNavigate, + CapKind::DatabaseRead => Self::DatabaseRead, + CapKind::DatabaseWrite => Self::DatabaseWrite, + CapKind::Ipc => Self::Ipc, + } + } +} + +impl TryFrom for CapKind { + type Error = Status; + fn try_from(k: proto::CapKind) -> Result { + match k { + proto::CapKind::Unspecified => Err(Status::invalid_argument("CapKind unspecified")), + proto::CapKind::FilesystemRead => Ok(Self::FilesystemRead), + proto::CapKind::FilesystemWrite => Ok(Self::FilesystemWrite), + proto::CapKind::FilesystemDelete => Ok(Self::FilesystemDelete), + proto::CapKind::NetworkEgress => Ok(Self::NetworkEgress), + proto::CapKind::NetworkIngress => Ok(Self::NetworkIngress), + proto::CapKind::ExecutionShell => Ok(Self::ExecutionShell), + proto::CapKind::ExecutionCode => Ok(Self::ExecutionCode), + proto::CapKind::Credentials => Ok(Self::Credentials), + proto::CapKind::SystemInfo => Ok(Self::SystemInfo), + proto::CapKind::SystemModify => Ok(Self::SystemModify), + proto::CapKind::Clipboard => Ok(Self::Clipboard), + proto::CapKind::BrowserNavigate => Ok(Self::BrowserNavigate), + proto::CapKind::DatabaseRead => Ok(Self::DatabaseRead), + proto::CapKind::DatabaseWrite => Ok(Self::DatabaseWrite), + proto::CapKind::Ipc => Ok(Self::Ipc), + } + } +} + +impl From for proto::ConfLevel { + fn from(c: ConfLevel) -> Self { + match c { + ConfLevel::Public => Self::Public, + ConfLevel::Internal => Self::Internal, + ConfLevel::Sensitive => Self::Sensitive, + ConfLevel::Restricted => Self::Restricted, + } + } +} + +impl TryFrom for ConfLevel { + type Error = Status; + fn try_from(c: proto::ConfLevel) -> Result { + match c { + proto::ConfLevel::Unspecified => Err(Status::invalid_argument("ConfLevel unspecified")), + proto::ConfLevel::Public => Ok(Self::Public), + proto::ConfLevel::Internal => Ok(Self::Internal), + proto::ConfLevel::Sensitive => Ok(Self::Sensitive), + proto::ConfLevel::Restricted => Ok(Self::Restricted), + } + } +} + +fn kernel_action_to_proto(action: &KernelAction) -> proto::KernelActionKind { + match action { + KernelAction::RegisterTool { .. } => proto::KernelActionKind::KernelActionRegisterTool, + KernelAction::Delegate { .. } => proto::KernelActionKind::KernelActionDelegate, + KernelAction::GrantCapability { .. } => { + proto::KernelActionKind::KernelActionGrantCapability + } + KernelAction::Revoke { .. } => proto::KernelActionKind::KernelActionRevoke, + KernelAction::CascadeRevoke { .. } => proto::KernelActionKind::KernelActionCascadeRevoke, + KernelAction::InvokeStart { .. } => proto::KernelActionKind::KernelActionInvokeStart, + KernelAction::InvokeComplete { .. } => proto::KernelActionKind::KernelActionInvokeComplete, + KernelAction::ReturnEndorsed { .. } => proto::KernelActionKind::KernelActionReturnEndorsed, + KernelAction::ReturnUnendorsed { .. } => { + proto::KernelActionKind::KernelActionReturnUnendorsed + } + KernelAction::SentinelElevateTaint { .. } => { + proto::KernelActionKind::KernelActionSentinelElevateTaint + } + } +} + +fn event_to_response(event: &KernelEvent) -> proto::EventResponse { + proto::EventResponse { + action: kernel_action_to_proto(&event.action) as i32, + sequence: event.sequence, + } +} + +/// Maps kernel state to the gRPC snapshot. +/// Ghost fields `gh_taint_invoked` and `gh_taint_received` are intentionally +/// excluded -- they exist only for formal verification invariants. +fn state_to_response(state: &KernelState) -> proto::StateSnapshotResponse { + let active_agents: Vec = state.agent_active.iter().map(|a| a.0.clone()).collect(); + + let agent_parents: HashMap = state + .agent_parent + .iter() + .map(|(k, v)| (k.0.clone(), v.0.clone())) + .collect(); + + let agent_capabilities: HashMap = state + .agent_cap + .iter() + .map(|(k, v)| { + let caps = v.iter().map(|c| proto::CapKind::from(*c) as i32).collect(); + (k.0.clone(), proto::CapabilitySet { capabilities: caps }) + }) + .collect(); + + let taint_levels: HashMap = state + .taint_levels + .iter() + .map(|(k, v)| { + let levels = v + .iter() + .map(|c| proto::ConfLevel::from(*c) as i32) + .collect(); + (k.0.clone(), proto::ConfLevelSet { levels }) + }) + .collect(); + + let in_flight: HashMap = state + .in_flight + .iter() + .map(|(k, v)| { + let ids = v.iter().map(|i| i.0.clone()).collect(); + ( + k.0.clone(), + proto::InvocationIdSet { + invocation_ids: ids, + }, + ) + }) + .collect(); + + let invocation_tool: HashMap = state + .invocation_tool + .iter() + .map(|(k, v)| (k.0.clone(), v.0.clone())) + .collect(); + + let registered_tools: Vec = state.tool_registered.iter().map(|t| t.0.clone()).collect(); + + proto::StateSnapshotResponse { + active_agents, + agent_parents, + agent_capabilities, + taint_levels, + in_flight, + invocation_tool, + registered_tools, + } +} + +fn gateway_error_to_status(err: GatewayError) -> Status { + match err { + GatewayError::Kernel(ke) => match ke { + KernelError::PreconditionViolation(msg) => Status::failed_precondition(msg), + KernelError::EventStoreError(msg) => Status::internal(msg), + }, + GatewayError::Mcp(me) => Status::internal(me.to_string()), + } +} + +fn cap_kind_from_i32(v: i32) -> Result { + proto::CapKind::try_from(v) + .map_err(|_| Status::invalid_argument(format!("unknown capability value: {v}")))? + .try_into() +} + +fn conf_level_from_i32(v: i32) -> Result { + proto::ConfLevel::try_from(v) + .map_err(|_| Status::invalid_argument(format!("unknown conf level value: {v}")))? + .try_into() +} + +// --- gRPC service --- + +pub struct ArgusGrpcService +where + A: AuthorizerOracle, + C: ContentGateOracle, + E: EventStore, +{ + gateway: Arc>, +} + +impl ArgusGrpcService +where + A: AuthorizerOracle, + C: ContentGateOracle, + E: EventStore, +{ + pub fn new(gateway: Arc>) -> Self { + Self { gateway } + } + + pub fn into_server(self) -> ArgusGatewayServer + where + A: Send + Sync + 'static, + C: Send + Sync + 'static, + E: Send + Sync + 'static, + { + ArgusGatewayServer::new(self) + } +} + +#[tonic::async_trait] +impl proto::argus_gateway_server::ArgusGateway for ArgusGrpcService +where + A: AuthorizerOracle + Send + Sync + 'static, + C: ContentGateOracle + Send + Sync + 'static, + E: EventStore + Send + Sync + 'static, +{ + async fn register_tool( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .register_tool(ToolId::new(&req.tool_id)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn delegate( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .delegate(AgentId::new(&req.grantor), AgentId::new(&req.grantee)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn grant_capability( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let cap = cap_kind_from_i32(req.capability)?; + let event = self + .gateway + .grant_capability(AgentId::new(&req.parent), AgentId::new(&req.child), cap) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn revoke( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .revoke(AgentId::new(&req.parent), AgentId::new(&req.target)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn cascade_revoke( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .cascade_revoke(AgentId::new(&req.child), AgentId::new(&req.parent)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn invoke_start( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .invoke_start( + AgentId::new(&req.agent_id), + ToolId::new(&req.tool_id), + InvocationId::new(&req.invocation_id), + ) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn invoke_complete( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .invoke_complete( + AgentId::new(&req.agent_id), + InvocationId::new(&req.invocation_id), + ) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn return_endorsed( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .return_endorsed(AgentId::new(&req.child), AgentId::new(&req.parent)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn return_unendorsed( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let event = self + .gateway + .return_unendorsed(AgentId::new(&req.child), AgentId::new(&req.parent)) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn sentinel_elevate_taint( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let level = conf_level_from_i32(req.level)?; + let event = self + .gateway + .sentinel_elevate_taint(AgentId::new(&req.agent_id), level) + .await + .map_err(gateway_error_to_status)?; + Ok(Response::new(event_to_response(&event))) + } + + async fn state_snapshot( + &self, + _request: Request, + ) -> Result, Status> { + let state = self.gateway.state_snapshot().await; + Ok(Response::new(state_to_response(&state))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cap_kind_roundtrip_all_variants() { + let variants = [ + (CapKind::FilesystemRead, proto::CapKind::FilesystemRead), + (CapKind::FilesystemWrite, proto::CapKind::FilesystemWrite), + (CapKind::FilesystemDelete, proto::CapKind::FilesystemDelete), + (CapKind::NetworkEgress, proto::CapKind::NetworkEgress), + (CapKind::NetworkIngress, proto::CapKind::NetworkIngress), + (CapKind::ExecutionShell, proto::CapKind::ExecutionShell), + (CapKind::ExecutionCode, proto::CapKind::ExecutionCode), + (CapKind::Credentials, proto::CapKind::Credentials), + (CapKind::SystemInfo, proto::CapKind::SystemInfo), + (CapKind::SystemModify, proto::CapKind::SystemModify), + (CapKind::Clipboard, proto::CapKind::Clipboard), + (CapKind::BrowserNavigate, proto::CapKind::BrowserNavigate), + (CapKind::DatabaseRead, proto::CapKind::DatabaseRead), + (CapKind::DatabaseWrite, proto::CapKind::DatabaseWrite), + (CapKind::Ipc, proto::CapKind::Ipc), + ]; + for (kernel, proto_val) in variants { + let converted: proto::CapKind = kernel.into(); + assert_eq!(converted, proto_val); + let back: CapKind = proto_val.try_into().unwrap(); + assert_eq!(back, kernel); + } + } + + #[test] + fn cap_kind_unspecified_errors() { + let result: Result = proto::CapKind::Unspecified.try_into(); + assert!(result.is_err()); + } + + #[test] + fn cap_kind_from_i32_invalid_errors() { + let result = cap_kind_from_i32(999); + assert!(result.is_err()); + } + + #[test] + fn conf_level_roundtrip_all_variants() { + let variants = [ + (ConfLevel::Public, proto::ConfLevel::Public), + (ConfLevel::Internal, proto::ConfLevel::Internal), + (ConfLevel::Sensitive, proto::ConfLevel::Sensitive), + (ConfLevel::Restricted, proto::ConfLevel::Restricted), + ]; + for (kernel, proto_val) in variants { + let converted: proto::ConfLevel = kernel.into(); + assert_eq!(converted, proto_val); + let back: ConfLevel = proto_val.try_into().unwrap(); + assert_eq!(back, kernel); + } + } + + #[test] + fn conf_level_unspecified_errors() { + let result: Result = proto::ConfLevel::Unspecified.try_into(); + assert!(result.is_err()); + } + + #[test] + fn kernel_action_to_proto_all_variants() { + let tool = ToolId::new("t"); + let agent = AgentId::new("a"); + let parent = AgentId::root(); + let inv = InvocationId::new("i"); + + let cases = [ + ( + KernelAction::RegisterTool { tool }, + proto::KernelActionKind::KernelActionRegisterTool, + ), + ( + KernelAction::Delegate { + grantor: parent.clone(), + grantee: agent.clone(), + }, + proto::KernelActionKind::KernelActionDelegate, + ), + ( + KernelAction::GrantCapability { + parent: parent.clone(), + child: agent.clone(), + cap: CapKind::FilesystemRead, + }, + proto::KernelActionKind::KernelActionGrantCapability, + ), + ( + KernelAction::Revoke { + parent: parent.clone(), + target: agent.clone(), + }, + proto::KernelActionKind::KernelActionRevoke, + ), + ( + KernelAction::CascadeRevoke { + child: agent.clone(), + parent: parent.clone(), + }, + proto::KernelActionKind::KernelActionCascadeRevoke, + ), + ( + KernelAction::InvokeStart { + agent: agent.clone(), + tool: ToolId::new("t"), + inv: inv.clone(), + }, + proto::KernelActionKind::KernelActionInvokeStart, + ), + ( + KernelAction::InvokeComplete { + agent: agent.clone(), + inv, + }, + proto::KernelActionKind::KernelActionInvokeComplete, + ), + ( + KernelAction::ReturnEndorsed { + child: agent.clone(), + parent: parent.clone(), + }, + proto::KernelActionKind::KernelActionReturnEndorsed, + ), + ( + KernelAction::ReturnUnendorsed { + child: agent.clone(), + parent: parent.clone(), + }, + proto::KernelActionKind::KernelActionReturnUnendorsed, + ), + ( + KernelAction::SentinelElevateTaint { + agent, + level: ConfLevel::Sensitive, + }, + proto::KernelActionKind::KernelActionSentinelElevateTaint, + ), + ]; + for (action, expected) in cases { + let converted = kernel_action_to_proto(&action); + assert_eq!(converted, expected); + } + } + + #[test] + fn event_response_conversion() { + let event = KernelEvent::new( + 42, + KernelAction::RegisterTool { + tool: ToolId::new("t"), + }, + ); + let response = event_to_response(&event); + assert_eq!(response.sequence, 42); + assert_eq!( + response.action, + proto::KernelActionKind::KernelActionRegisterTool as i32 + ); + } + + #[test] + fn gateway_error_to_status_precondition() { + let err = GatewayError::Kernel(KernelError::PreconditionViolation( + "tool not registered".into(), + )); + let status = gateway_error_to_status(err); + assert_eq!(status.code(), tonic::Code::FailedPrecondition); + assert!(status.message().contains("tool not registered")); + } + + #[test] + fn gateway_error_to_status_internal() { + let err = GatewayError::Kernel(KernelError::EventStoreError("disk full".into())); + let status = gateway_error_to_status(err); + assert_eq!(status.code(), tonic::Code::Internal); + assert!(status.message().contains("disk full")); + } +} diff --git a/argus/crates/argus-gateway/src/lib.rs b/argus/crates/argus-gateway/src/lib.rs new file mode 100644 index 0000000..e22e704 --- /dev/null +++ b/argus/crates/argus-gateway/src/lib.rs @@ -0,0 +1,20 @@ +mod error; +mod grpc; +mod llm_anthropic; +mod llm_codec; +mod llm_openai; +mod llm_proxy; +mod llm_types; +mod mcp_manager; +mod ready_check; +mod restart; +mod service; + +pub use error::{GatewayError, McpError, ProxyError}; +pub use grpc::{ArgusGatewayServer, ArgusGrpcService, proto}; +pub use llm_proxy::LlmProxy; +pub use llm_types::{LlmFormat, ProxyConfig, UpstreamConfig}; +pub use mcp_manager::{McpManager, McpServerConfig}; +pub use ready_check::{ReadyOutcome, poll_ready}; +pub use restart::RestartTracker; +pub use service::GatewayService; diff --git a/argus/crates/argus-gateway/src/llm_anthropic.rs b/argus/crates/argus-gateway/src/llm_anthropic.rs new file mode 100644 index 0000000..3a87c73 --- /dev/null +++ b/argus/crates/argus-gateway/src/llm_anthropic.rs @@ -0,0 +1,240 @@ +use crate::llm_codec::LlmCodec; +use crate::llm_types::{ToolCall, ToolResult}; +use serde_json::{Value, json}; + +pub struct AnthropicCodec; + +impl LlmCodec for AnthropicCodec { + fn extract_tool_calls(&self, response: &Value) -> Vec { + let Some(content) = response.get("content").and_then(|c| c.as_array()) else { + return Vec::new(); + }; + content + .iter() + .filter_map(|block| { + if block.get("type")?.as_str()? != "tool_use" { + return None; + } + let id = block.get("id")?.as_str()?.to_owned(); + let name = block.get("name")?.as_str()?.to_owned(); + let input = block.get("input").cloned().unwrap_or(json!({})); + let arguments = serde_json::to_string(&input).unwrap_or_default(); + Some(ToolCall { + id, + name, + arguments, + }) + }) + .collect() + } + + fn build_tool_results_request( + &self, + current_request: &Value, + assistant_response: &Value, + tool_results: &[ToolResult], + ) -> Value { + let mut request = current_request.clone(); + let messages = request + .get_mut("messages") + .and_then(|m| m.as_array_mut()) + .expect("request must have messages array"); + + let assistant_content = assistant_response + .get("content") + .cloned() + .unwrap_or(json!([])); + messages.push(json!({ + "role": "assistant", + "content": assistant_content, + })); + + let tool_result_blocks: Vec = tool_results + .iter() + .map(|r| { + let mut block = json!({ + "type": "tool_result", + "tool_use_id": r.tool_call_id, + "content": r.content, + }); + if r.is_error { + block["is_error"] = Value::Bool(true); + } + block + }) + .collect(); + messages.push(json!({ + "role": "user", + "content": tool_result_blocks, + })); + + request["stream"] = Value::Bool(false); + request + } + + fn response_to_sse(&self, response: &Value) -> Vec { + let mut events = Vec::new(); + + let mut msg_start = response.clone(); + msg_start["content"] = json!([]); + if let Some(obj) = msg_start.as_object_mut() { + obj.remove("stop_reason"); + } + events.push(format!( + "event: message_start\ndata: {}\n\n", + json!({"type": "message_start", "message": msg_start}) + )); + + if let Some(content) = response.get("content").and_then(|c| c.as_array()) { + for (i, block) in content.iter().enumerate() { + let block_type = block.get("type").and_then(|t| t.as_str()).unwrap_or("text"); + if block_type == "text" { + let text = block.get("text").and_then(|t| t.as_str()).unwrap_or(""); + events.push(format!( + "event: content_block_start\ndata: {}\n\n", + json!({"type": "content_block_start", "index": i, "content_block": {"type": "text", "text": ""}}) + )); + events.push(format!( + "event: content_block_delta\ndata: {}\n\n", + json!({"type": "content_block_delta", "index": i, "delta": {"type": "text_delta", "text": text}}) + )); + events.push(format!( + "event: content_block_stop\ndata: {}\n\n", + json!({"type": "content_block_stop", "index": i}) + )); + } + } + } + + let stop_reason = response + .get("stop_reason") + .and_then(|s| s.as_str()) + .unwrap_or("end_turn"); + events.push(format!( + "event: message_delta\ndata: {}\n\n", + json!({"type": "message_delta", "delta": {"stop_reason": stop_reason}}) + )); + events.push("event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n".to_owned()); + + events + } + + fn is_streaming_request(&self, request: &Value) -> bool { + request + .get("stream") + .and_then(|s| s.as_bool()) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn extract_tool_calls_single() { + let codec = AnthropicCodec; + let response = json!({ + "id": "msg_abc", + "type": "message", + "role": "assistant", + "content": [ + {"type": "text", "text": "Let me read that file."}, + {"type": "tool_use", "id": "toolu_abc", "name": "read_file", "input": {"path": "/etc/hosts"}} + ], + "stop_reason": "tool_use" + }); + let calls = codec.extract_tool_calls(&response); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].id, "toolu_abc"); + assert_eq!(calls[0].name, "read_file"); + assert!(calls[0].arguments.contains("/etc/hosts")); + } + + #[test] + fn extract_tool_calls_multiple() { + let codec = AnthropicCodec; + let response = json!({ + "content": [ + {"type": "tool_use", "id": "toolu_1", "name": "read_file", "input": {}}, + {"type": "tool_use", "id": "toolu_2", "name": "write_file", "input": {}} + ], + "stop_reason": "tool_use" + }); + let calls = codec.extract_tool_calls(&response); + assert_eq!(calls.len(), 2); + } + + #[test] + fn extract_tool_calls_text_only() { + let codec = AnthropicCodec; + let response = json!({ + "content": [ + {"type": "text", "text": "Hello world"} + ], + "stop_reason": "end_turn" + }); + let calls = codec.extract_tool_calls(&response); + assert!(calls.is_empty()); + } + + #[test] + fn build_tool_results_appends_messages() { + let codec = AnthropicCodec; + let original = json!({ + "model": "claude-sonnet-4-20250514", + "messages": [ + {"role": "user", "content": "Read the file"} + ], + "max_tokens": 1024 + }); + let assistant = json!({ + "role": "assistant", + "content": [ + {"type": "text", "text": "I'll read it."}, + {"type": "tool_use", "id": "toolu_abc", "name": "read_file", "input": {"path": "/"}} + ] + }); + let results = vec![ToolResult { + tool_call_id: "toolu_abc".into(), + content: "file contents".into(), + is_error: false, + }]; + let new_req = codec.build_tool_results_request(&original, &assistant, &results); + let messages = new_req["messages"].as_array().unwrap(); + assert_eq!(messages.len(), 3); + assert_eq!(messages[1]["role"], "assistant"); + assert_eq!(messages[2]["role"], "user"); + let tool_result = &messages[2]["content"][0]; + assert_eq!(tool_result["type"], "tool_result"); + assert_eq!(tool_result["tool_use_id"], "toolu_abc"); + } + + #[test] + fn response_to_sse_valid_events() { + let codec = AnthropicCodec; + let response = json!({ + "id": "msg_abc", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-20250514", + "content": [{"type": "text", "text": "Hello world"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5} + }); + let chunks = codec.response_to_sse(&response); + assert!(chunks.len() >= 3); + assert!(chunks[0].contains("event: message_start")); + assert!(chunks.last().unwrap().contains("event: message_stop")); + let has_content = chunks.iter().any(|c| c.contains("Hello world")); + assert!(has_content); + } + + #[test] + fn is_streaming_request_checks_stream_field() { + let codec = AnthropicCodec; + assert!(codec.is_streaming_request(&json!({"stream": true}))); + assert!(!codec.is_streaming_request(&json!({"model": "claude-sonnet-4-20250514"}))); + } +} diff --git a/argus/crates/argus-gateway/src/llm_codec.rs b/argus/crates/argus-gateway/src/llm_codec.rs new file mode 100644 index 0000000..4417b2d --- /dev/null +++ b/argus/crates/argus-gateway/src/llm_codec.rs @@ -0,0 +1,29 @@ +use crate::llm_types::{LlmFormat, ToolCall, ToolResult}; + +pub trait LlmCodec: Send + Sync { + fn extract_tool_calls(&self, response: &serde_json::Value) -> Vec; + + fn build_tool_results_request( + &self, + current_request: &serde_json::Value, + assistant_response: &serde_json::Value, + tool_results: &[ToolResult], + ) -> serde_json::Value; + + fn response_to_sse(&self, response: &serde_json::Value) -> Vec; + + fn is_streaming_request(&self, request: &serde_json::Value) -> bool; +} + +pub fn codec_for_path(path: &str) -> Option<(Box, LlmFormat)> { + use crate::llm_anthropic::AnthropicCodec; + use crate::llm_openai::OpenAiCodec; + + if path.starts_with("/v1/chat/completions") { + Some((Box::new(OpenAiCodec), LlmFormat::OpenAi)) + } else if path.starts_with("/v1/messages") { + Some((Box::new(AnthropicCodec), LlmFormat::Anthropic)) + } else { + None + } +} diff --git a/argus/crates/argus-gateway/src/llm_openai.rs b/argus/crates/argus-gateway/src/llm_openai.rs new file mode 100644 index 0000000..b4eb1b0 --- /dev/null +++ b/argus/crates/argus-gateway/src/llm_openai.rs @@ -0,0 +1,260 @@ +use crate::llm_codec::LlmCodec; +use crate::llm_types::{ToolCall, ToolResult}; +use serde_json::{Value, json}; + +pub struct OpenAiCodec; + +impl LlmCodec for OpenAiCodec { + fn extract_tool_calls(&self, response: &Value) -> Vec { + let Some(choices) = response.get("choices").and_then(|c| c.as_array()) else { + return Vec::new(); + }; + let Some(message) = choices.first().and_then(|c| c.get("message")) else { + return Vec::new(); + }; + let Some(tool_calls) = message.get("tool_calls").and_then(|t| t.as_array()) else { + return Vec::new(); + }; + tool_calls + .iter() + .filter_map(|tc| { + let id = tc.get("id")?.as_str()?.to_owned(); + let function = tc.get("function")?; + let name = function.get("name")?.as_str()?.to_owned(); + let arguments = function + .get("arguments") + .and_then(|a| a.as_str()) + .unwrap_or("{}") + .to_owned(); + Some(ToolCall { + id, + name, + arguments, + }) + }) + .collect() + } + + fn build_tool_results_request( + &self, + current_request: &Value, + assistant_response: &Value, + tool_results: &[ToolResult], + ) -> Value { + let mut request = current_request.clone(); + let messages = request + .get_mut("messages") + .and_then(|m| m.as_array_mut()) + .expect("request must have messages array"); + + if let Some(assistant_msg) = assistant_response + .get("choices") + .and_then(|c| c.as_array()) + .and_then(|c| c.first()) + .and_then(|c| c.get("message")) + { + messages.push(assistant_msg.clone()); + } + + for result in tool_results { + messages.push(json!({ + "role": "tool", + "tool_call_id": result.tool_call_id, + "content": result.content, + })); + } + + request["stream"] = Value::Bool(false); + request + } + + fn response_to_sse(&self, response: &Value) -> Vec { + let content = response + .get("choices") + .and_then(|c| c.as_array()) + .and_then(|c| c.first()) + .and_then(|c| c.get("message")) + .and_then(|m| m.get("content")) + .and_then(|c| c.as_str()) + .unwrap_or(""); + + let id = response + .get("id") + .cloned() + .unwrap_or_else(|| json!("chatcmpl-proxy")); + let model = response + .get("model") + .cloned() + .unwrap_or_else(|| json!("unknown")); + + let chunk = json!({ + "id": id, + "object": "chat.completion.chunk", + "model": model, + "choices": [{ + "index": 0, + "delta": { + "role": "assistant", + "content": content, + }, + "finish_reason": "stop" + }] + }); + + vec![ + format!("data: {}\n\n", serde_json::to_string(&chunk).unwrap()), + "data: [DONE]\n\n".to_owned(), + ] + } + + fn is_streaming_request(&self, request: &Value) -> bool { + request + .get("stream") + .and_then(|s| s.as_bool()) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::llm_codec::LlmCodec; + use serde_json::json; + + #[test] + fn extract_tool_calls_single() { + let codec = OpenAiCodec; + let response = json!({ + "id": "chatcmpl-abc", + "object": "chat.completion", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [{ + "id": "call_abc", + "type": "function", + "function": { + "name": "read_file", + "arguments": "{\"path\":\"/etc/hosts\"}" + } + }] + }, + "finish_reason": "tool_calls" + }] + }); + let calls = codec.extract_tool_calls(&response); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].id, "call_abc"); + assert_eq!(calls[0].name, "read_file"); + assert!(calls[0].arguments.contains("/etc/hosts")); + } + + #[test] + fn extract_tool_calls_multiple() { + let codec = OpenAiCodec; + let response = json!({ + "choices": [{ + "message": { + "role": "assistant", + "tool_calls": [ + {"id": "call_1", "type": "function", "function": {"name": "read_file", "arguments": "{}"}}, + {"id": "call_2", "type": "function", "function": {"name": "write_file", "arguments": "{}"}} + ] + }, + "finish_reason": "tool_calls" + }] + }); + let calls = codec.extract_tool_calls(&response); + assert_eq!(calls.len(), 2); + assert_eq!(calls[0].name, "read_file"); + assert_eq!(calls[1].name, "write_file"); + } + + #[test] + fn extract_tool_calls_none() { + let codec = OpenAiCodec; + let response = json!({ + "choices": [{ + "message": { + "role": "assistant", + "content": "Hello world" + }, + "finish_reason": "stop" + }] + }); + let calls = codec.extract_tool_calls(&response); + assert!(calls.is_empty()); + } + + #[test] + fn build_tool_results_appends_messages() { + let codec = OpenAiCodec; + let original = json!({ + "model": "gpt-4", + "messages": [ + {"role": "user", "content": "Read the file"} + ] + }); + let assistant = json!({ + "choices": [{ + "message": { + "role": "assistant", + "content": null, + "tool_calls": [{ + "id": "call_abc", + "type": "function", + "function": {"name": "read_file", "arguments": "{}"} + }] + } + }] + }); + let results = vec![ToolResult { + tool_call_id: "call_abc".into(), + content: "file contents".into(), + is_error: false, + }]; + let new_req = codec.build_tool_results_request(&original, &assistant, &results); + let messages = new_req["messages"].as_array().unwrap(); + assert_eq!(messages.len(), 3); + assert_eq!(messages[1]["role"], "assistant"); + assert_eq!(messages[2]["role"], "tool"); + assert_eq!(messages[2]["tool_call_id"], "call_abc"); + assert_eq!(messages[2]["content"], "file contents"); + assert_eq!(new_req["model"], "gpt-4"); + assert_eq!(new_req["stream"], false); + } + + #[test] + fn response_to_sse_produces_valid_chunks() { + let codec = OpenAiCodec; + let response = json!({ + "id": "chatcmpl-abc", + "object": "chat.completion", + "model": "gpt-4", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "Hello world" + }, + "finish_reason": "stop" + }] + }); + let chunks = codec.response_to_sse(&response); + assert_eq!(chunks.len(), 2); + assert!(chunks[0].starts_with("data: ")); + assert!(chunks[0].contains("chat.completion.chunk")); + assert!(chunks[0].contains("Hello world")); + assert_eq!(chunks[1].trim(), "data: [DONE]"); + } + + #[test] + fn is_streaming_request_checks_stream_field() { + let codec = OpenAiCodec; + assert!(codec.is_streaming_request(&json!({"stream": true}))); + assert!(!codec.is_streaming_request(&json!({"stream": false}))); + assert!(!codec.is_streaming_request(&json!({"model": "gpt-4"}))); + } +} diff --git a/argus/crates/argus-gateway/src/llm_proxy.rs b/argus/crates/argus-gateway/src/llm_proxy.rs new file mode 100644 index 0000000..692ddc7 --- /dev/null +++ b/argus/crates/argus-gateway/src/llm_proxy.rs @@ -0,0 +1,465 @@ +use std::convert::Infallible; +use std::sync::Arc; + +use bytes::Bytes; +use futures::future::join_all; +use http_body_util::{BodyExt, Full, StreamBody}; +use hyper::body::Frame; +use hyper::header::{HeaderMap, HeaderValue}; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::{Request, Response, StatusCode}; +use hyper_util::rt::TokioIo; +use serde_json::{Value, json}; +use tokio::net::TcpListener; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use uuid::Uuid; + +use argus_kernel::{ + AgentId, AuthorizerOracle, ContentGateOracle, EventStore, InvocationId, ToolId, +}; +use argus_sandbox::SandboxProvider; + +use crate::error::ProxyError; +use crate::llm_codec::{LlmCodec, codec_for_path}; +use crate::llm_types::{LlmFormat, ProxyConfig, SseProgressEvent, ToolResult, UpstreamConfig}; +use crate::mcp_manager::McpManager; +use crate::service::GatewayService; + +type BoxBody = http_body_util::combinators::BoxBody; + +pub struct LlmProxy +where + A: AuthorizerOracle, + C: ContentGateOracle, + E: EventStore, + S: SandboxProvider, +{ + gateway: Arc>, + mcp: Arc>>, + config: ProxyConfig, + http_client: reqwest::Client, +} + +impl LlmProxy +where + A: AuthorizerOracle + Send + Sync + 'static, + C: ContentGateOracle + Send + Sync + 'static, + E: EventStore + Send + Sync + 'static, + S: SandboxProvider + Send + Sync + 'static, +{ + pub fn new( + gateway: Arc>, + mcp: Arc>>, + config: ProxyConfig, + ) -> Self { + Self { + gateway, + mcp, + config, + http_client: reqwest::Client::new(), + } + } + + pub async fn serve(self: Arc, listener: TcpListener) { + let addr = listener.local_addr().unwrap(); + tracing::info!("LLM proxy listening on {addr}"); + loop { + let Ok((stream, _)) = listener.accept().await else { + continue; + }; + let io = TokioIo::new(stream); + let proxy = self.clone(); + tokio::spawn(async move { + let service = service_fn(move |req| { + let proxy = proxy.clone(); + async move { Ok::<_, Infallible>(proxy.handle_request(req).await) } + }); + if let Err(e) = http1::Builder::new().serve_connection(io, service).await { + tracing::error!("HTTP connection error: {e}"); + } + }); + } + } + + async fn handle_request( + self: Arc, + req: Request, + ) -> Response { + let path = req.uri().path().to_owned(); + let headers = req.headers().clone(); + + let body_bytes = match req.into_body().collect().await { + Ok(collected) => collected.to_bytes(), + Err(e) => { + return error_response( + StatusCode::BAD_REQUEST, + &format!("failed to read body: {e}"), + ); + } + }; + let body: Value = match serde_json::from_slice(&body_bytes) { + Ok(v) => v, + Err(e) => { + return error_response(StatusCode::BAD_REQUEST, &format!("invalid JSON: {e}")); + } + }; + + match self.handle_proxy_request(&path, &headers, body).await { + Ok(resp) => resp, + Err(e) => proxy_error_to_response(&e), + } + } + + async fn handle_proxy_request( + self: &Arc, + path: &str, + headers: &HeaderMap, + body: Value, + ) -> Result, ProxyError> { + let (codec, format) = + codec_for_path(path).ok_or_else(|| ProxyError::UnsupportedPath(path.to_owned()))?; + + let agent_str = headers + .get("x-argus-agent") + .and_then(|v| v.to_str().ok()) + .ok_or(ProxyError::MissingAgentHeader)?; + let agent = AgentId::new(agent_str); + + let state = self.gateway.state_snapshot().await; + if !state.agent_active.contains(&agent) { + return Err(ProxyError::AgentNotFound(agent_str.to_owned())); + } + + let upstream = self + .resolve_upstream(headers, format) + .ok_or(ProxyError::NoUpstream)?; + + let mut forward_headers = HeaderMap::new(); + for (name, value) in headers.iter() { + let n = name.as_str(); + if !n.starts_with("x-argus-") && n != "host" && n != "content-length" { + forward_headers.insert(name.clone(), value.clone()); + } + } + + if codec.is_streaming_request(&body) { + return self.handle_streaming(agent, codec, &upstream, forward_headers, body, path); + } + + let result = self + .run_tool_loop( + &agent, + &*codec, + &upstream, + &forward_headers, + body, + path, + None, + ) + .await?; + Ok(json_response(StatusCode::OK, &result)) + } + + fn handle_streaming( + self: &Arc, + agent: AgentId, + codec: Box, + upstream: &UpstreamConfig, + forward_headers: HeaderMap, + body: Value, + path: &str, + ) -> Result, ProxyError> { + let (tx, rx) = mpsc::channel::, Infallible>>(32); + let stream = ReceiverStream::new(rx); + let body_stream = StreamBody::new(stream); + let response = Response::builder() + .status(200) + .header("content-type", "text/event-stream") + .header("cache-control", "no-cache") + .body(body_stream.boxed()) + .unwrap(); + + let proxy = self.clone(); + let upstream = upstream.clone(); + let path = path.to_owned(); + tokio::spawn(async move { + let result = proxy + .run_tool_loop( + &agent, + &*codec, + &upstream, + &forward_headers, + body, + &path, + Some(&tx), + ) + .await; + match result { + Ok(final_response) => { + for chunk in codec.response_to_sse(&final_response) { + let _ = tx.send(Ok(Frame::data(Bytes::from(chunk)))).await; + } + } + Err(e) => { + let event = SseProgressEvent::Error { + message: e.to_string(), + }; + let _ = tx + .send(Ok(Frame::data(Bytes::from(event.to_sse_string())))) + .await; + } + } + }); + + Ok(response) + } + + #[allow(clippy::too_many_arguments)] + async fn run_tool_loop( + &self, + agent: &AgentId, + codec: &dyn LlmCodec, + upstream: &UpstreamConfig, + forward_headers: &HeaderMap, + mut request_body: Value, + path: &str, + sse_tx: Option<&mpsc::Sender, Infallible>>>, + ) -> Result { + for round in 1..=self.config.max_tool_rounds { + request_body["stream"] = Value::Bool(false); + + let url = format!("{}{}", upstream.url.trim_end_matches('/'), path); + let resp = self + .http_client + .post(&url) + .headers(forward_headers.clone()) + .json(&request_body) + .send() + .await?; + + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + return Err(ProxyError::UpstreamStatus { + status: status.as_u16(), + body, + }); + } + + let response_body: Value = resp.json().await?; + let tool_calls = codec.extract_tool_calls(&response_body); + + if tool_calls.is_empty() { + return Ok(response_body); + } + + let tool_results = self + .execute_tool_calls(agent, &tool_calls, round, sse_tx) + .await; + + let denied = tool_results.iter().filter(|r| r.is_error).count(); + emit_sse( + sse_tx, + &SseProgressEvent::RoundComplete { + round, + tool_calls: tool_calls.len(), + denied, + next: "llm_call".into(), + }, + ) + .await; + + request_body = + codec.build_tool_results_request(&request_body, &response_body, &tool_results); + } + + Err(ProxyError::MaxRoundsExceeded(self.config.max_tool_rounds)) + } + + async fn execute_tool_calls( + &self, + agent: &AgentId, + tool_calls: &[crate::llm_types::ToolCall], + round: usize, + sse_tx: Option<&mpsc::Sender, Infallible>>>, + ) -> Vec { + let futures: Vec<_> = tool_calls + .iter() + .map(|tc| self.execute_single_tool(agent, tc, round, sse_tx)) + .collect(); + join_all(futures).await + } + + async fn execute_single_tool( + &self, + agent: &AgentId, + tc: &crate::llm_types::ToolCall, + round: usize, + sse_tx: Option<&mpsc::Sender, Infallible>>>, + ) -> ToolResult { + let inv_id = Uuid::new_v4().to_string(); + let inv = InvocationId::new(&inv_id); + let tool_id = ToolId::new(&tc.name); + + emit_sse( + sse_tx, + &SseProgressEvent::ToolStart { + tool: tc.name.clone(), + invocation: inv_id.clone(), + round, + }, + ) + .await; + + if let Err(e) = self + .gateway + .invoke_start(agent.clone(), tool_id, inv.clone()) + .await + { + emit_sse( + sse_tx, + &SseProgressEvent::ToolDenied { + tool: tc.name.clone(), + invocation: inv_id, + reason: e.to_string(), + round, + }, + ) + .await; + return ToolResult { + tool_call_id: tc.id.clone(), + content: format!("Authorization denied: {e}"), + is_error: true, + }; + } + + let args: Value = serde_json::from_str(&tc.arguments).unwrap_or(json!({})); + let mcp_result = { + let mcp = self.mcp.read().await; + mcp.call_tool(&tc.name, args).await + }; + + if let Err(e) = self.gateway.invoke_complete(agent.clone(), inv).await { + tracing::warn!(tool = %tc.name, error = %e, "invoke_complete failed"); + } + + let (content, is_error, status) = match mcp_result { + Ok(result) => (result, false, "ok"), + Err(e) => (format!("Tool execution failed: {e}"), true, "error"), + }; + + emit_sse( + sse_tx, + &SseProgressEvent::ToolResult { + tool: tc.name.clone(), + invocation: inv_id, + status: status.into(), + round, + }, + ) + .await; + + ToolResult { + tool_call_id: tc.id.clone(), + content, + is_error, + } + } + + fn resolve_upstream( + &self, + headers: &HeaderMap, + format: LlmFormat, + ) -> Option { + if let Some(name) = headers + .get("x-argus-upstream") + .and_then(|v| v.to_str().ok()) + { + return self + .config + .upstreams + .iter() + .find(|u| u.name == name) + .cloned(); + } + self.config + .upstreams + .iter() + .find(|u| u.format == format) + .cloned() + } +} + +async fn emit_sse( + tx: Option<&mpsc::Sender, Infallible>>>, + event: &SseProgressEvent, +) { + if let Some(tx) = tx { + let _ = tx + .send(Ok(Frame::data(Bytes::from(event.to_sse_string())))) + .await; + } +} + +fn json_response(status: StatusCode, body: &Value) -> Response { + let bytes = serde_json::to_vec(body).unwrap_or_default(); + Response::builder() + .status(status) + .header("content-type", "application/json") + .body(Full::new(Bytes::from(bytes)).boxed()) + .unwrap() +} + +fn error_response(status: StatusCode, message: &str) -> Response { + json_response(status, &json!({"error": {"message": message}})) +} + +pub fn proxy_error_to_response(err: &ProxyError) -> Response { + let status = match err { + ProxyError::MissingAgentHeader | ProxyError::AgentNotFound(_) => StatusCode::UNAUTHORIZED, + ProxyError::UnsupportedPath(_) => StatusCode::NOT_FOUND, + ProxyError::NoUpstream | ProxyError::InvalidBody(_) | ProxyError::Json(_) => { + StatusCode::BAD_REQUEST + } + ProxyError::UpstreamRequest(_) | ProxyError::UpstreamStatus { .. } => { + StatusCode::BAD_GATEWAY + } + ProxyError::Gateway(_) => StatusCode::FORBIDDEN, + ProxyError::MaxRoundsExceeded(_) => StatusCode::INTERNAL_SERVER_ERROR, + }; + error_response(status, &err.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn proxy_error_missing_agent_is_401() { + let resp = proxy_error_to_response(&ProxyError::MissingAgentHeader); + assert_eq!(resp.status(), StatusCode::UNAUTHORIZED); + } + + #[test] + fn proxy_error_unsupported_path_is_404() { + let resp = proxy_error_to_response(&ProxyError::UnsupportedPath("/v2/foo".into())); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn proxy_error_upstream_status_is_502() { + let resp = proxy_error_to_response(&ProxyError::UpstreamStatus { + status: 500, + body: "internal".into(), + }); + assert_eq!(resp.status(), StatusCode::BAD_GATEWAY); + } + + #[test] + fn proxy_error_max_rounds_is_500() { + let resp = proxy_error_to_response(&ProxyError::MaxRoundsExceeded(15)); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/argus/crates/argus-gateway/src/llm_types.rs b/argus/crates/argus-gateway/src/llm_types.rs new file mode 100644 index 0000000..df69ed5 --- /dev/null +++ b/argus/crates/argus-gateway/src/llm_types.rs @@ -0,0 +1,126 @@ +use serde::Serialize; + +#[derive(Debug, Clone)] +pub struct ToolCall { + pub id: String, + pub name: String, + pub arguments: String, +} + +#[derive(Debug, Clone)] +pub struct ToolResult { + pub tool_call_id: String, + pub content: String, + pub is_error: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LlmFormat { + OpenAi, + Anthropic, +} + +#[derive(Debug, Clone)] +pub struct UpstreamConfig { + pub name: String, + pub url: String, + pub format: LlmFormat, +} + +#[derive(Debug, Clone)] +pub struct ProxyConfig { + pub max_tool_rounds: usize, + pub upstreams: Vec, +} + +impl Default for ProxyConfig { + fn default() -> Self { + Self { + max_tool_rounds: 15, + upstreams: Vec::new(), + } + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SseProgressEvent { + ToolStart { + tool: String, + invocation: String, + round: usize, + }, + ToolDenied { + tool: String, + invocation: String, + reason: String, + round: usize, + }, + ToolResult { + tool: String, + invocation: String, + status: String, + round: usize, + }, + RoundComplete { + round: usize, + tool_calls: usize, + denied: usize, + next: String, + }, + Error { + message: String, + }, +} + +impl SseProgressEvent { + pub fn event_name(&self) -> &'static str { + match self { + Self::ToolStart { .. } => "argus.tool_start", + Self::ToolDenied { .. } => "argus.tool_denied", + Self::ToolResult { .. } => "argus.tool_result", + Self::RoundComplete { .. } => "argus.round_complete", + Self::Error { .. } => "argus.error", + } + } + + pub fn to_sse_string(&self) -> String { + let data = serde_json::to_string(self).unwrap_or_default(); + format!("event: {}\ndata: {data}\n\n", self.event_name()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sse_tool_start_format() { + let event = SseProgressEvent::ToolStart { + tool: "read_file".into(), + invocation: "inv_1".into(), + round: 1, + }; + let sse = event.to_sse_string(); + assert!(sse.starts_with("event: argus.tool_start\n")); + assert!(sse.contains("\"tool\":\"read_file\"")); + assert!(sse.ends_with("\n\n")); + } + + #[test] + fn sse_error_format() { + let event = SseProgressEvent::Error { + message: "connection lost".into(), + }; + let sse = event.to_sse_string(); + assert!(sse.starts_with("event: argus.error\n")); + assert!(sse.contains("\"message\":\"connection lost\"")); + } + + #[test] + fn proxy_config_defaults() { + let config = ProxyConfig::default(); + assert_eq!(config.max_tool_rounds, 15); + assert!(config.upstreams.is_empty()); + } +} diff --git a/argus/crates/argus-gateway/src/mcp_manager.rs b/argus/crates/argus-gateway/src/mcp_manager.rs new file mode 100644 index 0000000..3ae6d06 --- /dev/null +++ b/argus/crates/argus-gateway/src/mcp_manager.rs @@ -0,0 +1,964 @@ +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use argus_registry::toml_types::{SidecarEntry, SidecarLifecycle}; +use argus_sandbox::{SandboxProfile, SandboxProvider}; +use rig::tool::ToolDyn; +use rig::tool::rmcp::McpTool; +use rmcp::ServiceExt; +use rmcp::service::{RoleClient, RunningService}; +use rmcp::transport::TokioChildProcess; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; +use tokio::sync::{Mutex, watch}; +use tokio::task::JoinHandle; +use tracing::{error, info, warn}; + +use crate::McpError; +use crate::ready_check::{ReadyOutcome, poll_ready}; +use crate::restart::RestartTracker; + +pub struct McpServerConfig { + pub name: String, + pub command: String, + pub args: Vec, + pub env: Vec<(String, String)>, + pub sandbox_profile: SandboxProfile, + pub sidecars: Vec, +} + +struct DaemonHandle { + name: String, + child: tokio::process::Child, + config: SidecarEntry, + restart_tracker: RestartTracker, +} + +struct McpServerHandle { + service: RunningService, + tools: HashMap, + daemons: Arc>>, + supervision_handle: Option>, + supervision_cancel: Option>, + fatal_rx: Option>, +} + +pub struct McpManager { + sandbox: Arc, + servers: HashMap, + tool_routes: HashMap, +} + +impl McpManager { + pub fn new(sandbox: S) -> Self { + Self { + sandbox: Arc::new(sandbox), + servers: HashMap::new(), + tool_routes: HashMap::new(), + } + } + + pub fn managed_tools(&self) -> Vec { + self.tool_routes.keys().cloned().collect() + } + + pub fn get_tool(&self, namespaced_id: &str) -> Option<&McpTool> { + let server_name = self.tool_routes.get(namespaced_id)?; + let handle = self.servers.get(server_name)?; + handle.tools.get(namespaced_id) + } + + pub fn all_mcp_tools(&self) -> Vec { + self.servers + .values() + .flat_map(|h| h.tools.values().cloned()) + .collect() + } + + pub fn has_fatal_failure(&self, server_name: &str) -> bool { + self.servers + .get(server_name) + .and_then(|h| h.fatal_rx.as_ref()) + .is_some_and(|rx| *rx.borrow()) + } + + pub async fn spawn_server(&mut self, config: McpServerConfig) -> Result<(), McpError> { + let mut daemons = Vec::new(); + + for sidecar in &config.sidecars { + match sidecar.lifecycle { + SidecarLifecycle::Init => { + if let Err(e) = self + .run_init_sidecar(&config.name, sidecar, &config.sandbox_profile) + .await + { + kill_daemons(&mut daemons).await; + return Err(e); + } + } + SidecarLifecycle::Daemon => { + match self + .start_daemon_sidecar(&config.name, sidecar, &config.sandbox_profile) + .await + { + Ok(handle) => daemons.push(handle), + Err(e) => { + kill_daemons(&mut daemons).await; + return Err(e); + } + } + } + } + } + + let mut cmd = Command::new(&config.command); + cmd.args(&config.args); + for (key, value) in &config.env { + cmd.env(key, value); + } + + let mut cmd = self.sandbox.prepare_command(cmd, &config.sandbox_profile)?; + set_process_group(&mut cmd); + + let transport = TokioChildProcess::new(cmd).map_err(|e| McpError::SpawnFailed { + name: config.name.clone(), + source: e, + })?; + + let service = ().serve(transport).await.map_err(|e| McpError::InitFailed { + name: config.name.clone(), + reason: format!("{e}"), + }); + + let service = match service { + Ok(s) => s, + Err(e) => { + kill_daemons(&mut daemons).await; + return Err(e); + } + }; + + let tools_result = + service + .list_tools(Default::default()) + .await + .map_err(|e| McpError::InitFailed { + name: config.name.clone(), + reason: format!("list_tools failed: {e}"), + }); + + let tools_result = match tools_result { + Ok(t) => t, + Err(e) => { + kill_daemons(&mut daemons).await; + return Err(e); + } + }; + + let sink = service.peer().to_owned(); + + let mut mcp_tools = HashMap::new(); + for tool in tools_result.tools { + let namespaced = format!("{}/{}", config.name, tool.name); + let mcp_tool = McpTool::from_mcp_server(tool, sink.clone()); + self.tool_routes + .insert(namespaced.clone(), config.name.clone()); + mcp_tools.insert(namespaced, mcp_tool); + } + + info!( + server = %config.name, + tools = ?mcp_tools.keys().collect::>(), + "MCP server spawned" + ); + + let has_daemons = !daemons.is_empty(); + let daemons = Arc::new(Mutex::new(daemons)); + + let (supervision_cancel, supervision_handle, fatal_rx) = if has_daemons { + let (cancel_tx, cancel_rx) = watch::channel(false); + let (fatal_tx, fatal_rx) = watch::channel(false); + + let task_daemons = Arc::clone(&daemons); + let task_sandbox = Arc::clone(&self.sandbox); + let task_profile = config.sandbox_profile.clone(); + let server_name = config.name.clone(); + + let handle = tokio::spawn(async move { + supervise_daemons( + server_name, + task_daemons, + task_sandbox, + task_profile, + cancel_rx, + fatal_tx, + ) + .await; + }); + + (Some(cancel_tx), Some(handle), Some(fatal_rx)) + } else { + (None, None, None) + }; + + self.servers.insert( + config.name, + McpServerHandle { + service, + tools: mcp_tools, + daemons, + supervision_handle, + supervision_cancel, + fatal_rx, + }, + ); + + Ok(()) + } + + async fn run_init_sidecar( + &self, + mcp_name: &str, + sidecar: &SidecarEntry, + profile: &SandboxProfile, + ) -> Result<(), McpError> { + info!( + server = %mcp_name, + sidecar = %sidecar.name, + "running init sidecar" + ); + + let mut cmd = Command::new(&sidecar.command); + cmd.args(&sidecar.args); + let mut cmd = self.sandbox.prepare_command(cmd, profile)?; + set_process_group(&mut cmd); + + let output = cmd.output().await.map_err(|e| McpError::SidecarFailed { + mcp: mcp_name.to_string(), + sidecar: sidecar.name.clone(), + reason: format!("failed to execute: {e}"), + })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + if !stdout.is_empty() { + info!( + server = %mcp_name, + sidecar = %sidecar.name, + "init stdout: {stdout}" + ); + } + if !stderr.is_empty() { + warn!( + server = %mcp_name, + sidecar = %sidecar.name, + "init stderr: {stderr}" + ); + } + return Err(McpError::SidecarFailed { + mcp: mcp_name.to_string(), + sidecar: sidecar.name.clone(), + reason: format!("exited with status {}", output.status.code().unwrap_or(-1)), + }); + } + + info!( + server = %mcp_name, + sidecar = %sidecar.name, + "init sidecar completed" + ); + Ok(()) + } + + async fn start_daemon_sidecar( + &self, + mcp_name: &str, + sidecar: &SidecarEntry, + profile: &SandboxProfile, + ) -> Result { + info!( + server = %mcp_name, + sidecar = %sidecar.name, + "starting daemon sidecar" + ); + + let mut child = spawn_daemon(&*self.sandbox, sidecar, profile, mcp_name)?; + + let ready_check = sidecar + .ready_check + .as_ref() + .expect("daemon sidecars must have a ready_check (validated at load time)"); + + let outcome = poll_ready( + ready_check, + sidecar.ready_timeout, + sidecar.ready_interval, + &mut child, + ) + .await; + + match outcome { + ReadyOutcome::Ready => { + info!( + server = %mcp_name, + sidecar = %sidecar.name, + "daemon sidecar ready" + ); + Ok(DaemonHandle { + name: sidecar.name.clone(), + child, + config: sidecar.clone(), + restart_tracker: RestartTracker::default_policy(), + }) + } + ReadyOutcome::Timeout => { + let _ = child.kill().await; + Err(McpError::SidecarFailed { + mcp: mcp_name.to_string(), + sidecar: sidecar.name.clone(), + reason: format!("ready check timed out after {}s", sidecar.ready_timeout), + }) + } + ReadyOutcome::ProcessDied => Err(McpError::SidecarFailed { + mcp: mcp_name.to_string(), + sidecar: sidecar.name.clone(), + reason: "process died before becoming ready".to_string(), + }), + } + } + + pub async fn call_tool( + &self, + tool_id: &str, + arguments: serde_json::Value, + ) -> Result { + let server_name = self + .tool_routes + .get(tool_id) + .ok_or_else(|| McpError::NoServerForTool(tool_id.to_string()))?; + + let mcp_tool = self + .servers + .get(server_name) + .and_then(|h| h.tools.get(tool_id)) + .ok_or_else(|| McpError::NoServerForTool(tool_id.to_string()))?; + + let call_failed = |reason: String| McpError::CallFailed { + server: server_name.clone(), + tool: tool_id.to_string(), + reason, + }; + + let args_string = serde_json::to_string(&arguments) + .map_err(|e| call_failed(format!("serialize: {e}")))?; + + mcp_tool + .call(args_string) + .await + .map_err(|e| call_failed(format!("{e}"))) + } + + pub async fn shutdown(&mut self) { + for (name, mut handle) in self.servers.drain() { + if let Some(cancel) = handle.supervision_cancel.take() { + let _ = cancel.send(true); + } + if let Some(task) = handle.supervision_handle.take() { + let _ = task.await; + } + + info!(server = %name, "shutting down MCP server"); + let _ = handle.service.close().await; + + let mut daemons = handle.daemons.lock().await; + if !daemons.is_empty() { + info!( + server = %name, + count = daemons.len(), + "sending SIGTERM to daemon sidecars" + ); + for daemon in daemons.iter() { + send_sigterm(&daemon.child); + } + + let grace = Duration::from_secs(10); + let _ = tokio::time::timeout(grace, async { + for daemon in daemons.iter_mut() { + let _ = daemon.child.wait().await; + } + }) + .await; + + for daemon in daemons.iter_mut() { + match daemon.child.try_wait() { + Ok(Some(_)) => {} + _ => { + warn!( + server = %name, + sidecar = %daemon.name, + "daemon did not exit in time, sending SIGKILL" + ); + let _ = daemon.child.kill().await; + } + } + } + } + } + self.tool_routes.clear(); + } + + pub async fn spawn_all( + &mut self, + configs: Vec, + ) -> Vec> { + let mut results = Vec::with_capacity(configs.len()); + for config in configs { + let name = config.name.clone(); + results.push(self.spawn_server(config).await.map(|()| name)); + } + results + } +} + +fn spawn_daemon( + sandbox: &dyn SandboxProvider, + sidecar: &SidecarEntry, + profile: &SandboxProfile, + mcp_name: &str, +) -> Result { + let mut cmd = Command::new(&sidecar.command); + cmd.args(&sidecar.args); + cmd.stdout(std::process::Stdio::piped()); + cmd.stderr(std::process::Stdio::piped()); + let mut cmd = sandbox.prepare_command(cmd, profile)?; + set_process_group(&mut cmd); + + let mut child = cmd.spawn().map_err(|e| McpError::SidecarFailed { + mcp: mcp_name.to_string(), + sidecar: sidecar.name.clone(), + reason: format!("failed to spawn: {e}"), + })?; + + forward_output( + child.stdout.take(), + child.stderr.take(), + mcp_name.to_string(), + sidecar.name.clone(), + ); + + Ok(child) +} + +enum SupervisionAction { + Restart { index: usize, config: SidecarEntry }, + Fatal, +} + +async fn supervise_daemons( + server_name: String, + daemons: Arc>>, + sandbox: Arc, + profile: SandboxProfile, + mut cancel_rx: watch::Receiver, + fatal_tx: watch::Sender, +) { + loop { + tokio::select! { + _ = cancel_rx.wait_for(|v| *v) => { + return; + } + _ = tokio::time::sleep(Duration::from_secs(1)) => {} + } + + let actions = { + let mut daemons = daemons.lock().await; + let mut actions = Vec::new(); + + for i in 0..daemons.len() { + let exited = match daemons[i].child.try_wait() { + Ok(Some(status)) => Some(status), + Ok(None) => None, + Err(e) => { + warn!( + server = %server_name, + sidecar = %daemons[i].name, + error = %e, + "failed to check daemon status" + ); + None + } + }; + + let Some(status) = exited else { + continue; + }; + + warn!( + server = %server_name, + sidecar = %daemons[i].name, + exit_code = status.code(), + "daemon sidecar exited unexpectedly" + ); + + if !daemons[i].restart_tracker.try_restart() { + error!( + server = %server_name, + sidecar = %daemons[i].name, + "restart budget exhausted, signaling fatal failure" + ); + actions.push(SupervisionAction::Fatal); + break; + } + + actions.push(SupervisionAction::Restart { + index: i, + config: daemons[i].config.clone(), + }); + } + + actions + }; + + for action in actions { + match action { + SupervisionAction::Fatal => { + let _ = fatal_tx.send(true); + return; + } + SupervisionAction::Restart { index, config } => { + info!( + server = %server_name, + sidecar = %config.name, + "restarting daemon sidecar" + ); + + let respawn_result = spawn_daemon(&*sandbox, &config, &profile, &server_name); + + match respawn_result { + Ok(mut child) => { + let ready_check = config + .ready_check + .as_ref() + .expect("daemon sidecars must have a ready_check"); + + let outcome = poll_ready( + ready_check, + config.ready_timeout, + config.ready_interval, + &mut child, + ) + .await; + + match outcome { + ReadyOutcome::Ready => { + info!( + server = %server_name, + sidecar = %config.name, + "restarted daemon sidecar is ready" + ); + let mut daemons = daemons.lock().await; + daemons[index].child = child; + } + other => { + error!( + server = %server_name, + sidecar = %config.name, + outcome = ?other, + "restarted daemon failed ready check, signaling fatal failure" + ); + let _ = child.kill().await; + let _ = fatal_tx.send(true); + return; + } + } + } + Err(e) => { + error!( + server = %server_name, + sidecar = %config.name, + error = %e, + "failed to respawn daemon, signaling fatal failure" + ); + let _ = fatal_tx.send(true); + return; + } + } + } + } + } + } +} + +fn forward_output( + stdout: Option, + stderr: Option, + server: String, + sidecar: String, +) { + if let Some(stdout) = stdout { + let server = server.clone(); + let sidecar = sidecar.clone(); + tokio::spawn(async move { + let reader = BufReader::new(stdout); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + info!( + server = %server, + sidecar = %sidecar, + "stdout: {line}" + ); + } + }); + } + + if let Some(stderr) = stderr { + tokio::spawn(async move { + let reader = BufReader::new(stderr); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + warn!( + server = %server, + sidecar = %sidecar, + "stderr: {line}" + ); + } + }); + } +} + +#[cfg(unix)] +fn set_process_group(cmd: &mut Command) { + unsafe { + cmd.pre_exec(|| { + if libc::setsid() == -1 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + }); + } +} + +#[cfg(not(unix))] +fn set_process_group(_cmd: &mut Command) {} + +#[cfg(unix)] +fn send_sigterm(child: &tokio::process::Child) { + if let Some(pid) = child.id() { + let pgid = -(pid as libc::pid_t); + let ret = unsafe { libc::kill(pgid, libc::SIGTERM) }; + if ret != 0 { + let ret = unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + tracing::warn!(pid, error = %err, "SIGTERM delivery failed"); + } + } + } +} + +#[cfg(not(unix))] +fn send_sigterm(_child: &tokio::process::Child) {} + +async fn kill_daemons(daemons: &mut [DaemonHandle]) { + for handle in daemons.iter_mut() { + let _ = handle.child.kill().await; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use argus_registry::toml_types::ReadyCheck; + use argus_sandbox::NoopSandbox; + + fn failing_init_sidecar() -> SidecarEntry { + SidecarEntry { + name: "bad-init".into(), + command: "false".into(), + args: vec![], + lifecycle: SidecarLifecycle::Init, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + } + } + + fn succeeding_init_sidecar() -> SidecarEntry { + SidecarEntry { + name: "good-init".into(), + command: "true".into(), + args: vec![], + lifecycle: SidecarLifecycle::Init, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + } + } + + fn failing_daemon_sidecar() -> SidecarEntry { + SidecarEntry { + name: "bad-daemon".into(), + command: "nonexistent-sidecar-binary".into(), + args: vec![], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Command { run: "true".into() }), + ready_timeout: 1, + ready_interval: 100, + } + } + + fn config_with_sidecars(sidecars: Vec) -> McpServerConfig { + McpServerConfig { + name: "test-server".into(), + command: "nonexistent-mcp-server".into(), + args: vec![], + env: vec![], + sandbox_profile: SandboxProfile::default(), + sidecars, + } + } + + #[tokio::test] + async fn spawn_aborts_on_failed_init_sidecar() { + let mut manager = McpManager::new(NoopSandbox); + let config = config_with_sidecars(vec![failing_init_sidecar()]); + + let result = manager.spawn_server(config).await; + assert!(result.is_err()); + + let err = result.unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("bad-init"), + "error should name the sidecar: {msg}" + ); + assert!( + msg.contains("test-server"), + "error should name the MCP server: {msg}" + ); + } + + #[tokio::test] + async fn spawn_aborts_on_failed_daemon_sidecar() { + let mut manager = McpManager::new(NoopSandbox); + let config = config_with_sidecars(vec![failing_daemon_sidecar()]); + + let result = manager.spawn_server(config).await; + assert!(result.is_err()); + + let err = result.unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("bad-daemon"), + "error should name the sidecar: {msg}" + ); + } + + #[tokio::test] + async fn spawn_passes_successful_init_then_fails_on_mcp() { + let mut manager = McpManager::new(NoopSandbox); + let config = config_with_sidecars(vec![succeeding_init_sidecar()]); + + let result = manager.spawn_server(config).await; + assert!(result.is_err()); + let err = result.unwrap_err(); + match err { + McpError::SpawnFailed { name, .. } => { + assert_eq!(name, "test-server"); + } + other => panic!("expected SpawnFailed, got: {other}"), + } + } + + #[tokio::test] + async fn spawn_aborts_second_init_kills_first_daemon() { + let mut manager = McpManager::new(NoopSandbox); + + let daemon = SidecarEntry { + name: "bg-process".into(), + command: "sleep".into(), + args: vec!["60".into()], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Command { run: "true".into() }), + ready_timeout: 5, + ready_interval: 100, + }; + + let config = config_with_sidecars(vec![daemon, failing_init_sidecar()]); + + let result = manager.spawn_server(config).await; + assert!(result.is_err()); + + let err = result.unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("bad-init"), + "should fail on the init sidecar: {msg}" + ); + } + + #[tokio::test] + async fn spawn_no_sidecars_proceeds_to_mcp() { + let mut manager = McpManager::new(NoopSandbox); + let config = config_with_sidecars(vec![]); + + let result = manager.spawn_server(config).await; + assert!(result.is_err()); + match result.unwrap_err() { + McpError::SpawnFailed { .. } => {} + other => panic!("expected SpawnFailed (no such MCP binary), got: {other}"), + } + } + + #[cfg(unix)] + #[tokio::test] + async fn send_sigterm_to_running_process() { + let mut child = Command::new("sleep").arg("60").spawn().unwrap(); + + send_sigterm(&child); + let status = child.wait().await.unwrap(); + assert!(!status.success()); + } + + #[cfg(unix)] + #[tokio::test] + async fn supervision_restarts_crashed_daemon() { + let (cancel_tx, cancel_rx) = watch::channel(false); + let (fatal_tx, _fatal_rx) = watch::channel(false); + + let sidecar_config = SidecarEntry { + name: "crasher".into(), + command: "sleep".into(), + args: vec!["0.1".into()], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Command { run: "true".into() }), + ready_timeout: 5, + ready_interval: 100, + }; + + let child = Command::new("sleep") + .arg("0.1") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + .unwrap(); + let original_pid = child.id(); + + let daemons = Arc::new(Mutex::new(vec![DaemonHandle { + name: "crasher".into(), + child, + config: sidecar_config, + restart_tracker: RestartTracker::new(3, Duration::from_secs(60)), + }])); + + let sandbox = Arc::new(NoopSandbox); + let profile = SandboxProfile::default(); + let task_daemons = Arc::clone(&daemons); + + let handle = tokio::spawn(async move { + supervise_daemons( + "test-server".into(), + task_daemons, + sandbox, + profile, + cancel_rx, + fatal_tx, + ) + .await; + }); + + tokio::time::sleep(Duration::from_secs(4)).await; + + let _ = cancel_tx.send(true); + let _ = handle.await; + + let locked = daemons.lock().await; + let new_pid = locked[0].child.id(); + assert_ne!( + original_pid, new_pid, + "PID should change after restart (original: {original_pid:?}, new: {new_pid:?})" + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn supervision_signals_fatal_on_exhausted_restarts() { + let (_cancel_tx, cancel_rx) = watch::channel(false); + let (fatal_tx, mut fatal_rx) = watch::channel(false); + + let sidecar_config = SidecarEntry { + name: "fast-crasher".into(), + command: "sleep".into(), + args: vec!["0.1".into()], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Command { run: "true".into() }), + ready_timeout: 5, + ready_interval: 100, + }; + + let child = Command::new("sleep") + .arg("0.1") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + .unwrap(); + + let daemons = Arc::new(Mutex::new(vec![DaemonHandle { + name: "fast-crasher".into(), + child, + config: sidecar_config, + restart_tracker: RestartTracker::new(0, Duration::from_secs(60)), + }])); + + let sandbox = Arc::new(NoopSandbox); + let profile = SandboxProfile::default(); + let task_daemons = Arc::clone(&daemons); + + let handle = tokio::spawn(async move { + supervise_daemons( + "test-server".into(), + task_daemons, + sandbox, + profile, + cancel_rx, + fatal_tx, + ) + .await; + }); + + let got_fatal = + tokio::time::timeout(Duration::from_secs(10), fatal_rx.wait_for(|v| *v)).await; + + assert!( + got_fatal.is_ok(), + "should receive fatal signal within timeout" + ); + let _ = handle.await; + } + + #[cfg(unix)] + #[tokio::test] + async fn has_fatal_failure_reflects_supervision_state() { + let mut manager = McpManager::new(NoopSandbox); + + let sidecar = SidecarEntry { + name: "doomed".into(), + command: "sleep".into(), + args: vec!["0.1".into()], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Command { run: "true".into() }), + ready_timeout: 5, + ready_interval: 100, + }; + + let config = config_with_sidecars(vec![sidecar]); + let _ = manager.spawn_server(config).await; + + assert!( + !manager.has_fatal_failure("nonexistent"), + "nonexistent server should not report fatal" + ); + } +} diff --git a/argus/crates/argus-gateway/src/ready_check.rs b/argus/crates/argus-gateway/src/ready_check.rs new file mode 100644 index 0000000..340b3c2 --- /dev/null +++ b/argus/crates/argus-gateway/src/ready_check.rs @@ -0,0 +1,177 @@ +use std::time::Duration; + +use argus_registry::toml_types::ReadyCheck; +use tokio::net::TcpStream; +use tokio::process::Child; +use tokio::time::{Instant, sleep, timeout}; +use tracing::debug; + +#[derive(Debug, PartialEq, Eq)] +pub enum ReadyOutcome { + Ready, + Timeout, + ProcessDied, +} + +pub async fn poll_ready( + check: &ReadyCheck, + timeout_secs: u64, + interval_ms: u64, + child: &mut Child, +) -> ReadyOutcome { + let deadline = Duration::from_secs(timeout_secs); + let interval = Duration::from_millis(interval_ms); + let start = Instant::now(); + let mut attempt = 0u32; + + let http_client = match check { + ReadyCheck::Http { .. } => reqwest::Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .ok(), + _ => None, + }; + + loop { + match child.try_wait() { + Ok(Some(_)) | Err(_) => return ReadyOutcome::ProcessDied, + Ok(None) => {} + } + + if start.elapsed() >= deadline { + return ReadyOutcome::Timeout; + } + + attempt += 1; + debug!(attempt, "polling ready check"); + + if check_once(check, http_client.as_ref()).await { + return ReadyOutcome::Ready; + } + + sleep(interval).await; + + if start.elapsed() >= deadline { + return ReadyOutcome::Timeout; + } + } +} + +async fn check_once(check: &ReadyCheck, http_client: Option<&reqwest::Client>) -> bool { + match check { + ReadyCheck::Http { url } => check_http(url, http_client).await, + ReadyCheck::Tcp { host, port } => check_tcp(host, *port).await, + ReadyCheck::Command { run } => check_command(run).await, + } +} + +async fn check_http(url: &str, client: Option<&reqwest::Client>) -> bool { + let owned; + let client = match client { + Some(c) => c, + None => { + owned = reqwest::Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .unwrap_or_default(); + &owned + } + }; + client + .get(url) + .send() + .await + .is_ok_and(|r| r.status().is_success()) +} + +async fn check_tcp(host: &str, port: u16) -> bool { + timeout(Duration::from_secs(2), TcpStream::connect((host, port))) + .await + .is_ok_and(|r| r.is_ok()) +} + +async fn check_command(run: &str) -> bool { + let parts: Vec<&str> = run.split_whitespace().collect(); + if parts.is_empty() { + return false; + } + tokio::process::Command::new(parts[0]) + .args(&parts[1..]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .await + .is_ok_and(|s| s.success()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn tcp_check_succeeds_on_open_port() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + assert!(check_tcp("127.0.0.1", port).await); + } + + #[tokio::test] + async fn tcp_check_fails_on_closed_port() { + assert!(!check_tcp("127.0.0.1", 1).await); + } + + #[tokio::test] + async fn command_check_succeeds_on_true() { + assert!(check_command("true").await); + } + + #[tokio::test] + async fn command_check_fails_on_false() { + assert!(!check_command("false").await); + } + + #[tokio::test] + async fn command_check_fails_on_empty() { + assert!(!check_command("").await); + } + + #[tokio::test] + async fn poll_ready_detects_process_death() { + let mut child = tokio::process::Command::new("false").spawn().unwrap(); + let _ = child.wait().await; + let check = ReadyCheck::Command { run: "true".into() }; + let result = poll_ready(&check, 5, 100, &mut child).await; + assert_eq!(result, ReadyOutcome::ProcessDied); + } + + #[tokio::test] + async fn poll_ready_succeeds_on_open_port() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + let mut child = tokio::process::Command::new("sleep") + .arg("10") + .spawn() + .unwrap(); + let check = ReadyCheck::Tcp { + host: "127.0.0.1".into(), + port, + }; + let result = poll_ready(&check, 5, 100, &mut child).await; + assert_eq!(result, ReadyOutcome::Ready); + let _ = child.kill().await; + } + + #[tokio::test] + async fn poll_ready_times_out_when_check_never_passes() { + let mut child = tokio::process::Command::new("sleep") + .arg("10") + .spawn() + .unwrap(); + let check = ReadyCheck::Command { + run: "false".into(), + }; + let result = poll_ready(&check, 1, 100, &mut child).await; + assert_eq!(result, ReadyOutcome::Timeout); + let _ = child.kill().await; + } +} diff --git a/argus/crates/argus-gateway/src/restart.rs b/argus/crates/argus-gateway/src/restart.rs new file mode 100644 index 0000000..7efe437 --- /dev/null +++ b/argus/crates/argus-gateway/src/restart.rs @@ -0,0 +1,99 @@ +use std::collections::VecDeque; +use std::time::{Duration, Instant}; + +pub struct RestartTracker { + timestamps: VecDeque, + max_restarts: u32, + window: Duration, +} + +impl RestartTracker { + pub fn new(max_restarts: u32, window: Duration) -> Self { + Self { + timestamps: VecDeque::new(), + max_restarts, + window, + } + } + + pub fn default_policy() -> Self { + Self::new(3, Duration::from_secs(60)) + } + + pub fn can_restart(&mut self) -> bool { + self.expire_old(); + self.timestamps.len() < self.max_restarts as usize + } + + pub fn try_restart(&mut self) -> bool { + self.expire_old(); + if self.timestamps.len() < self.max_restarts as usize { + self.timestamps.push_back(Instant::now()); + true + } else { + false + } + } + + pub fn record_restart(&mut self) { + self.expire_old(); + self.timestamps.push_back(Instant::now()); + } + + fn expire_old(&mut self) { + let cutoff = Instant::now() - self.window; + while self.timestamps.front().is_some_and(|t| *t < cutoff) { + self.timestamps.pop_front(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fresh_tracker_allows_restart() { + let mut tracker = RestartTracker::default_policy(); + assert!(tracker.can_restart()); + } + + #[test] + fn exhausted_tracker_denies_restart() { + let mut tracker = RestartTracker::new(2, Duration::from_secs(60)); + tracker.record_restart(); + tracker.record_restart(); + assert!(!tracker.can_restart()); + } + + #[test] + fn restarts_within_budget_allowed() { + let mut tracker = RestartTracker::new(3, Duration::from_secs(60)); + tracker.record_restart(); + tracker.record_restart(); + assert!(tracker.can_restart()); + } + + #[test] + fn expired_restarts_free_budget() { + let mut tracker = RestartTracker::new(1, Duration::from_millis(10)); + tracker.record_restart(); + assert!(!tracker.can_restart()); + std::thread::sleep(Duration::from_millis(20)); + assert!(tracker.can_restart()); + } + + #[test] + fn zero_max_always_denies() { + let mut tracker = RestartTracker::new(0, Duration::from_secs(60)); + assert!(!tracker.can_restart()); + } + + #[test] + fn try_restart_atomically_checks_and_records() { + let mut tracker = RestartTracker::new(2, Duration::from_secs(60)); + assert!(tracker.try_restart()); + assert!(tracker.try_restart()); + assert!(!tracker.try_restart()); + } +} diff --git a/argus/crates/argus-gateway/src/service.rs b/argus/crates/argus-gateway/src/service.rs new file mode 100644 index 0000000..9fec2f7 --- /dev/null +++ b/argus/crates/argus-gateway/src/service.rs @@ -0,0 +1,122 @@ +use argus_kernel::{ + AgentId, AuthorizerOracle, BackgroundTheory, CapKind, ConfLevel, ContentGateOracle, EventStore, + InvocationId, Kernel, KernelEvent, KernelState, ToolId, +}; +use tokio::sync::Mutex; + +use crate::GatewayError; + +pub struct GatewayService +where + A: AuthorizerOracle, + C: ContentGateOracle, + E: EventStore, +{ + kernel: Mutex>, +} + +impl GatewayService +where + A: AuthorizerOracle + Send, + C: ContentGateOracle + Send, + E: EventStore + Send, +{ + pub fn new(background: BackgroundTheory, authorizer: A, content_gate: C, events: E) -> Self { + Self { + kernel: Mutex::new(Kernel::new(background, authorizer, content_gate, events)), + } + } + + pub async fn register_tool(&self, tool: ToolId) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.register_tool(tool)?) + } + + pub async fn delegate( + &self, + grantor: AgentId, + grantee: AgentId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.delegate(grantor, grantee)?) + } + + pub async fn grant_capability( + &self, + parent: AgentId, + child: AgentId, + cap: CapKind, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.grant_capability(parent, child, cap)?) + } + + pub async fn revoke( + &self, + parent: AgentId, + target: AgentId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.revoke(parent, target)?) + } + + pub async fn cascade_revoke( + &self, + child: AgentId, + parent: AgentId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.cascade_revoke(child, parent)?) + } + + pub async fn invoke_start( + &self, + agent: AgentId, + tool: ToolId, + inv: InvocationId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.invoke_start(agent, tool, inv)?) + } + + pub async fn invoke_complete( + &self, + agent: AgentId, + inv: InvocationId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.invoke_complete(agent, inv)?) + } + + pub async fn return_endorsed( + &self, + child: AgentId, + parent: AgentId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.return_endorsed(child, parent)?) + } + + pub async fn return_unendorsed( + &self, + child: AgentId, + parent: AgentId, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.return_unendorsed(child, parent)?) + } + + pub async fn sentinel_elevate_taint( + &self, + agent: AgentId, + level: ConfLevel, + ) -> Result { + let mut kernel = self.kernel.lock().await; + Ok(kernel.sentinel_elevate_taint(agent, level)?) + } + + pub async fn state_snapshot(&self) -> KernelState { + let kernel = self.kernel.lock().await; + kernel.state().clone() + } +} diff --git a/argus/crates/argus-gateway/tests/grpc_integration.rs b/argus/crates/argus-gateway/tests/grpc_integration.rs new file mode 100644 index 0000000..48bb20c --- /dev/null +++ b/argus/crates/argus-gateway/tests/grpc_integration.rs @@ -0,0 +1,269 @@ +use std::collections::BTreeSet; +use std::sync::Arc; + +use argus_audit::InMemoryEventStore; +use argus_gateway::proto::argus_gateway_client::ArgusGatewayClient; +use argus_gateway::{ArgusGrpcService, GatewayService, proto}; +use argus_kernel::{ + BackgroundTheoryBuilder, CapKind, ConfLevel, EgressKind, FlowMode, ToolId, ToolMetadata, +}; +use argus_oracle::{MockAuthorizer, MockContentGate}; +use tokio::net::TcpListener; +use tonic::transport::{Channel, Server}; + +type TestGateway = GatewayService; + +fn test_gateway() -> TestGateway { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + b.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + GatewayService::new( + b.build(), + MockAuthorizer::allow_all(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ) +} + +fn deny_gateway() -> TestGateway { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + GatewayService::new( + b.build(), + MockAuthorizer::new(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ) +} + +async fn start_server(gateway: TestGateway) -> ArgusGatewayClient { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + let grpc_service = ArgusGrpcService::new(Arc::new(gateway)); + + tokio::spawn(async move { + Server::builder() + .add_service(grpc_service.into_server()) + .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener)) + .await + .unwrap(); + }); + + let endpoint = format!("http://127.0.0.1:{}", addr.port()); + loop { + match ArgusGatewayClient::connect(endpoint.clone()).await { + Ok(client) => break client, + Err(_) => tokio::time::sleep(std::time::Duration::from_millis(5)).await, + } + } +} + +#[tokio::test] +async fn grpc_register_tool() { + let mut client = start_server(test_gateway()).await; + let resp = client + .register_tool(proto::RegisterToolRequest { + tool_id: "read_file".into(), + }) + .await + .unwrap() + .into_inner(); + + assert_eq!(resp.sequence, 1); + assert_eq!( + resp.action, + proto::KernelActionKind::KernelActionRegisterTool as i32 + ); +} + +#[tokio::test] +async fn grpc_delegate_and_grant_capability() { + let mut client = start_server(test_gateway()).await; + + client + .register_tool(proto::RegisterToolRequest { + tool_id: "read_file".into(), + }) + .await + .unwrap(); + + let resp = client + .delegate(proto::DelegateRequest { + grantor: "root".into(), + grantee: "worker".into(), + }) + .await + .unwrap() + .into_inner(); + assert_eq!( + resp.action, + proto::KernelActionKind::KernelActionDelegate as i32 + ); + + let resp = client + .grant_capability(proto::GrantCapabilityRequest { + parent: "root".into(), + child: "worker".into(), + capability: proto::CapKind::FilesystemRead as i32, + }) + .await + .unwrap() + .into_inner(); + assert_eq!( + resp.action, + proto::KernelActionKind::KernelActionGrantCapability as i32 + ); + + let state = client + .state_snapshot(proto::StateSnapshotRequest {}) + .await + .unwrap() + .into_inner(); + assert!(state.active_agents.contains(&"worker".to_string())); + assert!(state.agent_capabilities.contains_key("worker")); +} + +#[tokio::test] +async fn grpc_full_invoke_pipeline() { + let mut client = start_server(test_gateway()).await; + + client + .register_tool(proto::RegisterToolRequest { + tool_id: "read_file".into(), + }) + .await + .unwrap(); + client + .delegate(proto::DelegateRequest { + grantor: "root".into(), + grantee: "worker".into(), + }) + .await + .unwrap(); + client + .grant_capability(proto::GrantCapabilityRequest { + parent: "root".into(), + child: "worker".into(), + capability: proto::CapKind::FilesystemRead as i32, + }) + .await + .unwrap(); + + let resp = client + .invoke_start(proto::InvokeStartRequest { + agent_id: "worker".into(), + tool_id: "read_file".into(), + invocation_id: "inv-1".into(), + }) + .await + .unwrap() + .into_inner(); + assert_eq!( + resp.action, + proto::KernelActionKind::KernelActionInvokeStart as i32 + ); + + let resp = client + .invoke_complete(proto::InvokeCompleteRequest { + agent_id: "worker".into(), + invocation_id: "inv-1".into(), + }) + .await + .unwrap() + .into_inner(); + assert_eq!( + resp.action, + proto::KernelActionKind::KernelActionInvokeComplete as i32 + ); + + let state = client + .state_snapshot(proto::StateSnapshotRequest {}) + .await + .unwrap() + .into_inner(); + let worker_flights = state.in_flight.get("worker"); + assert!(worker_flights.is_none() || worker_flights.unwrap().invocation_ids.is_empty()); +} + +#[tokio::test] +async fn grpc_authorization_denial() { + let mut client = start_server(deny_gateway()).await; + + client + .register_tool(proto::RegisterToolRequest { + tool_id: "read_file".into(), + }) + .await + .unwrap(); + client + .delegate(proto::DelegateRequest { + grantor: "root".into(), + grantee: "worker".into(), + }) + .await + .unwrap(); + client + .grant_capability(proto::GrantCapabilityRequest { + parent: "root".into(), + child: "worker".into(), + capability: proto::CapKind::FilesystemRead as i32, + }) + .await + .unwrap(); + + let result = client + .invoke_start(proto::InvokeStartRequest { + agent_id: "worker".into(), + tool_id: "read_file".into(), + invocation_id: "inv-1".into(), + }) + .await; + + assert!(result.is_err()); + let status = result.unwrap_err(); + assert_eq!(status.code(), tonic::Code::FailedPrecondition); +} + +#[tokio::test] +async fn grpc_state_snapshot_initial() { + let mut client = start_server(test_gateway()).await; + let state = client + .state_snapshot(proto::StateSnapshotRequest {}) + .await + .unwrap() + .into_inner(); + + assert!(state.active_agents.contains(&"root".to_string())); + assert!(state.registered_tools.is_empty()); + assert!(state.in_flight.is_empty()); +} diff --git a/argus/crates/argus-gateway/tests/integration.rs b/argus/crates/argus-gateway/tests/integration.rs new file mode 100644 index 0000000..d0e2771 --- /dev/null +++ b/argus/crates/argus-gateway/tests/integration.rs @@ -0,0 +1,356 @@ +use std::collections::BTreeSet; +use std::sync::Arc; + +use argus_audit::InMemoryEventStore; +use argus_gateway::GatewayService; +use argus_kernel::{ + AgentId, BackgroundTheoryBuilder, CapKind, ConfLevel, EgressKind, FlowMode, InvocationId, + KernelAction, ToolId, ToolMetadata, +}; +use argus_oracle::{MockAuthorizer, MockContentGate}; + +fn test_gateway() -> GatewayService { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("check_exists"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: true, + }, + ); + b.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + + GatewayService::new( + b.build(), + MockAuthorizer::allow_all(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ) +} + +#[tokio::test] +async fn register_tool_records_event() { + let gw = test_gateway(); + let event = gw.register_tool(ToolId::new("read_file")).await.unwrap(); + assert_eq!(event.sequence, 1); +} + +#[tokio::test] +async fn delegation_and_capability_flow() { + let gw = test_gateway(); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + + let e1 = gw + .delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + assert_eq!(e1.sequence, 2); + + gw.grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + let state = gw.state_snapshot().await; + assert!(state.agent_active.contains(&AgentId::new("worker"))); + assert!( + state + .agent_cap + .get(&AgentId::new("worker")) + .unwrap() + .contains(&CapKind::FilesystemRead) + ); +} + +#[tokio::test] +async fn revoke_removes_agent() { + let gw = test_gateway(); + gw.delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + + gw.revoke(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + + let state = gw.state_snapshot().await; + assert!(!state.agent_active.contains(&AgentId::new("worker"))); +} + +#[tokio::test] +async fn full_invocation_pipeline() { + let gw = test_gateway(); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + gw.delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + gw.grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + let e_start = gw + .invoke_start( + AgentId::new("worker"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .await + .unwrap(); + assert_eq!(e_start.sequence, 4); + + let state = gw.state_snapshot().await; + assert!( + state + .in_flight + .get(&AgentId::new("worker")) + .unwrap() + .contains(&InvocationId::new("inv-1")) + ); + + let e_complete = gw + .invoke_complete(AgentId::new("worker"), InvocationId::new("inv-1")) + .await + .unwrap(); + assert_eq!(e_complete.sequence, 5); + + let state = gw.state_snapshot().await; + assert!( + state + .taint_levels + .get(&AgentId::new("worker")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); +} + +#[tokio::test] +async fn return_endorsed_no_taint_propagation() { + let gw = test_gateway(); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + gw.delegate(AgentId::root(), AgentId::new("parent")) + .await + .unwrap(); + gw.grant_capability( + AgentId::root(), + AgentId::new("parent"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + gw.delegate(AgentId::new("parent"), AgentId::new("child")) + .await + .unwrap(); + gw.grant_capability( + AgentId::new("parent"), + AgentId::new("child"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + gw.invoke_start( + AgentId::new("child"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .await + .unwrap(); + gw.invoke_complete(AgentId::new("child"), InvocationId::new("i1")) + .await + .unwrap(); + + gw.return_endorsed(AgentId::new("child"), AgentId::new("parent")) + .await + .unwrap(); + + let state = gw.state_snapshot().await; + assert!(!state.taint_levels.contains_key(&AgentId::new("parent"))); +} + +#[tokio::test] +async fn return_unendorsed_propagates_taint() { + let gw = test_gateway(); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + gw.delegate(AgentId::root(), AgentId::new("parent")) + .await + .unwrap(); + gw.grant_capability( + AgentId::root(), + AgentId::new("parent"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + gw.delegate(AgentId::new("parent"), AgentId::new("child")) + .await + .unwrap(); + gw.grant_capability( + AgentId::new("parent"), + AgentId::new("child"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + gw.invoke_start( + AgentId::new("child"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .await + .unwrap(); + gw.invoke_complete(AgentId::new("child"), InvocationId::new("i1")) + .await + .unwrap(); + + gw.return_unendorsed(AgentId::new("child"), AgentId::new("parent")) + .await + .unwrap(); + + let state = gw.state_snapshot().await; + assert!( + state + .taint_levels + .get(&AgentId::new("parent")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); +} + +#[tokio::test] +async fn authorization_denial() { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + + let gw = GatewayService::new( + b.build(), + MockAuthorizer::new(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + gw.delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + gw.grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + let result = gw + .invoke_start( + AgentId::new("worker"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn precondition_unregistered_tool() { + let gw = test_gateway(); + let result = gw.register_tool(ToolId::new("nonexistent")).await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn precondition_duplicate_delegate() { + let gw = test_gateway(); + gw.delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + let result = gw.delegate(AgentId::root(), AgentId::new("worker")).await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn concurrent_tool_registration() { + let gw = Arc::new(test_gateway()); + let mut handles = vec![]; + + for i in 0..3 { + let tool_name = match i { + 0 => "read_file", + 1 => "send_email", + 2 => "check_exists", + _ => unreachable!(), + }; + let gw = Arc::clone(&gw); + handles.push(tokio::spawn(async move { + gw.register_tool(ToolId::new(tool_name)).await + })); + } + + let mut successes = 0; + for handle in handles { + if handle.await.unwrap().is_ok() { + successes += 1; + } + } + assert_eq!(successes, 3); + + let state = gw.state_snapshot().await; + assert_eq!(state.tool_registered.len(), 3); +} + +#[tokio::test] +async fn sentinel_elevate_taint_through_gateway() { + let gw = test_gateway(); + let agent = AgentId::new("a1"); + gw.delegate(AgentId::root(), agent.clone()).await.unwrap(); + + let event = gw + .sentinel_elevate_taint(agent.clone(), ConfLevel::Internal) + .await + .unwrap(); + assert!(matches!( + event.action, + KernelAction::SentinelElevateTaint { .. } + )); + + let st = gw.state_snapshot().await; + assert!(st.taint_levels[&agent].contains(&ConfLevel::Internal)); +} diff --git a/argus/crates/argus-gateway/tests/llm_proxy_integration.rs b/argus/crates/argus-gateway/tests/llm_proxy_integration.rs new file mode 100644 index 0000000..2085c16 --- /dev/null +++ b/argus/crates/argus-gateway/tests/llm_proxy_integration.rs @@ -0,0 +1,608 @@ +use std::collections::{BTreeSet, VecDeque}; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::sync::Arc; + +use bytes::Bytes; +use http_body_util::{BodyExt, Full}; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::{Request, Response, StatusCode}; +use hyper_util::rt::TokioIo; +use serde_json::{Value, json}; +use tokio::net::TcpListener; +use tokio::sync::Mutex; + +use argus_audit::InMemoryEventStore; +use argus_gateway::{GatewayService, LlmFormat, LlmProxy, McpManager, ProxyConfig, UpstreamConfig}; +use argus_kernel::{AgentId, BackgroundTheoryBuilder, CapKind, ConfLevel, ToolId, ToolMetadata}; +use argus_oracle::{MockAuthorizer, MockContentGate}; +use argus_sandbox::NoopSandbox; + +struct MockLlm { + responses: Arc>>, + requests: Arc>>, +} + +impl MockLlm { + fn new(responses: Vec) -> Self { + Self { + responses: Arc::new(Mutex::new( + responses.into_iter().map(|v| (StatusCode::OK, v)).collect(), + )), + requests: Arc::new(Mutex::new(Vec::new())), + } + } + + fn with_status(responses: Vec<(StatusCode, Value)>) -> Self { + Self { + responses: Arc::new(Mutex::new(VecDeque::from(responses))), + requests: Arc::new(Mutex::new(Vec::new())), + } + } + + async fn start(self) -> (SocketAddr, Arc>>) { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + let responses = self.responses.clone(); + let requests = self.requests.clone(); + + tokio::spawn(async move { + loop { + let Ok((stream, _)) = listener.accept().await else { + break; + }; + let io = TokioIo::new(stream); + let responses = responses.clone(); + let requests = requests.clone(); + tokio::spawn(async move { + let responses = responses.clone(); + let requests = requests.clone(); + let svc = service_fn(move |req: Request| { + let responses = responses.clone(); + let requests = requests.clone(); + async move { + let body = req.into_body().collect().await.unwrap().to_bytes(); + let body_json: Value = + serde_json::from_slice(&body).unwrap_or(json!({})); + requests.lock().await.push(body_json); + + let (status, response) = responses + .lock() + .await + .pop_front() + .unwrap_or((StatusCode::OK, json!({"error": "no more responses"}))); + let bytes = serde_json::to_vec(&response).unwrap(); + Ok::<_, Infallible>( + Response::builder() + .status(status) + .header("content-type", "application/json") + .body(Full::new(Bytes::from(bytes)).boxed()) + .unwrap(), + ) + } + }); + let _ = http1::Builder::new().serve_connection(io, svc).await; + }); + } + }); + + (addr, self.requests) + } +} + +fn openai_text_response(text: &str) -> Value { + json!({ + "id": "chatcmpl-test", + "object": "chat.completion", + "model": "gpt-4", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": text}, + "finish_reason": "stop" + }] + }) +} + +fn openai_tool_call_response(calls: Vec<(&str, &str, &str)>) -> Value { + let tool_calls: Vec = calls + .iter() + .map(|(id, name, args)| { + json!({ + "id": id, + "type": "function", + "function": {"name": name, "arguments": args} + }) + }) + .collect(); + json!({ + "id": "chatcmpl-test", + "object": "chat.completion", + "model": "gpt-4", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": null, "tool_calls": tool_calls}, + "finish_reason": "tool_calls" + }] + }) +} + +fn test_background() -> BackgroundTheoryBuilder { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Internal, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("write_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemWrite]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b +} + +fn allow_gateway() -> GatewayService { + GatewayService::new( + test_background().build(), + MockAuthorizer::allow_all(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ) +} + +fn deny_gateway() -> GatewayService { + GatewayService::new( + test_background().build(), + MockAuthorizer::new(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ) +} + +async fn setup_agent( + gateway: &GatewayService, +) { + gateway + .register_tool(ToolId::new("read_file")) + .await + .unwrap(); + gateway + .register_tool(ToolId::new("write_file")) + .await + .unwrap(); + gateway + .delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + gateway + .grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + gateway + .grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemWrite, + ) + .await + .unwrap(); +} + +async fn start_proxy_with_format( + gateway: GatewayService, + mock_addr: SocketAddr, + format: LlmFormat, +) -> SocketAddr { + let gateway = Arc::new(gateway); + let mcp = Arc::new(tokio::sync::RwLock::new(McpManager::new(NoopSandbox))); + let config = ProxyConfig { + max_tool_rounds: 3, + upstreams: vec![UpstreamConfig { + name: "mock".into(), + url: format!("http://{mock_addr}"), + format, + }], + }; + let proxy = Arc::new(LlmProxy::new(gateway, mcp, config)); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + tokio::spawn(async move { proxy.serve(listener).await }); + addr +} + +async fn start_proxy( + gateway: GatewayService, + mock_addr: SocketAddr, +) -> SocketAddr { + start_proxy_with_format(gateway, mock_addr, LlmFormat::OpenAi).await +} + +fn proxy_client() -> reqwest::Client { + reqwest::Client::new() +} + +#[tokio::test] +async fn happy_path_no_tool_calls() { + let mock = MockLlm::new(vec![openai_text_response("Hello from the LLM")]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({"model": "gpt-4", "messages": [{"role": "user", "content": "Hi"}]})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!( + body["choices"][0]["message"]["content"], + "Hello from the LLM" + ); +} + +#[tokio::test] +async fn missing_agent_header_returns_401() { + let mock = MockLlm::new(vec![openai_text_response("Hi")]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .json(&json!({"model": "gpt-4", "messages": []})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 401); +} + +#[tokio::test] +async fn unknown_agent_returns_401() { + let mock = MockLlm::new(vec![openai_text_response("Hi")]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "ghost") + .json(&json!({"model": "gpt-4", "messages": []})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 401); +} + +#[tokio::test] +async fn unsupported_path_returns_404() { + let mock = MockLlm::new(vec![]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v2/unknown")) + .header("x-argus-agent", "worker") + .json(&json!({"model": "gpt-4"})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 404); +} + +#[tokio::test] +async fn upstream_error_returns_502() { + let mock = MockLlm::with_status(vec![( + StatusCode::INTERNAL_SERVER_ERROR, + json!({"error": "boom"}), + )]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({"model": "gpt-4", "messages": [{"role": "user", "content": "Hi"}]})) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 502); +} + +#[tokio::test] +async fn tool_call_authorized_mcp_fails_then_text() { + let mock = MockLlm::new(vec![ + openai_tool_call_response(vec![("call_1", "read_file", "{\"path\":\"/tmp\"}")]), + openai_text_response("I could not read the file"), + ]); + let (mock_addr, requests) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Read /tmp"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!( + body["choices"][0]["message"]["content"], + "I could not read the file" + ); + + let reqs = requests.lock().await; + assert_eq!(reqs.len(), 2); + let second_messages = reqs[1]["messages"].as_array().unwrap(); + assert!(second_messages.iter().any(|m| m["role"] == "tool")); +} + +#[tokio::test] +async fn tool_call_denied() { + let mock = MockLlm::new(vec![ + openai_tool_call_response(vec![("call_1", "read_file", "{}")]), + openai_text_response("I was denied access"), + ]); + let (mock_addr, requests) = mock.start().await; + + let gw = deny_gateway(); + gw.register_tool(ToolId::new("read_file")).await.unwrap(); + gw.register_tool(ToolId::new("write_file")).await.unwrap(); + gw.delegate(AgentId::root(), AgentId::new("worker")) + .await + .unwrap(); + gw.grant_capability( + AgentId::root(), + AgentId::new("worker"), + CapKind::FilesystemRead, + ) + .await + .unwrap(); + + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Read file"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!( + body["choices"][0]["message"]["content"], + "I was denied access" + ); + + let reqs = requests.lock().await; + assert_eq!(reqs.len(), 2); + let tool_msg = reqs[1]["messages"] + .as_array() + .unwrap() + .iter() + .find(|m| m["role"] == "tool") + .unwrap(); + assert!( + tool_msg["content"] + .as_str() + .unwrap() + .contains("Authorization denied") + ); +} + +#[tokio::test] +async fn streaming_returns_sse_content_type() { + let mock = MockLlm::new(vec![openai_text_response("Streamed response")]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "gpt-4", + "stream": true, + "messages": [{"role": "user", "content": "Hi"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let ct = resp + .headers() + .get("content-type") + .unwrap() + .to_str() + .unwrap(); + assert_eq!(ct, "text/event-stream"); + + let body = resp.text().await.unwrap(); + assert!(body.contains("data:")); + assert!(body.contains("[DONE]")); +} + +fn anthropic_text_response(text: &str) -> Value { + json!({ + "id": "msg_test", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-20250514", + "content": [{"type": "text", "text": text}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5} + }) +} + +fn anthropic_tool_call_response(calls: Vec<(&str, &str, Value)>) -> Value { + let content: Vec = calls + .into_iter() + .map(|(id, name, input)| { + json!({ + "type": "tool_use", + "id": id, + "name": name, + "input": input, + }) + }) + .collect(); + json!({ + "id": "msg_test", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-20250514", + "content": content, + "stop_reason": "tool_use", + "usage": {"input_tokens": 10, "output_tokens": 20} + }) +} + +async fn start_proxy_anthropic( + gateway: GatewayService, + mock_addr: SocketAddr, +) -> SocketAddr { + start_proxy_with_format(gateway, mock_addr, LlmFormat::Anthropic).await +} + +#[tokio::test] +async fn anthropic_happy_path_no_tool_calls() { + let mock = MockLlm::new(vec![anthropic_text_response("Hello from Claude")]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy_anthropic(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/messages")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "claude-sonnet-4-20250514", + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Hi"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!(body["content"][0]["text"], "Hello from Claude"); +} + +#[tokio::test] +async fn anthropic_tool_call_then_text() { + let mock = MockLlm::new(vec![ + anthropic_tool_call_response(vec![("toolu_1", "read_file", json!({"path": "/tmp"}))]), + anthropic_text_response("I could not read the file"), + ]); + let (mock_addr, requests) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy_anthropic(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/messages")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "claude-sonnet-4-20250514", + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Read /tmp"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!(body["content"][0]["text"], "I could not read the file"); + + let reqs = requests.lock().await; + assert_eq!(reqs.len(), 2); + let second_messages = reqs[1]["messages"].as_array().unwrap(); + assert!(second_messages.iter().any(|m| { + m["role"] == "user" + && m["content"] + .as_array() + .map(|c| c.iter().any(|b| b["type"] == "tool_result")) + .unwrap_or(false) + })); +} + +#[tokio::test] +async fn max_rounds_exceeded() { + let mock = MockLlm::new(vec![ + openai_tool_call_response(vec![("call_1", "read_file", "{}")]), + openai_tool_call_response(vec![("call_2", "read_file", "{}")]), + openai_tool_call_response(vec![("call_3", "read_file", "{}")]), + openai_tool_call_response(vec![("call_4", "read_file", "{}")]), + ]); + let (mock_addr, _) = mock.start().await; + + let gw = allow_gateway(); + setup_agent(&gw).await; + let proxy_addr = start_proxy(gw, mock_addr).await; + + let resp = proxy_client() + .post(format!("http://{proxy_addr}/v1/chat/completions")) + .header("x-argus-agent", "worker") + .json(&json!({ + "model": "gpt-4", + "messages": [{"role": "user", "content": "loop forever"}] + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 500); + let body: Value = resp.json().await.unwrap(); + let msg = body["error"]["message"].as_str().unwrap(); + assert!(msg.contains("max tool rounds")); +} diff --git a/argus/crates/argus-gateway/tests/mcp_integration.rs b/argus/crates/argus-gateway/tests/mcp_integration.rs new file mode 100644 index 0000000..4f10f5f --- /dev/null +++ b/argus/crates/argus-gateway/tests/mcp_integration.rs @@ -0,0 +1,157 @@ +use std::collections::BTreeSet; + +use argus_audit::InMemoryEventStore; +use argus_gateway::{GatewayService, McpManager, McpServerConfig}; +use argus_kernel::{ + AgentId, BackgroundTheoryBuilder, ConfLevel, InvocationId, ToolId, ToolMetadata, +}; +use argus_oracle::{MockAuthorizer, MockContentGate}; +use argus_sandbox::{NoopSandbox, SandboxProfile}; +use serde_json::json; + +fn echo_server_path() -> String { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("CARGO_MANIFEST_DIR should have at least two parent directories") + .join("target/debug/examples/echo_mcp_server") + .to_string_lossy() + .into_owned() +} + +fn echo_server_config() -> McpServerConfig { + McpServerConfig { + name: "echo".to_string(), + command: echo_server_path(), + args: vec![], + env: vec![], + sandbox_profile: SandboxProfile::default(), + sidecars: vec![], + } +} + +#[tokio::test] +async fn spawn_server_discovers_tools() { + let mut manager = McpManager::new(NoopSandbox); + manager.spawn_server(echo_server_config()).await.unwrap(); + + let mut tools = manager.managed_tools(); + tools.sort(); + assert_eq!(tools, vec!["echo/add", "echo/echo"]); +} + +#[tokio::test] +async fn call_tool_routes_to_correct_server() { + let mut manager = McpManager::new(NoopSandbox); + manager.spawn_server(echo_server_config()).await.unwrap(); + + let result = manager + .call_tool("echo/echo", json!({ "message": "hello world" })) + .await + .unwrap(); + + assert_eq!(result, "hello world"); +} + +#[tokio::test] +async fn call_tool_add_returns_sum() { + let mut manager = McpManager::new(NoopSandbox); + manager.spawn_server(echo_server_config()).await.unwrap(); + + let result = manager + .call_tool("echo/add", json!({ "a": 2.0, "b": 3.0 })) + .await + .unwrap(); + + assert_eq!(result, "5"); +} + +#[tokio::test] +async fn call_tool_unknown_tool_returns_error() { + let manager = McpManager::new(NoopSandbox); + + let result = manager.call_tool("nonexistent/tool", json!({})).await; + + assert!(result.is_err()); +} + +#[tokio::test] +async fn shutdown_clears_all_servers() { + let mut manager = McpManager::new(NoopSandbox); + manager.spawn_server(echo_server_config()).await.unwrap(); + + assert!(!manager.managed_tools().is_empty()); + + manager.shutdown().await; + + assert!(manager.managed_tools().is_empty()); +} + +#[tokio::test] +async fn spawn_all_starts_servers_from_configs() { + let mut manager = McpManager::new(NoopSandbox); + + let results = manager.spawn_all(vec![echo_server_config()]).await; + + assert_eq!(results.len(), 1); + assert!(results[0].is_ok()); + assert!(!manager.managed_tools().is_empty()); +} + +#[tokio::test] +async fn full_invoke_pipeline_with_mcp() { + let mut mcp = McpManager::new(NoopSandbox); + mcp.spawn_server(echo_server_config()).await.unwrap(); + + let managed = mcp.managed_tools(); + assert!(managed.contains(&"echo/echo".to_string())); + + let mut bg = BackgroundTheoryBuilder::new(); + for tool_name in &managed { + bg.register_tool( + ToolId::new(tool_name), + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Public, + endorsed: true, + }, + ); + } + + let gateway = GatewayService::new( + bg.build(), + MockAuthorizer::allow_all(), + MockContentGate::pass_all(), + InMemoryEventStore::new(), + ); + + for tool_name in &managed { + gateway.register_tool(ToolId::new(tool_name)).await.unwrap(); + } + + let worker = AgentId::new("worker"); + gateway + .delegate(AgentId::root(), worker.clone()) + .await + .unwrap(); + + let tool = ToolId::new("echo/echo"); + let inv = InvocationId::new("inv-001"); + + gateway + .invoke_start(worker.clone(), tool, inv.clone()) + .await + .unwrap(); + + let result = mcp + .call_tool("echo/echo", json!({ "message": "authorized call" })) + .await + .unwrap(); + + assert_eq!(result, "authorized call"); + + gateway.invoke_complete(worker, inv).await.unwrap(); + + mcp.shutdown().await; +} diff --git a/argus/crates/argus-kernel/Cargo.toml b/argus/crates/argus-kernel/Cargo.toml new file mode 100644 index 0000000..3f77304 --- /dev/null +++ b/argus/crates/argus-kernel/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "argus-kernel" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +thiserror = { workspace = true } + +[dev-dependencies] +serde = { workspace = true } +serde_json = { workspace = true } diff --git a/argus/crates/argus-kernel/src/background.rs b/argus/crates/argus-kernel/src/background.rs new file mode 100644 index 0000000..4fa0aa7 --- /dev/null +++ b/argus/crates/argus-kernel/src/background.rs @@ -0,0 +1,171 @@ +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use crate::capability::CapKind; +use crate::types::{AgentId, ConfLevel, EgressKind, ToolId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ToolMetadata { + pub capabilities: BTreeSet, + pub egress: BTreeSet, + pub conf_floor: ConfLevel, + pub endorsed: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FlowMode { + Allow, + Inspect, + Deny, +} + +#[derive(Clone, Debug)] +pub struct BackgroundTheory { + tools: BTreeMap, + flow_policy: BTreeMap<(ConfLevel, EgressKind), FlowMode>, + flow_overrides: BTreeSet<(AgentId, ToolId, ConfLevel)>, +} + +impl BackgroundTheory { + pub fn has_tool(&self, tool: &ToolId) -> bool { + self.tools.contains_key(tool) + } + + pub fn tool_metadata(&self, tool: &ToolId) -> Option<&ToolMetadata> { + self.tools.get(tool) + } + + pub fn flow_mode(&self, level: ConfLevel, egress: EgressKind) -> FlowMode { + self.flow_policy + .get(&(level, egress)) + .copied() + .unwrap_or(FlowMode::Deny) + } + + pub fn has_flow_override(&self, agent: &AgentId, tool: &ToolId, level: ConfLevel) -> bool { + self.flow_overrides + .contains(&(agent.clone(), tool.clone(), level)) + } + + pub fn registered_tools(&self) -> impl Iterator { + self.tools.keys() + } +} + +pub struct BackgroundTheoryBuilder { + tools: BTreeMap, + flow_policy: BTreeMap<(ConfLevel, EgressKind), FlowMode>, + flow_overrides: BTreeSet<(AgentId, ToolId, ConfLevel)>, +} + +impl BackgroundTheoryBuilder { + pub fn new() -> Self { + Self { + tools: BTreeMap::new(), + flow_policy: BTreeMap::new(), + flow_overrides: BTreeSet::new(), + } + } + + pub fn register_tool(&mut self, id: ToolId, metadata: ToolMetadata) -> &mut Self { + self.tools.insert(id, metadata); + self + } + + pub fn set_flow(&mut self, level: ConfLevel, egress: EgressKind, mode: FlowMode) -> &mut Self { + self.flow_policy.insert((level, egress), mode); + self + } + + pub fn add_override(&mut self, agent: AgentId, tool: ToolId, level: ConfLevel) -> &mut Self { + self.flow_overrides.insert((agent, tool, level)); + self + } + + pub fn build(self) -> BackgroundTheory { + BackgroundTheory { + tools: self.tools, + flow_policy: self.flow_policy, + flow_overrides: self.flow_overrides, + } + } +} + +impl Default for BackgroundTheoryBuilder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deny_is_default_flow_mode() { + let bg = BackgroundTheoryBuilder::new().build(); + assert_eq!( + bg.flow_mode(ConfLevel::Sensitive, EgressKind::NetworkExternal), + FlowMode::Deny, + ); + } + + #[test] + fn flow_policy_last_write_wins() { + let mut builder = BackgroundTheoryBuilder::new(); + builder.set_flow( + ConfLevel::Internal, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + builder.set_flow( + ConfLevel::Internal, + EgressKind::NetworkExternal, + FlowMode::Inspect, + ); + let bg = builder.build(); + assert_eq!( + bg.flow_mode(ConfLevel::Internal, EgressKind::NetworkExternal), + FlowMode::Inspect, + ); + } + + #[test] + fn tool_metadata_lookup() { + let mut builder = BackgroundTheoryBuilder::new(); + let meta = ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Internal, + endorsed: false, + }; + builder.register_tool(ToolId::new("read_file"), meta.clone()); + let bg = builder.build(); + + let found = bg.tool_metadata(&ToolId::new("read_file")).unwrap(); + assert_eq!(found, &meta); + assert!(bg.tool_metadata(&ToolId::new("nonexistent")).is_none()); + } + + #[test] + fn flow_override_check() { + let mut builder = BackgroundTheoryBuilder::new(); + builder.add_override( + AgentId::new("agent-1"), + ToolId::new("tool-1"), + ConfLevel::Sensitive, + ); + let bg = builder.build(); + + assert!(bg.has_flow_override( + &AgentId::new("agent-1"), + &ToolId::new("tool-1"), + ConfLevel::Sensitive, + )); + assert!(!bg.has_flow_override( + &AgentId::new("agent-1"), + &ToolId::new("tool-1"), + ConfLevel::Public, + )); + } +} diff --git a/argus/crates/argus-kernel/src/capability.rs b/argus/crates/argus-kernel/src/capability.rs new file mode 100644 index 0000000..e46f381 --- /dev/null +++ b/argus/crates/argus-kernel/src/capability.rs @@ -0,0 +1,137 @@ +use std::fmt; +use std::path::PathBuf; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CapKind { + FilesystemRead, + FilesystemWrite, + FilesystemDelete, + NetworkEgress, + NetworkIngress, + ExecutionShell, + ExecutionCode, + Credentials, + SystemInfo, + SystemModify, + Clipboard, + BrowserNavigate, + DatabaseRead, + DatabaseWrite, + Ipc, +} + +impl CapKind { + pub fn all() -> &'static [CapKind] { + &[ + Self::FilesystemRead, + Self::FilesystemWrite, + Self::FilesystemDelete, + Self::NetworkEgress, + Self::NetworkIngress, + Self::ExecutionShell, + Self::ExecutionCode, + Self::Credentials, + Self::SystemInfo, + Self::SystemModify, + Self::Clipboard, + Self::BrowserNavigate, + Self::DatabaseRead, + Self::DatabaseWrite, + Self::Ipc, + ] + } + + pub fn as_str(self) -> &'static str { + match self { + Self::FilesystemRead => "filesystem_read", + Self::FilesystemWrite => "filesystem_write", + Self::FilesystemDelete => "filesystem_delete", + Self::NetworkEgress => "network_egress", + Self::NetworkIngress => "network_ingress", + Self::ExecutionShell => "execution_shell", + Self::ExecutionCode => "execution_code", + Self::Credentials => "credentials", + Self::SystemInfo => "system_info", + Self::SystemModify => "system_modify", + Self::Clipboard => "clipboard", + Self::BrowserNavigate => "browser_navigate", + Self::DatabaseRead => "database_read", + Self::DatabaseWrite => "database_write", + Self::Ipc => "ipc", + } + } + + pub fn from_catalog_name(s: &str) -> Option { + match s { + "filesystem_read" => Some(Self::FilesystemRead), + "filesystem_write" => Some(Self::FilesystemWrite), + "filesystem_delete" => Some(Self::FilesystemDelete), + "network_egress" => Some(Self::NetworkEgress), + "network_ingress" => Some(Self::NetworkIngress), + "execution_shell" => Some(Self::ExecutionShell), + "execution_code" => Some(Self::ExecutionCode), + "credentials" => Some(Self::Credentials), + "system_info" => Some(Self::SystemInfo), + "system_modify" => Some(Self::SystemModify), + "clipboard" => Some(Self::Clipboard), + "browser_navigate" => Some(Self::BrowserNavigate), + "database_read" => Some(Self::DatabaseRead), + "database_write" => Some(Self::DatabaseWrite), + "ipc" => Some(Self::Ipc), + _ => None, + } + } +} + +impl fmt::Display for CapKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DomainPort { + pub host: String, + pub port: u16, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NetScope { + Blocked, + EgressOnly, + EgressWithPorts(Vec), + EgressWithDomains(Vec), + Unrestricted, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Scope { + Filesystem { paths: Vec }, + Network { policy: NetScope }, + Execution { allowed_bins: Vec }, + None, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cap_kind_all_variants_count() { + assert_eq!(CapKind::all().len(), 15); + } + + #[test] + fn cap_kind_display_roundtrip() { + for &kind in CapKind::all() { + let name = kind.as_str(); + let parsed = CapKind::from_catalog_name(name); + assert_eq!(parsed, Some(kind), "roundtrip failed for {name}"); + } + } + + #[test] + fn cap_kind_unknown_returns_none() { + assert_eq!(CapKind::from_catalog_name("nonexistent"), None); + } +} diff --git a/argus/crates/argus-kernel/src/error.rs b/argus/crates/argus-kernel/src/error.rs new file mode 100644 index 0000000..ac2e6f8 --- /dev/null +++ b/argus/crates/argus-kernel/src/error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, thiserror::Error)] +pub enum KernelError { + #[error("precondition violated: {0}")] + PreconditionViolation(String), + + #[error("event store error: {0}")] + EventStoreError(String), +} diff --git a/argus/crates/argus-kernel/src/event.rs b/argus/crates/argus-kernel/src/event.rs new file mode 100644 index 0000000..92d9525 --- /dev/null +++ b/argus/crates/argus-kernel/src/event.rs @@ -0,0 +1,123 @@ +use crate::capability::CapKind; +use crate::types::{AgentId, ConfLevel, InvocationId, ToolId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum KernelAction { + RegisterTool { + tool: ToolId, + }, + Delegate { + grantor: AgentId, + grantee: AgentId, + }, + GrantCapability { + parent: AgentId, + child: AgentId, + cap: CapKind, + }, + Revoke { + parent: AgentId, + target: AgentId, + }, + CascadeRevoke { + child: AgentId, + parent: AgentId, + }, + InvokeStart { + agent: AgentId, + tool: ToolId, + inv: InvocationId, + }, + InvokeComplete { + agent: AgentId, + inv: InvocationId, + }, + ReturnEndorsed { + child: AgentId, + parent: AgentId, + }, + ReturnUnendorsed { + child: AgentId, + parent: AgentId, + }, + SentinelElevateTaint { + agent: AgentId, + level: ConfLevel, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KernelEvent { + pub sequence: u64, + pub action: KernelAction, +} + +impl KernelEvent { + pub fn new(sequence: u64, action: KernelAction) -> Self { + Self { sequence, action } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kernel_action_variant_count() { + let actions = [ + KernelAction::RegisterTool { + tool: ToolId::new("t"), + }, + KernelAction::Delegate { + grantor: AgentId::root(), + grantee: AgentId::new("a"), + }, + KernelAction::GrantCapability { + parent: AgentId::root(), + child: AgentId::new("a"), + cap: CapKind::FilesystemRead, + }, + KernelAction::Revoke { + parent: AgentId::root(), + target: AgentId::new("a"), + }, + KernelAction::CascadeRevoke { + child: AgentId::new("a"), + parent: AgentId::root(), + }, + KernelAction::InvokeStart { + agent: AgentId::new("a"), + tool: ToolId::new("t"), + inv: InvocationId::new("i"), + }, + KernelAction::InvokeComplete { + agent: AgentId::new("a"), + inv: InvocationId::new("i"), + }, + KernelAction::ReturnEndorsed { + child: AgentId::new("a"), + parent: AgentId::root(), + }, + KernelAction::ReturnUnendorsed { + child: AgentId::new("a"), + parent: AgentId::root(), + }, + KernelAction::SentinelElevateTaint { + agent: AgentId::new("a"), + level: ConfLevel::Sensitive, + }, + ]; + assert_eq!(actions.len(), 10); + } + + #[test] + fn kernel_event_construction() { + let event = KernelEvent::new( + 42, + KernelAction::RegisterTool { + tool: ToolId::new("t"), + }, + ); + assert_eq!(event.sequence, 42); + } +} diff --git a/argus/crates/argus-kernel/src/kernel.rs b/argus/crates/argus-kernel/src/kernel.rs new file mode 100644 index 0000000..a0cd5cf --- /dev/null +++ b/argus/crates/argus-kernel/src/kernel.rs @@ -0,0 +1,299 @@ +use crate::background::BackgroundTheory; +use crate::capability::CapKind; +use crate::error::KernelError; +use crate::event::{KernelAction, KernelEvent}; +use crate::state::KernelState; +use crate::traits::{AuthorizerOracle, ContentGateOracle, EventStore}; +use crate::transitions; +use crate::types::{AgentId, ConfLevel, InvocationId, ToolId}; + +pub struct Kernel { + state: KernelState, + background: BackgroundTheory, + sequence: u64, + authorizer: A, + content_gate: C, + events: E, +} + +impl Kernel { + pub fn new(background: BackgroundTheory, authorizer: A, content_gate: C, events: E) -> Self { + Self { + state: KernelState::initial(), + background, + sequence: 0, + authorizer, + content_gate, + events, + } + } + + pub fn state(&self) -> &KernelState { + &self.state + } + + pub fn background(&self) -> &BackgroundTheory { + &self.background + } + + pub fn sequence(&self) -> u64 { + self.sequence + } + + fn apply( + &mut self, + result: Result<(KernelState, KernelAction), KernelError>, + ) -> Result { + let (new_state, action) = result?; + let next_seq = self.sequence + 1; + let event = KernelEvent::new(next_seq, action); + self.events.append(&event)?; + self.sequence = next_seq; + self.state = new_state; + Ok(event) + } + + pub fn register_tool(&mut self, tool: ToolId) -> Result { + let result = transitions::register_tool(self.state.clone(), &self.background, tool); + self.apply(result) + } + + pub fn delegate( + &mut self, + grantor: AgentId, + grantee: AgentId, + ) -> Result { + let result = transitions::delegate(self.state.clone(), &self.background, grantor, grantee); + self.apply(result) + } + + pub fn grant_capability( + &mut self, + parent: AgentId, + child: AgentId, + cap: CapKind, + ) -> Result { + let result = + transitions::grant_capability(self.state.clone(), &self.background, parent, child, cap); + self.apply(result) + } + + pub fn revoke(&mut self, parent: AgentId, target: AgentId) -> Result { + let result = transitions::revoke(self.state.clone(), &self.background, parent, target); + self.apply(result) + } + + pub fn cascade_revoke( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = + transitions::cascade_revoke(self.state.clone(), &self.background, child, parent); + self.apply(result) + } + + pub fn invoke_start( + &mut self, + agent: AgentId, + tool: ToolId, + inv: InvocationId, + ) -> Result { + let result = transitions::invoke_start( + self.state.clone(), + &self.background, + &self.authorizer, + &self.content_gate, + agent, + tool, + inv, + ); + self.apply(result) + } + + pub fn invoke_complete( + &mut self, + agent: AgentId, + inv: InvocationId, + ) -> Result { + let result = transitions::invoke_complete(self.state.clone(), &self.background, agent, inv); + self.apply(result) + } + + pub fn return_endorsed( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = + transitions::return_endorsed(self.state.clone(), &self.background, child, parent); + self.apply(result) + } + + pub fn return_unendorsed( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = transitions::return_unendorsed( + self.state.clone(), + &self.background, + &self.content_gate, + child, + parent, + ); + self.apply(result) + } + + pub fn sentinel_elevate_taint( + &mut self, + agent: AgentId, + level: ConfLevel, + ) -> Result { + self.apply(transitions::sentinel_elevate_taint( + self.state.clone(), + &self.background, + &self.content_gate, + agent, + level, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::background::{BackgroundTheory, BackgroundTheoryBuilder, FlowMode, ToolMetadata}; + use crate::types::{ConfLevel, EgressKind}; + use std::cell::RefCell; + use std::collections::BTreeSet; + + struct AllowAll; + impl AuthorizerOracle for AllowAll { + fn allows(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } + } + struct PassAll; + impl ContentGateOracle for PassAll { + fn passes(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } + } + struct VecStore(RefCell>); + impl VecStore { + fn new() -> Self { + Self(RefCell::new(Vec::new())) + } + } + impl EventStore for VecStore { + fn append(&self, event: &KernelEvent) -> Result<(), KernelError> { + self.0.borrow_mut().push(event.clone()); + Ok(()) + } + } + + #[test] + fn full_lifecycle() { + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + builder.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + let bg = builder.build(); + + let store = VecStore::new(); + let mut kernel = Kernel::new(bg, AllowAll, PassAll, store); + + let e1 = kernel.register_tool(ToolId::new("read_file")).unwrap(); + assert_eq!(e1.sequence, 1); + + let e2 = kernel + .delegate(AgentId::root(), AgentId::new("a1")) + .unwrap(); + assert_eq!(e2.sequence, 2); + + let e3 = kernel + .grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + assert_eq!(e3.sequence, 3); + + let e4 = kernel + .invoke_start( + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .unwrap(); + assert_eq!(e4.sequence, 4); + + let e5 = kernel + .invoke_complete(AgentId::new("a1"), InvocationId::new("inv-1")) + .unwrap(); + assert_eq!(e5.sequence, 5); + assert!( + kernel + .state() + .taint_levels + .get(&AgentId::new("a1")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + + let e6 = kernel + .return_endorsed(AgentId::new("a1"), AgentId::root()) + .unwrap(); + assert_eq!(e6.sequence, 6); + + let e7 = kernel.revoke(AgentId::root(), AgentId::new("a1")).unwrap(); + assert_eq!(e7.sequence, 7); + assert!(!kernel.state().agent_active.contains(&AgentId::new("a1"))); + } + + #[test] + fn event_store_receives_all_events() { + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("t"), + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Public, + endorsed: true, + }, + ); + let bg = builder.build(); + let store = VecStore::new(); + let mut kernel = Kernel::new(bg, AllowAll, PassAll, store); + + kernel.register_tool(ToolId::new("t")).unwrap(); + kernel + .delegate(AgentId::root(), AgentId::new("a1")) + .unwrap(); + + assert_eq!(kernel.events.0.borrow().len(), 2); + assert_eq!(kernel.events.0.borrow()[0].sequence, 1); + assert_eq!(kernel.events.0.borrow()[1].sequence, 2); + } + + #[test] + fn sequence_not_incremented_on_error() { + let bg = BackgroundTheoryBuilder::new().build(); + let store = VecStore::new(); + let mut kernel = Kernel::new(bg, AllowAll, PassAll, store); + + let result = kernel.register_tool(ToolId::new("unknown")); + assert!(result.is_err()); + assert_eq!(kernel.sequence(), 0); + } +} diff --git a/argus/crates/argus-kernel/src/lib.rs b/argus/crates/argus-kernel/src/lib.rs new file mode 100644 index 0000000..720fe30 --- /dev/null +++ b/argus/crates/argus-kernel/src/lib.rs @@ -0,0 +1,18 @@ +mod background; +mod capability; +mod error; +mod event; +mod kernel; +mod state; +mod traits; +pub(crate) mod transitions; +mod types; + +pub use background::{BackgroundTheory, BackgroundTheoryBuilder, FlowMode, ToolMetadata}; +pub use capability::{CapKind, DomainPort, NetScope, Scope}; +pub use error::KernelError; +pub use event::{KernelAction, KernelEvent}; +pub use kernel::Kernel; +pub use state::KernelState; +pub use traits::{AuthorizerOracle, ContentGateOracle, EventStore}; +pub use types::{AgentId, ConfLevel, EgressKind, InvocationId, ToolId}; diff --git a/argus/crates/argus-kernel/src/state.rs b/argus/crates/argus-kernel/src/state.rs new file mode 100644 index 0000000..6966698 --- /dev/null +++ b/argus/crates/argus-kernel/src/state.rs @@ -0,0 +1,176 @@ +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use crate::background::BackgroundTheory; +use crate::capability::CapKind; +use crate::types::{AgentId, ConfLevel, InvocationId, ToolId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KernelState { + pub agent_active: BTreeSet, + pub agent_parent: BTreeMap, + pub agent_cap: BTreeMap>, + pub taint_levels: BTreeMap>, + pub in_flight: BTreeMap>, + pub invocation_tool: BTreeMap, + pub tool_registered: BTreeSet, + pub gh_taint_invoked: BTreeMap>, + pub gh_taint_received: BTreeMap>, +} + +impl KernelState { + pub fn initial() -> Self { + let root = AgentId::root(); + let all_caps: BTreeSet = CapKind::all().iter().copied().collect(); + + let mut agent_active = BTreeSet::new(); + agent_active.insert(root.clone()); + + let mut agent_cap = BTreeMap::new(); + agent_cap.insert(root, all_caps); + + Self { + agent_active, + agent_parent: BTreeMap::new(), + agent_cap, + taint_levels: BTreeMap::new(), + in_flight: BTreeMap::new(), + invocation_tool: BTreeMap::new(), + tool_registered: BTreeSet::new(), + gh_taint_invoked: BTreeMap::new(), + gh_taint_received: BTreeMap::new(), + } + } + + pub fn speculative_taint(&self, agent: &AgentId, bg: &BackgroundTheory) -> BTreeSet { + let mut taint: BTreeSet = + self.taint_levels.get(agent).cloned().unwrap_or_default(); + + if let Some(flights) = self.in_flight.get(agent) { + for inv in flights { + debug_assert!( + self.invocation_tool.contains_key(inv), + "in_flight contains InvocationId {inv} with no invocation_tool binding" + ); + if let Some(tool_id) = self.invocation_tool.get(inv) + && let Some(meta) = bg.tool_metadata(tool_id) + && !meta.endorsed + { + taint.insert(meta.conf_floor); + } + } + } + + taint + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::background::{BackgroundTheoryBuilder, ToolMetadata}; + use crate::types::EgressKind; + + #[test] + fn initial_state_has_root_active() { + let state = KernelState::initial(); + assert!(state.agent_active.contains(&AgentId::root())); + } + + #[test] + fn initial_state_root_has_all_caps() { + let state = KernelState::initial(); + let root_caps = state.agent_cap.get(&AgentId::root()).unwrap(); + for &kind in CapKind::all() { + assert!(root_caps.contains(&kind), "root missing cap: {kind}"); + } + } + + #[test] + fn initial_state_no_tools_registered() { + let state = KernelState::initial(); + assert!(state.tool_registered.is_empty()); + } + + #[test] + fn initial_state_no_taint() { + let state = KernelState::initial(); + assert!(state.taint_levels.is_empty()); + } + + #[test] + fn initial_state_no_in_flight() { + let state = KernelState::initial(); + assert!(state.in_flight.is_empty()); + } + + #[test] + fn speculative_taint_empty_for_clean_agent() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let taint = state.speculative_taint(&AgentId::new("agent-1"), &bg); + assert!(taint.is_empty()); + } + + #[test] + fn speculative_taint_includes_in_flight_non_endorsed() { + let mut state = KernelState::initial(); + let agent = AgentId::new("agent-1"); + let tool = ToolId::new("risky_tool"); + let inv = InvocationId::new("inv-1"); + + state.agent_active.insert(agent.clone()); + state.invocation_tool.insert(inv.clone(), tool.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + tool, + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + let bg = builder.build(); + + let taint = state.speculative_taint(&agent, &bg); + assert!(taint.contains(&ConfLevel::Sensitive)); + } + + #[test] + fn speculative_taint_skips_endorsed_tools() { + let mut state = KernelState::initial(); + let agent = AgentId::new("agent-1"); + let tool = ToolId::new("safe_tool"); + let inv = InvocationId::new("inv-1"); + + state.agent_active.insert(agent.clone()); + state.invocation_tool.insert(inv.clone(), tool.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + tool, + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: true, + }, + ); + let bg = builder.build(); + + let taint = state.speculative_taint(&agent, &bg); + assert!(taint.is_empty()); + } +} diff --git a/argus/crates/argus-kernel/src/traits.rs b/argus/crates/argus-kernel/src/traits.rs new file mode 100644 index 0000000..6022515 --- /dev/null +++ b/argus/crates/argus-kernel/src/traits.rs @@ -0,0 +1,29 @@ +use crate::background::BackgroundTheory; +use crate::error::KernelError; +use crate::event::KernelEvent; +use crate::state::KernelState; +use crate::types::{AgentId, ToolId}; + +pub trait AuthorizerOracle { + fn allows( + &self, + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + bg: &BackgroundTheory, + ) -> bool; +} + +pub trait ContentGateOracle { + fn passes( + &self, + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + bg: &BackgroundTheory, + ) -> bool; +} + +pub trait EventStore { + fn append(&self, event: &KernelEvent) -> Result<(), KernelError>; +} diff --git a/argus/crates/argus-kernel/src/transitions.rs b/argus/crates/argus-kernel/src/transitions.rs new file mode 100644 index 0000000..9234fc0 --- /dev/null +++ b/argus/crates/argus-kernel/src/transitions.rs @@ -0,0 +1,1331 @@ +use std::collections::BTreeSet; + +use crate::background::{BackgroundTheory, FlowMode}; +use crate::capability::CapKind; +use crate::error::KernelError; +use crate::event::KernelAction; +use crate::state::KernelState; +use crate::traits::{AuthorizerOracle, ContentGateOracle}; +use crate::types::{AgentId, ConfLevel, EgressKind, InvocationId, ToolId}; + +fn flow_allowed( + bg: &BackgroundTheory, + content_gate: &impl ContentGateOracle, + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + level: ConfLevel, + egress: EgressKind, +) -> bool { + match bg.flow_mode(level, egress) { + FlowMode::Allow => true, + FlowMode::Inspect => content_gate.passes(agent, tool, state, bg), + FlowMode::Deny => bg.has_flow_override(agent, tool, level), + } +} + +fn clear_agent_state(state: &mut KernelState, agent: &AgentId) { + state.taint_levels.remove(agent); + state.in_flight.remove(agent); + state.gh_taint_invoked.remove(agent); + state.gh_taint_received.remove(agent); +} + +pub fn register_tool( + mut state: KernelState, + bg: &BackgroundTheory, + tool: ToolId, +) -> Result<(KernelState, KernelAction), KernelError> { + if !bg.has_tool(&tool) { + return Err(KernelError::PreconditionViolation(format!( + "tool {tool} not in background theory" + ))); + } + if state.tool_registered.contains(&tool) { + return Err(KernelError::PreconditionViolation(format!( + "tool {tool} already registered" + ))); + } + + state.tool_registered.insert(tool.clone()); + + Ok((state, KernelAction::RegisterTool { tool })) +} + +pub fn delegate( + mut state: KernelState, + _bg: &BackgroundTheory, + grantor: AgentId, + grantee: AgentId, +) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&grantor) { + return Err(KernelError::PreconditionViolation(format!( + "grantor {grantor} is not active" + ))); + } + if state.agent_active.contains(&grantee) { + return Err(KernelError::PreconditionViolation(format!( + "grantee {grantee} is already active" + ))); + } + if grantee == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot delegate to root".to_owned(), + )); + } + + state.agent_active.insert(grantee.clone()); + state + .agent_parent + .retain(|child, parent| child != &grantee && parent != &grantee); + state.agent_parent.insert(grantee.clone(), grantor.clone()); + state.agent_cap.insert(grantee.clone(), BTreeSet::new()); + clear_agent_state(&mut state, &grantee); + + Ok((state, KernelAction::Delegate { grantor, grantee })) +} + +pub fn grant_capability( + mut state: KernelState, + _bg: &BackgroundTheory, + parent: AgentId, + child: AgentId, + cap: CapKind, +) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} is not active" + ))); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation(format!( + "child {child} is not active" + ))); + } + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "{child} is not a direct child of {parent}" + ))); + } + if !state + .agent_cap + .get(&parent) + .is_some_and(|caps| caps.contains(&cap)) + { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} does not hold capability {cap}" + ))); + } + + state + .agent_cap + .entry(child.clone()) + .or_default() + .insert(cap); + + Ok((state, KernelAction::GrantCapability { parent, child, cap })) +} + +pub fn revoke( + mut state: KernelState, + _bg: &BackgroundTheory, + parent: AgentId, + target: AgentId, +) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&target) != Some(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "{target} is not a direct child of {parent}" + ))); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} is not active" + ))); + } + if !state.agent_active.contains(&target) { + return Err(KernelError::PreconditionViolation(format!( + "target {target} is not active" + ))); + } + if target == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot revoke root".to_owned(), + )); + } + + state.agent_active.remove(&target); + state.agent_parent.retain(|child, _| child != &target); + state.agent_cap.remove(&target); + clear_agent_state(&mut state, &target); + + Ok((state, KernelAction::Revoke { parent, target })) +} + +pub fn cascade_revoke( + mut state: KernelState, + _bg: &BackgroundTheory, + child: AgentId, + parent: AgentId, +) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "{child} is not a direct child of {parent}" + ))); + } + if state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} is still active (use revoke, not cascade_revoke)" + ))); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation(format!( + "child {child} is not active" + ))); + } + if child == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot cascade_revoke root".to_owned(), + )); + } + + state.agent_active.remove(&child); + state.agent_parent.retain(|c, _| c != &child); + state.agent_cap.remove(&child); + clear_agent_state(&mut state, &child); + + Ok((state, KernelAction::CascadeRevoke { child, parent })) +} + +pub fn invoke_start( + mut state: KernelState, + bg: &BackgroundTheory, + authorizer: &A, + content_gate: &C, + agent: AgentId, + tool: ToolId, + inv: InvocationId, +) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&agent) { + return Err(KernelError::PreconditionViolation(format!( + "agent {agent} is not active" + ))); + } + if agent == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "root agent cannot invoke tools directly".to_owned(), + )); + } + if !state.tool_registered.contains(&tool) { + return Err(KernelError::PreconditionViolation(format!( + "tool {tool} is not registered" + ))); + } + if state.invocation_tool.contains_key(&inv) { + return Err(KernelError::PreconditionViolation(format!( + "invocation {inv} already exists" + ))); + } + for flights in state.in_flight.values() { + if flights.contains(&inv) { + return Err(KernelError::PreconditionViolation(format!( + "invocation {inv} is already in-flight" + ))); + } + } + + let tool_meta = bg.tool_metadata(&tool).ok_or_else(|| { + KernelError::PreconditionViolation(format!("tool {tool} not in background theory")) + })?; + + let agent_caps = state.agent_cap.get(&agent); + for required_cap in &tool_meta.capabilities { + if !agent_caps.is_some_and(|caps| caps.contains(required_cap)) { + return Err(KernelError::PreconditionViolation(format!( + "agent {agent} lacks required capability {required_cap}" + ))); + } + } + + let spec_taint = state.speculative_taint(&agent, bg); + for &level in &spec_taint { + for &egress in &tool_meta.egress { + if !flow_allowed(bg, content_gate, &agent, &tool, &state, level, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2a: speculative taint {level} blocked for egress {egress} on tool {tool}" + ))); + } + } + } + + if !tool_meta.endorsed { + if let Some(agent_flights) = state.in_flight.get(&agent) { + for flight_inv in agent_flights { + if let Some(flight_tool_id) = state.invocation_tool.get(flight_inv) + && let Some(flight_meta) = bg.tool_metadata(flight_tool_id) + { + for &egress in &flight_meta.egress { + if !flow_allowed( + bg, + content_gate, + &agent, + flight_tool_id, + &state, + tool_meta.conf_floor, + egress, + ) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2b: new tool {tool} taint {} conflicts with in-flight {flight_tool_id} egress {egress}", + tool_meta.conf_floor + ))); + } + } + } + } + } + + for &egress in &tool_meta.egress { + if !flow_allowed( + bg, + content_gate, + &agent, + &tool, + &state, + tool_meta.conf_floor, + egress, + ) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2c: tool {tool} self-flow blocked ({}, {egress})", + tool_meta.conf_floor + ))); + } + } + } + + if !authorizer.allows(&agent, &tool, &state, bg) { + return Err(KernelError::PreconditionViolation(format!( + "authorizer denied ({agent}, {tool})" + ))); + } + + state.invocation_tool.insert(inv.clone(), tool.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv.clone()); + + Ok((state, KernelAction::InvokeStart { agent, tool, inv })) +} + +pub fn invoke_complete( + mut state: KernelState, + bg: &BackgroundTheory, + agent: AgentId, + inv: InvocationId, +) -> Result<(KernelState, KernelAction), KernelError> { + if !state + .in_flight + .get(&agent) + .is_some_and(|flights| flights.contains(&inv)) + { + return Err(KernelError::PreconditionViolation(format!( + "invocation {inv} is not in-flight for agent {agent}" + ))); + } + if !state.agent_active.contains(&agent) { + return Err(KernelError::PreconditionViolation(format!( + "agent {agent} is not active" + ))); + } + + if let Some(flights) = state.in_flight.get_mut(&agent) { + flights.remove(&inv); + } + + if let Some(tool_id) = state.invocation_tool.get(&inv) + && let Some(meta) = bg.tool_metadata(tool_id) + && !meta.endorsed + { + state + .taint_levels + .entry(agent.clone()) + .or_default() + .insert(meta.conf_floor); + state + .gh_taint_invoked + .entry(agent.clone()) + .or_default() + .insert(meta.conf_floor); + } + + Ok((state, KernelAction::InvokeComplete { agent, inv })) +} + +pub fn return_endorsed( + state: KernelState, + _bg: &BackgroundTheory, + child: AgentId, + parent: AgentId, +) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "{child} is not a direct child of {parent}" + ))); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation(format!( + "child {child} is not active" + ))); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} is not active" + ))); + } + if state + .in_flight + .get(&child) + .is_some_and(|flights| !flights.is_empty()) + { + return Err(KernelError::PreconditionViolation(format!( + "child {child} has in-flight invocations" + ))); + } + + Ok((state, KernelAction::ReturnEndorsed { child, parent })) +} + +pub fn return_unendorsed( + mut state: KernelState, + bg: &BackgroundTheory, + content_gate: &C, + child: AgentId, + parent: AgentId, +) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "{child} is not a direct child of {parent}" + ))); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation(format!( + "child {child} is not active" + ))); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation(format!( + "parent {parent} is not active" + ))); + } + if state + .in_flight + .get(&child) + .is_some_and(|flights| !flights.is_empty()) + { + return Err(KernelError::PreconditionViolation(format!( + "child {child} has in-flight invocations" + ))); + } + + let child_taint = state.taint_levels.get(&child).cloned().unwrap_or_default(); + let empty_flights = BTreeSet::new(); + let parent_flights = state.in_flight.get(&parent).unwrap_or(&empty_flights); + + for &level in &child_taint { + for inv in parent_flights { + if let Some(tool_id) = state.invocation_tool.get(inv) + && let Some(meta) = bg.tool_metadata(tool_id) + { + for &egress in &meta.egress { + if !flow_allowed(bg, content_gate, &parent, tool_id, &state, level, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate: child taint {level} conflicts with parent in-flight tool {tool_id} egress {egress}" + ))); + } + } + } + } + } + + if !child_taint.is_empty() { + state + .taint_levels + .entry(parent.clone()) + .or_default() + .extend(&child_taint); + state + .gh_taint_received + .entry(parent.clone()) + .or_default() + .extend(&child_taint); + } + + Ok((state, KernelAction::ReturnUnendorsed { child, parent })) +} + +pub fn sentinel_elevate_taint( + mut state: KernelState, + bg: &BackgroundTheory, + content_gate: &C, + agent: AgentId, + level: ConfLevel, +) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&agent) { + return Err(KernelError::PreconditionViolation(format!( + "agent not active: {agent}" + ))); + } + + if let Some(in_flight_invs) = state.in_flight.get(&agent) { + for inv in in_flight_invs { + let tool = state.invocation_tool.get(inv).ok_or_else(|| { + KernelError::PreconditionViolation(format!("no tool binding for invocation {inv}")) + })?; + debug_assert!( + bg.tool_metadata(tool).is_some(), + "in-flight tool {tool} missing from background theory" + ); + if let Some(meta) = bg.tool_metadata(tool) { + for &egress in &meta.egress { + if !flow_allowed(bg, content_gate, &agent, tool, &state, level, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow incompatible: taint {level} with {tool} egress {egress}" + ))); + } + } + } + } + } + + state + .taint_levels + .entry(agent.clone()) + .or_default() + .insert(level); + state + .gh_taint_invoked + .entry(agent.clone()) + .or_default() + .insert(level); + + Ok((state, KernelAction::SentinelElevateTaint { agent, level })) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::background::{BackgroundTheoryBuilder, ToolMetadata}; + use crate::types::EgressKind; + + struct AllowAll; + impl AuthorizerOracle for AllowAll { + fn allows(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } + } + struct PassAll; + impl ContentGateOracle for PassAll { + fn passes(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } + } + struct FailAll; + impl ContentGateOracle for FailAll { + fn passes(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + false + } + } + + fn tool_meta_simple() -> ToolMetadata { + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + } + } + + fn bg_with_tool(id: &str) -> BackgroundTheory { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool(ToolId::new(id), tool_meta_simple()); + b.build() + } + + fn bg_with_tools() -> BackgroundTheory { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("check_exists"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: true, + }, + ); + b.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + b.build() + } + + fn state_with_agent(id: &str, caps: &[CapKind]) -> KernelState { + let mut state = KernelState::initial(); + let agent = AgentId::new(id); + state.agent_active.insert(agent.clone()); + state.agent_parent.insert(agent.clone(), AgentId::root()); + state + .agent_cap + .insert(agent, caps.iter().copied().collect()); + state.tool_registered.insert(ToolId::new("read_file")); + state.tool_registered.insert(ToolId::new("send_email")); + state.tool_registered.insert(ToolId::new("check_exists")); + state + } + + // --- register_tool --- + + #[test] + fn register_tool_success() { + let state = KernelState::initial(); + let bg = bg_with_tool("read_file"); + let tool = ToolId::new("read_file"); + + let (new_state, action) = register_tool(state, &bg, tool.clone()).unwrap(); + assert!(new_state.tool_registered.contains(&tool)); + assert_eq!(action, KernelAction::RegisterTool { tool }); + } + + #[test] + fn register_tool_rejects_already_registered() { + let mut state = KernelState::initial(); + let tool = ToolId::new("read_file"); + state.tool_registered.insert(tool.clone()); + let bg = bg_with_tool("read_file"); + assert!(register_tool(state, &bg, tool).is_err()); + } + + #[test] + fn register_tool_rejects_unknown_tool() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + assert!(register_tool(state, &bg, ToolId::new("unknown")).is_err()); + } + + // --- delegate --- + + #[test] + fn delegate_success() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let grantor = AgentId::root(); + let grantee = AgentId::new("child-1"); + + let (new_state, action) = delegate(state, &bg, grantor.clone(), grantee.clone()).unwrap(); + assert!(new_state.agent_active.contains(&grantee)); + assert_eq!(new_state.agent_parent.get(&grantee), Some(&grantor)); + assert!( + new_state + .agent_cap + .get(&grantee) + .map_or(true, |s| s.is_empty()) + ); + assert!(new_state.taint_levels.get(&grantee).is_none()); + assert_eq!(action, KernelAction::Delegate { grantor, grantee }); + } + + #[test] + fn delegate_rejects_inactive_grantor() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + assert!(delegate(state, &bg, AgentId::new("ghost"), AgentId::new("child")).is_err()); + } + + #[test] + fn delegate_rejects_already_active_grantee() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + assert!(delegate(state, &bg, AgentId::root(), AgentId::root()).is_err()); + } + + #[test] + fn delegate_clears_stale_grantee_parent_entries() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let grantee = AgentId::new("child-1"); + state + .agent_parent + .insert(AgentId::new("phantom"), grantee.clone()); + + let (new_state, _) = delegate(state, &bg, AgentId::root(), grantee.clone()).unwrap(); + assert_eq!(new_state.agent_parent.get(&grantee), Some(&AgentId::root())); + assert!( + !new_state + .agent_parent + .contains_key(&AgentId::new("phantom")) + ); + } + + // --- grant_capability --- + + #[test] + fn grant_capability_success() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state.agent_cap.insert(child.clone(), BTreeSet::new()); + + let (new_state, action) = grant_capability( + state, + &bg, + AgentId::root(), + child.clone(), + CapKind::FilesystemRead, + ) + .unwrap(); + assert!( + new_state + .agent_cap + .get(&child) + .unwrap() + .contains(&CapKind::FilesystemRead) + ); + assert_eq!( + action, + KernelAction::GrantCapability { + parent: AgentId::root(), + child, + cap: CapKind::FilesystemRead, + } + ); + } + + #[test] + fn grant_capability_rejects_cap_parent_lacks() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let parent = AgentId::new("parent"); + let child = AgentId::new("child"); + state.agent_active.insert(parent.clone()); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), parent.clone()); + state.agent_cap.insert(parent, BTreeSet::new()); + state.agent_cap.insert(child.clone(), BTreeSet::new()); + assert!( + grant_capability( + state, + &bg, + AgentId::new("parent"), + child, + CapKind::FilesystemRead + ) + .is_err() + ); + } + + #[test] + fn grant_capability_rejects_non_child() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let other = AgentId::new("other"); + state.agent_active.insert(other.clone()); + assert!( + grant_capability(state, &bg, AgentId::root(), other, CapKind::FilesystemRead).is_err() + ); + } + + // --- revoke --- + + #[test] + fn revoke_success() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state + .agent_cap + .insert(child.clone(), BTreeSet::from([CapKind::FilesystemRead])); + state + .taint_levels + .insert(child.clone(), BTreeSet::from([ConfLevel::Internal])); + + let (new_state, action) = revoke(state, &bg, AgentId::root(), child.clone()).unwrap(); + assert!(!new_state.agent_active.contains(&child)); + assert!(!new_state.agent_parent.contains_key(&child)); + assert!(new_state.taint_levels.get(&child).is_none()); + assert_eq!( + action, + KernelAction::Revoke { + parent: AgentId::root(), + target: child, + } + ); + } + + #[test] + fn revoke_rejects_non_child() { + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + assert!(revoke(state, &bg, AgentId::root(), AgentId::new("stranger")).is_err()); + } + + #[test] + fn revoke_preserves_grandchild_parent_link() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child"); + let grandchild = AgentId::new("grandchild"); + state.agent_active.insert(child.clone()); + state.agent_active.insert(grandchild.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state.agent_parent.insert(grandchild.clone(), child.clone()); + + let (new_state, _) = revoke(state, &bg, AgentId::root(), child.clone()).unwrap(); + assert_eq!(new_state.agent_parent.get(&grandchild), Some(&child)); + } + + // --- cascade_revoke --- + + #[test] + fn cascade_revoke_success() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let parent = AgentId::new("parent"); + let child = AgentId::new("child"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), parent.clone()); + + let (new_state, action) = + cascade_revoke(state, &bg, child.clone(), parent.clone()).unwrap(); + assert!(!new_state.agent_active.contains(&child)); + assert!(!new_state.agent_parent.contains_key(&child)); + assert_eq!(action, KernelAction::CascadeRevoke { child, parent }); + } + + #[test] + fn cascade_revoke_rejects_active_parent() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + assert!(cascade_revoke(state, &bg, child, AgentId::root()).is_err()); + } + + // --- invoke_complete --- + + #[test] + fn invoke_complete_adds_taint_for_non_endorsed() { + let mut state = KernelState::initial(); + let agent = AgentId::new("agent-1"); + let tool = ToolId::new("risky_tool"); + let inv = InvocationId::new("inv-1"); + + state.agent_active.insert(agent.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv.clone()); + state.invocation_tool.insert(inv.clone(), tool.clone()); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + tool, + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + let bg = builder.build(); + + let (new_state, action) = invoke_complete(state, &bg, agent.clone(), inv.clone()).unwrap(); + assert!( + !new_state + .in_flight + .get(&agent) + .map_or(false, |s| s.contains(&inv)) + ); + assert!( + new_state + .taint_levels + .get(&agent) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + assert!( + new_state + .gh_taint_invoked + .get(&agent) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + assert_eq!(action, KernelAction::InvokeComplete { agent, inv }); + } + + #[test] + fn invoke_complete_no_taint_for_endorsed() { + let mut state = KernelState::initial(); + let agent = AgentId::new("agent-1"); + let tool = ToolId::new("safe_tool"); + let inv = InvocationId::new("inv-1"); + + state.agent_active.insert(agent.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv.clone()); + state.invocation_tool.insert(inv.clone(), tool.clone()); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + tool, + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: true, + }, + ); + let bg = builder.build(); + + let (new_state, _) = invoke_complete(state, &bg, agent.clone(), inv).unwrap(); + assert!(new_state.taint_levels.get(&agent).is_none()); + } + + #[test] + fn invoke_complete_rejects_not_in_flight() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let agent = AgentId::new("agent-1"); + state.agent_active.insert(agent.clone()); + assert!(invoke_complete(state, &bg, agent, InvocationId::new("inv-1")).is_err()); + } + + // --- return_endorsed --- + + #[test] + fn return_endorsed_success() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + + let (new_state, action) = + return_endorsed(state.clone(), &bg, child.clone(), AgentId::root()).unwrap(); + assert_eq!(new_state.taint_levels, state.taint_levels); + assert_eq!( + action, + KernelAction::ReturnEndorsed { + child, + parent: AgentId::root(), + } + ); + } + + #[test] + fn return_endorsed_rejects_child_with_in_flight() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state + .in_flight + .entry(child.clone()) + .or_default() + .insert(InvocationId::new("inv-1")); + state + .invocation_tool + .insert(InvocationId::new("inv-1"), ToolId::new("t")); + assert!(return_endorsed(state, &bg, child, AgentId::root()).is_err()); + } + + #[test] + fn return_endorsed_rejects_non_child() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let stranger = AgentId::new("stranger"); + state.agent_active.insert(stranger.clone()); + assert!(return_endorsed(state, &bg, stranger, AgentId::root()).is_err()); + } + + // --- return_unendorsed --- + + #[test] + fn return_unendorsed_merges_taint() { + let mut state = KernelState::initial(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state + .taint_levels + .insert(child.clone(), BTreeSet::from([ConfLevel::Sensitive])); + + let bg = BackgroundTheoryBuilder::new().build(); + + let (new_state, action) = + return_unendorsed(state, &bg, &PassAll, child.clone(), AgentId::root()).unwrap(); + assert!( + new_state + .taint_levels + .get(&AgentId::root()) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + assert!( + new_state + .gh_taint_received + .get(&AgentId::root()) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + assert_eq!( + action, + KernelAction::ReturnUnendorsed { + child, + parent: AgentId::root(), + } + ); + } + + #[test] + fn return_unendorsed_blocked_by_flow_gate() { + let mut state = KernelState::initial(); + let child = AgentId::new("child-1"); + let parent_inv = InvocationId::new("parent-inv"); + let parent_tool = ToolId::new("egress_tool"); + + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state + .taint_levels + .insert(child.clone(), BTreeSet::from([ConfLevel::Sensitive])); + state + .in_flight + .entry(AgentId::root()) + .or_default() + .insert(parent_inv.clone()); + state + .invocation_tool + .insert(parent_inv, parent_tool.clone()); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + parent_tool, + ToolMetadata { + capabilities: BTreeSet::new(), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + let bg = builder.build(); + + assert!(return_unendorsed(state, &bg, &FailAll, child, AgentId::root()).is_err()); + } + + #[test] + fn return_unendorsed_rejects_child_with_in_flight() { + let mut state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + let child = AgentId::new("child-1"); + state.agent_active.insert(child.clone()); + state.agent_parent.insert(child.clone(), AgentId::root()); + state + .in_flight + .entry(child.clone()) + .or_default() + .insert(InvocationId::new("i")); + state + .invocation_tool + .insert(InvocationId::new("i"), ToolId::new("t")); + assert!(return_unendorsed(state, &bg, &PassAll, child, AgentId::root()).is_err()); + } + + // --- invoke_start --- + + #[test] + fn invoke_start_success_no_egress_tool() { + let state = state_with_agent("a1", &[CapKind::FilesystemRead]); + let bg = bg_with_tools(); + + let (new_state, action) = invoke_start( + state, + &bg, + &AllowAll, + &PassAll, + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .unwrap(); + + assert!( + new_state + .in_flight + .get(&AgentId::new("a1")) + .unwrap() + .contains(&InvocationId::new("inv-1")) + ); + assert_eq!( + *new_state + .invocation_tool + .get(&InvocationId::new("inv-1")) + .unwrap(), + ToolId::new("read_file"), + ); + assert_eq!( + action, + KernelAction::InvokeStart { + agent: AgentId::new("a1"), + tool: ToolId::new("read_file"), + inv: InvocationId::new("inv-1"), + } + ); + } + + #[test] + fn invoke_start_rejects_missing_capability() { + let state = state_with_agent("a1", &[]); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &PassAll, + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_rejects_root() { + let state = KernelState::initial(); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &PassAll, + AgentId::root(), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_rejects_unregistered_tool() { + let state = state_with_agent("a1", &[CapKind::FilesystemRead]); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &PassAll, + AgentId::new("a1"), + ToolId::new("unregistered"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_rejects_duplicate_invocation_id() { + let mut state = state_with_agent("a1", &[CapKind::FilesystemRead]); + state + .invocation_tool + .insert(InvocationId::new("inv-1"), ToolId::new("read_file")); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &PassAll, + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_rejects_authorizer_deny() { + struct DenyAll; + impl AuthorizerOracle for DenyAll { + fn allows( + &self, + _: &AgentId, + _: &ToolId, + _: &KernelState, + _: &BackgroundTheory, + ) -> bool { + false + } + } + let state = state_with_agent("a1", &[CapKind::FilesystemRead]); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &DenyAll, + &PassAll, + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_flow_gate_blocks_tainted_agent_egress() { + let mut state = state_with_agent("a1", &[CapKind::NetworkEgress]); + state + .taint_levels + .insert(AgentId::new("a1"), BTreeSet::from([ConfLevel::Sensitive])); + let bg = bg_with_tools(); + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &FailAll, + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("inv-1"), + ) + .is_err() + ); + } + + #[test] + fn invoke_start_flow_gate_allows_with_override() { + let mut state = state_with_agent("a1", &[CapKind::NetworkEgress]); + state + .taint_levels + .insert(AgentId::new("a1"), BTreeSet::from([ConfLevel::Sensitive])); + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + // Public egress is ALLOW (self-flow check 2c passes) + builder.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + // Override for Sensitive level (check 2a passes via override) + builder.add_override( + AgentId::new("a1"), + ToolId::new("send_email"), + ConfLevel::Sensitive, + ); + let bg = builder.build(); + + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &FailAll, + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("inv-1"), + ) + .is_ok() + ); + } + + #[test] + fn invoke_start_check_2b_new_tool_taint_vs_existing_inflight_egress() { + let mut state = state_with_agent("a1", &[CapKind::FilesystemRead, CapKind::NetworkEgress]); + let email_inv = InvocationId::new("email-inv"); + state + .in_flight + .entry(AgentId::new("a1")) + .or_default() + .insert(email_inv.clone()); + state + .invocation_tool + .insert(email_inv, ToolId::new("send_email")); + + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + builder.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + let bg = builder.build(); + + assert!( + invoke_start( + state, + &bg, + &AllowAll, + &FailAll, + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("inv-2"), + ) + .is_err() + ); + } +} diff --git a/argus/crates/argus-kernel/src/types.rs b/argus/crates/argus-kernel/src/types.rs new file mode 100644 index 0000000..e594195 --- /dev/null +++ b/argus/crates/argus-kernel/src/types.rs @@ -0,0 +1,158 @@ +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AgentId(pub String); + +impl AgentId { + pub fn root() -> Self { + Self("root".to_owned()) + } + + pub fn new(name: &str) -> Self { + Self(name.to_owned()) + } +} + +impl fmt::Display for AgentId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ToolId(pub String); + +impl ToolId { + pub fn new(name: &str) -> Self { + Self(name.to_owned()) + } +} + +impl fmt::Display for ToolId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InvocationId(pub String); + +impl InvocationId { + pub fn new(id: &str) -> Self { + Self(id.to_owned()) + } +} + +impl fmt::Display for InvocationId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ConfLevel { + Public, + Internal, + Sensitive, + Restricted, +} + +impl ConfLevel { + fn rank(self) -> u8 { + match self { + Self::Public => 0, + Self::Internal => 1, + Self::Sensitive => 2, + Self::Restricted => 3, + } + } +} + +impl Ord for ConfLevel { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.rank().cmp(&other.rank()) + } +} + +impl PartialOrd for ConfLevel { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Display for ConfLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Public => f.write_str("public"), + Self::Internal => f.write_str("internal"), + Self::Sensitive => f.write_str("sensitive"), + Self::Restricted => f.write_str("restricted"), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EgressKind { + NetworkExternal, + NetworkInternal, + FilesystemWrite, + Ipc, +} + +impl fmt::Display for EgressKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NetworkExternal => f.write_str("network_external"), + Self::NetworkInternal => f.write_str("network_internal"), + Self::FilesystemWrite => f.write_str("filesystem_write"), + Self::Ipc => f.write_str("ipc"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn conf_level_ordering() { + assert!(ConfLevel::Public < ConfLevel::Internal); + assert!(ConfLevel::Internal < ConfLevel::Sensitive); + assert!(ConfLevel::Sensitive < ConfLevel::Restricted); + } + + #[test] + fn conf_level_equality() { + assert_eq!(ConfLevel::Public, ConfLevel::Public); + assert_ne!(ConfLevel::Public, ConfLevel::Internal); + } + + #[test] + fn agent_id_root() { + assert_eq!(AgentId::root(), AgentId("root".into())); + } + + #[test] + fn agent_id_display() { + let id = AgentId::new("test-agent"); + assert_eq!(id.to_string(), "test-agent"); + } + + #[test] + fn tool_id_display() { + let id = ToolId::new("read_file"); + assert_eq!(id.to_string(), "read_file"); + } + + #[test] + fn invocation_id_display() { + let id = InvocationId::new("inv-001"); + assert_eq!(id.to_string(), "inv-001"); + } + + #[test] + fn egress_kind_display() { + assert_eq!(EgressKind::NetworkExternal.to_string(), "network_external"); + assert_eq!(EgressKind::Ipc.to_string(), "ipc"); + } +} diff --git a/argus/crates/argus-kernel/tests/safety_properties.rs b/argus/crates/argus-kernel/tests/safety_properties.rs new file mode 100644 index 0000000..f678a25 --- /dev/null +++ b/argus/crates/argus-kernel/tests/safety_properties.rs @@ -0,0 +1,477 @@ +use argus_kernel::*; +use std::collections::BTreeSet; + +struct AllowAll; +impl AuthorizerOracle for AllowAll { + fn allows(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } +} +struct PassAll; +impl ContentGateOracle for PassAll { + fn passes(&self, _: &AgentId, _: &ToolId, _: &KernelState, _: &BackgroundTheory) -> bool { + true + } +} +struct NoopStore; +impl EventStore for NoopStore { + fn append(&self, _: &KernelEvent) -> Result<(), KernelError> { + Ok(()) + } +} + +fn test_kernel() -> Kernel { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("check_exists"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: true, + }, + ); + b.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + Kernel::new(b.build(), AllowAll, PassAll, NoopStore) +} + +/// Safety 1: root_always_active +/// Root must remain active through all operations. +#[test] +fn root_survives_all_operations() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + k.revoke(AgentId::root(), AgentId::new("a1")).unwrap(); + assert!(k.state().agent_active.contains(&AgentId::root())); +} + +/// Safety 2: flow_confinement +/// A tainted agent cannot invoke tools with egress when flow policy denies it. +#[test] +fn tainted_agent_cannot_egress_when_denied() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.register_tool(ToolId::new("send_email")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::NetworkEgress) + .unwrap(); + + k.invoke_start( + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("a1"), InvocationId::new("i1")) + .unwrap(); + + let result = k.invoke_start( + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("i2"), + ); + assert!(result.is_err()); +} + +/// Safety 3: capability_subsumption +/// A child can never hold a capability its parent does not hold. +#[test] +fn child_cannot_get_cap_parent_lacks() { + let mut k = test_kernel(); + k.delegate(AgentId::root(), AgentId::new("parent")).unwrap(); + k.grant_capability( + AgentId::root(), + AgentId::new("parent"), + CapKind::FilesystemRead, + ) + .unwrap(); + k.delegate(AgentId::new("parent"), AgentId::new("child")) + .unwrap(); + + let result = k.grant_capability( + AgentId::new("parent"), + AgentId::new("child"), + CapKind::NetworkEgress, + ); + assert!(result.is_err()); +} + +/// Safety 4: taint_isolation +/// A freshly delegated child starts with no taint, regardless of parent state. +#[test] +fn fresh_child_has_no_taint() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + k.invoke_start( + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("a1"), InvocationId::new("i1")) + .unwrap(); + + k.delegate(AgentId::new("a1"), AgentId::new("child")) + .unwrap(); + assert!(k.state().taint_levels.get(&AgentId::new("child")).is_none()); +} + +/// Safety 5: revocation_completeness +/// revoke + cascade_revoke cleans the entire subtree. +#[test] +fn revoke_plus_cascade_cleans_subtree() { + let mut k = test_kernel(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.delegate(AgentId::new("a1"), AgentId::new("a2")).unwrap(); + + k.revoke(AgentId::root(), AgentId::new("a1")).unwrap(); + k.cascade_revoke(AgentId::new("a2"), AgentId::new("a1")) + .unwrap(); + + assert!(!k.state().agent_active.contains(&AgentId::new("a1"))); + assert!(!k.state().agent_active.contains(&AgentId::new("a2"))); +} + +/// Safety 6: taint_integrity +/// Endorsed tools do not add taint. +#[test] +fn endorsed_tool_no_taint() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("check_exists")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + + k.invoke_start( + AgentId::new("a1"), + ToolId::new("check_exists"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("a1"), InvocationId::new("i1")) + .unwrap(); + + assert!(k.state().taint_levels.get(&AgentId::new("a1")).is_none()); +} + +/// return_unendorsed propagates child taint to parent. +#[test] +fn return_unendorsed_propagates_taint_to_parent() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("parent")).unwrap(); + k.grant_capability( + AgentId::root(), + AgentId::new("parent"), + CapKind::FilesystemRead, + ) + .unwrap(); + k.delegate(AgentId::new("parent"), AgentId::new("child")) + .unwrap(); + k.grant_capability( + AgentId::new("parent"), + AgentId::new("child"), + CapKind::FilesystemRead, + ) + .unwrap(); + + k.invoke_start( + AgentId::new("child"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("child"), InvocationId::new("i1")) + .unwrap(); + + k.return_unendorsed(AgentId::new("child"), AgentId::new("parent")) + .unwrap(); + assert!( + k.state() + .taint_levels + .get(&AgentId::new("parent")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); +} + +/// Root cannot invoke tools directly (root is the orchestrator, not an agent). +#[test] +fn root_cannot_invoke() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + + let result = k.invoke_start( + AgentId::root(), + ToolId::new("read_file"), + InvocationId::new("i1"), + ); + assert!(result.is_err()); +} + +/// Speculative taint prevents exfiltration via parallel invocations. +#[test] +fn speculative_taint_blocks_parallel_exfil() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.register_tool(ToolId::new("send_email")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::FilesystemRead) + .unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("a1"), CapKind::NetworkEgress) + .unwrap(); + + k.invoke_start( + AgentId::new("a1"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + + let result = k.invoke_start( + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("i2"), + ); + assert!(result.is_err()); +} + +/// Deep delegation chain: grandparent -> parent -> child with capability subsumption. +#[test] +fn deep_delegation_chain() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + + k.delegate(AgentId::root(), AgentId::new("p")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("p"), CapKind::FilesystemRead) + .unwrap(); + + k.delegate(AgentId::new("p"), AgentId::new("c")).unwrap(); + k.grant_capability( + AgentId::new("p"), + AgentId::new("c"), + CapKind::FilesystemRead, + ) + .unwrap(); + + k.invoke_start( + AgentId::new("c"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("c"), InvocationId::new("i1")) + .unwrap(); + + k.return_unendorsed(AgentId::new("c"), AgentId::new("p")) + .unwrap(); + assert!( + k.state() + .taint_levels + .get(&AgentId::new("p")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); +} + +/// return_endorsed does NOT propagate child taint to parent. +/// This is the "I vouch for this child's output" path. +#[test] +fn return_endorsed_does_not_propagate_taint() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("read_file")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("p")).unwrap(); + k.grant_capability(AgentId::root(), AgentId::new("p"), CapKind::FilesystemRead) + .unwrap(); + k.delegate(AgentId::new("p"), AgentId::new("c")).unwrap(); + k.grant_capability( + AgentId::new("p"), + AgentId::new("c"), + CapKind::FilesystemRead, + ) + .unwrap(); + + k.invoke_start( + AgentId::new("c"), + ToolId::new("read_file"), + InvocationId::new("i1"), + ) + .unwrap(); + k.invoke_complete(AgentId::new("c"), InvocationId::new("i1")) + .unwrap(); + + k.return_endorsed(AgentId::new("c"), AgentId::new("p")) + .unwrap(); + assert!(k.state().taint_levels.get(&AgentId::new("p")).is_none()); +} + +fn test_kernel_with_deny_flow() -> Kernel { + let mut b = BackgroundTheoryBuilder::new(); + b.register_tool( + ToolId::new("read_file"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::new(), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + b.register_tool( + ToolId::new("send_email"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Public, + endorsed: false, + }, + ); + b.set_flow( + ConfLevel::Public, + EgressKind::NetworkExternal, + FlowMode::Allow, + ); + Kernel::new(b.build(), AllowAll, PassAll, NoopStore) +} + +#[test] +fn sentinel_elevate_taint_basic() { + let mut k = test_kernel(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + + let event = k + .sentinel_elevate_taint(AgentId::new("a1"), ConfLevel::Sensitive) + .unwrap(); + assert_eq!( + event.action, + KernelAction::SentinelElevateTaint { + agent: AgentId::new("a1"), + level: ConfLevel::Sensitive, + } + ); + assert!( + k.state() + .taint_levels + .get(&AgentId::new("a1")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); + assert!( + k.state() + .gh_taint_invoked + .get(&AgentId::new("a1")) + .unwrap() + .contains(&ConfLevel::Sensitive) + ); +} + +#[test] +fn sentinel_elevate_taint_inactive_agent_rejected() { + let mut k = test_kernel(); + let result = k.sentinel_elevate_taint(AgentId::new("ghost"), ConfLevel::Public); + assert!(result.is_err()); +} + +#[test] +fn sentinel_elevate_taint_flow_incompatible_rejected() { + let mut k = test_kernel_with_deny_flow(); + k.register_tool(ToolId::new("send_email")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + for &cap in CapKind::all() { + let _ = k.grant_capability(AgentId::root(), AgentId::new("a1"), cap); + } + + k.invoke_start( + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("i1"), + ) + .unwrap(); + + let result = k.sentinel_elevate_taint(AgentId::new("a1"), ConfLevel::Sensitive); + assert!(result.is_err()); +} + +#[test] +fn sentinel_elevate_taint_no_in_flight_always_succeeds() { + let mut k = test_kernel(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + + k.sentinel_elevate_taint(AgentId::new("a1"), ConfLevel::Restricted) + .unwrap(); + assert!( + k.state() + .taint_levels + .get(&AgentId::new("a1")) + .unwrap() + .contains(&ConfLevel::Restricted) + ); +} + +#[test] +fn flow_confinement_holds_after_sentinel_taint() { + let mut k = test_kernel(); + k.register_tool(ToolId::new("send_email")).unwrap(); + k.delegate(AgentId::root(), AgentId::new("a1")).unwrap(); + for &cap in CapKind::all() { + let _ = k.grant_capability(AgentId::root(), AgentId::new("a1"), cap); + } + + k.sentinel_elevate_taint(AgentId::new("a1"), ConfLevel::Sensitive) + .unwrap(); + + let result = k.invoke_start( + AgentId::new("a1"), + ToolId::new("send_email"), + InvocationId::new("i1"), + ); + assert!(result.is_err()); +} + +/// Event store failure leaves kernel in consistent state. +#[test] +fn event_store_failure_preserves_consistency() { + struct FailStore; + impl EventStore for FailStore { + fn append(&self, _: &KernelEvent) -> Result<(), KernelError> { + Err(KernelError::EventStoreError("simulated failure".into())) + } + } + + let bg = BackgroundTheoryBuilder::new().build(); + let mut k = Kernel::new(bg, AllowAll, PassAll, FailStore); + + let result = k.delegate(AgentId::root(), AgentId::new("a1")); + assert!(result.is_err()); + assert_eq!(k.sequence(), 0); + assert!(!k.state().agent_active.contains(&AgentId::new("a1"))); +} diff --git a/argus/crates/argus-oracle/Cargo.toml b/argus/crates/argus-oracle/Cargo.toml new file mode 100644 index 0000000..4316a59 --- /dev/null +++ b/argus/crates/argus-oracle/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "argus-oracle" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +argus-kernel = { workspace = true } +cedar-policy = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +toml = { workspace = true } +thiserror = { workspace = true } +secretscan = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/argus/crates/argus-oracle/cedar/default.cedar b/argus/crates/argus-oracle/cedar/default.cedar new file mode 100644 index 0000000..3426265 --- /dev/null +++ b/argus/crates/argus-oracle/cedar/default.cedar @@ -0,0 +1,7 @@ +permit( + principal, + action == Argus::Action::"invoke", + resource +) when { + resource.endorsed == true +}; diff --git a/argus/crates/argus-oracle/cedar/schema.cedarschema b/argus/crates/argus-oracle/cedar/schema.cedarschema new file mode 100644 index 0000000..d8f3386 --- /dev/null +++ b/argus/crates/argus-oracle/cedar/schema.cedarschema @@ -0,0 +1,26 @@ +namespace Argus { + entity Agent in [Agent] { + capabilities: Set, + taint_levels: Set, + }; + + type Scope = { + "paths"?: Set, + "domains"?: Set, + "commands"?: Set, + }; + + entity Tool { + endorsed: Bool, + conf_floor: String, + capabilities: Set, + egress: Set, + scope?: Scope, + }; + + action invoke appliesTo { + principal: [Agent], + resource: [Tool], + context: {}, + }; +} diff --git a/argus/crates/argus-oracle/src/cedar.rs b/argus/crates/argus-oracle/src/cedar.rs new file mode 100644 index 0000000..01a3a91 --- /dev/null +++ b/argus/crates/argus-oracle/src/cedar.rs @@ -0,0 +1,276 @@ +use std::collections::BTreeMap; + +use argus_kernel::{AgentId, AuthorizerOracle, BackgroundTheory, KernelState, ToolId}; +use cedar_policy::{Authorizer, Decision, PolicySet, Schema}; + +use crate::cedar_schema::{build_entities_for_request, build_request, load_built_in_schema}; +use crate::error::CedarError; +use crate::policy_source::{PolicySource, ToolScope}; + +pub struct CedarAuthorizer { + authorizer: Authorizer, + policy_set: PolicySet, + schema: Schema, + tool_scopes: BTreeMap, +} + +impl CedarAuthorizer { + pub fn new(source: &dyn PolicySource) -> Result { + let schema = load_built_in_schema()?; + let policy_set = source.load_policies()?; + let tool_scopes = source.load_tool_scopes()?; + + Ok(Self { + authorizer: Authorizer::new(), + policy_set, + schema, + tool_scopes, + }) + } +} + +impl AuthorizerOracle for CedarAuthorizer { + fn allows( + &self, + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + bg: &BackgroundTheory, + ) -> bool { + let Ok(entities) = build_entities_for_request(agent, tool, state, bg, &self.tool_scopes) + else { + return false; + }; + + let Ok(request) = build_request(agent, tool, Some(&self.schema)) else { + return false; + }; + + self.authorizer + .is_authorized(&request, &self.policy_set, &entities) + .decision() + == Decision::Allow + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + + use argus_kernel::{BackgroundTheoryBuilder, CapKind, ConfLevel, EgressKind, ToolMetadata}; + + fn test_background() -> BackgroundTheory { + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("endorsed-tool"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::from([EgressKind::FilesystemWrite]), + conf_floor: ConfLevel::Internal, + endorsed: true, + }, + ); + builder.register_tool( + ToolId::new("unendorsed-tool"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + builder.build() + } + + fn test_state() -> KernelState { + let mut state = KernelState::initial(); + state.agent_active.insert(AgentId::new("worker")); + state + .agent_parent + .insert(AgentId::new("worker"), AgentId::root()); + state.agent_cap.insert( + AgentId::new("worker"), + BTreeSet::from([CapKind::FilesystemRead, CapKind::NetworkEgress]), + ); + state + } + + struct InlinePolicySource { + policy_text: String, + scopes: BTreeMap, + } + + impl PolicySource for InlinePolicySource { + fn load_policies(&self) -> Result { + if self.policy_text.is_empty() { + return Ok(PolicySet::new()); + } + self.policy_text + .parse::() + .map_err(|e| CedarError::PolicyParse(e.to_string())) + } + fn load_tool_scopes(&self) -> Result, CedarError> { + Ok(self.scopes.clone()) + } + } + + fn source_with_policy(policy: &str) -> InlinePolicySource { + InlinePolicySource { + policy_text: policy.to_string(), + scopes: BTreeMap::new(), + } + } + + #[test] + fn allows_endorsed_tool_with_default_policy() { + let policy = r#"permit(principal, action == Argus::Action::"invoke", resource) when { resource.endorsed == true };"#; + let source = source_with_policy(policy); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &test_state(), + &test_background() + )); + } + + #[test] + fn denies_unendorsed_tool_with_endorsed_only_policy() { + let policy = r#"permit(principal, action == Argus::Action::"invoke", resource) when { resource.endorsed == true };"#; + let source = source_with_policy(policy); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("unendorsed-tool"), + &test_state(), + &test_background() + )); + } + + #[test] + fn denies_when_no_policies() { + let source = source_with_policy(""); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &test_state(), + &test_background() + )); + } + + #[test] + fn forbid_overrides_permit() { + let policy = r#" + permit(principal, action == Argus::Action::"invoke", resource); + forbid(principal, action == Argus::Action::"invoke", resource) + when { resource.endorsed == false }; + "#; + let source = source_with_policy(policy); + let bg = test_background(); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &test_state(), + &bg + )); + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("unendorsed-tool"), + &test_state(), + &bg + )); + } + + #[test] + fn tainted_state_changes_decision() { + let policy = r#" + permit(principal, action == Argus::Action::"invoke", resource); + forbid(principal, action == Argus::Action::"invoke", resource) + when { principal.taint_levels.contains("restricted") }; + "#; + let source = source_with_policy(policy); + let bg = test_background(); + let auth = CedarAuthorizer::new(&source).unwrap(); + + assert!(auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &test_state(), + &bg + )); + + let mut tainted_state = test_state(); + tainted_state.taint_levels.insert( + AgentId::new("worker"), + BTreeSet::from([ConfLevel::Restricted]), + ); + + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &tainted_state, + &bg + )); + } + + #[test] + fn unknown_agent_denied_by_attribute_policy() { + let policy = r#"permit(principal, action == Argus::Action::"invoke", resource) when { resource.endorsed == true };"#; + let source = source_with_policy(policy); + let auth = CedarAuthorizer::new(&source).unwrap(); + let _ = auth.allows( + &AgentId::new("ghost"), + &ToolId::new("endorsed-tool"), + &test_state(), + &test_background(), + ); + } + + #[test] + fn unknown_tool_denied_by_attribute_policy() { + let policy = r#"permit(principal, action == Argus::Action::"invoke", resource) when { resource.endorsed == true };"#; + let source = source_with_policy(policy); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("nonexistent"), + &test_state(), + &test_background() + )); + } + + #[test] + fn scope_based_policy() { + let policy = r#" + permit(principal, action == Argus::Action::"invoke", resource) + when { resource has scope && resource.scope has paths }; + "#; + let source = InlinePolicySource { + policy_text: policy.to_string(), + scopes: BTreeMap::from([( + ToolId::new("endorsed-tool"), + ToolScope { + paths: vec!["/home/**".to_string()], + ..Default::default() + }, + )]), + }; + let bg = test_background(); + let auth = CedarAuthorizer::new(&source).unwrap(); + assert!(auth.allows( + &AgentId::new("worker"), + &ToolId::new("endorsed-tool"), + &test_state(), + &bg + )); + assert!(!auth.allows( + &AgentId::new("worker"), + &ToolId::new("unendorsed-tool"), + &test_state(), + &bg + )); + } +} diff --git a/argus/crates/argus-oracle/src/cedar_schema.rs b/argus/crates/argus-oracle/src/cedar_schema.rs new file mode 100644 index 0000000..b76f2e2 --- /dev/null +++ b/argus/crates/argus-oracle/src/cedar_schema.rs @@ -0,0 +1,314 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::str::FromStr; + +use argus_kernel::{AgentId, BackgroundTheory, KernelState, ToolId}; +use cedar_policy::{ + Context, Entities, Entity, EntityId, EntityTypeName, EntityUid, Request, RestrictedExpression, + Schema, +}; + +use crate::error::CedarError; +use crate::policy_source::ToolScope; + +const BUILT_IN_SCHEMA: &str = include_str!("../cedar/schema.cedarschema"); + +pub fn load_built_in_schema() -> Result { + Schema::from_cedarschema_str(BUILT_IN_SCHEMA) + .map(|(schema, _warnings)| schema) + .map_err(|e| CedarError::SchemaValidation(e.to_string())) +} + +fn agent_type_name() -> EntityTypeName { + EntityTypeName::from_str("Argus::Agent").expect("valid type name") +} + +fn tool_type_name() -> EntityTypeName { + EntityTypeName::from_str("Argus::Tool").expect("valid type name") +} + +fn action_type_name() -> EntityTypeName { + EntityTypeName::from_str("Argus::Action").expect("valid type name") +} + +fn agent_uid(agent: &AgentId) -> EntityUid { + EntityUid::from_type_name_and_id(agent_type_name(), EntityId::new(&agent.0)) +} + +fn tool_uid(tool: &ToolId) -> EntityUid { + EntityUid::from_type_name_and_id(tool_type_name(), EntityId::new(&tool.0)) +} + +fn action_invoke_uid() -> EntityUid { + EntityUid::from_type_name_and_id(action_type_name(), EntityId::new("invoke")) +} + +fn string_set(items: impl IntoIterator>) -> RestrictedExpression { + RestrictedExpression::new_set( + items + .into_iter() + .map(|s| RestrictedExpression::new_string(s.as_ref().to_string())), + ) +} + +pub fn build_agent_entity( + agent: &AgentId, + state: &KernelState, +) -> Result, CedarError> { + if !state.agent_active.contains(agent) { + return Ok(None); + } + + let uid = agent_uid(agent); + + let caps: Vec = state + .agent_cap + .get(agent) + .map(|s| s.iter().map(|c| c.to_string()).collect()) + .unwrap_or_default(); + + let taints: Vec = state + .taint_levels + .get(agent) + .map(|s| s.iter().map(|c| c.to_string()).collect()) + .unwrap_or_default(); + + let attrs = HashMap::from([ + ("capabilities".to_string(), string_set(&caps)), + ("taint_levels".to_string(), string_set(&taints)), + ]); + + let parents: HashSet = state + .agent_parent + .get(agent) + .map(agent_uid) + .into_iter() + .collect(); + + Entity::new(uid, attrs, parents) + .map(Some) + .map_err(|e| CedarError::SchemaValidation(format!("agent entity: {e}"))) +} + +pub fn build_tool_entity( + tool: &ToolId, + bg: &BackgroundTheory, + scopes: &BTreeMap, +) -> Result, CedarError> { + let meta = match bg.tool_metadata(tool) { + Some(m) => m, + None => return Ok(None), + }; + let uid = tool_uid(tool); + + let caps: Vec = meta.capabilities.iter().map(|c| c.to_string()).collect(); + let egress: Vec = meta.egress.iter().map(|e| e.to_string()).collect(); + + let mut attrs = HashMap::from([ + ( + "endorsed".to_string(), + RestrictedExpression::new_bool(meta.endorsed), + ), + ( + "conf_floor".to_string(), + RestrictedExpression::new_string(meta.conf_floor.to_string()), + ), + ("capabilities".to_string(), string_set(&caps)), + ("egress".to_string(), string_set(&egress)), + ]); + + if let Some(scope) = scopes.get(tool) { + let scope_fields: Vec<(String, RestrictedExpression)> = [ + ("paths", &scope.paths), + ("domains", &scope.domains), + ("commands", &scope.commands), + ] + .into_iter() + .filter(|(_, v)| !v.is_empty()) + .map(|(name, v)| (name.to_string(), string_set(v))) + .collect(); + + if !scope_fields.is_empty() { + let record = RestrictedExpression::new_record(scope_fields) + .map_err(|e| CedarError::SchemaValidation(format!("tool scope: {e}")))?; + attrs.insert("scope".to_string(), record); + } + } + + Entity::new(uid, attrs, HashSet::new()) + .map(Some) + .map_err(|e| CedarError::SchemaValidation(format!("tool entity: {e}"))) +} + +pub fn build_entities_for_request( + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + bg: &BackgroundTheory, + scopes: &BTreeMap, +) -> Result { + let mut entities = vec![]; + + let mut current = Some(agent.clone()); + let mut visited = HashSet::new(); + while let Some(ref a) = current { + if !visited.insert(a.clone()) { + break; + } + if let Some(entity) = build_agent_entity(a, state)? { + entities.push(entity); + } + current = state.agent_parent.get(a).cloned(); + } + + if let Some(entity) = build_tool_entity(tool, bg, scopes)? { + entities.push(entity); + } + + Entities::from_entities(entities, None).map_err(|e| CedarError::SchemaValidation(e.to_string())) +} + +pub fn build_request( + agent: &AgentId, + tool: &ToolId, + schema: Option<&Schema>, +) -> Result { + Request::new( + agent_uid(agent), + action_invoke_uid(), + tool_uid(tool), + Context::empty(), + schema, + ) + .map_err(|e| CedarError::SchemaValidation(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + + use argus_kernel::{BackgroundTheoryBuilder, CapKind, ConfLevel, EgressKind, ToolMetadata}; + use cedar_policy::{Authorizer, Decision, PolicySet}; + + fn test_background() -> BackgroundTheory { + let mut builder = BackgroundTheoryBuilder::new(); + builder.register_tool( + ToolId::new("fs-reader"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::FilesystemRead]), + egress: BTreeSet::from([EgressKind::FilesystemWrite]), + conf_floor: ConfLevel::Internal, + endorsed: true, + }, + ); + builder.register_tool( + ToolId::new("web-fetch"), + ToolMetadata { + capabilities: BTreeSet::from([CapKind::NetworkEgress]), + egress: BTreeSet::from([EgressKind::NetworkExternal]), + conf_floor: ConfLevel::Sensitive, + endorsed: false, + }, + ); + builder.build() + } + + fn test_state() -> KernelState { + let mut state = KernelState::initial(); + state.agent_active.insert(AgentId::new("child")); + state + .agent_parent + .insert(AgentId::new("child"), AgentId::root()); + state.agent_cap.insert( + AgentId::new("child"), + BTreeSet::from([CapKind::FilesystemRead, CapKind::NetworkEgress]), + ); + state + .taint_levels + .insert(AgentId::new("child"), BTreeSet::from([ConfLevel::Internal])); + state + } + + #[test] + fn built_in_schema_parses() { + let schema = load_built_in_schema(); + assert!(schema.is_ok(), "Schema failed to parse: {:?}", schema.err()); + } + + #[test] + fn build_agent_entity_with_attributes() { + let state = test_state(); + let entity = build_agent_entity(&AgentId::new("child"), &state).unwrap(); + assert!(entity.is_some()); + } + + #[test] + fn build_agent_entity_returns_none_for_unknown() { + let state = KernelState::initial(); + let entity = build_agent_entity(&AgentId::new("nonexistent"), &state).unwrap(); + assert!(entity.is_none()); + } + + #[test] + fn build_tool_entity_with_scope() { + let bg = test_background(); + let scopes = BTreeMap::from([( + ToolId::new("fs-reader"), + ToolScope { + paths: vec!["/home/**".to_string()], + ..Default::default() + }, + )]); + let entity = build_tool_entity(&ToolId::new("fs-reader"), &bg, &scopes).unwrap(); + assert!(entity.is_some()); + } + + #[test] + fn build_tool_entity_returns_none_for_unknown() { + let bg = test_background(); + let entity = build_tool_entity(&ToolId::new("unknown"), &bg, &BTreeMap::new()).unwrap(); + assert!(entity.is_none()); + } + + #[test] + fn build_entities_for_request_includes_agent_chain_and_tool() { + let state = test_state(); + let bg = test_background(); + let scopes = BTreeMap::new(); + let entities = build_entities_for_request( + &AgentId::new("child"), + &ToolId::new("fs-reader"), + &state, + &bg, + &scopes, + ); + assert!(entities.is_ok()); + } + + #[test] + fn end_to_end_policy_evaluation() { + let _schema = load_built_in_schema().unwrap(); + let state = test_state(); + let bg = test_background(); + let scopes = BTreeMap::new(); + + let policy: PolicySet = r#"permit(principal, action == Argus::Action::"invoke", resource) when { resource.endorsed == true };"# + .parse() + .unwrap(); + + let entities = build_entities_for_request( + &AgentId::new("child"), + &ToolId::new("fs-reader"), + &state, + &bg, + &scopes, + ) + .unwrap(); + + let request = + build_request(&AgentId::new("child"), &ToolId::new("fs-reader"), None).unwrap(); + let authorizer = Authorizer::new(); + let response = authorizer.is_authorized(&request, &policy, &entities); + assert_eq!(response.decision(), Decision::Allow); + } +} diff --git a/argus/crates/argus-oracle/src/content_gate.rs b/argus/crates/argus-oracle/src/content_gate.rs new file mode 100644 index 0000000..592eead --- /dev/null +++ b/argus/crates/argus-oracle/src/content_gate.rs @@ -0,0 +1,210 @@ +use argus_kernel::{AgentId, BackgroundTheory, ContentGateOracle, KernelState, ToolId}; +use secretscan::patterns::{ + analyze_base64_for_secrets, analyze_hex_for_secrets, analyze_url_encoded_for_secrets, + get_all_patterns, +}; + +pub struct SentinelContentGate { + _private: (), +} + +impl Default for SentinelContentGate { + fn default() -> Self { + Self::new() + } +} + +impl SentinelContentGate { + pub fn new() -> Self { + Self { _private: () } + } + + pub fn scan_strings(&self, value: &serde_json::Value) -> Vec { + let mut detections = Vec::new(); + self.scan_value(value, &mut detections); + detections + } + + fn scan_value(&self, value: &serde_json::Value, detections: &mut Vec) { + match value { + serde_json::Value::String(s) => { + self.scan_text(s, detections); + } + serde_json::Value::Array(arr) => { + for v in arr { + self.scan_value(v, detections); + } + } + serde_json::Value::Object(map) => { + for v in map.values() { + self.scan_value(v, detections); + } + } + _ => {} + } + } + + fn scan_text(&self, text: &str, detections: &mut Vec) { + for (pattern_name, regex) in get_all_patterns().iter() { + if let Some(m) = regex.find(text) { + detections.push(Detection { + kind: DetectionKind::SecretPattern, + pattern_name: pattern_name.clone(), + matched_text: m.as_str().to_owned(), + }); + } + } + + for (pattern_name, matched) in analyze_base64_for_secrets(text) { + detections.push(Detection { + kind: DetectionKind::ObfuscatedSecret, + pattern_name, + matched_text: matched, + }); + } + + for (pattern_name, matched) in analyze_hex_for_secrets(text) { + detections.push(Detection { + kind: DetectionKind::ObfuscatedSecret, + pattern_name, + matched_text: matched, + }); + } + + if text.contains('%') { + for (pattern_name, matched) in analyze_url_encoded_for_secrets(text) { + detections.push(Detection { + kind: DetectionKind::ObfuscatedSecret, + pattern_name, + matched_text: matched, + }); + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Detection { + pub kind: DetectionKind, + pub pattern_name: String, + pub matched_text: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DetectionKind { + SecretPattern, + ObfuscatedSecret, +} + +impl ContentGateOracle for SentinelContentGate { + fn passes( + &self, + agent: &AgentId, + tool: &ToolId, + _state: &KernelState, + _bg: &BackgroundTheory, + ) -> bool { + tracing::debug!(agent = %agent, tool = %tool, "content gate evaluated"); + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use argus_kernel::BackgroundTheoryBuilder; + use serde_json::json; + + fn gate() -> SentinelContentGate { + SentinelContentGate::new() + } + + #[test] + fn detects_aws_access_key() { + let input = json!({"key": "AKIAIOSFODNN7EXAMPLE"}); + let detections = gate().scan_strings(&input); + assert!( + !detections.is_empty(), + "expected detection for AWS access key" + ); + assert!( + detections + .iter() + .any(|d| d.kind == DetectionKind::SecretPattern) + ); + } + + #[test] + fn detects_github_token() { + let input = json!({"token": "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij12"}); + let detections = gate().scan_strings(&input); + assert!( + !detections.is_empty(), + "expected detection for GitHub token" + ); + } + + #[test] + fn detects_openai_api_key() { + let input = json!({"api_key": "sk-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789"}); + let detections = gate().scan_strings(&input); + assert!( + !detections.is_empty(), + "expected detection for OpenAI API key" + ); + } + + #[test] + fn clean_input_produces_no_detections() { + let input = json!({ + "message": "Hello world", + "count": 42, + "items": ["apple", "banana"] + }); + let detections = gate().scan_strings(&input); + assert!( + detections.is_empty(), + "expected no detections for clean input, got: {detections:?}" + ); + } + + #[test] + fn scans_nested_json_recursively() { + let input = json!({ + "outer": { + "inner": { + "deep": "AKIAIOSFODNN7EXAMPLE" + } + } + }); + let detections = gate().scan_strings(&input); + assert!(!detections.is_empty(), "expected detection in nested JSON"); + } + + #[test] + fn detects_base64_obfuscated_secret() { + // "AKIAIOSFODNN7EXAMPLE" base64-encoded + let encoded = "QUtJQUlPU0ZPRE5ON0VYQU1QTEU="; + let input = json!({"payload": encoded}); + let detections = gate().scan_strings(&input); + assert!( + detections + .iter() + .any(|d| d.kind == DetectionKind::ObfuscatedSecret), + "expected ObfuscatedSecret detection for base64-encoded AWS key, got: {detections:?}" + ); + } + + #[test] + fn content_gate_oracle_always_passes() { + let gate = gate(); + let state = KernelState::initial(); + let bg = BackgroundTheoryBuilder::new().build(); + assert!(gate.passes( + &AgentId::new("any-agent"), + &ToolId::new("any-tool"), + &state, + &bg, + )); + } +} diff --git a/argus/crates/argus-oracle/src/error.rs b/argus/crates/argus-oracle/src/error.rs new file mode 100644 index 0000000..8e39269 --- /dev/null +++ b/argus/crates/argus-oracle/src/error.rs @@ -0,0 +1,11 @@ +#[derive(Debug, thiserror::Error)] +pub enum CedarError { + #[error("policy parse error: {0}")] + PolicyParse(String), + #[error("schema validation error: {0}")] + SchemaValidation(String), + #[error("scope manifest parse error: {0}")] + ScopeManifestParse(String), + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} diff --git a/argus/crates/argus-oracle/src/lib.rs b/argus/crates/argus-oracle/src/lib.rs new file mode 100644 index 0000000..7d9e1ba --- /dev/null +++ b/argus/crates/argus-oracle/src/lib.rs @@ -0,0 +1,13 @@ +mod cedar; +mod cedar_schema; +mod content_gate; +mod error; +mod mock; +mod policy_source; + +pub use cedar::CedarAuthorizer; +pub use cedar_schema::{build_entities_for_request, build_request, load_built_in_schema}; +pub use content_gate::{Detection, DetectionKind, SentinelContentGate}; +pub use error::CedarError; +pub use mock::{MockAuthorizer, MockContentGate}; +pub use policy_source::{FilePolicySource, PolicySource, ToolScope}; diff --git a/argus/crates/argus-oracle/src/mock.rs b/argus/crates/argus-oracle/src/mock.rs new file mode 100644 index 0000000..b1744c1 --- /dev/null +++ b/argus/crates/argus-oracle/src/mock.rs @@ -0,0 +1,148 @@ +use std::collections::BTreeSet; + +use argus_kernel::{ + AgentId, AuthorizerOracle, BackgroundTheory, ContentGateOracle, KernelState, ToolId, +}; + +pub struct MockAuthorizer { + allowed: BTreeSet<(AgentId, ToolId)>, + allow_all: bool, +} + +impl Default for MockAuthorizer { + fn default() -> Self { + Self::new() + } +} + +impl MockAuthorizer { + pub fn new() -> Self { + Self { + allowed: BTreeSet::new(), + allow_all: false, + } + } + + pub fn allow_all() -> Self { + Self { + allowed: BTreeSet::new(), + allow_all: true, + } + } + + pub fn allow(mut self, agent: AgentId, tool: ToolId) -> Self { + self.allowed.insert((agent, tool)); + self + } +} + +impl AuthorizerOracle for MockAuthorizer { + fn allows( + &self, + agent: &AgentId, + tool: &ToolId, + _state: &KernelState, + _bg: &BackgroundTheory, + ) -> bool { + self.allow_all || self.allowed.contains(&(agent.clone(), tool.clone())) + } +} + +pub struct MockContentGate { + allowed: BTreeSet<(AgentId, ToolId)>, + pass_all: bool, +} + +impl Default for MockContentGate { + fn default() -> Self { + Self::new() + } +} + +impl MockContentGate { + pub fn new() -> Self { + Self { + allowed: BTreeSet::new(), + pass_all: false, + } + } + + pub fn pass_all() -> Self { + Self { + allowed: BTreeSet::new(), + pass_all: true, + } + } + + pub fn pass(mut self, agent: AgentId, tool: ToolId) -> Self { + self.allowed.insert((agent, tool)); + self + } +} + +impl ContentGateOracle for MockContentGate { + fn passes( + &self, + agent: &AgentId, + tool: &ToolId, + _state: &KernelState, + _bg: &BackgroundTheory, + ) -> bool { + self.pass_all || self.allowed.contains(&(agent.clone(), tool.clone())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use argus_kernel::BackgroundTheoryBuilder; + + fn s() -> KernelState { + KernelState::initial() + } + + fn bg() -> BackgroundTheory { + BackgroundTheoryBuilder::new().build() + } + + #[test] + fn mock_authorizer_allows_configured_pair() { + let auth = MockAuthorizer::new().allow(AgentId::new("agent1"), ToolId::new("tool1")); + assert!(auth.allows(&AgentId::new("agent1"), &ToolId::new("tool1"), &s(), &bg())); + } + + #[test] + fn mock_authorizer_denies_unconfigured_pair() { + let auth = MockAuthorizer::new().allow(AgentId::new("agent1"), ToolId::new("tool1")); + assert!(!auth.allows(&AgentId::new("agent2"), &ToolId::new("tool1"), &s(), &bg())); + } + + #[test] + fn mock_authorizer_allow_all_permits_everything() { + let auth = MockAuthorizer::allow_all(); + assert!(auth.allows( + &AgentId::new("anyone"), + &ToolId::new("anything"), + &s(), + &bg() + )); + } + + #[test] + fn mock_content_gate_passes_configured_pair() { + let gate = MockContentGate::new().pass(AgentId::new("a"), ToolId::new("t")); + assert!(gate.passes(&AgentId::new("a"), &ToolId::new("t"), &s(), &bg())); + } + + #[test] + fn mock_content_gate_blocks_unconfigured_pair() { + let gate = MockContentGate::new(); + assert!(!gate.passes(&AgentId::new("a"), &ToolId::new("t"), &s(), &bg())); + } + + #[test] + fn mock_content_gate_pass_all_permits_everything() { + let gate = MockContentGate::pass_all(); + assert!(gate.passes(&AgentId::new("x"), &ToolId::new("y"), &s(), &bg())); + } +} diff --git a/argus/crates/argus-oracle/src/policy_source.rs b/argus/crates/argus-oracle/src/policy_source.rs new file mode 100644 index 0000000..3a8bb02 --- /dev/null +++ b/argus/crates/argus-oracle/src/policy_source.rs @@ -0,0 +1,163 @@ +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use argus_kernel::ToolId; +use cedar_policy::PolicySet; + +use crate::error::CedarError; + +#[derive(Debug, Clone, Default, serde::Deserialize)] +pub struct ToolScope { + #[serde(default)] + pub paths: Vec, + #[serde(default)] + pub domains: Vec, + #[serde(default)] + pub commands: Vec, +} + +pub trait PolicySource { + fn load_policies(&self) -> Result; + fn load_tool_scopes(&self) -> Result, CedarError>; +} + +#[derive(Debug, Clone, Default, serde::Deserialize)] +struct ScopeManifest { + #[serde(default)] + tools: BTreeMap, +} + +pub struct FilePolicySource { + policy_dir: PathBuf, +} + +impl FilePolicySource { + pub fn new(policy_dir: &Path) -> Self { + Self { + policy_dir: policy_dir.to_path_buf(), + } + } +} + +impl PolicySource for FilePolicySource { + fn load_policies(&self) -> Result { + let mut cedar_paths = Vec::new(); + for entry in std::fs::read_dir(&self.policy_dir).map_err(CedarError::Io)? { + let path = entry.map_err(CedarError::Io)?.path(); + if path.extension().and_then(|e| e.to_str()) == Some("cedar") { + cedar_paths.push(path); + } + } + cedar_paths.sort(); + + let mut combined = String::new(); + for path in &cedar_paths { + let content = std::fs::read_to_string(path).map_err(CedarError::Io)?; + combined.push_str(&content); + combined.push('\n'); + } + + if combined.is_empty() { + return Ok(PolicySet::new()); + } + combined + .parse::() + .map_err(|e| CedarError::PolicyParse(e.to_string())) + } + + fn load_tool_scopes(&self) -> Result, CedarError> { + let path = self.policy_dir.join("tool-scopes.toml"); + if !path.exists() { + return Ok(BTreeMap::new()); + } + let content = std::fs::read_to_string(&path).map_err(CedarError::Io)?; + let manifest: ScopeManifest = + toml::from_str(&content).map_err(|e| CedarError::ScopeManifestParse(e.to_string()))?; + Ok(manifest + .tools + .into_iter() + .map(|(name, scope)| (ToolId::new(&name), scope)) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + fn setup_policy_dir(dir: &Path, policies: &[(&str, &str)], scopes_toml: Option<&str>) { + fs::create_dir_all(dir).unwrap(); + for (name, content) in policies { + fs::write(dir.join(name), content).unwrap(); + } + if let Some(toml_content) = scopes_toml { + fs::write(dir.join("tool-scopes.toml"), toml_content).unwrap(); + } + } + + #[test] + fn loads_cedar_policies_from_dir() { + let tmp = tempfile::tempdir().unwrap(); + let policy = r#"permit(principal, action, resource);"#; + setup_policy_dir(tmp.path(), &[("basic.cedar", policy)], None); + + let source = FilePolicySource::new(tmp.path()); + let ps = source.load_policies().unwrap(); + assert!(!ps.is_empty()); + } + + #[test] + fn empty_dir_returns_empty_policy_set() { + let tmp = tempfile::tempdir().unwrap(); + setup_policy_dir(tmp.path(), &[], None); + + let source = FilePolicySource::new(tmp.path()); + let ps = source.load_policies().unwrap(); + assert!(ps.is_empty()); + } + + #[test] + fn invalid_policy_returns_error() { + let tmp = tempfile::tempdir().unwrap(); + setup_policy_dir(tmp.path(), &[("bad.cedar", "not valid cedar!!!")], None); + + let source = FilePolicySource::new(tmp.path()); + assert!(source.load_policies().is_err()); + } + + #[test] + fn loads_tool_scopes_from_toml() { + let tmp = tempfile::tempdir().unwrap(); + let scopes = r#" +[tools."fs-reader"] +paths = ["/home/user/**"] + +[tools."web-fetch"] +domains = ["api.example.com"] +"#; + setup_policy_dir(tmp.path(), &[], Some(scopes)); + + let source = FilePolicySource::new(tmp.path()); + let scopes = source.load_tool_scopes().unwrap(); + assert_eq!(scopes.len(), 2); + assert_eq!( + scopes[&ToolId::new("fs-reader")].paths, + vec!["/home/user/**"] + ); + assert_eq!( + scopes[&ToolId::new("web-fetch")].domains, + vec!["api.example.com"] + ); + } + + #[test] + fn missing_scopes_file_returns_empty_map() { + let tmp = tempfile::tempdir().unwrap(); + setup_policy_dir(tmp.path(), &[], None); + + let source = FilePolicySource::new(tmp.path()); + let scopes = source.load_tool_scopes().unwrap(); + assert!(scopes.is_empty()); + } +} diff --git a/argus/crates/argus-registry/Cargo.toml b/argus/crates/argus-registry/Cargo.toml new file mode 100644 index 0000000..a0ed894 --- /dev/null +++ b/argus/crates/argus-registry/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "argus-registry" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +argus-config.workspace = true +serde.workspace = true +serde_json.workspace = true +toml.workspace = true +thiserror.workspace = true +chrono.workspace = true +tracing.workspace = true +notify.workspace = true +tokio = { workspace = true, features = ["sync", "time", "rt-multi-thread"] } +rusqlite.workspace = true +sha2.workspace = true +hex.workspace = true +uuid.workspace = true + +[dev-dependencies] +tempfile.workspace = true +tokio = { workspace = true, features = ["sync", "time", "rt-multi-thread", "macros"] } diff --git a/argus/crates/argus-registry/src/argus_db.rs b/argus/crates/argus-registry/src/argus_db.rs new file mode 100644 index 0000000..6a6e61c --- /dev/null +++ b/argus/crates/argus-registry/src/argus_db.rs @@ -0,0 +1,46 @@ +use std::path::Path; +use std::sync::Arc; + +use crate::policy_attestation::PolicyAttestationStore; +use crate::trust_event::TrustEventStore; + +pub struct ArgusDb { + trust_events: Arc, + policy_attestations: Arc, +} + +impl ArgusDb { + pub fn new(db_path: &Path) -> Result { + let trust_events = Arc::new(TrustEventStore::new(db_path)?); + let policy_attestations = Arc::new(PolicyAttestationStore::new(db_path)?); + Ok(Self { + trust_events, + policy_attestations, + }) + } + + pub fn trust_events(&self) -> &Arc { + &self.trust_events + } + + pub fn policy_attestations(&self) -> &Arc { + &self.policy_attestations + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn argus_db_creates_both_stores() { + let temp = tempfile::TempDir::new().unwrap(); + let db = ArgusDb::new(&temp.path().join("argus.db")).unwrap(); + + let trust = db.trust_events(); + assert!(trust.recent_events(10).unwrap().is_empty()); + + let policy = db.policy_attestations(); + assert!(policy.latest_hashes().unwrap().is_empty()); + } +} diff --git a/argus/crates/argus-registry/src/assembler.rs b/argus/crates/argus-registry/src/assembler.rs new file mode 100644 index 0000000..9016019 --- /dev/null +++ b/argus/crates/argus-registry/src/assembler.rs @@ -0,0 +1,140 @@ +use std::fs; +use std::path::Path; + +use crate::toml_types::{McpArgs, McpEntry, McpMetadata, SandboxConfig}; + +pub struct AssemblyInput { + pub name: String, + pub description: String, + pub repository: String, + pub pinned_ref: String, + pub track: bool, + pub capabilities: Vec, + pub install: crate::toml_types::InstallConfig, + pub sandbox: SandboxConfig, +} + +pub fn assemble_mcp_entry(input: AssemblyInput) -> McpEntry { + let source = match &input.install { + crate::toml_types::InstallConfig::Npm { .. } => "npm", + crate::toml_types::InstallConfig::Pip { .. } => "pip", + crate::toml_types::InstallConfig::Cargo { .. } => "cargo", + crate::toml_types::InstallConfig::Docker { .. } => "docker", + crate::toml_types::InstallConfig::Binary { .. } => "binary", + }; + + McpEntry { + metadata: McpMetadata { + name: input.name, + description: input.description, + repository: Some(input.repository), + source: source.to_string(), + integrity: "untrusted".to_string(), + capabilities: input.capabilities, + schema_hash: None, + pinned_ref: Some(input.pinned_ref), + track: Some(input.track), + }, + install: input.install, + env: Default::default(), + args: McpArgs { default: vec![] }, + sandbox: Some(input.sandbox), + sidecars: vec![], + } +} + +pub fn write_registry_entry( + registry_dir: &Path, + name: &str, + entry: &McpEntry, + report_json: &str, +) -> std::io::Result<()> { + let mcps_dir = registry_dir.join("mcps"); + fs::create_dir_all(&mcps_dir)?; + + let toml_content = toml::to_string_pretty(entry) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + fs::write(mcps_dir.join(format!("{name}.toml")), toml_content)?; + + let reports_dir = registry_dir.join("reports"); + fs::create_dir_all(&reports_dir)?; + fs::write(reports_dir.join(format!("{name}.json")), report_json)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::toml_types::{InstallConfig, SandboxNetPolicy}; + + fn sample_input() -> AssemblyInput { + AssemblyInput { + name: "claude-mem".to_string(), + description: "Persistent memory for Claude Code".to_string(), + repository: "https://github.com/thedotmack/claude-mem".to_string(), + pinned_ref: "abc123".to_string(), + track: false, + capabilities: vec![ + "filesystem:read(~/.claude-mem/)".to_string(), + "network:egress(api.anthropic.com)".to_string(), + ], + install: InstallConfig::Npm { + package: "@thedotmack/claude-mem".to_string(), + version: "1.0.0".to_string(), + }, + sandbox: SandboxConfig { + extra_read: vec!["~/.claude-mem/".to_string()], + extra_write: vec!["~/.claude-mem/".to_string()], + require_sandbox: true, + net_policy: Some(SandboxNetPolicy::EgressDomains(vec![ + "api.anthropic.com".to_string(), + ])), + }, + } + } + + #[test] + fn assemble_mcp_entry_sets_untrusted_integrity() { + let entry = assemble_mcp_entry(sample_input()); + assert_eq!(entry.metadata.integrity, "untrusted"); + } + + #[test] + fn assemble_mcp_entry_sets_source_from_install() { + let entry = assemble_mcp_entry(sample_input()); + assert_eq!(entry.metadata.source, "npm"); + } + + #[test] + fn assemble_mcp_entry_preserves_pinned_ref() { + let entry = assemble_mcp_entry(sample_input()); + assert_eq!(entry.metadata.pinned_ref.as_deref(), Some("abc123")); + assert_eq!(entry.metadata.track, Some(false)); + } + + #[test] + fn assembled_entry_serializes_to_valid_toml() { + let entry = assemble_mcp_entry(sample_input()); + let toml_str = toml::to_string_pretty(&entry).unwrap(); + let deser: McpEntry = toml::from_str(&toml_str).unwrap(); + assert_eq!(deser.metadata.name, "claude-mem"); + assert_eq!(deser.metadata.capabilities.len(), 2); + } + + #[test] + fn write_registry_entry_creates_files() { + let dir = tempfile::tempdir().unwrap(); + let entry = assemble_mcp_entry(sample_input()); + let report = r#"{"tool": "claude-mem"}"#; + + write_registry_entry(dir.path(), "claude-mem", &entry, report).unwrap(); + + assert!(dir.path().join("mcps/claude-mem.toml").exists()); + assert!(dir.path().join("reports/claude-mem.json").exists()); + + let toml_content = fs::read_to_string(dir.path().join("mcps/claude-mem.toml")).unwrap(); + let loaded: McpEntry = toml::from_str(&toml_content).unwrap(); + assert_eq!(loaded.metadata.name, "claude-mem"); + } +} diff --git a/argus/crates/argus-registry/src/binding.rs b/argus/crates/argus-registry/src/binding.rs new file mode 100644 index 0000000..5146a80 --- /dev/null +++ b/argus/crates/argus-registry/src/binding.rs @@ -0,0 +1,210 @@ +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +use crate::hash::compute_hash; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ToolBinding { + pub name: String, + pub derivation_hash: String, + pub capabilities: HashSet, + pub source_path: PathBuf, + pub mcp_name: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct BindingTable { + tools: HashMap, +} + +impl BindingTable { + pub fn new() -> Self { + Self::default() + } + + pub fn lookup(&self, tool_name: &str) -> Option<&ToolBinding> { + self.tools.get(tool_name) + } + + pub fn register(&mut self, binding: ToolBinding) { + self.tools.insert(binding.name.clone(), binding); + } + + pub fn remove(&mut self, tool_name: &str) { + self.tools.remove(tool_name); + } + + pub fn verify_hash(&self, tool_name: &str, expected_hash: &str) -> bool { + self.tools + .get(tool_name) + .is_some_and(|b| b.derivation_hash == expected_hash) + } + + pub fn iter(&self) -> impl Iterator { + self.tools.values() + } + + pub fn len(&self) -> usize { + self.tools.len() + } + + pub fn is_empty(&self) -> bool { + self.tools.is_empty() + } + + pub fn verify_and_remove_stale(&mut self) -> Vec { + let mut stale = Vec::new(); + self.tools.retain(|name, binding| { + let hash_valid = + compute_hash(&binding.source_path).is_ok_and(|h| h == binding.derivation_hash); + if !hash_valid { + tracing::warn!( + target: "argus::registry", + tool = %name, + "Binding hash mismatch -- removing stale binding" + ); + stale.push(name.clone()); + } + hash_valid + }); + stale + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tool_binding_can_be_created() { + let binding = ToolBinding { + name: "read_file".to_string(), + derivation_hash: "sha256-abc123".to_string(), + capabilities: ["filesystem:read".to_string()].into_iter().collect(), + source_path: PathBuf::from("/store/abc-mcp"), + mcp_name: None, + }; + assert_eq!(binding.name, "read_file"); + } + + #[test] + fn binding_table_lookup_returns_none_for_unknown() { + let table = BindingTable::new(); + assert!(table.lookup("nonexistent").is_none()); + } + + #[test] + fn binding_table_register_and_lookup() { + let mut table = BindingTable::new(); + let binding = ToolBinding { + name: "read_file".to_string(), + derivation_hash: "sha256-abc123".to_string(), + capabilities: ["filesystem:read".to_string()].into_iter().collect(), + source_path: PathBuf::from("/store/abc-mcp"), + mcp_name: None, + }; + table.register(binding); + + let found = table.lookup("read_file").unwrap(); + assert_eq!(found.derivation_hash, "sha256-abc123"); + } + + #[test] + fn binding_table_remove() { + let mut table = BindingTable::new(); + let binding = ToolBinding { + name: "read_file".to_string(), + derivation_hash: "sha256-abc123".to_string(), + capabilities: HashSet::new(), + source_path: PathBuf::from("/store/abc-mcp"), + mcp_name: None, + }; + table.register(binding); + assert!(table.lookup("read_file").is_some()); + + table.remove("read_file"); + assert!(table.lookup("read_file").is_none()); + } + + #[test] + fn verify_and_remove_stale_keeps_valid_bindings() { + let temp = tempfile::TempDir::new().unwrap(); + let tool_dir = temp.path().join("my-tool"); + std::fs::create_dir(&tool_dir).unwrap(); + std::fs::write(tool_dir.join("main.js"), "console.log('hi')").unwrap(); + + let hash = compute_hash(&tool_dir).unwrap(); + + let mut table = BindingTable::new(); + table.register(ToolBinding { + name: "my-tool".to_string(), + derivation_hash: hash, + capabilities: HashSet::new(), + source_path: tool_dir, + mcp_name: None, + }); + + let removed = table.verify_and_remove_stale(); + assert_eq!(removed.len(), 0); + assert!(table.lookup("my-tool").is_some()); + } + + #[test] + fn verify_and_remove_stale_removes_tampered_bindings() { + let temp = tempfile::TempDir::new().unwrap(); + let tool_dir = temp.path().join("tampered-tool"); + std::fs::create_dir(&tool_dir).unwrap(); + std::fs::write(tool_dir.join("main.js"), "original").unwrap(); + + let hash = compute_hash(&tool_dir).unwrap(); + + std::fs::write(tool_dir.join("main.js"), "malicious").unwrap(); + + let mut table = BindingTable::new(); + table.register(ToolBinding { + name: "tampered-tool".to_string(), + derivation_hash: hash, + capabilities: HashSet::new(), + source_path: tool_dir, + mcp_name: None, + }); + + let removed = table.verify_and_remove_stale(); + assert_eq!(removed.len(), 1); + assert_eq!(removed[0], "tampered-tool"); + assert!(table.lookup("tampered-tool").is_none()); + } + + #[test] + fn verify_and_remove_stale_removes_missing_path() { + let mut table = BindingTable::new(); + table.register(ToolBinding { + name: "gone-tool".to_string(), + derivation_hash: "sha256:abc".to_string(), + capabilities: HashSet::new(), + source_path: PathBuf::from("/nonexistent/path"), + mcp_name: None, + }); + + let removed = table.verify_and_remove_stale(); + assert_eq!(removed.len(), 1); + assert!(table.lookup("gone-tool").is_none()); + } + + #[test] + fn binding_table_verify_hash() { + let mut table = BindingTable::new(); + let binding = ToolBinding { + name: "read_file".to_string(), + derivation_hash: "sha256-abc123".to_string(), + capabilities: HashSet::new(), + source_path: PathBuf::from("/store/abc-mcp"), + mcp_name: None, + }; + table.register(binding); + + assert!(table.verify_hash("read_file", "sha256-abc123")); + assert!(!table.verify_hash("read_file", "sha256-wrong")); + assert!(!table.verify_hash("nonexistent", "sha256-abc123")); + } +} diff --git a/argus/crates/argus-registry/src/hash.rs b/argus/crates/argus-registry/src/hash.rs new file mode 100644 index 0000000..2c49c39 --- /dev/null +++ b/argus/crates/argus-registry/src/hash.rs @@ -0,0 +1,150 @@ +use std::collections::BTreeMap; +use std::path::Path; + +use sha2::{Digest, Sha256}; + +pub fn compute_hash(path: &Path) -> Result { + compute_hash_excluding(path, &[]) +} + +pub fn compute_hash_excluding( + path: &Path, + exclude_names: &[&str], +) -> Result { + let mut hasher = Sha256::new(); + let mut entries: BTreeMap> = BTreeMap::new(); + + collect_files_for_hash(path, path, exclude_names, &mut entries)?; + + for (rel_path, content) in entries { + hasher.update(rel_path.as_bytes()); + hasher.update(b"\0"); + hasher.update((content.len() as u64).to_le_bytes()); + hasher.update(&content); + } + + let result = hasher.finalize(); + Ok(format!("sha256:{}", hex::encode(result))) +} + +fn collect_files_for_hash( + base: &Path, + dir: &Path, + exclude_names: &[&str], + entries: &mut BTreeMap>, +) -> Result<(), std::io::Error> { + if !dir.is_dir() { + if dir.is_file() { + insert_file_entry(base, dir, entries)?; + } + return Ok(()); + } + + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path + .file_name() + .is_some_and(|n| exclude_names.iter().any(|ex| *ex == n)) + { + continue; + } + + if entry.metadata()?.file_type().is_symlink() { + let target = std::fs::read_link(&path) + .map(|t| t.to_string_lossy().into_owned()) + .unwrap_or_default(); + let rel = path + .strip_prefix(base) + .unwrap_or(&path) + .to_string_lossy() + .into_owned(); + entries.insert(rel, format!("symlink:{target}").into_bytes()); + continue; + } + + if path.is_dir() { + collect_files_for_hash(base, &path, exclude_names, entries)?; + } else if path.is_file() { + insert_file_entry(base, &path, entries)?; + } + } + + Ok(()) +} + +fn insert_file_entry( + base: &Path, + file: &Path, + entries: &mut BTreeMap>, +) -> Result<(), std::io::Error> { + let rel_path = file + .strip_prefix(base) + .unwrap_or(file) + .to_string_lossy() + .into_owned(); + let content = std::fs::read(file)?; + entries.insert(rel_path, content); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn compute_hash_consistent() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("file.txt"), "content").unwrap(); + + let hash1 = compute_hash(temp.path()).unwrap(); + let hash2 = compute_hash(temp.path()).unwrap(); + + assert_eq!(hash1, hash2); + assert!(hash1.starts_with("sha256:")); + } + + #[test] + fn compute_hash_changes_with_content() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("file.txt"), "content1").unwrap(); + let hash1 = compute_hash(temp.path()).unwrap(); + + std::fs::write(temp.path().join("file.txt"), "content2").unwrap(); + let hash2 = compute_hash(temp.path()).unwrap(); + + assert_ne!(hash1, hash2); + } + + #[test] + fn compute_hash_empty_dir() { + let temp = TempDir::new().unwrap(); + let hash = compute_hash(temp.path()).unwrap(); + assert!(hash.starts_with("sha256:")); + } + + #[cfg(unix)] + #[test] + fn compute_hash_includes_symlinks() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("real.txt"), "content").unwrap(); + + let hash_before = compute_hash(temp.path()).unwrap(); + + let external = TempDir::new().unwrap(); + std::fs::write(external.path().join("secret.txt"), "secret").unwrap(); + std::os::unix::fs::symlink( + external.path().join("secret.txt"), + temp.path().join("link.txt"), + ) + .unwrap(); + + let hash_after = compute_hash(temp.path()).unwrap(); + assert_ne!( + hash_before, hash_after, + "Hash should change when symlink is added" + ); + } +} diff --git a/argus/crates/argus-registry/src/installer.rs b/argus/crates/argus-registry/src/installer.rs new file mode 100644 index 0000000..4b1ca09 --- /dev/null +++ b/argus/crates/argus-registry/src/installer.rs @@ -0,0 +1,131 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ItemKind { + Mcp, + Skill, +} + +impl ItemKind { + pub fn as_str(self) -> &'static str { + match self { + Self::Mcp => "mcp", + Self::Skill => "skill", + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstalledItem { + pub name: String, + pub kind: ItemKind, + pub derivation_hash: String, + pub path: PathBuf, + pub tools: Vec, + pub installed_at: String, +} + +#[derive(Error, Debug)] +pub enum InstallerError { + #[error("{kind:?} '{name}' not found in registry")] + NotFound { kind: ItemKind, name: String }, + #[error("Already installed: {0}")] + AlreadyInstalled(String), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Failed to parse manifest: {0}")] + ManifestParse(#[from] serde_json::Error), +} + +pub struct Installer { + mcps_dir: PathBuf, + skills_dir: PathBuf, +} + +impl Installer { + pub fn new(mcps_dir: PathBuf, skills_dir: PathBuf) -> Self { + Self { + mcps_dir, + skills_dir, + } + } + + pub fn list_installed(&self, kind: ItemKind) -> Result, InstallerError> { + let dir = match kind { + ItemKind::Mcp => &self.mcps_dir, + ItemKind::Skill => &self.skills_dir, + }; + + if !dir.exists() { + return Ok(vec![]); + } + + let mut items = vec![]; + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let manifest_path = entry.path().join("manifest.json"); + if manifest_path.exists() { + let content = std::fs::read_to_string(&manifest_path)?; + let item: InstalledItem = serde_json::from_str(&content)?; + items.push(item); + } + } + Ok(items) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn installer_can_be_created() { + let temp = TempDir::new().unwrap(); + let installer = Installer::new(temp.path().join("mcps"), temp.path().join("skills")); + assert!(installer.list_installed(ItemKind::Mcp).unwrap().is_empty()); + } + + #[test] + fn item_kind_serializes_lowercase() { + assert_eq!(serde_json::to_string(&ItemKind::Mcp).unwrap(), "\"mcp\""); + assert_eq!( + serde_json::to_string(&ItemKind::Skill).unwrap(), + "\"skill\"" + ); + } + + #[test] + fn item_kind_as_str() { + assert_eq!(ItemKind::Mcp.as_str(), "mcp"); + assert_eq!(ItemKind::Skill.as_str(), "skill"); + } + + #[test] + fn installed_item_can_be_serialized() { + let item = InstalledItem { + name: "stripe".to_string(), + kind: ItemKind::Mcp, + derivation_hash: "sha256-abc123".to_string(), + path: PathBuf::from("/store/abc-stripe"), + tools: vec!["create_payment".to_string()], + installed_at: "2026-02-03T12:00:00Z".to_string(), + }; + let json = serde_json::to_string(&item).unwrap(); + assert!(json.contains("\"name\":\"stripe\"")); + assert!(json.contains("\"kind\":\"mcp\"")); + } + + #[test] + fn installer_error_display() { + let err = InstallerError::NotFound { + kind: ItemKind::Mcp, + name: "stripe".to_string(), + }; + assert!(err.to_string().contains("stripe")); + assert!(err.to_string().contains("Mcp")); + } +} diff --git a/argus/crates/argus-registry/src/lib.rs b/argus/crates/argus-registry/src/lib.rs new file mode 100644 index 0000000..7523588 --- /dev/null +++ b/argus/crates/argus-registry/src/lib.rs @@ -0,0 +1,254 @@ +mod argus_db; +mod assembler; +mod binding; +mod hash; +mod installer; +mod policy_attestation; +mod quarantine; +pub mod resolver; +mod sandbox_gen; +pub mod sync; +pub mod toml_loader; +pub mod toml_types; +mod trust_event; +mod types; +mod watcher; + +pub use argus_db::ArgusDb; +pub use assembler::{AssemblyInput, assemble_mcp_entry, write_registry_entry}; +pub use binding::{BindingTable, ToolBinding}; +pub use hash::compute_hash; +pub use installer::{InstalledItem, Installer, InstallerError, ItemKind}; +pub use policy_attestation::{ + PolicyAttestation, PolicyAttestationStore, PolicyEventType, compute_content_hash, +}; +pub use quarantine::{QuarantineError, QuarantineManager, QuarantineReason, QuarantinedItem}; +pub use sandbox_gen::{ + ParsedCapability, extract_env_defaults, generate_env_declarations, generate_sandbox_config, + parse_capability, required_env_keys, +}; +pub use toml_types::{SidecarValidationError, validate_sidecars}; +pub use trust_event::{TrustEvent, TrustEventStore, TrustEventType}; +pub use watcher::{FolderWatcher, WatchEvent, WatcherError}; + +pub use types::*; + +use std::collections::HashMap; +use std::path::Path; +use thiserror::Error; +use toml_loader::{TomlRegistry, TomlRegistryError}; + +#[derive(Error, Debug)] +pub enum RegistryError { + #[error("Registry error: {0}")] + Toml(#[from] TomlRegistryError), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Registry not synced. Run 'argus registry sync' first.")] + NotSynced, +} + +#[derive(Default)] +pub struct Registry { + tools: HashMap, + synced: bool, +} + +impl Registry { + pub fn load(argus_home: &Path) -> Result { + let local_dir = argus_home.join("local-registry"); + + if !local_dir.is_dir() { + return Ok(Self { + tools: HashMap::new(), + synced: false, + }); + } + + let toml_registry = TomlRegistry::load_from_dir(&local_dir)?; + let tools = toml_registry_to_tools(&toml_registry); + + Ok(Self { + tools, + synced: true, + }) + } + + pub fn lookup(&self, name: &str) -> Option<&RegistryEntry> { + self.tools.get(name) + } + + pub fn is_synced(&self) -> bool { + self.synced + } + + pub fn tool_count(&self) -> usize { + self.tools.len() + } + + pub fn reload(&mut self, argus_home: &Path) -> Result<(), RegistryError> { + let new = Self::load(argus_home)?; + *self = new; + Ok(()) + } +} + +fn toml_registry_to_tools(registry: &TomlRegistry) -> HashMap { + let mut tools = HashMap::new(); + + for (name, mcp) in ®istry.mcps { + tools.insert( + name.clone(), + RegistryEntry { + capabilities: mcp.metadata.capabilities.clone(), + schema_hash: mcp.metadata.schema_hash.clone(), + }, + ); + } + + tools +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn registry_loads_empty_when_no_local_dir() { + let temp = TempDir::new().unwrap(); + let registry = Registry::load(temp.path()).unwrap(); + assert!(!registry.is_synced()); + assert_eq!(registry.tool_count(), 0); + } + + #[test] + fn registry_loads_from_toml() { + let temp = TempDir::new().unwrap(); + let local_dir = temp.path().join("local-registry"); + let mcps_dir = local_dir.join("mcps"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + std::fs::write( + mcps_dir.join("test.toml"), + r#" +[metadata] +name = "test" +description = "Test MCP" +source = "local" +integrity = "untrusted" +capabilities = ["filesystem:read"] + +[install] +type = "npm" +package = "test" +version = "1.0.0" +"#, + ) + .unwrap(); + + let registry = Registry::load(temp.path()).unwrap(); + assert!(registry.is_synced()); + assert_eq!(registry.tool_count(), 1); + + let entry = registry.lookup("test").unwrap(); + assert!(entry.capabilities.contains(&"filesystem:read".to_string())); + } + + #[test] + fn registry_default_is_empty() { + let reg = Registry::default(); + assert!(!reg.is_synced()); + assert_eq!(reg.tool_count(), 0); + } + + #[test] + fn registry_reload_picks_up_new_entries() { + let temp = TempDir::new().unwrap(); + let mcps_dir = temp.path().join("local-registry").join("mcps"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let mut registry = Registry::load(temp.path()).unwrap(); + assert_eq!(registry.tool_count(), 0); + + std::fs::write( + mcps_dir.join("test_tool.toml"), + r#" +[metadata] +name = "test-tool" +description = "A test tool" +source = "local" +integrity = "untrusted" +capabilities = ["filesystem:read"] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" +"#, + ) + .unwrap(); + + registry.reload(temp.path()).unwrap(); + assert_eq!(registry.tool_count(), 1); + assert!(registry.lookup("test_tool").is_some()); + } + + #[test] + fn registry_stores_all_capabilities() { + let temp = TempDir::new().unwrap(); + let mcps_dir = temp.path().join("local-registry").join("mcps"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + std::fs::write( + mcps_dir.join("multi_caps.toml"), + r#" +[metadata] +name = "multi-caps" +description = "MCP with multiple capabilities" +source = "local" +integrity = "untrusted" +capabilities = ["filesystem:read", "network:egress", "execution:shell"] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" +"#, + ) + .unwrap(); + + let registry = Registry::load(temp.path()).unwrap(); + let entry = registry.lookup("multi_caps").unwrap(); + assert_eq!(entry.capabilities.len(), 3); + } + + #[test] + fn registry_accepts_scoped_capabilities() { + let temp = TempDir::new().unwrap(); + let mcps_dir = temp.path().join("local-registry").join("mcps"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + std::fs::write( + mcps_dir.join("scoped.toml"), + r#" +[metadata] +name = "scoped-caps" +description = "MCP with scoped capabilities" +source = "local" +integrity = "observed" +capabilities = ["filesystem:read(/tmp/**)", "network:egress(api.github.com)", "execution:shell(git, ls)"] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" +"#, + ) + .unwrap(); + + let registry = Registry::load(temp.path()).unwrap(); + let entry = registry.lookup("scoped").unwrap(); + assert_eq!(entry.capabilities.len(), 3); + } +} diff --git a/argus/crates/argus-registry/src/policy_attestation.rs b/argus/crates/argus-registry/src/policy_attestation.rs new file mode 100644 index 0000000..47d7b5b --- /dev/null +++ b/argus/crates/argus-registry/src/policy_attestation.rs @@ -0,0 +1,287 @@ +use std::collections::HashMap; +use std::path::Path; +use std::str::FromStr; +use std::sync::Mutex; + +use rusqlite::{Connection, params}; + +use sha2::{Digest, Sha256}; + +use crate::trust_event::TrustEvent; + +#[derive(Debug, Clone, PartialEq)] +pub enum PolicyEventType { + Loaded, + Reloaded, + Rejected, +} + +impl PolicyEventType { + pub fn as_str(&self) -> &'static str { + match self { + Self::Loaded => "loaded", + Self::Reloaded => "reloaded", + Self::Rejected => "rejected", + } + } +} + +impl FromStr for PolicyEventType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "loaded" => Ok(Self::Loaded), + "reloaded" => Ok(Self::Reloaded), + "rejected" => Ok(Self::Rejected), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone)] +pub struct PolicyAttestation { + pub id: String, + pub timestamp: String, + pub policy_file: String, + pub content_hash: String, + pub signature: Option, + pub key_id: Option, + pub actor: String, + pub event_type: PolicyEventType, +} + +const POLICY_ATTESTATION_SCHEMA: &str = r#" +CREATE TABLE IF NOT EXISTS policy_attestations ( + id TEXT PRIMARY KEY, + timestamp TEXT NOT NULL, + policy_file TEXT NOT NULL, + content_hash TEXT NOT NULL, + signature TEXT, + key_id TEXT, + actor TEXT NOT NULL, + event_type TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_policy_attestations_file + ON policy_attestations(policy_file, timestamp); +"#; + +pub struct PolicyAttestationStore { + conn: Mutex, +} + +impl PolicyAttestationStore { + pub fn new(db_path: &Path) -> Result { + if let Some(parent) = db_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let conn = Connection::open(db_path)?; + Self::from_connection(conn) + } + + pub fn in_memory() -> Result { + let conn = Connection::open_in_memory()?; + Self::from_connection(conn) + } + + fn from_connection(conn: Connection) -> Result { + conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")?; + conn.execute_batch(POLICY_ATTESTATION_SCHEMA)?; + Ok(Self { + conn: Mutex::new(conn), + }) + } + + pub fn record(&self, attestation: &PolicyAttestation) -> Result<(), rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + conn.execute( + r#" + INSERT INTO policy_attestations + (id, timestamp, policy_file, content_hash, signature, + key_id, actor, event_type) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) + "#, + params![ + attestation.id, + attestation.timestamp, + attestation.policy_file, + attestation.content_hash, + attestation.signature, + attestation.key_id, + attestation.actor, + attestation.event_type.as_str(), + ], + )?; + Ok(()) + } + + pub fn latest_hashes(&self) -> Result, rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare( + r#" + SELECT pa.policy_file, pa.content_hash + FROM policy_attestations pa + INNER JOIN ( + SELECT policy_file, MAX(rowid) as max_rowid + FROM policy_attestations + WHERE event_type != 'rejected' + GROUP BY policy_file + ) latest ON pa.policy_file = latest.policy_file AND pa.rowid = latest.max_rowid + "#, + )?; + let rows = stmt.query_map([], |row| { + let file: String = row.get(0)?; + let hash: String = row.get(1)?; + Ok((file, hash)) + })?; + let mut map = HashMap::new(); + for row in rows { + let (file, hash) = row?; + map.insert(file, hash); + } + Ok(map) + } + + pub fn history_for_file( + &self, + policy_file: &str, + ) -> Result, rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare( + "SELECT * FROM policy_attestations WHERE policy_file = ?1 ORDER BY timestamp ASC", + )?; + let rows = stmt.query_map(params![policy_file], Self::row_to_attestation)?; + rows.collect() + } + + fn row_to_attestation(row: &rusqlite::Row) -> Result { + let event_type_str: String = row.get("event_type")?; + Ok(PolicyAttestation { + id: row.get("id")?, + timestamp: row.get("timestamp")?, + policy_file: row.get("policy_file")?, + content_hash: row.get("content_hash")?, + signature: row.get("signature")?, + key_id: row.get("key_id")?, + actor: row.get("actor")?, + event_type: PolicyEventType::from_str(&event_type_str) + .unwrap_or(PolicyEventType::Rejected), + }) + } +} + +pub fn compute_content_hash(content: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(content.as_bytes()); + format!("sha256:{}", hex::encode(hasher.finalize())) +} + +impl PolicyAttestation { + pub fn new_loaded(policy_file: &str, content_hash: &str, actor: &str) -> Self { + Self { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + policy_file: policy_file.to_string(), + content_hash: content_hash.to_string(), + signature: None, + key_id: None, + actor: actor.to_string(), + event_type: PolicyEventType::Loaded, + } + } + + pub fn new_reloaded(policy_file: &str, content_hash: &str, actor: &str) -> Self { + Self { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + policy_file: policy_file.to_string(), + content_hash: content_hash.to_string(), + signature: None, + key_id: None, + actor: actor.to_string(), + event_type: PolicyEventType::Reloaded, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn record_and_query_attestation() { + let store = PolicyAttestationStore::in_memory().unwrap(); + let att = PolicyAttestation::new_loaded("default.cedar", "sha256:abc", "daemon"); + store.record(&att).unwrap(); + + let history = store.history_for_file("default.cedar").unwrap(); + assert_eq!(history.len(), 1); + assert_eq!(history[0].policy_file, "default.cedar"); + assert_eq!(history[0].content_hash, "sha256:abc"); + assert_eq!(history[0].event_type, PolicyEventType::Loaded); + } + + #[test] + fn latest_hashes_returns_most_recent() { + let store = PolicyAttestationStore::in_memory().unwrap(); + + store + .record(&PolicyAttestation::new_loaded( + "a.cedar", + "sha256:v1", + "daemon", + )) + .unwrap(); + store + .record(&PolicyAttestation::new_reloaded( + "a.cedar", + "sha256:v2", + "daemon", + )) + .unwrap(); + store + .record(&PolicyAttestation::new_loaded( + "b.cedar", + "sha256:bbb", + "daemon", + )) + .unwrap(); + + let hashes = store.latest_hashes().unwrap(); + assert_eq!(hashes.len(), 2); + assert_eq!(hashes["a.cedar"], "sha256:v2"); + assert_eq!(hashes["b.cedar"], "sha256:bbb"); + } + + #[test] + fn history_for_file_chronological() { + let store = PolicyAttestationStore::in_memory().unwrap(); + + let mut a1 = PolicyAttestation::new_loaded("a.cedar", "sha256:v1", "daemon"); + a1.timestamp = "2026-01-01T00:00:00Z".to_string(); + store.record(&a1).unwrap(); + + let mut a2 = PolicyAttestation::new_reloaded("a.cedar", "sha256:v2", "daemon"); + a2.timestamp = "2026-01-02T00:00:00Z".to_string(); + store.record(&a2).unwrap(); + + let history = store.history_for_file("a.cedar").unwrap(); + assert_eq!(history.len(), 2); + assert_eq!(history[0].event_type, PolicyEventType::Loaded); + assert_eq!(history[1].event_type, PolicyEventType::Reloaded); + } + + #[test] + fn event_type_roundtrip() { + for event_type in [ + PolicyEventType::Loaded, + PolicyEventType::Reloaded, + PolicyEventType::Rejected, + ] { + let s = event_type.as_str(); + let parsed = PolicyEventType::from_str(s).unwrap(); + assert_eq!(parsed, event_type); + } + } +} diff --git a/argus/crates/argus-registry/src/quarantine.rs b/argus/crates/argus-registry/src/quarantine.rs new file mode 100644 index 0000000..fcaace4 --- /dev/null +++ b/argus/crates/argus-registry/src/quarantine.rs @@ -0,0 +1,359 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use thiserror::Error; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum QuarantineReason { + UnauthorizedAddition, + HashMismatch { expected: String, actual: String }, + ManualQuarantine { reason: String }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QuarantinedItem { + pub name: String, + pub original_path: PathBuf, + pub quarantine_path: PathBuf, + pub quarantined_at: DateTime, + pub reason: QuarantineReason, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash_at_quarantine: Option, +} + +#[derive(Error, Debug)] +pub enum QuarantineError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Failed to serialize metadata: {0}")] + Serialize(#[from] serde_json::Error), + #[error("Item not found: {0}")] + NotFound(String), + #[error("Item already exists in quarantine: {0}")] + AlreadyExists(String), + #[error( + "Integrity violation: item '{name}' was modified while in quarantine (expected {expected}, got {actual})" + )] + IntegrityViolation { + name: String, + expected: String, + actual: String, + }, +} + +pub struct QuarantineManager { + pub quarantine_dir: PathBuf, +} + +impl QuarantineManager { + pub fn new(quarantine_dir: &Path) -> Result { + std::fs::create_dir_all(quarantine_dir)?; + Ok(Self { + quarantine_dir: quarantine_dir.to_path_buf(), + }) + } + + pub fn quarantine( + &self, + source_path: &Path, + reason: QuarantineReason, + ) -> Result { + let name = source_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| { + QuarantineError::Io(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid source path", + )) + })? + .to_string(); + + let dest_path = self.quarantine_dir.join(&name); + + if dest_path.exists() { + return Err(QuarantineError::AlreadyExists(name)); + } + + let hash_at_quarantine = crate::compute_hash(source_path).ok(); + + std::fs::rename(source_path, &dest_path)?; + + let item = QuarantinedItem { + name: name.clone(), + original_path: source_path.to_path_buf(), + quarantine_path: dest_path, + quarantined_at: Utc::now(), + reason, + hash_at_quarantine, + }; + + let meta_path = self.meta_path(&name); + let meta_json = serde_json::to_string_pretty(&item)?; + std::fs::write(&meta_path, meta_json)?; + + Ok(item) + } + + pub fn list_quarantined(&self) -> Result, QuarantineError> { + let mut items = Vec::new(); + + for entry in std::fs::read_dir(&self.quarantine_dir)? { + let path = entry?.path(); + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + + if name.ends_with(".meta.json") { + let content = std::fs::read_to_string(&path)?; + let item: QuarantinedItem = serde_json::from_str(&content)?; + items.push(item); + } + } + + items.sort_by(|a, b| b.quarantined_at.cmp(&a.quarantined_at)); + Ok(items) + } + + pub fn restore_verified(&self, name: &str) -> Result { + let item = self.load_metadata(name)?; + + if let Some(expected_hash) = &item.hash_at_quarantine { + let current_hash = + crate::compute_hash(&item.quarantine_path).map_err(QuarantineError::Io)?; + if ¤t_hash != expected_hash { + return Err(QuarantineError::IntegrityViolation { + name: name.to_string(), + expected: expected_hash.clone(), + actual: current_hash, + }); + } + } else { + tracing::warn!( + target: "argus::quarantine", + name = %name, + "Restoring item without integrity verification (no hash recorded at quarantine time)" + ); + } + + std::fs::rename(&item.quarantine_path, &item.original_path)?; + std::fs::remove_file(self.meta_path(name))?; + + Ok(item.original_path) + } + + pub fn delete(&self, name: &str) -> Result<(), QuarantineError> { + let item_path = self.quarantine_dir.join(name); + self.load_metadata(name)?; + + if item_path.is_dir() { + std::fs::remove_dir_all(&item_path)?; + } else if item_path.exists() { + std::fs::remove_file(&item_path)?; + } + + std::fs::remove_file(self.meta_path(name))?; + + Ok(()) + } + + fn meta_path(&self, name: &str) -> PathBuf { + self.quarantine_dir.join(format!("{name}.meta.json")) + } + + fn load_metadata(&self, name: &str) -> Result { + let meta_path = self.meta_path(name); + + if !meta_path.exists() { + return Err(QuarantineError::NotFound(name.to_string())); + } + + let content = std::fs::read_to_string(&meta_path)?; + Ok(serde_json::from_str(&content)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quarantine_reason_serializes_correctly() { + let reason = QuarantineReason::UnauthorizedAddition; + let json = serde_json::to_string(&reason).unwrap(); + assert_eq!(json, "\"unauthorized_addition\""); + } + + #[test] + fn quarantine_reason_hash_mismatch_serializes() { + let reason = QuarantineReason::HashMismatch { + expected: "sha256-abc".to_string(), + actual: "sha256-xyz".to_string(), + }; + let json = serde_json::to_string(&reason).unwrap(); + assert!(json.contains("hash_mismatch")); + assert!(json.contains("sha256-abc")); + } + + #[test] + fn quarantined_item_can_be_created() { + let item = QuarantinedItem { + name: "suspicious-mcp".to_string(), + original_path: PathBuf::from("/home/user/.argus/mcps/suspicious-mcp"), + quarantine_path: PathBuf::from("/home/user/.argus/quarantine/suspicious-mcp"), + quarantined_at: Utc::now(), + reason: QuarantineReason::UnauthorizedAddition, + hash_at_quarantine: None, + }; + assert_eq!(item.name, "suspicious-mcp"); + } + + #[test] + fn quarantine_manager_creates_directory() { + let temp = tempfile::TempDir::new().unwrap(); + let quarantine_dir = temp.path().join("quarantine"); + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + assert!(quarantine_dir.exists()); + assert_eq!(manager.quarantine_dir, quarantine_dir); + } + + #[test] + fn quarantine_moves_item_and_creates_metadata() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item_path = mcps_dir.join("bad-mcp"); + std::fs::create_dir(&item_path).unwrap(); + std::fs::write(item_path.join("manifest.json"), "{}").unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + let result = manager + .quarantine(&item_path, QuarantineReason::UnauthorizedAddition) + .unwrap(); + + assert!(!item_path.exists()); + assert!(result.quarantine_path.exists()); + assert!(quarantine_dir.join("bad-mcp.meta.json").exists()); + } + + #[test] + fn list_quarantined_returns_all_items() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item1 = mcps_dir.join("mcp1"); + let item2 = mcps_dir.join("mcp2"); + std::fs::create_dir(&item1).unwrap(); + std::fs::create_dir(&item2).unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + manager + .quarantine(&item1, QuarantineReason::UnauthorizedAddition) + .unwrap(); + manager + .quarantine( + &item2, + QuarantineReason::HashMismatch { + expected: "a".to_string(), + actual: "b".to_string(), + }, + ) + .unwrap(); + + let items = manager.list_quarantined().unwrap(); + assert_eq!(items.len(), 2); + } + + #[test] + fn restore_verified_succeeds_when_unmodified() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item_path = mcps_dir.join("mcp1"); + std::fs::create_dir(&item_path).unwrap(); + std::fs::write(item_path.join("manifest.json"), r#"{"name":"mcp1"}"#).unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + manager + .quarantine(&item_path, QuarantineReason::UnauthorizedAddition) + .unwrap(); + + let restored = manager.restore_verified("mcp1").unwrap(); + assert!(restored.exists()); + } + + #[test] + fn restore_verified_rejects_when_modified() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item_path = mcps_dir.join("mcp2"); + std::fs::create_dir(&item_path).unwrap(); + std::fs::write(item_path.join("manifest.json"), r#"{"name":"mcp2"}"#).unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + manager + .quarantine(&item_path, QuarantineReason::UnauthorizedAddition) + .unwrap(); + + std::fs::write( + quarantine_dir.join("mcp2").join("manifest.json"), + r#"{"name":"evil"}"#, + ) + .unwrap(); + + let result = manager.restore_verified("mcp2"); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, QuarantineError::IntegrityViolation { .. })); + } + + #[test] + fn quarantine_stores_hash() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item_path = mcps_dir.join("mcp3"); + std::fs::create_dir(&item_path).unwrap(); + std::fs::write(item_path.join("file.txt"), "content").unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + let item = manager + .quarantine(&item_path, QuarantineReason::UnauthorizedAddition) + .unwrap(); + assert!(item.hash_at_quarantine.is_some()); + assert!(item.hash_at_quarantine.unwrap().starts_with("sha256:")); + } + + #[test] + fn delete_removes_quarantined_item_permanently() { + let temp = tempfile::TempDir::new().unwrap(); + let mcps_dir = temp.path().join("mcps"); + let quarantine_dir = temp.path().join("quarantine"); + std::fs::create_dir_all(&mcps_dir).unwrap(); + + let item_path = mcps_dir.join("mcp1"); + std::fs::create_dir(&item_path).unwrap(); + + let manager = QuarantineManager::new(&quarantine_dir).unwrap(); + manager + .quarantine(&item_path, QuarantineReason::UnauthorizedAddition) + .unwrap(); + + manager.delete("mcp1").unwrap(); + + assert!(!quarantine_dir.join("mcp1").exists()); + assert!(!quarantine_dir.join("mcp1.meta.json").exists()); + } +} diff --git a/argus/crates/argus-registry/src/resolver.rs b/argus/crates/argus-registry/src/resolver.rs new file mode 100644 index 0000000..6965a99 --- /dev/null +++ b/argus/crates/argus-registry/src/resolver.rs @@ -0,0 +1,84 @@ +use std::path::Path; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProjectType { + Npm, + Pip, + Cargo, + Docker, + Unknown, +} + +pub fn detect_project_type(path: &Path) -> ProjectType { + if path.join("package.json").exists() { + return ProjectType::Npm; + } + if path.join("pyproject.toml").exists() + || path.join("setup.py").exists() + || path.join("setup.cfg").exists() + { + return ProjectType::Pip; + } + if path.join("Cargo.toml").exists() { + return ProjectType::Cargo; + } + if path.join("Dockerfile").exists() { + return ProjectType::Docker; + } + ProjectType::Unknown +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn detect_npm_from_package_json() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("package.json"), "{}").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Npm); + } + + #[test] + fn detect_pip_from_pyproject() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("pyproject.toml"), "").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Pip); + } + + #[test] + fn detect_pip_from_setup_py() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("setup.py"), "").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Pip); + } + + #[test] + fn detect_cargo_from_cargo_toml() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("Cargo.toml"), "").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Cargo); + } + + #[test] + fn detect_docker_from_dockerfile() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("Dockerfile"), "").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Docker); + } + + #[test] + fn detect_unknown_for_empty_dir() { + let tmp = TempDir::new().unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Unknown); + } + + #[test] + fn npm_takes_priority_over_docker() { + let tmp = TempDir::new().unwrap(); + std::fs::write(tmp.path().join("package.json"), "{}").unwrap(); + std::fs::write(tmp.path().join("Dockerfile"), "").unwrap(); + assert_eq!(detect_project_type(tmp.path()), ProjectType::Npm); + } +} diff --git a/argus/crates/argus-registry/src/sandbox_gen.rs b/argus/crates/argus-registry/src/sandbox_gen.rs new file mode 100644 index 0000000..b28d6d5 --- /dev/null +++ b/argus/crates/argus-registry/src/sandbox_gen.rs @@ -0,0 +1,251 @@ +use std::collections::HashMap; + +use crate::toml_types::{InstallEnvVar, SandboxConfig, SandboxNetPolicy}; + +#[derive(Debug, Clone, PartialEq)] +pub enum ParsedCapability { + FilesystemRead(String), + FilesystemWrite(String), + NetworkEgress(String), + NetworkListen(String), + ProcessExec(String), + SecretEnv(String), + Unknown(String), +} + +pub fn parse_capability(s: &str) -> ParsedCapability { + if let Some(scope) = s + .strip_prefix("filesystem:read(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::FilesystemRead(scope.to_string()) + } else if let Some(scope) = s + .strip_prefix("filesystem:write(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::FilesystemWrite(scope.to_string()) + } else if let Some(scope) = s + .strip_prefix("network:egress(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::NetworkEgress(scope.to_string()) + } else if let Some(scope) = s + .strip_prefix("network:listen(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::NetworkListen(scope.to_string()) + } else if let Some(scope) = s + .strip_prefix("process:exec(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::ProcessExec(scope.to_string()) + } else if let Some(scope) = s + .strip_prefix("secret:env(") + .and_then(|s| s.strip_suffix(')')) + { + ParsedCapability::SecretEnv(scope.to_string()) + } else { + ParsedCapability::Unknown(s.to_string()) + } +} + +pub fn generate_sandbox_config(capability_strings: &[String]) -> SandboxConfig { + let mut extra_read = Vec::new(); + let mut extra_write = Vec::new(); + let mut egress_domains = Vec::new(); + + for cap_str in capability_strings { + match parse_capability(cap_str) { + ParsedCapability::FilesystemRead(path) => { + if !extra_read.contains(&path) { + extra_read.push(path); + } + } + ParsedCapability::FilesystemWrite(path) => { + if !extra_write.contains(&path) { + extra_write.push(path.clone()); + } + if !extra_read.contains(&path) { + extra_read.push(path); + } + } + ParsedCapability::NetworkEgress(domain) => { + if !egress_domains.contains(&domain) { + egress_domains.push(domain); + } + } + ParsedCapability::SecretEnv(_) + | ParsedCapability::NetworkListen(_) + | ParsedCapability::ProcessExec(_) + | ParsedCapability::Unknown(_) => {} + } + } + + let net_policy = if egress_domains.is_empty() { + Some(SandboxNetPolicy::Blocked) + } else { + Some(SandboxNetPolicy::EgressDomains(egress_domains)) + }; + + SandboxConfig { + extra_read, + extra_write, + require_sandbox: true, + net_policy, + } +} + +pub fn generate_env_declarations(capability_strings: &[String]) -> HashMap { + let mut env = HashMap::new(); + for cap_str in capability_strings { + if let ParsedCapability::SecretEnv(var) = parse_capability(cap_str) { + env.entry(var.clone()).or_insert_with(|| InstallEnvVar { + required: true, + description: format!("Required by secret:env({var}) capability"), + default: None, + }); + } + } + env +} + +pub fn extract_env_defaults(env: &HashMap) -> HashMap { + env.iter() + .filter_map(|(key, var)| var.default.as_ref().map(|d| (key.clone(), d.clone()))) + .collect() +} + +pub fn required_env_keys(env: &HashMap) -> Vec { + let mut keys: Vec = env + .iter() + .filter(|(_, var)| var.required) + .map(|(key, _)| key.clone()) + .collect(); + keys.sort(); + keys +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_filesystem_read_capability() { + assert_eq!( + parse_capability("filesystem:read(~/.claude-mem/)"), + ParsedCapability::FilesystemRead("~/.claude-mem/".to_string()) + ); + } + + #[test] + fn parse_network_egress_capability() { + assert_eq!( + parse_capability("network:egress(api.anthropic.com)"), + ParsedCapability::NetworkEgress("api.anthropic.com".to_string()) + ); + } + + #[test] + fn parse_secret_env_capability() { + assert_eq!( + parse_capability("secret:env(ANTHROPIC_API_KEY)"), + ParsedCapability::SecretEnv("ANTHROPIC_API_KEY".to_string()) + ); + } + + #[test] + fn parse_unknown_capability() { + assert_eq!( + parse_capability("custom:something"), + ParsedCapability::Unknown("custom:something".to_string()) + ); + } + + #[test] + fn generate_sandbox_from_claude_mem_capabilities() { + let caps = vec![ + "filesystem:read(~/.claude-mem/)".to_string(), + "filesystem:write(~/.claude-mem/)".to_string(), + "filesystem:read(.claude/)".to_string(), + "network:egress(api.anthropic.com)".to_string(), + "secret:env(ANTHROPIC_API_KEY)".to_string(), + ]; + + let config = generate_sandbox_config(&caps); + + assert!(config.extra_read.contains(&"~/.claude-mem/".to_string())); + assert!(config.extra_read.contains(&".claude/".to_string())); + assert!(config.extra_write.contains(&"~/.claude-mem/".to_string())); + assert!(config.require_sandbox); + + match &config.net_policy { + Some(SandboxNetPolicy::EgressDomains(domains)) => { + assert_eq!(domains, &vec!["api.anthropic.com".to_string()]); + } + other => panic!("Expected EgressDomains, got {:?}", other), + } + + let env_decls = generate_env_declarations(&caps); + assert_eq!(env_decls.len(), 1); + assert!(env_decls["ANTHROPIC_API_KEY"].required); + } + + #[test] + fn generate_sandbox_with_no_network_defaults_to_blocked() { + let caps = vec!["filesystem:read(/tmp/)".to_string()]; + let config = generate_sandbox_config(&caps); + assert_eq!(config.net_policy, Some(SandboxNetPolicy::Blocked)); + } + + #[test] + fn write_implies_read() { + let caps = vec!["filesystem:write(/data/)".to_string()]; + let config = generate_sandbox_config(&caps); + assert!(config.extra_read.contains(&"/data/".to_string())); + assert!(config.extra_write.contains(&"/data/".to_string())); + } + + #[test] + fn extract_defaults_from_env_map() { + let mut env = HashMap::new(); + env.insert( + "PORT".into(), + InstallEnvVar { + required: false, + description: "Port".into(), + default: Some("8080".into()), + }, + ); + env.insert( + "API_KEY".into(), + InstallEnvVar { + required: true, + description: "API key".into(), + default: None, + }, + ); + + let defaults = extract_env_defaults(&env); + assert_eq!(defaults.len(), 1); + assert_eq!(defaults["PORT"], "8080"); + + let required = required_env_keys(&env); + assert_eq!(required, vec!["API_KEY"]); + } + + #[test] + fn deduplicates_capabilities() { + let caps = vec![ + "filesystem:read(/tmp/)".to_string(), + "filesystem:read(/tmp/)".to_string(), + "network:egress(example.com)".to_string(), + "network:egress(example.com)".to_string(), + ]; + let config = generate_sandbox_config(&caps); + assert_eq!(config.extra_read.len(), 1); + match &config.net_policy { + Some(SandboxNetPolicy::EgressDomains(d)) => assert_eq!(d.len(), 1), + _ => panic!("Expected EgressDomains"), + } + } +} diff --git a/argus/crates/argus-registry/src/sync.rs b/argus/crates/argus-registry/src/sync.rs new file mode 100644 index 0000000..92c8254 --- /dev/null +++ b/argus/crates/argus-registry/src/sync.rs @@ -0,0 +1,263 @@ +use crate::toml_loader::{TomlRegistry, TomlRegistryError}; +use crate::toml_types::{McpEntry, SkillEntry}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::Path; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RegistrySource { + pub url: String, + pub priority: u32, + pub last_synced: Option>, +} + +pub struct MultiRegistry { + local: TomlRegistry, + external: Vec<(RegistrySource, TomlRegistry)>, +} + +impl MultiRegistry { + pub fn new(local: TomlRegistry) -> Self { + Self { + local, + external: Vec::new(), + } + } + + pub fn add_external(&mut self, source: RegistrySource, registry: TomlRegistry) { + self.external.push((source, registry)); + self.external.sort_by_key(|(s, _)| s.priority); + } + + pub fn resolve_mcp(&self, name: &str) -> Option<&McpEntry> { + if let Some(entry) = self.local.mcps.get(name) { + return Some(entry); + } + for (_, registry) in &self.external { + if let Some(entry) = registry.mcps.get(name) { + return Some(entry); + } + } + None + } + + pub fn resolve_skill(&self, name: &str) -> Option<&SkillEntry> { + if let Some(entry) = self.local.skills.get(name) { + return Some(entry); + } + for (_, registry) in &self.external { + if let Some(entry) = registry.skills.get(name) { + return Some(entry); + } + } + None + } + + pub fn all_mcps(&self) -> HashMap<&str, &McpEntry> { + let mut merged = HashMap::new(); + for (_, registry) in self.external.iter().rev() { + for (name, entry) in ®istry.mcps { + merged.insert(name.as_str(), entry); + } + } + for (name, entry) in &self.local.mcps { + merged.insert(name.as_str(), entry); + } + merged + } + + pub fn all_skills(&self) -> HashMap<&str, &SkillEntry> { + let mut merged = HashMap::new(); + for (_, registry) in self.external.iter().rev() { + for (name, entry) in ®istry.skills { + merged.insert(name.as_str(), entry); + } + } + for (name, entry) in &self.local.skills { + merged.insert(name.as_str(), entry); + } + merged + } + + pub fn sync_external_from_dir( + base: &Path, + source: &RegistrySource, + ) -> Result { + let hash = simple_hash(&source.url); + let dir = TomlRegistry::dir_for_source(base, &hash); + TomlRegistry::load_from_dir(&dir) + } +} + +pub fn simple_hash(input: &str) -> String { + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(input.as_bytes()); + hex::encode(&hash[..16]) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + fn write_fixture(dir: &Path, rel_path: &str, content: &str) { + let full = dir.join(rel_path); + if let Some(parent) = full.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + std::fs::write(full, content).unwrap(); + } + + fn mcp_toml(name: &str, integrity: &str) -> String { + format!( + r#" +[metadata] +name = "{name}" +description = "Test MCP {name}" +source = "community" +integrity = "{integrity}" +capabilities = ["filesystem:read"] + +[install] +type = "npm" +package = "@test/{name}" +version = "1.0.0" +"# + ) + } + + fn skill_toml(name: &str) -> String { + format!( + r#" +[metadata] +name = "{name}" +description = "Test skill {name}" +tools_used = ["read_file"] +"# + ) + } + + fn make_registry(dir: &TempDir, mcps: &[(&str, &str)], skills: &[&str]) -> TomlRegistry { + for (name, integrity) in mcps { + write_fixture( + dir.path(), + &format!("mcps/{name}.toml"), + &mcp_toml(name, integrity), + ); + } + for name in skills { + write_fixture( + dir.path(), + &format!("skills/{name}.toml"), + &skill_toml(name), + ); + } + TomlRegistry::load_from_dir(dir.path()).unwrap() + } + + #[test] + fn local_wins_over_external() { + let local_dir = TempDir::new().unwrap(); + let ext_dir = TempDir::new().unwrap(); + + let local = make_registry(&local_dir, &[("test-mcp", "untrusted")], &[]); + let external = make_registry(&ext_dir, &[("test-mcp", "verified")], &[]); + + let mut multi = MultiRegistry::new(local); + multi.add_external( + RegistrySource { + url: "https://example.com/reg".to_string(), + priority: 1, + last_synced: None, + }, + external, + ); + + let resolved = multi.resolve_mcp("test-mcp").unwrap(); + assert_eq!(resolved.metadata.integrity, "untrusted"); + } + + #[test] + fn lower_priority_external_wins() { + let local_dir = TempDir::new().unwrap(); + let ext1_dir = TempDir::new().unwrap(); + let ext2_dir = TempDir::new().unwrap(); + + let local = make_registry(&local_dir, &[], &[]); + let ext1 = make_registry(&ext1_dir, &[("test-mcp", "observed")], &[]); + let ext2 = make_registry(&ext2_dir, &[("test-mcp", "verified")], &[]); + + let mut multi = MultiRegistry::new(local); + multi.add_external( + RegistrySource { + url: "https://example.com/primary".to_string(), + priority: 1, + last_synced: None, + }, + ext1, + ); + multi.add_external( + RegistrySource { + url: "https://example.com/secondary".to_string(), + priority: 10, + last_synced: None, + }, + ext2, + ); + + let resolved = multi.resolve_mcp("test-mcp").unwrap(); + assert_eq!(resolved.metadata.integrity, "observed"); + } + + #[test] + fn all_mcps_merges_with_dedup() { + let local_dir = TempDir::new().unwrap(); + let ext_dir = TempDir::new().unwrap(); + + let local = make_registry( + &local_dir, + &[("local-only", "untrusted"), ("shared", "untrusted")], + &[], + ); + let external = make_registry( + &ext_dir, + &[("ext-only", "observed"), ("shared", "verified")], + &[], + ); + + let mut multi = MultiRegistry::new(local); + multi.add_external( + RegistrySource { + url: "https://example.com".to_string(), + priority: 1, + last_synced: None, + }, + external, + ); + + let all = multi.all_mcps(); + assert_eq!(all.len(), 3); + assert_eq!(all["shared"].metadata.integrity, "untrusted"); + assert_eq!(all["ext-only"].metadata.integrity, "observed"); + assert_eq!(all["local-only"].metadata.integrity, "untrusted"); + } + + #[test] + fn resolve_missing_returns_none() { + let local_dir = TempDir::new().unwrap(); + let local = make_registry(&local_dir, &[], &[]); + let multi = MultiRegistry::new(local); + assert!(multi.resolve_mcp("nonexistent").is_none()); + assert!(multi.resolve_skill("nonexistent").is_none()); + } + + #[test] + fn skill_resolution_works() { + let local_dir = TempDir::new().unwrap(); + let local = make_registry(&local_dir, &[], &["deploy"]); + let multi = MultiRegistry::new(local); + + let resolved = multi.resolve_skill("deploy").unwrap(); + assert_eq!(resolved.metadata.name, "deploy"); + } +} diff --git a/argus/crates/argus-registry/src/toml_loader.rs b/argus/crates/argus-registry/src/toml_loader.rs new file mode 100644 index 0000000..ac758d3 --- /dev/null +++ b/argus/crates/argus-registry/src/toml_loader.rs @@ -0,0 +1,291 @@ +use crate::toml_types::{CapabilityTaxonomy, McpEntry, SkillEntry, validate_sidecars}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +#[derive(Debug)] +pub struct TomlRegistry { + pub mcps: HashMap, + pub skills: HashMap, + pub taxonomy: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum TomlRegistryError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("TOML parse error in {file}: {source}")] + Parse { + file: String, + source: toml::de::Error, + }, + #[error("Registry directory not found: {0}")] + NotFound(String), + #[error("Validation error in {file}: {reason}")] + Validation { file: String, reason: String }, +} + +impl TomlRegistry { + pub fn empty() -> Self { + Self { + mcps: HashMap::new(), + skills: HashMap::new(), + taxonomy: None, + } + } + + pub fn load_from_dir(dir: &Path) -> Result { + if !dir.is_dir() { + return Err(TomlRegistryError::NotFound(dir.display().to_string())); + } + + let mcps = Self::load_entries::(&dir.join("mcps"))?; + + for (name, entry) in &mcps { + validate_sidecars(&entry.sidecars).map_err(|e| TomlRegistryError::Validation { + file: name.clone(), + reason: e.to_string(), + })?; + } + + let skills = Self::load_entries::(&dir.join("skills"))?; + let taxonomy = Self::load_taxonomy(&dir.join("capabilities"))?; + + Ok(Self { + mcps, + skills, + taxonomy, + }) + } + + pub fn load_mcp(path: &Path) -> Result { + let entry: McpEntry = Self::load_single(path)?; + validate_sidecars(&entry.sidecars).map_err(|e| TomlRegistryError::Validation { + file: path.display().to_string(), + reason: e.to_string(), + })?; + Ok(entry) + } + + pub fn load_skill(path: &Path) -> Result { + Self::load_single(path) + } + + fn load_single(path: &Path) -> Result { + let content = std::fs::read_to_string(path)?; + toml::from_str(&content).map_err(|source| TomlRegistryError::Parse { + file: path.display().to_string(), + source, + }) + } + + fn load_entries( + dir: &Path, + ) -> Result, TomlRegistryError> { + let mut entries = HashMap::new(); + if !dir.is_dir() { + return Ok(entries); + } + + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.extension().is_none_or(|e| e != "toml") { + continue; + } + let name = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or_default() + .to_string(); + let parsed: T = Self::load_single(&path)?; + entries.insert(name, parsed); + } + + Ok(entries) + } + + fn load_taxonomy(dir: &Path) -> Result, TomlRegistryError> { + let taxonomy_path = dir.join("taxonomy.toml"); + if !taxonomy_path.exists() { + return Ok(None); + } + Self::load_single(&taxonomy_path).map(Some) + } + + pub fn dir_for_source(base: &Path, source_name: &str) -> PathBuf { + base.join("registries").join(source_name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + fn write_fixture(dir: &Path, rel_path: &str, content: &str) { + let full = dir.join(rel_path); + if let Some(parent) = full.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + std::fs::write(full, content).unwrap(); + } + + const MCP_TOML: &str = r#" +[metadata] +name = "test-mcp" +description = "A test MCP" +source = "community" +integrity = "observed" +capabilities = ["filesystem:read"] + +[install] +type = "npm" +package = "@test/mcp" +version = "1.0.0" +"#; + + const SKILL_TOML: &str = r#" +[metadata] +name = "test-skill" +description = "A test skill" +tools_used = ["read_file", "write_file"] +capabilities = ["filesystem:read", "filesystem:write"] +"#; + + const TAXONOMY_TOML: &str = r#" +[capabilities.filesystem_read] +description = "Read files" +scope_type = "path_glob" + +[integrity_levels.untrusted] +description = "Unvetted" +level = 1 +"#; + + #[test] + fn load_from_dir_with_mcps_and_skills() { + let tmp = TempDir::new().unwrap(); + write_fixture(tmp.path(), "mcps/test-mcp.toml", MCP_TOML); + write_fixture(tmp.path(), "skills/test-skill.toml", SKILL_TOML); + + let registry = TomlRegistry::load_from_dir(tmp.path()).unwrap(); + assert_eq!(registry.mcps.len(), 1); + assert_eq!(registry.skills.len(), 1); + assert!(registry.mcps.contains_key("test-mcp")); + assert!(registry.skills.contains_key("test-skill")); + assert_eq!(registry.mcps["test-mcp"].metadata.name, "test-mcp"); + assert_eq!(registry.skills["test-skill"].metadata.name, "test-skill"); + } + + #[test] + fn load_from_dir_with_taxonomy() { + let tmp = TempDir::new().unwrap(); + write_fixture(tmp.path(), "capabilities/taxonomy.toml", TAXONOMY_TOML); + + let registry = TomlRegistry::load_from_dir(tmp.path()).unwrap(); + let taxonomy = registry.taxonomy.unwrap(); + assert_eq!(taxonomy.capabilities.len(), 1); + assert_eq!(taxonomy.integrity_levels.len(), 1); + } + + #[test] + fn load_from_dir_empty() { + let tmp = TempDir::new().unwrap(); + let registry = TomlRegistry::load_from_dir(tmp.path()).unwrap(); + assert!(registry.mcps.is_empty()); + assert!(registry.skills.is_empty()); + assert!(registry.taxonomy.is_none()); + } + + #[test] + fn load_from_missing_dir_returns_error() { + let result = TomlRegistry::load_from_dir(Path::new("/nonexistent/path")); + assert!(result.is_err()); + match result.unwrap_err() { + TomlRegistryError::NotFound(path) => { + assert!(path.contains("nonexistent")); + } + other => panic!("Expected NotFound, got: {other}"), + } + } + + #[test] + fn load_malformed_toml_returns_parse_error() { + let tmp = TempDir::new().unwrap(); + write_fixture(tmp.path(), "mcps/bad.toml", "this is not valid toml [[["); + + let result = TomlRegistry::load_from_dir(tmp.path()); + assert!(result.is_err()); + match result.unwrap_err() { + TomlRegistryError::Parse { file, .. } => { + assert!(file.contains("bad.toml")); + } + other => panic!("Expected Parse error, got: {other}"), + } + } + + #[test] + fn load_single_mcp() { + let tmp = TempDir::new().unwrap(); + let mcp_path = tmp.path().join("test.toml"); + std::fs::write(&mcp_path, MCP_TOML).unwrap(); + + let entry = TomlRegistry::load_mcp(&mcp_path).unwrap(); + assert_eq!(entry.metadata.name, "test-mcp"); + } + + #[test] + fn load_single_skill() { + let tmp = TempDir::new().unwrap(); + let skill_path = tmp.path().join("test.toml"); + std::fs::write(&skill_path, SKILL_TOML).unwrap(); + + let entry = TomlRegistry::load_skill(&skill_path).unwrap(); + assert_eq!(entry.metadata.name, "test-skill"); + } + + #[test] + fn non_toml_files_are_ignored() { + let tmp = TempDir::new().unwrap(); + write_fixture(tmp.path(), "mcps/test-mcp.toml", MCP_TOML); + write_fixture(tmp.path(), "mcps/readme.md", "# Not a TOML file"); + write_fixture(tmp.path(), "mcps/.hidden", "hidden file"); + + let registry = TomlRegistry::load_from_dir(tmp.path()).unwrap(); + assert_eq!(registry.mcps.len(), 1); + } + + #[test] + fn load_mcp_with_invalid_sidecar_returns_validation_error() { + let toml_str = r#" +[metadata] +name = "bad-sidecar" +description = "MCP with invalid sidecar" +source = "community" +integrity = "observed" +capabilities = [] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" + +[[sidecar]] +name = "broken-daemon" +command = "some-daemon" +lifecycle = "daemon" +"#; + let tmp = TempDir::new().unwrap(); + write_fixture(tmp.path(), "mcps/bad-sidecar.toml", toml_str); + + let result = TomlRegistry::load_from_dir(tmp.path()); + assert!(result.is_err()); + match result.unwrap_err() { + TomlRegistryError::Validation { file, reason } => { + assert_eq!(file, "bad-sidecar"); + assert!(reason.contains("must have a ready_check")); + } + other => panic!("Expected Validation error, got: {other}"), + } + } +} diff --git a/argus/crates/argus-registry/src/toml_types.rs b/argus/crates/argus-registry/src/toml_types.rs new file mode 100644 index 0000000..374e677 --- /dev/null +++ b/argus/crates/argus-registry/src/toml_types.rs @@ -0,0 +1,781 @@ +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeSet, HashMap}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct McpMetadata { + pub name: String, + pub description: String, + pub repository: Option, + pub source: String, + pub integrity: String, + pub capabilities: Vec, + pub schema_hash: Option, + #[serde(default)] + pub pinned_ref: Option, + #[serde(default)] + pub track: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum InstallConfig { + Npm { + package: String, + version: String, + }, + Pip { + package: String, + version: String, + python_version: Option, + }, + Cargo { + #[serde(rename = "crate")] + crate_name: String, + version: String, + }, + Docker { + image: String, + tag: String, + ports: Option>, + }, + Binary { + url: String, + checksum: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstallEnvVar { + pub required: bool, + pub description: String, + #[serde(default)] + pub default: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SandboxNetPolicy { + Blocked, + Egress, + EgressDomains(Vec), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SandboxConfig { + #[serde(default)] + pub extra_read: Vec, + #[serde(default)] + pub extra_write: Vec, + #[serde(default)] + pub require_sandbox: bool, + #[serde(default)] + pub net_policy: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct McpEntry { + pub metadata: McpMetadata, + pub install: InstallConfig, + #[serde(default)] + pub env: HashMap, + #[serde(default)] + pub args: McpArgs, + #[serde(default)] + pub sandbox: Option, + #[serde(default, rename = "sidecar")] + pub sidecars: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct McpArgs { + #[serde(default)] + pub default: Vec, +} + +fn default_ready_timeout() -> u64 { + 30 +} + +fn default_ready_interval() -> u64 { + 500 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SidecarEntry { + pub name: String, + pub command: String, + #[serde(default)] + pub args: Vec, + pub lifecycle: SidecarLifecycle, + #[serde(default)] + pub ready_check: Option, + #[serde(default = "default_ready_timeout")] + pub ready_timeout: u64, + #[serde(default = "default_ready_interval")] + pub ready_interval: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum SidecarLifecycle { + Daemon, + Init, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ReadyCheck { + Http { url: String }, + Tcp { host: String, port: u16 }, + Command { run: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum SidecarValidationError { + #[error("daemon sidecar '{name}' must have a ready_check")] + DaemonMissingReadyCheck { name: String }, + #[error("init sidecar '{name}' must not have a ready_check")] + InitWithReadyCheck { name: String }, + #[error("duplicate sidecar name '{name}'")] + DuplicateName { name: String }, +} + +pub fn validate_sidecars(sidecars: &[SidecarEntry]) -> Result<(), SidecarValidationError> { + let mut seen = BTreeSet::new(); + for s in sidecars { + if !seen.insert(&s.name) { + return Err(SidecarValidationError::DuplicateName { + name: s.name.clone(), + }); + } + match s.lifecycle { + SidecarLifecycle::Daemon if s.ready_check.is_none() => { + return Err(SidecarValidationError::DaemonMissingReadyCheck { + name: s.name.clone(), + }); + } + SidecarLifecycle::Init if s.ready_check.is_some() => { + return Err(SidecarValidationError::InitWithReadyCheck { + name: s.name.clone(), + }); + } + _ => {} + } + } + Ok(()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SkillMetadata { + pub name: String, + pub description: String, + pub tools_used: Vec, + #[serde(default)] + pub capabilities: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SkillEntry { + pub metadata: SkillMetadata, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CapabilityTaxonomy { + pub capabilities: HashMap, + pub integrity_levels: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CapabilityInfo { + pub description: String, + pub scope_type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IntegrityInfo { + pub description: String, + pub level: u8, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_npm_mcp_entry() { + let toml_str = r#" +[metadata] +name = "filesystem-server" +description = "Provides filesystem access tools" +repository = "https://github.com/modelcontextprotocol/servers" +source = "community" +integrity = "observed" +capabilities = ["filesystem:read(/tmp/**)", "filesystem:write(./project/**)"] + +[install] +type = "npm" +package = "@modelcontextprotocol/server-filesystem" +version = "1.2.3" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.metadata.name, "filesystem-server"); + assert_eq!(entry.metadata.integrity, "observed"); + assert_eq!(entry.metadata.capabilities.len(), 2); + match &entry.install { + InstallConfig::Npm { package, version } => { + assert_eq!(package, "@modelcontextprotocol/server-filesystem"); + assert_eq!(version, "1.2.3"); + } + _ => panic!("Expected npm install config"), + } + } + + #[test] + fn parse_pip_mcp_entry() { + let toml_str = r#" +[metadata] +name = "sqlite-server" +description = "SQLite database access" +source = "community" +integrity = "verified" +capabilities = ["database:read", "database:write"] + +[install] +type = "pip" +package = "mcp-server-sqlite" +version = "0.3.0" +python_version = ">=3.10" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.install { + InstallConfig::Pip { python_version, .. } => { + assert_eq!(python_version.as_deref(), Some(">=3.10")); + } + _ => panic!("Expected pip install config"), + } + } + + #[test] + fn parse_cargo_mcp_entry() { + let toml_str = r#" +[metadata] +name = "cargo-mcp" +description = "Rust MCP server" +source = "community" +integrity = "observed" +capabilities = ["filesystem:read"] + +[install] +type = "cargo" +crate = "some-mcp-server" +version = "0.5.0" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.install { + InstallConfig::Cargo { + crate_name, + version, + } => { + assert_eq!(crate_name, "some-mcp-server"); + assert_eq!(version, "0.5.0"); + } + _ => panic!("Expected cargo install config"), + } + } + + #[test] + fn parse_docker_mcp_entry() { + let toml_str = r#" +[metadata] +name = "docker-mcp" +description = "Dockerized MCP" +source = "community" +integrity = "verified" +capabilities = ["network:egress"] + +[install] +type = "docker" +image = "org/some-mcp" +tag = "1.2.3" +ports = [8080] +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.install { + InstallConfig::Docker { image, tag, ports } => { + assert_eq!(image, "org/some-mcp"); + assert_eq!(tag, "1.2.3"); + assert_eq!(ports.as_deref(), Some(&[8080][..])); + } + _ => panic!("Expected docker install config"), + } + } + + #[test] + fn parse_binary_mcp_entry() { + let toml_str = r#" +[metadata] +name = "binary-mcp" +description = "Binary MCP" +source = "community" +integrity = "proven" +capabilities = ["execution:shell"] + +[install] +type = "binary" +url = "https://github.com/org/repo/releases/download/v1.2.3/mcp-linux-amd64" +checksum = "sha256:def456" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.install { + InstallConfig::Binary { url, checksum } => { + assert!(url.contains("github.com")); + assert!(checksum.starts_with("sha256:")); + } + _ => panic!("Expected binary install config"), + } + } + + #[test] + fn parse_mcp_with_env_and_args() { + let toml_str = r#" +[metadata] +name = "db-server" +description = "Database MCP" +source = "community" +integrity = "verified" +capabilities = ["database:read", "network:egress"] + +[install] +type = "npm" +package = "@org/db-mcp" +version = "1.0.0" + +[env] +DATABASE_URL = { required = true, description = "Connection string" } +LOG_LEVEL = { required = false, description = "Logging verbosity" } + +[args] +default = ["--port", "8080"] +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.env.len(), 2); + assert!(entry.env["DATABASE_URL"].required); + assert!(!entry.env["LOG_LEVEL"].required); + assert_eq!(entry.args.default, vec!["--port", "8080"]); + } + + #[test] + fn parse_skill_entry() { + let toml_str = r#" +[metadata] +name = "deploy-app" +description = "Deploy application workflow" +tools_used = ["bash", "read_file", "write_file"] +capabilities = ["execution:shell(git, npm, docker)", "filesystem:write(./dist/**)"] +"#; + let entry: SkillEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.metadata.name, "deploy-app"); + assert_eq!(entry.metadata.tools_used.len(), 3); + assert_eq!(entry.metadata.capabilities.len(), 2); + } + + #[test] + fn parse_mcp_with_sandbox_section() { + let toml_str = r#" +[metadata] +name = "github-mcp" +description = "GitHub API access" +source = "community" +integrity = "observed" +capabilities = ["network:egress(api.github.com)", "filesystem:read($WORKDIR)", "credentials"] + +[install] +type = "npm" +package = "@mcp/github" +version = "1.0.0" + +[sandbox] +extra_read = ["$HOME/.config/gh"] +extra_write = ["$TMPDIR/gh-cache"] +require_sandbox = true +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + let sandbox = entry.sandbox.unwrap(); + assert_eq!(sandbox.extra_read, vec!["$HOME/.config/gh"]); + assert_eq!(sandbox.extra_write, vec!["$TMPDIR/gh-cache"]); + assert!(sandbox.require_sandbox); + } + + #[test] + fn parse_mcp_without_sandbox_section() { + let toml_str = r#" +[metadata] +name = "simple-mcp" +description = "Simple" +source = "community" +integrity = "untrusted" +capabilities = ["filesystem:read"] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert!(entry.sandbox.is_none()); + } + + #[test] + fn sandbox_config_defaults_are_sensible() { + let config = SandboxConfig::default(); + assert!(config.extra_read.is_empty()); + assert!(config.extra_write.is_empty()); + assert!(!config.require_sandbox); + } + + #[test] + fn mcp_entry_with_pinned_ref_and_track() { + let toml_str = r#" +[metadata] +name = "claude-mem" +description = "Persistent memory for Claude Code" +repository = "https://github.com/thedotmack/claude-mem" +source = "npm" +integrity = "untrusted" +capabilities = ["filesystem:read(~/.claude-mem/)"] +pinned_ref = "abc123def" +track = true + +[install] +type = "npm" +package = "@thedotmack/claude-mem" +version = "1.0.0" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.metadata.pinned_ref.as_deref(), Some("abc123def")); + assert_eq!(entry.metadata.track, Some(true)); + } + + #[test] + fn mcp_entry_without_pinned_ref_and_track_defaults_to_none() { + let toml_str = r#" +[metadata] +name = "test-tool" +description = "A test tool" +source = "npm" +integrity = "untrusted" +capabilities = [] + +[install] +type = "npm" +package = "test-tool" +version = "0.1.0" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert!(entry.metadata.pinned_ref.is_none()); + assert!(entry.metadata.track.is_none()); + } + + #[test] + fn parse_env_with_defaults() { + let toml_str = r#" +[metadata] +name = "test-mcp" +description = "Test" +source = "community" +integrity = "observed" +capabilities = [] + +[install] +type = "npm" +package = "test" +version = "1.0.0" + +[env] +WORKER_PORT = { required = false, default = "37777", description = "Worker port" } +DATA_DIR = { required = false, default = "$HOME/.data", description = "Data directory" } +API_KEY = { required = true, description = "API key (no default, must be provided)" } +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.env["WORKER_PORT"].default.as_deref(), Some("37777")); + assert_eq!( + entry.env["DATA_DIR"].default.as_deref(), + Some("$HOME/.data") + ); + assert!(entry.env["API_KEY"].default.is_none()); + } + + #[test] + fn parse_mcp_with_daemon_sidecar() { + let toml_str = r#" +[metadata] +name = "db-server" +description = "Database MCP" +source = "community" +integrity = "observed" +capabilities = ["database:read"] + +[install] +type = "npm" +package = "@test/db" +version = "1.0.0" + +[[sidecar]] +name = "postgres" +command = "postgres" +args = ["-D", "/var/lib/postgresql/data"] +lifecycle = "daemon" +ready_timeout = 60 +ready_interval = 1000 + +[sidecar.ready_check] +type = "tcp" +host = "127.0.0.1" +port = 5432 +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.sidecars.len(), 1); + let sc = &entry.sidecars[0]; + assert_eq!(sc.name, "postgres"); + assert_eq!(sc.command, "postgres"); + assert_eq!(sc.args, vec!["-D", "/var/lib/postgresql/data"]); + assert_eq!(sc.lifecycle, SidecarLifecycle::Daemon); + assert_eq!(sc.ready_timeout, 60); + assert_eq!(sc.ready_interval, 1000); + assert!(sc.ready_check.is_some()); + } + + #[test] + fn parse_mcp_with_init_sidecar() { + let toml_str = r#" +[metadata] +name = "init-server" +description = "Init MCP" +source = "community" +integrity = "observed" +capabilities = [] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" + +[[sidecar]] +name = "migrate" +command = "db-migrate" +args = ["--up"] +lifecycle = "init" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert_eq!(entry.sidecars.len(), 1); + let sc = &entry.sidecars[0]; + assert_eq!(sc.name, "migrate"); + assert_eq!(sc.lifecycle, SidecarLifecycle::Init); + assert!(sc.ready_check.is_none()); + assert_eq!(sc.ready_timeout, 30); + assert_eq!(sc.ready_interval, 500); + } + + #[test] + fn parse_mcp_without_sidecars_backward_compat() { + let toml_str = r#" +[metadata] +name = "simple" +description = "No sidecars" +source = "community" +integrity = "untrusted" +capabilities = [] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + assert!(entry.sidecars.is_empty()); + } + + #[test] + fn parse_mcp_with_http_ready_check() { + let toml_str = r#" +[metadata] +name = "http-check" +description = "HTTP ready check" +source = "community" +integrity = "observed" +capabilities = [] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" + +[[sidecar]] +name = "web" +command = "web-server" +lifecycle = "daemon" + +[sidecar.ready_check] +type = "http" +url = "http://localhost:8080/health" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.sidecars[0].ready_check { + Some(ReadyCheck::Http { url }) => { + assert_eq!(url, "http://localhost:8080/health"); + } + other => panic!("Expected Http ready check, got: {other:?}"), + } + } + + #[test] + fn parse_mcp_with_command_ready_check() { + let toml_str = r#" +[metadata] +name = "cmd-check" +description = "Command ready check" +source = "community" +integrity = "observed" +capabilities = [] + +[install] +type = "binary" +url = "builtin" +checksum = "builtin" + +[[sidecar]] +name = "service" +command = "my-service" +lifecycle = "daemon" + +[sidecar.ready_check] +type = "command" +run = "pg_isready -h localhost" +"#; + let entry: McpEntry = toml::from_str(toml_str).unwrap(); + match &entry.sidecars[0].ready_check { + Some(ReadyCheck::Command { run }) => { + assert_eq!(run, "pg_isready -h localhost"); + } + other => panic!("Expected Command ready check, got: {other:?}"), + } + } + + #[test] + fn validate_daemon_without_ready_check_fails() { + let sidecars = vec![SidecarEntry { + name: "broken".into(), + command: "broken-daemon".into(), + args: vec![], + lifecycle: SidecarLifecycle::Daemon, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + }]; + let err = validate_sidecars(&sidecars).unwrap_err(); + assert!(err.to_string().contains("broken")); + assert!(err.to_string().contains("must have a ready_check")); + } + + #[test] + fn validate_init_with_ready_check_fails() { + let sidecars = vec![SidecarEntry { + name: "bad-init".into(), + command: "init-cmd".into(), + args: vec![], + lifecycle: SidecarLifecycle::Init, + ready_check: Some(ReadyCheck::Http { + url: "http://localhost".into(), + }), + ready_timeout: 30, + ready_interval: 500, + }]; + let err = validate_sidecars(&sidecars).unwrap_err(); + assert!(err.to_string().contains("bad-init")); + assert!(err.to_string().contains("must not have a ready_check")); + } + + #[test] + fn validate_duplicate_names_fails() { + let sidecars = vec![ + SidecarEntry { + name: "dup".into(), + command: "cmd1".into(), + args: vec![], + lifecycle: SidecarLifecycle::Init, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + }, + SidecarEntry { + name: "dup".into(), + command: "cmd2".into(), + args: vec![], + lifecycle: SidecarLifecycle::Init, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + }, + ]; + let err = validate_sidecars(&sidecars).unwrap_err(); + assert!(err.to_string().contains("duplicate sidecar name 'dup'")); + } + + #[test] + fn validate_valid_mixed_sidecars_passes() { + let sidecars = vec![ + SidecarEntry { + name: "db".into(), + command: "postgres".into(), + args: vec![], + lifecycle: SidecarLifecycle::Daemon, + ready_check: Some(ReadyCheck::Tcp { + host: "127.0.0.1".into(), + port: 5432, + }), + ready_timeout: 30, + ready_interval: 500, + }, + SidecarEntry { + name: "migrate".into(), + command: "migrate".into(), + args: vec!["--up".into()], + lifecycle: SidecarLifecycle::Init, + ready_check: None, + ready_timeout: 30, + ready_interval: 500, + }, + ]; + validate_sidecars(&sidecars).unwrap(); + } + + #[test] + fn validate_empty_sidecars_passes() { + validate_sidecars(&[]).unwrap(); + } + + #[test] + fn parse_capability_taxonomy() { + let toml_str = r#" +[capabilities.filesystem_read] +description = "Read files and directories" +scope_type = "path_glob" + +[capabilities.network_egress] +description = "Make outbound network requests" +scope_type = "domain_glob" + +[integrity_levels.untrusted] +description = "Unvetted, no network" +level = 1 + +[integrity_levels.proven] +description = "Formally verified" +level = 4 +"#; + let taxonomy: CapabilityTaxonomy = toml::from_str(toml_str).unwrap(); + assert_eq!(taxonomy.capabilities.len(), 2); + assert_eq!(taxonomy.integrity_levels.len(), 2); + assert_eq!(taxonomy.integrity_levels["proven"].level, 4); + } +} diff --git a/argus/crates/argus-registry/src/trust_event.rs b/argus/crates/argus-registry/src/trust_event.rs new file mode 100644 index 0000000..92c62c0 --- /dev/null +++ b/argus/crates/argus-registry/src/trust_event.rs @@ -0,0 +1,438 @@ +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::Mutex; + +use rusqlite::{Connection, params}; +use uuid::Uuid; + +use crate::binding::{BindingTable, ToolBinding}; + +#[derive(Debug, Clone, PartialEq)] +pub enum TrustEventType { + Trusted, + Updated, + Revoked, + Quarantined, + Restored, +} + +impl TrustEventType { + pub fn as_str(&self) -> &'static str { + match self { + Self::Trusted => "trusted", + Self::Updated => "updated", + Self::Revoked => "revoked", + Self::Quarantined => "quarantined", + Self::Restored => "restored", + } + } +} + +impl FromStr for TrustEventType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "trusted" => Ok(Self::Trusted), + "updated" => Ok(Self::Updated), + "revoked" => Ok(Self::Revoked), + "quarantined" => Ok(Self::Quarantined), + "restored" => Ok(Self::Restored), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone)] +pub struct TrustEvent { + pub id: String, + pub timestamp: String, + pub actor: String, + pub tool_name: String, + pub event_type: TrustEventType, + pub derivation_hash: Option, + pub capabilities: Option>, + pub source_path: Option, + pub mcp_name: Option, + pub analysis_summary: Option, + pub rationale: Option, +} + +impl TrustEvent { + pub fn new_id() -> String { + Uuid::now_v7().to_string() + } + + pub fn now_rfc3339() -> String { + chrono::Utc::now().to_rfc3339() + } +} + +const TRUST_EVENTS_SCHEMA: &str = r#" +CREATE TABLE IF NOT EXISTS trust_events ( + id TEXT PRIMARY KEY, + timestamp TEXT NOT NULL, + actor TEXT NOT NULL, + tool_name TEXT NOT NULL, + event_type TEXT NOT NULL, + derivation_hash TEXT, + capabilities TEXT, + source_path TEXT, + mcp_name TEXT, + analysis_summary TEXT, + rationale TEXT +); + +CREATE INDEX IF NOT EXISTS idx_trust_events_tool + ON trust_events(tool_name, timestamp); +CREATE INDEX IF NOT EXISTS idx_trust_events_type + ON trust_events(event_type, timestamp); + +CREATE TABLE IF NOT EXISTS tool_bindings ( + name TEXT PRIMARY KEY, + derivation_hash TEXT NOT NULL, + capabilities TEXT NOT NULL, + source_path TEXT NOT NULL, + mcp_name TEXT +); +"#; + +pub struct TrustEventStore { + conn: Mutex, +} + +impl TrustEventStore { + pub fn new(db_path: &Path) -> Result { + if let Some(parent) = db_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let conn = Connection::open(db_path)?; + Self::from_connection(conn) + } + + pub fn in_memory() -> Result { + let conn = Connection::open_in_memory()?; + Self::from_connection(conn) + } + + fn from_connection(conn: Connection) -> Result { + conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")?; + conn.execute_batch(TRUST_EVENTS_SCHEMA)?; + Ok(Self { + conn: Mutex::new(conn), + }) + } + + pub fn write_event(&self, event: &TrustEvent) -> Result<(), rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + let capabilities_json = event + .capabilities + .as_ref() + .map(|c| serde_json::to_string(c).expect("HashSet always serializes")); + let source_path_str = event.source_path.as_ref().map(|p| p.display().to_string()); + + conn.execute( + r#" + INSERT INTO trust_events + (id, timestamp, actor, tool_name, event_type, + derivation_hash, capabilities, + source_path, mcp_name, analysis_summary, rationale) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) + "#, + params![ + event.id, + event.timestamp, + event.actor, + event.tool_name, + event.event_type.as_str(), + event.derivation_hash, + capabilities_json, + source_path_str, + event.mcp_name, + event.analysis_summary, + event.rationale, + ], + )?; + Ok(()) + } + + pub fn events_for_tool(&self, tool_name: &str) -> Result, rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + let mut stmt = + conn.prepare("SELECT * FROM trust_events WHERE tool_name = ?1 ORDER BY timestamp ASC")?; + let rows = stmt.query_map(params![tool_name], Self::row_to_event)?; + rows.collect() + } + + pub fn recent_events(&self, limit: usize) -> Result, rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + let mut stmt = + conn.prepare("SELECT * FROM trust_events ORDER BY timestamp DESC LIMIT ?1")?; + let rows = stmt.query_map(params![limit as i64], Self::row_to_event)?; + rows.collect() + } + + pub fn rebuild_bindings(&self) -> Result { + let mut conn = self.conn.lock().unwrap(); + let tx = conn.transaction()?; + + tx.execute("DELETE FROM tool_bindings", [])?; + + tx.execute_batch( + r#" + INSERT OR REPLACE INTO tool_bindings + (name, derivation_hash, capabilities, source_path, mcp_name) + SELECT e.tool_name, e.derivation_hash, e.capabilities, + e.source_path, e.mcp_name + FROM trust_events e + INNER JOIN ( + SELECT tool_name, MAX(rowid) as max_rowid + FROM trust_events + GROUP BY tool_name + ) latest ON e.tool_name = latest.tool_name AND e.rowid = latest.max_rowid + WHERE e.event_type != 'revoked' + AND e.derivation_hash IS NOT NULL; + "#, + )?; + + let mut table = BindingTable::new(); + { + let mut stmt = tx.prepare( + "SELECT name, derivation_hash, capabilities, source_path, mcp_name + FROM tool_bindings", + )?; + let rows = stmt.query_map([], |row| { + let name: String = row.get("name")?; + let derivation_hash: String = row.get("derivation_hash")?; + let capabilities_json: String = row.get("capabilities")?; + let source_path_str: String = row.get("source_path")?; + let mcp_name: Option = row.get("mcp_name")?; + + let capabilities: HashSet = + serde_json::from_str(&capabilities_json).unwrap_or_default(); + + Ok(ToolBinding { + name, + derivation_hash, + capabilities, + source_path: PathBuf::from(source_path_str), + mcp_name, + }) + })?; + for binding in rows { + table.register(binding?); + } + } + + tx.commit()?; + Ok(table) + } + + fn row_to_event(row: &rusqlite::Row) -> Result { + let event_type_str: String = row.get("event_type")?; + let capabilities_json: Option = row.get("capabilities")?; + let source_path_str: Option = row.get("source_path")?; + + Ok(TrustEvent { + id: row.get("id")?, + timestamp: row.get("timestamp")?, + actor: row.get("actor")?, + tool_name: row.get("tool_name")?, + event_type: TrustEventType::from_str(&event_type_str) + .unwrap_or(TrustEventType::Quarantined), + derivation_hash: row.get("derivation_hash")?, + capabilities: capabilities_json.map(|j| serde_json::from_str(&j).unwrap_or_default()), + source_path: source_path_str.map(PathBuf::from), + mcp_name: row.get("mcp_name")?, + analysis_summary: row.get("analysis_summary")?, + rationale: row.get("rationale")?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_trusted_event(tool_name: &str) -> TrustEvent { + TrustEvent { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + actor: "local".to_string(), + tool_name: tool_name.to_string(), + event_type: TrustEventType::Trusted, + derivation_hash: Some(format!("sha256:{tool_name}")), + capabilities: Some(["filesystem:read".to_string()].into_iter().collect()), + source_path: Some(PathBuf::from(format!("/store/{tool_name}"))), + mcp_name: None, + analysis_summary: None, + rationale: Some("initial trust".to_string()), + } + } + + #[test] + fn write_and_read_trust_event() { + let store = TrustEventStore::in_memory().unwrap(); + let event = sample_trusted_event("read_file"); + store.write_event(&event).unwrap(); + + let events = store.events_for_tool("read_file").unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].tool_name, "read_file"); + assert_eq!(events[0].event_type, TrustEventType::Trusted); + assert_eq!(events[0].actor, "local"); + assert_eq!( + events[0].derivation_hash.as_deref(), + Some("sha256:read_file") + ); + assert_eq!(events[0].rationale.as_deref(), Some("initial trust")); + } + + #[test] + fn events_for_tool_returns_chronological_order() { + let store = TrustEventStore::in_memory().unwrap(); + + let mut e1 = sample_trusted_event("tool_a"); + e1.timestamp = "2026-01-01T00:00:00Z".to_string(); + store.write_event(&e1).unwrap(); + + let mut e2 = sample_trusted_event("tool_a"); + e2.event_type = TrustEventType::Updated; + e2.timestamp = "2026-01-02T00:00:00Z".to_string(); + e2.derivation_hash = Some("sha256:updated".to_string()); + store.write_event(&e2).unwrap(); + + let events = store.events_for_tool("tool_a").unwrap(); + assert_eq!(events.len(), 2); + assert_eq!(events[0].event_type, TrustEventType::Trusted); + assert_eq!(events[1].event_type, TrustEventType::Updated); + } + + #[test] + fn rebuild_bindings_from_trusted_event() { + let store = TrustEventStore::in_memory().unwrap(); + store + .write_event(&sample_trusted_event("read_file")) + .unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert_eq!(table.len(), 1); + let binding = table.lookup("read_file").unwrap(); + assert_eq!(binding.derivation_hash, "sha256:read_file"); + assert!(binding.capabilities.contains("filesystem:read")); + } + + #[test] + fn rebuild_bindings_excludes_revoked() { + let store = TrustEventStore::in_memory().unwrap(); + store.write_event(&sample_trusted_event("tool_a")).unwrap(); + + let revoke = TrustEvent { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + actor: "local".to_string(), + tool_name: "tool_a".to_string(), + event_type: TrustEventType::Revoked, + derivation_hash: None, + capabilities: None, + source_path: None, + mcp_name: None, + analysis_summary: None, + rationale: Some("no longer needed".to_string()), + }; + store.write_event(&revoke).unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert!(table.is_empty()); + } + + #[test] + fn rebuild_bindings_uses_latest_event() { + let store = TrustEventStore::in_memory().unwrap(); + store.write_event(&sample_trusted_event("tool_a")).unwrap(); + + let mut updated = sample_trusted_event("tool_a"); + updated.event_type = TrustEventType::Updated; + updated.derivation_hash = Some("sha256:v2".to_string()); + store.write_event(&updated).unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert_eq!(table.len(), 1); + let binding = table.lookup("tool_a").unwrap(); + assert_eq!(binding.derivation_hash, "sha256:v2"); + } + + #[test] + fn recent_events_respects_limit() { + let store = TrustEventStore::in_memory().unwrap(); + for i in 0..5 { + let mut event = sample_trusted_event(&format!("tool_{i}")); + event.timestamp = format!("2026-01-0{i}T00:00:00Z"); + store.write_event(&event).unwrap(); + } + + let events = store.recent_events(3).unwrap(); + assert_eq!(events.len(), 3); + } + + #[test] + fn rebuild_bindings_multiple_tools() { + let store = TrustEventStore::in_memory().unwrap(); + store.write_event(&sample_trusted_event("tool_a")).unwrap(); + store.write_event(&sample_trusted_event("tool_b")).unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert_eq!(table.len(), 2); + assert!(table.lookup("tool_a").is_some()); + assert!(table.lookup("tool_b").is_some()); + } + + #[test] + fn event_type_roundtrip() { + for event_type in [ + TrustEventType::Trusted, + TrustEventType::Updated, + TrustEventType::Revoked, + TrustEventType::Quarantined, + TrustEventType::Restored, + ] { + let s = event_type.as_str(); + let parsed = TrustEventType::from_str(s).unwrap(); + assert_eq!(parsed, event_type); + } + } + + #[test] + fn rebuild_after_revoke_then_retrust() { + let store = TrustEventStore::in_memory().unwrap(); + store.write_event(&sample_trusted_event("tool_a")).unwrap(); + + let revoke = TrustEvent { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + actor: "local".to_string(), + tool_name: "tool_a".to_string(), + event_type: TrustEventType::Revoked, + derivation_hash: None, + capabilities: None, + source_path: None, + mcp_name: None, + analysis_summary: None, + rationale: None, + }; + store.write_event(&revoke).unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert!(table.is_empty()); + + let mut retrust = sample_trusted_event("tool_a"); + retrust.event_type = TrustEventType::Trusted; + retrust.derivation_hash = Some("sha256:v3".to_string()); + store.write_event(&retrust).unwrap(); + + let table = store.rebuild_bindings().unwrap(); + assert_eq!(table.len(), 1); + assert_eq!(table.lookup("tool_a").unwrap().derivation_hash, "sha256:v3"); + } +} diff --git a/argus/crates/argus-registry/src/types.rs b/argus/crates/argus-registry/src/types.rs new file mode 100644 index 0000000..b480562 --- /dev/null +++ b/argus/crates/argus-registry/src/types.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub struct RegistryEntry { + pub capabilities: Vec, + pub schema_hash: Option, +} diff --git a/argus/crates/argus-registry/src/watcher.rs b/argus/crates/argus-registry/src/watcher.rs new file mode 100644 index 0000000..b0cc9bd --- /dev/null +++ b/argus/crates/argus-registry/src/watcher.rs @@ -0,0 +1,560 @@ +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; + +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use thiserror::Error; +use tokio::sync::mpsc; + +use crate::hash::compute_hash; +use crate::trust_event::{TrustEvent, TrustEventStore, TrustEventType}; +use crate::{QuarantineManager, QuarantineReason}; +use argus_config::OperationalMode; + +#[derive(Debug, Clone)] +pub enum WatchEvent { + ItemAdded { + path: PathBuf, + authorized: bool, + }, + ItemRemoved { + path: PathBuf, + }, + ItemModified { + path: PathBuf, + }, + Quarantined { + name: String, + reason: QuarantineReason, + }, +} + +#[derive(Error, Debug)] +pub enum WatcherError { + #[error("Failed to create watcher: {0}")] + CreateWatcher(#[from] notify::Error), + #[error("Channel send error")] + ChannelSend, + #[error("Quarantine error: {0}")] + Quarantine(#[from] crate::QuarantineError), +} + +pub struct FolderWatcher { + mode: Arc>, + quarantine_manager: Arc, + event_tx: mpsc::Sender, + authorized_paths: Arc>>, + binding_table: Option>>, + trust_event_store: Option>, +} + +impl FolderWatcher { + pub fn new( + mode: Arc>, + quarantine_manager: Arc, + event_tx: mpsc::Sender, + ) -> Self { + Self { + mode, + quarantine_manager, + event_tx, + authorized_paths: Arc::new(RwLock::new(HashSet::new())), + binding_table: None, + trust_event_store: None, + } + } + + pub fn mark_authorized(&self, path: PathBuf) { + let mut authorized = self + .authorized_paths + .write() + .unwrap_or_else(|e| e.into_inner()); + authorized.insert(path); + } + + pub fn is_authorized(&self, path: &Path) -> bool { + let authorized = self + .authorized_paths + .read() + .unwrap_or_else(|e| e.into_inner()); + authorized.contains(path) + } + + pub fn with_binding_table(mut self, bt: Arc>) -> Self { + self.binding_table = Some(bt); + self + } + + pub fn with_trust_event_store(mut self, store: Arc) -> Self { + self.trust_event_store = Some(store); + self + } + + pub fn clear_authorized(&self, path: &Path) { + let mut authorized = self + .authorized_paths + .write() + .unwrap_or_else(|e| e.into_inner()); + authorized.remove(path); + } + + pub async fn handle_fs_event(&self, event: Event) -> Result<(), WatcherError> { + match event.kind { + EventKind::Create(_) => { + for path in event.paths { + if path.is_dir() { + self.handle_item_added(path).await?; + } + } + } + EventKind::Remove(_) => { + for path in event.paths { + let _ = self.event_tx.send(WatchEvent::ItemRemoved { path }).await; + } + } + EventKind::Modify(_) => { + for path in event.paths { + self.handle_modified(path).await?; + } + } + _ => {} + } + Ok(()) + } + + async fn handle_item_added(&self, path: PathBuf) -> Result<(), WatcherError> { + if self.is_authorized(&path) { + self.clear_authorized(&path); + let _ = self + .event_tx + .send(WatchEvent::ItemAdded { + path, + authorized: true, + }) + .await; + return Ok(()); + } + + let mode = self.mode.read().unwrap_or_else(|e| e.into_inner()).clone(); + match mode { + OperationalMode::Development => { + tracing::warn!( + path = %path.display(), + "Unauthorized item added in development mode - allowing with warning" + ); + let _ = self + .event_tx + .send(WatchEvent::ItemAdded { + path, + authorized: false, + }) + .await; + } + OperationalMode::Production { .. } => { + tracing::warn!( + path = %path.display(), + "Unauthorized item added in production mode - quarantining" + ); + let item = self + .quarantine_manager + .quarantine(&path, QuarantineReason::UnauthorizedAddition)?; + self.record_quarantine_event( + &item.name, + None, + "unauthorized addition detected by file watcher", + ); + let _ = self + .event_tx + .send(WatchEvent::Quarantined { + name: item.name, + reason: QuarantineReason::UnauthorizedAddition, + }) + .await; + } + } + Ok(()) + } + + async fn handle_modified(&self, path: PathBuf) -> Result<(), WatcherError> { + let Some(bt) = &self.binding_table else { + let _ = self.event_tx.send(WatchEvent::ItemModified { path }).await; + return Ok(()); + }; + + let binding_info = { + let table = bt.read().unwrap_or_else(|e| e.into_inner()); + table + .iter() + .find(|b| path.starts_with(&b.source_path)) + .map(|b| { + ( + b.name.clone(), + b.derivation_hash.clone(), + b.source_path.clone(), + ) + }) + }; + + let Some((name, expected_hash, source_path)) = binding_info else { + let _ = self.event_tx.send(WatchEvent::ItemModified { path }).await; + return Ok(()); + }; + + let current_hash = match compute_hash(&source_path) { + Ok(h) => h, + Err(_) => { + tracing::error!(tool = %name, "Failed to recompute hash for modified tool"); + return Ok(()); + } + }; + + if current_hash == expected_hash { + let _ = self.event_tx.send(WatchEvent::ItemModified { path }).await; + return Ok(()); + } + + let mode = self.mode.read().unwrap_or_else(|e| e.into_inner()).clone(); + match mode { + OperationalMode::Development => { + tracing::warn!(tool = %name, "Hash mismatch in development mode -- binding is stale"); + let _ = self.event_tx.send(WatchEvent::ItemModified { path }).await; + } + OperationalMode::Production { .. } => { + tracing::warn!(tool = %name, "Hash mismatch in production mode -- quarantining"); + let reason = QuarantineReason::HashMismatch { + expected: expected_hash, + actual: current_hash.clone(), + }; + if source_path.exists() { + let _ = self + .quarantine_manager + .quarantine(&source_path, reason.clone()); + } + self.record_quarantine_event( + &name, + Some(current_hash), + "hash mismatch detected by file watcher", + ); + bt.write().unwrap_or_else(|e| e.into_inner()).remove(&name); + let _ = self + .event_tx + .send(WatchEvent::Quarantined { name, reason }) + .await; + } + } + Ok(()) + } + + fn record_quarantine_event( + &self, + tool_name: &str, + derivation_hash: Option, + rationale: &str, + ) { + if let Some(store) = &self.trust_event_store { + let event = TrustEvent { + id: TrustEvent::new_id(), + timestamp: TrustEvent::now_rfc3339(), + actor: "watcher".to_string(), + tool_name: tool_name.to_string(), + event_type: TrustEventType::Quarantined, + derivation_hash, + capabilities: None, + source_path: None, + mcp_name: None, + analysis_summary: None, + rationale: Some(rationale.to_string()), + }; + if let Err(e) = store.write_event(&event) { + tracing::error!(error = %e, "Failed to record quarantine trust event"); + } + } + } + + pub fn start_watching( + self: Arc, + paths: Vec, + ) -> Result { + let watcher_self = self.clone(); + + let mut watcher = RecommendedWatcher::new( + move |res: Result| { + if let Ok(event) = res { + let watcher_clone = watcher_self.clone(); + tokio::spawn(async move { + if let Err(e) = watcher_clone.handle_fs_event(event).await { + tracing::error!(error = %e, "Failed to handle fs event"); + } + }); + } + }, + Config::default(), + )?; + + for path in paths { + if path.exists() { + watcher.watch(&path, RecursiveMode::NonRecursive)?; + tracing::info!(path = %path.display(), "Watching directory for changes"); + } + } + + Ok(watcher) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use argus_config::OperationalMode; + + #[test] + fn watch_event_item_added_can_be_created() { + let event = WatchEvent::ItemAdded { + path: PathBuf::from("/test/mcp"), + authorized: true, + }; + match event { + WatchEvent::ItemAdded { authorized, .. } => assert!(authorized), + _ => panic!("Wrong variant"), + } + } + + #[test] + fn watch_event_quarantined_contains_reason() { + let event = WatchEvent::Quarantined { + name: "bad-mcp".to_string(), + reason: QuarantineReason::UnauthorizedAddition, + }; + match event { + WatchEvent::Quarantined { name, reason } => { + assert_eq!(name, "bad-mcp"); + assert!(matches!(reason, QuarantineReason::UnauthorizedAddition)); + } + _ => panic!("Wrong variant"), + } + } + + #[test] + fn folder_watcher_tracks_authorized_paths() { + let mode = Arc::new(RwLock::new(OperationalMode::Development)); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(temp.path()).unwrap()); + let (tx, _rx) = mpsc::channel(10); + + let watcher = FolderWatcher::new(mode, qm, tx); + + let path = PathBuf::from("/test/mcp"); + assert!(!watcher.is_authorized(&path)); + + watcher.mark_authorized(path.clone()); + assert!(watcher.is_authorized(&path)); + + watcher.clear_authorized(&path); + assert!(!watcher.is_authorized(&path)); + } + + #[tokio::test] + async fn handle_item_added_development_mode_allows() { + let mode = Arc::new(RwLock::new(OperationalMode::Development)); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, mut rx) = mpsc::channel(10); + + let watcher = FolderWatcher::new(mode, qm, tx); + + let item_path = temp.path().join("mcps").join("new-mcp"); + std::fs::create_dir_all(&item_path).unwrap(); + + watcher.handle_item_added(item_path.clone()).await.unwrap(); + + let event = rx.try_recv().unwrap(); + match event { + WatchEvent::ItemAdded { path, authorized } => { + assert_eq!(path, item_path); + assert!(!authorized); + } + _ => panic!("Expected ItemAdded event"), + } + } + + #[tokio::test] + async fn modify_event_with_hash_mismatch_quarantines_in_production() { + let mode = Arc::new(RwLock::new(OperationalMode::Production { strict: false })); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, mut rx) = mpsc::channel(10); + + let tool_dir = temp.path().join("mcps").join("my-tool"); + std::fs::create_dir_all(&tool_dir).unwrap(); + std::fs::write(tool_dir.join("main.js"), "original").unwrap(); + + let hash = compute_hash(&tool_dir).unwrap(); + + let bt = Arc::new(RwLock::new(crate::BindingTable::new())); + { + let mut table = bt.write().unwrap(); + table.register(crate::ToolBinding { + name: "my-tool".to_string(), + derivation_hash: hash, + capabilities: std::collections::HashSet::new(), + source_path: tool_dir.clone(), + mcp_name: None, + }); + } + + let watcher = FolderWatcher::new(mode, qm, tx).with_binding_table(bt.clone()); + + std::fs::write(tool_dir.join("main.js"), "tampered").unwrap(); + + watcher + .handle_modified(tool_dir.join("main.js")) + .await + .unwrap(); + + let event = rx.try_recv().unwrap(); + match event { + WatchEvent::Quarantined { name, reason } => { + assert_eq!(name, "my-tool"); + assert!(matches!(reason, QuarantineReason::HashMismatch { .. })); + } + other => panic!("Expected Quarantined, got {:?}", other), + } + + let table = bt.read().unwrap(); + assert!(table.lookup("my-tool").is_none()); + } + + #[tokio::test] + async fn modify_event_with_matching_hash_sends_item_modified() { + let mode = Arc::new(RwLock::new(OperationalMode::Production { strict: false })); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, mut rx) = mpsc::channel(10); + + let tool_dir = temp.path().join("mcps").join("good-tool"); + std::fs::create_dir_all(&tool_dir).unwrap(); + std::fs::write(tool_dir.join("main.js"), "unchanged").unwrap(); + + let hash = compute_hash(&tool_dir).unwrap(); + + let bt = Arc::new(RwLock::new(crate::BindingTable::new())); + { + let mut table = bt.write().unwrap(); + table.register(crate::ToolBinding { + name: "good-tool".to_string(), + derivation_hash: hash, + capabilities: std::collections::HashSet::new(), + source_path: tool_dir.clone(), + mcp_name: None, + }); + } + + let watcher = FolderWatcher::new(mode, qm, tx).with_binding_table(bt.clone()); + + watcher + .handle_modified(tool_dir.join("main.js")) + .await + .unwrap(); + + let event = rx.try_recv().unwrap(); + assert!(matches!(event, WatchEvent::ItemModified { .. })); + + let table = bt.read().unwrap(); + assert!(table.lookup("good-tool").is_some()); + } + + #[tokio::test] + async fn modify_event_dev_mode_warns_but_does_not_quarantine() { + let mode = Arc::new(RwLock::new(OperationalMode::Development)); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, mut rx) = mpsc::channel(10); + + let tool_dir = temp.path().join("mcps").join("dev-tool"); + std::fs::create_dir_all(&tool_dir).unwrap(); + std::fs::write(tool_dir.join("main.js"), "original").unwrap(); + + let hash = compute_hash(&tool_dir).unwrap(); + + let bt = Arc::new(RwLock::new(crate::BindingTable::new())); + { + let mut table = bt.write().unwrap(); + table.register(crate::ToolBinding { + name: "dev-tool".to_string(), + derivation_hash: hash, + capabilities: std::collections::HashSet::new(), + source_path: tool_dir.clone(), + mcp_name: None, + }); + } + + let watcher = FolderWatcher::new(mode, qm, tx).with_binding_table(bt.clone()); + + std::fs::write(tool_dir.join("main.js"), "tampered").unwrap(); + + watcher + .handle_modified(tool_dir.join("main.js")) + .await + .unwrap(); + + let event = rx.try_recv().unwrap(); + assert!(matches!(event, WatchEvent::ItemModified { .. })); + + let table = bt.read().unwrap(); + assert!(table.lookup("dev-tool").is_some()); + } + + #[tokio::test] + async fn quarantine_writes_trust_event() { + let mode = Arc::new(RwLock::new(OperationalMode::Production { strict: false })); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, _rx) = mpsc::channel(10); + + let trust_store = Arc::new(TrustEventStore::in_memory().unwrap()); + let watcher = FolderWatcher::new(mode, qm, tx).with_trust_event_store(trust_store.clone()); + + let item_path = temp.path().join("mcps").join("suspect-mcp"); + std::fs::create_dir_all(&item_path).unwrap(); + + watcher.handle_item_added(item_path).await.unwrap(); + + let events = trust_store.events_for_tool("suspect-mcp").unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].event_type, TrustEventType::Quarantined); + assert_eq!(events[0].actor, "watcher"); + assert!( + events[0] + .rationale + .as_deref() + .unwrap() + .contains("unauthorized") + ); + } + + #[tokio::test] + async fn handle_item_added_production_mode_quarantines() { + let mode = Arc::new(RwLock::new(OperationalMode::Production { strict: false })); + let temp = tempfile::TempDir::new().unwrap(); + let qm = Arc::new(QuarantineManager::new(&temp.path().join("quarantine")).unwrap()); + let (tx, mut rx) = mpsc::channel(10); + + let watcher = FolderWatcher::new(mode, qm.clone(), tx); + + let item_path = temp.path().join("mcps").join("bad-mcp"); + std::fs::create_dir_all(&item_path).unwrap(); + + watcher.handle_item_added(item_path.clone()).await.unwrap(); + + assert!(!item_path.exists()); + + let event = rx.try_recv().unwrap(); + match event { + WatchEvent::Quarantined { name, reason } => { + assert_eq!(name, "bad-mcp"); + assert!(matches!(reason, QuarantineReason::UnauthorizedAddition)); + } + _ => panic!("Expected Quarantined event"), + } + } +} diff --git a/argus/crates/argus-sandbox/Cargo.toml b/argus/crates/argus-sandbox/Cargo.toml new file mode 100644 index 0000000..fd6fdf5 --- /dev/null +++ b/argus/crates/argus-sandbox/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "argus-sandbox" +version.workspace = true +edition.workspace = true +license.workspace = true + +[features] +default = ["runtime"] +runtime = ["dep:tokio", "dep:tempfile"] +types = [] + +[dependencies] +thiserror = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +secrecy = { workspace = true } +async-trait = { workspace = true } +toml = { workspace = true } +tokio = { workspace = true, features = ["process", "macros", "rt"], optional = true } +tempfile = { workspace = true, optional = true } diff --git a/argus/crates/argus-sandbox/src/build.rs b/argus/crates/argus-sandbox/src/build.rs new file mode 100644 index 0000000..824d0f0 --- /dev/null +++ b/argus/crates/argus-sandbox/src/build.rs @@ -0,0 +1,246 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use crate::sensitive::validate_path_not_sensitive; +use crate::{NetPolicy, SandboxError, SandboxProfile}; + +const SYSTEM_READ_ESSENTIALS: &[&str] = &[ + "/usr/lib", + "/usr/share", + "/dev/null", + "/dev/urandom", + "/etc/resolv.conf", + "/etc/ssl/certs", + "/etc/hosts", +]; + +#[cfg(target_os = "macos")] +const PLATFORM_READ_ESSENTIALS: &[&str] = &[ + "/usr/local/lib", + "/opt/homebrew", + "/Library/Frameworks", + "/System/Library/Frameworks", + "/private/etc/ssl", +]; + +#[cfg(target_os = "linux")] +const PLATFORM_READ_ESSENTIALS: &[&str] = &[ + "/lib", + "/lib64", + "/usr/local/lib", + "/etc/ld.so.cache", + "/etc/ssl/certs", + "/usr/share/ca-certificates", +]; + +#[cfg(not(any(target_os = "macos", target_os = "linux")))] +const PLATFORM_READ_ESSENTIALS: &[&str] = &[]; + +pub struct ProfileBuilder { + extra_read: Vec, + extra_write: Vec, + resolved_env: HashMap, + net_policy: NetPolicy, + command_path: Option, +} + +impl ProfileBuilder { + pub fn new() -> Self { + Self { + extra_read: Vec::new(), + extra_write: Vec::new(), + resolved_env: HashMap::new(), + net_policy: NetPolicy::Blocked, + command_path: None, + } + } + + pub fn extra_read(mut self, paths: Vec) -> Self { + self.extra_read = paths; + self + } + + pub fn extra_write(mut self, paths: Vec) -> Self { + self.extra_write = paths; + self + } + + pub fn resolved_env(mut self, vars: HashMap) -> Self { + self.resolved_env = vars; + self + } + + pub fn net_policy(mut self, policy: NetPolicy) -> Self { + self.net_policy = policy; + self + } + + pub fn command_path(mut self, path: PathBuf) -> Self { + self.command_path = Some(path); + self + } + + pub fn build(self) -> Result { + let mut read_paths: Vec = SYSTEM_READ_ESSENTIALS + .iter() + .chain(PLATFORM_READ_ESSENTIALS.iter()) + .map(PathBuf::from) + .collect(); + + if let Some(cmd) = &self.command_path { + read_paths.push(cmd.clone()); + } + + for raw in &self.extra_read { + let expanded = expand_env_vars(raw); + let path = PathBuf::from(&expanded); + validate_path_not_sensitive(&path)?; + read_paths.push(path); + } + + let mut write_paths = Vec::new(); + for raw in &self.extra_write { + let expanded = expand_env_vars(raw); + let path = PathBuf::from(&expanded); + validate_path_not_sensitive(&path)?; + write_paths.push(path); + } + + Ok(SandboxProfile { + allowed_read_paths: read_paths, + allowed_write_paths: write_paths, + net_policy: self.net_policy, + resolved_env: self.resolved_env, + }) + } +} + +impl Default for ProfileBuilder { + fn default() -> Self { + Self::new() + } +} + +fn expand_env_vars(s: &str) -> String { + let home = std::env::var("HOME").ok(); + let mut result = s.to_string(); + if let Some(ref home) = home { + result = result.replace("$HOME", home); + } + if let Ok(tmpdir) = std::env::var("TMPDIR") { + result = result.replace("$TMPDIR", &tmpdir); + } + if let Some(ref home) = home + && result.starts_with("~/") + { + result = format!("{}{}", home, &result[1..]); + } + result +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn default_builder_produces_deny_all_with_essentials() { + let profile = ProfileBuilder::new().build().unwrap(); + assert_eq!(profile.net_policy, NetPolicy::Blocked); + assert!(profile.allowed_write_paths.is_empty()); + assert!(profile.resolved_env.is_empty()); + assert!( + profile + .allowed_read_paths + .iter() + .any(|p| p == Path::new("/dev/null")) + ); + assert!( + profile + .allowed_read_paths + .iter() + .any(|p| p == Path::new("/usr/lib")) + ); + } + + #[test] + fn builder_adds_extra_read_paths() { + let profile = ProfileBuilder::new() + .extra_read(vec!["/tmp/data".into()]) + .build() + .unwrap(); + assert!( + profile + .allowed_read_paths + .iter() + .any(|p| p == Path::new("/tmp/data")) + ); + } + + #[test] + fn builder_rejects_sensitive_read_path() { + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into()); + let result = ProfileBuilder::new() + .extra_read(vec![format!("{home}/.ssh")]) + .build(); + assert!(result.is_err()); + } + + #[test] + fn builder_rejects_sensitive_write_path() { + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into()); + let result = ProfileBuilder::new() + .extra_write(vec![format!("{home}/.aws")]) + .build(); + assert!(result.is_err()); + } + + #[test] + fn builder_expands_home_variable() { + let profile = ProfileBuilder::new() + .extra_read(vec!["$HOME/projects".into()]) + .build() + .unwrap(); + let home = std::env::var("HOME").unwrap_or_else(|_| "/root".into()); + let expected = PathBuf::from(format!("{home}/projects")); + assert!(profile.allowed_read_paths.contains(&expected)); + } + + #[test] + fn builder_sets_net_policy() { + let profile = ProfileBuilder::new() + .net_policy(NetPolicy::EgressWithDomains(vec!["api.github.com".into()])) + .build() + .unwrap(); + assert_eq!( + profile.net_policy, + NetPolicy::EgressWithDomains(vec!["api.github.com".into()]) + ); + } + + #[test] + fn builder_includes_command_path() { + let profile = ProfileBuilder::new() + .command_path(PathBuf::from("/usr/bin/node")) + .build() + .unwrap(); + assert!( + profile + .allowed_read_paths + .iter() + .any(|p| p == Path::new("/usr/bin/node")) + ); + } + + #[test] + fn builder_sets_resolved_env() { + let mut vars = HashMap::new(); + vars.insert("API_KEY".into(), "secret123".into()); + vars.insert("PORT".into(), "8080".into()); + + let profile = ProfileBuilder::new().resolved_env(vars).build().unwrap(); + assert_eq!(profile.resolved_env.len(), 2); + assert_eq!(profile.resolved_env["API_KEY"], "secret123"); + } +} diff --git a/argus/crates/argus-sandbox/src/domain_match.rs b/argus/crates/argus-sandbox/src/domain_match.rs new file mode 100644 index 0000000..e2b435d --- /dev/null +++ b/argus/crates/argus-sandbox/src/domain_match.rs @@ -0,0 +1,65 @@ +pub fn domain_matches(pattern: &str, hostname: &str) -> bool { + if let Some(suffix) = pattern.strip_prefix("*.") { + let suffix_dot = format!(".{suffix}"); + hostname.ends_with(&suffix_dot) && hostname.len() > suffix_dot.len() + } else { + pattern == hostname + } +} + +pub fn domain_matches_any(patterns: &[String], hostname: &str) -> bool { + patterns.iter().any(|p| domain_matches(p, hostname)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn exact_match() { + assert!(domain_matches("api.github.com", "api.github.com")); + } + + #[test] + fn exact_no_match() { + assert!(!domain_matches("api.github.com", "evil.github.com")); + } + + #[test] + fn wildcard_matches_subdomain() { + assert!(domain_matches("*.github.com", "api.github.com")); + } + + #[test] + fn wildcard_matches_deep_subdomain() { + assert!(domain_matches("*.github.com", "raw.api.github.com")); + } + + #[test] + fn wildcard_does_not_match_bare_domain() { + assert!(!domain_matches("*.github.com", "github.com")); + } + + #[test] + fn wildcard_does_not_match_unrelated() { + assert!(!domain_matches("*.github.com", "api.gitlab.com")); + } + + #[test] + fn case_sensitive() { + assert!(!domain_matches("api.github.com", "API.GITHUB.COM")); + } + + #[test] + fn matches_any_with_list() { + let patterns = vec!["api.github.com".into(), "*.openai.com".into()]; + assert!(domain_matches_any(&patterns, "api.github.com")); + assert!(domain_matches_any(&patterns, "chat.openai.com")); + assert!(!domain_matches_any(&patterns, "evil.com")); + } + + #[test] + fn empty_patterns_matches_nothing() { + assert!(!domain_matches_any(&[], "anything.com")); + } +} diff --git a/argus/crates/argus-sandbox/src/error.rs b/argus/crates/argus-sandbox/src/error.rs new file mode 100644 index 0000000..a70744c --- /dev/null +++ b/argus/crates/argus-sandbox/src/error.rs @@ -0,0 +1,20 @@ +use std::io; +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +pub enum SandboxError { + #[error("failed to spawn sandboxed process: {0}")] + SpawnFailed(#[from] io::Error), + + #[error("sandbox profile generation failed: {reason}")] + ProfileError { reason: String }, + + #[error("path {path} denied: {reason}")] + SensitivePath { path: PathBuf, reason: String }, + + #[error("sandbox wrapper binary not found: {reason}")] + WrapperNotFound { reason: String }, + + #[error("failed to write sandbox profile: {0}")] + ProfileWriteFailed(io::Error), +} diff --git a/argus/crates/argus-sandbox/src/lib.rs b/argus/crates/argus-sandbox/src/lib.rs new file mode 100644 index 0000000..cda40dc --- /dev/null +++ b/argus/crates/argus-sandbox/src/lib.rs @@ -0,0 +1,27 @@ +mod build; +mod domain_match; +mod error; +mod noop; +#[cfg(feature = "runtime")] +mod os; +mod profile; +#[cfg(feature = "runtime")] +mod provider; +mod secret; +pub(crate) mod sensitive; +#[cfg(feature = "runtime")] +mod static_provider; + +pub use build::ProfileBuilder; +pub use domain_match::{domain_matches, domain_matches_any}; +pub use error::SandboxError; +pub use noop::NoopSandbox; +#[cfg(feature = "runtime")] +pub use os::OsSandbox; +pub use profile::{NetPolicy, SandboxProfile}; +#[cfg(feature = "runtime")] +pub use provider::SandboxProvider; +pub use secret::{ResolvedEnv, SecretError, SecretProvider, SecretSource, SecretValue}; +pub use sensitive::validate_path_not_sensitive; +#[cfg(feature = "runtime")] +pub use static_provider::StaticProvider; diff --git a/argus/crates/argus-sandbox/src/noop.rs b/argus/crates/argus-sandbox/src/noop.rs new file mode 100644 index 0000000..2b52211 --- /dev/null +++ b/argus/crates/argus-sandbox/src/noop.rs @@ -0,0 +1,13 @@ +#[derive(Debug, Clone, Default)] +pub struct NoopSandbox; + +#[cfg(feature = "runtime")] +impl crate::SandboxProvider for NoopSandbox { + fn prepare_command( + &self, + command: tokio::process::Command, + _profile: &crate::SandboxProfile, + ) -> Result { + Ok(command) + } +} diff --git a/argus/crates/argus-sandbox/src/os.rs b/argus/crates/argus-sandbox/src/os.rs new file mode 100644 index 0000000..f0aa140 --- /dev/null +++ b/argus/crates/argus-sandbox/src/os.rs @@ -0,0 +1,197 @@ +use std::path::PathBuf; + +use tokio::process::Command; + +use crate::{SandboxError, SandboxProfile, SandboxProvider}; + +pub struct OsSandbox { + wrapper_path: PathBuf, +} + +impl OsSandbox { + pub fn new() -> Result { + let wrapper_path = find_wrapper()?; + Ok(Self { wrapper_path }) + } + + fn write_profile(&self, profile: &SandboxProfile) -> Result { + use std::fs::Permissions; + use std::io::Write; + use std::os::unix::fs::PermissionsExt; + + let mut file = tempfile::Builder::new() + .prefix("argus-sandbox-") + .suffix(".json") + .permissions(Permissions::from_mode(0o600)) + .tempfile() + .map_err(SandboxError::ProfileWriteFailed)?; + + let json = serde_json::to_vec(profile).map_err(|e| SandboxError::ProfileError { + reason: e.to_string(), + })?; + file.write_all(&json) + .map_err(SandboxError::ProfileWriteFailed)?; + + let path = file + .into_temp_path() + .keep() + .map_err(|e| SandboxError::ProfileWriteFailed(e.error))?; + Ok(path) + } +} + +impl SandboxProvider for OsSandbox { + fn prepare_command( + &self, + command: Command, + profile: &SandboxProfile, + ) -> Result { + let profile_path = self.write_profile(profile)?; + + let inner = command.as_std(); + let program = inner.get_program().to_os_string(); + let args: Vec<_> = inner.get_args().map(|a| a.to_os_string()).collect(); + + let mut wrapped = Command::new(&self.wrapper_path); + wrapped.env_clear(); + wrapped.arg("--profile"); + wrapped.arg(&profile_path); + wrapped.arg("--"); + wrapped.arg(&program); + wrapped.args(&args); + + for (k, v) in &profile.resolved_env { + wrapped.env(k, v); + } + + Ok(wrapped) + } +} + +fn find_wrapper() -> Result { + if let Ok(exe) = std::env::current_exe() + && let Some(dir) = exe.parent() + { + let candidate = dir.join("argus-sandbox-exec"); + if candidate.is_file() { + return Ok(candidate); + } + } + + if let Ok(output) = std::process::Command::new("which") + .arg("argus-sandbox-exec") + .output() + && output.status.success() + { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !path.is_empty() { + return Ok(PathBuf::from(path)); + } + } + + Err(SandboxError::WrapperNotFound { + reason: "argus-sandbox-exec not found next to binary or in $PATH".into(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::NetPolicy; + + #[test] + fn write_profile_creates_file_with_valid_json() { + let sandbox = OsSandbox { + wrapper_path: PathBuf::from("/usr/bin/true"), + }; + let mut resolved = std::collections::HashMap::new(); + resolved.insert("HOME".into(), "/home/test".into()); + let profile = SandboxProfile { + allowed_read_paths: vec![PathBuf::from("/tmp")], + allowed_write_paths: vec![], + net_policy: NetPolicy::Blocked, + resolved_env: resolved, + }; + let path = sandbox.write_profile(&profile).unwrap(); + let contents = std::fs::read_to_string(&path).unwrap(); + let parsed: SandboxProfile = serde_json::from_str(&contents).unwrap(); + assert_eq!(parsed.allowed_read_paths, vec![PathBuf::from("/tmp")]); + std::fs::remove_file(path).ok(); + } + + #[test] + fn prepare_command_wraps_with_sandbox_exec() { + let sandbox = OsSandbox { + wrapper_path: PathBuf::from("/usr/bin/argus-sandbox-exec"), + }; + let mut original = Command::new("node"); + original.arg("server.js"); + original.env("API_KEY", "secret"); + + let profile = SandboxProfile::default(); + let wrapped = sandbox.prepare_command(original, &profile).unwrap(); + + let std_cmd = wrapped.as_std(); + assert_eq!( + std_cmd.get_program().to_str().unwrap(), + "/usr/bin/argus-sandbox-exec" + ); + let args: Vec<_> = std_cmd.get_args().map(|a| a.to_str().unwrap()).collect(); + assert_eq!(args[0], "--profile"); + assert!(args[1].contains("argus-sandbox-")); + assert_eq!(args[2], "--"); + assert_eq!(args[3], "node"); + assert_eq!(args[4], "server.js"); + + std::fs::remove_file(args[1]).ok(); + } + + #[test] + fn prepare_command_clears_original_env_vars() { + let sandbox = OsSandbox { + wrapper_path: PathBuf::from("/usr/bin/argus-sandbox-exec"), + }; + let mut original = Command::new("node"); + original.arg("server.js"); + original.env("LEAKED_SECRET", "should-not-appear"); + + let profile = SandboxProfile::default(); + let wrapped = sandbox.prepare_command(original, &profile).unwrap(); + + let std_cmd = wrapped.as_std(); + let has_leak = std_cmd + .get_envs() + .any(|(k, _)| k.to_str() == Some("LEAKED_SECRET")); + assert!(!has_leak, "env vars from original command should not leak"); + + let args: Vec<_> = std_cmd.get_args().map(|a| a.to_str().unwrap()).collect(); + std::fs::remove_file(args[1]).ok(); + } + + #[test] + fn prepare_command_injects_resolved_env() { + let sandbox = OsSandbox { + wrapper_path: PathBuf::from("/usr/bin/argus-sandbox-exec"), + }; + let original = Command::new("node"); + + let mut resolved = std::collections::HashMap::new(); + resolved.insert("INJECTED_VAR".to_string(), "injected_value".to_string()); + + let profile = SandboxProfile { + resolved_env: resolved, + ..Default::default() + }; + let wrapped = sandbox.prepare_command(original, &profile).unwrap(); + + let std_cmd = wrapped.as_std(); + let has_injected = std_cmd.get_envs().any(|(k, v)| { + k.to_str() == Some("INJECTED_VAR") + && v.and_then(|v| v.to_str()) == Some("injected_value") + }); + assert!(has_injected, "resolved_env values should be injected"); + + let args: Vec<_> = std_cmd.get_args().map(|a| a.to_str().unwrap()).collect(); + std::fs::remove_file(args[1]).ok(); + } +} diff --git a/argus/crates/argus-sandbox/src/profile.rs b/argus/crates/argus-sandbox/src/profile.rs new file mode 100644 index 0000000..7243e5c --- /dev/null +++ b/argus/crates/argus-sandbox/src/profile.rs @@ -0,0 +1,83 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum NetPolicy { + #[default] + Blocked, + EgressOnly, + EgressWithDomains(Vec), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SandboxProfile { + pub allowed_read_paths: Vec, + pub allowed_write_paths: Vec, + pub net_policy: NetPolicy, + #[serde(default)] + pub resolved_env: HashMap, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_profile_is_deny_all() { + let profile = SandboxProfile::default(); + assert!(profile.allowed_read_paths.is_empty()); + assert!(profile.allowed_write_paths.is_empty()); + assert_eq!(profile.net_policy, NetPolicy::Blocked); + assert!(profile.resolved_env.is_empty()); + } + + #[test] + fn net_policy_default_is_blocked() { + assert_eq!(NetPolicy::default(), NetPolicy::Blocked); + } + + #[test] + fn profile_serialization_roundtrip() { + let mut resolved = HashMap::new(); + resolved.insert("GITHUB_TOKEN".to_string(), "ghp_abc123".to_string()); + resolved.insert("HOME".to_string(), "/home/user".to_string()); + + let profile = SandboxProfile { + allowed_read_paths: vec![PathBuf::from("/usr/lib"), PathBuf::from("/tmp/data")], + allowed_write_paths: vec![PathBuf::from("/tmp/output")], + net_policy: NetPolicy::EgressWithDomains(vec![ + "api.github.com".into(), + "*.openai.com".into(), + ]), + resolved_env: resolved, + }; + let json = serde_json::to_string(&profile).unwrap(); + let deserialized: SandboxProfile = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.allowed_read_paths, profile.allowed_read_paths); + assert_eq!(deserialized.net_policy, profile.net_policy); + assert_eq!(deserialized.resolved_env, profile.resolved_env); + } + + #[test] + fn net_policy_blocked_serializes_as_string() { + let json = serde_json::to_string(&NetPolicy::Blocked).unwrap(); + assert_eq!(json, "\"blocked\""); + } + + #[test] + fn net_policy_egress_only_serializes_as_string() { + let json = serde_json::to_string(&NetPolicy::EgressOnly).unwrap(); + assert_eq!(json, "\"egress_only\""); + } + + #[test] + fn net_policy_egress_with_domains_serializes_as_object() { + let policy = NetPolicy::EgressWithDomains(vec!["api.github.com".into()]); + let json = serde_json::to_string(&policy).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); + assert!(parsed.get("egress_with_domains").is_some()); + } +} diff --git a/argus/crates/argus-sandbox/src/provider.rs b/argus/crates/argus-sandbox/src/provider.rs new file mode 100644 index 0000000..9b3e37b --- /dev/null +++ b/argus/crates/argus-sandbox/src/provider.rs @@ -0,0 +1,21 @@ +use tokio::process::Command; + +use crate::{SandboxError, SandboxProfile}; + +pub trait SandboxProvider: Send + Sync { + fn prepare_command( + &self, + command: Command, + profile: &SandboxProfile, + ) -> Result; +} + +impl SandboxProvider for Box { + fn prepare_command( + &self, + command: Command, + profile: &SandboxProfile, + ) -> Result { + (**self).prepare_command(command, profile) + } +} diff --git a/argus/crates/argus-sandbox/src/secret.rs b/argus/crates/argus-sandbox/src/secret.rs new file mode 100644 index 0000000..975919b --- /dev/null +++ b/argus/crates/argus-sandbox/src/secret.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; +use std::fmt; +use std::path::PathBuf; + +use secrecy::{ExposeSecret, SecretString}; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SecretSource { + Static, + Sops { file: PathBuf }, + OnePassword { vault: String, item: String }, + Keychain, + Vault { path: String }, +} + +impl fmt::Display for SecretSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Static => write!(f, "static"), + Self::Sops { file } => write!(f, "sops({})", file.display()), + Self::OnePassword { vault, item } => write!(f, "1password({vault}/{item})"), + Self::Keychain => write!(f, "keychain"), + Self::Vault { path } => write!(f, "vault({path})"), + } + } +} + +pub struct SecretValue { + pub value: SecretString, + pub source: SecretSource, +} + +impl fmt::Debug for SecretValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SecretValue") + .field("value", &"[REDACTED]") + .field("source", &self.source) + .finish() + } +} + +impl SecretValue { + pub fn new_static(value: &str) -> Self { + Self { + value: SecretString::from(value.to_string()), + source: SecretSource::Static, + } + } + + pub fn new(value: String, source: SecretSource) -> Self { + Self { + value: SecretString::from(value), + source, + } + } +} + +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum SecretError { + #[error("secret not found: {key}")] + NotFound { key: String }, + #[error("failed to resolve secret '{key}': {reason}")] + ProviderError { key: String, reason: String }, +} + +#[async_trait::async_trait] +pub trait SecretProvider: Send + Sync { + async fn resolve(&self, key: &str) -> Result; + + async fn resolve_batch( + &self, + keys: &[&str], + ) -> Result, SecretError> { + let mut result = HashMap::new(); + for key in keys { + result.insert(key.to_string(), self.resolve(key).await?); + } + Ok(result) + } +} + +pub struct ResolvedEnv { + vars: HashMap, +} + +impl ResolvedEnv { + pub fn new() -> Self { + Self { + vars: HashMap::new(), + } + } + + pub fn insert(&mut self, key: String, value: SecretValue) { + self.vars.insert(key, value); + } + + pub fn len(&self) -> usize { + self.vars.len() + } + + pub fn is_empty(&self) -> bool { + self.vars.is_empty() + } + + pub fn contains_key(&self, key: &str) -> bool { + self.vars.contains_key(key) + } + + pub fn to_string_map(&self) -> HashMap { + self.vars + .iter() + .map(|(k, v)| (k.clone(), v.value.expose_secret().to_string())) + .collect() + } +} + +impl Default for ResolvedEnv { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn secret_value_from_static() { + let sv = SecretValue::new_static("sk-12345"); + assert_eq!(sv.source, SecretSource::Static); + assert_eq!(sv.value.expose_secret(), "sk-12345"); + } + + #[test] + fn secret_source_display() { + assert_eq!(SecretSource::Static.to_string(), "static"); + assert_eq!( + SecretSource::Sops { + file: PathBuf::from("secrets.yaml") + } + .to_string(), + "sops(secrets.yaml)" + ); + } + + #[test] + fn resolved_env_builds_from_pairs() { + let mut env = ResolvedEnv::new(); + env.insert("API_KEY".into(), SecretValue::new_static("val1")); + env.insert("PORT".into(), SecretValue::new_static("8080")); + assert_eq!(env.len(), 2); + assert!(env.contains_key("API_KEY")); + } + + #[test] + fn resolved_env_to_string_map_exposes_values() { + let mut env = ResolvedEnv::new(); + env.insert("KEY".into(), SecretValue::new_static("secret")); + let map = env.to_string_map(); + assert_eq!(map.get("KEY").unwrap(), "secret"); + } +} diff --git a/argus/crates/argus-sandbox/src/sensitive.rs b/argus/crates/argus-sandbox/src/sensitive.rs new file mode 100644 index 0000000..d11c256 --- /dev/null +++ b/argus/crates/argus-sandbox/src/sensitive.rs @@ -0,0 +1,110 @@ +use std::path::{Path, PathBuf}; + +use crate::SandboxError; + +const SENSITIVE_PREFIXES: &[&str] = &[ + ".ssh", + ".aws", + ".gnupg", + ".config/gcloud", + ".azure", + ".kube", + ".docker/config.json", + ".config/gh", + ".netrc", + ".npmrc", + ".pypirc", + ".cargo/credentials.toml", + ".config/google-chrome", + ".config/chromium", + ".mozilla/firefox", + "Library/Application Support/Google/Chrome", + "Library/Application Support/Firefox", + ".bash_history", + ".zsh_history", + ".fish_history", + ".password-store", + ".local/share/keyrings", + "Library/Keychains", +]; + +pub fn validate_path_not_sensitive(path: &Path) -> Result<(), SandboxError> { + let home = home_dir(); + for prefix in SENSITIVE_PREFIXES { + let sensitive = home.join(prefix); + if path.starts_with(&sensitive) { + return Err(SandboxError::SensitivePath { + path: path.to_path_buf(), + reason: format!("matches sensitive prefix: ~/{prefix}"), + }); + } + } + Ok(()) +} + +fn home_dir() -> PathBuf { + std::env::var("HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("/root")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rejects_ssh_directory() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".ssh")); + assert!(result.is_err()); + } + + #[test] + fn rejects_ssh_subdirectory() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".ssh/id_rsa")); + assert!(result.is_err()); + } + + #[test] + fn allows_non_sensitive_path() { + let result = validate_path_not_sensitive(Path::new("/tmp/workspace")); + assert!(result.is_ok()); + } + + #[test] + fn allows_similar_but_different_name() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".sshfoo")); + assert!(result.is_ok()); + } + + #[test] + fn rejects_aws_credentials() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".aws/credentials")); + assert!(result.is_err()); + } + + #[test] + fn rejects_gnupg() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".gnupg/private-keys-v1.d")); + assert!(result.is_err()); + } + + #[test] + fn rejects_kube_config() { + let home = home_dir(); + let result = validate_path_not_sensitive(&home.join(".kube/config")); + assert!(result.is_err()); + } + + #[test] + fn rejects_shell_history() { + let home = home_dir(); + assert!(validate_path_not_sensitive(&home.join(".bash_history")).is_err()); + assert!(validate_path_not_sensitive(&home.join(".zsh_history")).is_err()); + assert!(validate_path_not_sensitive(&home.join(".fish_history")).is_err()); + } +} diff --git a/argus/crates/argus-sandbox/src/static_provider.rs b/argus/crates/argus-sandbox/src/static_provider.rs new file mode 100644 index 0000000..3415406 --- /dev/null +++ b/argus/crates/argus-sandbox/src/static_provider.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; + +use crate::secret::{SecretError, SecretProvider, SecretSource, SecretValue}; + +pub struct StaticProvider { + defaults: HashMap, + overrides: HashMap, +} + +impl StaticProvider { + pub fn new(defaults: HashMap, overrides: HashMap) -> Self { + Self { + defaults, + overrides, + } + } + + pub fn from_defaults(defaults: HashMap) -> Self { + Self::new(defaults, HashMap::new()) + } + + pub fn with_override_file(mut self, path: &std::path::Path) -> Result { + if path.is_file() { + let contents = std::fs::read_to_string(path) + .map_err(|e| format!("failed to read override file {}: {e}", path.display()))?; + let parsed = parse_override_toml(&contents)?; + self.overrides.extend(parsed); + } + Ok(self) + } +} + +#[async_trait::async_trait] +impl SecretProvider for StaticProvider { + async fn resolve(&self, key: &str) -> Result { + if let Some(val) = self.overrides.get(key) { + return Ok(SecretValue::new(val.clone(), SecretSource::Static)); + } + if let Some(val) = self.defaults.get(key) { + return Ok(SecretValue::new(val.clone(), SecretSource::Static)); + } + Err(SecretError::NotFound { + key: key.to_string(), + }) + } +} + +pub fn parse_override_toml(content: &str) -> Result, String> { + let table: HashMap = + toml::from_str(content).map_err(|e| format!("invalid override TOML: {e}"))?; + Ok(table) +} + +#[cfg(test)] +mod tests { + use super::*; + use secrecy::ExposeSecret; + + #[tokio::test] + async fn resolves_from_defaults() { + let mut defaults = HashMap::new(); + defaults.insert("PORT".to_string(), "8080".to_string()); + + let provider = StaticProvider::new(defaults, HashMap::new()); + let val = provider.resolve("PORT").await.unwrap(); + assert_eq!(val.value.expose_secret(), "8080"); + assert_eq!(val.source, SecretSource::Static); + } + + #[tokio::test] + async fn overrides_take_precedence() { + let mut defaults = HashMap::new(); + defaults.insert("PORT".to_string(), "8080".to_string()); + + let mut overrides = HashMap::new(); + overrides.insert("PORT".to_string(), "9090".to_string()); + + let provider = StaticProvider::new(defaults, overrides); + let val = provider.resolve("PORT").await.unwrap(); + assert_eq!(val.value.expose_secret(), "9090"); + } + + #[tokio::test] + async fn not_found_returns_error() { + let provider = StaticProvider::new(HashMap::new(), HashMap::new()); + let err = provider.resolve("MISSING").await.unwrap_err(); + assert!(matches!(err, SecretError::NotFound { .. })); + } + + #[tokio::test] + async fn resolve_batch_returns_all() { + let mut defaults = HashMap::new(); + defaults.insert("A".to_string(), "1".to_string()); + defaults.insert("B".to_string(), "2".to_string()); + + let provider = StaticProvider::new(defaults, HashMap::new()); + let result = provider.resolve_batch(&["A", "B"]).await.unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result["A"].value.expose_secret(), "1"); + } + + #[test] + fn loads_overrides_from_toml_string() { + let toml_str = r#" +PORT = "9090" +API_KEY = "sk-override" +"#; + let overrides = parse_override_toml(toml_str).unwrap(); + assert_eq!(overrides["PORT"], "9090"); + assert_eq!(overrides["API_KEY"], "sk-override"); + } +} diff --git a/argus/crates/argus-sandbox/tests/env_isolation_test.rs b/argus/crates/argus-sandbox/tests/env_isolation_test.rs new file mode 100644 index 0000000..d06e5ae --- /dev/null +++ b/argus/crates/argus-sandbox/tests/env_isolation_test.rs @@ -0,0 +1,81 @@ +#![cfg(feature = "runtime")] + +use std::collections::HashMap; + +use argus_sandbox::{NetPolicy, ProfileBuilder, SandboxProfile, SecretProvider, StaticProvider}; +use secrecy::ExposeSecret; + +#[tokio::test] +async fn full_env_isolation_chain() { + let mut defaults = HashMap::new(); + defaults.insert("WORKER_PORT".into(), "37777".into()); + defaults.insert("LOG_LEVEL".into(), "INFO".into()); + + let mut overrides = HashMap::new(); + overrides.insert("LOG_LEVEL".into(), "DEBUG".into()); + + let provider = StaticProvider::new(defaults, overrides); + + let keys = ["WORKER_PORT", "LOG_LEVEL"]; + let resolved = provider.resolve_batch(&keys).await.unwrap(); + + let env_map: HashMap = resolved + .iter() + .map(|(k, v)| (k.clone(), v.value.expose_secret().to_string())) + .collect(); + + let profile = ProfileBuilder::new() + .extra_read(vec!["/tmp/test".into()]) + .resolved_env(env_map) + .net_policy(NetPolicy::Blocked) + .build() + .unwrap(); + + assert_eq!(profile.resolved_env["WORKER_PORT"], "37777"); + assert_eq!(profile.resolved_env["LOG_LEVEL"], "DEBUG"); + + let json = serde_json::to_string(&profile).unwrap(); + let deserialized: SandboxProfile = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.resolved_env["WORKER_PORT"], "37777"); + assert_eq!(deserialized.resolved_env["LOG_LEVEL"], "DEBUG"); +} + +#[tokio::test] +async fn missing_required_env_var_is_caught() { + let provider = StaticProvider::new(HashMap::new(), HashMap::new()); + let result = provider.resolve("MISSING_REQUIRED_KEY").await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn override_file_integration() { + let dir = tempfile::TempDir::new().unwrap(); + let override_path = dir.path().join("test-mcp.toml"); + std::fs::write( + &override_path, + "API_KEY = \"sk-from-file\"\nPORT = \"9999\"\n", + ) + .unwrap(); + + let mut defaults = HashMap::new(); + defaults.insert("PORT".into(), "8080".into()); + + let provider = StaticProvider::from_defaults(defaults) + .with_override_file(&override_path) + .unwrap(); + + let api_key = provider.resolve("API_KEY").await.unwrap(); + assert_eq!(api_key.value.expose_secret(), "sk-from-file"); + + let port = provider.resolve("PORT").await.unwrap(); + assert_eq!(port.value.expose_secret(), "9999"); +} + +#[test] +fn empty_profile_serializes_without_env() { + let profile = SandboxProfile::default(); + let json = serde_json::to_string(&profile).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); + let env = parsed.get("resolved_env").unwrap(); + assert!(env.as_object().unwrap().is_empty()); +} diff --git a/argus/crates/argus-sandbox/tests/noop_tests.rs b/argus/crates/argus-sandbox/tests/noop_tests.rs new file mode 100644 index 0000000..1f09491 --- /dev/null +++ b/argus/crates/argus-sandbox/tests/noop_tests.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "runtime")] + +use argus_sandbox::{NetPolicy, NoopSandbox, SandboxProfile, SandboxProvider}; +use tokio::process::Command; + +#[test] +fn noop_sandbox_returns_command_unchanged() { + let sandbox = NoopSandbox; + let cmd = Command::new("echo"); + let profile = SandboxProfile::default(); + + let result = sandbox.prepare_command(cmd, &profile); + assert!(result.is_ok()); +} + +#[test] +fn default_profile_denies_everything() { + let profile = SandboxProfile::default(); + assert_eq!(profile.net_policy, NetPolicy::Blocked); + assert!(profile.allowed_read_paths.is_empty()); + assert!(profile.allowed_write_paths.is_empty()); + assert!(profile.resolved_env.is_empty()); +} diff --git a/argus/crates/argus-sentinel/Cargo.toml b/argus/crates/argus-sentinel/Cargo.toml new file mode 100644 index 0000000..5821ba5 --- /dev/null +++ b/argus/crates/argus-sentinel/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "argus-sentinel" +version.workspace = true +edition.workspace = true + +[dependencies] +argus-kernel.workspace = true +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["sync", "time"] } diff --git a/argus/crates/argus-sentinel/src/breaker.rs b/argus/crates/argus-sentinel/src/breaker.rs new file mode 100644 index 0000000..657b712 --- /dev/null +++ b/argus/crates/argus-sentinel/src/breaker.rs @@ -0,0 +1,146 @@ +use std::collections::VecDeque; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct Evidence { + pub tier: u8, + pub signal: f64, + pub confidence: f64, + pub timestamp: Instant, +} + +#[derive(Clone, Debug)] +pub enum BreakerState { + Closed, + Open(Vec), + HalfOpen, +} + +impl BreakerState { + pub fn is_open(&self) -> bool { + matches!(self, Self::Open(_)) + } + + pub fn is_closed(&self) -> bool { + matches!(self, Self::Closed) + } + + pub fn is_half_open(&self) -> bool { + matches!(self, Self::HalfOpen) + } +} + +pub struct CircuitBreaker { + threshold: usize, + window: Duration, + events: VecDeque, + state: BreakerState, +} + +impl CircuitBreaker { + pub fn new(threshold: usize, window: Duration) -> Self { + Self { + threshold, + window, + events: VecDeque::new(), + state: BreakerState::Closed, + } + } + + pub fn record_escalation(&mut self, evidence: Evidence) { + self.prune_expired(); + self.events.push_back(evidence); + if self.events.len() >= self.threshold { + let trail: Vec = self.events.iter().cloned().collect(); + self.state = BreakerState::Open(trail); + } + } + + pub fn state(&self) -> &BreakerState { + &self.state + } + + pub fn is_open(&self) -> bool { + self.state.is_open() + } + + pub fn acknowledge(&mut self) { + if self.state.is_open() { + self.state = BreakerState::HalfOpen; + } + } + + pub fn reset(&mut self) { + self.state = BreakerState::Closed; + self.events.clear(); + } + + fn prune_expired(&mut self) { + let cutoff = Instant::now() - self.window; + while let Some(front) = self.events.front() { + if front.timestamp < cutoff { + self.events.pop_front(); + } else { + break; + } + } + } + + pub fn evidence(&self) -> &VecDeque { + &self.events + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_evidence(signal: f64) -> Evidence { + Evidence { + tier: 2, + signal, + confidence: 0.9, + timestamp: Instant::now(), + } + } + + #[test] + fn breaker_stays_closed_under_threshold() { + let mut breaker = CircuitBreaker::new(3, Duration::from_secs(60)); + breaker.record_escalation(make_evidence(0.5)); + breaker.record_escalation(make_evidence(0.6)); + assert!(breaker.state().is_closed()); + } + + #[test] + fn breaker_trips_at_threshold() { + let mut breaker = CircuitBreaker::new(3, Duration::from_secs(60)); + breaker.record_escalation(make_evidence(0.5)); + breaker.record_escalation(make_evidence(0.6)); + breaker.record_escalation(make_evidence(0.7)); + assert!(breaker.is_open()); + if let BreakerState::Open(trail) = breaker.state() { + assert_eq!(trail.len(), 3); + } else { + panic!("expected Open state with evidence"); + } + } + + #[test] + fn breaker_acknowledge_moves_to_half_open() { + let mut breaker = CircuitBreaker::new(1, Duration::from_secs(60)); + breaker.record_escalation(make_evidence(0.9)); + assert!(breaker.is_open()); + breaker.acknowledge(); + assert!(breaker.state().is_half_open()); + } + + #[test] + fn breaker_reset_clears() { + let mut breaker = CircuitBreaker::new(1, Duration::from_secs(60)); + breaker.record_escalation(make_evidence(0.9)); + breaker.reset(); + assert!(breaker.state().is_closed()); + assert!(breaker.evidence().is_empty()); + } +} diff --git a/argus/crates/argus-sentinel/src/classifier.rs b/argus/crates/argus-sentinel/src/classifier.rs new file mode 100644 index 0000000..d575241 --- /dev/null +++ b/argus/crates/argus-sentinel/src/classifier.rs @@ -0,0 +1,84 @@ +use argus_kernel::ConfLevel; + +#[derive(Clone, Debug)] +pub struct TaintSignal { + pub suspicion: f64, + pub taint_level: Option, + pub markers: Vec, +} + +impl TaintSignal { + pub fn from_suspicion(suspicion: f64, markers: Vec) -> Self { + let taint_level = if suspicion < 0.3 { + None + } else if suspicion < 0.6 { + Some(ConfLevel::Internal) + } else if suspicion < 0.8 { + Some(ConfLevel::Sensitive) + } else { + Some(ConfLevel::Restricted) + }; + Self { + suspicion, + taint_level, + markers, + } + } +} + +pub trait DivergenceClassifier: Send + Sync { + fn classify(&self, reasoning_trace: &str) -> TaintSignal; +} + +pub struct MockClassifier { + fixed_score: f64, +} + +impl MockClassifier { + pub fn new(fixed_score: f64) -> Self { + Self { fixed_score } + } +} + +impl DivergenceClassifier for MockClassifier { + fn classify(&self, _reasoning_trace: &str) -> TaintSignal { + TaintSignal::from_suspicion(self.fixed_score, vec!["mock".to_owned()]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn clean_signal_no_taint() { + let signal = TaintSignal::from_suspicion(0.1, vec![]); + assert!(signal.taint_level.is_none()); + } + + #[test] + fn moderate_suspicion_internal_taint() { + let signal = TaintSignal::from_suspicion(0.4, vec![]); + assert_eq!(signal.taint_level, Some(ConfLevel::Internal)); + } + + #[test] + fn high_suspicion_sensitive_taint() { + let signal = TaintSignal::from_suspicion(0.7, vec![]); + assert_eq!(signal.taint_level, Some(ConfLevel::Sensitive)); + } + + #[test] + fn extreme_suspicion_restricted_taint() { + let signal = TaintSignal::from_suspicion(0.9, vec![]); + assert_eq!(signal.taint_level, Some(ConfLevel::Restricted)); + } + + #[test] + fn mock_classifier_returns_fixed_score() { + let classifier = MockClassifier::new(0.5); + let signal = classifier.classify("any trace"); + assert!((signal.suspicion - 0.5).abs() < f64::EPSILON); + assert_eq!(signal.taint_level, Some(ConfLevel::Internal)); + } +} diff --git a/argus/crates/argus-sentinel/src/lib.rs b/argus/crates/argus-sentinel/src/lib.rs new file mode 100644 index 0000000..bad418c --- /dev/null +++ b/argus/crates/argus-sentinel/src/lib.rs @@ -0,0 +1,7 @@ +mod breaker; +mod classifier; +mod session; + +pub use breaker::{BreakerState, CircuitBreaker, Evidence}; +pub use classifier::{DivergenceClassifier, MockClassifier, TaintSignal}; +pub use session::{SessionAnalyzer, UserIntent}; diff --git a/argus/crates/argus-sentinel/src/session.rs b/argus/crates/argus-sentinel/src/session.rs new file mode 100644 index 0000000..a5f6185 --- /dev/null +++ b/argus/crates/argus-sentinel/src/session.rs @@ -0,0 +1,105 @@ +use std::collections::BTreeSet; +use std::time::{Duration, Instant}; + +use crate::breaker::{BreakerState, CircuitBreaker, Evidence}; +use crate::classifier::TaintSignal; + +#[derive(Clone, Debug)] +pub struct UserIntent { + pub goal: String, + pub expected_tools: BTreeSet, + pub expected_scope: Vec, +} + +pub struct SessionAnalyzer { + intent: Option, + breaker: CircuitBreaker, + taint_escalations: Vec, +} + +impl SessionAnalyzer { + pub fn new(breaker_threshold: usize, breaker_window: Duration) -> Self { + Self { + intent: None, + breaker: CircuitBreaker::new(breaker_threshold, breaker_window), + taint_escalations: Vec::new(), + } + } + + pub fn update_intent(&mut self, intent: UserIntent) { + self.intent = Some(intent); + } + + pub fn intent(&self) -> Option<&UserIntent> { + self.intent.as_ref() + } + + pub fn record_taint_signal(&mut self, signal: TaintSignal) { + let evidence = Evidence { + tier: if signal.suspicion >= 0.8 { 1 } else { 2 }, + signal: signal.suspicion, + confidence: signal.suspicion, + timestamp: Instant::now(), + }; + self.breaker.record_escalation(evidence); + self.taint_escalations.push(signal); + } + + pub fn breaker_state(&self) -> &BreakerState { + self.breaker.state() + } + + pub fn is_breaker_open(&self) -> bool { + self.breaker.is_open() + } + + pub fn acknowledge_breaker(&mut self) { + self.breaker.acknowledge(); + } + + pub fn taint_escalations(&self) -> &[TaintSignal] { + &self.taint_escalations + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn session_tracks_intent() { + let mut session = SessionAnalyzer::new(3, Duration::from_secs(60)); + assert!(session.intent().is_none()); + + let intent = UserIntent { + goal: "refactor module".to_owned(), + expected_tools: BTreeSet::from(["read_file".to_owned(), "write_file".to_owned()]), + expected_scope: vec!["src/".to_owned()], + }; + session.update_intent(intent); + assert_eq!(session.intent().unwrap().goal, "refactor module"); + } + + #[test] + fn session_feeds_breaker() { + let mut session = SessionAnalyzer::new(2, Duration::from_secs(60)); + let signal = TaintSignal::from_suspicion(0.7, vec!["suspicious".to_owned()]); + session.record_taint_signal(signal); + assert!(!session.is_breaker_open()); + + let signal = TaintSignal::from_suspicion(0.8, vec!["very_suspicious".to_owned()]); + session.record_taint_signal(signal); + assert!(session.is_breaker_open()); + } + + #[test] + fn session_acknowledge_breaker() { + let mut session = SessionAnalyzer::new(1, Duration::from_secs(60)); + let signal = TaintSignal::from_suspicion(0.9, vec!["alert".to_owned()]); + session.record_taint_signal(signal); + assert!(session.is_breaker_open()); + + session.acknowledge_breaker(); + assert!(session.breaker_state().is_half_open()); + } +} diff --git a/argus/formal/Makefile b/argus/formal/Makefile new file mode 100644 index 0000000..1d57d60 --- /dev/null +++ b/argus/formal/Makefile @@ -0,0 +1,14 @@ +COQPROJECT := _CoqProject +COQMAKEFILE := Makefile.coq + +all: $(COQMAKEFILE) + $(MAKE) -f $(COQMAKEFILE) + +$(COQMAKEFILE): $(COQPROJECT) + coq_makefile -f $(COQPROJECT) -o $(COQMAKEFILE) + +clean: + -$(MAKE) -f $(COQMAKEFILE) clean + rm -f $(COQMAKEFILE) $(COQMAKEFILE).conf + +.PHONY: all clean diff --git a/argus/formal/Makefile.coq b/argus/formal/Makefile.coq new file mode 100644 index 0000000..b359082 --- /dev/null +++ b/argus/formal/Makefile.coq @@ -0,0 +1,965 @@ +########################################################################## +## # The Rocq Prover / The Rocq Development Team ## +## v # Copyright INRIA, CNRS and contributors ## +## /dev/null 2>/dev/null; echo $$?)) +STDTIME?=command time -f $(TIMEFMT) +else +ifeq (0,$(shell gtime -f "" true >/dev/null 2>/dev/null; echo $$?)) +STDTIME?=gtime -f $(TIMEFMT) +else +STDTIME?=command time +endif +endif +else +STDTIME?=command time -f $(TIMEFMT) +endif + +COQBIN?= +ifneq (,$(COQBIN)) +# add an ending / +COQBIN:=$(COQBIN)/ +endif + +# Coq binaries +ROCQ ?= "$(COQBIN)rocq" +COQC ?= "$(COQBIN)rocq" c +COQTOP ?= "$(COQBIN)rocq" repl +COQCHK ?= "$(COQBIN)rocqchk" +COQNATIVE ?= "$(COQBIN)rocq" native-precompile +COQDEP ?= "$(COQBIN)rocq" dep +COQDOC ?= "$(COQBIN)rocq" doc +COQPP ?= "$(COQBIN)rocq" pp-mlg +COQMKFILE ?= "$(COQBIN)rocq" makefile +OCAMLLIBDEP ?= "$(COQBIN)ocamllibdep" + +# Timing scripts +COQMAKE_ONE_TIME_FILE ?= "$(COQCORELIB)/tools/make-one-time-file.py" +COQMAKE_BOTH_TIME_FILES ?= "$(COQCORELIB)/tools/make-both-time-files.py" +COQMAKE_BOTH_SINGLE_TIMING_FILES ?= "$(COQCORELIB)/tools/make-both-single-timing-files.py" +BEFORE ?= +AFTER ?= + +# OCaml binaries +CAMLC ?= "$(OCAMLFIND)" ocamlc -c +CAMLOPTC ?= "$(OCAMLFIND)" opt -c +CAMLLINK ?= "$(OCAMLFIND)" ocamlc -linkall +CAMLOPTLINK ?= "$(OCAMLFIND)" opt -linkall +CAMLDOC ?= "$(OCAMLFIND)" ocamldoc +CAMLDEP ?= "$(OCAMLFIND)" ocamldep -slash -ml-synonym .mlpack + +# DESTDIR is prepended to all installation paths +DESTDIR ?= + +# Debug builds, typically -g to OCaml, -debug to Rocq. +CAMLDEBUG ?= +COQDEBUG ?= + +# Extra packages to be linked in (as in findlib -package) +CAMLPKGS ?= +FINDLIBPKGS = -package rocq-runtime.plugins.ltac $(CAMLPKGS) + +# Option for making timing files +TIMING?= +# Option for changing sorting of timing output file +TIMING_SORT_BY ?= auto +# Option for changing the fuzz parameter on the output file +TIMING_FUZZ ?= 0 +# Option for changing whether to use real or user time for timing tables +TIMING_REAL?= +# Option for including the memory column(s) +TIMING_INCLUDE_MEM?= +# Option for sorting by the memory column +TIMING_SORT_BY_MEM?= +# Output file names for timed builds +TIME_OF_BUILD_FILE ?= time-of-build.log +TIME_OF_BUILD_BEFORE_FILE ?= time-of-build-before.log +TIME_OF_BUILD_AFTER_FILE ?= time-of-build-after.log +TIME_OF_PRETTY_BUILD_FILE ?= time-of-build-pretty.log +TIME_OF_PRETTY_BOTH_BUILD_FILE ?= time-of-build-both.log +TIME_OF_PRETTY_BUILD_EXTRA_FILES ?= - # also output to the command line + +TGTS ?= + +# Retro compatibility (DESTDIR is standard on Unix, DSTROOT is not) +ifdef DSTROOT +DESTDIR := $(DSTROOT) +endif + +# Substitution of the path by appending $(DESTDIR) if needed. +# The variable $(COQMF_WINDRIVE) can be needed for Cygwin environments. +windrive_path = $(if $(COQMF_WINDRIVE),$(subst $(COQMF_WINDRIVE),/,$(1)),$(1)) +destination_path = $(if $(DESTDIR),$(DESTDIR)/$(call windrive_path,$(1)),$(1)) + +# Installation paths of libraries and documentation. +COQLIBINSTALL ?= $(call destination_path,$(COQLIB)/user-contrib) +COQDOCINSTALL ?= $(call destination_path,$(DOCDIR)/coq/user-contrib) +COQPLUGININSTALL ?= $(call destination_path,$(COQCORELIB)/..) +COQTOPINSTALL ?= $(call destination_path,$(COQLIB)/toploop) # FIXME: Unused variable? + +# findlib files installation +FINDLIBPREINST= mkdir -p "$(COQPLUGININSTALL)/" +FINDLIBDESTDIR= -destdir "$(COQPLUGININSTALL)/" + +# we need to move out of sight $(METAFILE) otherwise findlib thinks the +# package is already installed +findlib_install = \ + $(HIDE)if [ "$(METAFILE)" ]; then \ + $(FINDLIBPREINST) && \ + mv "$(METAFILE)" "$(METAFILE).skip" ; \ + "$(OCAMLFIND)" install $(2) $(FINDLIBDESTDIR) $(FINDLIBPACKAGE) $(1); \ + rc=$$?; \ + mv "$(METAFILE).skip" "$(METAFILE)"; \ + exit $$rc; \ + fi +findlib_remove = \ + $(HIDE)if [ ! -z "$(METAFILE)" ]; then\ + "$(OCAMLFIND)" remove $(FINDLIBDESTDIR) $(FINDLIBPACKAGE); \ + fi + + +########## End of parameters ################################################## +# What follows may be relevant to you only if you need to +# extend this Makefile. If so, look for 'Extension point' here and +# put in Makefile.coq.local double colon rules accordingly. +# E.g. to perform some work after the all target completes you can write +# +# post-all:: +# echo "All done!" +# +# in Makefile.coq.local +# +############################################################################### + + + + +# Flags ####################################################################### +# +# We define a bunch of variables combining the parameters. +# To add additional flags to coq, coqchk or coqdoc, set the +# {COQ,COQCHK,COQDOC}EXTRAFLAGS variable to whatever you want to add. +# To overwrite the default choice and set your own flags entirely, set the +# {COQ,COQCHK,COQDOC}FLAGS variable. + +SHOW := $(if $(VERBOSE),@true "",@echo "") +HIDE := $(if $(VERBOSE),,@) + +TIMER=$(if $(TIMED), $(STDTIME), $(TIMECMD)) + +OPT?= + +# The DYNLIB variable is used by "coqdep -dyndep var" in .v.d +ifeq '$(OPT)' '-byte' +USEBYTE:=true +DYNLIB:=.cma +else +USEBYTE:= +DYNLIB:=.cmxs +endif + +# these variables are meant to be overridden if you want to add *extra* flags +COQEXTRAFLAGS?= +COQCHKEXTRAFLAGS?= +COQDOCEXTRAFLAGS?= + +# Find the last argument of the form "-native-compiler FLAG" +COQUSERNATIVEFLAG:=$(strip \ +$(subst -native-compiler-,,\ +$(lastword \ +$(filter -native-compiler-%,\ +$(subst -native-compiler ,-native-compiler-,\ +$(strip $(COQEXTRAFLAGS))))))) + +COQFILTEREDEXTRAFLAGS:=$(strip \ +$(filter-out -native-compiler-%,\ +$(subst -native-compiler ,-native-compiler-,\ +$(strip $(COQEXTRAFLAGS))))) + +COQACTUALNATIVEFLAG:=$(lastword $(COQMF_COQ_NATIVE_COMPILER_DEFAULT) $(COQMF_COQPROJECTNATIVEFLAG) $(COQUSERNATIVEFLAG)) + +ifeq '$(COQACTUALNATIVEFLAG)' 'yes' + COQNATIVEFLAG="-w" "-deprecated-native-compiler-option" "-native-compiler" "ondemand" + COQDONATIVE="yes" +else +ifeq '$(COQACTUALNATIVEFLAG)' 'ondemand' + COQNATIVEFLAG="-w" "-deprecated-native-compiler-option" "-native-compiler" "ondemand" + COQDONATIVE="no" +else + COQNATIVEFLAG="-w" "-deprecated-native-compiler-option" "-native-compiler" "no" + COQDONATIVE="no" +endif +endif + +# these flags do NOT contain the libraries, to make them easier to overwrite +COQFLAGS?=-q $(OTHERFLAGS) $(COQFILTEREDEXTRAFLAGS) $(COQNATIVEFLAG) +COQCHKFLAGS?=-silent -o $(COQCHKEXTRAFLAGS) +COQDOCFLAGS?=-interpolate -utf8 $(COQDOCEXTRAFLAGS) + +COQDOCLIBS?=$(COQLIBS_NOML) + +# The version of Coq being run and the version of rocq makefile that +# generated this makefile +# NB --print-version is not in the rocq shim +COQ_VERSION:=$(shell $(ROCQ) c --print-version | cut -d " " -f 1) +COQMAKEFILE_VERSION:=9.0.1 + +# COQ_SRC_SUBDIRS is for user-overriding, usually to add +# `user-contrib/Foo` to the includes, we keep COQCORE_SRC_SUBDIRS for +# Coq's own core libraries, which should be replaced by ocamlfind +# options at some point. +COQ_SRC_SUBDIRS?= +COQSRCLIBS?= $(foreach d,$(COQ_SRC_SUBDIRS), -I "$(COQLIB)/$(d)") + +CAMLFLAGS+=$(OCAMLLIBS) $(COQSRCLIBS) +# ocamldoc fails with unknown argument otherwise +CAMLDOCFLAGS:=$(filter-out -annot, $(filter-out -bin-annot, $(CAMLFLAGS))) +CAMLFLAGS+=$(OCAMLWARN) + +ifneq (,$(TIMING)) + ifeq (after,$(TIMING)) + TIMING_EXT=after-timing + else + ifeq (before,$(TIMING)) + TIMING_EXT=before-timing + else + TIMING_EXT=timing + endif + endif + TIMING_ARG=-time-file $<.$(TIMING_EXT) +else + TIMING_ARG= +endif + +ifneq (,$(PROFILING)) + PROFILE_ARG=-profile $@.prof.json + PROFILE_ZIP=gzip -f $@.prof.json +else + PROFILE_ARG= + PROFILE_ZIP=true +endif + +# Files ####################################################################### +# +# We here define a bunch of variables about the files being part of the +# Rocq project in order to ease the writing of build target and build rules + +VDFILE := .Makefile.coq.d + +ALLSRCFILES := \ + $(MLGFILES) \ + $(MLFILES) \ + $(MLPACKFILES) \ + $(MLLIBFILES) \ + $(MLIFILES) + +# helpers +vo_to_obj = $(addsuffix .o,\ + $(filter-out Warning: Error:,\ + $(shell $(COQTOP) -q -noinit -batch -quiet -print-mod-uid $(1)))) +strip_dotslash = $(patsubst ./%,%,$(1)) + +# without this we get undefined variables in the expansion for the +# targets of the [deprecated,use-mllib-or-mlpack] rule +with_undef = $(if $(filter-out undefined, $(origin $(1))),$($(1))) + +VO = vo +VOS = vos + +VOFILES = $(VFILES:.v=.$(VO)) +GLOBFILES = $(VFILES:.v=.glob) +HTMLFILES = $(VFILES:.v=.html) +GHTMLFILES = $(VFILES:.v=.g.html) +BEAUTYFILES = $(addsuffix .beautified,$(VFILES)) +TEXFILES = $(VFILES:.v=.tex) +GTEXFILES = $(VFILES:.v=.g.tex) +CMOFILES = \ + $(MLGFILES:.mlg=.cmo) \ + $(MLFILES:.ml=.cmo) \ + $(MLPACKFILES:.mlpack=.cmo) +CMXFILES = $(CMOFILES:.cmo=.cmx) +OFILES = $(CMXFILES:.cmx=.o) +CMAFILES = $(MLLIBFILES:.mllib=.cma) $(MLPACKFILES:.mlpack=.cma) +CMXAFILES = $(CMAFILES:.cma=.cmxa) +CMIFILES = \ + $(CMOFILES:.cmo=.cmi) \ + $(MLIFILES:.mli=.cmi) +# the /if/ is because old _CoqProject did not list a .ml(pack|lib) but just +# a .mlg file +CMXSFILES = \ + $(MLPACKFILES:.mlpack=.cmxs) \ + $(CMXAFILES:.cmxa=.cmxs) \ + $(if $(MLPACKFILES)$(CMXAFILES),,\ + $(MLGFILES:.mlg=.cmxs) $(MLFILES:.ml=.cmxs)) + +# files that are packed into a plugin (no extension) +PACKEDFILES = \ + $(call strip_dotslash, \ + $(foreach lib, \ + $(call strip_dotslash, \ + $(MLPACKFILES:.mlpack=_MLPACK_DEPENDENCIES)),$(call with_undef,$(lib)))) +# files that are archived into a .cma (mllib) +LIBEDFILES = \ + $(call strip_dotslash, \ + $(foreach lib, \ + $(call strip_dotslash, \ + $(MLLIBFILES:.mllib=_MLLIB_DEPENDENCIES)),$(call with_undef,$(lib)))) +CMIFILESTOINSTALL = $(filter-out $(addsuffix .cmi,$(PACKEDFILES)),$(CMIFILES)) +CMOFILESTOINSTALL = $(filter-out $(addsuffix .cmo,$(PACKEDFILES)),$(CMOFILES)) +OBJFILES = $(call vo_to_obj,$(VOFILES)) +ALLNATIVEFILES = \ + $(OBJFILES:.o=.cmi) \ + $(OBJFILES:.o=.cmx) \ + $(OBJFILES:.o=.cmxs) +FINDLIBPACKAGE=$(patsubst .%,%,$(suffix $(METAFILE))) + +# trick: wildcard filters out non-existing files, so that `install` doesn't show +# warnings and `clean` doesn't pass to rm a list of files that is too long for +# the shell. +NATIVEFILES = $(wildcard $(ALLNATIVEFILES)) +FILESTOINSTALL = \ + $(VOFILES) \ + $(VFILES) \ + $(GLOBFILES) \ + $(NATIVEFILES) +FINDLIBFILESTOINSTALL = \ + $(CMIFILESTOINSTALL) +ifeq '$(HASNATDYNLINK)' 'true' +DO_NATDYNLINK = yes +FINDLIBFILESTOINSTALL += $(CMXSFILES) $(CMXAFILES) $(CMOFILESTOINSTALL:.cmo=.cmx) +else +DO_NATDYNLINK = +endif + +ALLDFILES = $(addsuffix .d,$(ALLSRCFILES)) $(VDFILE) + +# Compilation targets ######################################################### + +all: + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" pre-all + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" real-all + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" post-all +.PHONY: all + +all.timing.diff: + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" pre-all + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" real-all.timing.diff TIME_OF_PRETTY_BUILD_EXTRA_FILES="" + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" post-all +.PHONY: all.timing.diff + +ifeq (0,$(TIMING_REAL)) +TIMING_REAL_ARG := +TIMING_USER_ARG := --user +else +ifeq (1,$(TIMING_REAL)) +TIMING_REAL_ARG := --real +TIMING_USER_ARG := +else +TIMING_REAL_ARG := +TIMING_USER_ARG := +endif +endif + +ifeq (0,$(TIMING_INCLUDE_MEM)) +TIMING_INCLUDE_MEM_ARG := --no-include-mem +else +TIMING_INCLUDE_MEM_ARG := +endif + +ifeq (1,$(TIMING_SORT_BY_MEM)) +TIMING_SORT_BY_MEM_ARG := --sort-by-mem +else +TIMING_SORT_BY_MEM_ARG := +endif + +make-pretty-timed-before:: TIME_OF_BUILD_FILE=$(TIME_OF_BUILD_BEFORE_FILE) +make-pretty-timed-after:: TIME_OF_BUILD_FILE=$(TIME_OF_BUILD_AFTER_FILE) +make-pretty-timed make-pretty-timed-before make-pretty-timed-after:: + $(HIDE)rm -f pretty-timed-success.ok + $(HIDE)($(MAKE) --no-print-directory -f "$(PARENT)" $(TGTS) TIMED=1 2>&1 && touch pretty-timed-success.ok) | tee -a $(TIME_OF_BUILD_FILE) + $(HIDE)rm pretty-timed-success.ok # must not be -f; must fail if the touch failed +print-pretty-timed:: + $(HIDE)$(COQMAKE_ONE_TIME_FILE) $(TIMING_INCLUDE_MEM_ARG) $(TIMING_SORT_BY_MEM_ARG) $(TIMING_REAL_ARG) $(TIME_OF_BUILD_FILE) $(TIME_OF_PRETTY_BUILD_FILE) $(TIME_OF_PRETTY_BUILD_EXTRA_FILES) +print-pretty-timed-diff:: + $(HIDE)$(COQMAKE_BOTH_TIME_FILES) --sort-by=$(TIMING_SORT_BY) $(TIMING_INCLUDE_MEM_ARG) $(TIMING_SORT_BY_MEM_ARG) $(TIMING_REAL_ARG) $(TIME_OF_BUILD_AFTER_FILE) $(TIME_OF_BUILD_BEFORE_FILE) $(TIME_OF_PRETTY_BOTH_BUILD_FILE) $(TIME_OF_PRETTY_BUILD_EXTRA_FILES) +ifeq (,$(BEFORE)) +print-pretty-single-time-diff:: + @echo 'Error: Usage: $(MAKE) print-pretty-single-time-diff AFTER=path/to/file.v.after-timing BEFORE=path/to/file.v.before-timing' + $(HIDE)false +else +ifeq (,$(AFTER)) +print-pretty-single-time-diff:: + @echo 'Error: Usage: $(MAKE) print-pretty-single-time-diff AFTER=path/to/file.v.after-timing BEFORE=path/to/file.v.before-timing' + $(HIDE)false +else +print-pretty-single-time-diff:: + $(HIDE)$(COQMAKE_BOTH_SINGLE_TIMING_FILES) --fuzz=$(TIMING_FUZZ) --sort-by=$(TIMING_SORT_BY) $(TIMING_USER_ARG) $(AFTER) $(BEFORE) $(TIME_OF_PRETTY_BUILD_FILE) $(TIME_OF_PRETTY_BUILD_EXTRA_FILES) +endif +endif +pretty-timed: + $(HIDE)$(MAKE) --no-print-directory -f "$(PARENT)" make-pretty-timed + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" print-pretty-timed +.PHONY: pretty-timed make-pretty-timed make-pretty-timed-before make-pretty-timed-after print-pretty-timed print-pretty-timed-diff print-pretty-single-time-diff + +# Extension points for actions to be performed before/after the all target +pre-all:: + @# Extension point + $(HIDE)if [ "$(COQMAKEFILE_VERSION)" != "$(COQ_VERSION)" ]; then\ + echo "W: This Makefile was generated by Rocq/Coq $(COQMAKEFILE_VERSION)";\ + echo "W: while the current Rocq version is $(COQ_VERSION)";\ + fi +.PHONY: pre-all + +post-all:: + @# Extension point +.PHONY: post-all + +real-all: $(VOFILES) $(if $(USEBYTE),bytefiles,optfiles) +.PHONY: real-all + +real-all.timing.diff: $(VOFILES:.vo=.v.timing.diff) +.PHONY: real-all.timing.diff + +bytefiles: $(CMOFILES) $(CMAFILES) +.PHONY: bytefiles + +optfiles: $(if $(DO_NATDYNLINK),$(CMXSFILES)) +.PHONY: optfiles + +vos: $(VOFILES:%.vo=%.vos) +.PHONY: vos + +vok: $(VOFILES:%.vo=%.vok) +.PHONY: vok + +validate: $(VOFILES) + $(TIMER) $(COQCHK) $(COQCHKFLAGS) $(COQLIBS_NOML) $(PROFILE_ARG) $^ + $(HIDE)$(PROFILE_ZIP) +.PHONY: validate + +only: $(TGTS) +.PHONY: only + +# Documentation targets ####################################################### + +html: $(GLOBFILES) $(VFILES) + $(SHOW)'COQDOC -d html $(GAL)' + $(HIDE)mkdir -p html + $(HIDE)$(COQDOC) \ + -toc $(COQDOCFLAGS) -html $(GAL) $(COQDOCLIBS) -d html $(VFILES) + +mlihtml: $(MLIFILES:.mli=.cmi) + $(SHOW)'CAMLDOC -d $@' + $(HIDE)mkdir $@ || rm -rf $@/* + $(HIDE)$(CAMLDOC) -html \ + -d $@ -m A $(CAMLDEBUG) $(CAMLDOCFLAGS) $(MLIFILES) $(FINDLIBPKGS) + +all-mli.tex: $(MLIFILES:.mli=.cmi) + $(SHOW)'CAMLDOC -latex $@' + $(HIDE)$(CAMLDOC) -latex \ + -o $@ -m A $(CAMLDEBUG) $(CAMLDOCFLAGS) $(MLIFILES) $(FINDLIBPKGS) + +all.ps: $(VFILES) + $(SHOW)'COQDOC -ps $(GAL)' + $(HIDE)$(COQDOC) \ + -toc $(COQDOCFLAGS) -ps $(GAL) $(COQDOCLIBS) \ + -o $@ `$(COQDEP) -sort $(VFILES)` + +all.pdf: $(VFILES) + $(SHOW)'COQDOC -pdf $(GAL)' + $(HIDE)$(COQDOC) \ + -toc $(COQDOCFLAGS) -pdf $(GAL) $(COQDOCLIBS) \ + -o $@ `$(COQDEP) -sort $(VFILES)` + +# FIXME: not quite right, since the output name is different +gallinahtml: GAL=-g +gallinahtml: html + +all-gal.ps: GAL=-g +all-gal.ps: all.ps + +all-gal.pdf: GAL=-g +all-gal.pdf: all.pdf + +# ? +beautify: $(BEAUTYFILES) + for file in $^; do mv $${file%.beautified} $${file%beautified}old && mv $${file} $${file%.beautified}; done + @echo 'Do not do "make clean" until you are sure that everything went well!' + @echo 'If there were a problem, execute "for file in $$(find . -name \*.v.old -print); do mv $${file} $${file%.old}; done" in your shell/' +.PHONY: beautify + +# Installation targets ######################################################## +# +# There rules can be extended in Makefile.coq.local +# Extensions can't assume when they run. + +# We use $(file) to avoid generating a very long command string to pass to the shell +# (cf https://coq.zulipchat.com/#narrow/stream/250632-Coq-Platform-devs-.26-users/topic/Strange.20command.20length.20limit.20on.20Linux) +# However Apple ships old make which doesn't have $(file) so we need a fallback +$(file >.hasfile,1) +HASFILE:=$(shell if [ -e .hasfile ]; then echo 1; rm .hasfile; fi) + +MKFILESTOINSTALL= $(if $(HASFILE),$(file >.filestoinstall,$(FILESTOINSTALL)),\ + $(shell rm -f .filestoinstall) \ + $(foreach x,$(FILESTOINSTALL),$(shell printf '%s\n' "$x" >> .filestoinstall))) + +# findlib needs the package to not be installed, so we remove it before +# installing it (see the call to findlib_remove) +install: META + @$(MKFILESTOINSTALL) + $(HIDE)code=0; for f in $$(cat .filestoinstall); do\ + if ! [ -f "$$f" ]; then >&2 echo $$f does not exist; code=1; fi \ + done; exit $$code + $(HIDE)for f in $$(cat .filestoinstall); do\ + df="`$(COQMKFILE) -destination-of "$$f" $(COQLIBS)`";\ + if [ "$$?" != "0" -o -z "$$df" ]; then\ + echo SKIP "$$f" since it has no logical path;\ + else\ + install -d "$(COQLIBINSTALL)/$$df" &&\ + install -m 0644 "$$f" "$(COQLIBINSTALL)/$$df" &&\ + echo INSTALL "$$f" "$(COQLIBINSTALL)/$$df";\ + fi;\ + done + $(call findlib_remove) + $(call findlib_install, META $(FINDLIBFILESTOINSTALL)) + $(HIDE)$(MAKE) install-extra -f "$(SELF)" + @rm -f .filestoinstall +install-extra:: + @# Extension point +.PHONY: install install-extra + +META: $(METAFILE) + $(HIDE)if [ "$(METAFILE)" ]; then \ + cat "$(METAFILE)" | grep -v 'directory.*=.*' > META; \ + fi + +install-byte: + $(call findlib_install, $(CMAFILES) $(CMOFILESTOINSTALL), -add) + +install-doc:: html mlihtml + @# Extension point + $(HIDE)install -d "$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/html" + $(HIDE)for i in html/*; do \ + dest="$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/$$i";\ + install -m 0644 "$$i" "$$dest";\ + echo INSTALL "$$i" "$$dest";\ + done + $(HIDE)install -d \ + "$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/mlihtml" + $(HIDE)for i in mlihtml/*; do \ + dest="$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/$$i";\ + install -m 0644 "$$i" "$$dest";\ + echo INSTALL "$$i" "$$dest";\ + done +.PHONY: install-doc + +uninstall:: + @# Extension point + @$(MKFILESTOINSTALL) + $(call findlib_remove) + $(HIDE)for f in $$(cat .filestoinstall); do \ + df="`$(COQMKFILE) -destination-of "$$f" $(COQLIBS)`" &&\ + instf="$(COQLIBINSTALL)/$$df/`basename $$f`" &&\ + rm -f "$$instf" &&\ + echo RM "$$instf" ;\ + done + $(HIDE)for f in $$(cat .filestoinstall); do \ + df="`$(COQMKFILE) -destination-of "$$f" $(COQLIBS)`" &&\ + echo RMDIR "$(COQLIBINSTALL)/$$df/" &&\ + (rmdir "$(COQLIBINSTALL)/$$df/" 2>/dev/null || true); \ + done + @rm -f .filestoinstall + +.PHONY: uninstall + +uninstall-doc:: + @# Extension point + $(SHOW)'RM $(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/html' + $(HIDE)rm -rf "$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/html" + $(SHOW)'RM $(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/mlihtml' + $(HIDE)rm -rf "$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/mlihtml" + $(HIDE) rmdir "$(COQDOCINSTALL)/$(INSTALLCOQDOCROOT)/" || true +.PHONY: uninstall-doc + +# Cleaning #################################################################### +# +# There rules can be extended in Makefile.coq.local +# Extensions can't assume when they run. + +clean:: + @# Extension point + $(SHOW)'CLEAN' + $(HIDE)rm -f $(CMOFILES) + $(HIDE)rm -f $(CMIFILES) + $(HIDE)rm -f $(CMAFILES) + $(HIDE)rm -f $(CMXFILES) + $(HIDE)rm -f $(CMXAFILES) + $(HIDE)rm -f $(CMXSFILES) + $(HIDE)rm -f $(OFILES) + $(HIDE)rm -f $(CMXAFILES:.cmxa=.a) + $(HIDE)rm -f $(MLGFILES:.mlg=.ml) + $(HIDE)rm -f $(CMXFILES:.cmx=.cmt) + $(HIDE)rm -f $(MLIFILES:.mli=.cmti) + $(HIDE)rm -f $(ALLDFILES) + $(HIDE)rm -f $(NATIVEFILES) + $(HIDE)find . -name .coq-native -type d -empty -delete + $(HIDE)rm -f $(VOFILES) + $(HIDE)rm -f $(VOFILES:.vo=.vos) + $(HIDE)rm -f $(VOFILES:.vo=.vok) + $(HIDE)rm -f $(VOFILES:.vo=.vo.prof.json) + $(HIDE)rm -f $(VOFILES:.vo=.vo.prof.json.gz) + $(HIDE)rm -f $(BEAUTYFILES) $(VFILES:=.old) + $(HIDE)rm -f all.ps all-gal.ps all.pdf all-gal.pdf all.glob all-mli.tex + $(HIDE)rm -f $(VFILES:.v=.glob) + $(HIDE)rm -f $(VFILES:.v=.tex) + $(HIDE)rm -f $(VFILES:.v=.g.tex) + $(HIDE)rm -f pretty-timed-success.ok + $(HIDE)rm -f META + $(HIDE)rm -rf html mlihtml +.PHONY: clean + +cleanall:: clean + @# Extension point + $(SHOW)'CLEAN *.aux *.timing' + $(HIDE)rm -f $(foreach f,$(VFILES:.v=),$(dir $(f)).$(notdir $(f)).aux) + $(HIDE)rm -f $(TIME_OF_BUILD_FILE) $(TIME_OF_BUILD_BEFORE_FILE) $(TIME_OF_BUILD_AFTER_FILE) $(TIME_OF_PRETTY_BUILD_FILE) $(TIME_OF_PRETTY_BOTH_BUILD_FILE) + $(HIDE)rm -f $(VOFILES:.vo=.v.timing) + $(HIDE)rm -f $(VOFILES:.vo=.v.before-timing) + $(HIDE)rm -f $(VOFILES:.vo=.v.after-timing) + $(HIDE)rm -f $(VOFILES:.vo=.v.timing.diff) + $(HIDE)rm -f .lia.cache .nia.cache +.PHONY: cleanall + +archclean:: + @# Extension point + $(SHOW)'CLEAN *.cmx *.o' + $(HIDE)rm -f $(NATIVEFILES) + $(HIDE)rm -f $(CMOFILES:%.cmo=%.cmx) +.PHONY: archclean + + +# Compilation rules ########################################################### + +$(MLIFILES:.mli=.cmi): %.cmi: %.mli + $(SHOW)'CAMLC -c $<' + $(HIDE)$(TIMER) $(CAMLC) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) $< + +$(MLGFILES:.mlg=.ml): %.ml: %.mlg + $(SHOW)'COQPP $<' + $(HIDE)$(COQPP) $< + +# Stupid hack around a deficient syntax: we cannot concatenate two expansions +$(filter %.cmo, $(MLFILES:.ml=.cmo) $(MLGFILES:.mlg=.cmo)): %.cmo: %.ml + $(SHOW)'CAMLC -c $<' + $(HIDE)$(TIMER) $(CAMLC) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) $< + +# Same hack +$(filter %.cmx, $(MLFILES:.ml=.cmx) $(MLGFILES:.mlg=.cmx)): %.cmx: %.ml + $(SHOW)'CAMLOPT -c $(FOR_PACK) $<' + $(HIDE)$(TIMER) $(CAMLOPTC) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) $(FOR_PACK) $< + + +$(MLLIBFILES:.mllib=.cmxs): %.cmxs: %.cmxa + $(SHOW)'CAMLOPT -shared -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) \ + -shared -o $@ $< + +$(MLLIBFILES:.mllib=.cma): %.cma: | %.mllib + $(SHOW)'CAMLC -a -o $@' + $(HIDE)$(TIMER) $(CAMLLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -a -o $@ $^ + +$(MLLIBFILES:.mllib=.cmxa): %.cmxa: | %.mllib + $(SHOW)'CAMLOPT -a -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -a -o $@ $^ + + +$(MLPACKFILES:.mlpack=.cmxs): %.cmxs: %.cmxa + $(SHOW)'CAMLOPT -shared -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) \ + -shared -o $@ $< + +$(MLPACKFILES:.mlpack=.cmxa): %.cmxa: %.cmx | %.mlpack + $(SHOW)'CAMLOPT -a -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -a -o $@ $< + +$(MLPACKFILES:.mlpack=.cma): %.cma: %.cmo | %.mlpack + $(SHOW)'CAMLC -a -o $@' + $(HIDE)$(TIMER) $(CAMLLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -a -o $@ $^ + +$(MLPACKFILES:.mlpack=.cmo): %.cmo: | %.mlpack + $(SHOW)'CAMLC -pack -o $@' + $(HIDE)$(TIMER) $(CAMLLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -pack -o $@ $^ + +$(MLPACKFILES:.mlpack=.cmx): %.cmx: | %.mlpack + $(SHOW)'CAMLOPT -pack -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) -pack -o $@ $^ + +# This rule is for _CoqProject with no .mllib nor .mlpack +$(filter-out $(MLLIBFILES:.mllib=.cmxs) $(MLPACKFILES:.mlpack=.cmxs) $(addsuffix .cmxs,$(PACKEDFILES)) $(addsuffix .cmxs,$(LIBEDFILES)),$(MLFILES:.ml=.cmxs) $(MLGFILES:.mlg=.cmxs)): %.cmxs: %.cmx + $(SHOW)'[deprecated,use-mllib-or-mlpack] CAMLOPT -shared -o $@' + $(HIDE)$(TIMER) $(CAMLOPTLINK) $(CAMLDEBUG) $(CAMLFLAGS) $(FINDLIBPKGS) \ + -shared -o $@ $< + +# can't make +# https://www.gnu.org/software/make/manual/make.html#Static-Pattern +# work with multiple target rules +# so use eval in a loop instead +# with grouped targets https://www.gnu.org/software/make/manual/make.html#Multiple-Targets +# if available (GNU Make >= 4.3) +ifneq (,$(filter grouped-target,$(.FEATURES))) +define globvorule= + +# take care to $$ variables using $< etc + $(1).vo $(1).glob &: $(1).v | $$(VDFILE) + $$(SHOW)ROCQ compile $(1).v + $$(HIDE)$$(TIMER) $$(ROCQ) compile $$(COQDEBUG) $$(TIMING_ARG) $$(PROFILE_ARG) $$(COQFLAGS) $$(COQLIBS) $(1).v + $$(HIDE)$$(PROFILE_ZIP) +ifeq ($(COQDONATIVE), "yes") + $$(SHOW)COQNATIVE $(1).vo + $$(HIDE)$$(call TIMER,$(1).vo.native) $$(COQNATIVE) $$(COQLIBS) $(1).vo +endif + +endef +else + +$(VOFILES): %.vo: %.v | $(VDFILE) + $(SHOW)ROCQ compile $< + $(HIDE)$(TIMER) $(ROCQ) compile $(COQDEBUG) $(TIMING_ARG) $(PROFILE_ARG) $(COQFLAGS) $(COQLIBS) $< + $(HIDE)$(PROFILE_ZIP) +ifeq ($(COQDONATIVE), "yes") + $(SHOW)COQNATIVE $@ + $(HIDE)$(call TIMER,$@.native) $(COQNATIVE) $(COQLIBS) $@ +endif + +# this is broken :( todo fix if we ever find a solution that doesn't need grouped targets +$(GLOBFILES): %.glob: %.v + $(SHOW)'ROCQ compile $< (for .glob)' + $(HIDE)$(TIMER) $(ROCQ) compile $(COQDEBUG) $(COQFLAGS) $(COQLIBS) $< + +endif + +$(foreach vfile,$(VFILES:.v=),$(eval $(call globvorule,$(vfile)))) + +$(VFILES:.v=.vos): %.vos: %.v + $(SHOW)ROCQ compile -vos $< + $(HIDE)$(TIMER) $(ROCQ) compile -vos $(COQDEBUG) $(COQFLAGS) $(COQLIBS) $< + +$(VFILES:.v=.vok): %.vok: %.v + $(SHOW)ROCQ compile -vok $< + $(HIDE)$(TIMER) $(ROCQ) compile -vok $(COQDEBUG) $(COQFLAGS) $(COQLIBS) $< + +$(addsuffix .timing.diff,$(VFILES)): %.timing.diff : %.before-timing %.after-timing + $(SHOW)PYTHON TIMING-DIFF $*.{before,after}-timing + $(HIDE)$(MAKE) --no-print-directory -f "$(SELF)" print-pretty-single-time-diff BEFORE=$*.before-timing AFTER=$*.after-timing TIME_OF_PRETTY_BUILD_FILE="$@" + +$(BEAUTYFILES): %.v.beautified: %.v + $(SHOW)'BEAUTIFY $<' + $(HIDE)$(TIMER) $(ROCQ) compile $(COQDEBUG) $(COQFLAGS) $(COQLIBS) -beautify $< + +$(TEXFILES): %.tex: %.v + $(SHOW)'COQDOC -latex $<' + $(HIDE)$(COQDOC) $(COQDOCFLAGS) -latex $< -o $@ + +$(GTEXFILES): %.g.tex: %.v + $(SHOW)'COQDOC -latex -g $<' + $(HIDE)$(COQDOC) $(COQDOCFLAGS) -latex -g $< -o $@ + +$(HTMLFILES): %.html: %.v %.glob + $(SHOW)'COQDOC -html $<' + $(HIDE)$(COQDOC) $(COQDOCFLAGS) -html $< -o $@ + +$(GHTMLFILES): %.g.html: %.v %.glob + $(SHOW)'COQDOC -html -g $<' + $(HIDE)$(COQDOC) $(COQDOCFLAGS) -html -g $< -o $@ + +# Dependency files ############################################################ + +ifndef MAKECMDGOALS + -include $(ALLDFILES) +else + ifneq ($(filter-out archclean clean cleanall printenv make-pretty-timed make-pretty-timed-before make-pretty-timed-after print-pretty-timed print-pretty-timed-diff print-pretty-single-time-diff,$(MAKECMDGOALS)),) + -include $(ALLDFILES) + endif +endif + +.SECONDARY: $(ALLDFILES) + +redir_if_ok = > "$@" || ( RV=$$?; rm -f "$@"; exit $$RV ) + +GENMLFILES:=$(MLGFILES:.mlg=.ml) +$(addsuffix .d,$(ALLSRCFILES)): $(GENMLFILES) + +$(addsuffix .d,$(MLIFILES)): %.mli.d: %.mli + $(SHOW)'CAMLDEP $<' + $(HIDE)$(CAMLDEP) $(OCAMLLIBS) "$<" $(redir_if_ok) + +$(addsuffix .d,$(MLGFILES)): %.mlg.d: %.ml + $(SHOW)'CAMLDEP $<' + $(HIDE)$(CAMLDEP) $(OCAMLLIBS) "$<" $(redir_if_ok) + +$(addsuffix .d,$(MLFILES)): %.ml.d: %.ml + $(SHOW)'CAMLDEP $<' + $(HIDE)$(CAMLDEP) $(OCAMLLIBS) "$<" $(redir_if_ok) + +$(addsuffix .d,$(MLLIBFILES)): %.mllib.d: %.mllib + $(SHOW)'OCAMLLIBDEP $<' + $(HIDE)$(OCAMLLIBDEP) -c $(OCAMLLIBS) "$<" $(redir_if_ok) + +$(addsuffix .d,$(MLPACKFILES)): %.mlpack.d: %.mlpack + $(SHOW)'OCAMLLIBDEP $<' + $(HIDE)$(OCAMLLIBDEP) -c $(OCAMLLIBS) "$<" $(redir_if_ok) + +# If this makefile is created using a _CoqProject we have coqdep get +# options from it. This avoids argument length limits for pathological +# projects. Note that extra options might be on the command line. +VDFILE_FLAGS:=$(if _CoqProject,-f _CoqProject,) $(CMDLINE_COQLIBS) $(CMDLINE_VFILES) + +$(VDFILE): _CoqProject $(VFILES) + $(SHOW)'ROCQ DEP VFILES' + $(HIDE)$(TIMER) $(COQDEP) -vos -dyndep var $(VDFILE_FLAGS) $(redir_if_ok) + +# Misc ######################################################################## + +byte: + $(HIDE)$(MAKE) all "OPT:=-byte" -f "$(SELF)" +.PHONY: byte + +opt: + $(HIDE)$(MAKE) all "OPT:=-opt" -f "$(SELF)" +.PHONY: opt + +# This is deprecated. To extend this makefile use +# extension points and Makefile.coq.local +printenv:: + $(warning printenv is deprecated) + $(warning write extensions in Makefile.coq.local or include Makefile.coq.conf) + @echo 'COQLIB = $(COQLIB)' + @echo 'COQCORELIB = $(COQCORELIB)' + @echo 'DOCDIR = $(DOCDIR)' + @echo 'OCAMLFIND = $(OCAMLFIND)' + @echo 'HASNATDYNLINK = $(HASNATDYNLINK)' + @echo 'SRC_SUBDIRS = $(SRC_SUBDIRS)' + @echo 'COQ_SRC_SUBDIRS = $(COQ_SRC_SUBDIRS)' + @echo 'COQCORE_SRC_SUBDIRS = $(COQCORE_SRC_SUBDIRS)' + @echo 'OCAMLFIND = $(OCAMLFIND)' + @echo 'PP = $(PP)' + @echo 'COQFLAGS = $(COQFLAGS)' + @echo 'COQLIB = $(COQLIBS)' + @echo 'COQLIBINSTALL = $(COQLIBINSTALL)' + @echo 'COQDOCINSTALL = $(COQDOCINSTALL)' +.PHONY: printenv + +# Generate a .merlin file. If you need to append directives to this +# file you can extend the merlin-hook target in Makefile.coq.local +.merlin: + $(SHOW)'FILL .merlin' + $(HIDE)echo 'FLG $(COQMF_CAMLFLAGS)' > .merlin + $(HIDE)echo 'B $(COQCORELIB)' >> .merlin + $(HIDE)echo 'S $(COQCORELIB)' >> .merlin + $(HIDE)$(foreach d,$(COQCORE_SRC_SUBDIRS), \ + echo 'B $(COQCORELIB)$(d)' >> .merlin;) + $(HIDE)$(foreach d,$(COQ_SRC_SUBDIRS), \ + echo 'S $(COQLIB)$(d)' >> .merlin;) + $(HIDE)$(foreach d,$(SRC_SUBDIRS), echo 'B $(d)' >> .merlin;) + $(HIDE)$(foreach d,$(SRC_SUBDIRS), echo 'S $(d)' >> .merlin;) + $(HIDE)$(MAKE) merlin-hook -f "$(SELF)" +.PHONY: merlin + +merlin-hook:: + @# Extension point +.PHONY: merlin-hook + +# prints all variables +debug: + $(foreach v,\ + $(sort $(filter-out $(INITIAL_VARS) INITIAL_VARS,\ + $(.VARIABLES))),\ + $(info $(v) = $($(v)))) +.PHONY: debug + +.DEFAULT_GOAL := all + +# Users can create Makefile.coq.local-late to hook into double-colon rules +# or add other needed Makefile code, using defined +# variables if necessary. +-include Makefile.coq.local-late + +# Local Variables: +# mode: makefile-gmake +# End: diff --git a/argus/formal/Makefile.coq.conf b/argus/formal/Makefile.coq.conf new file mode 100644 index 0000000..8c1f837 --- /dev/null +++ b/argus/formal/Makefile.coq.conf @@ -0,0 +1,71 @@ +# This configuration file was generated by running: +# coq_makefile -f _CoqProject -o Makefile.coq + +COQBIN?= +ifneq (,$(COQBIN)) +# add an ending / +COQBIN:=$(COQBIN)/ +endif +COQMKFILE ?= "$(COQBIN)rocq" makefile + +############################################################################### +# # +# Project files. # +# # +############################################################################### + +COQMF_CMDLINE_VFILES := +COQMF_SOURCES := $(shell $(COQMKFILE) -sources-of -f _CoqProject $(COQMF_CMDLINE_VFILES)) +COQMF_VFILES := $(filter %.v, $(COQMF_SOURCES)) +COQMF_MLIFILES := $(filter %.mli, $(COQMF_SOURCES)) +COQMF_MLFILES := $(filter %.ml, $(COQMF_SOURCES)) +COQMF_MLGFILES := $(filter %.mlg, $(COQMF_SOURCES)) +COQMF_MLPACKFILES := $(filter %.mlpack, $(COQMF_SOURCES)) +COQMF_MLLIBFILES := $(filter %.mllib, $(COQMF_SOURCES)) +COQMF_METAFILE = + +############################################################################### +# # +# Path directives (-I, -R, -Q). # +# # +############################################################################### + +COQMF_OCAMLLIBS = +COQMF_SRC_SUBDIRS = +COQMF_COQLIBS = -R . ArgusKernel +COQMF_COQLIBS_NOML = -R . ArgusKernel +COQMF_CMDLINE_COQLIBS = + +############################################################################### +# # +# Rocq configuration. # +# # +############################################################################### + +COQMF_COQLIB=/Users/ygorcastor/.opam/rocq-of-rust/lib/coq/ +COQMF_COQCORELIB=/Users/ygorcastor/.opam/rocq-of-rust/lib/coq/../rocq-runtime/ +COQMF_DOCDIR=/Users/ygorcastor/.opam/rocq-of-rust/share/doc/ +COQMF_OCAMLFIND=/Users/ygorcastor/.opam/rocq-of-rust/bin/ocamlfind +COQMF_CAMLFLAGS=-thread -bin-annot -strict-sequence -w -a+1..3-4+5..8-9+10..26-27+28..39-40-41-42+43-44-45+46..47-48+49..57-58+59..66-67-68+69-70 +COQMF_WARN=-warn-error +a-3 +COQMF_HASNATDYNLINK=true +COQMF_COQ_SRC_SUBDIRS=boot config lib clib kernel library engine pretyping interp gramlib parsing proofs tactics toplevel printing ide stm vernac plugins/btauto plugins/cc plugins/derive plugins/extraction plugins/firstorder plugins/funind plugins/ltac plugins/ltac2 plugins/ltac2_ltac1 plugins/micromega plugins/nsatz plugins/ring plugins/rtauto plugins/ssr plugins/ssrmatching plugins/syntax +COQMF_COQ_NATIVE_COMPILER_DEFAULT=no +COQMF_WINDRIVE= + +############################################################################### +# # +# Native compiler. # +# # +############################################################################### + +COQMF_COQPROJECTNATIVEFLAG = + +############################################################################### +# # +# Extra variables. # +# # +############################################################################### + +COQMF_OTHERFLAGS = '-impredicative-set' +COQMF_INSTALLCOQDOCROOT = ArgusKernel diff --git a/argus/formal/_CoqProject b/argus/formal/_CoqProject new file mode 100644 index 0000000..6976236 --- /dev/null +++ b/argus/formal/_CoqProject @@ -0,0 +1,15 @@ +-R . ArgusKernel +-arg -impredicative-set + +axioms/BTreeAxioms.v +axioms/DecidableAxioms.v +axioms/OracleAxioms.v + +spec/TzimtzumV2.v +spec/TzimtzumV2_safety.v +spec/TzimtzumV2_proofs.v + +refinement/ConcreteSpec.v +refinement/StateRelation.v +refinement/Simulation.v +refinement/Soundness.v diff --git a/argus/formal/axioms/BTreeAxioms.v b/argus/formal/axioms/BTreeAxioms.v new file mode 100644 index 0000000..612ab47 --- /dev/null +++ b/argus/formal/axioms/BTreeAxioms.v @@ -0,0 +1,191 @@ +(** Axiomatized interface for Rust's BTreeSet and BTreeMap. + Trust boundary: we assume these operations match their + mathematical set/map counterparts. *) + +From Stdlib Require Import Bool. + +Module BTreeSet. + Parameter t : Type -> Type. + + Parameter empty : forall {A : Type}, t A. + Parameter insert : forall {A : Type}, A -> t A -> t A. + Parameter remove : forall {A : Type}, A -> t A -> t A. + Parameter mem : forall {A : Type}, A -> t A -> bool. + Parameter is_empty : forall {A : Type}, t A -> bool. + Parameter union : forall {A : Type}, t A -> t A -> t A. + + Section Axioms. + Variable A : Type. + Variable eq_dec : forall (x y : A), {x = y} + {x <> y}. + + Axiom mem_empty : forall (x : A), + mem x empty = false. + + Axiom mem_insert : forall (x y : A) (s : t A), + mem x (insert y s) = (if eq_dec x y then true else mem x s). + + Axiom mem_remove : forall (x y : A) (s : t A), + mem x (remove y s) = (if eq_dec x y then false else mem x s). + + Axiom mem_union : forall (x : A) (s1 s2 : t A), + mem x (union s1 s2) = mem x s1 || mem x s2. + + Axiom is_empty_empty : + is_empty (@empty A) = true. + + Axiom is_empty_insert : forall (x : A) (s : t A), + is_empty (insert x s) = false. + End Axioms. +End BTreeSet. + +Module BTreeMap. + Parameter t : Type -> Type -> Type. + + Parameter empty : forall {K V : Type}, t K V. + Parameter insert : forall {K V : Type}, K -> V -> t K V -> t K V. + Parameter remove : forall {K V : Type}, K -> t K V -> t K V. + Parameter get : forall {K V : Type}, K -> t K V -> option V. + Parameter contains_key : forall {K V : Type}, K -> t K V -> bool. + + Parameter remove_by_value : forall {K V : Type}, V -> t K V -> t K V. + + Section Axioms. + Variable K V : Type. + + Axiom get_empty : forall (k : K), + get k (@empty K V) = None. + + Axiom get_insert_same : forall (k : K) (v : V) (m : t K V), + get k (insert k v m) = Some v. + + Axiom get_insert_other : forall (k k' : K) (v : V) (m : t K V), + k <> k' -> get k (insert k' v m) = get k m. + + Axiom get_remove_same : forall (k : K) (m : t K V), + get k (remove k m) = None. + + Axiom get_remove_other : forall (k k' : K) (m : t K V), + k <> k' -> get k (remove k' m) = get k m. + + Axiom contains_key_spec : forall (k : K) (m : t K V), + contains_key k m = match get k m with Some _ => true | None => false end. + End Axioms. + + Section RemoveByValueAxioms. + Variable K V : Type. + Variable eq_dec_v : forall (x y : V), {x = y} + {x <> y}. + + Axiom get_remove_by_value_hit : forall (k : K) (v : V) (m : t K V), + get k m = Some v -> + get k (remove_by_value v m) = None. + + Axiom get_remove_by_value_miss : forall (k : K) (v v' : V) (m : t K V), + get k m = Some v' -> v <> v' -> + get k (remove_by_value v m) = Some v'. + + Axiom get_remove_by_value_none : forall (k : K) (v : V) (m : t K V), + get k m = None -> + get k (remove_by_value v m) = None. + End RemoveByValueAxioms. + + Section NestedOps. + Variable K V : Type. + + Definition mem_nested (k : K) (v : V) (m : t K (BTreeSet.t V)) : bool := + match get k m with + | Some s => BTreeSet.mem v s + | None => false + end. + + Definition insert_nested (k : K) (v : V) (m : t K (BTreeSet.t V)) + : t K (BTreeSet.t V) := + match get k m with + | Some s => insert k (BTreeSet.insert v s) m + | None => insert k (BTreeSet.insert v BTreeSet.empty) m + end. + + Definition union_nested (k : K) (vs : BTreeSet.t V) + (m : t K (BTreeSet.t V)) : t K (BTreeSet.t V) := + match get k m with + | Some s => insert k (BTreeSet.union s vs) m + | None => insert k vs m + end. + + Definition remove_key_nested (k : K) (m : t K (BTreeSet.t V)) + : t K (BTreeSet.t V) := + remove k m. + End NestedOps. + + Section NestedLemmas. + Variable K V : Type. + Variable eq_dec_k : forall (x y : K), {x = y} + {x <> y}. + Variable eq_dec_v : forall (x y : V), {x = y} + {x <> y}. + + Lemma mem_nested_empty : forall (k : K) (v : V), + mem_nested K V k v (@empty K (BTreeSet.t V)) = false. + Proof. + intros. unfold mem_nested. rewrite get_empty. reflexivity. + Qed. + + Lemma mem_nested_insert_same : forall k v1 v2 m, + mem_nested K V k v1 (insert_nested K V k v2 m) = + (if eq_dec_v v1 v2 then true else mem_nested K V k v1 m). + Proof. + intros. unfold mem_nested, insert_nested. + destruct (get k m) eqn:Hget; + rewrite get_insert_same; + rewrite (BTreeSet.mem_insert V eq_dec_v); + destruct (eq_dec_v v1 v2); auto. + rewrite BTreeSet.mem_empty. reflexivity. + Qed. + + Lemma mem_nested_insert_other : forall k1 k2 v1 v2 m, + k1 <> k2 -> + mem_nested K V k1 v1 (insert_nested K V k2 v2 m) = + mem_nested K V k1 v1 m. + Proof. + intros k1 k2 v1 v2 m Hneq. unfold mem_nested, insert_nested. + destruct (get k2 m) eqn:Hget; + (rewrite get_insert_other; [reflexivity | exact Hneq]). + Qed. + + Lemma mem_nested_union_same : forall k v vs m, + mem_nested K V k v (union_nested K V k vs m) = + mem_nested K V k v m || BTreeSet.mem v vs. + Proof. + intros. unfold mem_nested, union_nested. + destruct (get k m) eqn:Hget. + - rewrite get_insert_same. + rewrite BTreeSet.mem_union. + reflexivity. + - rewrite get_insert_same. + reflexivity. + Qed. + + Lemma mem_nested_union_other : forall k1 k2 v vs m, + k1 <> k2 -> + mem_nested K V k1 v (union_nested K V k2 vs m) = + mem_nested K V k1 v m. + Proof. + intros k1 k2 v vs m Hneq. unfold mem_nested, union_nested. + destruct (get k2 m) eqn:Hget; + (rewrite get_insert_other; [reflexivity | exact Hneq]). + Qed. + + Lemma mem_nested_remove_key_same : forall k v m, + mem_nested K V k v (remove_key_nested K V k m) = false. + Proof. + intros. unfold mem_nested, remove_key_nested. + rewrite get_remove_same. reflexivity. + Qed. + + Lemma mem_nested_remove_key_other : forall k1 k2 v m, + k1 <> k2 -> + mem_nested K V k1 v (remove_key_nested K V k2 m) = + mem_nested K V k1 v m. + Proof. + intros k1 k2 v m Hneq. unfold mem_nested, remove_key_nested. + rewrite get_remove_other; [reflexivity | exact Hneq]. + Qed. + End NestedLemmas. +End BTreeMap. diff --git a/argus/formal/axioms/DecidableAxioms.v b/argus/formal/axioms/DecidableAxioms.v new file mode 100644 index 0000000..78762e9 --- /dev/null +++ b/argus/formal/axioms/DecidableAxioms.v @@ -0,0 +1,55 @@ +(** Decidable equality and ordering for all kernel domain types. + These correspond to Rust's PartialEq/Eq/Ord derives on + AgentId, ToolId, InvocationId, CapKind, EgressKind, ConfLevel. + + Ported from TzimtzumV2.lean: types (lines 60-66), + ordering axioms (lines 191-203), named constants (lines 85-89). *) + +From Stdlib Require Import Bool. + +(** --- Uninterpreted sorts --- *) +Parameter AgentId : Type. +Parameter ToolId : Type. +Parameter InvocationId : Type. +Parameter CapKind : Type. +Parameter EgressKind : Type. +Parameter ConfLevel : Type. + +(** --- Decidable equality --- *) +Axiom AgentId_eq_dec : forall (x y : AgentId), {x = y} + {x <> y}. +Axiom ToolId_eq_dec : forall (x y : ToolId), {x = y} + {x <> y}. +Axiom InvocationId_eq_dec : forall (x y : InvocationId), {x = y} + {x <> y}. +Axiom CapKind_eq_dec : forall (x y : CapKind), {x = y} + {x <> y}. +Axiom EgressKind_eq_dec : forall (x y : EgressKind), {x = y} + {x <> y}. +Axiom ConfLevel_eq_dec : forall (x y : ConfLevel), {x = y} + {x <> y}. + +(** --- Named constants --- *) +Parameter cl_public : ConfLevel. +Parameter cl_internal : ConfLevel. +Parameter cl_sensitive : ConfLevel. +Parameter cl_restricted : ConfLevel. +Parameter root_agent : AgentId. + +(** --- ConfLevel ordering (total order) --- *) +Parameter le_conf : ConfLevel -> ConfLevel -> bool. + +Axiom conf_refl : forall L, le_conf L L = true. +Axiom conf_trans : forall L1 L2 L3, + le_conf L1 L2 = true -> le_conf L2 L3 = true -> le_conf L1 L3 = true. +Axiom conf_antisym : forall L1 L2, + le_conf L1 L2 = true -> le_conf L2 L1 = true -> L1 = L2. +Axiom conf_total : forall L1 L2, + le_conf L1 L2 = true \/ le_conf L2 L1 = true. + +(** Chain: public < internal < sensitive < restricted *) +Axiom conf_chain_01 : le_conf cl_public cl_internal = true. +Axiom conf_chain_12 : le_conf cl_internal cl_sensitive = true. +Axiom conf_chain_23 : le_conf cl_sensitive cl_restricted = true. + +(** Distinctness (prevents SMT-style collapsing) *) +Axiom cl_distinct_01 : cl_public <> cl_internal. +Axiom cl_distinct_02 : cl_public <> cl_sensitive. +Axiom cl_distinct_03 : cl_public <> cl_restricted. +Axiom cl_distinct_12 : cl_internal <> cl_sensitive. +Axiom cl_distinct_13 : cl_internal <> cl_restricted. +Axiom cl_distinct_23 : cl_sensitive <> cl_restricted. diff --git a/argus/formal/axioms/OracleAxioms.v b/argus/formal/axioms/OracleAxioms.v new file mode 100644 index 0000000..9849548 --- /dev/null +++ b/argus/formal/axioms/OracleAxioms.v @@ -0,0 +1,15 @@ +(** Oracle axioms for AuthorizerOracle and ContentGateOracle. + These are immutable background theory (not part of mutable state). + + In Rust (traits.rs): + trait AuthorizerOracle { fn allows(&self, agent: &AgentId, tool: &ToolId) -> bool; } + trait ContentGateOracle { fn passes(&self, agent: &AgentId, tool: &ToolId) -> bool; } + + In Lean (TzimtzumV2.lean:144-145): + immutable relation authorizer_allows : AgentId -> ToolId -> Bool + immutable relation content_gate_passes : AgentId -> ToolId -> Bool *) + +Require Import ArgusKernel.axioms.DecidableAxioms. + +Parameter authorizer_allows : AgentId -> ToolId -> bool. +Parameter content_gate_passes : AgentId -> ToolId -> bool. diff --git a/argus/formal/extracted/background.v b/argus/formal/extracted/background.v new file mode 100644 index 0000000..c813253 --- /dev/null +++ b/argus/formal/extracted/background.v @@ -0,0 +1,2118 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module background. + (* StructRecord + { + name := "ToolMetadata"; + const_params := []; + ty_params := []; + fields := + [ + ("capabilities", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" ]); + ("egress", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::EgressKind"; Ty.path "alloc::alloc::Global" ]); + ("conf_floor", Ty.path "argus_kernel::types::ConfLevel"); + ("endorsed", Ty.path "bool") + ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_background_ToolMetadata. + Definition Self : Ty.t := Ty.path "argus_kernel::background::ToolMetadata". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + self + |) in + Value.mkStructRecord + "argus_kernel::background::ToolMetadata" + [] + [] + [ + ("capabilities", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "capabilities" + |) + |) + |) + |) + ] + |)); + ("egress", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::EgressKind"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::EgressKind"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + |) + |) + ] + |)); + ("conf_floor", + M.call_closure (| + Ty.path "argus_kernel::types::ConfLevel", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ConfLevel", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + |) + |) + ] + |)); + ("endorsed", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "bool", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + |) + |) + ] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_background_ToolMetadata. + + Module Impl_core_fmt_Debug_for_argus_kernel_background_ToolMetadata. + Definition Self : Ty.t := Ty.path "argus_kernel::background::ToolMetadata". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field4_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "ToolMetadata" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "capabilities" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "capabilities" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "egress" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::EgressKind"; Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "conf_floor" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "endorsed" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.apply (Ty.path "&") [] [ Ty.path "bool" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "bool" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_background_ToolMetadata. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_background_ToolMetadata. + Definition Self : Ty.t := Ty.path "argus_kernel::background::ToolMetadata". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_background_ToolMetadata. + + Module Impl_core_cmp_PartialEq_argus_kernel_background_ToolMetadata_for_argus_kernel_background_ToolMetadata. + Definition Self : Ty.t := Ty.path "argus_kernel::background::ToolMetadata". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + other + |) in + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "capabilities" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::background::ToolMetadata", + "capabilities" + |) + |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::EgressKind"; Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::EgressKind"; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::types::ConfLevel", + [], + [ Ty.path "argus_kernel::types::ConfLevel" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + ] + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::background::ToolMetadata" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_background_ToolMetadata_for_argus_kernel_background_ToolMetadata. + + Module Impl_core_cmp_Eq_for_argus_kernel_background_ToolMetadata. + Definition Self : Ty.t := Ty.path "argus_kernel::background::ToolMetadata". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_background_ToolMetadata. + + (* + Enum FlowMode + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "Allow"; + item := StructTuple []; + }; + { + name := "Inspect"; + item := StructTuple []; + }; + { + name := "Deny"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_FlowMode_Allow : + M.IsDiscriminant "argus_kernel::background::FlowMode::Allow" 0. + Axiom IsDiscriminant_FlowMode_Inspect : + M.IsDiscriminant "argus_kernel::background::FlowMode::Inspect" 1. + Axiom IsDiscriminant_FlowMode_Deny : + M.IsDiscriminant "argus_kernel::background::FlowMode::Deny" 2. + + Module Impl_core_clone_Clone_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ], + self + |) in + M.read (| M.deref (| M.read (| self |) |) |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_background_FlowMode. + + Module Impl_core_marker_Copy_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + Axiom Implements : + M.IsTraitInstance + "core::marker::Copy" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_Copy_for_argus_kernel_background_FlowMode. + + Module Impl_core_fmt_Debug_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Allow" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Allow" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Inspect" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Inspect" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Deny" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Deny" |) |) |))) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_background_FlowMode. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_background_FlowMode. + + Module Impl_core_cmp_PartialEq_argus_kernel_background_FlowMode_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::background::FlowMode" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::background::FlowMode" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::background::FlowMode" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_background_FlowMode_for_argus_kernel_background_FlowMode. + + Module Impl_core_cmp_Eq_for_argus_kernel_background_FlowMode. + Definition Self : Ty.t := Ty.path "argus_kernel::background::FlowMode". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ], + self + |) in + Value.Tuple [])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_background_FlowMode. + + (* StructRecord + { + name := "BackgroundTheory"; + const_params := []; + ty_params := []; + fields := + [ + ("tools", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ]); + ("flow_policy", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ]); + ("flow_overrides", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ]) + ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_background_BackgroundTheory. + Definition Self : Ty.t := Ty.path "argus_kernel::background::BackgroundTheory". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + Value.mkStructRecord + "argus_kernel::background::BackgroundTheory" + [] + [] + [ + ("tools", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "tools" + |) + |) + |) + |) + ] + |)); + ("flow_policy", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_policy" + |) + |) + |) + |) + ] + |)); + ("flow_overrides", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_overrides" + |) + |) + |) + |) + ] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_background_BackgroundTheory. + + Module Impl_core_fmt_Debug_for_argus_kernel_background_BackgroundTheory. + Definition Self : Ty.t := Ty.path "argus_kernel::background::BackgroundTheory". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field3_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "BackgroundTheory" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "tools" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "tools" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "flow_policy" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_policy" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "flow_overrides" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ] + ] + ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_overrides" + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_background_BackgroundTheory. + + Module Impl_argus_kernel_background_BackgroundTheory. + Definition Self : Ty.t := Ty.path "argus_kernel::background::BackgroundTheory". + + (* + pub fn has_tool(&self, tool: &ToolId) -> bool { + self.tools.contains_key(tool) + } + *) + Definition has_tool (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; tool ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + let tool := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + tool + |) in + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + "contains_key", + [], + [ Ty.path "argus_kernel::types::ToolId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "tools" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool |) |) |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_has_tool : M.IsAssociatedFunction.C Self "has_tool" has_tool. + Admitted. + Global Typeclasses Opaque has_tool. + + (* + pub fn tool_metadata(&self, tool: &ToolId) -> Option<&ToolMetadata> { + self.tools.get(tool) + } + *) + Definition tool_metadata (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; tool ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + let tool := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + tool + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ] ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::ToolId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "tools" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool |) |) |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_tool_metadata : + M.IsAssociatedFunction.C Self "tool_metadata" tool_metadata. + Admitted. + Global Typeclasses Opaque tool_metadata. + + (* + pub fn flow_mode(&self, level: ConfLevel, egress: EgressKind) -> FlowMode { + self.flow_policy + .get(&(level, egress)) + .copied() + .unwrap_or(FlowMode::Deny) + } + *) + Definition flow_mode (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; level; egress ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + let level := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", level |) in + let egress := M.alloc (| Ty.path "argus_kernel::types::EgressKind", egress |) in + M.call_closure (| + Ty.path "argus_kernel::background::FlowMode", + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::FlowMode" ], + "unwrap_or", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::FlowMode" ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ] ], + "copied", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::FlowMode" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_policy" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ], + Value.Tuple [ M.read (| level |); M.read (| egress |) ] + |) + |) + |) + |) + ] + |) + ] + |); + Value.StructTuple "argus_kernel::background::FlowMode::Deny" [] [] [] + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_flow_mode : + M.IsAssociatedFunction.C Self "flow_mode" flow_mode. + Admitted. + Global Typeclasses Opaque flow_mode. + + (* + pub fn has_flow_override(&self, agent: &AgentId, tool: &ToolId, level: ConfLevel) -> bool { + self.flow_overrides + .contains(&(agent.clone(), tool.clone(), level)) + } + *) + Definition has_flow_override (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; agent; tool; level ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + let agent := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + agent + |) in + let tool := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + tool + |) in + let level := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", level |) in + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "flow_overrides" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ], + Value.Tuple + [ + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) ] + |); + M.call_closure (| + Ty.path "argus_kernel::types::ToolId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ToolId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool |) |) |) ] + |); + M.read (| level |) + ] + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_has_flow_override : + M.IsAssociatedFunction.C Self "has_flow_override" has_flow_override. + Admitted. + Global Typeclasses Opaque has_flow_override. + + (* + pub fn registered_tools(&self) -> impl Iterator { + self.tools.keys() + } + *) + Definition registered_tools (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + self + |) in + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::Keys") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + "keys", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheory", + "tools" + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_registered_tools : + M.IsAssociatedFunction.C Self "registered_tools" registered_tools. + Admitted. + Global Typeclasses Opaque registered_tools. + End Impl_argus_kernel_background_BackgroundTheory. + + (* StructRecord + { + name := "BackgroundTheoryBuilder"; + const_params := []; + ty_params := []; + fields := + [ + ("tools", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ]); + ("flow_policy", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ]); + ("flow_overrides", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ]) + ]; + } *) + + Module Impl_argus_kernel_background_BackgroundTheoryBuilder. + Definition Self : Ty.t := Ty.path "argus_kernel::background::BackgroundTheoryBuilder". + + (* + pub fn new() -> Self { + Self { + tools: BTreeMap::new(), + flow_policy: BTreeMap::new(), + flow_overrides: BTreeSet::new(), + } + } + *) + Definition new (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [] => + ltac:(M.monadic + (Value.mkStructRecord + "argus_kernel::background::BackgroundTheoryBuilder" + [] + [] + [ + ("tools", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("flow_policy", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("flow_overrides", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : M.IsAssociatedFunction.C Self "new" new. + Admitted. + Global Typeclasses Opaque new. + + (* + pub fn register_tool(&mut self, id: ToolId, metadata: ToolMetadata) -> &mut Self { + self.tools.insert(id, metadata); + self + } + *) + Definition register_tool (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; id; metadata ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + self + |) in + let id := M.alloc (| Ty.path "argus_kernel::types::ToolId", id |) in + let metadata := + M.alloc (| Ty.path "argus_kernel::background::ToolMetadata", metadata |) in + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.read (| + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::background::ToolMetadata"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheoryBuilder", + "tools" + |) + |); + M.read (| id |); + M.read (| metadata |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |) + |) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_register_tool : + M.IsAssociatedFunction.C Self "register_tool" register_tool. + Admitted. + Global Typeclasses Opaque register_tool. + + (* + pub fn set_flow( + &mut self, + level: ConfLevel, + egress: EgressKind, + mode: FlowMode, + ) -> &mut Self { + self.flow_policy.insert((level, egress), mode); + self + } + *) + Definition set_flow (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; level; egress; mode ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + self + |) in + let level := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", level |) in + let egress := M.alloc (| Ty.path "argus_kernel::types::EgressKind", egress |) in + let mode := M.alloc (| Ty.path "argus_kernel::background::FlowMode", mode |) in + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.read (| + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::FlowMode" ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::background::FlowMode" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "argus_kernel::types::EgressKind" + ]; + Ty.path "argus_kernel::background::FlowMode"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheoryBuilder", + "flow_policy" + |) + |); + Value.Tuple [ M.read (| level |); M.read (| egress |) ]; + M.read (| mode |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |) + |) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_set_flow : M.IsAssociatedFunction.C Self "set_flow" set_flow. + Admitted. + Global Typeclasses Opaque set_flow. + + (* + pub fn add_override( + &mut self, + agent: AgentId, + tool: ToolId, + level: ConfLevel, + ) -> &mut Self { + self.flow_overrides.insert((agent, tool, level)); + self + } + *) + Definition add_override (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; agent; tool; level ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + self + |) in + let agent := M.alloc (| Ty.path "argus_kernel::types::AgentId", agent |) in + let tool := M.alloc (| Ty.path "argus_kernel::types::ToolId", tool |) in + let level := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", level |) in + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.read (| + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "argus_kernel::types::ConfLevel" + ]; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::background::BackgroundTheoryBuilder", + "flow_overrides" + |) + |); + Value.Tuple [ M.read (| agent |); M.read (| tool |); M.read (| level |) ] + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::background::BackgroundTheoryBuilder" ], + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |) + |) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_add_override : + M.IsAssociatedFunction.C Self "add_override" add_override. + Admitted. + Global Typeclasses Opaque add_override. + + (* + pub fn build(self) -> BackgroundTheory { + BackgroundTheory { + tools: self.tools, + flow_policy: self.flow_policy, + flow_overrides: self.flow_overrides, + } + } + *) + Definition build (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| Ty.path "argus_kernel::background::BackgroundTheoryBuilder", self |) in + Value.mkStructRecord + "argus_kernel::background::BackgroundTheory" + [] + [] + [ + ("tools", + M.read (| + M.SubPointer.get_struct_record_field (| + self, + "argus_kernel::background::BackgroundTheoryBuilder", + "tools" + |) + |)); + ("flow_policy", + M.read (| + M.SubPointer.get_struct_record_field (| + self, + "argus_kernel::background::BackgroundTheoryBuilder", + "flow_policy" + |) + |)); + ("flow_overrides", + M.read (| + M.SubPointer.get_struct_record_field (| + self, + "argus_kernel::background::BackgroundTheoryBuilder", + "flow_overrides" + |) + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_build : M.IsAssociatedFunction.C Self "build" build. + Admitted. + Global Typeclasses Opaque build. + End Impl_argus_kernel_background_BackgroundTheoryBuilder. + + Module Impl_core_default_Default_for_argus_kernel_background_BackgroundTheoryBuilder. + Definition Self : Ty.t := Ty.path "argus_kernel::background::BackgroundTheoryBuilder". + + (* + fn default() -> Self { + Self::new() + } + *) + Definition default (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [] => + ltac:(M.monadic + (M.call_closure (| + Ty.path "argus_kernel::background::BackgroundTheoryBuilder", + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheoryBuilder", + "new", + [], + [] + |), + [] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::default::Default" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("default", InstanceField.Method default) ]. + End Impl_core_default_Default_for_argus_kernel_background_BackgroundTheoryBuilder. +End background. diff --git a/argus/formal/extracted/capability.v b/argus/formal/extracted/capability.v new file mode 100644 index 0000000..ec0cc35 --- /dev/null +++ b/argus/formal/extracted/capability.v @@ -0,0 +1,3113 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module capability. + (* + Enum CapKind + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "FilesystemRead"; + item := StructTuple []; + }; + { + name := "FilesystemWrite"; + item := StructTuple []; + }; + { + name := "FilesystemDelete"; + item := StructTuple []; + }; + { + name := "NetworkEgress"; + item := StructTuple []; + }; + { + name := "NetworkIngress"; + item := StructTuple []; + }; + { + name := "ExecutionShell"; + item := StructTuple []; + }; + { + name := "ExecutionCode"; + item := StructTuple []; + }; + { + name := "Credentials"; + item := StructTuple []; + }; + { + name := "SystemInfo"; + item := StructTuple []; + }; + { + name := "SystemModify"; + item := StructTuple []; + }; + { + name := "Clipboard"; + item := StructTuple []; + }; + { + name := "BrowserNavigate"; + item := StructTuple []; + }; + { + name := "DatabaseRead"; + item := StructTuple []; + }; + { + name := "DatabaseWrite"; + item := StructTuple []; + }; + { + name := "Ipc"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_CapKind_FilesystemRead : + M.IsDiscriminant "argus_kernel::capability::CapKind::FilesystemRead" 0. + Axiom IsDiscriminant_CapKind_FilesystemWrite : + M.IsDiscriminant "argus_kernel::capability::CapKind::FilesystemWrite" 1. + Axiom IsDiscriminant_CapKind_FilesystemDelete : + M.IsDiscriminant "argus_kernel::capability::CapKind::FilesystemDelete" 2. + Axiom IsDiscriminant_CapKind_NetworkEgress : + M.IsDiscriminant "argus_kernel::capability::CapKind::NetworkEgress" 3. + Axiom IsDiscriminant_CapKind_NetworkIngress : + M.IsDiscriminant "argus_kernel::capability::CapKind::NetworkIngress" 4. + Axiom IsDiscriminant_CapKind_ExecutionShell : + M.IsDiscriminant "argus_kernel::capability::CapKind::ExecutionShell" 5. + Axiom IsDiscriminant_CapKind_ExecutionCode : + M.IsDiscriminant "argus_kernel::capability::CapKind::ExecutionCode" 6. + Axiom IsDiscriminant_CapKind_Credentials : + M.IsDiscriminant "argus_kernel::capability::CapKind::Credentials" 7. + Axiom IsDiscriminant_CapKind_SystemInfo : + M.IsDiscriminant "argus_kernel::capability::CapKind::SystemInfo" 8. + Axiom IsDiscriminant_CapKind_SystemModify : + M.IsDiscriminant "argus_kernel::capability::CapKind::SystemModify" 9. + Axiom IsDiscriminant_CapKind_Clipboard : + M.IsDiscriminant "argus_kernel::capability::CapKind::Clipboard" 10. + Axiom IsDiscriminant_CapKind_BrowserNavigate : + M.IsDiscriminant "argus_kernel::capability::CapKind::BrowserNavigate" 11. + Axiom IsDiscriminant_CapKind_DatabaseRead : + M.IsDiscriminant "argus_kernel::capability::CapKind::DatabaseRead" 12. + Axiom IsDiscriminant_CapKind_DatabaseWrite : + M.IsDiscriminant "argus_kernel::capability::CapKind::DatabaseWrite" 13. + Axiom IsDiscriminant_CapKind_Ipc : M.IsDiscriminant "argus_kernel::capability::CapKind::Ipc" 14. + + Module Impl_core_clone_Clone_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + M.read (| M.deref (| M.read (| self |) |) |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_capability_CapKind. + + Module Impl_core_marker_Copy_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + Axiom Implements : + M.IsTraitInstance + "core::marker::Copy" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_Copy_for_argus_kernel_capability_CapKind. + + Module Impl_core_fmt_Debug_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemRead" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "FilesystemRead" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemWrite" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "FilesystemWrite" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemDelete" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "FilesystemDelete" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::NetworkEgress" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "NetworkEgress" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::NetworkIngress" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "NetworkIngress" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::ExecutionShell" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "ExecutionShell" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::ExecutionCode" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "ExecutionCode" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::Credentials" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Credentials" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::SystemInfo" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "SystemInfo" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::SystemModify" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "SystemModify" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::Clipboard" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Clipboard" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::BrowserNavigate" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "BrowserNavigate" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::DatabaseRead" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "DatabaseRead" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::DatabaseWrite" + |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "DatabaseWrite" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::Ipc" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Ipc" |) |) |))) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_capability_CapKind. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_CapKind. + + Module Impl_core_cmp_PartialEq_argus_kernel_capability_CapKind_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::capability::CapKind" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_capability_CapKind_for_argus_kernel_capability_CapKind. + + Module Impl_core_cmp_Eq_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + Value.Tuple [])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_capability_CapKind. + + Module Impl_core_cmp_PartialOrd_argus_kernel_capability_CapKind_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* PartialOrd *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.call_closure (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.get_trait_method (| + "core::cmp::PartialOrd", + Ty.path "isize", + [], + [ Ty.path "isize" ], + "partial_cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __arg1_discr |) |) + |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::capability::CapKind" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_capability_CapKind_for_argus_kernel_capability_CapKind. + + Module Impl_core_cmp_Ord_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* Ord *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "core::cmp::Ordering", + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| "core::cmp::Ord", Ty.path "isize", [], [], "cmp", [], [] |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __arg1_discr |) |) + |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_capability_CapKind. + + Module Impl_core_hash_Hash_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + M.alloc (| + Ty.tuple [], + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "isize", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_capability_CapKind. + + Module Impl_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* + pub fn all() -> &'static [CapKind] { + &[ + Self::FilesystemRead, + Self::FilesystemWrite, + Self::FilesystemDelete, + Self::NetworkEgress, + Self::NetworkIngress, + Self::ExecutionShell, + Self::ExecutionCode, + Self::Credentials, + Self::SystemInfo, + Self::SystemModify, + Self::Clipboard, + Self::BrowserNavigate, + Self::DatabaseRead, + Self::DatabaseWrite, + Self::Ipc, + ] + } + *) + Definition all (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [] => + ltac:(M.monadic + (M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "slice") [] [ Ty.path "argus_kernel::capability::CapKind" ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 15 ] + [ Ty.path "argus_kernel::capability::CapKind" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "slice") [] [ Ty.path "argus_kernel::capability::CapKind" ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 15 ] + [ Ty.path "argus_kernel::capability::CapKind" ], + Value.Array + [ + Value.StructTuple + "argus_kernel::capability::CapKind::FilesystemRead" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::FilesystemWrite" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::FilesystemDelete" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::NetworkEgress" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::NetworkIngress" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::ExecutionShell" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::ExecutionCode" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::Credentials" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::SystemInfo" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::SystemModify" + [] + [] + []; + Value.StructTuple "argus_kernel::capability::CapKind::Clipboard" [] [] []; + Value.StructTuple + "argus_kernel::capability::CapKind::BrowserNavigate" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::DatabaseRead" + [] + [] + []; + Value.StructTuple + "argus_kernel::capability::CapKind::DatabaseWrite" + [] + [] + []; + Value.StructTuple "argus_kernel::capability::CapKind::Ipc" [] [] [] + ] + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_all : M.IsAssociatedFunction.C Self "all" all. + Admitted. + Global Typeclasses Opaque all. + + (* + pub fn as_str(self) -> &'static str { + match self { + Self::FilesystemRead => "filesystem_read", + Self::FilesystemWrite => "filesystem_write", + Self::FilesystemDelete => "filesystem_delete", + Self::NetworkEgress => "network_egress", + Self::NetworkIngress => "network_ingress", + Self::ExecutionShell => "execution_shell", + Self::ExecutionCode => "execution_code", + Self::Credentials => "credentials", + Self::SystemInfo => "system_info", + Self::SystemModify => "system_modify", + Self::Clipboard => "clipboard", + Self::BrowserNavigate => "browser_navigate", + Self::DatabaseRead => "database_read", + Self::DatabaseWrite => "database_write", + Self::Ipc => "ipc", + } + } + *) + Definition as_str (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := M.alloc (| Ty.path "argus_kernel::capability::CapKind", self |) in + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + self, + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemRead" + |) in + mk_str (| "filesystem_read" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemWrite" + |) in + mk_str (| "filesystem_write" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::FilesystemDelete" + |) in + mk_str (| "filesystem_delete" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::NetworkEgress" |) in + mk_str (| "network_egress" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::NetworkIngress" + |) in + mk_str (| "network_ingress" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::ExecutionShell" + |) in + mk_str (| "execution_shell" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::ExecutionCode" |) in + mk_str (| "execution_code" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::Credentials" |) in + mk_str (| "credentials" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::SystemInfo" |) in + mk_str (| "system_info" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::SystemModify" |) in + mk_str (| "system_modify" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::Clipboard" |) in + mk_str (| "clipboard" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::capability::CapKind::BrowserNavigate" + |) in + mk_str (| "browser_navigate" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::DatabaseRead" |) in + mk_str (| "database_read" |))); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::DatabaseWrite" |) in + mk_str (| "database_write" |))); + fun γ => + ltac:(M.monadic + (let _ := M.is_struct_tuple (| γ, "argus_kernel::capability::CapKind::Ipc" |) in + mk_str (| "ipc" |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_as_str : M.IsAssociatedFunction.C Self "as_str" as_str. + Admitted. + Global Typeclasses Opaque as_str. + + (* + pub fn from_catalog_name(s: &str) -> Option { + match s { + "filesystem_read" => Some(Self::FilesystemRead), + "filesystem_write" => Some(Self::FilesystemWrite), + "filesystem_delete" => Some(Self::FilesystemDelete), + "network_egress" => Some(Self::NetworkEgress), + "network_ingress" => Some(Self::NetworkIngress), + "execution_shell" => Some(Self::ExecutionShell), + "execution_code" => Some(Self::ExecutionCode), + "credentials" => Some(Self::Credentials), + "system_info" => Some(Self::SystemInfo), + "system_modify" => Some(Self::SystemModify), + "clipboard" => Some(Self::Clipboard), + "browser_navigate" => Some(Self::BrowserNavigate), + "database_read" => Some(Self::DatabaseRead), + "database_write" => Some(Self::DatabaseWrite), + "ipc" => Some(Self::Ipc), + _ => None, + } + } + *) + Definition from_catalog_name (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ s ] => + ltac:(M.monadic + (let s := M.alloc (| Ty.apply (Ty.path "&") [] [ Ty.path "str" ], s |) in + M.match_operator (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + s, + [ + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "filesystem_read" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::FilesystemRead" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "filesystem_write" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ + Value.StructTuple + "argus_kernel::capability::CapKind::FilesystemWrite" + [] + [] + [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "filesystem_delete" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ + Value.StructTuple + "argus_kernel::capability::CapKind::FilesystemDelete" + [] + [] + [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "network_egress" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::NetworkEgress" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "network_ingress" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::NetworkIngress" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "execution_shell" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::ExecutionShell" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "execution_code" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::ExecutionCode" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| M.read (| γ |), mk_str (| "credentials" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::Credentials" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| M.read (| γ |), mk_str (| "system_info" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::SystemInfo" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| M.read (| γ |), mk_str (| "system_modify" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::SystemModify" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| M.read (| γ |), mk_str (| "clipboard" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::Clipboard" [] [] [] ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "browser_navigate" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ + Value.StructTuple + "argus_kernel::capability::CapKind::BrowserNavigate" + [] + [] + [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| M.read (| γ |), mk_str (| "database_read" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::DatabaseRead" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := + is_constant_or_break_match (| + M.read (| γ |), + mk_str (| "database_write" |) + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::DatabaseWrite" [] [] [] + ])); + fun γ => + ltac:(M.monadic + (let _ := is_constant_or_break_match (| M.read (| γ |), mk_str (| "ipc" |) |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [ Value.StructTuple "argus_kernel::capability::CapKind::Ipc" [] [] [] ])); + fun γ => + ltac:(M.monadic + (Value.StructTuple + "core::option::Option::None" + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + [])) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_from_catalog_name : + M.IsAssociatedFunction.C Self "from_catalog_name" from_catalog_name. + Admitted. + Global Typeclasses Opaque from_catalog_name. + End Impl_argus_kernel_capability_CapKind. + + Module Impl_core_fmt_Display_for_argus_kernel_capability_CapKind. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::CapKind". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + M.get_associated_function (| + Ty.path "argus_kernel::capability::CapKind", + "as_str", + [], + [] + |), + [ M.read (| M.deref (| M.read (| self |) |) |) ] + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_capability_CapKind. + + (* StructRecord + { + name := "DomainPort"; + const_params := []; + ty_params := []; + fields := [ ("host", Ty.path "alloc::string::String"); ("port", Ty.path "u16") ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_capability_DomainPort. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::DomainPort". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::DomainPort" ], + self + |) in + Value.mkStructRecord + "argus_kernel::capability::DomainPort" + [] + [] + [ + ("host", + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "alloc::string::String", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "host" + |) + |) + |) + |) + ] + |)); + ("port", + M.call_closure (| + Ty.path "u16", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "u16", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "port" + |) + |) + |) + |) + ] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_capability_DomainPort. + + Module Impl_core_fmt_Debug_for_argus_kernel_capability_DomainPort. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::DomainPort". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::DomainPort" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "DomainPort" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "host" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "host" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "port" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.apply (Ty.path "&") [] [ Ty.path "u16" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "u16" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "port" + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_capability_DomainPort. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_DomainPort. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::DomainPort". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_DomainPort. + + Module Impl_core_cmp_PartialEq_argus_kernel_capability_DomainPort_for_argus_kernel_capability_DomainPort. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::DomainPort". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::DomainPort" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::DomainPort" ], + other + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "host" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::capability::DomainPort", + "host" + |) + |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::capability::DomainPort", + "port" + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::capability::DomainPort", + "port" + |) + |) + ] + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::capability::DomainPort" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_capability_DomainPort_for_argus_kernel_capability_DomainPort. + + Module Impl_core_cmp_Eq_for_argus_kernel_capability_DomainPort. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::DomainPort". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::DomainPort" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_capability_DomainPort. + + (* + Enum NetScope + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "Blocked"; + item := StructTuple []; + }; + { + name := "EgressOnly"; + item := StructTuple []; + }; + { + name := "EgressWithPorts"; + item := + StructTuple + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ]; + }; + { + name := "EgressWithDomains"; + item := + StructTuple + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "argus_kernel::capability::DomainPort"; Ty.path "alloc::alloc::Global" ] + ]; + }; + { + name := "Unrestricted"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_NetScope_Blocked : + M.IsDiscriminant "argus_kernel::capability::NetScope::Blocked" 0. + Axiom IsDiscriminant_NetScope_EgressOnly : + M.IsDiscriminant "argus_kernel::capability::NetScope::EgressOnly" 1. + Axiom IsDiscriminant_NetScope_EgressWithPorts : + M.IsDiscriminant "argus_kernel::capability::NetScope::EgressWithPorts" 2. + Axiom IsDiscriminant_NetScope_EgressWithDomains : + M.IsDiscriminant "argus_kernel::capability::NetScope::EgressWithDomains" 3. + Axiom IsDiscriminant_NetScope_Unrestricted : + M.IsDiscriminant "argus_kernel::capability::NetScope::Unrestricted" 4. + + Module Impl_core_clone_Clone_for_argus_kernel_capability_NetScope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::NetScope". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + self + |) in + M.match_operator (| + Ty.path "argus_kernel::capability::NetScope", + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::Blocked" |) in + Value.StructTuple "argus_kernel::capability::NetScope::Blocked" [] [] [])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::EgressOnly" |) in + Value.StructTuple "argus_kernel::capability::NetScope::EgressOnly" [] [] [])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::capability::NetScope::EgressWithPorts", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + Value.StructTuple + "argus_kernel::capability::NetScope::EgressWithPorts" + [] + [] + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::capability::NetScope::EgressWithDomains", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ], + γ1_0 + |) in + Value.StructTuple + "argus_kernel::capability::NetScope::EgressWithDomains" + [] + [] + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::Unrestricted" |) in + Value.StructTuple "argus_kernel::capability::NetScope::Unrestricted" [] [] [])) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_capability_NetScope. + + Module Impl_core_fmt_Debug_for_argus_kernel_capability_NetScope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::NetScope". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::Blocked" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Blocked" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::EgressOnly" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "EgressOnly" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::capability::NetScope::EgressWithPorts", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "EgressWithPorts" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::capability::NetScope::EgressWithDomains", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "EgressWithDomains" |) |) + |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::capability::NetScope::Unrestricted" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Unrestricted" |) |) |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_capability_NetScope. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_NetScope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::NetScope". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_NetScope. + + Module Impl_core_cmp_PartialEq_argus_kernel_capability_NetScope_for_argus_kernel_capability_NetScope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::NetScope". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::NetScope" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::NetScope" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |), + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.tuple + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ]; + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ] + ], + Value.Tuple [ M.read (| self |); M.read (| other |) ] + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_tuple_field (| + γ0_0, + "argus_kernel::capability::NetScope::EgressWithPorts", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_tuple_field (| + γ0_1, + "argus_kernel::capability::NetScope::EgressWithPorts", + 0 + |) in + let __arg1_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "u16"; Ty.path "alloc::alloc::Global" ] + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_tuple_field (| + γ0_0, + "argus_kernel::capability::NetScope::EgressWithDomains", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_tuple_field (| + γ0_1, + "argus_kernel::capability::NetScope::EgressWithDomains", + 0 + |) in + let __arg1_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ + Ty.path "argus_kernel::capability::DomainPort"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => ltac:(M.monadic (Value.Bool true)) + ] + |))) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::capability::NetScope" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_capability_NetScope_for_argus_kernel_capability_NetScope. + + Module Impl_core_cmp_Eq_for_argus_kernel_capability_NetScope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::NetScope". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_capability_NetScope. + + (* + Enum Scope + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "Filesystem"; + item := + StructRecord + [ + ("paths", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ]) + ]; + }; + { + name := "Network"; + item := StructRecord [ ("policy", Ty.path "argus_kernel::capability::NetScope") ]; + }; + { + name := "Execution"; + item := + StructRecord + [ + ("allowed_bins", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ]) + ]; + }; + { + name := "None"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_Scope_Filesystem : + M.IsDiscriminant "argus_kernel::capability::Scope::Filesystem" 0. + Axiom IsDiscriminant_Scope_Network : + M.IsDiscriminant "argus_kernel::capability::Scope::Network" 1. + Axiom IsDiscriminant_Scope_Execution : + M.IsDiscriminant "argus_kernel::capability::Scope::Execution" 2. + Axiom IsDiscriminant_Scope_None : M.IsDiscriminant "argus_kernel::capability::Scope::None" 3. + + Module Impl_core_clone_Clone_for_argus_kernel_capability_Scope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::Scope". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ], + self + |) in + M.match_operator (| + Ty.path "argus_kernel::capability::Scope", + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Filesystem", + "paths" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + Value.mkStructRecord + "argus_kernel::capability::Scope::Filesystem" + [] + [] + [ + ("paths", + M.call_closure (| + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Network", + "policy" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + γ1_0 + |) in + Value.mkStructRecord + "argus_kernel::capability::Scope::Network" + [] + [] + [ + ("policy", + M.call_closure (| + Ty.path "argus_kernel::capability::NetScope", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::capability::NetScope", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Execution", + "allowed_bins" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + Value.mkStructRecord + "argus_kernel::capability::Scope::Execution" + [] + [] + [ + ("allowed_bins", + M.call_closure (| + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::capability::Scope::None" |) in + Value.StructTuple "argus_kernel::capability::Scope::None" [] [] [])) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_capability_Scope. + + Module Impl_core_fmt_Debug_for_argus_kernel_capability_Scope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::Scope". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Filesystem", + "paths" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Filesystem" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "paths" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Network", + "policy" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::NetScope" ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Network" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "policy" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::capability::Scope::Execution", + "allowed_bins" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Execution" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "allowed_bins" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::capability::Scope::None" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "None" |) |) |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_capability_Scope. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_Scope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::Scope". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_capability_Scope. + + Module Impl_core_cmp_PartialEq_argus_kernel_capability_Scope_for_argus_kernel_capability_Scope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::Scope". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::Scope" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::capability::Scope" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |), + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.tuple + [ + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ]; + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ] + ], + Value.Tuple [ M.read (| self |); M.read (| other |) ] + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::capability::Scope::Filesystem", + "paths" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::capability::Scope::Filesystem", + "paths" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" + ] + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::capability::Scope::Network", + "policy" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::capability::Scope::Network", + "policy" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::NetScope" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::capability::Scope::Execution", + "allowed_bins" + |) in + let __self_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::capability::Scope::Execution", + "allowed_bins" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" ] + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::vec::Vec") + [] + [ Ty.path "std::path::PathBuf"; Ty.path "alloc::alloc::Global" + ] + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => ltac:(M.monadic (Value.Bool true)) + ] + |))) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::capability::Scope" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_capability_Scope_for_argus_kernel_capability_Scope. + + Module Impl_core_cmp_Eq_for_argus_kernel_capability_Scope. + Definition Self : Ty.t := Ty.path "argus_kernel::capability::Scope". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::Scope" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_capability_Scope. +End capability. diff --git a/argus/formal/extracted/error.v b/argus/formal/extracted/error.v new file mode 100644 index 0000000..4bda027 --- /dev/null +++ b/argus/formal/extracted/error.v @@ -0,0 +1,486 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module error. + (* + Enum KernelError + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "PreconditionViolation"; + item := StructTuple [ Ty.path "alloc::string::String" ]; + }; + { + name := "EventStoreError"; + item := StructTuple [ Ty.path "alloc::string::String" ]; + } + ]; + } + *) + + Axiom IsDiscriminant_KernelError_PreconditionViolation : + M.IsDiscriminant "argus_kernel::error::KernelError::PreconditionViolation" 0. + Axiom IsDiscriminant_KernelError_EventStoreError : + M.IsDiscriminant "argus_kernel::error::KernelError::EventStoreError" 1. + + Module Impl_core_fmt_Debug_for_argus_kernel_error_KernelError. + Definition Self : Ty.t := Ty.path "argus_kernel::error::KernelError". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::error::KernelError" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::error::KernelError::PreconditionViolation", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "PreconditionViolation" |) |) + |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::error::KernelError::EventStoreError", + 0 + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "EventStoreError" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_error_KernelError. + + Module Impl_core_error_Error_for_argus_kernel_error_KernelError. + Definition Self : Ty.t := Ty.path "argus_kernel::error::KernelError". + + Axiom Implements : + M.IsTraitInstance + "core::error::Error" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_error_Error_for_argus_kernel_error_KernelError. + + Module Impl_core_fmt_Display_for_argus_kernel_error_KernelError. + Definition Self : Ty.t := Ty.path "argus_kernel::error::KernelError". + + (* thiserror::Error *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; __formatter ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::error::KernelError" ], + self + |) in + let __formatter := + M.alloc (| + Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], + __formatter + |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::error::KernelError::PreconditionViolation", + 0 + |) in + let _0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ1_0 + |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.alloc (| + Ty.tuple [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ], + Value.Tuple + [ + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + M.get_trait_method (| + "thiserror::display::AsDisplay", + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + [], + [], + "as_display", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, _0 |) ] + |) + ] + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let __display0 := + M.copy (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ0_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_fmt", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.read (| __formatter |) |) + |); + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 1; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 ] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ], + Value.Array [ mk_str (| "precondition violated: " |) ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 ] + [ Ty.path "core::fmt::rt::Argument" ], + Value.Array + [ + M.call_closure (| + Ty.path "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "alloc::string::String" ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| Pointer.Kind.Ref, __display0 |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |))) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "argus_kernel::error::KernelError::EventStoreError", + 0 + |) in + let _0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ1_0 + |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.alloc (| + Ty.tuple [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ], + Value.Tuple + [ + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + M.get_trait_method (| + "thiserror::display::AsDisplay", + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + [], + [], + "as_display", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, _0 |) ] + |) + ] + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let __display0 := + M.copy (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + γ0_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_fmt", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.read (| __formatter |) |) + |); + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 1; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 ] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ], + Value.Array [ mk_str (| "event store error: " |) ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 ] + [ Ty.path "core::fmt::rt::Argument" ], + Value.Array + [ + M.call_closure (| + Ty.path "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "alloc::string::String" ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| Pointer.Kind.Ref, __display0 |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |))) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_error_KernelError. +End error. diff --git a/argus/formal/extracted/event.v b/argus/formal/extracted/event.v new file mode 100644 index 0000000..93a4d8b --- /dev/null +++ b/argus/formal/extracted/event.v @@ -0,0 +1,2922 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module event. + (* + Enum KernelAction + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "RegisterTool"; + item := StructRecord [ ("tool", Ty.path "argus_kernel::types::ToolId") ]; + }; + { + name := "Delegate"; + item := + StructRecord + [ + ("grantor", Ty.path "argus_kernel::types::AgentId"); + ("grantee", Ty.path "argus_kernel::types::AgentId") + ]; + }; + { + name := "GrantCapability"; + item := + StructRecord + [ + ("parent", Ty.path "argus_kernel::types::AgentId"); + ("child", Ty.path "argus_kernel::types::AgentId"); + ("cap", Ty.path "argus_kernel::capability::CapKind") + ]; + }; + { + name := "Revoke"; + item := + StructRecord + [ + ("parent", Ty.path "argus_kernel::types::AgentId"); + ("target", Ty.path "argus_kernel::types::AgentId") + ]; + }; + { + name := "CascadeRevoke"; + item := + StructRecord + [ + ("child", Ty.path "argus_kernel::types::AgentId"); + ("parent", Ty.path "argus_kernel::types::AgentId") + ]; + }; + { + name := "InvokeStart"; + item := + StructRecord + [ + ("agent", Ty.path "argus_kernel::types::AgentId"); + ("tool", Ty.path "argus_kernel::types::ToolId"); + ("inv", Ty.path "argus_kernel::types::InvocationId") + ]; + }; + { + name := "InvokeComplete"; + item := + StructRecord + [ + ("agent", Ty.path "argus_kernel::types::AgentId"); + ("inv", Ty.path "argus_kernel::types::InvocationId") + ]; + }; + { + name := "ReturnEndorsed"; + item := + StructRecord + [ + ("child", Ty.path "argus_kernel::types::AgentId"); + ("parent", Ty.path "argus_kernel::types::AgentId") + ]; + }; + { + name := "ReturnUnendorsed"; + item := + StructRecord + [ + ("child", Ty.path "argus_kernel::types::AgentId"); + ("parent", Ty.path "argus_kernel::types::AgentId") + ]; + } + ]; + } + *) + + Axiom IsDiscriminant_KernelAction_RegisterTool : + M.IsDiscriminant "argus_kernel::event::KernelAction::RegisterTool" 0. + Axiom IsDiscriminant_KernelAction_Delegate : + M.IsDiscriminant "argus_kernel::event::KernelAction::Delegate" 1. + Axiom IsDiscriminant_KernelAction_GrantCapability : + M.IsDiscriminant "argus_kernel::event::KernelAction::GrantCapability" 2. + Axiom IsDiscriminant_KernelAction_Revoke : + M.IsDiscriminant "argus_kernel::event::KernelAction::Revoke" 3. + Axiom IsDiscriminant_KernelAction_CascadeRevoke : + M.IsDiscriminant "argus_kernel::event::KernelAction::CascadeRevoke" 4. + Axiom IsDiscriminant_KernelAction_InvokeStart : + M.IsDiscriminant "argus_kernel::event::KernelAction::InvokeStart" 5. + Axiom IsDiscriminant_KernelAction_InvokeComplete : + M.IsDiscriminant "argus_kernel::event::KernelAction::InvokeComplete" 6. + Axiom IsDiscriminant_KernelAction_ReturnEndorsed : + M.IsDiscriminant "argus_kernel::event::KernelAction::ReturnEndorsed" 7. + Axiom IsDiscriminant_KernelAction_ReturnUnendorsed : + M.IsDiscriminant "argus_kernel::event::KernelAction::ReturnUnendorsed" 8. + + Module Impl_core_clone_Clone_for_argus_kernel_event_KernelAction. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelAction". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + self + |) in + M.match_operator (| + Ty.path "argus_kernel::event::KernelAction", + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::RegisterTool", + "tool" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ1_0 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::RegisterTool" + [] + [] + [ + ("tool", + M.call_closure (| + Ty.path "argus_kernel::types::ToolId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ToolId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Delegate", + "grantor" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Delegate", + "grantee" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::Delegate" + [] + [] + [ + ("grantor", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("grantee", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "parent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "child" + |) in + let γ1_2 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "cap" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + γ1_2 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::GrantCapability" + [] + [] + [ + ("parent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("child", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)); + ("cap", + M.call_closure (| + Ty.path "argus_kernel::capability::CapKind", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::capability::CapKind", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_2 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Revoke", + "parent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Revoke", + "target" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::Revoke" + [] + [] + [ + ("parent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("target", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::CascadeRevoke", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::CascadeRevoke", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::CascadeRevoke" + [] + [] + [ + ("child", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("parent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "agent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "tool" + |) in + let γ1_2 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ1_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + γ1_2 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::InvokeStart" + [] + [] + [ + ("agent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("tool", + M.call_closure (| + Ty.path "argus_kernel::types::ToolId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ToolId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)); + ("inv", + M.call_closure (| + Ty.path "argus_kernel::types::InvocationId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::InvocationId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_2 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeComplete", + "agent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeComplete", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::InvokeComplete" + [] + [] + [ + ("agent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("inv", + M.call_closure (| + Ty.path "argus_kernel::types::InvocationId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::InvocationId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::ReturnEndorsed" + [] + [] + [ + ("child", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("parent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + Value.mkStructRecord + "argus_kernel::event::KernelAction::ReturnUnendorsed" + [] + [] + [ + ("child", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |)); + ("parent", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |)) + ])) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_event_KernelAction. + + Module Impl_core_fmt_Debug_for_argus_kernel_event_KernelAction. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelAction". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::RegisterTool", + "tool" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ1_0 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "RegisterTool" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "tool" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ] ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_0 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Delegate", + "grantor" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Delegate", + "grantee" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Delegate" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "grantor" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "grantee" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "parent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "child" + |) in + let γ1_2 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::GrantCapability", + "cap" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::capability::CapKind" ], + γ1_2 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field3_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "GrantCapability" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "parent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "child" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "cap" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_2 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Revoke", + "parent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::Revoke", + "target" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Revoke" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "parent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "target" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::CascadeRevoke", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::CascadeRevoke", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "CascadeRevoke" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "child" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "parent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "agent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "tool" + |) in + let γ1_2 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeStart", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ1_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + γ1_2 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field3_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "InvokeStart" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "agent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "tool" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_1 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "inv" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_2 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeComplete", + "agent" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::InvokeComplete", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "InvokeComplete" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "agent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "inv" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "ReturnEndorsed" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "child" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "parent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let γ1_0 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "child" + |) in + let γ1_1 := + M.SubPointer.get_struct_record_field (| + γ, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ1_1 + |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "ReturnUnendorsed" |) |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "child" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| __self_0 |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "parent" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_1 |) |) + |) + ] + |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_event_KernelAction. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_event_KernelAction. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelAction". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_event_KernelAction. + + Module Impl_core_cmp_PartialEq_argus_kernel_event_KernelAction_for_argus_kernel_event_KernelAction. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelAction". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::event::KernelAction" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::event::KernelAction" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |), + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.tuple + [ + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ]; + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ] + ], + Value.Tuple [ M.read (| self |); M.read (| other |) ] + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::RegisterTool", + "tool" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ2_0 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::RegisterTool", + "tool" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ2_0 + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + [], + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::Delegate", + "grantor" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::Delegate", + "grantee" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::Delegate", + "grantor" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::Delegate", + "grantee" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::GrantCapability", + "parent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::GrantCapability", + "child" + |) in + let γ2_2 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::GrantCapability", + "cap" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + γ2_2 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::GrantCapability", + "parent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::GrantCapability", + "child" + |) in + let γ2_2 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::GrantCapability", + "cap" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let __arg1_2 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + γ2_2 + |) in + LogicalOp.and (| + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_2 |); + M.borrow (| Pointer.Kind.Ref, __arg1_2 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::Revoke", + "parent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::Revoke", + "target" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::Revoke", + "parent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::Revoke", + "target" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::CascadeRevoke", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::CascadeRevoke", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::CascadeRevoke", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::CascadeRevoke", + "parent" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::InvokeStart", + "agent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::InvokeStart", + "tool" + |) in + let γ2_2 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::InvokeStart", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ2_1 + |) in + let __self_2 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + γ2_2 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::InvokeStart", + "agent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::InvokeStart", + "tool" + |) in + let γ2_2 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::InvokeStart", + "inv" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ2_1 + |) in + let __arg1_2 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + γ2_2 + |) in + LogicalOp.and (| + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ToolId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ToolId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_2 |); + M.borrow (| Pointer.Kind.Ref, __arg1_2 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::InvokeComplete", + "agent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::InvokeComplete", + "inv" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::InvokeComplete", + "agent" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::InvokeComplete", + "inv" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::ReturnEndorsed", + "parent" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_0, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "parent" + |) in + let __self_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __self_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + let γ0_1 := M.deref (| M.read (| γ0_1 |) |) in + let γ2_0 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "child" + |) in + let γ2_1 := + M.SubPointer.get_struct_record_field (| + γ0_1, + "argus_kernel::event::KernelAction::ReturnUnendorsed", + "parent" + |) in + let __arg1_0 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_0 + |) in + let __arg1_1 := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + γ2_1 + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_0 |); + M.borrow (| Pointer.Kind.Ref, __arg1_0 |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, __self_1 |); + M.borrow (| Pointer.Kind.Ref, __arg1_1 |) + ] + |))) + |))); + fun γ => + ltac:(M.monadic + (M.never_to_any (| + M.call_closure (| + Ty.path "never", + M.get_function (| "core::intrinsics::unreachable", [], [] |), + [] + |) + |))) + ] + |))) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::event::KernelAction" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_event_KernelAction_for_argus_kernel_event_KernelAction. + + Module Impl_core_cmp_Eq_for_argus_kernel_event_KernelAction. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelAction". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_event_KernelAction. + + (* StructRecord + { + name := "KernelEvent"; + const_params := []; + ty_params := []; + fields := + [ ("sequence", Ty.path "u64"); ("action", Ty.path "argus_kernel::event::KernelAction") ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelEvent" ], + self + |) in + Value.mkStructRecord + "argus_kernel::event::KernelEvent" + [] + [] + [ + ("sequence", + M.call_closure (| + Ty.path "u64", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "u64", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "sequence" + |) + |) + |) + |) + ] + |)); + ("action", + M.call_closure (| + Ty.path "argus_kernel::event::KernelAction", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::event::KernelAction", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "action" + |) + |) + |) + |) + ] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_event_KernelEvent. + + Module Impl_core_fmt_Debug_for_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelEvent" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_field2_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "KernelEvent" |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "sequence" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply (Ty.path "&") [] [ Ty.path "u64" ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "sequence" + |) + |) + |) + |) + ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "action" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelAction" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "action" + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_event_KernelEvent. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_event_KernelEvent. + + Module Impl_core_cmp_PartialEq_argus_kernel_event_KernelEvent_for_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelEvent" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelEvent" ], + other + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "sequence" + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::event::KernelEvent", + "sequence" + |) + |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::event::KernelAction", + [], + [ Ty.path "argus_kernel::event::KernelAction" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::event::KernelEvent", + "action" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::event::KernelEvent", + "action" + |) + |) + ] + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::event::KernelEvent" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_event_KernelEvent_for_argus_kernel_event_KernelEvent. + + Module Impl_core_cmp_Eq_for_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::event::KernelEvent" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_event_KernelEvent. + + Module Impl_argus_kernel_event_KernelEvent. + Definition Self : Ty.t := Ty.path "argus_kernel::event::KernelEvent". + + (* + pub fn new(sequence: u64, action: KernelAction) -> Self { + Self { sequence, action } + } + *) + Definition new (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ sequence; action ] => + ltac:(M.monadic + (let sequence := M.alloc (| Ty.path "u64", sequence |) in + let action := M.alloc (| Ty.path "argus_kernel::event::KernelAction", action |) in + Value.mkStructRecord + "argus_kernel::event::KernelEvent" + [] + [] + [ ("sequence", M.read (| sequence |)); ("action", M.read (| action |)) ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : M.IsAssociatedFunction.C Self "new" new. + Admitted. + Global Typeclasses Opaque new. + End Impl_argus_kernel_event_KernelEvent. +End event. diff --git a/argus/formal/extracted/kernel.v b/argus/formal/extracted/kernel.v new file mode 100644 index 0000000..6cd1d0d --- /dev/null +++ b/argus/formal/extracted/kernel.v @@ -0,0 +1,1902 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module kernel. + (* StructRecord + { + name := "Kernel"; + const_params := []; + ty_params := [ "A"; "C"; "E" ]; + fields := + [ + ("state", Ty.path "argus_kernel::state::KernelState"); + ("background", Ty.path "argus_kernel::background::BackgroundTheory"); + ("sequence", Ty.path "u64"); + ("authorizer", A); + ("content_gate", C); + ("events", E) + ]; + } *) + + Module Impl_argus_kernel_kernel_Kernel_A_C_E. + Definition Self (A C E : Ty.t) : Ty.t := + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ]. + + (* + pub fn new( + background: BackgroundTheory, + authorizer: A, + content_gate: C, + events: E, + ) -> Self { + Self { + state: KernelState::initial(), + background, + sequence: 0, + authorizer, + content_gate, + events, + } + } + *) + Definition new (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ background; authorizer; content_gate; events ] => + ltac:(M.monadic + (let background := + M.alloc (| Ty.path "argus_kernel::background::BackgroundTheory", background |) in + let authorizer := M.alloc (| A, authorizer |) in + let content_gate := M.alloc (| C, content_gate |) in + let events := M.alloc (| E, events |) in + Value.mkStructRecord + "argus_kernel::kernel::Kernel" + [] + [ A; C; E ] + [ + ("state", + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_associated_function (| + Ty.path "argus_kernel::state::KernelState", + "initial", + [], + [] + |), + [] + |)); + ("background", M.read (| background |)); + ("sequence", Value.Integer IntegerKind.U64 0); + ("authorizer", M.read (| authorizer |)); + ("content_gate", M.read (| content_gate |)); + ("events", M.read (| events |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "new" (new A C E). + Admitted. + Global Typeclasses Opaque new. + + (* + pub fn state(&self) -> &KernelState { + &self.state + } + *) + Definition state (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_state : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "state" (state A C E). + Admitted. + Global Typeclasses Opaque state. + + (* + pub fn background(&self) -> &BackgroundTheory { + &self.background + } + *) + Definition background + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_background : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "background" (background A C E). + Admitted. + Global Typeclasses Opaque background. + + (* + pub fn sequence(&self) -> u64 { + self.sequence + } + *) + Definition sequence (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "sequence" + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_sequence : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "sequence" (sequence A C E). + Admitted. + Global Typeclasses Opaque sequence. + + (* + fn apply( + &mut self, + result: Result<(KernelState, KernelAction), KernelError>, + ) -> Result { + let (new_state, action) = result?; + let next_seq = self.sequence + 1; + let event = KernelEvent::new(next_seq, action); + self.events.append(&event)?; + self.sequence = next_seq; + self.state = new_state; + Ok(event) + } + *) + Definition apply (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; result ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let result := + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + result + |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.alloc (| + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ], + M.match_operator (| + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ], + M.alloc (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ] + ], + M.get_trait_method (| + "core::ops::try_trait::Try", + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + [], + [], + "branch", + [], + [] + |), + [ M.read (| result |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Break", + 0 + |) in + let residual := + M.copy (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ], + γ0_0 + |) in + M.never_to_any (| + M.read (| + M.return_ (| + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_trait_method (| + "core::ops::try_trait::FromResidual", + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + [], + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ] + ], + "from_residual", + [], + [] + |), + [ M.read (| residual |) ] + |) + |) + |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Continue", + 0 + |) in + let val := + M.copy (| + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ], + γ0_0 + |) in + M.read (| val |))) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := M.SubPointer.get_tuple_field (| γ, 0 |) in + let γ0_1 := M.SubPointer.get_tuple_field (| γ, 1 |) in + let new_state := + M.copy (| Ty.path "argus_kernel::state::KernelState", γ0_0 |) in + let action := + M.copy (| Ty.path "argus_kernel::event::KernelAction", γ0_1 |) in + M.read (| + let~ next_seq : Ty.path "u64" := + M.call_closure (| + Ty.path "u64", + BinOp.Wrap.add, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "sequence" + |) + |); + Value.Integer IntegerKind.U64 1 + ] + |) in + let~ event : Ty.path "argus_kernel::event::KernelEvent" := + M.call_closure (| + Ty.path "argus_kernel::event::KernelEvent", + M.get_associated_function (| + Ty.path "argus_kernel::event::KernelEvent", + "new", + [], + [] + |), + [ M.read (| next_seq |); M.read (| action |) ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.tuple [] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.tuple [] + ], + M.get_trait_method (| + "core::ops::try_trait::Try", + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "argus_kernel::error::KernelError" ], + [], + [], + "branch", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "argus_kernel::error::KernelError" ], + M.get_trait_method (| + "argus_kernel::traits::EventStore", + E, + [], + [], + "append", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "events" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, event |) |) + |) + ] + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Break", + 0 + |) in + let residual := + M.copy (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ], + γ0_0 + |) in + M.never_to_any (| + M.read (| + M.return_ (| + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_trait_method (| + "core::ops::try_trait::FromResidual", + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + [], + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ] + ], + "from_residual", + [], + [] + |), + [ M.read (| residual |) ] + |) + |) + |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Continue", + 0 + |) in + let val := M.copy (| Ty.tuple [], γ0_0 |) in + M.read (| val |))) + ] + |) in + let~ _ : Ty.tuple [] := + M.write (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "sequence" + |), + M.read (| next_seq |) + |) in + let~ _ : Ty.tuple [] := + M.write (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |), + M.read (| new_state |) + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ] + [ M.read (| event |) ] + |) + |))) + ] + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_apply : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "apply" (apply A C E). + Admitted. + Global Typeclasses Opaque apply. + + (* + pub fn register_tool(&mut self, tool: ToolId) -> Result { + let result = transitions::register_tool(self.state.clone(), &self.background, tool); + self.apply(result) + } + *) + Definition register_tool + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; tool ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let tool := M.alloc (| Ty.path "argus_kernel::types::ToolId", tool |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::register_tool", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| tool |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_register_tool : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "register_tool" (register_tool A C E). + Admitted. + Global Typeclasses Opaque register_tool. + + (* + pub fn delegate( + &mut self, + grantor: AgentId, + grantee: AgentId, + ) -> Result { + let result = + transitions::delegate(self.state.clone(), &self.background, grantor, grantee); + self.apply(result) + } + *) + Definition delegate (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; grantor; grantee ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let grantor := M.alloc (| Ty.path "argus_kernel::types::AgentId", grantor |) in + let grantee := M.alloc (| Ty.path "argus_kernel::types::AgentId", grantee |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::delegate", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| grantor |); + M.read (| grantee |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_delegate : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "delegate" (delegate A C E). + Admitted. + Global Typeclasses Opaque delegate. + + (* + pub fn grant_capability( + &mut self, + parent: AgentId, + child: AgentId, + cap: CapKind, + ) -> Result { + let result = transitions::grant_capability( + self.state.clone(), + &self.background, + parent, + child, + cap, + ); + self.apply(result) + } + *) + Definition grant_capability + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; parent; child; cap ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let cap := M.alloc (| Ty.path "argus_kernel::capability::CapKind", cap |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::grant_capability", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| parent |); + M.read (| child |); + M.read (| cap |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_grant_capability : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "grant_capability" (grant_capability A C E). + Admitted. + Global Typeclasses Opaque grant_capability. + + (* + pub fn revoke( + &mut self, + parent: AgentId, + target: AgentId, + ) -> Result { + let result = + transitions::revoke(self.state.clone(), &self.background, parent, target); + self.apply(result) + } + *) + Definition revoke (A C E : Ty.t) (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; parent; target ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + let target := M.alloc (| Ty.path "argus_kernel::types::AgentId", target |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::revoke", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| parent |); + M.read (| target |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_revoke : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "revoke" (revoke A C E). + Admitted. + Global Typeclasses Opaque revoke. + + (* + pub fn cascade_revoke( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = + transitions::cascade_revoke(self.state.clone(), &self.background, child, parent); + self.apply(result) + } + *) + Definition cascade_revoke + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; child; parent ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::cascade_revoke", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| child |); + M.read (| parent |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_cascade_revoke : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "cascade_revoke" (cascade_revoke A C E). + Admitted. + Global Typeclasses Opaque cascade_revoke. + + (* + pub fn invoke_start( + &mut self, + agent: AgentId, + tool: ToolId, + inv: InvocationId, + ) -> Result { + let result = transitions::invoke_start( + self.state.clone(), + &self.background, + &self.authorizer, + &self.content_gate, + agent, + tool, + inv, + ); + self.apply(result) + } + *) + Definition invoke_start + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; agent; tool; inv ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let agent := M.alloc (| Ty.path "argus_kernel::types::AgentId", agent |) in + let tool := M.alloc (| Ty.path "argus_kernel::types::ToolId", tool |) in + let inv := M.alloc (| Ty.path "argus_kernel::types::InvocationId", inv |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::invoke_start", [], [ A; C ] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "authorizer" + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "content_gate" + |) + |) + |) + |); + M.read (| agent |); + M.read (| tool |); + M.read (| inv |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_invoke_start : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "invoke_start" (invoke_start A C E). + Admitted. + Global Typeclasses Opaque invoke_start. + + (* + pub fn invoke_complete( + &mut self, + agent: AgentId, + inv: InvocationId, + ) -> Result { + let result = + transitions::invoke_complete(self.state.clone(), &self.background, agent, inv); + self.apply(result) + } + *) + Definition invoke_complete + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; agent; inv ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let agent := M.alloc (| Ty.path "argus_kernel::types::AgentId", agent |) in + let inv := M.alloc (| Ty.path "argus_kernel::types::InvocationId", inv |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::invoke_complete", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| agent |); + M.read (| inv |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_invoke_complete : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "invoke_complete" (invoke_complete A C E). + Admitted. + Global Typeclasses Opaque invoke_complete. + + (* + pub fn return_endorsed( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = + transitions::return_endorsed(self.state.clone(), &self.background, child, parent); + self.apply(result) + } + *) + Definition return_endorsed + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; child; parent ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::return_endorsed", [], [] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.read (| child |); + M.read (| parent |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_return_endorsed : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "return_endorsed" (return_endorsed A C E). + Admitted. + Global Typeclasses Opaque return_endorsed. + + (* + pub fn return_unendorsed( + &mut self, + child: AgentId, + parent: AgentId, + ) -> Result { + let result = transitions::return_unendorsed( + self.state.clone(), + &self.background, + &self.content_gate, + child, + parent, + ); + self.apply(result) + } + *) + Definition return_unendorsed + (A C E : Ty.t) + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + let Self : Ty.t := Self A C E in + match ε, τ, α with + | [], [], [ self; child; parent ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ] ], + self + |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.read (| + let~ result : + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_function (| "argus_kernel::transitions::return_unendorsed", [], [ C ] |), + [ + M.call_closure (| + Ty.path "argus_kernel::state::KernelState", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::state::KernelState", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "state" + |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "background" + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::kernel::Kernel", + "content_gate" + |) + |) + |) + |); + M.read (| child |); + M.read (| parent |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "argus_kernel::event::KernelEvent"; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply (Ty.path "argus_kernel::kernel::Kernel") [] [ A; C; E ], + "apply", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| self |) |) |); + M.read (| result |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_return_unendorsed : + forall (A C E : Ty.t), + M.IsAssociatedFunction.C (Self A C E) "return_unendorsed" (return_unendorsed A C E). + Admitted. + Global Typeclasses Opaque return_unendorsed. + End Impl_argus_kernel_kernel_Kernel_A_C_E. +End kernel. diff --git a/argus/formal/extracted/state.v b/argus/formal/extracted/state.v new file mode 100644 index 0000000..9274202 --- /dev/null +++ b/argus/formal/extracted/state.v @@ -0,0 +1,3194 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module state. + (* StructRecord + { + name := "KernelState"; + const_params := []; + ty_params := []; + fields := + [ + ("agent_active", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ]); + ("agent_parent", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ]); + ("agent_cap", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ]); + ("taint_levels", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ]); + ("in_flight", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ]); + ("invocation_tool", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ]); + ("tool_registered", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ]); + ("gh_taint_invoked", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ]); + ("gh_taint_received", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ]) + ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + self + |) in + Value.mkStructRecord + "argus_kernel::state::KernelState" + [] + [] + [ + ("agent_active", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_active" + |) + |) + |) + |) + ] + |)); + ("agent_parent", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_parent" + |) + |) + |) + |) + ] + |)); + ("agent_cap", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_cap" + |) + |) + |) + |) + ] + |)); + ("taint_levels", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |) + |) + |) + ] + |)); + ("in_flight", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |) + |) + |) + ] + |)); + ("invocation_tool", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |) + |) + |) + ] + |)); + ("tool_registered", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "tool_registered" + |) + |) + |) + |) + ] + |)); + ("gh_taint_invoked", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |) + |) + |) + ] + |)); + ("gh_taint_received", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_trait_method (| + "core::clone::Clone", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |) + |) + |) + ] + |)) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_state_KernelState. + + Module Impl_core_fmt_Debug_for_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.read (| + let~ names : + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 9 ] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ] + ] := + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 9 ] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ], + Value.Array + [ + mk_str (| "agent_active" |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "agent_parent" |) |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "agent_cap" |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "taint_levels" |) |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "in_flight" |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "invocation_tool" |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "tool_registered" |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "gh_taint_invoked" |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "gh_taint_received" |) |) + |) + ] + |) + |) + |) + |) in + let~ values : + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ] ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ] ] + ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 9 ] + [ Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ] ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ] ] + ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 9 ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ] + ], + Value.Array + [ + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_active" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_parent" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_cap" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "tool_registered" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |) + |) + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_struct_fields_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "KernelState" |) |) |); + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ] + ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 9 ] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ] + ]) + (Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "str" ] ] + ]), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| names |) |) |) ] + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| values |) |) |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_state_KernelState. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_state_KernelState. + + Module Impl_core_cmp_PartialEq_argus_kernel_state_KernelState_for_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + other + |) in + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "agent_active" + |) + |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "agent_parent" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "agent_cap" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "tool_registered" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "tool_registered" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |) + ] + |))) + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] + ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |) + ] + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::state::KernelState" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_state_KernelState_for_argus_kernel_state_KernelState. + + Module Impl_core_cmp_Eq_for_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ + fun γ => + ltac:(M.monadic + (Value.Tuple [])) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_state_KernelState. + + Module Impl_argus_kernel_state_KernelState. + Definition Self : Ty.t := Ty.path "argus_kernel::state::KernelState". + + (* + pub fn initial() -> Self { + let root = AgentId::root(); + let all_caps: BTreeSet = CapKind::all().iter().copied().collect(); + + let mut agent_active = BTreeSet::new(); + agent_active.insert(root.clone()); + + let mut agent_cap = BTreeMap::new(); + agent_cap.insert(root, all_caps); + + Self { + agent_active, + agent_parent: BTreeMap::new(), + agent_cap, + taint_levels: BTreeMap::new(), + in_flight: BTreeMap::new(), + invocation_tool: BTreeMap::new(), + tool_registered: BTreeSet::new(), + gh_taint_invoked: BTreeMap::new(), + gh_taint_received: BTreeMap::new(), + } + } + *) + Definition initial (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [] => + ltac:(M.monadic + (M.read (| + let~ root : Ty.path "argus_kernel::types::AgentId" := + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_associated_function (| + Ty.path "argus_kernel::types::AgentId", + "root", + [], + [] + |), + [] + |) in + let~ all_caps : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "core::iter::adapters::copied::Copied") + [] + [ + Ty.apply + (Ty.path "core::slice::iter::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + [], + [], + "collect", + [], + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ] + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::iter::adapters::copied::Copied") + [] + [ + Ty.apply + (Ty.path "core::slice::iter::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "core::slice::iter::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + [], + [], + "copied", + [], + [ Ty.path "argus_kernel::capability::CapKind" ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::slice::iter::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + M.get_associated_function (| + Ty.apply + (Ty.path "slice") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + "iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "slice") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + M.get_associated_function (| + Ty.path "argus_kernel::capability::CapKind", + "all", + [], + [] + |), + [] + |) + |) + |) + ] + |) + ] + |) + ] + |) in + let~ agent_active : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + "new", + [], + [] + |), + [] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + "insert", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, agent_active |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, root |) ] + |) + ] + |) in + let~ agent_cap : + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, agent_cap |); + M.read (| root |); + M.read (| all_caps |) + ] + |) in + M.alloc (| + Ty.path "argus_kernel::state::KernelState", + Value.mkStructRecord + "argus_kernel::state::KernelState" + [] + [] + [ + ("agent_active", M.read (| agent_active |)); + ("agent_parent", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("agent_cap", M.read (| agent_cap |)); + ("taint_levels", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("in_flight", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("invocation_tool", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("tool_registered", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + "new", + [], + [] + |), + [] + |)); + ("gh_taint_invoked", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)); + ("gh_taint_received", + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |)) + ] + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_initial : M.IsAssociatedFunction.C Self "initial" initial. + Admitted. + Global Typeclasses Opaque initial. + + (* + pub fn speculative_taint( + &self, + agent: &AgentId, + bg: &BackgroundTheory, + ) -> BTreeSet { + let mut taint: BTreeSet = self + .taint_levels + .get(agent) + .cloned() + .unwrap_or_default(); + + if let Some(flights) = self.in_flight.get(agent) { + for inv in flights { + debug_assert!( + self.invocation_tool.contains_key(inv), + "in_flight contains InvocationId {inv} with no invocation_tool binding" + ); + if let Some(tool_id) = self.invocation_tool.get(inv) + && let Some(meta) = bg.tool_metadata(tool_id) + && !meta.endorsed + { + taint.insert(meta.conf_floor); + } + } + } + + taint + } + *) + Definition speculative_taint (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; agent; bg ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + self + |) in + let agent := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + agent + |) in + let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + M.read (| + let~ taint : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ], + "unwrap_or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "cloned", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) + ] + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ M.read (| flights |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::InvocationId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| M.break (||) |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let inv := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + γ0_0 + |) in + M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + Value.Bool true + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path + "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path + "argus_kernel::types::ToolId"; + Ty.path + "alloc::alloc::Global" + ], + "contains_key", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + self + |) + |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + inv + |) + |) + |) + ] + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.never_to_any (| + M.call_closure (| + Ty.path "never", + M.get_function (| + "core::panicking::panic_fmt", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 2; + Value.Integer + IntegerKind.Usize + 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 2 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "in_flight contains InvocationId " + |); + mk_str (| + " with no invocation_tool binding" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 1 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + inv + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) + |))); + fun γ => + ltac:(M.monadic + (Value.Tuple [])) + ] + |) in + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |) + |))); + fun γ => + ltac:(M.monadic (Value.Tuple [])) + ] + |) in + M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path + "argus_kernel::types::ToolId"; + Ty.path + "alloc::alloc::Global" + ], + "get", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| self |) + |), + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| inv |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let tool_id := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ], + γ0_0 + |) in + let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.get_associated_function (| + Ty.path + "argus_kernel::background::BackgroundTheory", + "tool_metadata", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| bg |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| tool_id |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let meta := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ], + γ0_0 + |) in + let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| meta |) + |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.read (| + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::ConfLevel"; + Ty.path + "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + taint + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| meta |) + |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + ] + |) in + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |) + |))); + fun γ => + ltac:(M.monadic (Value.Tuple [])) + ] + |) + |) + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + taint + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_speculative_taint : + M.IsAssociatedFunction.C Self "speculative_taint" speculative_taint. + Admitted. + Global Typeclasses Opaque speculative_taint. + End Impl_argus_kernel_state_KernelState. +End state. diff --git a/argus/formal/extracted/traits.v b/argus/formal/extracted/traits.v new file mode 100644 index 0000000..b8e1cbc --- /dev/null +++ b/argus/formal/extracted/traits.v @@ -0,0 +1,13 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module traits. + (* Trait *) + (* Empty module 'AuthorizerOracle' *) + + (* Trait *) + (* Empty module 'ContentGateOracle' *) + + (* Trait *) + (* Empty module 'EventStore' *) +End traits. diff --git a/argus/formal/extracted/transitions.v b/argus/formal/extracted/transitions.v new file mode 100644 index 0000000..625cbf2 --- /dev/null +++ b/argus/formal/extracted/transitions.v @@ -0,0 +1,14951 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module transitions. + (* + fn flow_allowed( + bg: &BackgroundTheory, + content_gate: &impl ContentGateOracle, + agent: &AgentId, + tool: &ToolId, + state: &KernelState, + level: ConfLevel, + egress: EgressKind, + ) -> bool { + match bg.flow_mode(level, egress) { + FlowMode::Allow => true, + FlowMode::Inspect => content_gate.passes(agent, tool, state, bg), + FlowMode::Deny => bg.has_flow_override(agent, tool, level), + } + } + *) + Definition flow_allowed (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ impl_ContentGateOracle ], [ bg; content_gate; agent; tool; state; level; egress ] => + ltac:(M.monadic + (let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + let content_gate := + M.alloc (| Ty.apply (Ty.path "&") [] [ impl_ContentGateOracle ], content_gate |) in + let agent := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + agent + |) in + let tool := + M.alloc (| Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], tool |) in + let state := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::state::KernelState" ], + state + |) in + let level := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", level |) in + let egress := M.alloc (| Ty.path "argus_kernel::types::EgressKind", egress |) in + M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.path "argus_kernel::background::FlowMode", + M.call_closure (| + Ty.path "argus_kernel::background::FlowMode", + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheory", + "flow_mode", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |); + M.read (| level |); + M.read (| egress |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Allow" |) in + Value.Bool true)); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Inspect" |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "argus_kernel::traits::ContentGateOracle", + impl_ContentGateOracle, + [], + [], + "passes", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| content_gate |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| state |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let _ := M.is_struct_tuple (| γ, "argus_kernel::background::FlowMode::Deny" |) in + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheory", + "has_flow_override", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool |) |) |); + M.read (| level |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_flow_allowed : + M.IsFunction.C "argus_kernel::transitions::flow_allowed" flow_allowed. + Admitted. + Global Typeclasses Opaque flow_allowed. + + (* + fn clear_agent_state(state: &mut KernelState, agent: &AgentId) { + state.taint_levels.remove(agent); + state.in_flight.remove(agent); + state.gh_taint_invoked.remove(agent); + state.gh_taint_received.remove(agent); + } + *) + Definition clear_agent_state (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; agent ] => + ltac:(M.monadic + (let state := + M.alloc (| + Ty.apply (Ty.path "&mut") [] [ Ty.path "argus_kernel::state::KernelState" ], + state + |) in + let agent := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + agent + |) in + M.read (| + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| state |) |), + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| state |) |), + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| state |) |), + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| state |) |), + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| agent |) |) |) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_clear_agent_state : + M.IsFunction.C "argus_kernel::transitions::clear_agent_state" clear_agent_state. + Admitted. + Global Typeclasses Opaque clear_agent_state. + + (* + pub fn register_tool( + mut state: KernelState, + bg: &BackgroundTheory, + tool: ToolId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if !bg.has_tool(&tool) { + return Err(KernelError::PreconditionViolation( + format!("tool {tool} not in background theory"), + )); + } + if state.tool_registered.contains(&tool) { + return Err(KernelError::PreconditionViolation( + format!("tool {tool} already registered"), + )); + } + + state.tool_registered.insert(tool.clone()); + + Ok((state, KernelAction::RegisterTool { tool })) + } + *) + Definition register_tool (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; bg; tool ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + let tool := M.alloc (| Ty.path "argus_kernel::types::ToolId", tool |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheory", + "has_tool", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| bg |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, tool |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "tool " |); + mk_str (| + " not in background theory" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::ToolId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "tool_registered" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, tool |) |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "tool " |); + mk_str (| " already registered" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ToolId"; Ty.path "alloc::alloc::Global" ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "tool_registered" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::ToolId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ToolId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, tool |) ] + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::RegisterTool" + [] + [] + [ ("tool", M.read (| tool |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_register_tool : + M.IsFunction.C "argus_kernel::transitions::register_tool" register_tool. + Admitted. + Global Typeclasses Opaque register_tool. + + (* + pub fn delegate( + mut state: KernelState, + _bg: &BackgroundTheory, + grantor: AgentId, + grantee: AgentId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&grantor) { + return Err(KernelError::PreconditionViolation( + format!("grantor {grantor} is not active"), + )); + } + if state.agent_active.contains(&grantee) { + return Err(KernelError::PreconditionViolation( + format!("grantee {grantee} is already active"), + )); + } + if grantee == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot delegate to root".to_owned(), + )); + } + + state.agent_active.insert(grantee.clone()); + state + .agent_parent + .retain(|child, parent| child != &grantee && parent != &grantee); + state.agent_parent.insert(grantee.clone(), grantor.clone()); + state.agent_cap.insert(grantee.clone(), BTreeSet::new()); + clear_agent_state(&mut state, &grantee); + + Ok((state, KernelAction::Delegate { grantor, grantee })) + } + *) + Definition delegate (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; _bg; grantor; grantee ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let _bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + _bg + |) in + let grantor := M.alloc (| Ty.path "argus_kernel::types::AgentId", grantor |) in + let grantee := M.alloc (| Ty.path "argus_kernel::types::AgentId", grantee |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, grantor |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "grantor " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + grantor + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, grantee |) |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "grantee " |); + mk_str (| " is already active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + grantee + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::types::AgentId", + [], + [ Ty.path "argus_kernel::types::AgentId" ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, grantee |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "argus_kernel::types::AgentId", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_associated_function (| + Ty.path "argus_kernel::types::AgentId", + "root", + [], + [] + |), + [] + |) + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "cannot delegate to root" |) |) + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, grantee |) ] + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "retain", + [], + [ + Ty.function + [ + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]; + Ty.apply (Ty.path "&mut") [] [ Ty.path "argus_kernel::types::AgentId" ] + ] + (Ty.path "bool") + ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0; α1 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let child := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + γ + |) in + M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α1 + |), + [ + fun γ => + ltac:(M.monadic + (let parent := + M.copy (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + γ + |) in + LogicalOp.and (| + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, child |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" + ], + M.borrow (| Pointer.Kind.Ref, grantee |) + |) + |) + ] + |), + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, parent |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" + ], + M.borrow (| Pointer.Kind.Ref, grantee |) + |) + |) + ] + |))) + |))) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::types::AgentId" ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, grantee |) ] + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, grantor |) ] + |) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, grantee |) ] + |); + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_function (| "argus_kernel::transitions::clear_agent_state", [], [] |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.borrow (| Pointer.Kind.MutRef, state |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, grantee |) |) + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::Delegate" + [] + [] + [ ("grantor", M.read (| grantor |)); ("grantee", M.read (| grantee |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_delegate : + M.IsFunction.C "argus_kernel::transitions::delegate" delegate. + Admitted. + Global Typeclasses Opaque delegate. + + (* + pub fn grant_capability( + mut state: KernelState, + _bg: &BackgroundTheory, + parent: AgentId, + child: AgentId, + cap: CapKind, + ) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} is not active"), + )); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation( + format!("child {child} is not active"), + )); + } + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation( + format!("{child} is not a direct child of {parent}"), + )); + } + if !state.agent_cap.get(&parent).is_some_and(|caps| caps.contains(&cap)) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} does not hold capability {cap}"), + )); + } + + state.agent_cap.entry(child.clone()).or_default().insert(cap); + + Ok((state, KernelAction::GrantCapability { parent, child, cap })) + } + *) + Definition grant_capability (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; _bg; parent; child; cap ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let _bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + _bg + |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let cap := M.alloc (| Ty.path "argus_kernel::capability::CapKind", cap |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + [], + [ + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + Value.StructTuple + "core::option::Option::Some" + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "" |); + mk_str (| + " is not a direct child of " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "is_some_and", + [], + [ + Ty.function + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + (Ty.path "bool") + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let caps := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + γ + |) in + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ + Ty.path + "argus_kernel::capability::CapKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| caps |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| Pointer.Kind.Ref, cap |) + |) + |) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| + " does not hold capability " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::capability::CapKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + cap + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::capability::CapKind"; Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, child |) ] + |) + ] + |) + ] + |) + |) + |); + M.read (| cap |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::GrantCapability" + [] + [] + [ + ("parent", M.read (| parent |)); + ("child", M.read (| child |)); + ("cap", M.read (| cap |)) + ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_grant_capability : + M.IsFunction.C "argus_kernel::transitions::grant_capability" grant_capability. + Admitted. + Global Typeclasses Opaque grant_capability. + + (* + pub fn revoke( + mut state: KernelState, + _bg: &BackgroundTheory, + parent: AgentId, + target: AgentId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&target) != Some(&parent) { + return Err(KernelError::PreconditionViolation( + format!("{target} is not a direct child of {parent}"), + )); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} is not active"), + )); + } + if !state.agent_active.contains(&target) { + return Err(KernelError::PreconditionViolation( + format!("target {target} is not active"), + )); + } + if target == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot revoke root".to_owned(), + )); + } + + state.agent_active.remove(&target); + state.agent_parent.retain(|child, _| child != &target); + state.agent_cap.remove(&target); + clear_agent_state(&mut state, &target); + + Ok((state, KernelAction::Revoke { parent, target })) + } + *) + Definition revoke (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; _bg; parent; target ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let _bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + _bg + |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + let target := M.alloc (| Ty.path "argus_kernel::types::AgentId", target |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + [], + [ + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, target |) |) + |) + ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + Value.StructTuple + "core::option::Option::Some" + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "" |); + mk_str (| + " is not a direct child of " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + target + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, target |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "target " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + target + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::types::AgentId", + [], + [ Ty.path "argus_kernel::types::AgentId" ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, target |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "argus_kernel::types::AgentId", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_associated_function (| + Ty.path "argus_kernel::types::AgentId", + "root", + [], + [] + |), + [] + |) + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "cannot revoke root" |) |) + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, target |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "retain", + [], + [ + Ty.function + [ + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]; + Ty.apply (Ty.path "&mut") [] [ Ty.path "argus_kernel::types::AgentId" ] + ] + (Ty.path "bool") + ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0; α1 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let child := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + γ + |) in + M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α1 + |), + [ + fun γ => + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, child |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + M.borrow (| Pointer.Kind.Ref, target |) + |) + |) + ] + |))) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, target |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_function (| "argus_kernel::transitions::clear_agent_state", [], [] |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.borrow (| Pointer.Kind.MutRef, state |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, target |) |) + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::Revoke" + [] + [] + [ ("parent", M.read (| parent |)); ("target", M.read (| target |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_revoke : + M.IsFunction.C "argus_kernel::transitions::revoke" revoke. + Admitted. + Global Typeclasses Opaque revoke. + + (* + pub fn cascade_revoke( + mut state: KernelState, + _bg: &BackgroundTheory, + child: AgentId, + parent: AgentId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation( + format!("{child} is not a direct child of {parent}"), + )); + } + if state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} is still active (use revoke, not cascade_revoke)"), + )); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation( + format!("child {child} is not active"), + )); + } + if child == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "cannot cascade_revoke root".to_owned(), + )); + } + + state.agent_active.remove(&child); + state.agent_parent.retain(|c, _| c != &child); + state.agent_cap.remove(&child); + clear_agent_state(&mut state, &child); + + Ok((state, KernelAction::CascadeRevoke { child, parent })) + } + *) + Definition cascade_revoke (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; _bg; child; parent ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let _bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + _bg + |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + [], + [ + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + Value.StructTuple + "core::option::Option::Some" + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "" |); + mk_str (| + " is not a direct child of " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| + " is still active (use revoke, not cascade_revoke)" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::types::AgentId", + [], + [ Ty.path "argus_kernel::types::AgentId" ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, child |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "argus_kernel::types::AgentId", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_associated_function (| + Ty.path "argus_kernel::types::AgentId", + "root", + [], + [] + |), + [] + |) + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "cannot cascade_revoke root" |) |) + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::AgentId"; Ty.path "alloc::alloc::Global" ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "retain", + [], + [ + Ty.function + [ + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ]; + Ty.apply (Ty.path "&mut") [] [ Ty.path "argus_kernel::types::AgentId" ] + ] + (Ty.path "bool") + ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0; α1 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let c := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + γ + |) in + M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&mut") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + α1 + |), + [ + fun γ => + ltac:(M.monadic + (M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, c |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ], + M.borrow (| Pointer.Kind.Ref, child |) + |) + |) + ] + |))) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_function (| "argus_kernel::transitions::clear_agent_state", [], [] |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.borrow (| Pointer.Kind.MutRef, state |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::CascadeRevoke" + [] + [] + [ ("child", M.read (| child |)); ("parent", M.read (| parent |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_cascade_revoke : + M.IsFunction.C "argus_kernel::transitions::cascade_revoke" cascade_revoke. + Admitted. + Global Typeclasses Opaque cascade_revoke. + + (* + pub fn invoke_start( + mut state: KernelState, + bg: &BackgroundTheory, + authorizer: &A, + content_gate: &C, + agent: AgentId, + tool: ToolId, + inv: InvocationId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if !state.agent_active.contains(&agent) { + return Err(KernelError::PreconditionViolation( + format!("agent {agent} is not active"), + )); + } + if agent == AgentId::root() { + return Err(KernelError::PreconditionViolation( + "root agent cannot invoke tools directly".to_owned(), + )); + } + if !state.tool_registered.contains(&tool) { + return Err(KernelError::PreconditionViolation( + format!("tool {tool} is not registered"), + )); + } + if state.invocation_tool.contains_key(&inv) { + return Err(KernelError::PreconditionViolation( + format!("invocation {inv} already exists"), + )); + } + for flights in state.in_flight.values() { + if flights.contains(&inv) { + return Err(KernelError::PreconditionViolation( + format!("invocation {inv} is already in-flight"), + )); + } + } + + let tool_meta = bg.tool_metadata(&tool).ok_or_else(|| { + KernelError::PreconditionViolation(format!("tool {tool} not in background theory")) + })?; + + let agent_caps = state.agent_cap.get(&agent); + for required_cap in &tool_meta.capabilities { + if !agent_caps.is_some_and(|caps| caps.contains(required_cap)) { + return Err(KernelError::PreconditionViolation( + format!("agent {agent} lacks required capability {required_cap}"), + )); + } + } + + let spec_taint = state.speculative_taint(&agent, bg); + for &level in &spec_taint { + for &egress in &tool_meta.egress { + if !flow_allowed(bg, content_gate, &agent, &tool, &state, level, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2a: speculative taint {level} blocked for egress {egress} on tool {tool}" + ))); + } + } + } + + if !tool_meta.endorsed { + if let Some(agent_flights) = state.in_flight.get(&agent) { + for flight_inv in agent_flights { + if let Some(flight_tool_id) = state.invocation_tool.get(flight_inv) + && let Some(flight_meta) = bg.tool_metadata(flight_tool_id) + { + for &egress in &flight_meta.egress { + if !flow_allowed(bg, content_gate, &agent, flight_tool_id, &state, tool_meta.conf_floor, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2b: new tool {tool} taint {} conflicts with in-flight {flight_tool_id} egress {egress}", + tool_meta.conf_floor + ))); + } + } + } + } + } + + for &egress in &tool_meta.egress { + if !flow_allowed(bg, content_gate, &agent, &tool, &state, tool_meta.conf_floor, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate 2c: tool {tool} self-flow blocked ({}, {egress})", + tool_meta.conf_floor + ))); + } + } + } + + if !authorizer.allows(&agent, &tool, &state, bg) { + return Err(KernelError::PreconditionViolation( + format!("authorizer denied ({agent}, {tool})"), + )); + } + + state.invocation_tool.insert(inv.clone(), tool.clone()); + state + .in_flight + .entry(agent.clone()) + .or_default() + .insert(inv.clone()); + + Ok((state, KernelAction::InvokeStart { agent, tool, inv })) + } + *) + Definition invoke_start (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ A; C ], [ state; bg; authorizer; content_gate; agent; tool; inv ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + let authorizer := M.alloc (| Ty.apply (Ty.path "&") [] [ A ], authorizer |) in + let content_gate := M.alloc (| Ty.apply (Ty.path "&") [] [ C ], content_gate |) in + let agent := M.alloc (| Ty.path "argus_kernel::types::AgentId", agent |) in + let tool := M.alloc (| Ty.path "argus_kernel::types::ToolId", tool |) in + let inv := M.alloc (| Ty.path "argus_kernel::types::InvocationId", inv |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "agent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "argus_kernel::types::AgentId", + [], + [ Ty.path "argus_kernel::types::AgentId" ], + "eq", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, agent |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "argus_kernel::types::AgentId", + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_associated_function (| + Ty.path "argus_kernel::types::AgentId", + "root", + [], + [] + |), + [] + |) + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + mk_str (| "root agent cannot invoke tools directly" |) + |) + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::ToolId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "tool_registered" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, tool |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "tool " |); + mk_str (| " is not registered" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "contains_key", + [], + [ Ty.path "argus_kernel::types::InvocationId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, inv |) |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "invocation " |); + mk_str (| " already exists" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + inv + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "values", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |) + ] + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "alloc::collections::btree::map::Values") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| M.read (| M.break (||) |) |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| flights |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + inv + |) + |) + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 2; + Value.Integer + IntegerKind.Usize + 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 2 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "invocation " + |); + mk_str (| + " is already in-flight" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 1 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + inv + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |) in + let~ tool_meta : + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ] := + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::ToolMetadata" ], + M.alloc (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::ops::control_flow::ControlFlow") + [] + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ]; + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + M.get_trait_method (| + "core::ops::try_trait::Try", + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ]; + Ty.path "argus_kernel::error::KernelError" + ], + [], + [], + "branch", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + "ok_or_else", + [], + [ + Ty.path "argus_kernel::error::KernelError"; + Ty.function [] (Ty.path "argus_kernel::error::KernelError") + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheory", + "tool_metadata", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, tool |) |) + |) + ] + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "argus_kernel::error::KernelError", + M.alloc (| Ty.tuple [], α0 |), + [ + fun γ => + ltac:(M.monadic + (Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : + Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 2; + Value.Integer + IntegerKind.Usize + 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ + Value.Integer + IntegerKind.Usize + 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "tool " |); + mk_str (| + " not in background theory" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ + Value.Integer + IntegerKind.Usize + 1 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ])) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Break", + 0 + |) in + let residual := + M.copy (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ], + γ0_0 + |) in + M.never_to_any (| + M.read (| + M.return_ (| + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + M.get_trait_method (| + "core::ops::try_trait::FromResidual", + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + [], + [ + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.path "core::convert::Infallible"; + Ty.path "argus_kernel::error::KernelError" + ] + ], + "from_residual", + [], + [] + |), + [ M.read (| residual |) ] + |) + |) + |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::ops::control_flow::ControlFlow::Continue", + 0 + |) in + let val := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ], + γ0_0 + |) in + M.read (| val |))) + ] + |) in + let~ agent_caps : + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_cap" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::capability::CapKind"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| tool_meta |) |), + "argus_kernel::background::ToolMetadata", + "capabilities" + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| M.read (| M.break (||) |) |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let required_cap := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::capability::CapKind" ], + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path + "alloc::alloc::Global" + ] + ] + ], + "is_some_and", + [], + [ + Ty.function + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path + "alloc::alloc::Global" + ] + ] + ] + (Ty.path "bool") + ] + |), + [ + M.read (| agent_caps |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path + "alloc::alloc::Global" + ] + ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let caps := + M.copy (| + Ty.apply + (Ty.path + "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path + "alloc::alloc::Global" + ] + ], + γ + |) in + M.call_closure (| + Ty.path + "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::capability::CapKind"; + Ty.path + "alloc::alloc::Global" + ], + "contains", + [], + [ + Ty.path + "argus_kernel::capability::CapKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + caps + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + required_cap + |) + |) + |) + ] + |))) + ] + |))) + | _ => + M.impossible + "wrong number of arguments" + end)) + ] + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 2; + Value.Integer + IntegerKind.Usize + 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 2 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "agent " + |); + mk_str (| + " lacks required capability " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 2 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::capability::CapKind" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + required_cap + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |) in + let~ spec_taint : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.path "argus_kernel::state::KernelState", + "speculative_taint", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, state |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |) + ] + |) in + let~ _ : Ty.tuple [] := + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, spec_taint |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| M.read (| M.break (||) |) |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let level := + M.copy (| + Ty.path "argus_kernel::types::ConfLevel", + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.call_closure (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::EgressKind"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| tool_meta |) + |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| + Pointer.Kind.MutRef, + iter + |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| + M.break (||) + |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let γ0_0 := + M.deref (| + M.read (| γ0_0 |) + |) in + let egress := + M.copy (| + Ty.path + "argus_kernel::types::EgressKind", + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path + "bool", + M.call_closure (| + Ty.path + "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path + "bool", + M.get_function (| + "argus_kernel::transitions::flow_allowed", + [], + [ + C + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + bg + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + content_gate + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + state + |) + |) + |); + M.read (| + level + |); + M.read (| + egress + |) + ] + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| + γ + |), + Value.Bool + true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ + res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 3; + Value.Integer + IntegerKind.Usize + 3 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "flow gate 2a: speculative taint " + |); + mk_str (| + " blocked for egress " + |); + mk_str (| + " on tool " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ConfLevel" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + level + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + egress + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => + ltac:(M.monadic + (Value.Tuple + [])) + ] + |))) + ] + |) in + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |))) + |) + |))) + ] + |) + |)) + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| tool_meta |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let agent_flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::InvocationId" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ M.read (| agent_flights |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| + Pointer.Kind.MutRef, + iter + |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| M.break (||) |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flight_inv := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path + "argus_kernel::types::ToolId"; + Ty.path + "alloc::alloc::Global" + ], + "get", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + flight_inv + |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flight_tool_id := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ], + γ0_0 + |) in + let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.get_associated_function (| + Ty.path + "argus_kernel::background::BackgroundTheory", + "tool_metadata", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + bg + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + flight_tool_id + |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flight_meta := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ], + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.call_closure (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path + "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::EgressKind"; + Ty.path + "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + flight_meta + |) + |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] := + M.read (| + γ + |) in + M.read (| + M.loop (| + Ty.tuple + [], + ltac:(M.monadic + (let~ + _ : + Ty.tuple + [] := + M.match_operator (| + Ty.tuple + [], + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| + Pointer.Kind.MutRef, + iter + |) + |) + |) + ] + |) + |), + [ + fun + γ => + ltac:(M.monadic + (let + _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| + M.break (||) + |) + |))); + fun + γ => + ltac:(M.monadic + (let + γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let + γ0_0 := + M.deref (| + M.read (| + γ0_0 + |) + |) in + let + egress := + M.copy (| + Ty.path + "argus_kernel::types::EgressKind", + γ0_0 + |) in + M.match_operator (| + Ty.tuple + [], + M.alloc (| + Ty.tuple + [], + Value.Tuple + [] + |), + [ + fun + γ => + ltac:(M.monadic + (let + γ := + M.use + (M.alloc (| + Ty.path + "bool", + M.call_closure (| + Ty.path + "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path + "bool", + M.get_function (| + "argus_kernel::transitions::flow_allowed", + [], + [ + C + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + bg + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + content_gate + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + flight_tool_id + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + state + |) + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + tool_meta + |) + |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |); + M.read (| + egress + |) + ] + |) + ] + |) + |)) in + let + _ := + is_constant_or_break_match (| + M.read (| + γ + |), + Value.Bool + true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ + res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 4; + Value.Integer + IntegerKind.Usize + 4 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 4 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "flow gate 2b: new tool " + |); + mk_str (| + " taint " + |); + mk_str (| + " conflicts with in-flight " + |); + mk_str (| + " egress " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 4 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + M.match_operator (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 4 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + M.alloc (| + Ty.tuple + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ConfLevel" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + Value.Tuple + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + tool_meta + |) + |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + tool + |); + M.borrow (| + Pointer.Kind.Ref, + flight_tool_id + |); + M.borrow (| + Pointer.Kind.Ref, + egress + |) + ] + |), + [ + fun + γ => + ltac:(M.monadic + (let + args := + M.copy (| + Ty.tuple + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ConfLevel" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + γ + |) in + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 1 + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ConfLevel" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 0 + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 2 + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 3 + |) + |) + |) + |) + ] + |) + ])) + ] + |) + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun + γ => + ltac:(M.monadic + (Value.Tuple + [])) + ] + |))) + ] + |) in + M.alloc (| + Ty.tuple + [], + Value.Tuple + [] + |))) + |) + |))) + ] + |) + |)) + |))); + fun γ => + ltac:(M.monadic + (Value.Tuple [])) + ] + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::EgressKind" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::EgressKind" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::EgressKind"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| tool_meta |) |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::EgressKind" ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::EgressKind" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::EgressKind" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| M.break (||) |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let egress := + M.copy (| + Ty.path "argus_kernel::types::EgressKind", + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_function (| + "argus_kernel::transitions::flow_allowed", + [], + [ C ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| bg |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + content_gate + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + state + |) + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + tool_meta + |) + |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |); + M.read (| egress |) + ] + |) + ] + |) + |)) in + let _ := + is_constant_or_break_match (| + M.read (| γ |), + Value.Bool true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 4; + Value.Integer + IntegerKind.Usize + 3 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 4 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "flow gate 2c: tool " + |); + mk_str (| + " self-flow blocked (" + |); + mk_str (| + ", " + |); + mk_str (| + ")" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + M.match_operator (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + M.alloc (| + Ty.tuple + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ConfLevel" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + Value.Tuple + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + tool_meta + |) + |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + tool + |); + M.borrow (| + Pointer.Kind.Ref, + egress + |) + ] + |), + [ + fun + γ => + ltac:(M.monadic + (let + args := + M.copy (| + Ty.tuple + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ConfLevel" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ]; + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + γ + |) in + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 1 + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ConfLevel" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 0 + |) + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + M.SubPointer.get_tuple_field (| + args, + 2 + |) + |) + |) + |) + ] + |) + ])) + ] + |) + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "argus_kernel::traits::AuthorizerOracle", + A, + [], + [], + "allows", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| authorizer |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, tool |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, state |) |) + |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 3; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 3 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| + "authorizer denied (" + |); + mk_str (| ", " |); + mk_str (| ")" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ToolId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::types::ToolId" ] := + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.path "argus_kernel::types::ToolId" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::InvocationId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::InvocationId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, inv |) ] + |); + M.call_closure (| + Ty.path "argus_kernel::types::ToolId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::ToolId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, tool |) ] + |) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, agent |) ] + |) + ] + |) + ] + |) + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::InvocationId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::InvocationId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, inv |) ] + |) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::InvokeStart" + [] + [] + [ + ("agent", M.read (| agent |)); + ("tool", M.read (| tool |)); + ("inv", M.read (| inv |)) + ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_invoke_start : + M.IsFunction.C "argus_kernel::transitions::invoke_start" invoke_start. + Admitted. + Global Typeclasses Opaque invoke_start. + + (* + pub fn invoke_complete( + mut state: KernelState, + bg: &BackgroundTheory, + agent: AgentId, + inv: InvocationId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if !state.in_flight.get(&agent).is_some_and(|flights| flights.contains(&inv)) { + return Err(KernelError::PreconditionViolation( + format!("invocation {inv} is not in-flight for agent {agent}"), + )); + } + if !state.agent_active.contains(&agent) { + return Err(KernelError::PreconditionViolation( + format!("agent {agent} is not active"), + )); + } + + if let Some(flights) = state.in_flight.get_mut(&agent) { + flights.remove(&inv); + } + + if let Some(tool_id) = state.invocation_tool.get(&inv) + && let Some(meta) = bg.tool_metadata(tool_id) + && !meta.endorsed + { + state + .taint_levels + .entry(agent.clone()) + .or_default() + .insert(meta.conf_floor); + state + .gh_taint_invoked + .entry(agent.clone()) + .or_default() + .insert(meta.conf_floor); + } + + Ok((state, KernelAction::InvokeComplete { agent, inv })) + } + *) + Definition invoke_complete (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; bg; agent; inv ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + let agent := M.alloc (| Ty.path "argus_kernel::types::AgentId", agent |) in + let inv := M.alloc (| Ty.path "argus_kernel::types::InvocationId", inv |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "is_some_and", + [], + [ + Ty.function + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + (Ty.path "bool") + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ + |) in + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| flights |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| Pointer.Kind.Ref, inv |) + |) + |) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "invocation " |); + mk_str (| + " is not in-flight for agent " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + inv + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "agent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + agent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get_mut", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, agent |) |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let flights := + M.copy (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ0_0 + |) in + M.read (| + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ], + "remove", + [], + [ Ty.path "argus_kernel::types::InvocationId" ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| M.read (| flights |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, inv |) |) + |) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ToolId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "argus_kernel::types::ToolId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::InvocationId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, inv |) |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let tool_id := + M.copy (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + γ0_0 + |) in + let γ := + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ] + ], + M.get_associated_function (| + Ty.path "argus_kernel::background::BackgroundTheory", + "tool_metadata", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| bg |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| tool_id |) |) |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let meta := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::background::ToolMetadata" ], + γ0_0 + |) in + let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| meta |) |), + "argus_kernel::background::ToolMetadata", + "endorsed" + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.read (| + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, agent |) ] + |) + ] + |) + ] + |) + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| meta |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + ] + |) in + let~ _ : Ty.path "bool" := + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ], + "insert", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "gh_taint_invoked" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, agent |) ] + |) + ] + |) + ] + |) + |) + |); + M.read (| + M.SubPointer.get_struct_record_field (| + M.deref (| M.read (| meta |) |), + "argus_kernel::background::ToolMetadata", + "conf_floor" + |) + |) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::InvokeComplete" + [] + [] + [ ("agent", M.read (| agent |)); ("inv", M.read (| inv |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_invoke_complete : + M.IsFunction.C "argus_kernel::transitions::invoke_complete" invoke_complete. + Admitted. + Global Typeclasses Opaque invoke_complete. + + (* + pub fn return_endorsed( + state: KernelState, + _bg: &BackgroundTheory, + child: AgentId, + parent: AgentId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation( + format!("{child} is not a direct child of {parent}"), + )); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation( + format!("child {child} is not active"), + )); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} is not active"), + )); + } + if state.in_flight.get(&child).is_some_and(|flights| !flights.is_empty()) { + return Err(KernelError::PreconditionViolation( + format!("child {child} has in-flight invocations"), + )); + } + + Ok((state, KernelAction::ReturnEndorsed { child, parent })) + } + *) + Definition return_endorsed (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ state; _bg; child; parent ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let _bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + _bg + |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + [], + [ + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + Value.StructTuple + "core::option::Option::Some" + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "" |); + mk_str (| + " is not a direct child of " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "is_some_and", + [], + [ + Ty.function + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + (Ty.path "bool") + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ + |) in + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ], + "is_empty", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| flights |) |) + |) + ] + |) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| + " has in-flight invocations" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::ReturnEndorsed" + [] + [] + [ ("child", M.read (| child |)); ("parent", M.read (| parent |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_return_endorsed : + M.IsFunction.C "argus_kernel::transitions::return_endorsed" return_endorsed. + Admitted. + Global Typeclasses Opaque return_endorsed. + + (* + pub fn return_unendorsed( + mut state: KernelState, + bg: &BackgroundTheory, + content_gate: &C, + child: AgentId, + parent: AgentId, + ) -> Result<(KernelState, KernelAction), KernelError> { + if state.agent_parent.get(&child) != Some(&parent) { + return Err(KernelError::PreconditionViolation( + format!("{child} is not a direct child of {parent}"), + )); + } + if !state.agent_active.contains(&child) { + return Err(KernelError::PreconditionViolation( + format!("child {child} is not active"), + )); + } + if !state.agent_active.contains(&parent) { + return Err(KernelError::PreconditionViolation( + format!("parent {parent} is not active"), + )); + } + if state.in_flight.get(&child).is_some_and(|flights| !flights.is_empty()) { + return Err(KernelError::PreconditionViolation( + format!("child {child} has in-flight invocations"), + )); + } + + let child_taint = state + .taint_levels + .get(&child) + .cloned() + .unwrap_or_default(); + let empty_flights = BTreeSet::new(); + let parent_flights = state.in_flight.get(&parent).unwrap_or(&empty_flights); + + for &level in &child_taint { + for inv in parent_flights { + if let Some(tool_id) = state.invocation_tool.get(inv) + && let Some(meta) = bg.tool_metadata(tool_id) + { + for &egress in &meta.egress { + if !flow_allowed(bg, content_gate, &parent, tool_id, &state, level, egress) { + return Err(KernelError::PreconditionViolation(format!( + "flow gate: child taint {level} conflicts with parent in-flight tool {tool_id} egress {egress}" + ))); + } + } + } + } + } + + if !child_taint.is_empty() { + state + .taint_levels + .entry(parent.clone()) + .or_default() + .extend(&child_taint); + state + .gh_taint_received + .entry(parent.clone()) + .or_default() + .extend(&child_taint); + } + + Ok((state, KernelAction::ReturnUnendorsed { child, parent })) + } + *) + Definition return_unendorsed (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ C ], [ state; bg; content_gate; child; parent ] => + ltac:(M.monadic + (let state := M.alloc (| Ty.path "argus_kernel::state::KernelState", state |) in + let bg := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::background::BackgroundTheory" ], + bg + |) in + let content_gate := M.alloc (| Ty.apply (Ty.path "&") [] [ C ], content_gate |) in + let child := M.alloc (| Ty.path "argus_kernel::types::AgentId", child |) in + let parent := M.alloc (| Ty.path "argus_kernel::types::AgentId", parent |) in + M.catch_return + (Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ]) (| + ltac:(M.monadic + (M.read (| + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + [], + [ + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + ], + "ne", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_parent" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ], + Value.StructTuple + "core::option::Option::Some" + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::AgentId" ] + ] + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 2 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "" |); + mk_str (| + " is not a direct child of " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.path "alloc::alloc::Global" + ], + "contains", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "agent_active" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "parent " |); + mk_str (| " is not active" |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "is_some_and", + [], + [ + Ty.function + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + (Ty.path "bool") + ] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |); + M.closure + (fun γ => + ltac:(M.monadic + match γ with + | [ α0 ] => + ltac:(M.monadic + (M.match_operator (| + Ty.path "bool", + M.alloc (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + α0 + |), + [ + fun γ => + ltac:(M.monadic + (let flights := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + γ + |) in + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ], + "is_empty", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.read (| flights |) |) + |) + ] + |) + ] + |))) + ] + |))) + | _ => M.impossible "wrong number of arguments" + end)) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ Ty.path "alloc::string::String" ] + |), + [ + M.read (| + let~ res : Ty.path "alloc::string::String" := + M.call_closure (| + Ty.path "alloc::string::String", + M.get_function (| "alloc::fmt::format", [], [] |), + [ + M.call_closure (| + Ty.path "core::fmt::Arguments", + M.get_associated_function (| + Ty.path "core::fmt::Arguments", + "new_v1", + [ + Value.Integer IntegerKind.Usize 2; + Value.Integer IntegerKind.Usize 1 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 2 + ] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "str" ] + ], + Value.Array + [ + mk_str (| "child " |); + mk_str (| + " has in-flight invocations" + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path "array") + [ Value.Integer IntegerKind.Usize 1 + ] + [ Ty.path "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::AgentId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + child + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + let~ child_taint : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::ConfLevel"; Ty.path "alloc::alloc::Global" + ] + ], + "unwrap_or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "cloned", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, child |) |) + |) + ] + |) + ] + |) + ] + |) in + let~ empty_flights : + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" + ] := + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ Ty.path "argus_kernel::types::InvocationId"; Ty.path "alloc::alloc::Global" + ], + "new", + [], + [] + |), + [] + |) in + let~ parent_flights : + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] := + M.call_closure (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + "unwrap_or", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "get", + [], + [ Ty.path "argus_kernel::types::AgentId" ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "in_flight" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, parent |) |) + |) + ] + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, empty_flights |) |) + |) + ] + |) in + let~ _ : Ty.tuple [] := + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, child_taint |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + M.call_closure (| + Ty.apply + (Ty.path "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path "alloc::collections::btree::set::Iter") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| Pointer.Kind.MutRef, iter |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| M.read (| M.break (||) |) |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let γ0_0 := M.deref (| M.read (| γ0_0 |) |) in + let level := + M.copy (| + Ty.path "argus_kernel::types::ConfLevel", + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple [], + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + M.call_closure (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ M.read (| parent_flights |) ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let~ iter : + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] := + M.read (| γ |) in + M.read (| + M.loop (| + Ty.tuple [], + ltac:(M.monadic + (let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| + Pointer.Kind.MutRef, + iter + |) + |) + |) + ] + |) + |), + [ + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| + M.break (||) + |) + |))); + fun γ => + ltac:(M.monadic + (let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let inv := + M.copy (| + Ty.apply + (Ty.path "&") + [] + [ + Ty.path + "argus_kernel::types::InvocationId" + ], + γ0_0 + |) in + M.match_operator (| + Ty.tuple [], + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path + "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path + "argus_kernel::types::InvocationId"; + Ty.path + "argus_kernel::types::ToolId"; + Ty.path + "alloc::alloc::Global" + ], + "get", + [], + [ + Ty.path + "argus_kernel::types::InvocationId" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "invocation_tool" + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + inv + |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let tool_id := + M.copy (| + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ], + γ0_0 + |) in + let γ := + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ] + ], + M.get_associated_function (| + Ty.path + "argus_kernel::background::BackgroundTheory", + "tool_metadata", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + bg + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + tool_id + |) + |) + |) + ] + |) + |) in + let γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let meta := + M.copy (| + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::background::ToolMetadata" + ], + γ0_0 + |) in + M.read (| + M.use + (M.alloc (| + Ty.tuple + [], + M.match_operator (| + Ty.tuple + [], + M.alloc (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.call_closure (| + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + M.get_trait_method (| + "core::iter::traits::collect::IntoIterator", + Ty.apply + (Ty.path + "&") + [] + [ + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path + "argus_kernel::types::EgressKind"; + Ty.path + "alloc::alloc::Global" + ] + ], + [], + [], + "into_iter", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_record_field (| + M.deref (| + M.read (| + meta + |) + |), + "argus_kernel::background::ToolMetadata", + "egress" + |) + |) + ] + |) + |), + [ + fun + γ => + ltac:(M.monadic + (let~ + iter : + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] := + M.read (| + γ + |) in + M.read (| + M.loop (| + Ty.tuple + [], + ltac:(M.monadic + (let~ + _ : + Ty.tuple + [] := + M.match_operator (| + Ty.tuple + [], + M.alloc (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.call_closure (| + Ty.apply + (Ty.path + "core::option::Option") + [] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + ], + M.get_trait_method (| + "core::iter::traits::iterator::Iterator", + Ty.apply + (Ty.path + "alloc::collections::btree::set::Iter") + [] + [ + Ty.path + "argus_kernel::types::EgressKind" + ], + [], + [], + "next", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.borrow (| + Pointer.Kind.MutRef, + iter + |) + |) + |) + ] + |) + |), + [ + fun + γ => + ltac:(M.monadic + (let + _ := + M.is_struct_tuple (| + γ, + "core::option::Option::None" + |) in + M.never_to_any (| + M.read (| + M.break (||) + |) + |))); + fun + γ => + ltac:(M.monadic + (let + γ0_0 := + M.SubPointer.get_struct_tuple_field (| + γ, + "core::option::Option::Some", + 0 + |) in + let + γ0_0 := + M.deref (| + M.read (| + γ0_0 + |) + |) in + let + egress := + M.copy (| + Ty.path + "argus_kernel::types::EgressKind", + γ0_0 + |) in + M.match_operator (| + Ty.tuple + [], + M.alloc (| + Ty.tuple + [], + Value.Tuple + [] + |), + [ + fun + γ => + ltac:(M.monadic + (let + γ := + M.use + (M.alloc (| + Ty.path + "bool", + M.call_closure (| + Ty.path + "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path + "bool", + M.get_function (| + "argus_kernel::transitions::flow_allowed", + [], + [ + C + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + bg + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + content_gate + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + parent + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.read (| + tool_id + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + state + |) + |) + |); + M.read (| + level + |); + M.read (| + egress + |) + ] + |) + ] + |) + |)) in + let + _ := + is_constant_or_break_match (| + M.read (| + γ + |), + Value.Bool + true + |) in + M.never_to_any (| + M.read (| + M.return_ (| + Value.StructTuple + "core::result::Result::Err" + [] + [ + Ty.tuple + [ + Ty.path + "argus_kernel::state::KernelState"; + Ty.path + "argus_kernel::event::KernelAction" + ]; + Ty.path + "argus_kernel::error::KernelError" + ] + [ + Value.StructTuple + "argus_kernel::error::KernelError::PreconditionViolation" + [] + [] + [ + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "core::hint::must_use", + [], + [ + Ty.path + "alloc::string::String" + ] + |), + [ + M.read (| + let~ + res : + Ty.path + "alloc::string::String" := + M.call_closure (| + Ty.path + "alloc::string::String", + M.get_function (| + "alloc::fmt::format", + [], + [] + |), + [ + M.call_closure (| + Ty.path + "core::fmt::Arguments", + M.get_associated_function (| + Ty.path + "core::fmt::Arguments", + "new_v1", + [ + Value.Integer + IntegerKind.Usize + 3; + Value.Integer + IntegerKind.Usize + 3 + ], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "str" + ] + ], + Value.Array + [ + mk_str (| + "flow gate: child taint " + |); + mk_str (| + " conflicts with parent in-flight tool " + |); + mk_str (| + " egress " + |) + ] + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply + (Ty.path + "array") + [ + Value.Integer + IntegerKind.Usize + 3 + ] + [ + Ty.path + "core::fmt::rt::Argument" + ], + Value.Array + [ + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::ConfLevel" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + level + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.apply + (Ty.path + "&") + [] + [ + Ty.path + "argus_kernel::types::ToolId" + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + tool_id + |) + |) + |) + ] + |); + M.call_closure (| + Ty.path + "core::fmt::rt::Argument", + M.get_associated_function (| + Ty.path + "core::fmt::rt::Argument", + "new_display", + [], + [ + Ty.path + "argus_kernel::types::EgressKind" + ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + egress + |) + |) + |) + ] + |) + ] + |) + |) + |) + |) + ] + |) + ] + |) in + res + |) + ] + |) + ] + ] + |) + |) + |))); + fun + γ => + ltac:(M.monadic + (Value.Tuple + [])) + ] + |))) + ] + |) in + M.alloc (| + Ty.tuple + [], + Value.Tuple + [] + |))) + |) + |))) + ] + |) + |)) + |))); + fun γ => + ltac:(M.monadic + (Value.Tuple + [])) + ] + |))) + ] + |) in + M.alloc (| + Ty.tuple [], + Value.Tuple [] + |))) + |) + |))) + ] + |) + |)) + |))) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |))) + |) + |))) + ] + |) + |)) + |) in + let~ _ : Ty.tuple [] := + M.match_operator (| + Ty.tuple [], + M.alloc (| Ty.tuple [], Value.Tuple [] |), + [ + fun γ => + ltac:(M.monadic + (let γ := + M.use + (M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + UnOp.not, + [ + M.call_closure (| + Ty.path "bool", + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ], + "is_empty", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, child_taint |) ] + |) + ] + |) + |)) in + let _ := is_constant_or_break_match (| M.read (| γ |), Value.Bool true |) in + M.read (| + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::iter::traits::collect::Extend", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + "extend", + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "taint_levels" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, parent |) ] + |) + ] + |) + ] + |) + |) + |); + M.borrow (| Pointer.Kind.Ref, child_taint |) + ] + |) in + let~ _ : Ty.tuple [] := + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::iter::traits::collect::Extend", + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ], + [], + [ + Ty.apply + (Ty.path "&") + [] + [ Ty.path "argus_kernel::types::ConfLevel" ] + ], + "extend", + [], + [ + Ty.apply + (Ty.path "&") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ] + ] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.deref (| + M.call_closure (| + Ty.apply + (Ty.path "&mut") + [] + [ + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ] + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "or_default", + [], + [] + |), + [ + M.call_closure (| + Ty.apply + (Ty.path "alloc::collections::btree::map::entry::Entry") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + M.get_associated_function (| + Ty.apply + (Ty.path "alloc::collections::btree::map::BTreeMap") + [] + [ + Ty.path "argus_kernel::types::AgentId"; + Ty.apply + (Ty.path + "alloc::collections::btree::set::BTreeSet") + [] + [ + Ty.path "argus_kernel::types::ConfLevel"; + Ty.path "alloc::alloc::Global" + ]; + Ty.path "alloc::alloc::Global" + ], + "entry", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.MutRef, + M.SubPointer.get_struct_record_field (| + state, + "argus_kernel::state::KernelState", + "gh_taint_received" + |) + |); + M.call_closure (| + Ty.path "argus_kernel::types::AgentId", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "argus_kernel::types::AgentId", + [], + [], + "clone", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, parent |) ] + |) + ] + |) + ] + |) + |) + |); + M.borrow (| Pointer.Kind.Ref, child_taint |) + ] + |) in + M.alloc (| Ty.tuple [], Value.Tuple [] |) + |))); + fun γ => ltac:(M.monadic (Value.Tuple [])) + ] + |) in + M.alloc (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ], + Value.StructTuple + "core::result::Result::Ok" + [] + [ + Ty.tuple + [ + Ty.path "argus_kernel::state::KernelState"; + Ty.path "argus_kernel::event::KernelAction" + ]; + Ty.path "argus_kernel::error::KernelError" + ] + [ + Value.Tuple + [ + M.read (| state |); + Value.mkStructRecord + "argus_kernel::event::KernelAction::ReturnUnendorsed" + [] + [] + [ ("child", M.read (| child |)); ("parent", M.read (| parent |)) ] + ] + ] + |) + |))) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance Instance_IsFunction_return_unendorsed : + M.IsFunction.C "argus_kernel::transitions::return_unendorsed" return_unendorsed. + Admitted. + Global Typeclasses Opaque return_unendorsed. +End transitions. diff --git a/argus/formal/extracted/types.v b/argus/formal/extracted/types.v new file mode 100644 index 0000000..cd2b023 --- /dev/null +++ b/argus/formal/extracted/types.v @@ -0,0 +1,2908 @@ +(* Generated by rocq-of-rust *) +Require Import RocqOfRust.RocqOfRust. + +Module types. + (* StructTuple + { + name := "AgentId"; + const_params := []; + ty_params := []; + fields := [ Ty.path "alloc::string::String" ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + Value.StructTuple + "argus_kernel::types::AgentId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "alloc::string::String", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |) + ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_types_AgentId. + + Module Impl_core_fmt_Debug_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "AgentId" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_types_AgentId. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_AgentId. + + Module Impl_core_cmp_PartialEq_argus_kernel_types_AgentId_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + other + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::AgentId" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_types_AgentId_for_argus_kernel_types_AgentId. + + Module Impl_core_cmp_Eq_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_types_AgentId. + + Module Impl_core_cmp_PartialOrd_argus_kernel_types_AgentId_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* PartialOrd *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + other + |) in + M.call_closure (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.get_trait_method (| + "core::cmp::PartialOrd", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "partial_cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::AgentId" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_types_AgentId_for_argus_kernel_types_AgentId. + + Module Impl_core_cmp_Ord_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* Ord *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + other + |) in + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| + "core::cmp::Ord", + Ty.path "alloc::string::String", + [], + [], + "cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_types_AgentId. + + Module Impl_core_hash_Hash_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "alloc::string::String", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_types_AgentId. + + Module Impl_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* + pub fn root() -> Self { + Self("root".to_owned()) + } + *) + Definition root (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [] => + ltac:(M.monadic + (Value.StructTuple + "argus_kernel::types::AgentId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "root" |) |) |) ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_root : M.IsAssociatedFunction.C Self "root" root. + Admitted. + Global Typeclasses Opaque root. + + (* + pub fn new(name: &str) -> Self { + Self(name.to_owned()) + } + *) + Definition new (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ name ] => + ltac:(M.monadic + (let name := M.alloc (| Ty.apply (Ty.path "&") [] [ Ty.path "str" ], name |) in + Value.StructTuple + "argus_kernel::types::AgentId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| name |) |) |) ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : M.IsAssociatedFunction.C Self "new" new. + Admitted. + Global Typeclasses Opaque new. + End Impl_argus_kernel_types_AgentId. + + Module Impl_core_fmt_Display_for_argus_kernel_types_AgentId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::AgentId". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::AgentId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + M.get_trait_method (| + "core::ops::deref::Deref", + Ty.path "alloc::string::String", + [], + [], + "deref", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::AgentId", + 0 + |) + |) + |) + |) + ] + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_types_AgentId. + + (* StructTuple + { + name := "ToolId"; + const_params := []; + ty_params := []; + fields := [ Ty.path "alloc::string::String" ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + Value.StructTuple + "argus_kernel::types::ToolId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "alloc::string::String", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |) + ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_types_ToolId. + + Module Impl_core_fmt_Debug_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "ToolId" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_types_ToolId. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_ToolId. + + Module Impl_core_cmp_PartialEq_argus_kernel_types_ToolId_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + other + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::ToolId" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_types_ToolId_for_argus_kernel_types_ToolId. + + Module Impl_core_cmp_Eq_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_types_ToolId. + + Module Impl_core_cmp_PartialOrd_argus_kernel_types_ToolId_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* PartialOrd *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + other + |) in + M.call_closure (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.get_trait_method (| + "core::cmp::PartialOrd", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "partial_cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::ToolId" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_types_ToolId_for_argus_kernel_types_ToolId. + + Module Impl_core_cmp_Ord_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* Ord *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + other + |) in + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| + "core::cmp::Ord", + Ty.path "alloc::string::String", + [], + [], + "cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_types_ToolId. + + Module Impl_core_hash_Hash_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "alloc::string::String", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_types_ToolId. + + Module Impl_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* + pub fn new(name: &str) -> Self { + Self(name.to_owned()) + } + *) + Definition new (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ name ] => + ltac:(M.monadic + (let name := M.alloc (| Ty.apply (Ty.path "&") [] [ Ty.path "str" ], name |) in + Value.StructTuple + "argus_kernel::types::ToolId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| name |) |) |) ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : M.IsAssociatedFunction.C Self "new" new. + Admitted. + Global Typeclasses Opaque new. + End Impl_argus_kernel_types_ToolId. + + Module Impl_core_fmt_Display_for_argus_kernel_types_ToolId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ToolId". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ToolId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + M.get_trait_method (| + "core::ops::deref::Deref", + Ty.path "alloc::string::String", + [], + [], + "deref", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::ToolId", + 0 + |) + |) + |) + |) + ] + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_types_ToolId. + + (* StructTuple + { + name := "InvocationId"; + const_params := []; + ty_params := []; + fields := [ Ty.path "alloc::string::String" ]; + } *) + + Module Impl_core_clone_Clone_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + Value.StructTuple + "argus_kernel::types::InvocationId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "core::clone::Clone", + Ty.path "alloc::string::String", + [], + [], + "clone", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |) + ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_types_InvocationId. + + Module Impl_core_fmt_Debug_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "debug_tuple_field1_finish", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "InvocationId" |) |) |); + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ], + M.pointer_coercion + M.PointerCoercion.Unsize + (Ty.apply + (Ty.path "&") + [] + [ Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ] ]) + (Ty.apply (Ty.path "&") [] [ Ty.dyn [ ("core::fmt::Debug::Trait", []) ] ]), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "alloc::string::String" ], + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |) + |) + |) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_types_InvocationId. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_InvocationId. + + Module Impl_core_cmp_PartialEq_argus_kernel_types_InvocationId_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + other + |) in + M.call_closure (| + Ty.path "bool", + M.get_trait_method (| + "core::cmp::PartialEq", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "eq", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::InvocationId" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_types_InvocationId_for_argus_kernel_types_InvocationId. + + Module Impl_core_cmp_Eq_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + M.match_operator (| + Ty.tuple [], + Value.DeclaredButUndefined, + [ fun γ => ltac:(M.monadic (Value.Tuple [])) ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_types_InvocationId. + + Module Impl_core_cmp_PartialOrd_argus_kernel_types_InvocationId_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* PartialOrd *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + other + |) in + M.call_closure (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.get_trait_method (| + "core::cmp::PartialOrd", + Ty.path "alloc::string::String", + [], + [ Ty.path "alloc::string::String" ], + "partial_cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::InvocationId" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_types_InvocationId_for_argus_kernel_types_InvocationId. + + Module Impl_core_cmp_Ord_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* Ord *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + other + |) in + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| + "core::cmp::Ord", + Ty.path "alloc::string::String", + [], + [], + "cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| other |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_types_InvocationId. + + Module Impl_core_hash_Hash_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "alloc::string::String", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_types_InvocationId. + + Module Impl_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* + pub fn new(id: &str) -> Self { + Self(id.to_owned()) + } + *) + Definition new (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ id ] => + ltac:(M.monadic + (let id := M.alloc (| Ty.apply (Ty.path "&") [] [ Ty.path "str" ], id |) in + Value.StructTuple + "argus_kernel::types::InvocationId" + [] + [] + [ + M.call_closure (| + Ty.path "alloc::string::String", + M.get_trait_method (| + "alloc::borrow::ToOwned", + Ty.path "str", + [], + [], + "to_owned", + [], + [] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| id |) |) |) ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_new : M.IsAssociatedFunction.C Self "new" new. + Admitted. + Global Typeclasses Opaque new. + End Impl_argus_kernel_types_InvocationId. + + Module Impl_core_fmt_Display_for_argus_kernel_types_InvocationId. + Definition Self : Ty.t := Ty.path "argus_kernel::types::InvocationId". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::InvocationId" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.call_closure (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + M.get_trait_method (| + "core::ops::deref::Deref", + Ty.path "alloc::string::String", + [], + [], + "deref", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.SubPointer.get_struct_tuple_field (| + M.deref (| M.read (| self |) |), + "argus_kernel::types::InvocationId", + 0 + |) + |) + |) + |) + ] + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_types_InvocationId. + + (* + Enum ConfLevel + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "Public"; + item := StructTuple []; + }; + { + name := "Internal"; + item := StructTuple []; + }; + { + name := "Sensitive"; + item := StructTuple []; + }; + { + name := "Restricted"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_ConfLevel_Public : + M.IsDiscriminant "argus_kernel::types::ConfLevel::Public" 0. + Axiom IsDiscriminant_ConfLevel_Internal : + M.IsDiscriminant "argus_kernel::types::ConfLevel::Internal" 1. + Axiom IsDiscriminant_ConfLevel_Sensitive : + M.IsDiscriminant "argus_kernel::types::ConfLevel::Sensitive" 2. + Axiom IsDiscriminant_ConfLevel_Restricted : + M.IsDiscriminant "argus_kernel::types::ConfLevel::Restricted" 3. + + Module Impl_core_clone_Clone_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + M.read (| M.deref (| M.read (| self |) |) |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_types_ConfLevel. + + Module Impl_core_marker_Copy_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + Axiom Implements : + M.IsTraitInstance + "core::marker::Copy" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_Copy_for_argus_kernel_types_ConfLevel. + + Module Impl_core_fmt_Debug_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Public" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Public" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Internal" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Internal" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Sensitive" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Sensitive" |) |) |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Restricted" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Restricted" |) |) |))) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_types_ConfLevel. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_ConfLevel. + + Module Impl_core_cmp_PartialEq_argus_kernel_types_ConfLevel_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::ConfLevel" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::ConfLevel" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::ConfLevel" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_types_ConfLevel_for_argus_kernel_types_ConfLevel. + + Module Impl_core_cmp_Eq_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + Value.Tuple [])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_types_ConfLevel. + + Module Impl_core_hash_Hash_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::ConfLevel" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + M.alloc (| + Ty.tuple [], + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "isize", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_types_ConfLevel. + + Module Impl_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* + fn rank(self) -> u8 { + match self { + Self::Public => 0, + Self::Internal => 1, + Self::Sensitive => 2, + Self::Restricted => 3, + } + } + *) + Definition rank (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := M.alloc (| Ty.path "argus_kernel::types::ConfLevel", self |) in + M.match_operator (| + Ty.path "u8", + self, + [ + fun γ => + ltac:(M.monadic + (let _ := M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Public" |) in + Value.Integer IntegerKind.U8 0)); + fun γ => + ltac:(M.monadic + (let _ := M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Internal" |) in + Value.Integer IntegerKind.U8 1)); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Sensitive" |) in + Value.Integer IntegerKind.U8 2)); + fun γ => + ltac:(M.monadic + (let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Restricted" |) in + Value.Integer IntegerKind.U8 3)) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Global Instance AssociatedFunction_rank : M.IsAssociatedFunction.C Self "rank" rank. + Admitted. + Global Typeclasses Opaque rank. + End Impl_argus_kernel_types_ConfLevel. + + Module Impl_core_cmp_Ord_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.rank().cmp(&other.rank()) + } + *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + other + |) in + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| "core::cmp::Ord", Ty.path "u8", [], [], "cmp", [], [] |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "u8", + M.call_closure (| + Ty.path "u8", + M.get_associated_function (| + Ty.path "argus_kernel::types::ConfLevel", + "rank", + [], + [] + |), + [ M.read (| M.deref (| M.read (| self |) |) |) ] + |) + |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| + M.borrow (| + Pointer.Kind.Ref, + M.alloc (| + Ty.path "u8", + M.call_closure (| + Ty.path "u8", + M.get_associated_function (| + Ty.path "argus_kernel::types::ConfLevel", + "rank", + [], + [] + |), + [ M.read (| M.deref (| M.read (| other |) |) |) ] + |) + |) + |) + |) + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_types_ConfLevel. + + Module Impl_core_cmp_PartialOrd_argus_kernel_types_ConfLevel_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + other + |) in + Value.StructTuple + "core::option::Option::Some" + [] + [ Ty.path "core::cmp::Ordering" ] + [ + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| + "core::cmp::Ord", + Ty.path "argus_kernel::types::ConfLevel", + [], + [], + "cmp", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) + ] + |) + ])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::ConfLevel" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_types_ConfLevel_for_argus_kernel_types_ConfLevel. + + Module Impl_core_fmt_Display_for_argus_kernel_types_ConfLevel. + Definition Self : Ty.t := Ty.path "argus_kernel::types::ConfLevel". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Public => f.write_str("public"), + Self::Internal => f.write_str("internal"), + Self::Sensitive => f.write_str("sensitive"), + Self::Restricted => f.write_str("restricted"), + } + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::ConfLevel" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Public" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "public" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Internal" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "internal" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Sensitive" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "sensitive" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::ConfLevel::Restricted" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "restricted" |) |) |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_types_ConfLevel. + + (* + Enum EgressKind + { + const_params := []; + ty_params := []; + variants := + [ + { + name := "NetworkExternal"; + item := StructTuple []; + }; + { + name := "NetworkInternal"; + item := StructTuple []; + }; + { + name := "FilesystemWrite"; + item := StructTuple []; + }; + { + name := "Ipc"; + item := StructTuple []; + } + ]; + } + *) + + Axiom IsDiscriminant_EgressKind_NetworkExternal : + M.IsDiscriminant "argus_kernel::types::EgressKind::NetworkExternal" 0. + Axiom IsDiscriminant_EgressKind_NetworkInternal : + M.IsDiscriminant "argus_kernel::types::EgressKind::NetworkInternal" 1. + Axiom IsDiscriminant_EgressKind_FilesystemWrite : + M.IsDiscriminant "argus_kernel::types::EgressKind::FilesystemWrite" 2. + Axiom IsDiscriminant_EgressKind_Ipc : M.IsDiscriminant "argus_kernel::types::EgressKind::Ipc" 3. + + Module Impl_core_clone_Clone_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* Clone *) + Definition clone (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + M.read (| M.deref (| M.read (| self |) |) |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::clone::Clone" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("clone", InstanceField.Method clone) ]. + End Impl_core_clone_Clone_for_argus_kernel_types_EgressKind. + + Module Impl_core_marker_Copy_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + Axiom Implements : + M.IsTraitInstance + "core::marker::Copy" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_Copy_for_argus_kernel_types_EgressKind. + + Module Impl_core_fmt_Debug_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* Debug *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| Ty.path "core::fmt::Formatter", "write_str", [], [] |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.match_operator (| + Ty.apply (Ty.path "&") [] [ Ty.path "str" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::types::EgressKind::NetworkExternal" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "NetworkExternal" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::types::EgressKind::NetworkInternal" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "NetworkInternal" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| + γ, + "argus_kernel::types::EgressKind::FilesystemWrite" + |) in + M.borrow (| + Pointer.Kind.Ref, + M.deref (| mk_str (| "FilesystemWrite" |) |) + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::types::EgressKind::Ipc" |) in + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "Ipc" |) |) |))) + ] + |) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Debug" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Debug_for_argus_kernel_types_EgressKind. + + Module Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + Axiom Implements : + M.IsTraitInstance + "core::marker::StructuralPartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) []. + End Impl_core_marker_StructuralPartialEq_for_argus_kernel_types_EgressKind. + + Module Impl_core_cmp_PartialEq_argus_kernel_types_EgressKind_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* PartialEq *) + Definition eq (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "bool", + M.call_closure (| + Ty.path "bool", + BinOp.eq, + [ M.read (| __self_discr |); M.read (| __arg1_discr |) ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialEq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::EgressKind" ] + Self + (* Instance *) [ ("eq", InstanceField.Method eq) ]. + End Impl_core_cmp_PartialEq_argus_kernel_types_EgressKind_for_argus_kernel_types_EgressKind. + + Module Impl_core_cmp_Eq_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* Eq *) + Definition assert_receiver_is_total_eq + (ε : list Value.t) + (τ : list Ty.t) + (α : list Value.t) + : M := + match ε, τ, α with + | [], [], [ self ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + Value.Tuple [])) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Eq" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) + [ ("assert_receiver_is_total_eq", InstanceField.Method assert_receiver_is_total_eq) ]. + End Impl_core_cmp_Eq_for_argus_kernel_types_EgressKind. + + Module Impl_core_cmp_PartialOrd_argus_kernel_types_EgressKind_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* PartialOrd *) + Definition partial_cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.call_closure (| + Ty.apply (Ty.path "core::option::Option") [] [ Ty.path "core::cmp::Ordering" ], + M.get_trait_method (| + "core::cmp::PartialOrd", + Ty.path "isize", + [], + [ Ty.path "isize" ], + "partial_cmp", + [], + [] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __arg1_discr |) |) + |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::PartialOrd" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [ Ty.path "argus_kernel::types::EgressKind" ] + Self + (* Instance *) [ ("partial_cmp", InstanceField.Method partial_cmp) ]. + End Impl_core_cmp_PartialOrd_argus_kernel_types_EgressKind_for_argus_kernel_types_EgressKind. + + Module Impl_core_cmp_Ord_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* Ord *) + Definition cmp (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; other ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let other := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + other + |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + let~ __arg1_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| other |) |) |) ] + |) in + M.alloc (| + Ty.path "core::cmp::Ordering", + M.call_closure (| + Ty.path "core::cmp::Ordering", + M.get_trait_method (| "core::cmp::Ord", Ty.path "isize", [], [], "cmp", [], [] |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __arg1_discr |) |) + |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::cmp::Ord" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("cmp", InstanceField.Method cmp) ]. + End Impl_core_cmp_Ord_for_argus_kernel_types_EgressKind. + + Module Impl_core_hash_Hash_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* Hash *) + Definition hash (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [ __H ], [ self; state ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let state := M.alloc (| Ty.apply (Ty.path "&mut") [] [ __H ], state |) in + M.read (| + let~ __self_discr : Ty.path "isize" := + M.call_closure (| + Ty.path "isize", + M.get_function (| + "core::intrinsics::discriminant_value", + [], + [ Ty.path "argus_kernel::types::EgressKind" ] + |), + [ M.borrow (| Pointer.Kind.Ref, M.deref (| M.read (| self |) |) |) ] + |) in + M.alloc (| + Ty.tuple [], + M.call_closure (| + Ty.tuple [], + M.get_trait_method (| + "core::hash::Hash", + Ty.path "isize", + [], + [], + "hash", + [], + [ __H ] + |), + [ + M.borrow (| + Pointer.Kind.Ref, + M.deref (| M.borrow (| Pointer.Kind.Ref, __self_discr |) |) + |); + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| state |) |) |) + ] + |) + |) + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::hash::Hash" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("hash", InstanceField.Method hash) ]. + End Impl_core_hash_Hash_for_argus_kernel_types_EgressKind. + + Module Impl_core_fmt_Display_for_argus_kernel_types_EgressKind. + Definition Self : Ty.t := Ty.path "argus_kernel::types::EgressKind". + + (* + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NetworkExternal => f.write_str("network_external"), + Self::NetworkInternal => f.write_str("network_internal"), + Self::FilesystemWrite => f.write_str("filesystem_write"), + Self::Ipc => f.write_str("ipc"), + } + } + *) + Definition fmt (ε : list Value.t) (τ : list Ty.t) (α : list Value.t) : M := + match ε, τ, α with + | [], [], [ self; f ] => + ltac:(M.monadic + (let self := + M.alloc (| + Ty.apply (Ty.path "&") [] [ Ty.path "argus_kernel::types::EgressKind" ], + self + |) in + let f := + M.alloc (| Ty.apply (Ty.path "&mut") [] [ Ty.path "core::fmt::Formatter" ], f |) in + M.match_operator (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + self, + [ + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::EgressKind::NetworkExternal" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "network_external" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::EgressKind::NetworkInternal" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "network_internal" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := + M.is_struct_tuple (| γ, "argus_kernel::types::EgressKind::FilesystemWrite" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "filesystem_write" |) |) |) + ] + |))); + fun γ => + ltac:(M.monadic + (let γ := M.deref (| M.read (| γ |) |) in + let _ := M.is_struct_tuple (| γ, "argus_kernel::types::EgressKind::Ipc" |) in + M.call_closure (| + Ty.apply + (Ty.path "core::result::Result") + [] + [ Ty.tuple []; Ty.path "core::fmt::Error" ], + M.get_associated_function (| + Ty.path "core::fmt::Formatter", + "write_str", + [], + [] + |), + [ + M.borrow (| Pointer.Kind.MutRef, M.deref (| M.read (| f |) |) |); + M.borrow (| Pointer.Kind.Ref, M.deref (| mk_str (| "ipc" |) |) |) + ] + |))) + ] + |))) + | _, _, _ => M.impossible "wrong number of arguments" + end. + + Axiom Implements : + M.IsTraitInstance + "core::fmt::Display" + (* Trait polymorphic consts *) [] + (* Trait polymorphic types *) [] + Self + (* Instance *) [ ("fmt", InstanceField.Method fmt) ]. + End Impl_core_fmt_Display_for_argus_kernel_types_EgressKind. +End types. diff --git a/argus/formal/refinement/ConcreteSpec.v b/argus/formal/refinement/ConcreteSpec.v new file mode 100644 index 0000000..110ee5f --- /dev/null +++ b/argus/formal/refinement/ConcreteSpec.v @@ -0,0 +1,369 @@ +(** Typed Concrete Specification for TzimtzumV2.3 + Uses BTreeAxioms (axiomatized BTreeMap/BTreeSet) to model the Rust + kernel's state. This bridges the gap between the abstract function-based + spec (TzimtzumV2.v) and the actual Rust implementation without requiring + rocq-of-rust extraction link files. + + ANY BTreeMap/BTreeSet-based implementation whose transitions match these + definitions correctly implements the abstract specification. *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.BTreeAxioms. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. + +(** ================================================================ *) +(** Concrete State (mirrors Rust KernelState) *) +(** ================================================================ *) + +Record ConcreteState := mkCState { + c_agent_active : BTreeSet.t AgentId; + c_agent_parent : BTreeMap.t AgentId AgentId; + c_agent_cap : BTreeMap.t AgentId (BTreeSet.t CapKind); + c_taint_levels : BTreeMap.t AgentId (BTreeSet.t ConfLevel); + c_in_flight : BTreeMap.t AgentId (BTreeSet.t InvocationId); + c_invocation_tool : BTreeMap.t InvocationId ToolId; + c_tool_registered : BTreeSet.t ToolId; + c_gh_taint_invoked : BTreeMap.t AgentId (BTreeSet.t ConfLevel); + c_gh_taint_received : BTreeMap.t AgentId (BTreeSet.t ConfLevel) +}. + +(** CapKind is an uninterpreted sort, so we axiomatize a set of all + capabilities for root's initial state. *) +Parameter all_caps_set : BTreeSet.t CapKind. +Axiom all_caps_mem : forall c, BTreeSet.mem c all_caps_set = true. + +(** ================================================================ *) +(** Initial Concrete State *) +(** ================================================================ *) + +Definition c_initial_state : ConcreteState := mkCState + (BTreeSet.insert root_agent BTreeSet.empty) + BTreeMap.empty + (BTreeMap.insert root_agent all_caps_set BTreeMap.empty) + BTreeMap.empty + BTreeMap.empty + BTreeMap.empty + BTreeSet.empty + BTreeMap.empty + BTreeMap.empty. + +(** ================================================================ *) +(** Shorthand notations for nested operations *) +(** ================================================================ *) + +Definition mem_agent_cap := + BTreeMap.mem_nested AgentId CapKind. +Definition mem_taint := + BTreeMap.mem_nested AgentId ConfLevel. +Definition mem_in_flight := + BTreeMap.mem_nested AgentId InvocationId. + +Definition insert_cap := + BTreeMap.insert_nested AgentId CapKind. +Definition insert_taint := + BTreeMap.insert_nested AgentId ConfLevel. +Definition insert_in_flight := + BTreeMap.insert_nested AgentId InvocationId. + +Definition union_taint := + BTreeMap.union_nested AgentId ConfLevel. + +Definition remove_cap := + BTreeMap.remove_key_nested AgentId CapKind. +Definition remove_taint := + BTreeMap.remove_key_nested AgentId ConfLevel. +Definition remove_in_flight := + BTreeMap.remove_key_nested AgentId InvocationId. +Definition remove_gh_invoked := + BTreeMap.remove_key_nested AgentId ConfLevel. +Definition remove_gh_received := + BTreeMap.remove_key_nested AgentId ConfLevel. + +(** ================================================================ *) +(** Concrete Transitions *) +(** ================================================================ *) + +(** register_tool: flip tool_registered flag *) +Definition c_register_tool (st : ConcreteState) (tool : ToolId) + : option ConcreteState := + if BTreeSet.mem tool (c_tool_registered st) then None + else Some (mkCState + (c_agent_active st) + (c_agent_parent st) + (c_agent_cap st) + (c_taint_levels st) + (c_in_flight st) + (c_invocation_tool st) + (BTreeSet.insert tool (c_tool_registered st)) + (c_gh_taint_invoked st) + (c_gh_taint_received st)). + +(** delegate: grantor creates child grantee with empty state *) +Definition c_delegate (st : ConcreteState) + (grantor grantee : AgentId) : option ConcreteState := + if negb (BTreeSet.mem grantor (c_agent_active st)) then None + else if BTreeSet.mem grantee (c_agent_active st) then None + else if AgentId_eq_dec grantee root_agent then None + else Some (mkCState + (BTreeSet.insert grantee (c_agent_active st)) + (BTreeMap.insert grantee grantor + (BTreeMap.remove_by_value grantee (BTreeMap.remove grantee (c_agent_parent st)))) + (BTreeMap.remove grantee (c_agent_cap st)) + (BTreeMap.remove grantee (c_taint_levels st)) + (BTreeMap.remove grantee (c_in_flight st)) + (c_invocation_tool st) + (c_tool_registered st) + (BTreeMap.remove grantee (c_gh_taint_invoked st)) + (BTreeMap.remove grantee (c_gh_taint_received st))). + +(** grant_capability: parent grants a single cap to direct child *) +Definition c_grant_capability (st : ConcreteState) + (prnt child : AgentId) (cap : CapKind) : option ConcreteState := + if negb (BTreeSet.mem prnt (c_agent_active st)) then None + else if negb (BTreeSet.mem child (c_agent_active st)) then None + else if negb (match BTreeMap.get child (c_agent_parent st) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end) then None + else if negb (mem_agent_cap prnt cap (c_agent_cap st)) then None + else Some (mkCState + (c_agent_active st) + (c_agent_parent st) + (insert_cap child cap (c_agent_cap st)) + (c_taint_levels st) + (c_in_flight st) + (c_invocation_tool st) + (c_tool_registered st) + (c_gh_taint_invoked st) + (c_gh_taint_received st)). + +(** revoke: parent removes a direct child *) +Definition c_revoke (st : ConcreteState) + (prnt target : AgentId) : option ConcreteState := + if negb (match BTreeMap.get target (c_agent_parent st) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end) then None + else if negb (BTreeSet.mem prnt (c_agent_active st)) then None + else if negb (BTreeSet.mem target (c_agent_active st)) then None + else if AgentId_eq_dec target root_agent then None + else Some (mkCState + (BTreeSet.remove target (c_agent_active st)) + (BTreeMap.remove target (c_agent_parent st)) + (BTreeMap.remove target (c_agent_cap st)) + (BTreeMap.remove target (c_taint_levels st)) + (BTreeMap.remove target (c_in_flight st)) + (c_invocation_tool st) + (c_tool_registered st) + (BTreeMap.remove target (c_gh_taint_invoked st)) + (BTreeMap.remove target (c_gh_taint_received st))). + +(** cascade_revoke: orphan cleanup -- child's parent is inactive *) +Definition c_cascade_revoke (st : ConcreteState) + (child prnt : AgentId) : option ConcreteState := + if negb (match BTreeMap.get child (c_agent_parent st) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end) then None + else if BTreeSet.mem prnt (c_agent_active st) then None + else if negb (BTreeSet.mem child (c_agent_active st)) then None + else if AgentId_eq_dec child root_agent then None + else Some (mkCState + (BTreeSet.remove child (c_agent_active st)) + (BTreeMap.remove child (c_agent_parent st)) + (BTreeMap.remove child (c_agent_cap st)) + (BTreeMap.remove child (c_taint_levels st)) + (BTreeMap.remove child (c_in_flight st)) + (c_invocation_tool st) + (c_tool_registered st) + (BTreeMap.remove child (c_gh_taint_invoked st)) + (BTreeMap.remove child (c_gh_taint_received st))). + +(** invoke_start: precondition (Prop) + state transform. + The key difference from abstract: invocation_tool is MUTABLE here. *) +Definition c_invoke_start_pre (st : ConcreteState) + (a : AgentId) (tool : ToolId) (inv : InvocationId) : Prop := + BTreeSet.mem a (c_agent_active st) = true + /\ a <> root_agent + /\ BTreeSet.mem tool (c_tool_registered st) = true + /\ BTreeMap.get inv (c_invocation_tool st) = None + /\ (forall ag, mem_in_flight ag inv (c_in_flight st) = false) + /\ (forall c, tool_cap tool c = true -> + mem_agent_cap a c (c_agent_cap st) = true) + /\ (forall l e, + (mem_taint a l (c_taint_levels st) = true + \/ (exists I, mem_in_flight a I (c_in_flight st) = true + /\ tool_conf_floor (invocation_tool I) = l + /\ tool_endorsed (invocation_tool I) = false)) -> + tool_egress tool e = true -> + flow_allowed a tool l e = true) + /\ (forall i e, + mem_in_flight a i (c_in_flight st) = true -> + tool_egress (invocation_tool i) e = true -> + tool_endorsed tool = false -> + flow_allowed a (invocation_tool i) (tool_conf_floor tool) e = true) + /\ (forall e, + tool_endorsed tool = false -> + tool_egress tool e = true -> + flow_allowed a tool (tool_conf_floor tool) e = true) + /\ authorizer_allows a tool = true. + +Definition c_invoke_start_state (st : ConcreteState) + (a : AgentId) (tool : ToolId) (inv : InvocationId) : ConcreteState := + mkCState + (c_agent_active st) + (c_agent_parent st) + (c_agent_cap st) + (c_taint_levels st) + (insert_in_flight a inv (c_in_flight st)) + (BTreeMap.insert inv tool (c_invocation_tool st)) + (c_tool_registered st) + (c_gh_taint_invoked st) + (c_gh_taint_received st). + +(** invoke_complete: tool returns, invocation leaves in-flight set *) +Definition c_invoke_complete (st : ConcreteState) + (a : AgentId) (inv : InvocationId) : option ConcreteState := + if negb (mem_in_flight a inv (c_in_flight st)) then None + else if negb (BTreeSet.mem a (c_agent_active st)) then None + else + match BTreeMap.get inv (c_invocation_tool st) with + | None => None + | Some tool => + let add_taint := negb (tool_endorsed tool) in + let floor := tool_conf_floor tool in + let new_taint := + if add_taint then insert_taint a floor (c_taint_levels st) + else c_taint_levels st in + let new_in_flight := + match BTreeMap.get a (c_in_flight st) with + | Some s => BTreeMap.insert a (BTreeSet.remove inv s) (c_in_flight st) + | None => c_in_flight st + end in + let new_gh := + if add_taint then insert_taint a floor (c_gh_taint_invoked st) + else c_gh_taint_invoked st in + Some (mkCState + (c_agent_active st) + (c_agent_parent st) + (c_agent_cap st) + new_taint + new_in_flight + (c_invocation_tool st) + (c_tool_registered st) + new_gh + (c_gh_taint_received st)) + end. + +(** return_endorsed: pure guard, no state change *) +Definition c_return_endorsed_pre (st : ConcreteState) + (child prnt : AgentId) : Prop := + match BTreeMap.get child (c_agent_parent st) with + | Some p => p = prnt + | None => False + end + /\ BTreeSet.mem child (c_agent_active st) = true + /\ BTreeSet.mem prnt (c_agent_active st) = true + /\ (forall i, mem_in_flight child i (c_in_flight st) = false). + +(** return_unendorsed: child taint propagates to parent *) +Definition c_return_unendorsed_pre (st : ConcreteState) + (child prnt : AgentId) : Prop := + match BTreeMap.get child (c_agent_parent st) with + | Some p => p = prnt + | None => False + end + /\ BTreeSet.mem child (c_agent_active st) = true + /\ BTreeSet.mem prnt (c_agent_active st) = true + /\ (forall i, mem_in_flight child i (c_in_flight st) = false) + /\ (forall l i e, + mem_taint child l (c_taint_levels st) = true -> + mem_in_flight prnt i (c_in_flight st) = true -> + tool_egress (invocation_tool i) e = true -> + flow_allowed prnt (invocation_tool i) l e = true). + +Definition c_return_unendorsed_state (st : ConcreteState) + (child prnt : AgentId) : ConcreteState := + let child_taint := + match BTreeMap.get child (c_taint_levels st) with + | Some s => s + | None => BTreeSet.empty + end in + mkCState + (c_agent_active st) + (c_agent_parent st) + (c_agent_cap st) + (union_taint prnt child_taint (c_taint_levels st)) + (c_in_flight st) + (c_invocation_tool st) + (c_tool_registered st) + (c_gh_taint_invoked st) + (union_taint prnt child_taint (c_gh_taint_received st)). + +(** sentinel_elevate_taint: sentinel adds taint at a given level *) +Definition c_sentinel_elevate_taint_pre (st : ConcreteState) + (a : AgentId) (l : ConfLevel) : Prop := + BTreeSet.mem a (c_agent_active st) = true + /\ (forall i e, + mem_in_flight a i (c_in_flight st) = true -> + tool_egress (invocation_tool i) e = true -> + flow_allowed a (invocation_tool i) l e = true). + +Definition c_sentinel_elevate_taint_state (st : ConcreteState) + (a : AgentId) (l : ConfLevel) : ConcreteState := + mkCState + (c_agent_active st) + (c_agent_parent st) + (c_agent_cap st) + (insert_taint a l (c_taint_levels st)) + (c_in_flight st) + (c_invocation_tool st) + (c_tool_registered st) + (insert_taint a l (c_gh_taint_invoked st)) + (c_gh_taint_received st). + +(** ================================================================ *) +(** Step Relation and Reachability *) +(** ================================================================ *) + +Inductive c_step : ConcreteState -> ConcreteState -> Prop := + | c_step_register_tool : forall st st' tool, + c_register_tool st tool = Some st' -> c_step st st' + | c_step_delegate : forall st st' grantor grantee, + c_delegate st grantor grantee = Some st' -> c_step st st' + | c_step_grant_capability : forall st st' prnt child cap, + c_grant_capability st prnt child cap = Some st' -> c_step st st' + | c_step_revoke : forall st st' prnt target, + c_revoke st prnt target = Some st' -> c_step st st' + | c_step_cascade_revoke : forall st st' child prnt, + c_cascade_revoke st child prnt = Some st' -> c_step st st' + | c_step_invoke_start : forall st a tool inv, + c_invoke_start_pre st a tool inv -> + c_step st (c_invoke_start_state st a tool inv) + | c_step_invoke_complete : forall st st' a inv, + c_invoke_complete st a inv = Some st' -> c_step st st' + | c_step_return_endorsed : forall st child prnt, + c_return_endorsed_pre st child prnt -> + c_step st st + | c_step_return_unendorsed : forall st child prnt, + c_return_unendorsed_pre st child prnt -> + c_step st (c_return_unendorsed_state st child prnt) + | c_step_sentinel_elevate_taint : forall st a l, + c_sentinel_elevate_taint_pre st a l -> + c_step st (c_sentinel_elevate_taint_state st a l). + +Inductive c_reachable : ConcreteState -> Prop := + | c_reach_init : c_reachable c_initial_state + | c_reach_step : forall st st', + c_reachable st -> c_step st st' -> c_reachable st'. + +(** ================================================================ *) +(** Concrete Invariant: invocation_tool completeness *) +(** ================================================================ *) + +Definition c_invocation_tool_complete (st : ConcreteState) : Prop := + forall a inv, + mem_in_flight a inv (c_in_flight st) = true -> + exists tool, BTreeMap.get inv (c_invocation_tool st) = Some tool. diff --git a/argus/formal/refinement/Simulation.v b/argus/formal/refinement/Simulation.v new file mode 100644 index 0000000..c5f4082 --- /dev/null +++ b/argus/formal/refinement/Simulation.v @@ -0,0 +1,475 @@ +(** Per-Transition Simulation Proofs + For each concrete transition: if concrete and abstract states agree + (via state_refines) and the concrete transition succeeds, then the + corresponding abstract transition also succeeds and the resulting + states agree. + + Easy proofs: register_tool, grant_capability, revoke, + cascade_revoke, return_endorsed + Admitted: delegate, invoke_start, invoke_complete, + return_unendorsed *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.BTreeAxioms. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. +Require Import ArgusKernel.spec.TzimtzumV2_safety. +Require Import ArgusKernel.refinement.ConcreteSpec. +Require Import ArgusKernel.refinement.StateRelation. + +(** ================================================================ *) +(** Auxiliary *) +(** ================================================================ *) + +Ltac destruct_refines H := + destruct H as [Hactive [Hparent [Hcap [Htaint [Hflight + [Hinvtool [Htoolreg [Hghinv Hghrecv]]]]]]]]. + +(** Split state_refines into exactly 9 subgoals without splitting the iff. *) +Ltac split_refines := + unfold state_refines; + split; [| split; [| split; [| split; [| split; [| + split; [| split; [| split]]]]]]]. + + +(** ================================================================ *) +(** register_tool simulation *) +(** ================================================================ *) + +Lemma register_tool_simulation : + forall cs as_ tool cs', + state_refines cs as_ -> + c_register_tool cs tool = Some cs' -> + exists as'', + register_tool as_ tool = Some as'' /\ + state_refines cs' as''. +Proof. + intros cs as_ tool cs' Href Hconcrete. + destruct_refines Href. + unfold c_register_tool in Hconcrete. + destruct (BTreeSet.mem tool (c_tool_registered cs)) eqn:Hmem; [discriminate|]. + inversion Hconcrete; subst; clear Hconcrete. + rewrite Htoolreg in Hmem. + unfold register_tool. rewrite Hmem. + eexists. split; [reflexivity|]. + split_refines. + - intros a0. simpl. apply Hactive. + - intros c0 p0. simpl. exact (Hparent c0 p0). + - intros a0 c. simpl. apply Hcap. + - intros a0 l. simpl. apply Htaint. + - intros a0 i. simpl. apply Hflight. + - intros inv0 t0. simpl. apply Hinvtool. + - intros t0. simpl. + rewrite (BTreeSet.mem_insert ToolId ToolId_eq_dec). + destruct (ToolId_eq_dec t0 tool) as [->|Hneq]. + + rewrite beq_tool_refl. reflexivity. + + unfold beq_tool. destruct (ToolId_eq_dec t0 tool); [contradiction|]. + apply Htoolreg. + - intros a0 l. simpl. apply Hghinv. + - intros a0 l. simpl. apply Hghrecv. +Qed. + +(** ================================================================ *) +(** grant_capability simulation *) +(** ================================================================ *) + +Lemma grant_capability_simulation : + forall cs as_ prnt child cap cs', + state_refines cs as_ -> + c_grant_capability cs prnt child cap = Some cs' -> + exists as'', + grant_capability as_ prnt child cap = Some as'' /\ + state_refines cs' as''. +Proof. + intros cs as_ prnt child cap cs' Href Hconcrete. + destruct_refines Href. + unfold c_grant_capability in Hconcrete. + destruct (negb (BTreeSet.mem prnt (c_agent_active cs))) eqn:Hprnt_act; + [discriminate|]. + apply negb_false_iff in Hprnt_act. + destruct (negb (BTreeSet.mem child (c_agent_active cs))) eqn:Hchild_act; + [discriminate|]. + apply negb_false_iff in Hchild_act. + destruct (negb (match BTreeMap.get child (c_agent_parent cs) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end)) eqn:Hpar; [discriminate|]. + apply negb_false_iff in Hpar. + destruct (BTreeMap.get child (c_agent_parent cs)) eqn:Hget_par; [|discriminate]. + destruct (AgentId_eq_dec a prnt) as [->|]; [|discriminate]. + destruct (negb (mem_agent_cap prnt cap (c_agent_cap cs))) eqn:Hcap_check; + [discriminate|]. + apply negb_false_iff in Hcap_check. + inversion Hconcrete; subst; clear Hconcrete. + rewrite Hactive in Hprnt_act. + rewrite Hactive in Hchild_act. + assert (Hpar_abs : agent_parent as_ child prnt = true) + by (exact (proj1 (Hparent _ _) Hget_par)). + rewrite Hcap in Hcap_check. + unfold grant_capability. + rewrite Hprnt_act. simpl. + rewrite Hchild_act. simpl. + rewrite Hpar_abs. simpl. + rewrite Hcap_check. simpl. + eexists. split; [reflexivity|]. + split_refines. + - intros a0. simpl. apply Hactive. + - intros c0 p0. simpl. exact (Hparent c0 p0). + - intros a0 c. simpl. + unfold mem_agent_cap, BTreeMap.mem_nested, insert_cap, BTreeMap.insert_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq_a]. + + destruct (BTreeMap.get child (c_agent_cap cs)) eqn:Hget_cap. + * rewrite BTreeMap.get_insert_same. + rewrite (BTreeSet.mem_insert CapKind CapKind_eq_dec). + destruct (CapKind_eq_dec c cap) as [->|Hneq_c]. + -- rewrite beq_agent_refl. rewrite beq_cap_refl. reflexivity. + -- unfold beq_agent. destruct (AgentId_eq_dec child child); [|contradiction]. + unfold beq_cap. destruct (CapKind_eq_dec c cap); [contradiction|]. + pose proof (Hcap child c) as Hspec. + unfold mem_agent_cap, BTreeMap.mem_nested in Hspec. + rewrite Hget_cap in Hspec. exact Hspec. + * rewrite BTreeMap.get_insert_same. + rewrite (BTreeSet.mem_insert CapKind CapKind_eq_dec). + destruct (CapKind_eq_dec c cap) as [->|Hneq_c]. + -- rewrite beq_agent_refl. rewrite beq_cap_refl. reflexivity. + -- rewrite BTreeSet.mem_empty. + unfold beq_agent. destruct (AgentId_eq_dec child child); [|contradiction]. + unfold beq_cap. destruct (CapKind_eq_dec c cap); [contradiction|]. + pose proof (Hcap child c) as Hspec. + unfold mem_agent_cap, BTreeMap.mem_nested in Hspec. + rewrite Hget_cap in Hspec. exact Hspec. + + destruct (BTreeMap.get child (c_agent_cap cs)) eqn:Hget_cap. + * rewrite BTreeMap.get_insert_other; [| exact Hneq_a]. + unfold beq_agent. destruct (AgentId_eq_dec a0 child); [contradiction|]. + apply Hcap. + * rewrite BTreeMap.get_insert_other; [| exact Hneq_a]. + unfold beq_agent. destruct (AgentId_eq_dec a0 child); [contradiction|]. + apply Hcap. + - intros a0 l. simpl. apply Htaint. + - intros a0 i. simpl. apply Hflight. + - intros inv0 t0. simpl. apply Hinvtool. + - intros t0. simpl. apply Htoolreg. + - intros a0 l. simpl. apply Hghinv. + - intros a0 l. simpl. apply Hghrecv. +Qed. + +(** ================================================================ *) +(** Helper tactic for revoke-style proofs *) +(** ================================================================ *) + +Ltac revoke_field_case target Hfield := + destruct (AgentId_eq_dec _ target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; + rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [| exact Hneq]; + rewrite beq_agent_neq; auto; apply Hfield ]. + +(** ================================================================ *) +(** revoke simulation *) +(** ================================================================ *) + +Lemma revoke_simulation : + forall cs as_ prnt target cs', + state_refines cs as_ -> + c_revoke cs prnt target = Some cs' -> + exists as'', + revoke as_ prnt target = Some as'' /\ + state_refines cs' as''. +Proof. + intros cs as_ prnt target cs' Href Hconcrete. + destruct_refines Href. + unfold c_revoke in Hconcrete. + destruct (negb (match BTreeMap.get target (c_agent_parent cs) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end)) eqn:Hpar; [discriminate|]. + apply negb_false_iff in Hpar. + destruct (BTreeMap.get target (c_agent_parent cs)) eqn:Hget_par; [|discriminate]. + destruct (AgentId_eq_dec a prnt) as [->|]; [|discriminate]. + destruct (negb (BTreeSet.mem prnt (c_agent_active cs))) eqn:Hprnt_act; + [discriminate|]. + apply negb_false_iff in Hprnt_act. + destruct (negb (BTreeSet.mem target (c_agent_active cs))) eqn:Htgt_act; + [discriminate|]. + apply negb_false_iff in Htgt_act. + destruct (AgentId_eq_dec target root_agent); [discriminate|]. + inversion Hconcrete; subst; clear Hconcrete. + rewrite Hactive in Hprnt_act. + rewrite Hactive in Htgt_act. + assert (Hpar_abs : agent_parent as_ target prnt = true) + by (exact (proj1 (Hparent _ _) Hget_par)). + unfold revoke. + rewrite Hpar_abs. simpl. + rewrite Hprnt_act. simpl. + rewrite Htgt_act. simpl. + unfold beq_agent at 1. destruct (AgentId_eq_dec target root_agent); [contradiction|]. simpl. + eexists. split; [reflexivity|]. + split_refines. + - intros a0. simpl. + rewrite (BTreeSet.mem_remove AgentId AgentId_eq_dec). + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite beq_agent_refl; reflexivity + | rewrite (beq_agent_neq _ _ Hneq); apply Hactive ]. + - intros c p. simpl. split; intros. + + destruct (AgentId_eq_dec c target) as [->|Hneq]. + * rewrite BTreeMap.get_remove_same in H. discriminate. + * rewrite BTreeMap.get_remove_other in H; [|exact Hneq]. + rewrite (beq_agent_neq _ _ Hneq). + exact (proj1 (Hparent _ _) H). + + destruct (AgentId_eq_dec c target) as [->|Hneq]. + * rewrite beq_agent_refl in H. discriminate. + * rewrite (beq_agent_neq _ _ Hneq) in H. + rewrite BTreeMap.get_remove_other; [|exact Hneq]. + exact (proj2 (Hparent _ _) H). + - intros a0 c. simpl. unfold mem_agent_cap, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hcap ]. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Htaint ]. + - intros a0 i. simpl. unfold mem_in_flight, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hflight ]. + - intros inv0 t0. simpl. apply Hinvtool. + - intros t0. simpl. apply Htoolreg. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hghinv ]. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 target) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hghrecv ]. +Qed. + +(** ================================================================ *) +(** cascade_revoke simulation *) +(** ================================================================ *) + +Lemma cascade_revoke_simulation : + forall cs as_ child prnt cs', + state_refines cs as_ -> + c_cascade_revoke cs child prnt = Some cs' -> + exists as'', + cascade_revoke as_ child prnt = Some as'' /\ + state_refines cs' as''. +Proof. + intros cs as_ child prnt cs' Href Hconcrete. + destruct_refines Href. + unfold c_cascade_revoke in Hconcrete. + destruct (negb (match BTreeMap.get child (c_agent_parent cs) with + | Some p => if AgentId_eq_dec p prnt then true else false + | None => false + end)) eqn:Hpar; [discriminate|]. + apply negb_false_iff in Hpar. + destruct (BTreeMap.get child (c_agent_parent cs)) eqn:Hget_par; [|discriminate]. + destruct (AgentId_eq_dec a prnt) as [->|]; [|discriminate]. + destruct (BTreeSet.mem prnt (c_agent_active cs)) eqn:Hprnt_act; [discriminate|]. + destruct (negb (BTreeSet.mem child (c_agent_active cs))) eqn:Hchild_act; + [discriminate|]. + apply negb_false_iff in Hchild_act. + destruct (AgentId_eq_dec child root_agent); [discriminate|]. + inversion Hconcrete; subst; clear Hconcrete. + rewrite Hactive in Hprnt_act. + rewrite Hactive in Hchild_act. + assert (Hpar_abs : agent_parent as_ child prnt = true) + by (exact (proj1 (Hparent _ _) Hget_par)). + unfold cascade_revoke. + rewrite Hpar_abs. simpl. + rewrite Hprnt_act. simpl. + rewrite Hchild_act. simpl. + unfold beq_agent at 1. destruct (AgentId_eq_dec child root_agent); [contradiction|]. simpl. + eexists. split; [reflexivity|]. + split_refines. + - intros a0. simpl. + rewrite (BTreeSet.mem_remove AgentId AgentId_eq_dec). + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite beq_agent_refl; reflexivity + | rewrite (beq_agent_neq _ _ Hneq); apply Hactive ]. + - intros c p. simpl. split; intros. + + destruct (AgentId_eq_dec c child) as [->|Hneq]. + * rewrite BTreeMap.get_remove_same in H. discriminate. + * rewrite BTreeMap.get_remove_other in H; [|exact Hneq]. + rewrite (beq_agent_neq _ _ Hneq). + exact (proj1 (Hparent _ _) H). + + destruct (AgentId_eq_dec c child) as [->|Hneq]. + * rewrite beq_agent_refl in H. discriminate. + * rewrite (beq_agent_neq _ _ Hneq) in H. + rewrite BTreeMap.get_remove_other; [|exact Hneq]. + exact (proj2 (Hparent _ _) H). + - intros a0 c. simpl. unfold mem_agent_cap, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hcap ]. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Htaint ]. + - intros a0 i. simpl. unfold mem_in_flight, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hflight ]. + - intros inv0 t0. simpl. apply Hinvtool. + - intros t0. simpl. apply Htoolreg. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hghinv ]. + - intros a0 l. simpl. unfold mem_taint, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a0 child) as [->|Hneq]; + [ rewrite BTreeMap.get_remove_same; rewrite beq_agent_refl; reflexivity + | rewrite BTreeMap.get_remove_other; [|exact Hneq]; + rewrite (beq_agent_neq _ _ Hneq); apply Hghrecv ]. +Qed. + +(** ================================================================ *) +(** return_endorsed simulation (trivial -- identity) *) +(** ================================================================ *) + +Lemma return_endorsed_simulation : + forall cs as_ child prnt, + state_refines cs as_ -> + c_return_endorsed_pre cs child prnt -> + return_endorsed_pre as_ child prnt. +Proof. + intros cs as_ child prnt Href Hpre. + destruct_refines Href. + unfold c_return_endorsed_pre in Hpre. + destruct Hpre as [Hpar_c [Hchild_c [Hprnt_c Hno_flight]]]. + unfold return_endorsed_pre. + destruct (BTreeMap.get child (c_agent_parent cs)) eqn:Hget; [|contradiction]. + subst a. + repeat split. + - exact (proj1 (Hparent _ _) Hget). + - rewrite <- Hactive. assumption. + - rewrite <- Hactive. assumption. + - intros i. rewrite <- Hflight. apply Hno_flight. +Qed. + +(** ================================================================ *) +(** delegate simulation (Admitted -- needs remove_by_value reasoning) *) +(** ================================================================ *) + +Lemma delegate_simulation : + forall cs as_ grantor grantee cs', + state_refines cs as_ -> + c_invocation_tool_complete cs -> + c_delegate cs grantor grantee = Some cs' -> + exists as'', + delegate as_ grantor grantee = Some as'' /\ + state_refines cs' as''. +Admitted. + +(** ================================================================ *) +(** invoke_start simulation (Admitted -- complex precondition) *) +(** ================================================================ *) + +Lemma invoke_start_simulation : + forall cs as_ a tool inv, + state_refines cs as_ -> + c_invocation_tool_complete cs -> + c_invoke_start_pre cs a tool inv -> + invoke_start_pre as_ a tool inv /\ + state_refines + (c_invoke_start_state cs a tool inv) + (invoke_start_state as_ a inv). +Admitted. + +(** ================================================================ *) +(** invoke_complete simulation (Admitted -- invocation_tool lookup) *) +(** ================================================================ *) + +Lemma invoke_complete_simulation : + forall cs as_ a inv cs', + state_refines cs as_ -> + c_invocation_tool_complete cs -> + c_invoke_complete cs a inv = Some cs' -> + exists as'', + invoke_complete as_ a inv = Some as'' /\ + state_refines cs' as''. +Admitted. + +(** ================================================================ *) +(** return_unendorsed simulation (Admitted -- union reasoning) *) +(** ================================================================ *) + +Lemma return_unendorsed_simulation : + forall cs as_ child prnt, + state_refines cs as_ -> + c_return_unendorsed_pre cs child prnt -> + return_unendorsed_pre as_ child prnt /\ + state_refines + (c_return_unendorsed_state cs child prnt) + (return_unendorsed_state as_ child prnt). +Admitted. + +(** ================================================================ *) +(** sentinel_elevate_taint simulation (Admitted) *) +(** ================================================================ *) + +Lemma sentinel_elevate_taint_simulation : + forall cs as_ a l, + state_refines cs as_ -> + c_sentinel_elevate_taint_pre cs a l -> + sentinel_elevate_taint_pre as_ a l /\ + state_refines + (c_sentinel_elevate_taint_state cs a l) + (sentinel_elevate_taint_state as_ a l). +Admitted. + +(** ================================================================ *) +(** Combined: any concrete step simulates an abstract step *) +(** ================================================================ *) + +Theorem simulation : + forall cs cs' as_, + state_refines cs as_ -> + c_invocation_tool_complete cs -> + c_step cs cs' -> + exists as'', + step as_ as'' /\ + state_refines cs' as''. +Proof. + intros cs cs' as_ Href Hcomplete Hcstep. + inversion Hcstep; subst. + - destruct (register_tool_simulation _ _ _ _ Href H) as [as'' [Habs Href']]. + exists as''. exact (conj (step_register_tool _ _ _ Habs) Href'). + - destruct (delegate_simulation _ _ _ _ _ Href Hcomplete H) + as [as'' [Habs Href']]. + exists as''. exact (conj (step_delegate _ _ _ _ Habs) Href'). + - destruct (grant_capability_simulation _ _ _ _ _ _ Href H) + as [as'' [Habs Href']]. + exists as''. exact (conj (step_grant_capability _ _ _ _ _ Habs) Href'). + - destruct (revoke_simulation _ _ _ _ _ Href H) as [as'' [Habs Href']]. + exists as''. exact (conj (step_revoke _ _ _ _ Habs) Href'). + - destruct (cascade_revoke_simulation _ _ _ _ _ Href H) + as [as'' [Habs Href']]. + exists as''. exact (conj (step_cascade_revoke _ _ _ _ Habs) Href'). + - destruct (invoke_start_simulation _ _ _ _ _ Href Hcomplete H) + as [Hpre Href']. + eexists. exact (conj (step_invoke_start _ _ _ _ Hpre) Href'). + - destruct (invoke_complete_simulation _ _ _ _ _ Href Hcomplete H) + as [as'' [Habs Href']]. + exists as''. exact (conj (step_invoke_complete _ _ _ _ Habs) Href'). + - exists as_. + exact (conj (step_return_endorsed _ _ _ (return_endorsed_simulation _ _ _ _ Href H)) Href). + - destruct (return_unendorsed_simulation _ _ _ _ Href H) + as [Hpre Href']. + eexists. exact (conj (step_return_unendorsed _ _ _ Hpre) Href'). + - destruct (sentinel_elevate_taint_simulation _ _ _ _ Href H) + as [Hpre Href']. + eexists. exact (conj (step_sentinel_elevate_taint _ _ _ Hpre) Href'). +Qed. diff --git a/argus/formal/refinement/Soundness.v b/argus/formal/refinement/Soundness.v new file mode 100644 index 0000000..b2f071d --- /dev/null +++ b/argus/formal/refinement/Soundness.v @@ -0,0 +1,73 @@ +(** Top-Level Soundness Theorem + Crown jewel: every reachable concrete state satisfies all safety properties. + + Proof: by induction on c_reachable, composing: + 1. simulation (Simulation.v) -- concrete step implies abstract step + 2. invariants_inductive (TzimtzumV2_proofs.v) -- abstract safety *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.BTreeAxioms. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. +Require Import ArgusKernel.spec.TzimtzumV2_safety. +Require Import ArgusKernel.spec.TzimtzumV2_proofs. +Require Import ArgusKernel.refinement.ConcreteSpec. +Require Import ArgusKernel.refinement.StateRelation. +Require Import ArgusKernel.refinement.Simulation. + +(** The invocation_tool_complete invariant is preserved by all transitions. *) +Lemma c_invocation_tool_complete_preserved : + forall cs cs', + c_invocation_tool_complete cs -> + c_step cs cs' -> + c_invocation_tool_complete cs'. +Admitted. + +Lemma c_invocation_tool_complete_initial : + c_invocation_tool_complete c_initial_state. +Proof. + unfold c_invocation_tool_complete, c_initial_state, mem_in_flight, + BTreeMap.mem_nested; simpl. + intros a inv. + rewrite (BTreeMap.get_empty AgentId (BTreeSet.t InvocationId)). + discriminate. +Qed. + +Lemma c_invocation_tool_complete_reachable : + forall cs, c_reachable cs -> c_invocation_tool_complete cs. +Proof. + intros cs Hreach. induction Hreach. + - exact c_invocation_tool_complete_initial. + - exact (c_invocation_tool_complete_preserved _ _ IHHreach H). +Qed. + +(** Every reachable concrete state refines some reachable abstract state. *) +Theorem refinement_preserves_reachability : + forall cs, + c_reachable cs -> + exists as_, state_refines cs as_ /\ reachable as_. +Proof. + intros cs Hreach. induction Hreach as [| cs cs' Hreach IH Hstep]. + - exists initial_state. split. + + exact initial_state_refines. + + exact reach_init. + - destruct IH as [as_ [Href Hreach_abs]]. + pose proof (c_invocation_tool_complete_reachable cs Hreach) as Hcomplete. + destruct (simulation _ _ _ Href Hcomplete Hstep) as [as'' [Habs_step Href']]. + exists as''. split. + + exact Href'. + + exact (reach_step _ _ Hreach_abs Habs_step). +Qed. + +(** Crown jewel: every reachable concrete state satisfies all safety. *) +Theorem implementation_sound : + forall cs, + c_reachable cs -> + exists as_, state_refines cs as_ /\ all_invariants as_. +Proof. + intros cs Hreach. + destruct (refinement_preserves_reachability cs Hreach) as [as_ [Href Hreach_abs]]. + exists as_. split. + - exact Href. + - exact (invariants_inductive _ Hreach_abs). +Qed. diff --git a/argus/formal/refinement/StateRelation.v b/argus/formal/refinement/StateRelation.v new file mode 100644 index 0000000..acea695 --- /dev/null +++ b/argus/formal/refinement/StateRelation.v @@ -0,0 +1,74 @@ +(** Refinement Relation: bridges the concrete BTreeMap-based state + and the abstract function-based state from TzimtzumV2.v. + + The key insight: invocation_tool is a Parameter (immutable) in the + abstract spec but a BTreeMap (mutable) in the concrete spec. + The refinement requires the concrete map to agree with the abstract + function wherever a binding exists. *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.BTreeAxioms. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. +Require Import ArgusKernel.refinement.ConcreteSpec. + +(** ================================================================ *) +(** State Refinement Relation (9 conjuncts) *) +(** ================================================================ *) + +Definition state_refines (cs : ConcreteState) (as_ : KernelState) : Prop := + (forall a, BTreeSet.mem a (c_agent_active cs) = agent_active as_ a) + /\ (forall c p, + BTreeMap.get c (c_agent_parent cs) = Some p <-> + agent_parent as_ c p = true) + /\ (forall a c, + mem_agent_cap a c (c_agent_cap cs) = agent_cap as_ a c) + /\ (forall a l, + mem_taint a l (c_taint_levels cs) = taint_levels as_ a l) + /\ (forall a i, + mem_in_flight a i (c_in_flight cs) = in_flight as_ a i) + /\ (forall inv tool, + BTreeMap.get inv (c_invocation_tool cs) = Some tool -> + invocation_tool inv = tool) + /\ (forall t, + BTreeSet.mem t (c_tool_registered cs) = tool_registered as_ t) + /\ (forall a l, + mem_taint a l (c_gh_taint_invoked cs) = gh_taint_invoked as_ a l) + /\ (forall a l, + mem_taint a l (c_gh_taint_received cs) = gh_taint_received as_ a l). + +(** ================================================================ *) +(** Initial State Refinement *) +(** ================================================================ *) + +Theorem initial_state_refines : + state_refines c_initial_state initial_state. +Proof. + unfold state_refines, c_initial_state, initial_state; simpl. + repeat split; intros. + - rewrite (BTreeSet.mem_insert AgentId AgentId_eq_dec). + destruct (AgentId_eq_dec a root_agent) as [->|Hneq]. + + rewrite beq_agent_refl. reflexivity. + + rewrite BTreeSet.mem_empty. + rewrite beq_agent_neq; auto. + - rewrite BTreeMap.get_empty in H. discriminate. + - discriminate. + - unfold mem_agent_cap, BTreeMap.mem_nested. + destruct (AgentId_eq_dec a root_agent) as [->|Hneq]. + + rewrite BTreeMap.get_insert_same. + rewrite all_caps_mem. rewrite beq_agent_refl. reflexivity. + + rewrite BTreeMap.get_insert_other; [| exact Hneq]. + rewrite BTreeMap.get_empty. + rewrite beq_agent_neq; auto. + - unfold mem_taint, BTreeMap.mem_nested. + rewrite BTreeMap.get_empty. reflexivity. + - unfold mem_in_flight, BTreeMap.mem_nested. + rewrite BTreeMap.get_empty. reflexivity. + - rewrite BTreeMap.get_empty in H. discriminate. + - rewrite BTreeSet.mem_empty. reflexivity. + - unfold mem_taint, BTreeMap.mem_nested. + rewrite BTreeMap.get_empty. reflexivity. + - unfold mem_taint, BTreeMap.mem_nested. + rewrite BTreeMap.get_empty. reflexivity. +Qed. diff --git a/argus/formal/spec/TzimtzumV2.v b/argus/formal/spec/TzimtzumV2.v new file mode 100644 index 0000000..ed707fb --- /dev/null +++ b/argus/formal/spec/TzimtzumV2.v @@ -0,0 +1,407 @@ +(** TzimtzumV2.3: Tool Authorization Protocol Security Kernel + Coq port of tzimtzum/TzimtzumV2.lean + + Architecture: Agents form a tree rooted at root_agent. Each agent carries + capabilities (typed permissions), taint (confidentiality exposure), and + in-flight invocations. Tools have immutable metadata. Every invocation + passes through a three-check gate: capability, flow, and authorizer. + + This file defines: + - Background theory (tool metadata, flow policy, oracles) + - Mutable state record (KernelState) + - Initial state + - 9 actions as state transformers *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. + +(** --- Immutable Tool Metadata (Background Theory) --- + Lean: lines 111-114. Declared at registration, never changes. *) + +Parameter tool_cap : ToolId -> CapKind -> bool. +Parameter tool_egress : ToolId -> EgressKind -> bool. +Parameter tool_conf_floor : ToolId -> ConfLevel. +Parameter tool_endorsed : ToolId -> bool. + +(** Lean: line 173. Maps each invocation ID to its tool. + Immutable in the abstract spec -- the binding is part of background theory. + The Rust implementation stores this mutably; the refinement bridge resolves + this gap. *) +Parameter invocation_tool : InvocationId -> ToolId. + +(** --- Immutable Flow Policy --- + Lean: lines 141-145. Three modes: ALLOW, INSPECT, DENY. + Encoded as two Bool relations with mutual exclusivity axiom. *) + +Parameter flow_allows : ConfLevel -> EgressKind -> bool. +Parameter flow_inspects : ConfLevel -> EgressKind -> bool. +Parameter flow_override : AgentId -> ToolId -> ConfLevel -> bool. + +Axiom flow_exclusive : forall L E, + flow_allows L E = true -> flow_inspects L E = false. + +(** --- Mutable State --- + Lean: lines 168-176. Function-based representation matching Veil DSL. + Each field is a total function returning bool (membership predicate). *) + +Record KernelState := mkState { + agent_active : AgentId -> bool; + agent_parent : AgentId -> AgentId -> bool; + agent_cap : AgentId -> CapKind -> bool; + taint_levels : AgentId -> ConfLevel -> bool; + in_flight : AgentId -> InvocationId -> bool; + tool_registered : ToolId -> bool; + gh_taint_invoked : AgentId -> ConfLevel -> bool; + gh_taint_received : AgentId -> ConfLevel -> bool +}. + +(** --- Boolean equality helpers --- + Wrapper around decidable equality for use in if-then-else. *) + +Definition beq_agent (x y : AgentId) : bool := + if AgentId_eq_dec x y then true else false. +Definition beq_tool (x y : ToolId) : bool := + if ToolId_eq_dec x y then true else false. +Definition beq_inv (x y : InvocationId) : bool := + if InvocationId_eq_dec x y then true else false. +Definition beq_cap (x y : CapKind) : bool := + if CapKind_eq_dec x y then true else false. +Definition beq_conf (x y : ConfLevel) : bool := + if ConfLevel_eq_dec x y then true else false. +Definition beq_egress (x y : EgressKind) : bool := + if EgressKind_eq_dec x y then true else false. + +(** --- Lemmas for boolean equality reflection --- *) + +Lemma beq_agent_refl : forall a, beq_agent a a = true. +Proof. intros. unfold beq_agent. destruct (AgentId_eq_dec a a); [reflexivity | contradiction]. Qed. + +Lemma beq_agent_true : forall a b, beq_agent a b = true -> a = b. +Proof. intros. unfold beq_agent in H. destruct (AgentId_eq_dec a b); [auto | discriminate]. Qed. + +Lemma beq_agent_false : forall a b, beq_agent a b = false -> a <> b. +Proof. intros. unfold beq_agent in H. destruct (AgentId_eq_dec a b); [discriminate | auto]. Qed. + +Lemma beq_agent_neq : forall a b, a <> b -> beq_agent a b = false. +Proof. intros. unfold beq_agent. destruct (AgentId_eq_dec a b); [contradiction | reflexivity]. Qed. + +Lemma beq_inv_refl : forall i, beq_inv i i = true. +Proof. intros. unfold beq_inv. destruct (InvocationId_eq_dec i i); [reflexivity | contradiction]. Qed. + +Lemma beq_conf_refl : forall l, beq_conf l l = true. +Proof. intros. unfold beq_conf. destruct (ConfLevel_eq_dec l l); [reflexivity | contradiction]. Qed. + +Lemma beq_cap_refl : forall c, beq_cap c c = true. +Proof. intros. unfold beq_cap. destruct (CapKind_eq_dec c c); [reflexivity | contradiction]. Qed. + +Lemma beq_tool_refl : forall t, beq_tool t t = true. +Proof. intros. unfold beq_tool. destruct (ToolId_eq_dec t t); [reflexivity | contradiction]. Qed. + +Lemma beq_tool_true : forall a b, beq_tool a b = true -> a = b. +Proof. intros. unfold beq_tool in H. destruct (ToolId_eq_dec a b); [auto | discriminate]. Qed. + +Lemma beq_inv_true : forall a b, beq_inv a b = true -> a = b. +Proof. intros. unfold beq_inv in H. destruct (InvocationId_eq_dec a b); [auto | discriminate]. Qed. + +Lemma beq_inv_neq : forall a b, a <> b -> beq_inv a b = false. +Proof. intros. unfold beq_inv. destruct (InvocationId_eq_dec a b); [contradiction | reflexivity]. Qed. + +Lemma beq_cap_true : forall a b, beq_cap a b = true -> a = b. +Proof. intros. unfold beq_cap in H. destruct (CapKind_eq_dec a b); [auto | discriminate]. Qed. + +Lemma beq_cap_neq : forall a b, a <> b -> beq_cap a b = false. +Proof. intros. unfold beq_cap. destruct (CapKind_eq_dec a b); [contradiction | reflexivity]. Qed. + +Lemma beq_conf_true : forall a b, beq_conf a b = true -> a = b. +Proof. intros. unfold beq_conf in H. destruct (ConfLevel_eq_dec a b); [auto | discriminate]. Qed. + +Lemma beq_conf_neq : forall a b, a <> b -> beq_conf a b = false. +Proof. intros. unfold beq_conf. destruct (ConfLevel_eq_dec a b); [contradiction | reflexivity]. Qed. + +(** --- Speculative Taint (Derived Predicate) --- + Lean: lines 227-231. Worst-case taint including in-flight non-endorsed tools. + Prop-level because InvocationId is uninterpreted (cannot enumerate). *) + +Definition speculative_taint (st : KernelState) (a : AgentId) (l : ConfLevel) : Prop := + taint_levels st a l = true + \/ (exists I, in_flight st a I = true + /\ tool_conf_floor (invocation_tool I) = l + /\ tool_endorsed (invocation_tool I) = false). + +(** --- Initial State --- + Lean: lines 240-249. Only root_agent active, root holds all capabilities, + everything else empty. *) + +Definition initial_state : KernelState := mkState + (fun a => beq_agent a root_agent) + (fun _ _ => false) + (fun a _ => beq_agent a root_agent) + (fun _ _ => false) + (fun _ _ => false) + (fun _ => false) + (fun _ _ => false) + (fun _ _ => false). + +(** --- Flow Allowed Helper --- + Matches Rust transitions.rs:11-24 and Lean's flow gate pattern. + Three-way disjunction: ALLOW, INSPECT+content_gate, or override. *) + +Definition flow_allowed (agent : AgentId) (tool : ToolId) + (level : ConfLevel) (egress : EgressKind) : bool := + flow_allows level egress + || (flow_inspects level egress && content_gate_passes agent tool) + || flow_override agent tool level. + +(** ================================================================ *) +(** --- Actions --- *) +(** ================================================================ *) + +(** register_tool: Lean lines 270-273 + Flips tool_registered flag. Tool metadata is immutable background theory. *) + +Definition register_tool (st : KernelState) (tool : ToolId) + : option KernelState := + if tool_registered st tool then None + else Some (mkState + (agent_active st) + (agent_parent st) + (agent_cap st) + (taint_levels st) + (in_flight st) + (fun t => if beq_tool t tool then true else tool_registered st t) + (gh_taint_invoked st) + (gh_taint_received st)). + +(** delegate: Lean lines 287-299 + Grantor creates a fresh child. Child starts with empty caps, taint, in-flight. + Stale parent entries involving grantee are cleared (defense-in-depth). *) + +Definition delegate (st : KernelState) (grantor grantee : AgentId) + : option KernelState := + if negb (agent_active st grantor) then None + else if agent_active st grantee then None + else if beq_agent grantee root_agent then None + else Some (mkState + (fun a => if beq_agent a grantee then true else agent_active st a) + (fun c p => + if beq_agent c grantee && beq_agent p grantor then true + else if beq_agent c grantee || beq_agent p grantee then false + else agent_parent st c p) + (fun a c => if beq_agent a grantee then false else agent_cap st a c) + (fun a l => if beq_agent a grantee then false else taint_levels st a l) + (fun a i => if beq_agent a grantee then false else in_flight st a i) + (tool_registered st) + (fun a l => if beq_agent a grantee then false else gh_taint_invoked st a l) + (fun a l => if beq_agent a grantee then false else gh_taint_received st a l)). + +(** grant_capability: Lean lines 308-314 + Parent grants a single capability it holds to a direct child. + Capabilities flow strictly downward. *) + +Definition grant_capability (st : KernelState) + (prnt child : AgentId) (cap : CapKind) : option KernelState := + if negb (agent_active st prnt) then None + else if negb (agent_active st child) then None + else if negb (agent_parent st child prnt) then None + else if negb (agent_cap st prnt cap) then None + else Some (mkState + (agent_active st) + (agent_parent st) + (fun a c => + if beq_agent a child && beq_cap c cap then true + else agent_cap st a c) + (taint_levels st) + (in_flight st) + (tool_registered st) + (gh_taint_invoked st) + (gh_taint_received st)). + +(** revoke: Lean lines 325-337 + Parent removes a direct child. All state for target is cleared. *) + +Definition revoke (st : KernelState) (prnt target : AgentId) + : option KernelState := + if negb (agent_parent st target prnt) then None + else if negb (agent_active st prnt) then None + else if negb (agent_active st target) then None + else if beq_agent target root_agent then None + else Some (mkState + (fun a => if beq_agent a target then false else agent_active st a) + (fun c p => if beq_agent c target then false else agent_parent st c p) + (fun a c => if beq_agent a target then false else agent_cap st a c) + (fun a l => if beq_agent a target then false else taint_levels st a l) + (fun a i => if beq_agent a target then false else in_flight st a i) + (tool_registered st) + (fun a l => if beq_agent a target then false else gh_taint_invoked st a l) + (fun a l => if beq_agent a target then false else gh_taint_received st a l)). + +(** cascade_revoke: Lean lines 360-372 + Orphan cleanup: child's parent is inactive, so child is removed. + Identical to revoke except precondition checks parent is INactive. *) + +Definition cascade_revoke (st : KernelState) (child prnt : AgentId) + : option KernelState := + if negb (agent_parent st child prnt) then None + else if agent_active st prnt then None + else if negb (agent_active st child) then None + else if beq_agent child root_agent then None + else Some (mkState + (fun a => if beq_agent a child then false else agent_active st a) + (fun c p => if beq_agent c child then false else agent_parent st c p) + (fun a c => if beq_agent a child then false else agent_cap st a c) + (fun a l => if beq_agent a child then false else taint_levels st a l) + (fun a i => if beq_agent a child then false else in_flight st a i) + (tool_registered st) + (fun a l => if beq_agent a child then false else gh_taint_invoked st a l) + (fun a l => if beq_agent a child then false else gh_taint_received st a l)). + +(** invoke_start: Lean lines 402-433 + Three-check authorization gate. Uses Prop-level preconditions because + universal quantifiers over uninterpreted sorts are not decidable. + Split into precondition (Prop) and state transform (pure function). *) + +Definition invoke_start_pre (st : KernelState) + (a : AgentId) (tool : ToolId) (inv : InvocationId) : Prop := + agent_active st a = true + /\ a <> root_agent + /\ tool_registered st tool = true + /\ invocation_tool inv = tool + /\ (forall ag, in_flight st ag inv = false) + (** CHECK 1: Capability gate *) + /\ (forall c, tool_cap tool c = true -> agent_cap st a c = true) + (** CHECK 2a: Speculative taint x new tool's egress *) + /\ (forall l e, + speculative_taint st a l -> + tool_egress tool e = true -> + flow_allowed a tool l e = true) + (** CHECK 2b: New tool's taint x existing in-flight tool's egress *) + /\ (forall i e, + in_flight st a i = true -> + tool_egress (invocation_tool i) e = true -> + tool_endorsed tool = false -> + flow_allowed a (invocation_tool i) (tool_conf_floor tool) e = true) + (** CHECK 2c: Self-flow (tool's own taint x its own egress) *) + /\ (forall e, + tool_endorsed tool = false -> + tool_egress tool e = true -> + flow_allowed a tool (tool_conf_floor tool) e = true) + (** CHECK 3: Authorizer gate *) + /\ authorizer_allows a tool = true. + +Definition invoke_start_state (st : KernelState) + (a : AgentId) (inv : InvocationId) : KernelState := + mkState + (agent_active st) + (agent_parent st) + (agent_cap st) + (taint_levels st) + (fun ag i => + if beq_agent ag a && beq_inv i inv then true + else in_flight st ag i) + (tool_registered st) + (gh_taint_invoked st) + (gh_taint_received st). + +(** invoke_complete: Lean lines 446-458 + Tool returns a result. Invocation leaves in-flight set. + Non-endorsed tools add taint at their conf_floor level. *) + +Definition invoke_complete (st : KernelState) + (a : AgentId) (inv : InvocationId) : option KernelState := + if negb (in_flight st a inv) then None + else if negb (agent_active st a) then None + else + let tool := invocation_tool inv in + let add_taint := negb (tool_endorsed tool) in + let floor := tool_conf_floor tool in + Some (mkState + (agent_active st) + (agent_parent st) + (agent_cap st) + (fun ag l => + if beq_agent ag a && add_taint && beq_conf l floor + then true + else taint_levels st ag l) + (fun ag i => + if beq_agent ag a && beq_inv i inv + then false + else in_flight st ag i) + (tool_registered st) + (fun ag l => + if beq_agent ag a && add_taint && beq_conf l floor + then true + else gh_taint_invoked st ag l) + (gh_taint_received st)). + +(** return_endorsed: Lean lines 471-477 + Child returns a bounded result to parent. No taint propagation. + Pure guard action -- verifies preconditions, no state change. *) + +Definition return_endorsed_pre (st : KernelState) + (child prnt : AgentId) : Prop := + agent_parent st child prnt = true + /\ agent_active st child = true + /\ agent_active st prnt = true + /\ (forall i, in_flight st child i = false). + +(** return_unendorsed: Lean lines 497-512 + Child returns unbounded result. Parent inherits child's taint via set union. + Flow gate: incoming taint must be compatible with parent's in-flight tools. *) + +Definition return_unendorsed_pre (st : KernelState) + (child prnt : AgentId) : Prop := + agent_parent st child prnt = true + /\ agent_active st child = true + /\ agent_active st prnt = true + /\ (forall i, in_flight st child i = false) + /\ (forall l i e, + taint_levels st child l = true -> + in_flight st prnt i = true -> + tool_egress (invocation_tool i) e = true -> + flow_allowed prnt (invocation_tool i) l e = true). + +Definition return_unendorsed_state (st : KernelState) + (child prnt : AgentId) : KernelState := + mkState + (agent_active st) + (agent_parent st) + (agent_cap st) + (fun a l => + if beq_agent a prnt && taint_levels st child l + then true + else taint_levels st a l) + (in_flight st) + (tool_registered st) + (gh_taint_invoked st) + (fun a l => + if beq_agent a prnt && taint_levels st child l + then true + else gh_taint_received st a l). + +(** sentinel_elevate_taint: Sentinel adds taint at a given level. + Precondition ensures flow compatibility with all in-flight tools. *) + +Definition sentinel_elevate_taint_pre (st : KernelState) + (a : AgentId) (l : ConfLevel) : Prop := + agent_active st a = true + /\ (forall i e, + in_flight st a i = true -> + tool_egress (invocation_tool i) e = true -> + flow_allowed a (invocation_tool i) l e = true). + +Definition sentinel_elevate_taint_state (st : KernelState) + (a : AgentId) (l : ConfLevel) : KernelState := + mkState + (agent_active st) + (agent_parent st) + (agent_cap st) + (fun ag lvl => + if beq_agent ag a && beq_conf lvl l then true + else taint_levels st ag lvl) + (in_flight st) + (tool_registered st) + (fun ag lvl => + if beq_agent ag a && beq_conf lvl l then true + else gh_taint_invoked st ag lvl) + (gh_taint_received st). diff --git a/argus/formal/spec/TzimtzumV2_proofs.v b/argus/formal/spec/TzimtzumV2_proofs.v new file mode 100644 index 0000000..d8db75e --- /dev/null +++ b/argus/formal/spec/TzimtzumV2_proofs.v @@ -0,0 +1,891 @@ +(** TzimtzumV2.3 Safety Proofs + Proves all_invariants is inductive: + 1. Base case: initial_state satisfies all invariants + 2. Inductive case: each action preserves all invariants + + Proof structure: 9 per-action preservation lemmas + 1 base case + = 10 lemmas composing into invariants_inductive. *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. +Require Import ArgusKernel.spec.TzimtzumV2_safety. + +(** ================================================================ *) +(** Useful Tactics *) +(** ================================================================ *) + +Ltac unfold_state := + unfold initial_state, register_tool, delegate, grant_capability, + revoke, cascade_revoke, invoke_start_state, + invoke_complete, return_unendorsed_state, + sentinel_elevate_taint_state in *; + simpl in *. + +Ltac unfold_invariants := + unfold all_invariants, root_always_active, default_deny, + flow_confinement, flow_confinement_weak, + capability_subsumption, revocation_clean, taint_integrity, + parent_implies_active, single_parent, no_self_parent, + root_no_parent, in_flight_active, in_flight_registered, + in_flight_unique, root_all_caps, root_no_in_flight, + ghost_invoked_sound, ghost_received_sound, + in_flight_flow_compat in *. + +Ltac beq_crush := + repeat match goal with + | |- context[beq_agent ?a ?a] => rewrite beq_agent_refl + | |- context[beq_inv ?i ?i] => rewrite beq_inv_refl + | |- context[beq_conf ?l ?l] => rewrite beq_conf_refl + | |- context[beq_cap ?c ?c] => rewrite beq_cap_refl + | |- context[beq_tool ?t ?t] => rewrite beq_tool_refl + | H : ?a <> ?b |- context[beq_agent ?a ?b] => rewrite (beq_agent_neq _ _ H) + | H : beq_agent ?a ?b = true |- _ => apply beq_agent_true in H; subst + | H : beq_agent ?a ?b = false |- _ => apply beq_agent_false in H + end; simpl in *; try discriminate; auto. + +(** ================================================================ *) +(** Base Case: Initial State *) +(** ================================================================ *) + +Lemma initial_root_always_active : root_always_active initial_state. +Proof. + unfold root_always_active, initial_state. simpl. + apply beq_agent_refl. +Qed. + +Lemma initial_default_deny : default_deny initial_state. +Proof. + unfold default_deny, initial_state. simpl. + intros a i H. discriminate. +Qed. + +Lemma initial_flow_confinement : flow_confinement initial_state. +Proof. + unfold flow_confinement, initial_state. simpl. + intros a l i e Htaint _ _. discriminate. +Qed. + +Lemma initial_flow_confinement_weak : flow_confinement_weak initial_state. +Proof. + unfold flow_confinement_weak, initial_state. simpl. + intros a l i e Htaint _ _. discriminate. +Qed. + +Lemma initial_capability_subsumption : capability_subsumption initial_state. +Proof. + unfold capability_subsumption, initial_state. simpl. + intros c p Hpar _ _ _. discriminate. +Qed. + +Lemma initial_revocation_clean : revocation_clean initial_state. +Proof. + unfold revocation_clean, initial_state. simpl. + intros a Hinactive. split; intros; reflexivity. +Qed. + +Lemma initial_taint_integrity : taint_integrity initial_state. +Proof. + unfold taint_integrity, initial_state. simpl. + intros a l Htaint _. discriminate. +Qed. + +Lemma initial_parent_implies_active : parent_implies_active initial_state. +Proof. + unfold parent_implies_active, initial_state. simpl. + intros c p H. discriminate. +Qed. + +Lemma initial_single_parent : single_parent initial_state. +Proof. + unfold single_parent, initial_state. simpl. + intros c p1 p2 H1 _ _. discriminate. +Qed. + +Lemma initial_no_self_parent : no_self_parent initial_state. +Proof. + unfold no_self_parent, initial_state. simpl. + intros. reflexivity. +Qed. + +Lemma initial_root_no_parent : root_no_parent initial_state. +Proof. + unfold root_no_parent, initial_state. simpl. + intros. reflexivity. +Qed. + +Lemma initial_in_flight_active : in_flight_active initial_state. +Proof. + unfold in_flight_active, initial_state. simpl. + intros a i H. discriminate. +Qed. + +Lemma initial_in_flight_registered : in_flight_registered initial_state. +Proof. + unfold in_flight_registered, initial_state. simpl. + intros a i H. discriminate. +Qed. + +Lemma initial_in_flight_unique : in_flight_unique initial_state. +Proof. + unfold in_flight_unique, initial_state. simpl. + intros a1 a2 i H1 _. discriminate. +Qed. + +Lemma initial_root_all_caps : root_all_caps initial_state. +Proof. + unfold root_all_caps, initial_state. simpl. + intros c. apply beq_agent_refl. +Qed. + +Lemma initial_root_no_in_flight : root_no_in_flight initial_state. +Proof. + unfold root_no_in_flight, initial_state. simpl. + intros. reflexivity. +Qed. + +Lemma initial_ghost_invoked_sound : ghost_invoked_sound initial_state. +Proof. + unfold ghost_invoked_sound, initial_state. simpl. + intros a l H. discriminate. +Qed. + +Lemma initial_ghost_received_sound : ghost_received_sound initial_state. +Proof. + unfold ghost_received_sound, initial_state. simpl. + intros a l H. discriminate. +Qed. + +Lemma initial_in_flight_flow_compat : in_flight_flow_compat initial_state. +Proof. + unfold in_flight_flow_compat, initial_state. simpl. + intros a i1 i2 e H1 _ _ _. discriminate. +Qed. + +Theorem initial_all_invariants : all_invariants initial_state. +Proof. + unfold all_invariants. + exact (conj initial_root_always_active + (conj initial_default_deny + (conj initial_flow_confinement + (conj initial_flow_confinement_weak + (conj initial_capability_subsumption + (conj initial_revocation_clean + (conj initial_taint_integrity + (conj initial_parent_implies_active + (conj initial_single_parent + (conj initial_no_self_parent + (conj initial_root_no_parent + (conj initial_in_flight_active + (conj initial_in_flight_registered + (conj initial_in_flight_unique + (conj initial_root_all_caps + (conj initial_root_no_in_flight + (conj initial_ghost_invoked_sound + (conj initial_ghost_received_sound + initial_in_flight_flow_compat)))))))))))))))))). +Qed. + +(** ================================================================ *) +(** Per-Action Preservation Lemmas *) +(** ================================================================ *) + +Lemma register_tool_preserves : forall st st' tool, + all_invariants st -> + register_tool st tool = Some st' -> + all_invariants st'. +Proof. + intros st st' tool Hinv Hstep. + unfold register_tool in Hstep. + destruct (tool_registered st tool) eqn:Hreg; [discriminate|]. + injection Hstep as Heq; subst st'. + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + assert (Hifr' : in_flight_registered + (mkState (agent_active st) (agent_parent st) (agent_cap st) + (taint_levels st) (in_flight st) + (fun t => if beq_tool t tool then true else tool_registered st t) + (gh_taint_invoked st) (gh_taint_received st))). + { unfold in_flight_registered; simpl; intros a i Hif. + destruct (beq_tool (invocation_tool i) tool); [reflexivity | exact (Hifr a i Hif)]. } + unfold all_invariants. + exact (conj Hraa (conj Hdd (conj Hfc (conj Hfcw (conj Hcs (conj Hrc + (conj Hti (conj Hpia (conj Hsp (conj Hnsp (conj Hrnp (conj Hifa + (conj Hifr' (conj Hifu (conj Hrac (conj Hrni (conj Hgis + (conj Hgrs Hiffc)))))))))))))))))). +Qed. + +Ltac deq a b := + let H := fresh "Heq" in + destruct (AgentId_eq_dec a b) as [H|H]; + [rewrite H in *; clear H; try rewrite beq_agent_refl in *; simpl in * | + try rewrite beq_agent_neq in * by assumption; simpl in *]. + +Ltac split_all := + match goal with + | |- _ /\ _ => split; [| split_all] + | _ => idtac + end. + +Ltac deq_inv a b := + let H := fresh "Heq" in + destruct (InvocationId_eq_dec a b) as [H|H]; + [rewrite H in *; clear H; try rewrite beq_inv_refl in *; simpl in * | + try rewrite beq_inv_neq in * by assumption; simpl in *]. + +Lemma flow_allowed_to_weak : forall a tool l e, + flow_allowed a tool l e = true -> + flow_allows l e = true \/ flow_inspects l e = true \/ flow_override a tool l = true. +Proof. + intros a0 t l e H. unfold flow_allowed in H. + apply orb_true_iff in H. destruct H as [H|H]. + - apply orb_true_iff in H. destruct H as [H|H]. + + left. exact H. + + apply andb_true_iff in H. right. left. exact (proj1 H). + - right. right. exact H. +Qed. + +Lemma delegate_preserves : forall st st' grantor grantee, + all_invariants st -> + delegate st grantor grantee = Some st' -> + all_invariants st'. +Proof. + intros st st' grantor grantee Hinv Hstep. + unfold delegate in Hstep. + destruct (negb (agent_active st grantor)) eqn:E1; [discriminate|]. + apply negb_false_iff in E1. + destruct (agent_active st grantee) eqn:E2; [discriminate|]. + destruct (beq_agent grantee root_agent) eqn:E3; [discriminate|]. + apply beq_agent_false in E3. + injection Hstep as Heq; subst st'. + assert (Hgne : grantor <> grantee). + { intro Habs; subst; rewrite E1 in E2; discriminate. } + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + assert (Hrne : root_agent <> grantee) by (intro; subst; auto). + assert (Hgne' : grantee <> grantor) by (intro; subst; apply Hgne; reflexivity). + unfold all_invariants; unfold_invariants; simpl. + split_all. + - (* root_always_active *) + rewrite (beq_agent_neq root_agent grantee Hrne). exact Hraa. + - (* default_deny *) + intros a i Hif. + deq a grantee; [discriminate|]. + exact (Hdd a i Hif). + - (* flow_confinement *) + intros a l i e Htaint Hif Hegr. + deq a grantee; [discriminate|]. + exact (Hfc a l i e Htaint Hif Hegr). + - (* flow_confinement_weak *) + intros a l i e Htaint Hif Hegr. + deq a grantee; [discriminate|]. + exact (Hfcw a l i e Htaint Hif Hegr). + - (* capability_subsumption *) + intros c p Hpar Hac Hap cap Hcap. + deq c grantee; [discriminate|]. + deq p grantee; [discriminate|]. + exact (Hcs c p Hpar Hac Hap cap Hcap). + - (* revocation_clean *) + intros a Hinact. + deq a grantee; [discriminate|]. + exact (Hrc a Hinact). + - (* taint_integrity *) + intros a l Htaint Hact. + deq a grantee; [discriminate|]. + exact (Hti a l Htaint Hact). + - (* parent_implies_active *) + intros c p Hpar. + deq c grantee; [reflexivity|]. + deq p grantee; [discriminate|]. + exact (Hpia c p Hpar). + - (* single_parent *) + intros c p1 p2 Hp1 Hp2 Hact. + deq c grantee. + + destruct (beq_agent p1 grantor) eqn:E4; [|simpl in Hp1; discriminate]. + destruct (beq_agent p2 grantor) eqn:E5; [|simpl in Hp2; discriminate]. + apply beq_agent_true in E4. apply beq_agent_true in E5. congruence. + + deq p1 grantee; [discriminate|]. + deq p2 grantee; [discriminate|]. + exact (Hsp c p1 p2 Hp1 Hp2 Hact). + - (* no_self_parent *) + intros a. deq a grantee. + + rewrite (beq_agent_neq grantee grantor Hgne'). simpl. reflexivity. + + exact (Hnsp a). + - (* root_no_parent *) + intros p. + rewrite (beq_agent_neq root_agent grantee Hrne). simpl. + destruct (AgentId_eq_dec p grantee) as [Hpe|Hpe]. + + subst. rewrite beq_agent_refl. reflexivity. + + rewrite (beq_agent_neq p grantee Hpe). exact (Hrnp p). + - (* in_flight_active *) + intros a i Hif. deq a grantee; [discriminate|]. + exact (Hifa a i Hif). + - (* in_flight_registered *) + intros a i Hif. deq a grantee; [discriminate|]. exact (Hifr a i Hif). + - (* in_flight_unique *) + intros a1 a2 i Hif1 Hif2. + deq a1 grantee; [discriminate|]. + deq a2 grantee; [discriminate|]. + exact (Hifu a1 a2 i Hif1 Hif2). + - (* root_all_caps *) + intros c. rewrite (beq_agent_neq root_agent grantee Hrne). exact (Hrac c). + - (* root_no_in_flight *) + intros i. rewrite (beq_agent_neq root_agent grantee Hrne). exact (Hrni i). + - (* ghost_invoked_sound *) + intros a l Hgi. deq a grantee; [discriminate|]. + exact (Hgis a l Hgi). + - (* ghost_received_sound *) + intros a l Hgr. deq a grantee; [discriminate|]. + exact (Hgrs a l Hgr). + - (* in_flight_flow_compat *) + intros a i1 i2 e Hif1 Hif2 Hend Hegr. + deq a grantee; [discriminate|]. + exact (Hiffc a i1 i2 e Hif1 Hif2 Hend Hegr). +Qed. + +Ltac cap_case a child cap0 cap := + destruct (beq_agent a child && beq_cap cap0 cap) eqn:?; simpl; + [try reflexivity | + try match goal with H : _ = true |- _ => idtac end]. + +Lemma grant_capability_preserves : forall st st' prnt child cap, + all_invariants st -> + grant_capability st prnt child cap = Some st' -> + all_invariants st'. +Proof. + intros st st' prnt child cap Hinv Hstep. + unfold grant_capability in Hstep. + destruct (negb (agent_active st prnt)) eqn:E1; [discriminate|]. + apply negb_false_iff in E1. + destruct (negb (agent_active st child)) eqn:E2; [discriminate|]. + apply negb_false_iff in E2. + destruct (negb (agent_parent st child prnt)) eqn:E3; [discriminate|]. + apply negb_false_iff in E3. + destruct (negb (agent_cap st prnt cap)) eqn:E4; [discriminate|]. + apply negb_false_iff in E4. + injection Hstep as Heq; subst st'. + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + unfold all_invariants; unfold_invariants; simpl. + split_all. + - exact Hraa. + - (* default_deny *) + intros a i Hif. destruct (Hdd a i Hif) as [Hauth Hcaps]. + split; [exact Hauth|]. + intros c0 Htc. + destruct (beq_agent a child && beq_cap c0 cap) eqn:Eac; [reflexivity|]. + exact (Hcaps c0 Htc). + - exact Hfc. + - exact Hfcw. + - (* capability_subsumption *) + intros c0 p Hpar Hac Hap cap0 Hcap0. + destruct (AgentId_eq_dec c0 child) as [Hce|Hce]; + [subst; rewrite beq_agent_refl in Hcap0 | + rewrite (beq_agent_neq _ _ Hce) in Hcap0]; simpl in Hcap0. + + destruct (CapKind_eq_dec cap0 cap) as [Hke|Hke]; + [subst; rewrite beq_cap_refl in Hcap0; simpl in Hcap0 | + rewrite (beq_cap_neq _ _ Hke) in Hcap0; simpl in Hcap0]. + * destruct (AgentId_eq_dec p child) as [Hpe|Hpe]; + [subst; rewrite beq_agent_refl; rewrite beq_cap_refl; reflexivity | + rewrite (beq_agent_neq _ _ Hpe); simpl]. + assert (p = prnt) by exact (Hsp child p prnt Hpar E3 Hac). + subst. exact E4. + * destruct (AgentId_eq_dec p child) as [Hpe|Hpe]; + [subst; rewrite beq_agent_refl; rewrite (beq_cap_neq _ _ Hke); simpl | + rewrite (beq_agent_neq _ _ Hpe); simpl]; + exact (Hcs child _ Hpar Hac Hap cap0 Hcap0). + + destruct (AgentId_eq_dec p child) as [Hpe|Hpe]. + * subst. rewrite beq_agent_refl. + destruct (CapKind_eq_dec cap0 cap) as [Hke|Hke]; + [subst; rewrite beq_cap_refl; reflexivity | + rewrite (beq_cap_neq _ _ Hke); simpl; + exact (Hcs c0 child Hpar Hac Hap cap0 Hcap0)]. + * rewrite (beq_agent_neq _ _ Hpe). simpl. + exact (Hcs c0 p Hpar Hac Hap cap0 Hcap0). + - exact Hrc. + - exact Hti. + - exact Hpia. + - exact Hsp. + - exact Hnsp. + - exact Hrnp. + - exact Hifa. + - exact Hifr. + - exact Hifu. + - (* root_all_caps *) + intros c0. destruct (beq_agent root_agent child && beq_cap c0 cap) eqn:Erc; + [reflexivity | exact (Hrac c0)]. + - exact Hrni. + - exact Hgis. + - exact Hgrs. + - exact Hiffc. +Qed. + +Lemma revoke_preserves : forall st st' prnt target, + all_invariants st -> + revoke st prnt target = Some st' -> + all_invariants st'. +Proof. + intros st st' prnt target Hinv Hstep. + unfold revoke in Hstep. + destruct (negb (agent_parent st target prnt)) eqn:E1; [discriminate|]. + apply negb_false_iff in E1. + destruct (negb (agent_active st prnt)) eqn:E2; [discriminate|]. + apply negb_false_iff in E2. + destruct (negb (agent_active st target)) eqn:E3; [discriminate|]. + apply negb_false_iff in E3. + destruct (beq_agent target root_agent) eqn:E4; [discriminate|]. + apply beq_agent_false in E4. + injection Hstep as Heq; subst st'. + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + assert (Hrne : root_agent <> target) by (intro; subst; auto). + unfold all_invariants; unfold_invariants; simpl. + split_all. + - rewrite (beq_agent_neq root_agent target Hrne). exact Hraa. + - intros a i Hif. deq a target; [discriminate|]. exact (Hdd a i Hif). + - intros a l i e Ht Hif He. + deq a target; [discriminate|]. exact (Hfc a l i e Ht Hif He). + - intros a l i e Ht Hif He. + deq a target; [discriminate|]. exact (Hfcw a l i e Ht Hif He). + - intros c p Hpar Hac Hap cap Hcap. + deq c target; [discriminate|]. + deq p target; [discriminate|]. + exact (Hcs c p Hpar Hac Hap cap Hcap). + - intros a Hinact. deq a target. + + split; intros; reflexivity. + + exact (Hrc a Hinact). + - intros a l Ht Hact. + deq a target; [discriminate|]. exact (Hti a l Ht Hact). + - intros c p Hpar. + deq c target; [discriminate|]. exact (Hpia c p Hpar). + - intros c p1 p2 Hp1 Hp2 Hact. + deq c target; [discriminate|]. exact (Hsp c p1 p2 Hp1 Hp2 Hact). + - intros a. deq a target; [reflexivity|]. exact (Hnsp a). + - intros p. rewrite (beq_agent_neq root_agent target Hrne). exact (Hrnp p). + - intros a i Hif. deq a target; [discriminate|]. exact (Hifa a i Hif). + - intros a i Hif. deq a target; [discriminate|]. exact (Hifr a i Hif). + - intros a1 a2 i Hif1 Hif2. + deq a1 target; [discriminate|]. + deq a2 target; [discriminate|]. + exact (Hifu a1 a2 i Hif1 Hif2). + - intros c. rewrite (beq_agent_neq root_agent target Hrne). exact (Hrac c). + - intros i. rewrite (beq_agent_neq root_agent target Hrne). exact (Hrni i). + - intros a l Hgi. deq a target; [discriminate|]. exact (Hgis a l Hgi). + - intros a l Hgr. deq a target; [discriminate|]. exact (Hgrs a l Hgr). + - intros a i1 i2 e Hif1 Hif2 Hend Hegr. + deq a target; [discriminate|]. exact (Hiffc a i1 i2 e Hif1 Hif2 Hend Hegr). +Qed. + +Lemma cascade_revoke_preserves : forall st st' child prnt, + all_invariants st -> + cascade_revoke st child prnt = Some st' -> + all_invariants st'. +Proof. + intros st st' child prnt Hinv Hstep. + unfold cascade_revoke in Hstep. + destruct (negb (agent_parent st child prnt)) eqn:E1; [discriminate|]. + apply negb_false_iff in E1. + destruct (agent_active st prnt) eqn:E2; [discriminate|]. + destruct (negb (agent_active st child)) eqn:E3; [discriminate|]. + apply negb_false_iff in E3. + destruct (beq_agent child root_agent) eqn:E4; [discriminate|]. + apply beq_agent_false in E4. + injection Hstep as Heq; subst st'. + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + assert (Hrne : root_agent <> child) by (intro; subst; auto). + unfold all_invariants; unfold_invariants; simpl. + split_all. + - rewrite (beq_agent_neq root_agent child Hrne). exact Hraa. + - intros a i Hif. deq a child; [discriminate|]. exact (Hdd a i Hif). + - intros a l i e Ht Hif He. + deq a child; [discriminate|]. exact (Hfc a l i e Ht Hif He). + - intros a l i e Ht Hif He. + deq a child; [discriminate|]. exact (Hfcw a l i e Ht Hif He). + - intros c p Hpar Hac Hap cap Hcap. + deq c child; [discriminate|]. + deq p child; [discriminate|]. + exact (Hcs c p Hpar Hac Hap cap Hcap). + - intros a Hinact. deq a child. + + split; intros; reflexivity. + + exact (Hrc a Hinact). + - intros a l Ht Hact. + deq a child; [discriminate|]. exact (Hti a l Ht Hact). + - intros c p Hpar. + deq c child; [discriminate|]. exact (Hpia c p Hpar). + - intros c p1 p2 Hp1 Hp2 Hact. + deq c child; [discriminate|]. exact (Hsp c p1 p2 Hp1 Hp2 Hact). + - intros a. deq a child; [reflexivity|]. exact (Hnsp a). + - intros p. rewrite (beq_agent_neq root_agent child Hrne). exact (Hrnp p). + - intros a i Hif. deq a child; [discriminate|]. exact (Hifa a i Hif). + - intros a i Hif. deq a child; [discriminate|]. exact (Hifr a i Hif). + - intros a1 a2 i Hif1 Hif2. + deq a1 child; [discriminate|]. + deq a2 child; [discriminate|]. + exact (Hifu a1 a2 i Hif1 Hif2). + - intros c. rewrite (beq_agent_neq root_agent child Hrne). exact (Hrac c). + - intros i. rewrite (beq_agent_neq root_agent child Hrne). exact (Hrni i). + - intros a l Hgi. deq a child; [discriminate|]. exact (Hgis a l Hgi). + - intros a l Hgr. deq a child; [discriminate|]. exact (Hgrs a l Hgr). + - intros a i1 i2 e Hif1 Hif2 Hend Hegr. + deq a child; [discriminate|]. exact (Hiffc a i1 i2 e Hif1 Hif2 Hend Hegr). +Qed. + +Lemma invoke_start_preserves : forall st a tool inv, + all_invariants st -> + invoke_start_pre st a tool inv -> + all_invariants (invoke_start_state st a inv). +Proof. + intros st a tool inv Hinv Hpre. + destruct Hpre as (Hact & Hnroot & Hreg & Htool & Hfresh & + Hcap_gate & Hspec_flow & Hrev_flow & Hself_flow & Hauth). + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + unfold all_invariants; unfold invoke_start_state; unfold_invariants; simpl. + split_all. + - exact Hraa. + - intros a0 i0 Hif0. + deq a0 a. + + deq_inv i0 inv. + * rewrite Htool in *. + split; [exact Hauth | intros c0 Htc; exact (Hcap_gate c0 Htc)]. + * exact (Hdd a i0 Hif0). + + exact (Hdd a0 i0 Hif0). + - intros a0 l i0 e Htaint Hif0 Hegr. + deq a0 a. + + deq_inv i0 inv. + * rewrite Htool in *. + apply Hspec_flow; [left; exact Htaint | exact Hegr]. + * exact (Hfc a l i0 e Htaint Hif0 Hegr). + + exact (Hfc a0 l i0 e Htaint Hif0 Hegr). + - intros a0 l i0 e Htaint Hif0 Hegr. + deq a0 a. + + deq_inv i0 inv. + * rewrite Htool in *. + apply flow_allowed_to_weak. + apply Hspec_flow; [left; exact Htaint | exact Hegr]. + * exact (Hfcw a l i0 e Htaint Hif0 Hegr). + + exact (Hfcw a0 l i0 e Htaint Hif0 Hegr). + - exact Hcs. + - intros a0 Hinact. deq a0 a; [rewrite Hact in Hinact; discriminate|]. + exact (Hrc a0 Hinact). + - exact Hti. + - exact Hpia. + - exact Hsp. + - exact Hnsp. + - exact Hrnp. + - intros a0 i0 Hif0. deq a0 a. + + deq_inv i0 inv; [exact Hact | exact (Hifa a i0 Hif0)]. + + exact (Hifa a0 i0 Hif0). + - intros a0 i0 Hif0. deq a0 a. + + deq_inv i0 inv; [rewrite Htool; exact Hreg | exact (Hifr a i0 Hif0)]. + + exact (Hifr a0 i0 Hif0). + - intros a1 a2 i0 Hif1 Hif2. + deq a1 a. + + deq a2 a; [reflexivity|]. + deq_inv i0 inv. + * rewrite (Hfresh a2) in Hif2; discriminate. + * exact (Hifu a a2 i0 Hif1 Hif2). + + deq a2 a. + * deq_inv i0 inv. + -- rewrite (Hfresh a1) in Hif1; discriminate. + -- exact (Hifu a1 a i0 Hif1 Hif2). + * exact (Hifu a1 a2 i0 Hif1 Hif2). + - exact Hrac. + - intros i0. + assert (Hne : root_agent <> a) by (intro; subst; auto). + rewrite (beq_agent_neq root_agent a Hne). simpl. exact (Hrni i0). + - exact Hgis. + - exact Hgrs. + - intros a0 i1 i2 e Hif1 Hif2 Hend Hegr. + deq a0 a. + + deq_inv i1 inv. + * deq_inv i2 inv. + -- rewrite Htool in *. exact (Hself_flow e Hend Hegr). + -- rewrite Htool in *. exact (Hrev_flow i2 e Hif2 Hegr Hend). + * deq_inv i2 inv. + -- rewrite Htool in *. + apply Hspec_flow; [| exact Hegr]. + right. exists i1. exact (conj Hif1 (conj eq_refl Hend)). + -- exact (Hiffc a i1 i2 e Hif1 Hif2 Hend Hegr). + + exact (Hiffc a0 i1 i2 e Hif1 Hif2 Hend Hegr). +Qed. + +Lemma invoke_complete_preserves : forall st st' a inv, + all_invariants st -> + invoke_complete st a inv = Some st' -> + all_invariants st'. +Proof. + intros st st' a inv Hinv Hstep. + unfold invoke_complete in Hstep. + destruct (negb (in_flight st a inv)) eqn:E1; [discriminate|]. + apply negb_false_iff in E1. + destruct (negb (agent_active st a)) eqn:E2; [discriminate|]. + apply negb_false_iff in E2. + injection Hstep as Heq; subst st'. + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + assert (Hne : a <> root_agent). + { intro Habs; subst. rewrite (Hrni inv) in E1. discriminate. } + unfold all_invariants; unfold_invariants; simpl. + split_all. + - exact Hraa. + - intros a0 i0 Hif0. deq a0 a. + + deq_inv i0 inv; [discriminate|]. exact (Hdd a i0 Hif0). + + exact (Hdd a0 i0 Hif0). + - intros a0 l i0 e Htaint Hif0 Hegr. deq a0 a. + + deq_inv i0 inv; [discriminate|]. + destruct (negb (tool_endorsed (invocation_tool inv)) && + beq_conf l (tool_conf_floor (invocation_tool inv))) eqn:Econd. + * apply andb_true_iff in Econd. destruct Econd as [Hne_tool Hlf]. + apply negb_true_iff in Hne_tool. apply beq_conf_true in Hlf. subst l. + exact (Hiffc a inv i0 e E1 Hif0 Hne_tool Hegr). + * exact (Hfc a l i0 e Htaint Hif0 Hegr). + + exact (Hfc a0 l i0 e Htaint Hif0 Hegr). + - intros a0 l i0 e Htaint Hif0 Hegr. deq a0 a. + + deq_inv i0 inv; [discriminate|]. + destruct (negb (tool_endorsed (invocation_tool inv)) && + beq_conf l (tool_conf_floor (invocation_tool inv))) eqn:Econd. + * apply andb_true_iff in Econd. destruct Econd as [Hne_tool Hlf]. + apply negb_true_iff in Hne_tool. apply beq_conf_true in Hlf. subst l. + apply flow_allowed_to_weak. + exact (Hiffc a inv i0 e E1 Hif0 Hne_tool Hegr). + * exact (Hfcw a l i0 e Htaint Hif0 Hegr). + + exact (Hfcw a0 l i0 e Htaint Hif0 Hegr). + - exact Hcs. + - intros a0 Hinact. deq a0 a; [rewrite E2 in Hinact; discriminate|]. + exact (Hrc a0 Hinact). + - intros a0 l Htaint Hact0. deq a0 a. + + destruct (negb (tool_endorsed (invocation_tool inv)) && + beq_conf l (tool_conf_floor (invocation_tool inv))) eqn:Econd. + * left. reflexivity. + * exact (Hti a l Htaint Hact0). + + exact (Hti a0 l Htaint Hact0). + - exact Hpia. + - exact Hsp. + - exact Hnsp. + - exact Hrnp. + - intros a0 i0 Hif0. deq a0 a. + + deq_inv i0 inv; [discriminate|]. exact (Hifa a i0 Hif0). + + exact (Hifa a0 i0 Hif0). + - intros a0 i0 Hif0. deq a0 a. + + deq_inv i0 inv; [discriminate|]. exact (Hifr a i0 Hif0). + + exact (Hifr a0 i0 Hif0). + - intros a1 a2 i0 Hif1 Hif2. deq a1 a. + + deq_inv i0 inv; [discriminate|]. + deq a2 a; [reflexivity|]. + exact (Hifu a a2 i0 Hif1 Hif2). + + deq a2 a. + * deq_inv i0 inv; [discriminate|]. + exact (Hifu a1 a i0 Hif1 Hif2). + * exact (Hifu a1 a2 i0 Hif1 Hif2). + - exact Hrac. + - intros i0. + assert (Hne2 : root_agent <> a) by (intro; subst; auto). + rewrite (beq_agent_neq root_agent a Hne2). simpl. exact (Hrni i0). + - intros a0 l Hgi. deq a0 a. + + destruct (negb (tool_endorsed (invocation_tool inv)) && + beq_conf l (tool_conf_floor (invocation_tool inv))) eqn:Econd. + * left. reflexivity. + * exact (Hgis a l Hgi). + + exact (Hgis a0 l Hgi). + - intros a0 l Hgr. deq a0 a. + + destruct (Hgrs a l Hgr) as [Ht|Ha]. + * left. + destruct (negb (tool_endorsed (invocation_tool inv)) && + beq_conf l (tool_conf_floor (invocation_tool inv))); + [reflexivity | exact Ht]. + * rewrite E2 in Ha. discriminate. + + exact (Hgrs a0 l Hgr). + - intros a0 i1 i2 e Hif1 Hif2 Hend Hegr. deq a0 a. + + deq_inv i1 inv; [discriminate|]. + deq_inv i2 inv; [discriminate|]. + exact (Hiffc a i1 i2 e Hif1 Hif2 Hend Hegr). + + exact (Hiffc a0 i1 i2 e Hif1 Hif2 Hend Hegr). +Qed. + +Lemma return_endorsed_preserves : forall st child prnt, + all_invariants st -> + return_endorsed_pre st child prnt -> + all_invariants st. +Proof. + intros st child prnt Hinv _. exact Hinv. +Qed. + +Lemma return_unendorsed_preserves : forall st child prnt, + all_invariants st -> + return_unendorsed_pre st child prnt -> + all_invariants (return_unendorsed_state st child prnt). +Proof. + intros st child prnt Hinv Hpre. + destruct Hpre as (Hpar & Hact_child & Hact_prnt & Hno_flight & Hflow_gate). + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + unfold all_invariants; unfold return_unendorsed_state; unfold_invariants; simpl. + split_all. + - exact Hraa. + - exact Hdd. + - intros a l i0 e Htaint Hif0 Hegr. deq a prnt. + + destruct (taint_levels st child l) eqn:Echild. + * exact (Hflow_gate l i0 e Echild Hif0 Hegr). + * simpl in Htaint. exact (Hfc prnt l i0 e Htaint Hif0 Hegr). + + exact (Hfc a l i0 e Htaint Hif0 Hegr). + - intros a l i0 e Htaint Hif0 Hegr. deq a prnt. + + destruct (taint_levels st child l) eqn:Echild. + * apply flow_allowed_to_weak. + exact (Hflow_gate l i0 e Echild Hif0 Hegr). + * simpl in Htaint. exact (Hfcw prnt l i0 e Htaint Hif0 Hegr). + + exact (Hfcw a l i0 e Htaint Hif0 Hegr). + - exact Hcs. + - intros a Hinact. deq a prnt; [rewrite Hact_prnt in Hinact; discriminate|]. + exact (Hrc a Hinact). + - intros a l Htaint Hact0. deq a prnt. + + destruct (taint_levels st child l) eqn:Echild. + * right. reflexivity. + * simpl in Htaint. exact (Hti prnt l Htaint Hact0). + + exact (Hti a l Htaint Hact0). + - exact Hpia. + - exact Hsp. + - exact Hnsp. + - exact Hrnp. + - exact Hifa. + - exact Hifr. + - exact Hifu. + - exact Hrac. + - exact Hrni. + - intros a l Hgi. + destruct (Hgis a l Hgi) as [Ht|Ha]. + + left. destruct (beq_agent a prnt && taint_levels st child l); + [reflexivity | exact Ht]. + + right. exact Ha. + - intros a l Hgr. deq a prnt. + + destruct (taint_levels st child l) eqn:Echild. + * left. reflexivity. + * simpl in Hgr. exact (Hgrs prnt l Hgr). + + exact (Hgrs a l Hgr). + - exact Hiffc. +Qed. + +Ltac deq_conf a b := + let H := fresh "Heq" in + destruct (ConfLevel_eq_dec a b) as [H|H]; + [rewrite H in *; clear H; try rewrite beq_conf_refl in *; simpl in * | + try rewrite beq_conf_neq in * by assumption; simpl in *]. + +Lemma sentinel_elevate_taint_preserves : forall st a l, + all_invariants st -> + sentinel_elevate_taint_pre st a l -> + all_invariants (sentinel_elevate_taint_state st a l). +Proof. + intros st a l Hinv Hpre. + destruct Hpre as (Hact & Hflow_gate). + destruct Hinv as (Hraa & Hdd & Hfc & Hfcw & Hcs & Hrc & Hti & + Hpia & Hsp & Hnsp & Hrnp & Hifa & Hifr & Hifu & + Hrac & Hrni & Hgis & Hgrs & Hiffc). + unfold all_invariants; unfold sentinel_elevate_taint_state; unfold_invariants; simpl. + split_all. + - exact Hraa. + - exact Hdd. + - intros a0 l0 i0 e Htaint Hif0 Hegr. deq a0 a. + + deq_conf l0 l. + * exact (Hflow_gate i0 e Hif0 Hegr). + * exact (Hfc a l0 i0 e Htaint Hif0 Hegr). + + exact (Hfc a0 l0 i0 e Htaint Hif0 Hegr). + - intros a0 l0 i0 e Htaint Hif0 Hegr. deq a0 a. + + deq_conf l0 l. + * apply flow_allowed_to_weak. exact (Hflow_gate i0 e Hif0 Hegr). + * exact (Hfcw a l0 i0 e Htaint Hif0 Hegr). + + exact (Hfcw a0 l0 i0 e Htaint Hif0 Hegr). + - exact Hcs. + - intros a0 Hinact. deq a0 a; [rewrite Hact in Hinact; discriminate|]. + exact (Hrc a0 Hinact). + - intros a0 l0 Htaint Hact0. deq a0 a. + + deq_conf l0 l. + * left. reflexivity. + * exact (Hti a l0 Htaint Hact0). + + exact (Hti a0 l0 Htaint Hact0). + - exact Hpia. + - exact Hsp. + - exact Hnsp. + - exact Hrnp. + - exact Hifa. + - exact Hifr. + - exact Hifu. + - exact Hrac. + - exact Hrni. + - intros a0 l0 Hgi. deq a0 a. + + deq_conf l0 l. + * left. reflexivity. + * exact (Hgis a l0 Hgi). + + exact (Hgis a0 l0 Hgi). + - intros a0 l0 Hgr. + destruct (Hgrs a0 l0 Hgr) as [Ht|Ha]. + + left. destruct (beq_agent a0 a && beq_conf l0 l); + [reflexivity | exact Ht]. + + right. exact Ha. + - exact Hiffc. +Qed. + +(** ================================================================ *) +(** Top-Level Inductive Theorem *) +(** ================================================================ *) + +Theorem invariants_inductive : forall st, + reachable st -> all_invariants st. +Proof. + intros st Hreach. induction Hreach as [| st st' _ IH Hstep]. + - exact initial_all_invariants. + - inversion Hstep; subst; + eauto using register_tool_preserves, delegate_preserves, + grant_capability_preserves, revoke_preserves, + cascade_revoke_preserves, invoke_start_preserves, + invoke_complete_preserves, return_endorsed_preserves, + return_unendorsed_preserves, sentinel_elevate_taint_preserves. +Qed. + +(** ================================================================ *) +(** Individual Safety Extractions *) +(** ================================================================ *) + +Ltac extract_invariant := + match goal with + | H : all_invariants _ |- _ => + unfold all_invariants in H; decompose [and] H; assumption + end. + +Theorem safety_root_always_active : forall st, + reachable st -> root_always_active st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_default_deny : forall st, + reachable st -> default_deny st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_flow_confinement : forall st, + reachable st -> flow_confinement st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_flow_confinement_weak : forall st, + reachable st -> flow_confinement_weak st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_capability_subsumption : forall st, + reachable st -> capability_subsumption st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_revocation_clean : forall st, + reachable st -> revocation_clean st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. + +Theorem safety_taint_integrity : forall st, + reachable st -> taint_integrity st. +Proof. intros st H. pose proof (invariants_inductive st H). extract_invariant. Qed. diff --git a/argus/formal/spec/TzimtzumV2_safety.v b/argus/formal/spec/TzimtzumV2_safety.v new file mode 100644 index 0000000..313063b --- /dev/null +++ b/argus/formal/spec/TzimtzumV2_safety.v @@ -0,0 +1,215 @@ +(** TzimtzumV2.3 Safety Properties and Strengthening Invariants + Ported from tzimtzum/TzimtzumV2.lean lines 514-645. + + Defines: + - Step relation (one-step state transition via any of 9 actions) + - Reachability (transitive closure from initial_state) + - 7 safety properties + - 12 strengthening invariants + - Combined invariant bundle *) + +From Stdlib Require Import Bool. +Require Import ArgusKernel.axioms.DecidableAxioms. +Require Import ArgusKernel.axioms.OracleAxioms. +Require Import ArgusKernel.spec.TzimtzumV2. + +(** ================================================================ *) +(** Step Relation *) +(** ================================================================ *) + +Inductive step : KernelState -> KernelState -> Prop := + | step_register_tool : forall st st' tool, + register_tool st tool = Some st' -> step st st' + | step_delegate : forall st st' grantor grantee, + delegate st grantor grantee = Some st' -> step st st' + | step_grant_capability : forall st st' prnt child cap, + grant_capability st prnt child cap = Some st' -> step st st' + | step_revoke : forall st st' prnt target, + revoke st prnt target = Some st' -> step st st' + | step_cascade_revoke : forall st st' child prnt, + cascade_revoke st child prnt = Some st' -> step st st' + | step_invoke_start : forall st a tool inv, + invoke_start_pre st a tool inv -> + step st (invoke_start_state st a inv) + | step_invoke_complete : forall st st' a inv, + invoke_complete st a inv = Some st' -> step st st' + | step_return_endorsed : forall st child prnt, + return_endorsed_pre st child prnt -> step st st + | step_return_unendorsed : forall st child prnt, + return_unendorsed_pre st child prnt -> + step st (return_unendorsed_state st child prnt) + | step_sentinel_elevate_taint : forall st a l, + sentinel_elevate_taint_pre st a l -> + step st (sentinel_elevate_taint_state st a l). + +Inductive reachable : KernelState -> Prop := + | reach_init : reachable initial_state + | reach_step : forall st st', + reachable st -> step st st' -> reachable st'. + +(** ================================================================ *) +(** Safety Properties (Lean lines 527-596) *) +(** ================================================================ *) + +(** Safety 1: Root agent can never be deactivated. + Trivially preserved: revoke and cascade_revoke require target != root. *) +Definition root_always_active (st : KernelState) : Prop := + agent_active st root_agent = true. + +(** Safety 2: In-flight invocations were explicitly authorized. + Authorizer allowed (agent, tool) AND agent holds all required capabilities. *) +Definition default_deny (st : KernelState) : Prop := + forall a i, + in_flight st a i = true -> + authorizer_allows a (invocation_tool i) = true + /\ (forall c, tool_cap (invocation_tool i) c = true -> + agent_cap st a c = true). + +(** Safety 3: Tainted agent with in-flight egress tool implies flow policy permits. + The Lethal Trifecta defense. *) +Definition flow_confinement (st : KernelState) : Prop := + forall a l i e, + taint_levels st a l = true -> + in_flight st a i = true -> + tool_egress (invocation_tool i) e = true -> + flow_allowed a (invocation_tool i) l e = true. + +(** Safety 4: Oracle-independent flow safety. + DENY-mode pairs are structurally blocked regardless of oracle behavior. *) +Definition flow_confinement_weak (st : KernelState) : Prop := + forall a l i e, + taint_levels st a l = true -> + in_flight st a i = true -> + tool_egress (invocation_tool i) e = true -> + flow_allows l e = true + \/ flow_inspects l e = true + \/ flow_override a (invocation_tool i) l = true. + +(** Safety 5: Child capabilities are a subset of parent capabilities. + Capabilities only flow downward in the delegation tree. *) +Definition capability_subsumption (st : KernelState) : Prop := + forall c p, + agent_parent st c p = true -> + agent_active st c = true -> + agent_active st p = true -> + forall cap, agent_cap st c cap = true -> agent_cap st p cap = true. + +(** Safety 6: Inactive agents leave no residue. + No zombie state after revocation. *) +Definition revocation_clean (st : KernelState) : Prop := + forall a, + agent_active st a = false -> + (forall i, in_flight st a i = false) + /\ (forall l, taint_levels st a l = false). + +(** Safety 7: Taint is traceable to either invocation or child return. + No phantom taint. *) +Definition taint_integrity (st : KernelState) : Prop := + forall a l, + taint_levels st a l = true -> + agent_active st a = true -> + gh_taint_invoked st a l = true \/ gh_taint_received st a l = true. + +(** ================================================================ *) +(** Strengthening Invariants (Lean lines 609-645) *) +(** ================================================================ *) + +(** Tree well-formedness *) + +Definition parent_implies_active (st : KernelState) : Prop := + forall c p, agent_parent st c p = true -> agent_active st c = true. + +Definition single_parent (st : KernelState) : Prop := + forall c p1 p2, + agent_parent st c p1 = true -> + agent_parent st c p2 = true -> + agent_active st c = true -> + p1 = p2. + +Definition no_self_parent (st : KernelState) : Prop := + forall a, agent_parent st a a = false. + +Definition root_no_parent (st : KernelState) : Prop := + forall p, agent_parent st root_agent p = false. + +(** In-flight well-formedness *) + +Definition in_flight_active (st : KernelState) : Prop := + forall a i, in_flight st a i = true -> agent_active st a = true. + +Definition in_flight_registered (st : KernelState) : Prop := + forall a i, + in_flight st a i = true -> + tool_registered st (invocation_tool i) = true. + +Definition in_flight_unique (st : KernelState) : Prop := + forall a1 a2 i, + in_flight st a1 i = true -> + in_flight st a2 i = true -> + a1 = a2. + +(** Root properties *) + +Definition root_all_caps (st : KernelState) : Prop := + forall c, agent_cap st root_agent c = true. + +Definition root_no_in_flight (st : KernelState) : Prop := + forall i, in_flight st root_agent i = false. + +(** Ghost soundness: ghost relations stay in sync with taint_levels *) + +Definition ghost_invoked_sound (st : KernelState) : Prop := + forall a l, + gh_taint_invoked st a l = true -> + taint_levels st a l = true \/ agent_active st a = false. + +Definition ghost_received_sound (st : KernelState) : Prop := + forall a l, + gh_taint_received st a l = true -> + taint_levels st a l = true \/ agent_active st a = false. + +(** In-flight flow compatibility: concurrent invocations are mutually compatible. + If tool I1 is non-endorsed (will produce taint at its conf_floor), and tool I2 + has egress, the policy must permit (conf_floor(I1), egress(I2)). *) + +Definition in_flight_flow_compat (st : KernelState) : Prop := + forall a i1 i2 e, + in_flight st a i1 = true -> + in_flight st a i2 = true -> + tool_endorsed (invocation_tool i1) = false -> + tool_egress (invocation_tool i2) e = true -> + flow_allowed a (invocation_tool i2) + (tool_conf_floor (invocation_tool i1)) e = true. + +(** ================================================================ *) +(** Combined Invariant Bundle *) +(** ================================================================ *) + +Definition all_invariants (st : KernelState) : Prop := + root_always_active st + /\ default_deny st + /\ flow_confinement st + /\ flow_confinement_weak st + /\ capability_subsumption st + /\ revocation_clean st + /\ taint_integrity st + /\ parent_implies_active st + /\ single_parent st + /\ no_self_parent st + /\ root_no_parent st + /\ in_flight_active st + /\ in_flight_registered st + /\ in_flight_unique st + /\ root_all_caps st + /\ root_no_in_flight st + /\ ghost_invoked_sound st + /\ ghost_received_sound st + /\ in_flight_flow_compat st. + +(** ================================================================ *) +(** Top-Level Safety Theorem Statements *) +(** ================================================================ *) + +(** The invariants_inductive theorem and individual safety extractions + are proved in TzimtzumV2_proofs.v after all per-action preservation + lemmas are established. *) diff --git a/argus/scripts/extract-and-verify.sh b/argus/scripts/extract-and-verify.sh new file mode 100755 index 0000000..3d6cc2c --- /dev/null +++ b/argus/scripts/extract-and-verify.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +KERNEL_DIR="$REPO_ROOT/crates/argus-kernel" +KERNEL_SRC="$KERNEL_DIR/src" +EXTRACTED_DIR="$REPO_ROOT/formal/extracted" +FORMAL_DIR="$REPO_ROOT/formal" +NIGHTLY="nightly-2024-12-07" + +EXPECTED_MODULES=( + background capability error event kernel state traits transitions types +) + +step() { printf '\n==> %s\n' "$1"; } + +step "Extracting Rocq from argus-kernel (cargo +$NIGHTLY rocq-of-rust)" +cd "$KERNEL_DIR" +# Extraction succeeds despite edition 2024 let-chain warnings. +# rocq-of-rust exits non-zero due to those warnings, so we don't fail on it. +cargo "+$NIGHTLY" rocq-of-rust 2>&1 || true + +missing=() +for mod in "${EXPECTED_MODULES[@]}"; do + if [[ ! -f "$KERNEL_SRC/$mod.v" ]]; then + missing+=("$mod.v") + fi +done + +if [[ ${#missing[@]} -gt 0 ]]; then + echo "ERROR: extraction failed -- missing files: ${missing[*]}" + exit 1 +fi +echo "Extracted ${#EXPECTED_MODULES[@]} modules." + +step "Moving extracted files to $EXTRACTED_DIR" +mkdir -p "$EXTRACTED_DIR" +for mod in "${EXPECTED_MODULES[@]}"; do + mv -f "$KERNEL_SRC/$mod.v" "$EXTRACTED_DIR/$mod.v" +done +echo "Moved ${#EXPECTED_MODULES[@]} files." + +step "Building formal proofs (Rocq)" +cd "$FORMAL_DIR" +eval "$(opam env --switch=rocq-of-rust 2>/dev/null)" || true +make clean 2>/dev/null || true +make + +step "All done" +echo "Extraction: ${#EXPECTED_MODULES[@]} modules in $EXTRACTED_DIR" +echo "Proofs: $(grep -c '\.v' _CoqProject) files compiled successfully" diff --git a/tzimtzum/Makefile b/tzimtzum/Makefile new file mode 100644 index 0000000..9756075 --- /dev/null +++ b/tzimtzum/Makefile @@ -0,0 +1,15 @@ +LEAN_NUM_THREADS ?= 12 + +.PHONY: build verify clean report + +build: + LEAN_NUM_THREADS=$(LEAN_NUM_THREADS) lake build TzimtzumV2 --old 2>&1 | tee build.log + +verify: + LEAN_NUM_THREADS=$(LEAN_NUM_THREADS) lake build TzimtzumV2 2>&1 | tee build.log + +report: build + @python3 verify_report.py < build.log | tee verification-report.md + +clean: + lake clean diff --git a/tzimtzum/README.md b/tzimtzum/README.md new file mode 100644 index 0000000..f9afc1d --- /dev/null +++ b/tzimtzum/README.md @@ -0,0 +1,331 @@ +# Tzimtzum v2.3 + +A formally verified security kernel for LLM tool authorization. +Part of the [Argus](../argus/) Tool Authorization Gateway. + +Tzimtzum is a protocol specification -- written in [Lean 4](https://lean-lang.org/) using +the [Veil](https://github.com/verse-lab/veil) verification framework -- that defines how +LLM agents can safely invoke tools without leaking private data through indirect prompt +injection attacks. All 7 safety properties and 12 strengthening invariants are verified +automatically via push-button SMT (cvc5). No manual proofs required. + +## The Problem + +The **Lethal Trifecta** ([Simon Willison, 2025](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/)): + +> **Private data + Untrusted content + External communication = Exfiltration** + +If all three legs are present, indirect prompt injection can always extract data. The only +structural defense is to remove one leg. Tzimtzum removes the third -- it enforces +authorization at tool boundaries so that a confused (prompt-injected) agent cannot reach +egress channels when it carries taint from private data. + +```mermaid +graph LR + A["Private Data
(confidential tool results)"] --- T["Lethal
Trifecta"] + B["Untrusted Content
(prompt injection)"] --- T + C["External Communication
(egress tools)"] --- T + T --> X["Data Exfiltration"] + + style C stroke:#e74c3c,stroke-width:3px + style T fill:#e74c3c,color:#fff + style X fill:#e74c3c,color:#fff +``` + +Tzimtzum blocks the connection between tainted agents and egress channels via a graduated +flow policy (ALLOW / INSPECT / DENY), enforced deterministically at every tool invocation. + +## Architecture + +Agents form a tree rooted at a single `root_agent`. Each agent carries: + +- **Capabilities** -- typed permissions flowing strictly downward (parent to child, never upward) +- **Taint set** -- confidentiality levels the agent has been exposed to (grows monotonically, max 4 values) +- **In-flight set** -- currently executing tool invocations (for speculative taint) + +Tools are registered with immutable, static labels: required capabilities, egress kinds, +confidentiality floor, and endorsement status. Labels never change after registration. + +```mermaid +graph TD + ROOT["root_agent
holds all capabilities
orchestrates, never invokes"] + + ROOT --> A1["Agent A
caps: {fs_read, net_write}
taint: {internal}"] + ROOT --> A2["Agent B
caps: {fs_read}
taint: {}"] + + A1 --> A3["Agent C
caps: {fs_read}
taint: {}"] + + A1 -. "invoke" .-> T1["send-email
egress: network_external
conf: internal"] + A2 -. "invoke" .-> T2["read-file
egress: {}
conf: sensitive"] + A3 -. "invoke" .-> T3["file-exists
endorsed: true
conf: sensitive"] + + style ROOT fill:#2ecc71,color:#fff + style T1 fill:#e74c3c,color:#fff + style T2 fill:#3498db,color:#fff + style T3 fill:#9b59b6,color:#fff +``` + +### The Three-Check Invoke Gate + +Every tool invocation passes through three independent checks. If any fails, the +invocation is denied (default-deny). + +```mermaid +flowchart LR + REQ["invoke_start
(agent, tool)"] --> C1 + + subgraph "Gate" + direction LR + C1{"Capability
Gate"} -->|pass| C2{"Flow
Gate"} + C2 -->|pass| C3{"Authorizer
Gate"} + end + + C3 -->|pass| OK["In-flight
(tool executes)"] + C1 -->|fail| DENY["DENIED"] + C2 -->|fail| DENY + C3 -->|fail| DENY + + style DENY fill:#e74c3c,color:#fff + style OK fill:#2ecc71,color:#fff +``` + +**Check 1 -- Capability gate.** The agent holds every capability the tool requires. +Simple set containment. + +**Check 2 -- Flow gate.** For each (taint_level, egress_kind) pair, the flow policy +must permit it: + +| Mode | Meaning | +|------|---------| +| **ALLOW** | No restriction. Data at this level can reach this egress freely. | +| **INSPECT** | Permitted only if a content gate certifies the arguments are safe. | +| **DENY** | Hard block. No invocation possible. **(Default for all pairs.)** | + +The flow gate checks **speculative taint** -- the worst-case taint including all in-flight +non-endorsed tools. This eliminates TOCTOU races and enables safe parallel tool execution. + +**Check 3 -- Authorizer gate.** An external policy engine (e.g. Cedar) authorizes the +specific (agent, tool) pair. Parameter-level conditions (regex on arguments, recipient +allowlists) live here. + +### Speculative Taint + +The key mechanism enabling parallel execution. When the flow gate runs, it checks not just +the agent's current taint but also the potential taint from all tools currently executing: + +``` +speculative_taint(A, L) = + taint_levels(A, L) + OR (exists I, in_flight(A, I) + AND tool_conf_floor(invocation_tool(I)) = L + AND NOT tool_endorsed(invocation_tool(I))) +``` + +This guarantees flow confinement holds regardless of the order in which concurrent tools +complete. The conservatism only affects the dangerous case -- attempting egress while +taint-producing tools are in-flight -- which is exactly the race being prevented. + +### Endorsed Tools + +Tools with bounded output schemas (boolean, enum, bounded integer) are **endorsed**. Their +results carry at most a few bits of information and do not add taint on completion. This is +the anti-taint-accumulation mechanism -- it prevents agents from becoming permanently +blocked after touching any non-public data. + +## The 9 Actions + +| # | Action | Purpose | +|---|--------|---------| +| 1 | `register_tool` | Add a tool with static immutable labels | +| 2 | `delegate` | Create a child agent with empty capabilities and taint | +| 3 | `grant_capability` | Grant a single capability downward (parent to child) | +| 4 | `revoke` | Parent removes a direct child (full cleanup) | +| 5 | `cascade_revoke` | Propagate revocation to orphaned children | +| 6 | `invoke_start` | Three-check authorization gate; marks invocation in-flight | +| 7 | `invoke_complete` | Tool finished; apply taint if non-endorsed | +| 8 | `return_endorsed` | Child returns bounded result (no taint propagation) | +| 9 | `return_unendorsed` | Child returns unbounded result (taint propagates via union; flow gate checks compatibility with parent's in-flight tools) | + +### Information Flow on Return + +```mermaid +flowchart TD + CHILD["Child Agent
taint: {sensitive}"] + + CHILD -->|"return_endorsed
(bounded schema)"| P1["Parent
taint: unchanged"] + CHILD -->|"return_unendorsed
(arbitrary data)"| P2["Parent
taint: {sensitive} merged"] + + style P1 fill:#2ecc71,color:#fff + style P2 fill:#e67e22,color:#fff +``` + +## Verified Safety Properties + +All properties are verified inductively: they hold in the initial state and every action +preserves them. Verification is fully automatic via Veil 2.0 + cvc5 (push-button SMT). + +| # | Property | Statement | +|---|----------|-----------| +| 1 | **root_always_active** | The root agent can never be deactivated | +| 2 | **default_deny** | In-flight invocations imply explicit authorization AND capability coverage | +| 3 | **flow_confinement** | Tainted agents cannot reach egress channels unless the flow policy permits it (ALLOW, INSPECT+pass, or scoped override) | +| 4 | **capability_subsumption** | Child capabilities are always a subset of parent capabilities | +| 5 | **revocation_clean** | Inactive agents have no in-flight invocations and no taint | +| 6 | **taint_integrity** | Every taint level is traceable to an invocation or a child return | +| 7 | **flow_confinement_weak** | DENY-mode (level, egress) pairs are structurally blocked regardless of oracle behavior | + +**flow_confinement** is the core property -- the Lethal Trifecta defense. Formally: + +``` +taint_levels A L /\ in_flight A I /\ tool_egress (invocation_tool I) E -> + flow_allows L E + \/ (flow_inspects L E /\ content_gate_passes A (invocation_tool I)) + \/ flow_override A (invocation_tool I) L +``` + +12 strengthening invariants support the inductive proof: tree well-formedness (4), +in-flight well-formedness (3), root properties (2), ghost relation soundness (2), +and in-flight flow compatibility (1). + +## Three-Layer Defense + +Tzimtzum is Layer 2 of a three-layer defense architecture. Each layer uses fundamentally +different mechanisms, so a single exploit technique cannot bypass all three. + +```mermaid +graph TD + subgraph "Layer 1: Sandbox Confinement" + L1["OS-level enforcement
Seatbelt (macOS) / Landlock (Linux)
Enforces tool label axioms
0.6ms overhead"] + end + + subgraph "Layer 2: Protocol Gateway (Tzimtzum)" + L2["Capability + Flow + Authorizer gates
Speculative taint, endorsement
Formally verified (Lean 4 / Veil 2.0)
7 safety properties"] + end + + subgraph "Layer 3: Observability" + L3["OTel traces, circuit breakers
Delegation message analysis
Tier 2 async LLM auditor
Covers threat model boundaries"] + end + + L1 --> L2 --> L3 + + style L1 fill:#2ecc71,color:#fff + style L2 fill:#3498db,color:#fff + style L3 fill:#9b59b6,color:#fff +``` + +| Layer | Role | Mechanism | +|-------|------|-----------| +| **Layer 1** | Prevent resource access beyond declared labels | OS kernel sandboxing (Seatbelt/Landlock) | +| **Layer 2** | Enforce authorization at tool boundaries | Formally verified state machine (this spec) | +| **Layer 3** | Detect what the protocol cannot model | OTel traces, circuit breakers, async LLM audit | + +## Trust Assumptions + +The safety properties are conditional on these axioms. Each has a corresponding enforcement +mechanism outside the formal model. + +| # | Assumption | Enforcement | +|---|-----------|-------------| +| **A1** | Tool labels are correct (egress kinds, confidentiality floor) | Layer 1 sandbox-label consistency. Mismatch triggers quarantine. | +| **A2** | Authorizer policies are well-formed | Z3 policy overlap analysis at deploy time. | +| **A3** | State transitions serialize on global state (tool execution is parallel) | Single-writer gateway. Speculative taint handles concurrent execution. | +| **A4** | Content gate correctly identifies sensitive data in arguments | Four-paradigm inspection pipeline: Cedar regex, EDM fingerprinting, literal matching, embedding similarity. | + +## What Tzimtzum Does NOT Defend Against + +These are fundamental limitations shared with other IFC/capability systems. + +| Boundary | Attack Vector | Why Undefendable | Mitigation | +|----------|--------------|------------------|------------| +| **B1** | Instruction-channel encoding (data in delegation messages) | Protocol tracks tool-result provenance, not instruction content | Delegation message size cap + depth limit from tainted parents. Layer 3 semantic analysis. | +| **B2** | Endorsed return bandwidth (1-16 bits/call over many calls) | Type-bounded outputs are small but nonzero | Per-session rate limits on endorsed invocations | +| **B3** | Implicit flows within agent reasoning | No system tracks implicit flows in LLM reasoning | Layer 3 goal-divergence auditor | +| **B4** | Adversarially controlled LLM (compromised weights, not just injected) | Protocol assumes confused agent, not malicious agent | Model provenance verification (out of scope) | +| **B5** | Covert side channels (timing, tool selection, error messages, ordering) | Control-plane channels exist in any tool-calling system | Layer 3 anomaly detection on invocation patterns | + +### What This Means + +Given correct labels (A1), well-formed policies (A2), atomic state transitions (A3), +and accurate content gate (A4): **no confused agent can violate flow confinement at tool +boundaries**. DENY-mode pairs are structurally blocked. INSPECT-mode pairs are +content-gated. This is a precise, provable claim. Everything outside this boundary is +defense-in-depth. + +## Building + +Requires [Lean 4.27.0](https://github.com/leanprover/lean4/releases) and an internet +connection (Veil is fetched from git on first build). + +```bash +cd tzimtzum + +# Full build + verification (fetches Veil, runs #gen_spec and #check_invariants) +lake build --old + +# Or via Makefile +make build + +# Faster incremental builds +LEAN_NUM_THREADS=12 lake build --old +``` + +The `#gen_spec` command assembles the relational transition system (~60s with +`maxHeartbeats 6000000`). `#check_invariants` sends all verification conditions to cvc5. + +### Known Build Artifact + +`#gen_spec` produces cosmetic `LawfulFieldRepresentation` synthesis errors during RTS +definition generation. These are a [Veil 2.0 framework limitation](https://github.com/verse-lab/veil) +(header-first elaboration can't synthesize trivial `IsSubStateOf` proofs). They do not +affect verification -- `#check_invariants` uses a separate code path and all VCs pass. + +## File Structure + +``` +tzimtzum/ + TzimtzumV2.lean -- The complete formal specification (~648 lines) + lakefile.lean -- Lake build configuration (depends on Veil 2.0) + README.md -- This file +``` + +The entire protocol -- sorts, axioms, state, actions, safety properties, invariants, and +verification commands -- lives in a single Lean file. + +## References + +### This Project + +- Tzimtzum v2.3 formal spec: [`TzimtzumV2.lean`](./TzimtzumV2.lean) +- Argus Tool Authorization Gateway: [`../argus/`](../argus/) + +### The Problem + +- Willison, S. (2025). [The Lethal Trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) + +### Verification Framework + +- Veil 2.0 -- Push-button verification for distributed protocols: [github.com/verse-lab/veil](https://github.com/verse-lab/veil) +- Lean 4 theorem prover: [lean-lang.org](https://lean-lang.org/) + +### Academic Prior Art + +The protocol design synthesizes ideas from these papers: + +| Paper | Key Contribution Adopted | +|-------|--------------------------| +| [Fides](https://arxiv.org/abs/2505.23643) (Microsoft Research, 2025) | Type-bounded endorsement for taint-creep prevention | +| [CaMeL](https://arxiv.org/abs/2503.18813) (DeepMind, 2025) | Side-effect classification, default-deny | +| [Progent](https://arxiv.org/abs/2504.11703) (2025) | Parameter-level policy conditions, Z3 overlap analysis | +| [PFI](https://arxiv.org/abs/2503.15547) (2025) | DataGuard literal matching, opaque data references | +| [SEAgent](https://arxiv.org/abs/2601.11893) (2026) | Multi-hop flow graph analysis | +| [LlamaFirewall](https://arxiv.org/abs/2505.03574) (Meta, 2025) | Two-tiered scanning architecture | +| [AgentBound](https://arxiv.org/abs/2510.21236) (2025) | Manifest-based sandbox enforcement | +| [MiniScope](https://arxiv.org/abs/2512.11147) (UC Berkeley, 2025) | ILP-based minimal capability scoping | + +### Additional References + +- [Horus -- Trustless Delegation](https://arxiv.org/abs/2507.00631) (evaluated, rejected as over-engineered) +- [Inter-Agent Trust Models](https://arxiv.org/abs/2511.03434) (Brief/Claim/Proof taxonomy) +- [Open Challenges in Multi-Agent Security](https://arxiv.org/abs/2505.02077) (steganographic collusion) +- [Systems Security Foundations for Agentic Computing](https://arxiv.org/abs/2512.01295) +- [OWASP Top 10 for Agentic Applications 2026](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) diff --git a/tzimtzum/TzimtzumV2.lean b/tzimtzum/TzimtzumV2.lean new file mode 100644 index 0000000..4f0484f --- /dev/null +++ b/tzimtzum/TzimtzumV2.lean @@ -0,0 +1,687 @@ +import Veil + +/-! +# Tzimtzum v2.3: Tool Authorization Protocol Security Kernel + +Formally verified security kernel for LLM tool authorization. + +## Problem + +The Lethal Trifecta (Simon Willison, 2025): **Private data + Untrusted content + +External communication = Exfiltration**. If all three legs are present, indirect prompt +injection can always extract data. This protocol structurally prevents the third leg +(egress) from connecting to the first two by enforcing authorization at tool boundaries. + +## Architecture + +Agents form a tree rooted at `root_agent`. Each agent carries: +- **Capabilities**: typed permissions flowing downward only (parent -> child) +- **Taint set**: confidentiality levels the agent has been exposed to (grows monotonically) +- **In-flight set**: currently executing tool invocations (for speculative taint) + +Tools are registered with static, immutable labels: required capabilities, egress kinds, +confidentiality floor, and endorsement status. Endorsed tools (bounded output schema) +do not add taint on completion. + +Every tool invocation passes through a three-check gate: +1. **Capability gate** -- agent holds all capabilities the tool requires +2. **Flow gate** -- for each (taint_level, egress_kind) pair, policy allows the flow +3. **Authorizer gate** -- external policy engine authorizes the specific (agent, tool) pair + +The flow gate uses graduated enforcement: ALLOW (no restriction), INSPECT (permit if +a content gate certifies the arguments are safe), or DENY (hard block). Default is DENY +for all pairs. + +## Design Choices + +- **Summary taint**: at most 4 confidentiality levels per agent, O(1) flow checks +- **Automatic taint**: no operator choice at result time -- taint is applied on invoke_complete +- **Static tool labels**: labels are immutable background theory, declared at registration +- **Speculative taint**: flow gate checks worst-case taint including all in-flight + non-endorsed tools, eliminating TOCTOU races without requiring sequential execution +- **Graduated flow enforcement**: ALLOW / INSPECT / DENY per (level, egress) pair + +## Verification + +6 safety properties + 12 strengthening invariants, all verified via Veil 2.0 push-button +SMT (cvc5). No manual proofs required. See `argus/design/2026-02-16-tzimtzum-v2-protocol-design.md` +for the full protocol design document. +-/ + +veil module TzimtzumV2 + +/-! +## Sorts + +Six uninterpreted sorts model the protocol's domain. "Uninterpreted" means the SMT solver +reasons about them abstractly -- it doesn't know what an AgentId looks like, only that +agents can be equal or different. This makes the proof hold for ANY concrete instantiation. +-/ +type AgentId -- An LLM agent node in the delegation tree +type ToolId -- A registered tool (API, MCP server, etc.) +type InvocationId -- A unique identifier for a single tool invocation (in-flight tracking) +type CapKind -- A capability kind (filesystem_read, network_write, execution, etc.) +type EgressKind -- An outbound channel classification (network_external, network_internal, etc.) +type ConfLevel -- A confidentiality level (public < internal < sensitive < restricted) + +/-! +## Ordering + +Confidentiality levels form a total order: public < internal < sensitive < restricted. +Since ConfLevel is uninterpreted, we axiomatize the ordering via an immutable Bool relation +`le_conf` with explicit axioms below. This is a standard Veil pattern for ordered sorts. +-/ +immutable relation le_conf : ConfLevel -> ConfLevel -> Bool + +/-! +## Named Constants + +These are distinguished elements of the uninterpreted sorts. `immutable individual` +means they exist in the background theory and never change. + +The four confidentiality levels form the lattice: public < internal < sensitive < restricted. +`root_agent` is the top of the delegation tree -- always active, holds all capabilities. +-/ +immutable individual cl_public : ConfLevel +immutable individual cl_internal : ConfLevel +immutable individual cl_sensitive : ConfLevel +immutable individual cl_restricted : ConfLevel +immutable individual root_agent : AgentId + +/-! +## Immutable Tool Metadata + +Tool properties are declared at registration time and never change. This is a key design +simplification over v1: by making tool metadata immutable, we eliminate an entire class of +invariants (tool label consistency) and the promote/demote actions. + +- `tool_cap T C`: tool T requires capability C to invoke +- `tool_egress T E`: tool T has outbound channel of kind E (can have multiple) +- `tool_conf_floor T`: the confidentiality floor -- invoking T exposes the agent to data at this level +- `tool_endorsed T`: true if T has a bounded output schema (boolean, enum, bounded int). + Endorsed tools don't add taint on completion because their output is information-theoretically bounded. + +**Trust assumption (A1) -- shared resources**: The protocol tracks data flow at tool +invocation boundaries only. Inter-tool communication via shared resources (filesystem, +database, APIs) is invisible to the protocol. Tools sharing writable resources must +declare `tool_conf_floor` accounting for the worst-case data reachable through those +shared resources. Layer 1 (sandbox) enforces resource isolation so that label +declarations match actual access boundaries. +-/ +immutable relation tool_cap : ToolId -> CapKind -> Bool +immutable relation tool_egress : ToolId -> EgressKind -> Bool +immutable function tool_conf_floor : ToolId -> ConfLevel +immutable relation tool_endorsed : ToolId -> Bool + +/-! +## Immutable Flow Policy + +The flow policy is the core of the information flow control. It maps +(confidentiality_level, egress_kind) pairs to one of three enforcement modes: + +- **ALLOW**: `flow_allows L E = true`. No restriction. Data at level L can reach egress E freely. +- **INSPECT**: `flow_inspects L E = true`. Permitted only if `content_gate_passes A T` -- + a deterministic content inspection oracle certifies the arguments don't contain sensitive data. +- **DENY**: neither is true. Hard block. No invocation possible. + +We encode this as two Bool relations instead of a FlowMode enum sort. This avoids +Veil elaboration issues with enum sorts and is equivalent: the `flow_exclusive` axiom +ensures ALLOW and INSPECT are mutually exclusive, and DENY is the implicit default. + +`flow_override` provides per-(agent, tool, level) exceptions. These are principal-scoped +and NOT inherited by children. Overrides are immutable (part of the background theory) -- +temporal scoping is achieved through agent lifecycle: delegate a short-lived child with +the override, revoke it when the task completes. This aligns with least-privilege: +prefer scoped children over long-lived elevated agents. + +`authorizer_allows` and `content_gate_passes` are external oracles. The authorizer is the +policy engine; the content gate is a deterministic argument inspection pipeline +(EDM fingerprinting, regex patterns, literal matching, embedding similarity). +-/ +immutable relation flow_allows : ConfLevel -> EgressKind -> Bool +immutable relation flow_inspects : ConfLevel -> EgressKind -> Bool +immutable relation flow_override : AgentId -> ToolId -> ConfLevel -> Bool +immutable relation authorizer_allows : AgentId -> ToolId -> Bool +immutable relation content_gate_passes : AgentId -> ToolId -> Bool + +/-! +## Mutable State + +These relations change as the protocol executes. Uppercase variables in Veil are +implicitly universally quantified, so `relation agent_active : AgentId -> Bool` +defines a predicate over all agents. + +- `agent_active A`: agent A exists and is participating in the system +- `agent_parent C P`: C is a direct child of P in the delegation tree +- `agent_cap A C`: agent A holds capability C +- `taint_levels A L`: agent A has been exposed to data at confidentiality level L +- `in_flight A I`: agent A has invocation I currently executing (not yet completed) +- `invocation_tool I`: maps each invocation ID to its tool (immutable -- binding is permanent) +- `tool_registered T`: tool T has been registered in the system +- `gh_taint_invoked A L`: ghost tracking -- A acquired taint at L via invoke_complete +- `gh_taint_received A L`: ghost tracking -- A acquired taint at L via return_unendorsed + +The `gh_` (ghost) tracking relations exist solely to prove taint_integrity: that every +taint level is traceable to either an invocation or a receipt from a child. They mirror +the taint_levels updates but record the SOURCE of each taint addition. +-/ +relation agent_active : AgentId -> Bool +relation agent_parent : AgentId -> AgentId -> Bool +relation agent_cap : AgentId -> CapKind -> Bool +relation taint_levels : AgentId -> ConfLevel -> Bool +relation in_flight : AgentId -> InvocationId -> Bool +immutable function invocation_tool : InvocationId -> ToolId +relation tool_registered : ToolId -> Bool +relation gh_taint_invoked : AgentId -> ConfLevel -> Bool +relation gh_taint_received : AgentId -> ConfLevel -> Bool + +#gen_state + +/-! +## Ordering Axioms + +These axioms make `le_conf` a total order over the four named confidentiality levels. +The axioms are: reflexivity, transitivity, antisymmetry, totality (any two levels are +comparable), the chain (public < internal < sensitive < restricted), and pairwise +distinctness of all four levels. + +Without distinctness axioms, the SMT solver could collapse two levels into one, +producing spurious counterexamples. +-/ +assumption [conf_refl] le_conf L L = true +assumption [conf_trans] le_conf L1 L2 /\ le_conf L2 L3 -> le_conf L1 L3 = true +assumption [conf_antisym] le_conf L1 L2 /\ le_conf L2 L1 -> L1 = L2 +assumption [conf_total] le_conf L1 L2 = true \/ le_conf L2 L1 = true +assumption [conf_chain_01] le_conf cl_public cl_internal = true +assumption [conf_chain_12] le_conf cl_internal cl_sensitive = true +assumption [conf_chain_23] le_conf cl_sensitive cl_restricted = true +assumption [conf_distinct_01] cl_public != cl_internal +assumption [conf_distinct_02] cl_public != cl_sensitive +assumption [conf_distinct_03] cl_public != cl_restricted +assumption [conf_distinct_12] cl_internal != cl_sensitive +assumption [conf_distinct_13] cl_internal != cl_restricted +assumption [conf_distinct_23] cl_sensitive != cl_restricted + +/-! +## Flow Policy Axiom + +ALLOW and INSPECT are mutually exclusive. A (level, egress) pair cannot be both +allowed freely AND require inspection. If `flow_allows` is true, `flow_inspects` +must be false. The third mode (DENY) is implicit: when neither is true, the pair +is denied. +-/ +assumption [flow_exclusive] flow_allows L E -> flow_inspects L E = false + +/-! +## Ghost Relations + +`speculative_taint` is a derived predicate (not stored in state). It computes the +worst-case taint an agent could have, accounting for both: +1. Actual taint (`taint_levels`) from completed invocations +2. Potential taint from all in-flight non-endorsed tools + +This is the key mechanism enabling safe parallel execution. When the flow gate checks +speculative taint at invoke_start, it guarantees flow confinement holds regardless of +which order the in-flight tools complete. +-/ +ghost relation speculative_taint (a : AgentId) (l : ConfLevel) := + taint_levels a l + \/ (exists I, in_flight a I + /\ tool_conf_floor (invocation_tool I) = l + /\ not (tool_endorsed (invocation_tool I))) + +/-! +## Initial State + +The system starts with only root_agent active. Root holds all capabilities (it's the +authority from which all permissions flow). Everything else is empty: no children, +no taint, no in-flight invocations, no registered tools. +-/ +after_init { + agent_active A := decide (A = root_agent); + agent_parent A B := false; + agent_cap A C := decide (A = root_agent); + taint_levels A L := false; + in_flight A I := false; + tool_registered T := false; + gh_taint_invoked A L := false; + gh_taint_received A L := false +} + +/-! +## Actions + +The protocol has 9 actions. Each action has: +- **Parameters**: the inputs to the action +- **Preconditions** (`require`): conditions that must hold for the action to fire. + If any require fails, the action is simply not taken (no error state). +- **State updates** (`:=`): how the mutable state changes. Veil's frame rule preserves + any relation not explicitly mentioned. +-/ + +/-! +### register_tool: Add a tool to the system + +Simply flips the `tool_registered` flag. All tool metadata (capabilities, egress kinds, +confidentiality floor, endorsement) is immutable background theory -- it exists +regardless of registration. Registration is a gate: invoke_start requires the tool +to be registered. +-/ +action register_tool (tool : ToolId) { + require not (tool_registered tool); + tool_registered tool := true +} + +/-! +### delegate: Create a child agent + +A grantor (parent) creates a fresh child agent. The child starts with: +- Empty capabilities (must be granted individually via grant_capability) +- Empty taint set (the child has never seen any data) +- Empty in-flight set (no pending invocations) + +The parent link `agent_parent grantee grantor` is established. The update to +`agent_parent` also cleans any stale entries involving the grantee ID (defense-in-depth, +since `not (agent_active grantee)` should mean no entries exist). +-/ +action delegate (grantor grantee : AgentId) { + require agent_active grantor; + require not (agent_active grantee); + require grantee != root_agent; + agent_active grantee := true; + agent_parent C P := decide $ + (C = grantee /\ P = grantor) \/ (agent_parent C P /\ C != grantee /\ P != grantee); + agent_cap grantee C := false; + taint_levels grantee L := false; + in_flight grantee I := false; + gh_taint_invoked grantee L := false; + gh_taint_received grantee L := false +} + +/-! +### grant_capability: Give a single capability to a child + +Capabilities flow strictly downward: a parent can only grant capabilities it holds. +Separated from delegate for incremental proof -- each grant independently verifies +the parent holds the capability, which directly supports the capability_subsumption safety. +-/ +action grant_capability (prnt child : AgentId) (cap : CapKind) { + require agent_active prnt; + require agent_active child; + require agent_parent child prnt; + require agent_cap prnt cap; + agent_cap N C := decide $ (N = child /\ C = cap) \/ (agent_cap N C) +} + +/-! +### revoke: Remove a child agent (direct parent action) + +Parent removes a direct child. All state for the target is cleaned: deactivated, +parent link removed, capabilities cleared, taint cleared, in-flight cleared. + +This is a "hard stop" -- any in-flight tool executions for the revoked agent are +effectively aborted (the runtime layer handles this). +-/ +action revoke (prnt target : AgentId) { + require agent_parent target prnt; + require agent_active prnt; + require agent_active target; + require target != root_agent; + agent_active target := false; + agent_parent C P := decide $ agent_parent C P /\ C != target; + agent_cap target C := false; + taint_levels target L := false; + in_flight target I := false; + gh_taint_invoked target L := false; + gh_taint_received target L := false +} + +/-! +### cascade_revoke: Propagate revocation down the tree + +When a parent is revoked, its children become orphans. cascade_revoke fires for each +orphaned child (child's parent is inactive). This ensures no agent survives in the tree +without an active parent. + +Separated from revoke because the precondition is different: revoke requires the parent +to be ACTIVE (it's choosing to remove the child), while cascade_revoke requires the +parent to be INACTIVE (the child is being cleaned up because its parent is gone). + +**Transient window safety**: Between revoke and cascade_revoke, orphaned agents are +active with a dead parent. This is safe: return_endorsed and return_unendorsed both +require `agent_active prnt`, so orphans cannot escalate taint upward. grant_capability +requires an active parent, so orphans cannot gain new permissions. flow_confinement and +default_deny hold independently of parent liveness. The orphan is in a sealed subtree -- +it can do work but cannot communicate upward or acquire new capabilities. + +**Runtime contract**: cascade_revoke SHOULD fire promptly after revoke to avoid wasted +computation in orphaned subtrees. Safety does not depend on promptness. +-/ +action cascade_revoke (child prnt : AgentId) { + require agent_parent child prnt; + require not (agent_active prnt); + require agent_active child; + require child != root_agent; + agent_active child := false; + agent_parent C P := decide $ agent_parent C P /\ C != child; + agent_cap child C := false; + taint_levels child L := false; + in_flight child I := false; + gh_taint_invoked child L := false; + gh_taint_received child L := false +} + +/-! +### invoke_start: The three-check authorization gate + +This is the core action of the protocol. An agent requests to invoke a tool, and the +protocol evaluates three independent checks. If all pass, the invocation is marked +as in-flight. + +**Preconditions**: Agent must be active, non-root (root orchestrates but doesn't invoke), +tool must be registered, and the invocation ID must be fresh (not used by any agent). + +**Check 1 -- Capability gate**: The agent holds every capability the tool requires. +Simple set containment. + +**Check 2 -- Flow gate**: The graduated flow enforcement check. Three sub-checks ensure +flow confinement holds for all combinations of taint and egress: + +- **2a**: For each existing speculative taint level and each egress kind of the new tool, + the flow policy must allow it (ALLOW, INSPECT+pass, or override). +- **2b**: For each already in-flight tool's egress and the new tool's potential taint, + the flow policy must allow it. This is the "reverse direction" -- the new tool's + taint could violate existing in-flight tools' egress. +- **2c**: The new tool's own taint against its own egress (self-flow). If it's + non-endorsed and has egress, the flow policy must permit the combination. + +**Check 3 -- Authorizer gate**: The external policy engine authorizes (agent, tool). + +Only after all three checks pass does the invocation become in-flight. +-/ +action invoke_start (a : AgentId) (tool : ToolId) (inv : InvocationId) { + require agent_active a; + require a != root_agent; + require tool_registered tool; + require invocation_tool inv = tool; + require forall AG, not (in_flight AG inv); + -- CHECK 1: Capability gate + require forall C, tool_cap tool C -> agent_cap a C; + -- CHECK 2a: Flow gate (existing speculative taint x new tool's egress) + require forall L E, + speculative_taint a L /\ tool_egress tool E -> + flow_allows L E + \/ (flow_inspects L E /\ content_gate_passes a tool) + \/ flow_override a tool L; + -- CHECK 2b: Flow gate (new tool's taint x existing in-flight's egress) + require forall I E, + in_flight a I /\ tool_egress (invocation_tool I) E + /\ not (tool_endorsed tool) -> + flow_allows (tool_conf_floor tool) E + \/ (flow_inspects (tool_conf_floor tool) E + /\ content_gate_passes a (invocation_tool I)) + \/ flow_override a (invocation_tool I) (tool_conf_floor tool); + -- CHECK 2c: Self-flow gate (tool's own taint x its own egress) + require forall E, + not (tool_endorsed tool) /\ tool_egress tool E -> + flow_allows (tool_conf_floor tool) E + \/ (flow_inspects (tool_conf_floor tool) E /\ content_gate_passes a tool) + \/ flow_override a tool (tool_conf_floor tool); + -- CHECK 3: Authorizer gate + require authorizer_allows a tool; + in_flight a inv := true +} + +/-! +### invoke_complete: Tool execution finished + +The tool has returned a result. Two things happen: +1. The invocation is removed from the in-flight set +2. If the tool is NOT endorsed, the agent acquires taint at the tool's confidentiality + floor level. Endorsed tools produce bounded output and don't add taint. + +The ghost relation `gh_taint_invoked` mirrors the taint update to track that this +taint came from an invocation (needed for taint_integrity safety). +-/ +action invoke_complete (a : AgentId) (inv : InvocationId) { + require in_flight a inv; + require agent_active a; + in_flight a inv := false; + taint_levels A L := decide $ + taint_levels A L + \/ (A = a /\ not (tool_endorsed (invocation_tool inv)) + /\ tool_conf_floor (invocation_tool inv) = L); + gh_taint_invoked A L := decide $ + gh_taint_invoked A L + \/ (A = a /\ not (tool_endorsed (invocation_tool inv)) + /\ tool_conf_floor (invocation_tool inv) = L) +} + +/-! +### return_endorsed: Child returns a bounded result to parent + +The child has finished all its work (no in-flight invocations) and returns an endorsed +result to its parent. Because the result has a bounded output schema (boolean, enum, +bounded integer), it carries at most a few bits of information and does NOT propagate +taint to the parent. The parent's taint set is unchanged. + +This is a pure guard action -- it verifies the structural preconditions but makes no +state changes. +-/ +action return_endorsed (child prnt : AgentId) { + require agent_parent child prnt; + require agent_active child; + require agent_active prnt; + require forall I, not (in_flight child I); + pure () +} + +/-! +### return_unendorsed: Child returns an unbounded result to parent + +The child returns a non-endorsed result (arbitrary text, data, etc.). The parent +inherits the child's entire taint set via set union. This is conservative but prevents +taint laundering: without this, a parent could delegate to a child, have the child +read sensitive data, and receive it back without acquiring taint. + +Additional precondition: the incoming taint from the child must be compatible with all +of the parent's in-flight invocations. For each (child taint level, parent in-flight +egress) pair, the flow policy must permit it (ALLOW, INSPECT+content_gate, or override). +This is structurally identical to invoke_start's Check 2a but evaluated at receive time +against the child's actual taint. The parent can remain busy -- the return only blocks +when there is a genuine flow conflict. + +The ghost relation `gh_taint_received` tracks that this taint came from a child return +(needed for taint_integrity safety). +-/ +action return_unendorsed (child prnt : AgentId) { + require agent_parent child prnt; + require agent_active child; + require agent_active prnt; + require forall I, not (in_flight child I); + -- Flow gate: incoming taint must be compatible with parent's in-flight tools + require forall L I E, + taint_levels child L /\ in_flight prnt I /\ tool_egress (invocation_tool I) E -> + flow_allows L E + \/ (flow_inspects L E /\ content_gate_passes prnt (invocation_tool I)) + \/ flow_override prnt (invocation_tool I) L; + taint_levels A L := decide $ + taint_levels A L \/ (A = prnt /\ taint_levels child L); + gh_taint_received A L := decide $ + gh_taint_received A L \/ (A = prnt /\ taint_levels child L) +} + +/-! +### sentinel_elevate_taint: Gateway adjusts agent taint based on analysis pipeline + +The gateway's analysis pipeline (Tier 1/2/3) may determine that an agent should be +treated as tainted at a higher confidentiality level. This action allows the sentinel +to elevate an agent's taint, provided the new taint level is flow-compatible with all +of the agent's currently in-flight tools. +-/ +action sentinel_elevate_taint (a : AgentId) (l : ConfLevel) { + require agent_active a; + require forall I E, + in_flight a I /\ tool_egress (invocation_tool I) E -> + flow_allows l E + \/ (flow_inspects l E /\ content_gate_passes a (invocation_tool I)) + \/ flow_override a (invocation_tool I) l; + taint_levels a l := true; + gh_taint_invoked a l := true +} + +/-! +## Safety Properties + +These are the properties we prove hold in ALL reachable states. Veil verifies them +inductively: they hold in the initial state, and every action preserves them (given +the strengthening invariants). +-/ + +/-! +**root_always_active**: The root agent can never be deactivated. This is trivially +preserved because no action can deactivate root (revoke and cascade_revoke both +require `target != root_agent`). +-/ +safety [root_always_active] + agent_active root_agent + +/-! +**default_deny**: If a tool invocation is in-flight, then it was explicitly authorized. +Specifically: the authorizer allowed (agent, tool) AND the agent holds every capability +the tool requires. This is the "no unauthorized access" property. +-/ +safety [default_deny] + in_flight A I -> + authorizer_allows A (invocation_tool I) + /\ (forall C, tool_cap (invocation_tool I) C -> agent_cap A C) + +/-! +**flow_confinement**: The core information flow property. If an agent carries taint at +level L, has an in-flight invocation of a tool with egress kind E, then the flow policy +permits (L, E) -- either via ALLOW, INSPECT+content_gate_passes, or a scoped override. + +This is the Lethal Trifecta defense: a tainted agent (private data exposure) cannot +reach an egress channel (external communication) unless the flow policy explicitly permits it. +-/ +safety [flow_confinement] + taint_levels A L /\ in_flight A I /\ tool_egress (invocation_tool I) E -> + flow_allows L E + \/ (flow_inspects L E /\ content_gate_passes A (invocation_tool I)) + \/ flow_override A (invocation_tool I) L + +/-! +**flow_confinement_weak**: Oracle-independent flow safety. If an agent carries taint at +level L and has an in-flight invocation with egress kind E, then the (L, E) pair must be +in ALLOW or INSPECT mode, or have a scoped override. DENY-mode pairs are structurally +blocked regardless of oracle behavior. + +This is strictly weaker than flow_confinement (drops the content_gate_passes requirement). +It proves that the operator controls the blast radius of content gate failure: only +INSPECT-mode channels depend on oracle correctness. DENY channels are oracle-free. +-/ +safety [flow_confinement_weak] + taint_levels A L /\ in_flight A I /\ tool_egress (invocation_tool I) E -> + flow_allows L E + \/ flow_inspects L E + \/ flow_override A (invocation_tool I) L + +/-! +**capability_subsumption**: For any active parent-child pair, the child's capabilities +are a subset of the parent's. Capabilities only flow downward. +-/ +safety [capability_subsumption] + agent_parent C P /\ agent_active C /\ agent_active P -> + forall Cap, agent_cap C Cap -> agent_cap P Cap + +/-! +**revocation_clean**: Inactive agents leave no residue. If an agent is not active, +it has no in-flight invocations and no taint. This ensures revocation is complete -- +no "zombie" state persists after an agent is removed. +-/ +safety [revocation_clean] + not (agent_active A) -> not (in_flight A I) /\ not (taint_levels A L) + +/-! +**taint_integrity**: Taint doesn't appear from nowhere. If an active agent has taint +at level L, it's because either: +- The agent completed a non-endorsed tool invocation at that level (gh_taint_invoked), OR +- The agent received an unendorsed return from a tainted child (gh_taint_received) + +This proves the taint tracking is sound -- no "phantom taint" can appear. +-/ +safety [taint_integrity] + taint_levels A L /\ agent_active A -> + gh_taint_invoked A L \/ gh_taint_received A L + +/-! +## Strengthening Invariants + +These are auxiliary invariants that aren't directly interesting as safety properties +but are needed to make the inductive proof go through. Each one eliminates a +"counterexample to induction" (CTI) -- a state that satisfies the invariants but +where some action could produce a state violating a safety property. + +The SMT solver discovers CTIs automatically. The human's job is to add invariants +that rule out the unreachable states the CTIs exploit. +-/ + +-- Tree well-formedness: the parent relation is consistent with active status +invariant [parent_implies_active] agent_parent C P -> agent_active C +invariant [single_parent] agent_parent C P1 /\ agent_parent C P2 /\ agent_active C -> P1 = P2 +invariant [no_self_parent] agent_parent A A = false +invariant [root_no_parent] agent_parent root_agent P = false + +-- In-flight well-formedness: invocations only exist for active agents with registered tools +invariant [in_flight_active] in_flight A I -> agent_active A +invariant [in_flight_registered] in_flight A I -> tool_registered (invocation_tool I) +invariant [in_flight_unique] in_flight A1 I /\ in_flight A2 I -> A1 = A2 + +-- Root properties: root holds all capabilities and never invokes tools directly +invariant [root_all_caps] agent_cap root_agent C = true +invariant [root_no_in_flight] in_flight root_agent I = false + +-- Ghost soundness: ghost tracking relations stay in sync with taint_levels. +-- If a ghost relation says taint exists but taint_levels disagrees, the agent must be inactive +-- (revocation cleared taint_levels but we don't bother clearing ghost relations for inactive agents). +invariant [ghost_invoked_sound] + gh_taint_invoked A L -> taint_levels A L \/ not (agent_active A) +invariant [ghost_received_sound] + gh_taint_received A L -> taint_levels A L \/ not (agent_active A) + +-- In-flight flow compatibility: any two concurrent in-flight invocations for the same +-- agent must be mutually compatible under the flow policy. If tool I1 is non-endorsed +-- (will produce taint at its conf floor), and tool I2 has egress, the policy must permit +-- (conf_floor(I1), egress(I2)). Without this, invoke_complete for I1 could create taint +-- that violates flow_confinement for the still-in-flight I2. +invariant [in_flight_flow_compat] + in_flight A I1 /\ in_flight A I2 + /\ not (tool_endorsed (invocation_tool I1)) + /\ tool_egress (invocation_tool I2) E -> + flow_allows (tool_conf_floor (invocation_tool I1)) E + \/ (flow_inspects (tool_conf_floor (invocation_tool I1)) E + /\ content_gate_passes A (invocation_tool I2)) + \/ flow_override A (invocation_tool I2) (tool_conf_floor (invocation_tool I1)) + +/-! +## Verification + +`#gen_spec` finalizes the Veil specification (assembles Init + Next into a relational +transition system). `#check_invariants` sends all verification conditions to the SMT +solver (cvc5) and reports results. + +Known limitation: `#gen_spec` produces cosmetic `LawfulFieldRepresentation` synthesis +errors in the RTS definition. These are a Veil 2.0 framework bug (header-first +elaboration can't synthesize trivial IsSubStateOf proofs). They do not affect verification +-- `#check_invariants` uses a separate code path and all VCs pass. +-/ + +set_option maxHeartbeats 6000000 +set_option synthInstance.maxHeartbeats 1000000 +set_option pp.deepTerms false +set_option pp.proofs false +#time #gen_spec + +#check_invariants + +end TzimtzumV2 diff --git a/tzimtzum/lake-manifest.json b/tzimtzum/lake-manifest.json new file mode 100644 index 0000000..6899ea9 --- /dev/null +++ b/tzimtzum/lake-manifest.json @@ -0,0 +1,145 @@ +{"version": "1.1.0", + "packagesDir": ".lake/packages", + "packages": + [{"url": "https://github.com/verse-lab/veil", + "type": "git", + "subDir": null, + "scope": "", + "rev": "2ab274a804a0c306b9be1eac62c1ceb35a311c9e", + "name": "veil", + "manifestFile": "lake-manifest.json", + "inputRev": "veil-2.0-preview", + "inherited": false, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/verse-lab/loom.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "37fe56324c533a2b0ed28ce637ee7c32e728b61d", + "name": "Loom", + "manifestFile": "lake-manifest.json", + "inputRev": "temp-extract-list-v4.27.0-for-veil", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/zqy1018/lean-smt.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "d7af8d05e3be465d093f661046c0ef30cb043af8", + "name": "smt", + "manifestFile": "lake-manifest.json", + "inputRev": "v4.27.0-for-veil", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/lean-auto.git", + "type": "git", + "subDir": null, + "scope": "", + "rev": "7e8f3ab431d4790bd803e467d661b1be2522bfd3", + "name": "auto", + "manifestFile": "lake-manifest.json", + "inputRev": "7e8f3ab431d4790bd803e467d661b1be2522bfd3", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/mathlib4", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "a3a10db0e9d66acbebf76c5e6a135066525ac900", + "name": "mathlib", + "manifestFile": "lake-manifest.json", + "inputRev": "v4.27.0", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/plausible", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "009dc1e6f2feb2c96c081537d80a0905b2c6498f", + "name": "plausible", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/LeanSearchClient", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "5ce7f0a355f522a952a3d678d696bd563bb4fd28", + "name": "LeanSearchClient", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/import-graph", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "8f497d55985a189cea8020d9dc51260af1e41ad2", + "name": "importGraph", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/ProofWidgets4", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "c04225ee7c0585effbd933662b3151f01b600e40", + "name": "proofwidgets", + "manifestFile": "lake-manifest.json", + "inputRev": "v0.0.85", + "inherited": true, + "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/aesop", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "cb837cc26236ada03c81837bebe0acd9c70ced7d", + "name": "aesop", + "manifestFile": "lake-manifest.json", + "inputRev": "master", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/quote4", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "bd58c9efe2086d56ca361807014141a860ddbf8c", + "name": "Qq", + "manifestFile": "lake-manifest.json", + "inputRev": "master", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover-community/batteries", + "type": "git", + "subDir": null, + "scope": "leanprover-community", + "rev": "b25b36a7caf8e237e7d1e6121543078a06777c8a", + "name": "batteries", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/leanprover/lean4-cli", + "type": "git", + "subDir": null, + "scope": "leanprover", + "rev": "55c37290ff6186e2e965d68cf853a57c0702db82", + "name": "Cli", + "manifestFile": "lake-manifest.json", + "inputRev": "v4.27.0", + "inherited": true, + "configFile": "lakefile.toml"}, + {"url": "https://github.com/zqy1018/lean-cvc5", + "type": "git", + "subDir": null, + "scope": "", + "rev": "c6ea5922acf05433ac072c2cf7a94955981c4f67", + "name": "cvc5", + "manifestFile": "lake-manifest.json", + "inputRev": "v4.27.0", + "inherited": true, + "configFile": "lakefile.lean"}], + "name": "tzimtzum", + "lakeDir": ".lake"} diff --git a/tzimtzum/lakefile.lean b/tzimtzum/lakefile.lean new file mode 100644 index 0000000..0277c68 --- /dev/null +++ b/tzimtzum/lakefile.lean @@ -0,0 +1,12 @@ +import Lake +open Lake DSL + +require veil from git + "https://github.com/verse-lab/veil" @ "veil-2.0-preview" + +package tzimtzum where + version := v!"0.2.0" + +@[default_target] +lean_lib TzimtzumV2 where + globs := #[`TzimtzumV2] diff --git a/tzimtzum/lean-toolchain b/tzimtzum/lean-toolchain new file mode 100644 index 0000000..5249182 --- /dev/null +++ b/tzimtzum/lean-toolchain @@ -0,0 +1 @@ +leanprover/lean4:v4.27.0 diff --git a/tzimtzum/scenarios-adversarial.md b/tzimtzum/scenarios-adversarial.md new file mode 100644 index 0000000..fc1b65b --- /dev/null +++ b/tzimtzum/scenarios-adversarial.md @@ -0,0 +1,379 @@ +# Tzimtzum v2.3 -- Adversarial Scenarios + +Attack attempts against the protocol and the safety properties that +block them. Each scenario shows the exact precondition or gate check +that prevents the attack. + +Uses the same [shared universe](scenarios-happy-paths.md#shared-universe) +(tools, flow policy, agents) as the happy path walkthroughs. + +--- + +## Scenario 1: The Lethal Trifecta + +> **Attack**: An agent reads sensitive data from a database, then tries to +> exfiltrate it via email. +> +> **Blocked by**: `flow_confinement` -- the flow gate evaluates +> `(sensitive, network_external) = DENY` and hard-blocks the invocation. + +**Agents**: `root` -> `assistant` (holds `{db_read, email_send}`) + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant DB as read-db + participant E as send-email + + A->>P: invoke_start(assistant, read-db, inv1) + Note over P: All checks pass (no taint, no egress) + P-->>A: inv1 in-flight + + DB-->>P: sensitive records + P->>A: invoke_complete(assistant, inv1) + Note over P: not endorsed, conf_floor=sensitive + Note over P: taint += {sensitive} + + A->>P: invoke_start(assistant, send-email, inv2) + Note over P: Check 1: email_send -- pass + Note over P: Check 2a: taint={sensitive}, egress=network_external + Note over P: (sensitive, network_external) = DENY + Note over P: flow_override? -- no + P--xA: BLOCKED +``` + +| Step | Action | in_flight | taint | result | +|------|--------|-----------|-------|--------| +| 0 | -- | `{}` | `{}` | | +| 1 | `invoke_start(read-db, inv1)` | `{inv1}` | `{}` | allowed | +| 2 | `invoke_complete(inv1)` | `{}` | `{sensitive}` | | +| 3 | `invoke_start(send-email, inv2)` | `{}` | `{sensitive}` | **BLOCKED** | + +**Why it works**: This is the protocol's core defense. The three legs of +the Lethal Trifecta are: private data (read-db exposes sensitive), +untrusted content (the agent may be prompt-injected), and external +communication (send-email has network_external egress). The flow gate +severs the third leg -- once the agent carries sensitive taint, all +DENY-mode egress channels are permanently closed to it. + +--- + +## Scenario 2: Racing the Taint + +> **Attack**: Start a database read and an email send in parallel, hoping +> the email fires before the taint from the database read materializes. +> +> **Blocked by**: `flow_confinement` via **speculative taint** -- the +> in-flight non-endorsed `read-db` is included in the taint calculation +> before it completes. + +**Agents**: `root` -> `assistant` (holds `{db_read, email_send}`) + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant DB as read-db + participant E as send-email + + A->>P: invoke_start(assistant, read-db, inv1) + Note over P: All checks pass (no taint, no egress) + P-->>A: inv1 in-flight + + Note over A: read-db still executing... + + A->>P: invoke_start(assistant, send-email, inv2) + Note over P: Check 2a: speculative_taint includes {sensitive} + Note over P: (from in-flight inv1: read-db, non-endorsed,
conf_floor=sensitive) + Note over P: (sensitive, network_external) = DENY + P--xA: BLOCKED +``` + +| Step | Action | in_flight | taint | speculative_taint | result | +|------|--------|-----------|-------|-------------------|--------| +| 0 | -- | `{}` | `{}` | `{}` | | +| 1 | `invoke_start(read-db, inv1)` | `{inv1}` | `{}` | `{sensitive}` | allowed | +| 2 | `invoke_start(send-email, inv2)` | `{inv1}` | `{}` | `{sensitive}` | **BLOCKED** | + +The reverse order is also blocked. If the agent starts `send-email` first +(which succeeds, since there's no taint yet), then tries `read-db`: + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + + A->>P: invoke_start(assistant, send-email, inv1) + Note over P: No taint, (public, network_external)=ALLOW -- pass + P-->>A: inv1 in-flight + + A->>P: invoke_start(assistant, read-db, inv2) + Note over P: Check 2b: inv1 in-flight (send-email) + Note over P: send-email egress = network_external + Note over P: read-db is non-endorsed, conf_floor = sensitive + Note over P: (sensitive, network_external) = DENY + P--xA: BLOCKED +``` + +**Why it works**: Speculative taint eliminates the TOCTOU race entirely. +Check 2a catches "existing taint vs. new egress" and Check 2b catches +"new taint vs. existing egress". No matter which tool the agent tries +to start first, the conflicting pair `(sensitive, network_external)` is +detected. This is why both checks exist -- they are the forward and +reverse directions of the same flow constraint. + +--- + +## Scenario 3: Taint Laundering via Delegation + +> **Attack**: The agent can't send email because it has sensitive taint. +> It delegates the database read to a clean child, hoping to receive the +> data back without acquiring taint. +> +> **Blocked by**: `return_unendorsed` propagates the child's taint to the +> parent. The laundering fails. + +**Agents**: `root` -> `assistant` (holds `{db_read, email_send}`) -> `child` + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant C as child + + Note over A: Plan: delegate DB read to child,
get clean data back, send email + + A->>P: delegate(assistant, child) + P-->>C: child active, caps={} + + A->>P: grant_capability(assistant, child, db_read) + P-->>C: caps={db_read} + + C->>P: invoke_start(child, read-db, inv1) + Note over P: All checks pass + P-->>C: inv1 in-flight + + P->>C: invoke_complete(child, inv1) + Note over P: not endorsed -- child taint += {sensitive} + + C->>P: return_unendorsed(child, assistant) + Note over P: taint propagation: assistant taint = {} ∪ {sensitive} + P-->>A: data returned, taint propagated + + A->>P: invoke_start(assistant, send-email, inv2) + Note over P: Check 2a: taint={sensitive}, egress=network_external + Note over P: (sensitive, network_external) = DENY + P--xA: BLOCKED +``` + +| Step | Action | child taint | assistant taint | result | +|------|--------|:-:|:-:|--------| +| 0 | -- | -- | `{}` | | +| 1 | `delegate` + `grant_capability` | `{}` | `{}` | | +| 2 | `invoke_start(read-db)` | `{}` | `{}` | allowed | +| 3 | `invoke_complete(read-db)` | `{sensitive}` | `{}` | | +| 4 | `return_unendorsed` | `{sensitive}` | `{sensitive}` | taint propagated | +| 5 | `invoke_start(send-email)` | -- | `{sensitive}` | **BLOCKED** | + +**Why it works**: Taint follows data, not delegation boundaries. Any +unendorsed return (arbitrary text, records, etc.) propagates the child's +full taint set to the parent via set union. The delegation boundary is +not a taint firewall -- it's a capability boundary. You can limit what a +child can *do*, but you can't receive its unbounded output without +accepting its taint. + +The only way to avoid taint propagation is `return_endorsed` -- but that +requires the child to have used only endorsed tools (bounded output schemas), +meaning the data is information-theoretically bounded (a boolean, an enum, +a small integer). You can't smuggle a database dump through a boolean. + +--- + +## Scenario 4: Capability Escalation + +> **Attack**: A child agent tries to invoke a tool requiring capabilities +> it was never granted. +> +> **Blocked by**: `default_deny` (Check 1) -- the capability gate requires +> set containment. `capability_subsumption` ensures the parent can't grant +> what it doesn't hold either. + +**Agents**: `root` -> `assistant` (holds `{web_access}`) -> `restricted-child` + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant C as restricted-child + + A->>P: delegate(assistant, restricted-child) + P-->>C: restricted-child active, caps={} + + A->>P: grant_capability(assistant, restricted-child, web_access) + P-->>C: caps={web_access} + + Note over C: Attempt 1: invoke a tool requiring db_read + + C->>P: invoke_start(restricted-child, read-db, inv1) + Note over P: Check 1: read-db requires db_read + Note over P: restricted-child caps = {web_access} + Note over P: db_read not in {web_access} + P--xC: BLOCKED (missing capability) + + Note over C: Attempt 2: ask parent to grant db_read + + Note over A: assistant caps = {web_access} + Note over A: db_read not in {web_access} + A->>P: grant_capability(assistant, restricted-child, db_read) + Note over P: require agent_cap assistant db_read + Note over P: assistant doesn't hold db_read + P--xA: BLOCKED (parent doesn't hold it either) +``` + +| Step | Action | child caps | result | +|------|--------|:-:|--------| +| 0 | `delegate` + `grant(web_access)` | `{web_access}` | | +| 1 | child: `invoke_start(read-db)` | `{web_access}` | **BLOCKED** (Check 1) | +| 2 | parent: `grant_capability(db_read)` | `{web_access}` | **BLOCKED** (parent lacks it) | + +**Why it works**: Capabilities flow strictly downward and only via explicit +grants. `capability_subsumption` is the safety property that guarantees +this: for any active parent-child pair, the child's capabilities are a +subset of the parent's. There is no action in the protocol that creates +capabilities from nothing -- they all originate from `root_agent`, which +holds everything by the `root_all_caps` invariant. + +--- + +## Scenario 5: Orphan Escalation After Revocation + +> **Attack**: A parent is revoked while a child still has sensitive taint. +> The child tries to escalate by returning its tainted data upward or +> acquiring new capabilities. +> +> **Blocked by**: `return_endorsed` and `return_unendorsed` require +> `agent_active prnt`. `grant_capability` requires an active parent. +> The orphan is sealed in a disconnected subtree. + +**Agents**: `root` -> `assistant` -> `child` + +```mermaid +sequenceDiagram + participant R as root + participant A as assistant + participant P as Protocol + participant C as child + + Note over A,C: Setup: child has sensitive taint from earlier work + + C->>P: return_unendorsed(child, assistant) + Note over P: require agent_active assistant + Note over P: assistant was revoked -- agent_active = false + P--xC: BLOCKED (parent inactive) + + C->>P: return_endorsed(child, assistant) + Note over P: require agent_active assistant + P--xC: BLOCKED (parent inactive) + + Note over C: Try to get new capabilities + + Note over P: grant_capability requires agent_active parent + Note over P: assistant is inactive -- no grants possible + P--xC: BLOCKED (no active parent to grant) + + Note over C: Try to invoke tools + + C->>P: invoke_start(child, send-email, inv1) + Note over P: Check 1: child lacks email_send (never granted) + P--xC: BLOCKED (missing capability) + + R->>P: cascade_revoke(child, assistant) + Note over P: assistant inactive, child active -- pass + Note over P: child deactivated, all state cleaned +``` + +| Step | Action | child active | child taint | result | +|------|--------|:-:|:-:|--------| +| 0 | (assistant revoked) | yes | `{sensitive}` | orphaned | +| 1 | `return_unendorsed(child, assistant)` | yes | `{sensitive}` | **BLOCKED** | +| 2 | `return_endorsed(child, assistant)` | yes | `{sensitive}` | **BLOCKED** | +| 3 | `invoke_start(send-email)` | yes | `{sensitive}` | **BLOCKED** | +| 4 | `cascade_revoke(child, assistant)` | no | -- | cleaned | + +**Why it works**: The orphan is in a sealed disconnected subtree. It +cannot return data upward (parent is inactive). It cannot acquire new +capabilities (no active parent to grant them). It cannot invoke tools +it doesn't already have capabilities for. The `flow_confinement` and +`default_deny` safety properties hold independently of parent liveness. + +The only "cost" of the transient orphan window is wasted computation -- +the child might continue executing in-flight tools, but it can't +communicate the results to anyone. `cascade_revoke` cleans it up. + +--- + +## Scenario 6: Self-Exfiltrating Tool + +> **Attack**: Register a malicious tool that both reads sensitive data +> (high `conf_floor`) and has external egress -- a tool that is +> simultaneously the data source and the exfiltration channel. +> +> **Blocked by**: `flow_confinement` via **Check 2c** (self-flow gate) -- +> the tool's own confidentiality floor is checked against its own egress +> before the invocation starts. + +**Agents**: `root` -> `assistant` (holds `{evil_cap}`) + +**Malicious tool**: + +| Tool | conf_floor | endorsed | egress | requires | +|------|-----------|----------|--------|----------| +| `exfil-tool` | sensitive | no | `network_external` | `evil_cap` | + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + + Note over P: register_tool(exfil-tool) + Note over P: Tool metadata (immutable):
conf_floor=sensitive, egress=network_external + + A->>P: invoke_start(assistant, exfil-tool, inv1) + Note over P: Check 1: evil_cap -- pass + Note over P: Check 2a: taint={} -- no pairs + Note over P: Check 2b: no in-flight -- skip + Note over P: Check 2c: not endorsed, egress=network_external + Note over P: (conf_floor=sensitive, network_external) = DENY + Note over P: flow_override? -- no + P--xA: BLOCKED +``` + +| Step | Action | in_flight | taint | result | +|------|--------|-----------|-------|--------| +| 0 | `register_tool(exfil-tool)` | `{}` | `{}` | | +| 1 | `invoke_start(exfil-tool, inv1)` | `{}` | `{}` | **BLOCKED** (Check 2c) | + +**Why it works**: Check 2c exists specifically for this case. Even with +zero prior taint and no other in-flight tools, a tool that produces +sensitive data AND has external egress is blocked before it ever executes. +The self-flow check evaluates the tool's own `conf_floor` against its own +egress kinds. + +This means the tool registration itself is harmless -- the tool can exist +in the registry. But no agent can invoke it under a DENY policy for +`(sensitive, network_external)`. The threat is neutralized at invocation +time, not at registration time. + +--- + +## Summary: Safety Properties in Action + +| Scenario | Attack | Safety Property | Gate Check | +|----------|--------|----------------|------------| +| 1. Lethal Trifecta | read sensitive, send external | `flow_confinement` | 2a | +| 2. Racing the Taint | parallel read + send | `flow_confinement` | 2a (forward) + 2b (reverse) | +| 3. Taint Laundering | delegate read, receive clean | `flow_confinement` | `return_unendorsed` taint union | +| 4. Capability Escalation | invoke without caps | `default_deny` + `capability_subsumption` | 1 | +| 5. Orphan Escalation | return after parent revoked | `revocation_clean` | preconditions | +| 6. Self-Exfiltrating Tool | tool reads and sends | `flow_confinement` | 2c | diff --git a/tzimtzum/scenarios-happy-paths.md b/tzimtzum/scenarios-happy-paths.md new file mode 100644 index 0000000..bffb0a6 --- /dev/null +++ b/tzimtzum/scenarios-happy-paths.md @@ -0,0 +1,307 @@ +# Tzimtzum v2.3 -- Scenario Walkthroughs + +Concrete traces through the protocol's 9 actions. Each scenario shows +every action fired, the three-check gate evaluation, and the state after +each step. + +## Shared Universe + +### Tools + +| Tool | conf_floor | endorsed | egress | requires | +|------|-----------|----------|--------|----------| +| `read-db` | sensitive | no | -- | `db_read` | +| `send-email` | public | no | `network_external` | `email_send` | +| `search-web` | public | yes | `network_external` | `web_access` | +| `summarize` | public | yes | -- | -- | +| `post-slack` | public | no | `network_external` | `messaging` | +| `read-file` | internal | no | -- | `fs_read` | +| `query-api` | sensitive | no | `network_internal` | `api_access` | + +### Flow Policy + +| (level, egress) | mode | +|-----------------|------| +| (public, network_external) | ALLOW | +| (internal, network_external) | INSPECT | +| (sensitive, network_external) | **DENY** | +| (public, network_internal) | ALLOW | +| (internal, network_internal) | ALLOW | +| (sensitive, network_internal) | INSPECT | + +Unlisted pairs default to **DENY**. + +### How to Read the Gate Checks + +Every `invoke_start` evaluates three independent checks: + +1. **Check 1 (Capability)**: agent holds every capability the tool requires +2. **Check 2 (Flow gate)**: three sub-checks ensuring no (taint, egress) pair violates policy + - **2a**: existing speculative taint vs. new tool's egress + - **2b**: new tool's potential taint vs. existing in-flight tools' egress + - **2c**: new tool's own taint vs. its own egress (self-flow) +3. **Check 3 (Authorizer)**: external policy engine says yes + +--- + +## Scenario 1: Simple Search + +> An agent invokes an endorsed tool with no prior taint. The fast path -- +> every check is trivially satisfied. + +**Agents**: `root` -> `assistant` (holds `{web_access}`) + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant T as search-web + + A->>P: invoke_start(assistant, search-web, inv1) + Note over P: Check 1: web_access in {web_access} -- pass + Note over P: Check 2a: speculative_taint = {} -- nothing to check + Note over P: Check 2b: no in-flight tools -- nothing to check + Note over P: Check 2c: search-web is endorsed -- skip + Note over P: Check 3: authorizer_allows -- pass + P-->>A: inv1 is now in-flight + + T-->>P: search results + P->>A: invoke_complete(assistant, inv1) + Note over P: search-web is endorsed -- no taint added +``` + +| Step | Action | in_flight | taint | speculative_taint | +|------|--------|-----------|-------|-------------------| +| 0 | -- | `{}` | `{}` | `{}` | +| 1 | `invoke_start(search-web, inv1)` | `{inv1}` | `{}` | `{}` | +| 2 | `invoke_complete(inv1)` | `{}` | `{}` | `{}` | + +**Takeaway**: Endorsed tools are the fast path. No taint accumulates, +so future invocations face zero flow gate friction. + +--- + +## Scenario 2: Read Then Search (INSPECT Mode) + +> An agent reads a local file (gaining `internal` taint), then searches +> the web. The flow gate evaluates `(internal, network_external) = INSPECT` +> and the content gate certifies the search query doesn't leak the file data. + +**Agents**: `root` -> `assistant` (holds `{fs_read, web_access}`) + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant F as read-file + participant W as search-web + + A->>P: invoke_start(assistant, read-file, inv1) + Note over P: Check 1: fs_read -- pass + Note over P: Check 2a: speculative_taint = {} -- skip + Note over P: Check 2c: not endorsed, no egress -- no pairs + Note over P: Check 3: authorizer -- pass + P-->>A: inv1 in-flight + + F-->>P: file contents + P->>A: invoke_complete(assistant, inv1) + Note over P: not endorsed, conf_floor=internal -- taint += {internal} + + A->>P: invoke_start(assistant, search-web, inv2) + Note over P: Check 1: web_access -- pass + Note over P: Check 2a: taint={internal}, egress=network_external + Note over P: (internal, network_external) = INSPECT + Note over P: content_gate_passes(assistant, search-web) = true -- pass + Note over P: Check 2c: endorsed -- skip + Note over P: Check 3: authorizer -- pass + P-->>A: inv2 in-flight + + W-->>P: search results + P->>A: invoke_complete(assistant, inv2) + Note over P: endorsed -- no taint added +``` + +| Step | Action | in_flight | taint | speculative_taint | +|------|--------|-----------|-------|-------------------| +| 0 | -- | `{}` | `{}` | `{}` | +| 1 | `invoke_start(read-file, inv1)` | `{inv1}` | `{}` | `{internal}` | +| 2 | `invoke_complete(inv1)` | `{}` | `{internal}` | `{internal}` | +| 3 | `invoke_start(search-web, inv2)` | `{inv2}` | `{internal}` | `{internal}` | +| 4 | `invoke_complete(inv2)` | `{}` | `{internal}` | `{internal}` | + +**Takeaway**: INSPECT is the graduated middle ground between ALLOW and DENY. +The content gate (deterministic inspection pipeline) certifies the outgoing +arguments don't contain the tainted data. If the content gate fails, +the invocation is blocked -- same as DENY for that specific call. + +--- + +## Scenario 3: Parallel Safe Invocations + +> An agent starts `read-file` and `summarize` concurrently. Speculative +> taint from the in-flight `read-file` is conservative but doesn't block +> `summarize` because it has no egress channel. + +**Agents**: `root` -> `assistant` (holds `{fs_read}`) + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant F as read-file + participant S as summarize + + A->>P: invoke_start(assistant, read-file, inv1) + Note over P: All checks pass (no taint, no egress) + P-->>A: inv1 in-flight + + A->>P: invoke_start(assistant, summarize, inv2) + Note over P: Check 2a: speculative_taint={internal} (from inv1) + Note over P: summarize has no egress -- no (level, egress) pairs + Note over P: Check 2b: inv1 in-flight, read-file has no egress -- skip + Note over P: Check 2c: summarize is endorsed -- skip + P-->>A: inv2 in-flight + + S-->>P: summary + P->>A: invoke_complete(assistant, inv2) + Note over P: endorsed -- no taint + + F-->>P: file contents + P->>A: invoke_complete(assistant, inv1) + Note over P: not endorsed -- taint += {internal} +``` + +| Step | Action | in_flight | taint | speculative_taint | +|------|--------|-----------|-------|-------------------| +| 0 | -- | `{}` | `{}` | `{}` | +| 1 | `invoke_start(read-file, inv1)` | `{inv1}` | `{}` | `{internal}` | +| 2 | `invoke_start(summarize, inv2)` | `{inv1, inv2}` | `{}` | `{internal}` | +| 3 | `invoke_complete(inv2)` | `{inv1}` | `{}` | `{internal}` | +| 4 | `invoke_complete(inv1)` | `{}` | `{internal}` | `{internal}` | + +**Takeaway**: Speculative taint is conservative (worst-case from all +in-flight non-endorsed tools) but precise about what it blocks. Tools +without egress never conflict with taint, so they always run in parallel +with anything. + +--- + +## Scenario 4: Delegation with Endorsed Return + +> Assistant delegates a researcher child for web search. The child uses +> only endorsed tools, so the parent receives zero taint on return. + +**Agents**: `root` -> `assistant` (holds `{web_access}`) -> `researcher` + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant C as researcher + + A->>P: delegate(assistant, researcher) + Note over P: assistant active, researcher fresh -- pass + P-->>C: researcher active, caps={} + + A->>P: grant_capability(assistant, researcher, web_access) + Note over P: assistant holds web_access -- pass + P-->>C: caps={web_access} + + C->>P: invoke_start(researcher, search-web, inv1) + Note over P: All checks pass + P-->>C: inv1 in-flight + P->>C: invoke_complete(researcher, inv1) + Note over P: endorsed -- no taint + + C->>P: invoke_start(researcher, summarize, inv2) + P-->>C: inv2 in-flight + P->>C: invoke_complete(researcher, inv2) + Note over P: endorsed -- no taint + + C->>P: return_endorsed(researcher, assistant) + Note over P: researcher in-flight={} -- pass + Note over P: researcher taint={} -- endorsed return + Note over P: assistant taint unchanged + P-->>A: bounded result (no taint transfer) + + A->>P: revoke(assistant, researcher) + Note over P: researcher deactivated, state cleaned +``` + +| Step | Action | researcher active | researcher taint | assistant taint | +|------|--------|:-:|:-:|:-:| +| 0 | -- | no | -- | `{}` | +| 1 | `delegate` | yes | `{}` | `{}` | +| 2 | `grant_capability(web_access)` | yes | `{}` | `{}` | +| 3 | `invoke_start(search-web)` | yes | `{}` | `{}` | +| 4 | `invoke_complete(search-web)` | yes | `{}` | `{}` | +| 5 | `invoke_start(summarize)` | yes | `{}` | `{}` | +| 6 | `invoke_complete(summarize)` | yes | `{}` | `{}` | +| 7 | `return_endorsed` | yes | `{}` | `{}` | +| 8 | `revoke` | no | -- | `{}` | + +**Takeaway**: Endorsed tools + endorsed return = zero taint propagation +through the entire delegation chain. This is the ideal pattern for +isolated tasks: the parent stays clean regardless of how many endorsed +tools the child uses. + +--- + +## Scenario 5: Full Lifecycle with Taint Propagation + +> Assistant delegates a child to read from a database. The child acquires +> sensitive taint, returns the data (unendorsed), and the parent inherits +> the taint. The child is then revoked. + +**Agents**: `root` -> `assistant` (holds `{db_read}`) -> `data-reader` + +```mermaid +sequenceDiagram + participant A as assistant + participant P as Protocol + participant C as data-reader + + A->>P: delegate(assistant, data-reader) + P-->>C: data-reader active, caps={} + + A->>P: grant_capability(assistant, data-reader, db_read) + P-->>C: caps={db_read} + + C->>P: invoke_start(data-reader, read-db, inv1) + Note over P: All checks pass (no taint, no egress on read-db) + P-->>C: inv1 in-flight + + P->>C: invoke_complete(data-reader, inv1) + Note over P: not endorsed, conf_floor=sensitive + Note over P: data-reader taint += {sensitive} + + C->>P: return_unendorsed(data-reader, assistant) + Note over P: data-reader in-flight={} -- pass + Note over P: Flow gate: child taint={sensitive} + Note over P: assistant in-flight={} -- no pairs to check + Note over P: assistant taint = {} ∪ {sensitive} = {sensitive} + P-->>A: unbounded result (taint propagated) + + A->>P: revoke(assistant, data-reader) + Note over P: data-reader deactivated, all state cleaned +``` + +| Step | Action | data-reader active | data-reader taint | assistant taint | +|------|--------|:-:|:-:|:-:| +| 0 | -- | no | -- | `{}` | +| 1 | `delegate` | yes | `{}` | `{}` | +| 2 | `grant_capability(db_read)` | yes | `{}` | `{}` | +| 3 | `invoke_start(read-db)` | yes | `{}` | `{}` | +| 4 | `invoke_complete(read-db)` | yes | `{sensitive}` | `{}` | +| 5 | `return_unendorsed` | yes | `{sensitive}` | `{sensitive}` | +| 6 | `revoke` | no | -- | `{sensitive}` | + +**Takeaway**: `return_unendorsed` is the taint propagation mechanism. +The parent inherits the child's full taint set via set union. This is +conservative but prevents taint laundering: you cannot avoid acquiring +taint by delegating the sensitive read to a child. The data flows up, +and the taint follows it. + +Note that after step 6, the assistant carries `{sensitive}` taint +permanently. Any future invocation of a tool with `network_external` +egress will be evaluated against `(sensitive, network_external) = DENY`. diff --git a/tzimtzum/verification-report.md b/tzimtzum/verification-report.md new file mode 100644 index 0000000..1b6f1b3 --- /dev/null +++ b/tzimtzum/verification-report.md @@ -0,0 +1,47 @@ +# Tzimtzum v2.3 -- Verification Report + +| | | +|---|---| +| **Spec** | `TzimtzumV2.lean` (578 lines) | +| **Prover** | Lean 4 + Veil 2.0 + cvc5 (SMT) | +| **Date** | 2026-03-13 10:07 UTC | +| **Status** | **PASSED** | +| **VCs** | 220/220 passed | + +## Safety Properties (7) + +| Property | Init | sentinel_elevate_taint | invoke_complete | grant_capability | delegate | register_tool | return_endorsed | invoke_start | cascade_revoke | return_unendorsed | revoke | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `root_always_active` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `default_deny` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `flow_confinement` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `flow_confinement_weak` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `capability_subsumption` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `revocation_clean` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `taint_integrity` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | + +## Strengthening Invariants (12) + +| Property | Init | sentinel_elevate_taint | invoke_complete | grant_capability | delegate | register_tool | return_endorsed | invoke_start | cascade_revoke | return_unendorsed | revoke | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `parent_implies_active` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `single_parent` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `no_self_parent` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `root_no_parent` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `in_flight_active` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `in_flight_registered` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `in_flight_unique` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `root_all_caps` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `root_no_in_flight` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `ghost_invoked_sound` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `ghost_received_sound` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | +| `in_flight_flow_compat` | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | PASS | + +## Summary + +- 7 safety properties +- 12 strengthening invariants +- 10 actions verified +- 220 verification conditions total +- 220 passed, 0 failed + diff --git a/tzimtzum/verify_report.py b/tzimtzum/verify_report.py new file mode 100644 index 0000000..c25b1be --- /dev/null +++ b/tzimtzum/verify_report.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Parse Veil #check_invariants build output and produce a verification report.""" + +import sys +import re +from datetime import datetime, timezone +from pathlib import Path +from collections import OrderedDict + + +def parse_build_log(text: str) -> dict: + """Extract #check_invariants results from plain text build output.""" + result = {"init": OrderedDict(), "actions": OrderedDict()} + + init_match = re.search( + r"Initialization must establish the invariant:\s*\n((?:\s+\w+\s+\.\.\.\s+[✅❌]\n?)+)", + text, + ) + if init_match: + for match in re.finditer(r"(\w+)\s+\.\.\.\s+(✅|❌)", init_match.group(1)): + result["init"][match.group(1)] = match.group(2) == "✅" + + actions_match = re.search( + r"The following set of actions must preserve the invariant " + r"and successfully terminate:\s*\n([\s\S]+?)(?:\nwarning:|\nerror:|\Z)", + text, + ) + if actions_match: + current_action = None + for line in actions_match.group(1).splitlines(): + line = line.rstrip() + if not line.strip(): + continue + prop_match = re.match(r"\s{4,}(\w+)\s+\.\.\.\s+(✅|❌)", line) + action_match = re.match(r" (\w+)\s*$", line) + if prop_match and current_action: + result["actions"].setdefault(current_action, OrderedDict()) + result["actions"][current_action][prop_match.group(1)] = ( + prop_match.group(2) == "✅" + ) + elif action_match: + current_action = action_match.group(1) + + return result + + +def parse_timing(text: str) -> str | None: + """Extract #time gen_spec timing.""" + match = re.search(r"(\d+\.\d+s)", text) + if match: + return match.group(1) + return None + + +def classify_properties(names: list[str]) -> tuple[list[str], list[str], list[str]]: + """Classify property names into safeties, invariants, and internal.""" + known_safeties = { + "root_always_active", "default_deny", "flow_confinement", + "flow_confinement_weak", "capability_subsumption", + "revocation_clean", "taint_integrity", + } + known_internal = {"doesNotThrow"} + + safeties, invariants, internal = [], [], [] + for name in names: + if name in known_internal: + internal.append(name) + elif name in known_safeties: + safeties.append(name) + else: + invariants.append(name) + + return safeties, invariants, internal + + +def count_lines(spec_path: Path) -> int: + if not spec_path.exists(): + return 0 + return sum(1 for line in spec_path.read_text().splitlines() if line.strip()) + + +def render_report(data: dict, spec_path: Path, timing: str | None) -> str: + lines = [] + + all_props = list(data.get("init", {}).keys()) + actions = list(data.get("actions", {}).keys()) + safeties, invariants, internal = classify_properties(all_props) + + total_vcs, passed_vcs, failed_vcs = 0, 0, 0 + for ok in data.get("init", {}).values(): + total_vcs += 1 + passed_vcs += 1 if ok else 0 + failed_vcs += 0 if ok else 1 + for action_props in data.get("actions", {}).values(): + for ok in action_props.values(): + total_vcs += 1 + passed_vcs += 1 if ok else 0 + failed_vcs += 0 if ok else 1 + + status = "PASSED" if failed_vcs == 0 else "FAILED" + loc = count_lines(spec_path) + now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") + + lines.append("# Tzimtzum v2.3 -- Verification Report\n") + lines.append("| | |") + lines.append("|---|---|") + lines.append(f"| **Spec** | `{spec_path.name}` ({loc} lines) |") + lines.append("| **Prover** | Lean 4 + Veil 2.0 + cvc5 (SMT) |") + lines.append(f"| **Date** | {now} |") + lines.append(f"| **Status** | **{status}** |") + lines.append(f"| **VCs** | {passed_vcs}/{total_vcs} passed |") + if timing: + lines.append(f"| **Time** | {timing} |") + lines.append("") + + def mark(ok: bool | None) -> str: + if ok is True: + return "PASS" + if ok is False: + return "FAIL" + return "-" + + def render_table(props: list[str], label: str): + if not props: + return + lines.append(f"## {label} ({len(props)})\n") + header = ["Property", "Init"] + actions + lines.append("| " + " | ".join(header) + " |") + lines.append("| " + " | ".join(["---"] * len(header)) + " |") + for prop in props: + row = [f"`{prop}`"] + row.append(mark(data.get("init", {}).get(prop))) + for action in actions: + row.append(mark(data.get("actions", {}).get(action, {}).get(prop))) + lines.append("| " + " | ".join(row) + " |") + lines.append("") + + render_table(safeties, "Safety Properties") + render_table(invariants, "Strengthening Invariants") + + lines.append("## Summary\n") + lines.append(f"- {len(safeties)} safety properties") + lines.append(f"- {len(invariants)} strengthening invariants") + lines.append(f"- {len(actions)} actions verified") + lines.append(f"- {total_vcs} verification conditions total") + lines.append(f"- {passed_vcs} passed, {failed_vcs} failed") + lines.append("") + + return "\n".join(lines) + + +def main(): + spec_path = Path("TzimtzumV2.lean") + text = sys.stdin.read() + + data = parse_build_log(text) + if not data.get("init") and not data.get("actions"): + print("No verification results found in build output.", file=sys.stderr) + sys.exit(1) + + timing = parse_timing(text) + report = render_report(data, spec_path, timing) + print(report) + + +if __name__ == "__main__": + main()