diff --git a/.ci-artifact-pins.txt b/.ci-artifact-pins.txt new file mode 100644 index 0000000..2126dd3 --- /dev/null +++ b/.ci-artifact-pins.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MPL-2.0 +# Pinned artifacts for the slm-real-inference CI job (SHA-256 verified at download time). +# llama.cpp nightly binary (ggml-org/llama.cpp release b11100, 2026-09-22): +LLAMA_CPP_TAG=b11100 +LLAMA_CPP_UBUNTU_X64_URL=https://github.com/ggml-org/llama.cpp/releases/download/b11100/llama-b11100-bin-ubuntu-x64.tar.gz +LLAMA_CPP_UBUNTU_X64_SHA256=a836c913236ab4533ef9aaf49f0e1ad2955c8159869d7eec1a9072a92e13d61b +# Smoke model: Qwen2.5-0.5B-Instruct Q4_K_M (official Qwen repo, commit 9217f5db79a29953eb74d5343926648285ec7e67). +# Chosen over SmolLM2-135M-Instruct Q4_K_M (bartowski mirror) after on-runner evaluation: +# the 135M model loops `0.000000...` at temp 0 and never closes the verdict JSON within +# the token budget; Qwen2.5-0.5B emits a valid object on the first attempt. +GGUF_MODEL_URL=https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q4_k_m.gguf +GGUF_MODEL_SHA256=74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh old mode 100755 new mode 100644 diff --git a/.github/hooks/validate-a2ml.sh b/.github/hooks/validate-a2ml.sh old mode 100755 new mode 100644 diff --git a/.github/hooks/validate-k9.sh b/.github/hooks/validate-k9.sh old mode 100755 new mode 100644 diff --git a/.github/workflows/arbiter-ci.yml b/.github/workflows/arbiter-ci.yml new file mode 100644 index 0000000..3e36898 --- /dev/null +++ b/.github/workflows/arbiter-ci.yml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Arbiter CI +on: + push: + branches: [main, master] + paths: + - "src/arbiter/**" + - "src/contract/src/arbiter.rs" + - ".github/workflows/arbiter-ci.yml" + pull_request: + branches: [main, master] + paths: + - "src/arbiter/**" + - ".github/workflows/arbiter-ci.yml" +permissions: + contents: read +jobs: + elixir: + name: OTP arbiter (format, deps, ExUnit, escript smoke) + runs-on: ubuntu-latest + timeout-minutes: 25 + permissions: + contents: read + defaults: + run: + working-directory: src/arbiter + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + otp-version: "27" + elixir-version: "1.18" + - name: Install build tooling + run: mix local.hex --force && mix local.rebar --force + - name: Check formatting + run: mix format --check-formatted + # NOTE: no mix.lock is committed yet (cannot be generated without a + # local OTP toolchain). deps are resolved fresh; lockfile to be added + # by a maintainer — see docs/UPSTREAM-DELIVERY.adoc. + - name: Fetch dependencies + run: mix deps.get + - name: Run ExUnit suite + run: mix test + - name: Build escript + run: mix escript.build + - name: Protocol smoke — valid allow round-trip, exactly one audit record + run: | + set -euo pipefail + AUDIT_DIR="$(mktemp -d)" + export CONATIVE_AUDIT_PATH="$AUDIT_DIR/audit.jsonl" + RESPONSE="$(printf '%s\n' \ + '{"protocol_version":1,"request_id":"smoke-1","llm":{"confidence":0.95},"slm":{"violation_confidence":0.05},"oracle":{"verdict":"allow"}}' \ + | ./conative_arbiter)" + echo "response: $RESPONSE" + echo "$RESPONSE" | grep -q '"verdict":"allow"' + echo "$RESPONSE" | grep -q '"request_id":"smoke-1"' + echo "$RESPONSE" | grep -q '"audit_recorded":true' + test "$(wc -l < "$CONATIVE_AUDIT_PATH")" -eq 1 + - name: Protocol smoke — malformed input fails closed (error, never a verdict) + run: | + set -euo pipefail + AUDIT_DIR="$(mktemp -d)" + export CONATIVE_AUDIT_PATH="$AUDIT_DIR/audit.jsonl" + RESPONSE="$(printf '%s\n' 'not json' | ./conative_arbiter || true)" + echo "response: $RESPONSE" + echo "$RESPONSE" | grep -q '"error"' + ! echo "$RESPONSE" | grep -q '"verdict"' + test ! -s "$CONATIVE_AUDIT_PATH" || test "$(wc -l < "$CONATIVE_AUDIT_PATH")" -eq 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7f8543..ad14267 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,3 +73,22 @@ jobs: with: name: conative-cli path: target/release/conative + nickel-native: + name: Native Nickel policy backend + # Dedicated job: nickel-lang-parser does not link-compile under ~2 GB + # (OOM-killed, reproduced), so this must run on a full-size hosted + # runner (16 GB). The exact command is part of the delivery contract — + # do not "optimise" it (single job, no debuginfo, warnings as errors, + # lib tests only, locked dependency set). + runs-on: ubuntu-latest + timeout-minutes: 45 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + with: + key: nickel-native + - name: Test policy-oracle with native Nickel (pinned command) + run: CARGO_BUILD_JOBS=1 RUSTFLAGS="-C debuginfo=0 -Dwarnings" cargo test -p policy-oracle --features nickel --lib --locked diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml old mode 100755 new mode 100644 diff --git a/.github/workflows/slm-real-inference.yml b/.github/workflows/slm-real-inference.yml new file mode 100644 index 0000000..9e21083 --- /dev/null +++ b/.github/workflows/slm-real-inference.yml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: MPL-2.0 +name: SLM Real Inference +# Real-model smoke tests. Artifacts are pinned + SHA-256 verified and are +# never committed to the repository (see .ci-artifact-pins.txt). +# +# - `local-gguf`: pinned llama.cpp binary + pinned Qwen2.5-0.5B GGUF, runs the +# ignored llama-cli round-trip AND the HTTP adapter against a loopback +# llama-server. Safe on every PR: no secrets involved. +# - `remote-provider`: the live remote-provider smoke. Runs ONLY on +# workflow_dispatch or pushes to the protected main branch of the upstream +# repository, inside the `slm-remote-production` GitHub Environment (which +# holds CONATIVE_SLM_ENDPOINT / CONATIVE_SLM_MODEL_NAME / SLM_API_KEY and +# requires reviewer approval). Never on pull requests — fork PR code must +# never see these secrets. +on: + pull_request: + branches: [main, master] + push: + branches: [main, master] + workflow_dispatch: +permissions: + contents: read +env: + CARGO_TERM_COLOR: always +jobs: + local-gguf: + name: Local GGUF (pinned llama.cpp + pinned model) + runs-on: ubuntu-latest + timeout-minutes: 45 + permissions: + contents: read + env: + LLAMA_CPP_UBUNTU_X64_URL: https://github.com/ggml-org/llama.cpp/releases/download/b11100/llama-b11100-bin-ubuntu-x64.tar.gz + LLAMA_CPP_UBUNTU_X64_SHA256: a836c913236ab4533ef9aaf49f0e1ad2955c8159869d7eec1a9072a92e13d61b + GGUF_MODEL_URL: https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q4_k_m.gguf + GGUF_MODEL_SHA256: 74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + with: + key: slm-real-inference + - name: Cache pinned artifacts + id: artifacts + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ${{ runner.temp }}/slm-artifacts + key: slm-artifacts-${{ env.LLAMA_CPP_UBUNTU_X64_SHA256 }}-${{ env.GGUF_MODEL_SHA256 }} + - name: Download + verify llama.cpp binary + if: steps.artifacts.outputs.cache-hit != 'true' + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/slm-artifacts" + cd "$RUNNER_TEMP/slm-artifacts" + curl -fsSL --retry 3 -o llama-cli.tar.gz "$LLAMA_CPP_UBUNTU_X64_URL" + echo "$LLAMA_CPP_UBUNTU_X64_SHA256 llama-cli.tar.gz" | sha256sum -c - + mkdir -p bin && tar xzf llama-cli.tar.gz -C bin --strip-components=1 + - name: Download + verify GGUF model + if: steps.artifacts.outputs.cache-hit != 'true' + run: | + set -euo pipefail + cd "$RUNNER_TEMP/slm-artifacts" + curl -fsSL --retry 3 -o model.gguf "$GGUF_MODEL_URL" + echo "$GGUF_MODEL_SHA256 model.gguf" | sha256sum -c - + - name: llama-cli round-trip (real model, ignored-by-default test) + run: | + set -euo pipefail + export LD_LIBRARY_PATH="$RUNNER_TEMP/slm-artifacts/bin" + export CONATIVE_LLAMA_CLI="$RUNNER_TEMP/slm-artifacts/bin/llama-cli" + export CONATIVE_GGUF_MODEL="$RUNNER_TEMP/slm-artifacts/model.gguf" + cargo test -p slm-evaluator --test real_inference real_llama -- --ignored --nocapture + - name: HTTP adapter round-trip via loopback llama-server + run: | + set -euo pipefail + export LD_LIBRARY_PATH="$RUNNER_TEMP/slm-artifacts/bin" + "$RUNNER_TEMP/slm-artifacts/bin/llama-server" \ + -m "$RUNNER_TEMP/slm-artifacts/model.gguf" \ + --host 127.0.0.1 --port 18080 -t 4 -c 2048 --log-disable & + SERVER_PID=$! + trap 'kill $SERVER_PID 2>/dev/null || true' EXIT + for i in $(seq 1 120); do + curl -fsS http://127.0.0.1:18080/health >/dev/null 2>&1 && break + sleep 1 + done + export CONATIVE_SLM_ENDPOINT=http://127.0.0.1:18080 + cargo test -p slm-evaluator --features http --test real_inference real_http -- --ignored --nocapture + remote-provider: + name: Remote provider (protected environment, approval-gated) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + environment: slm-remote-production + # Never on pull requests (fork code must never touch secrets); never on + # forks of the repository. Only dispatched runs or protected main pushes. + if: >- + github.repository == 'hyperpolymath/conative-gating' && + (github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && github.ref == 'refs/heads/main')) + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 + with: + key: slm-remote-provider + - name: Remote provider round-trip (creds from Environment secrets) + env: + CONATIVE_SLM_ENDPOINT: ${{ secrets.CONATIVE_SLM_ENDPOINT }} + CONATIVE_SLM_MODEL_NAME: ${{ secrets.CONATIVE_SLM_MODEL_NAME }} + SLM_API_KEY: ${{ secrets.SLM_API_KEY }} + run: | + set -euo pipefail + : "${CONATIVE_SLM_ENDPOINT:?set in the slm-remote-production environment}" + : "${SLM_API_KEY:?set in the slm-remote-production environment}" + cargo test -p slm-evaluator --features http --test real_inference real_http -- --ignored --nocapture diff --git a/.gitignore b/.gitignore index 3846b37..2294218 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,10 @@ Thumbs.db # Dependencies /node_modules/ -/vendor/ +# /vendor/ is ignored except the reviewed Bunsenite vendor fork used by the +# `nickel` feature (see vendor/bunsenite/VENDOR.adoc) +/vendor/* +!/vendor/bunsenite/ /deps/ /.elixir_ls/ diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 32015d6..4e53bbd 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,6 +18,27 @@ https://semver.org/spec/v2.0.0.html[Semantic Versioning]. ==== Added +* feat(oracle): native Nickel policy backend via vendored Bunsenite + (`nickel` feature, default-features-off `nickel-lang-core 0.18.0`), + fail-closed `import` rejection, `Policy::from_policy_file` dispatch — + see `docs/NICKEL-POLICY.adoc` +* feat(slm): provider layer with verdict contract + correlation + preservation; `LlamaCppProvider` (pinned llama.cpp CLI, `--single-turn` + hardened) and feature-gated `HttpSlmProvider` (`http`, https-or-loopback, + `SLM_API_KEY` env-only) — see `docs/SLM_PROVIDERS.adoc` +* feat(contract): `ContractRunner::evaluate_with_provider` — terminal + oracle blocks, asymmetric Warn addend, threshold matrix, fail-closed + provider errors (`Sys902` Escalate) +* feat(arbiter): OTP consensus arbiter escript (protocol v1) with durable + JSONL audit sink (flush-before-ack, rotation, fail-closed, bounded + history, no proposal content) — see `docs/ARBITER_PROTOCOL.adoc` +* feat(contract): Rust arbiter client enforcing `audit_recorded: true` +* test(slm): env-gated real-inference smokes (local GGUF + HTTP adapter) + with pinned, SHA-256-verified CI artifacts +* test(contract): generative proptest suite (terminality, thresholds, + fail-closed, determinism, correlation, concurrency) +* ci: `nickel-native` job (exact pinned command), `arbiter-ci` workflow, + `slm-real-inference` workflow with approval-gated remote-provider job * feat(crg): add crg-grade and crg-badge justfile recipes * feat: add stapeln.toml container definition * feat: deploy UX Manifesto infrastructure diff --git a/Cargo.lock b/Cargo.lock index e96911d..a9494c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alloca" version = "0.4.0" @@ -37,9 +58,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -58,9 +79,9 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -91,17 +112,104 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[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 = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[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 = "2.10.0" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + +[[package]] +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "bumpalo" @@ -109,6 +217,26 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bunsenite" +version = "1.0.2" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "miette", + "nickel-lang-core", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + [[package]] name = "bytes" version = "1.11.1" @@ -137,6 +265,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.42" @@ -180,9 +325,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -190,9 +335,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -200,7 +345,7 @@ dependencies = [ "strsim", "terminal_size", "unicase", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] @@ -214,14 +359,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -240,6 +385,27 @@ dependencies = [ "roff", ] +[[package]] +name = "codespan" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583f52b0658b321b25fd6b209b6c76cf058f433071297de64e5980c3d9aad937" +dependencies = [ + "codespan-reporting", + "serde", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -257,19 +423,49 @@ dependencies = [ "criterion", "gating-contract", "policy-oracle", + "proptest", "serde", "serde_json", + "slm-evaluator", "tracing", "tracing-subscriber", "uuid", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[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 = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "criterion" version = "0.8.2" @@ -282,7 +478,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools", + "itertools 0.13.0", "num-traits", "oorandom", "page_size", @@ -302,7 +498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools", + "itertools 0.13.0", ] [[package]] @@ -336,12 +532,52 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[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 = "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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[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 = "equivalent" version = "1.0.2" @@ -358,18 +594,100 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "find-msvc-tools" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "gating-contract" version = "0.1.0" @@ -379,12 +697,47 @@ dependencies = [ "serde", "serde_json", "slm-evaluator", - "thiserror", + "thiserror 2.0.17", "tokio", "tracing", "uuid", ] +[[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", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.1" @@ -392,12 +745,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "glob" version = "0.3.3" @@ -421,7 +783,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -429,6 +791,24 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "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 = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -437,36 +817,292 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "iana-time-zone" -version = "0.1.64" +name = "http" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "bytes", + "itoa", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "cc", + "bytes", + "http", ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + +[[package]] name = "indexmap" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -478,6 +1114,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -493,6 +1150,15 @@ dependencies = [ "either", ] +[[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" @@ -509,6 +1175,56 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json_scanner" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0a2dc336065c75719cffd3c6c929e0ec4ed85b92b8248a7bbd999acb0e419c" +dependencies = [ + "memchr", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[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 0.14.0", + "lalrpop-util", + "petgraph", + "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 = "lazy_static" version = "1.5.0" @@ -523,15 +1239,27 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -548,12 +1276,162 @@ 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 2.0.119", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "malachite" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba00455c89cf785ef73a0dfc941ab3c21211963c86130c0bd48d7994b942707" +dependencies = [ + "malachite-base", + "malachite-float", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f44099731f17094b07825c88ccb5fbd1bfa1f82fafff7daa33e8b8652db16e" +dependencies = [ + "hashbrown 0.16.1", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-float" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23982acc6f68aa384504a44d112f42fc82207035272f23485220a861e2cf1af" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", + "malachite-q", + "serde", +] + +[[package]] +name = "malachite-nz" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a137660cdba20f136c8a223125f08088adb4e0b72fbb8466f08c43e31cc0427d" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "serde", + "wide", +] + +[[package]] +name = "malachite-q" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcbeed95e34c0fcc3864ccd146e129cbbf7de1513d3afbcfb47c7674c82d94" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "malachite-nz", + "serde", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "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 2.0.119", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.1.1" @@ -565,6 +1443,91 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nickel-lang-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692d8a2ba34c633bc37e704dc94f4ca33edaa8fbf6d08efdcadb81db333ccdb6" +dependencies = [ + "base64", + "bumpalo", + "codespan", + "codespan-reporting", + "colorchoice", + "indexmap", + "indoc", + "json_scanner", + "lalrpop", + "lalrpop-util", + "logos", + "malachite", + "malachite-q", + "md-5", + "nickel-lang-parser", + "nickel-lang-vector", + "once_cell", + "ouroboros", + "paste", + "pretty", + "regex", + "saphyr-parser", + "serde", + "serde_json", + "serde_yaml", + "sha-1", + "sha2", + "simple-counter", + "smallvec", + "strip-ansi-escapes", + "strsim", + "toml", + "toml_edit", + "typed-arena", + "unicode-segmentation", +] + +[[package]] +name = "nickel-lang-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7aaf73e60b66ef4fffc969b0e4e419a15a029525f9b53f2f5cc0ca41bbe17ff" +dependencies = [ + "bumpalo", + "codespan", + "codespan-reporting", + "indexmap", + "lalrpop", + "lalrpop-util", + "logos", + "malachite", + "nickel-lang-vector", + "ouroboros", + "pretty", + "regex", + "saphyr-parser", + "serde", + "serde_json", + "simple-counter", + "toml_edit", + "typed-arena", +] + +[[package]] +name = "nickel-lang-vector" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f243832286908d8873add24a905d6732ffabd6cfb2bf74cb18d667e892e279" +dependencies = [ + "imbl-sized-chunks", + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -583,6 +1546,15 @@ dependencies = [ "autocfg", ] +[[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" @@ -601,6 +1573,36 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "owo-colors" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" + [[package]] name = "page_size" version = "0.6.0" @@ -634,6 +1636,43 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[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", +] + +[[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-lite" version = "0.2.16" @@ -641,80 +1680,276 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] -name = "plotters" -version = "0.3.7" +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "policy-oracle" +version = "0.1.0" +dependencies = [ + "bunsenite", + "glob", + "regex", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "serde_core", + "writeable", + "zerovec", +] + +[[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 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "version_check", + "yansi", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.5", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" +dependencies = [ + "bytes", + "getrandom 0.4.1", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", + "proc-macro2", ] [[package]] -name = "plotters-backend" -version = "0.3.7" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "plotters-svg" -version = "0.3.7" +name = "rand" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "plotters-backend", + "rand_chacha", + "rand_core 0.9.5", ] [[package]] -name = "policy-oracle" -version = "0.1.0" +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ - "glob", - "regex", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "uuid", + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.1", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "proc-macro2", - "syn", + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] -name = "proc-macro2" -version = "1.0.103" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "unicode-ident", + "getrandom 0.3.4", ] [[package]] -name = "quote" -version = "1.0.42" +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "proc-macro2", + "rand_core 0.10.1", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] [[package]] name = "rayon" @@ -774,17 +2009,83 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[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 = "roff" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189" +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags", "errno", @@ -793,12 +2094,74 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +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 = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42c6efa15875e6ecb39ca61fb0b0c1a40b84fac5a5ffe71eef7d1000c8eb3f5f" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -808,6 +2171,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "saphyr-parser" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb771b59f6b1985d1406325ec28f97cfb14256abcec4fdfb37b36a1766d6af7" +dependencies = [ + "arraydeque", + "hashlink", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -847,7 +2220,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -863,6 +2236,72 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +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_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -888,13 +2327,32 @@ dependencies = [ "libc", ] +[[package]] +name = "simple-counter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb57743b52ea059937169c0061d70298fe2df1d2c988b44caae79dd979d9b49" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "slm-evaluator" version = "0.1.0" dependencies = [ + "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", "tokio", "tracing", "uuid", @@ -916,23 +2374,145 @@ dependencies = [ "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 = "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 = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[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 = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" -version = "2.0.111" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" 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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +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 = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.4.3" @@ -943,13 +2523,43 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "textwrap" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" +dependencies = [ + "icu_segmenter", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -960,7 +2570,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -972,6 +2582,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -982,6 +2603,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + [[package]] name = "tokio" version = "1.50.0" @@ -1007,9 +2634,116 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "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", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[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_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", ] +[[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" @@ -1029,7 +2763,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1067,11 +2801,35 @@ dependencies = [ "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.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -1079,6 +2837,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[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" @@ -1091,6 +2861,36 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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" @@ -1103,7 +2903,7 @@ version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -1115,6 +2915,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -1125,6 +2949,15 @@ dependencies = [ "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" @@ -1162,6 +2995,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.106" @@ -1181,7 +3027,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1238,6 +3084,35 @@ dependencies = [ "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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wide" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d920ac99c3c8edce110cb8d07dbb324d6d026011dce85b1e9355b70f0adacc4f" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1290,7 +3165,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1301,7 +3176,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1328,13 +3203,22 @@ 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.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -1346,6 +3230,22 @@ 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" @@ -1353,64 +3253,127 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "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_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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -1433,7 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -1444,10 +3407,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", - "syn", + "syn 2.0.119", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1463,7 +3426,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1505,6 +3468,41 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -1522,7 +3520,69 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e3ed4eb..66ac74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,13 @@ members = [ "src/slm", "src/contract", ] +# The vendored Bunsenite fork is a path dependency of policy-oracle (optional, +# behind the `nickel` feature). Exclude it from workspace membership so that +# plain `cargo check/test --workspace` does not pull in the Nickel dependency +# tree; it is built only when the `nickel` feature is enabled. +exclude = [ + "vendor/bunsenite", +] [workspace.dependencies] tokio = { version = "1", features = ["full"] } @@ -29,9 +36,17 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_mangen = "0.3" +[features] +# Native Nickel (.ncl) policy loading in the CLI (policy-oracle/nickel). +default = [] +nickel = ["policy-oracle/nickel"] +# Real HTTP SLM provider for `contract eval --slm` (slm-evaluator/http). +slm-http = ["slm-evaluator/http"] + [dependencies] policy-oracle = { path = "src/oracle" } gating-contract = { path = "src/contract" } +slm-evaluator = { path = "src/slm" } clap.workspace = true chrono.workspace = true clap_complete.workspace = true @@ -44,6 +59,7 @@ tracing-subscriber.workspace = true [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } +proptest = "1" [[bench]] name = "oracle_bench" diff --git a/README.adoc b/README.adoc index 12ddcde..3953f35 100644 --- a/README.adoc +++ b/README.adoc @@ -332,6 +332,9 @@ Copyright (C) 2025 Jonathan D.A. Jewell * link:docs/ARCHITECTURE.md[Full Architecture Specification] * link:docs/MAAF_INTEGRATION.adoc[MAAF Integration] * link:docs/STATE_ECOSYSTEM_SCHEMA.adoc[STATE/ECOSYSTEM Schema] +* link:docs/NICKEL-POLICY.adoc[Native Nickel Policy Backend] +* link:docs/SLM_PROVIDERS.adoc[SLM Provider Backends] +* link:docs/ARBITER_PROTOCOL.adoc[Consensus Arbiter Protocol] == Architecture diff --git a/ROADMAP.adoc b/ROADMAP.adoc index d42a0ad..a282aa8 100644 --- a/ROADMAP.adoc +++ b/ROADMAP.adoc @@ -34,12 +34,16 @@ Development roadmap for the SLM-as-Cerebellum policy enforcement system. | compliant/violations/edge_cases | SLM Evaluator -| [yellow]#*PLACEHOLDER*# -| Interface defined, needs llama.cpp +| [green]#*BACKENDS LANDED*# +| Real `llama-cli` + HTTP providers, contract-verified; model calibration open | Consensus Arbiter -| [yellow]#*STARTED*# -| GenServer skeleton, Application module, decide/3 logic +| [green]#*BACKENDS LANDED*# +| Protocol v1 escript + durable audit sink + Rust client; ops hardening open + +| Native Nickel policies +| [green]#*FEATURE LANDED*# +| Vendored Bunsenite backend; CI-verified build (memory-gated) | LLM Integration | [red]#*NOT STARTED*# diff --git a/TEST-NEEDS.adoc b/TEST-NEEDS.adoc index 6f0d331..a92b087 100644 --- a/TEST-NEEDS.adoc +++ b/TEST-NEEDS.adoc @@ -9,6 +9,26 @@ * *Property tests*: 10 pass (determinism, outcomes, performance) * *Security aspect tests*: 20 pass (bypass prevention, manipulation detection) + +=== 2026-09-22 update — SLM backends + arbiter + +* *Rust suites*: 187 assertions green locally + (15 slm units incl. provider contract + timeout, 22 slm `http` + feature, 60 contract units incl. 9 SLM-stage + arbiter client + validation, 30 oracle, 19 pipeline, 10 property, 20 security, + 11 generative/proptest: terminality under arbitrary outcomes, + threshold arithmetic at 0/1, low-confidence escalation, fail-closed + failure modes, determinism, request-ID preservation, concurrent + correlation no-mixing) +* *Real-inference smokes* (env-gated, `--ignored` in CI-driven jobs): + local Qwen2.5-0.5B GGUF round-trip (14.1 s / 2 vCPU) and llama-server + HTTP round-trip (3.6 s warm) — both green on 2026-09-22 +* *Elixir (OTP arbiter)*: 28 ExUnit tests written (consensus decision + matrix, protocol validation, audit sink incl. rotation + fail-closed, + server round-trips) — verified by the `arbiter-ci` workflow; no local + OTP toolchain existed +* *Native Nickel*: feature tests run in the dedicated `nickel-native` + CI job (parser cannot compile under ~2 GB) * *Integration tests*: 1 Zig template placeholder (ready for expansion) * *Benchmarks*: Baseline infrastructure in place (criterion-ready) * *panic-attack scan*: Ready to run diff --git a/docs/ARBITER_PROTOCOL.adoc b/docs/ARBITER_PROTOCOL.adoc new file mode 100644 index 0000000..cf3b44f --- /dev/null +++ b/docs/ARBITER_PROTOCOL.adoc @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Consensus Arbiter Protocol (v1) +:toc: + +The OTP consensus arbiter (`src/arbiter/`) is a standalone Elixir escript +consuming newline-delimited JSON on stdin and answering on stdout. The +Rust client (`src/contract/src/arbiter.rs`) spawns one process per +decision; both sides fail closed. + +== Wire format + +=== Request + +[source,json] +---- +{"protocol_version":1,"request_id":"", + "llm":{"confidence":0.95}, + "slm":{"violation_confidence":0.05}, + "oracle":{"verdict":"allow|soft_concern|hard_violation"}} +---- + +* `protocol_version` — must be integer `1`; any other version is a + protocol error (unknown versions never yield Allow). +* `request_id` — non-empty; echoed verbatim in the response and the audit + record. Empty/missing ids are rejected. +* `llm.confidence`, `slm.violation_confidence` — numbers in `[0, 1]` + (integers are coerced). Out-of-range values are rejected. +* `oracle.verdict` — one of `allow`, `soft_concern`, `hard_violation`. + +=== Response (decision) + +[source,json] +---- +{"protocol_version":1,"request_id":"…", + "verdict":"allow|escalate|block","reason":"…","audit_recorded":true} +---- + +`audit_recorded` is `true` iff the decision was durably appended to the +audit log **before** the response was written (flush before ack). + +=== Response (service error) + +[source,json] +---- +{"protocol_version":1,"request_id":"…","error":"…"} +---- + +Produced for malformed input, unsupported versions, and **any audit +persistence failure**. No `verdict` key is ever present in a service +error. + +== Decision matrix (consensus) + +* `hard_violation` → **block** (terminal; mirroring the Rust oracle). +* `no_go = violation_confidence × 1.5 (SLM weight)`; `soft_concern` adds + 0.2 to `no_go`. +* `no_go >= 0.9` → **block**. +* `llm.confidence <= 0.8` or `no_go >= 0.4` → **escalate**. +* otherwise → the oracle verdict stands (allow / soft concern). + +The asymmetric matrix (SLM votes count 1.5× because abstaining is cheap +and false-allows are expensive) is documented in +`lib/conative_gating/consensus_arbiter.ex`. + +== Fail-closed guarantees + +* Malformed / wrong-version / out-of-range input → service error, never a + verdict; **no audit record is written for protocol errors** (the request + was never a decision). +* Audit sink unavailable or a persistence failure at record time → service + error; neither an ack nor a verdict is emitted. +* Exactly one audit record per accepted request (flush before ack — + enforced by design; the Rust client requires `audit_recorded: true`). +* The audit record contains decision metadata only (ids, votes, verdict, + reason) — **no proposal content** is ever persisted. + +== Audit log (OTP sink) + +* One JSONL record per decision at `CONATIVE_AUDIT_PATH` + (default `./conative-gating-audit.jsonl`; the test env isolates this + under `_build/test/`). +* Rotation: when the file exceeds `CONATIVE_AUDIT_MAX_BYTES` + (default 10 MiB), it is renamed to `.1` before the next append + (last-generation retention; ship/rotate further in ops if required). +* Invalid `CONATIVE_AUDIT_*` values fail closed at boot — a misconfigured + arbiter never silently runs without its sink. +* In-memory history is bounded (last N records) for diagnostics. + +Hardening backlog (deliberately ops-owned, see +`docs/UPSTREAM-DELIVERY.adoc`): file permissions/ownership policy, +cross-process append locking for multi-instance deployments, disk-full +behaviour drills, log shipping/backup, and optional tamper evidence +(hash chaining) are documented there; the single-instance semantics above +are the tested floor. + +== Concurrency + +One arbiter process per decision-stream; requests are answered in order. +The Rust-side concurrency property (no correlation-ID mixing across +in-flight requests) is tested in `tests/generative_test.rs` at the +contract layer; the escript is single-request-stream per process, so +cross-request mixing is structurally impossible. diff --git a/docs/MAAF_INTEGRATION.adoc b/docs/MAAF_INTEGRATION.adoc index b067153..3aa4706 100644 --- a/docs/MAAF_INTEGRATION.adoc +++ b/docs/MAAF_INTEGRATION.adoc @@ -269,7 +269,10 @@ NOTE: The `wordpress-wharf` (gitlab) and `wharf` (github) repositories need reco **Purpose**: Cross-language Nickel configuration loader with FFI bindings. -**Repository**: https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite +**Repository**: https://gitlab.com/hyperpolymath/bunsenite (canonical; +the former `campaign-for-cooler-coding-and-programming/bunsenite` path is +a stale mirror — conative-gating vendors the canonical tree under +`vendor/bunsenite/`, see `vendor/bunsenite/VENDOR.adoc`) **Key Features**: * Robust Nickel parsing via `nickel-lang-core` diff --git a/docs/NICKEL-POLICY.adoc b/docs/NICKEL-POLICY.adoc new file mode 100644 index 0000000..bec5f17 --- /dev/null +++ b/docs/NICKEL-POLICY.adoc @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Native Nickel Policy Backend +:toc: + +The policy oracle can natively load and validate `.ncl` policy files by +embedding https://nickel-lang.org[Nickel] through the vendored +https://gitlab.com/hyperpolymath/bunsenite[Bunsenite] bindings. + +== Feature gate + +The native backend is behind the `nickel` cargo feature (off by default): + +---- +cargo build -p policy-oracle --features nickel +cargo test -p policy-oracle --features nickel --lib --locked +conative scan --policy policy.ncl # binary built --features nickel +---- + +Without the feature, any `.ncl` policy path fails with an explicit +error — **never** a silent JSON fallback. + +`Policy::from_policy_file(path)` dispatches on extension: +`.ncl` → native Nickel evaluation; anything else → JSON +(backward-compatible with every existing policy file). +`Policy::from_nickel_source(source)` is available for embedded callers. + +== Vendored Bunsenite + +The dependency is `vendor/bunsenite/`, pinned to upstream rev +`f788de3950b7541354806299cc8605dcf1608d11` (see +`vendor/bunsenite/UPSTREAM-REVISION` and `vendor/bunsenite/VENDOR.adoc`). + +One deliberate local diff: `nickel-lang-core = "0.18.0"` with +`default-features = false`, which drops the REPL/doc/format/markdown +feature chain (tree-sitter, topiary, rustyline, comrak, their C +toolchains) from the embedded evaluator. `nickel-lang-core 0.19.0` exists; +the pin stays on 0.18.0 per the delivery spec — revisit at PR review. + +The vendor directory carries Apache-2.0/MIT licenses upstream; see +`vendor/bunsenite/LICENSE*`. + +NOTE: The vendor tree is **excluded from workspace membership** +(`exclude = ["vendor/bunsenite"]`) so plain `cargo check --workspace` does +not pull the Nickel graph; it is built only via the `nickel` feature. + +== Build memory constraint (important) + +`nickel-lang-parser` (compiled by `nickel-lang-core` even with all default +features off) does not link-compile reliably under ~2 GB RAM; the build is +SIGKILLed (OOM), reproduced twice with `CARGO_BUILD_JOBS=1 +CARGO_INCREMENTAL=0 ... -Dwarnings`. The reduced-default-features fork +shrinks the graph but cannot remove the parser crate. + +Therefore the authoritative verification of the `nickel` feature is the +dedicated CI job (`nickel-native` in `.github/workflows/ci.yml`), which +runs on a full-size hosted runner with the exact contracted command: + +---- +CARGO_BUILD_JOBS=1 RUSTFLAGS="-C debuginfo=0 -Dwarnings" \ + cargo test -p policy-oracle --features nickel --lib --locked +---- + +== Multi-file imports: NOT supported (decision) + +**Decision: Nickel `import` statements are rejected. Policies must be a +single self-contained `.ncl` file.** + +Rationale: + +* the audit boundary requires that exactly the bytes under review are what + was evaluated — resolving relative imports would make evaluation depend + on ambient filesystem state (symlinks, TOCTOU swaps, + include-what-you-didn't-review); +* Bunsenite's `eval_for_nickel` evaluates an anonymous root term, so import + roots would need bespoke resolution policy anyway; +* refusing early and loudly makes policy composition explicit: assemble the + final `.ncl` at policy-review time, commit the result. + +Implementation: `src/oracle/src/nickel.rs` pre-scans the source for +top-level `import` expressions **outside of comments and string literals** +and fails with `OracleError::NickelUnsupported` naming the offending +import. No evaluation is attempted. + +If/when composition is genuinely needed, the sanctioned paths are: + +* generate the single-file policy from a policy repo (recommended), or +* request a `Policy` multiple-file merge at the JSON model level + (languages/toolchain/patterns/enforcement lists are plain data). + +This decision, and how to revisit it, is recorded here per the delivery +contract. + +== Validation contract + +A Nickel policy must evaluate to a record satisfying the strict +`policy_oracle::Policy` contract (exact fields `name`, `languages`, +`toolchain`, `patterns`, `enforcement`; the strict serde contract rejects +unknown fields — Nickel refinements do not silently widen the model). +Evaluation errors, contract errors and unsupported features all surface as +explicit `OracleError`s; policy loading fails closed. diff --git a/docs/SLM_PROVIDERS.adoc b/docs/SLM_PROVIDERS.adoc new file mode 100644 index 0000000..94d1609 --- /dev/null +++ b/docs/SLM_PROVIDERS.adoc @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += SLM Provider Backends +:toc: + +`slm-evaluator` evaluates proposals through pluggable **providers**. Two +real backends ship in this change: a local `llama.cpp` CLI adapter and an +OpenAI-compatible HTTP adapter (behind the `http` cargo feature). + +== The provider contract (verbatim) + +Every provider must: + +* accept a `SlmRequest` carrying the **correlation ID** + (`proposal_id`) of the gating request, plus bounded `content`/`context` + (`MAX_CONTENT_CHARS` = 4096, `MAX_CONTEXT_CHARS` = 1024); +* return a `SlmEvaluation` whose `proposal_id` **echoes the request** — + the ID is preserved end-to-end; +* produce a verdict object with **exactly** the fields + `{"spirit_score": 0..1, "confidence": 0..1, "reasoning": string, "should_block": bool}`; +* be **fail-closed**: timeouts, transport failure, malformed JSON, and + out-of-range scores are all `SlmError`s — the gating contract turns any + provider error into a non-overridable **Escalate** (`Sys902`), never an + Allow. + +`evaluate_with_provider` never calls a provider when the deterministic +oracle already ruled Block — an SLM never re-litigates a terminal oracle +decision, and proposal content never leaves the process needlessly. + +== LlamaCppProvider (local GGUF) + +Runs a pinned `llama-cli`: + +---- +llama-cli -m MODEL -p PROMPT -n TOKENS --temp 0 --no-display-prompt --single-turn +---- + +`--single-turn` is additive to the documented argument set: current +`llama-cli` nightlies otherwise enter their interactive conversation loop +and never exit (observed with b11100). + +Environment configuration (`LlamaCppProvider::from_env`: + +* `CONATIVE_GGUF_MODEL` — path to the GGUF model (required; unset = + provider not configured); +* `CONATIVE_LLAMA_CLI` — executable path (default `llama-cli`); +* `CONATIVE_SLM_MAX_TOKENS` — per-request decoding budget (default 256 — + small instruct models were observed truncated mid-JSON at 128); +* `CONATIVE_SLM_TIMEOUT_SECS` — timeout (default 120). + +== HttpSlmProvider (OpenAI-compatible, `--features http`) + +Feature-gated. `reqwest` (blocking, rustls) to +`{CONATIVE_SLM_ENDPOINT}/v1/chat/completions`. + +* `CONATIVE_SLM_ENDPOINT` — endpoint origin, e.g. `https://slm.example.com` + (required; **https, or loopback for development** — plain http to a + non-loopback host is refused at construction); +* `CONATIVE_SLM_MODEL_NAME` — model id (default `local-slm`); +* `SLM_API_KEY` — bearer token, **read from the environment only**, never + logged, never sent anywhere but the configured endpoint; +* `CONATIVE_SLM_MAX_TOKENS` / `CONATIVE_SLM_TIMEOUT_SECS` as above. + +A loopback `llama-server` satisfies this adapter for development/CI: + +---- +llama-server -m model.gguf --host 127.0.0.1 --port 18080 & +export CONATIVE_SLM_ENDPOINT=http://127.0.0.1:18080 +---- + +== Pinned CI artifacts (SHA-256 verified, never committed) + +Recorded in `.ci-artifact-pins.txt`; downloaded + verified in the +`slm-real-inference` workflow: + +[cols="2,3,1", options="header"] +|=== +|Artifact |Source pinned |SHA-256 + +|llama.cpp b11100 ubuntu-x64 binary +|`github.com/ggml-org/llama.cpp` release `b11100` +|`a836c913236ab4533ef9aaf49f0e1ad2955c8159869d7eec1a9072a92e13d61b` + +|Qwen2.5-0.5B-Instruct Q4_K_M GGUF +|`huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF` @ `9217f5db…` +|`74a4da8c9fdbcd15bd1f6d01d621410d31c6fc00986f5eb687824e7b93d7a9db` +|=== + +Model choice note: `SmolLM2-135M-Instruct` (Q4_K_M) was tried first for +size (105 MB) and **rejected empirically**: at `--temp 0` it emits +`"spirit_score": 0.000000…` indefinitely and never closes the verdict +object within the token budget. Qwen2.5-0.5B produces a valid verdict on +the first attempt. The parser nevertheless scans *all* balanced objects +and accepts the last schema-valid one, because current `llama-cli` +pollutes stdout with a banner and a prompt echo containing the invalid +template object. Fail-closed behaviour is unchanged. + +== Benchmarks (measured, 2 vCPU / 2 GB sandbox, 2026-09-22) + +[cols="2,2,3", options="header"] +|=== +|Backend |Latency |Notes + +|llama-cli, Qwen2.5-0.5B Q4_K_M +|*14.1 s* end-to-end (`temp 0`, 128-token answer) +|includes process spawn + model mmap; generation ≈ 15 tok/s, prompt +processing ≈ 80 tok/s + +|llama-server loopback, same model +|*3.6 s* warm per evaluation +|server amortises model load; identical deterministic verdict + +|SmolLM2-135M Q4_K_M +|fails contract +|token-loop (see above); kept out of CI +|=== + +These are floor numbers from a deliberately tiny environment; the CI +runner (4 vCPU/16 GB) is expected to be several times faster. The gating +CLI enforces the provider timeout regardless of wall-clock here. +Verdict *quality* of a 0.5B model is advisory-grade only (it scored a +clean hello-world 0.9 violation-confidence once) — treat small-model +verdicts as a signal that must be backed by the oracle's hard rules and +human escalation paths. + +== Running the real smoke tests + +---- +# local GGUF +CONATIVE_LLAMA_CLI=./llama-cli CONATIVE_GGUF_MODEL=./model.gguf \ + cargo test -p slm-evaluator --test real_inference real_llama -- --ignored --nocapture + +# HTTP adapter (loopback llama-server or any OpenAI-compatible endpoint) +CONATIVE_SLM_ENDPOINT=http://127.0.0.1:18080 \ + cargo test -p slm-evaluator --features http --test real_inference real_http -- --ignored --nocapture +---- + +== Live remote provider (protected) + +The genuine remote-provider smoke never runs on PRs. It runs only: + +* on `workflow_dispatch`, and +* on pushes to the protected `main` branch, + +inside the `slm-remote-production` GitHub Environment, which must be +configured with `CONATIVE_SLM_ENDPOINT`, `CONATIVE_SLM_MODEL_NAME`, and +`SLM_API_KEY` secrets and a required-reviewers protection rule. See +`.github/workflows/slm-real-inference.yml`. Fork PR code never receives +these secrets. diff --git a/docs/UPSTREAM-DELIVERY.adoc b/docs/UPSTREAM-DELIVERY.adoc new file mode 100644 index 0000000..c4c0482 --- /dev/null +++ b/docs/UPSTREAM-DELIVERY.adoc @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Upstream Delivery Notes (2026-09-22 rebuild) +:toc: + +State of the feature set delivered by the +`agent/native-nickel-slm-arbiter` branch, and what remains ops-owned. + +== Delivered + +* **Native Nickel backend** — vendored Bunsenite @ `f788de39…` + (default-features-off `nickel-lang-core 0.18.0`), `nickel` feature, + fail-closed import rejection, `Policy::from_policy_file` dispatch. + Verified by the `nickel-native` CI job (the parser cannot build under + ~2 GB — see `docs/NICKEL-POLICY.adoc`). +* **SLM provider layer** — `SlmProvider` trait, verdict contract + + correlation preservation, `LlamaCppProvider` (`--single-turn` hardened), + `HttpSlmProvider` (`--features http`, https-or-loopback, `SLM_API_KEY` + env-only), `from_env()` selection, CLI `eval --slm` (loud exit 3 on + misconfiguration). +* **Contract integration** — `evaluate_with_provider` (oracle Block + terminal, provider never called; Warn addend; threshold matrix; + provider failure → non-overridable `Sys902` Escalate), Rust arbiter + client with enforced `audit_recorded: true`. +* **OTP arbiter** — protocol v1 decode/validate/encode, consensus + decision matrix (SLM weight 1.5, +0.2 soft-concern addend, 0.9/0.4 + thresholds, `llm.confidence <= 0.8` escalate), stdio server, escript, + durable audit sink (JSONL, flush-before-ack, rotation at + `CONATIVE_AUDIT_MAX_BYTES`, fail-closed, bounded history, no content). +* **Tests** — 187 Rust assertions green locally (incl. 22 http-feature, + 11 generative/proptest covering terminality, threshold arithmetic at + 0/1, low-confidence escalation, fail-closed failure modes, determinism, + request-ID preservation, concurrency correlation no-mixing), 28 ExUnit + tests for the arbiter (CI-verified; no local OTP toolchain existed). +* **Real-model smokes** — llama-cli + Qwen2.5-0.5B Q4_K_M round-trip + (14.1 s on 2 vCPU) and llama-server HTTP round-trip (3.6 s warm), both + reproducible via pinned artifacts in `.ci-artifact-pins.txt` and the + `slm-real-inference` workflow. + +== Ops-owned setup (before merging) + +. **Rotate the PAT** used to push this branch + (https://github.com/settings/tokens) — it travelled through a chat + channel; treat as compromised. +. **Create the `slm-remote-production` GitHub Environment** with required + reviewers and secrets `CONATIVE_SLM_ENDPOINT`, `CONATIVE_SLM_MODEL_NAME`, + `SLM_API_KEY`. The remote smoke job is inert until then (by design). +. **Commit `src/arbiter/mix.lock`** — it cannot be generated without an + OTP toolchain; run `mix deps.get` once in `src/arbiter/` on any OTP-27 + machine and commit the lockfile. +. Audit-sink hardening for production multi-instance use: decide file + permissions/ownership (`umask`), add cross-process append locking if + more than one arbiter writes one file, wire log shipping/backup, and + consider hash-chained records for tamper evidence. The tested floor + (single instance, flush-before-ack, rotation) is documented in + `docs/ARBITER_PROTOCOL.adoc`. +. Watch the first `nickel-native` CI run — it is the authoritative + verification vehicle for the feature build. + +== Known limitations / deliberate deferrals + +* Small-model verdict *quality* is advisory-grade; plumbing is verified, + calibration is future work (`docs/SLM_PROVIDERS.adoc` benchmark notes). +* Nickel multi-file `import` is intentionally unsupported (decision + + alternatives in `docs/NICKEL-POLICY.adoc`). +* `nickel-lang-core` pinned to 0.18.0 per spec; 0.19.0 upgrade is a PR + review point. +* The audit "arbiter restart does not corrupt the log" invariant is + covered structurally (append-only JSONL + per-line records) and by the + rotation test; a chaos-restart drill belongs with ops hardening above. diff --git a/hooks/validate-codeql.sh b/hooks/validate-codeql.sh old mode 100755 new mode 100644 diff --git a/hooks/validate-permissions.sh b/hooks/validate-permissions.sh old mode 100755 new mode 100644 diff --git a/hooks/validate-sha-pins.sh b/hooks/validate-sha-pins.sh old mode 100755 new mode 100644 diff --git a/hooks/validate-spdx.sh b/hooks/validate-spdx.sh old mode 100755 new mode 100644 diff --git a/scripts/apply-common-files.sh b/scripts/apply-common-files.sh old mode 100755 new mode 100644 diff --git a/scripts/apply-justfiles.sh b/scripts/apply-justfiles.sh old mode 100755 new mode 100644 diff --git a/scripts/bulk-standardize.sh b/scripts/bulk-standardize.sh old mode 100755 new mode 100644 diff --git a/scripts/mass-apply-templates.sh b/scripts/mass-apply-templates.sh old mode 100755 new mode 100644 diff --git a/scripts/reconcile-wharf-repos.sh b/scripts/reconcile-wharf-repos.sh old mode 100755 new mode 100644 diff --git a/setup.sh b/setup.sh old mode 100755 new mode 100644 diff --git a/src/arbiter/config/runtime.exs b/src/arbiter/config/runtime.exs new file mode 100644 index 0000000..04f8984 --- /dev/null +++ b/src/arbiter/config/runtime.exs @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# Runtime configuration — evaluated at boot (escript-friendly). + +import Config + +config :conative_gating, + audit_path: System.get_env("CONATIVE_AUDIT_PATH"), + audit_max_bytes: + case System.get_env("CONATIVE_AUDIT_MAX_BYTES") do + nil -> + nil + + raw -> + case Integer.parse(raw) do + {value, ""} when value > 0 -> value + _ -> nil + end + end + +# Tests must never pollute the working directory: route the default audit +# path into the build tree where artifacts are disposable. +if config_env() == :test do + config :conative_gating, + audit_path: + System.get_env("CONATIVE_AUDIT_PATH") || + Path.expand(Path.join([__DIR__, "..", "_build", "test", "conative-gating-audit.jsonl"])) +end diff --git a/src/arbiter/lib/conative_gating/application.ex b/src/arbiter/lib/conative_gating/application.ex index d9bd01c..bb97983 100644 --- a/src/arbiter/lib/conative_gating/application.ex +++ b/src/arbiter/lib/conative_gating/application.ex @@ -15,7 +15,9 @@ defmodule ConativeGating.Application do def start(_type, _args) do children = [ # Start the Consensus Arbiter GenServer - ConativeGating.ConsensusArbiter + ConativeGating.ConsensusArbiter, + # Durable JSONL audit sink (CONATIVE_AUDIT_PATH / CONATIVE_AUDIT_MAX_BYTES) + {ConativeGating.AuditLog, []} ] opts = [strategy: :one_for_one, name: ConativeGating.Supervisor] diff --git a/src/arbiter/lib/conative_gating/arbiter_protocol.ex b/src/arbiter/lib/conative_gating/arbiter_protocol.ex new file mode 100644 index 0000000..0782b3a --- /dev/null +++ b/src/arbiter/lib/conative_gating/arbiter_protocol.ex @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.ArbiterProtocol do + @moduledoc """ + Versioned JSON-lines protocol for the Consensus Arbiter. + + Wire format (protocol version 1): + + request: + {"protocol_version":1,"request_id":"…", + "llm":{"confidence":0.95}, + "slm":{"violation_confidence":0.05}, + "oracle":{"verdict":"allow|soft_concern|hard_violation"}} + + response: + {"protocol_version":1,"request_id":"…", + "verdict":"allow|escalate|block","reason":"…","audit_recorded":true} + + service error: + {"protocol_version":1,"request_id":"…","error":"…"} + + Decoding never raises: every malformed request yields + `{:error, reason}` so the caller can answer with a protocol error and fail + closed. See `docs/ARBITER_PROTOCOL.adoc`. + """ + + @protocol_version 1 + @oracle_verdicts ~w(allow soft_concern hard_violation) + @final_verdicts ~w(allow escalate block) + + @typedoc "A validated consensus request." + @type request :: %{ + protocol_version: 1, + request_id: String.t(), + llm: %{confidence: float()}, + slm: %{violation_confidence: float()}, + oracle: %{verdict: String.t()} + } + + def protocol_version, do: @protocol_version + def final_verdicts, do: @final_verdicts + def oracle_verdicts, do: @oracle_verdicts + + @doc """ + Decode and validate one request line. + """ + @spec decode_request(binary()) :: {:ok, request()} | {:error, atom() | tuple()} + def decode_request(line) when is_binary(line) do + with {:ok, decoded} <- Jason.decode(line), + {:ok, request} <- validate_request(decoded) do + {:ok, request} + else + {:error, %Jason.DecodeError{}} -> {:error, :malformed_json} + {:error, reason} -> {:error, reason} + end + end + + defp validate_request(%{"protocol_version" => version} = request) + when version != @protocol_version do + _ = request + {:error, {:unsupported_protocol_version, version}} + end + + defp validate_request( + %{ + "protocol_version" => @protocol_version, + "request_id" => request_id, + "llm" => %{"confidence" => confidence}, + "slm" => %{"violation_confidence" => violation_confidence}, + "oracle" => %{"verdict" => oracle_verdict} + } = request + ) + when is_binary(request_id) and is_number(confidence) and + is_number(violation_confidence) do + cond do + request_id == "" -> + {:error, :missing_request_id} + + confidence < 0 or confidence > 1 -> + {:error, {:out_of_range, "llm.confidence"}} + + violation_confidence < 0 or violation_confidence > 1 -> + {:error, {:out_of_range, "slm.violation_confidence"}} + + oracle_verdict not in @oracle_verdicts -> + {:error, {:unknown_oracle_verdict, oracle_verdict}} + + true -> + {:ok, + %{ + protocol_version: @protocol_version, + request_id: request_id, + llm: %{confidence: confidence / 1}, + slm: %{violation_confidence: violation_confidence / 1}, + oracle: %{verdict: oracle_verdict} + }} + end + end + + defp validate_request(_other), do: {:error, :invalid_request_shape} + + @doc "Encode a consensus response (one line, no trailing newline)." + @spec encode_response(String.t(), String.t(), String.t() | nil, boolean()) :: binary() + def encode_response(request_id, verdict, reason, audit_recorded) + when verdict in @final_verdicts and is_boolean(audit_recorded) do + Jason.encode!(%{ + protocol_version: @protocol_version, + request_id: request_id, + verdict: verdict, + reason: reason, + audit_recorded: audit_recorded + }) + end + + @doc "Encode a service-level error response (clients must fail closed)." + @spec encode_error(String.t() | nil, binary()) :: binary() + def encode_error(request_id, message) do + Jason.encode!(%{ + protocol_version: @protocol_version, + request_id: request_id || "", + error: message + }) + end +end diff --git a/src/arbiter/lib/conative_gating/audit_log.ex b/src/arbiter/lib/conative_gating/audit_log.ex new file mode 100644 index 0000000..c00b4bd --- /dev/null +++ b/src/arbiter/lib/conative_gating/audit_log.ex @@ -0,0 +1,205 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.AuditLog do + @moduledoc """ + Durable JSONL audit sink for gating decisions. + + Guarantees: + + * **Exactly one record per decision** — one `record/2` call appends one + line; callers must invoke it exactly once per accepted request. + * **Flushed before acknowledged** — each record is written and + `:file.sync`'d before the caller receives `:ok`. + * **Rotation** — when appending would push the active file past + `max_bytes`, the active file is renamed to `.1` (single + generation, overwriting any previous rotation) before writing. + * **Fail-closed** — any filesystem failure returns `{:error, reason}` and + leaves history untouched. A decision whose audit cannot persist must + never be answered as `allow` upstream. + * **No proposal content** — `:content`/`"content"` keys are stripped + defensively; audit carries identifiers, votes, and metadata only. + * **Bounded memory** — an in-memory history (default 1,000 entries) is + kept for diagnostics, never growing beyond capacity. + + Configuration (first match wins): explicit start options, then the + `:conative_gating` application env, then process env vars: + + * `CONATIVE_AUDIT_PATH` — JSONL file path (default + `conative-gating-audit.jsonl`) + * `CONATIVE_AUDIT_MAX_BYTES` — rotation threshold (default 10 MiB) + """ + + use GenServer + require Logger + + @default_path "conative-gating-audit.jsonl" + @default_max_bytes 10 * 1024 * 1024 + @default_history_capacity 1_000 + + # ------------------------------------------------------------------------- + # Client API + # ------------------------------------------------------------------------- + + def start_link(opts \\ []) do + {name, opts} = Keyword.pop(opts, :name, __MODULE__) + GenServer.start_link(__MODULE__, opts, name: name) + end + + @doc """ + Persist one audit entry (a JSON-encodable map). Returns `:ok` only after + the record has been synced to disk; `{:error, reason}` otherwise. + """ + @spec record(map(), GenServer.server()) :: :ok | {:error, term()} + def record(entry, server \\ __MODULE__) when is_map(entry) do + GenServer.call(server, {:record, entry}) + end + + @doc "Bounded in-memory diagnostic history (newest last)." + @spec history(GenServer.server()) :: [map()] + def history(server \\ __MODULE__) do + GenServer.call(server, :history) + end + + @doc "The effective sink configuration (diagnostics/tests)." + @spec config(GenServer.server()) :: map() + def config(server \\ __MODULE__) do + GenServer.call(server, :config) + end + + # ------------------------------------------------------------------------- + # Server + # ------------------------------------------------------------------------- + + @impl true + def init(opts) do + app_env = Application.get_all_env(:conative_gating) + + path = + Keyword.get(opts, :path) || + Keyword.get(app_env, :audit_path) || + System.get_env("CONATIVE_AUDIT_PATH") || + @default_path + + max_bytes = + Keyword.get(opts, :max_bytes) || + Keyword.get(app_env, :audit_max_bytes) || + case System.get_env("CONATIVE_AUDIT_MAX_BYTES") do + nil -> @default_max_bytes + raw -> parse_positive_integer(raw, @default_max_bytes) + end + + history_capacity = Keyword.get(opts, :history_capacity, @default_history_capacity) + + {:ok, + %{ + path: Path.expand(path), + max_bytes: max_bytes, + history_capacity: history_capacity, + history: [] + }} + end + + @impl true + def handle_call({:record, entry}, _from, state) do + sanitized = sanitize(entry) + line = Jason.encode!(sanitized) + + case persist(state.path, state.max_bytes, line) do + :ok -> + history = (state.history ++ [sanitized]) |> Enum.take(-state.history_capacity) + {:reply, :ok, %{state | history: history}} + + {:error, reason} = error -> + Logger.error("audit persistence failed (fail-closed): #{inspect(reason)}") + {:reply, error, state} + end + end + + def handle_call(:history, _from, state), do: {:reply, state.history, state} + + def handle_call(:config, _from, state) do + {:reply, + %{ + path: state.path, + max_bytes: state.max_bytes, + history_capacity: state.history_capacity, + history_size: length(state.history) + }, state} + end + + # ------------------------------------------------------------------------- + # Persistence + # ------------------------------------------------------------------------- + + # Write one JSONL record, rotating first when the active file would exceed + # max_bytes. The record is synced before returning :ok. + defp persist(path, max_bytes, line) do + record_bytes = byte_size(line) + 1 + current_size = file_size(path) + + with :ok <- maybe_rotate(path, max_bytes, current_size + record_bytes), + {:ok, io} <- File.open(path, [:append, :utf8, :raw]), + :ok <- write_and_sync(io, line) do + :ok + else + {:error, reason} -> {:error, reason} + end + end + + defp write_and_sync(io, line) do + with :ok <- IO.binwrite(io, [line, "\n"]), + :ok <- :file.sync(io) do + File.close(io) + else + {:error, reason} -> + File.close(io) + {:error, reason} + end + end + + defp maybe_rotate(path, max_bytes, projected_size) + when projected_size <= max_bytes, + do: :ok + + defp maybe_rotate(path, _max_bytes, _projected_size) do + rotated = path <> ".1" + + if File.exists?(path) do + File.rm(rotated) + File.rename(path, rotated) + else + :ok + end + end + + defp file_size(path) do + case File.stat(path) do + {:ok, %{size: size}} -> size + {:error, _} -> 0 + end + end + + # Belt-and-braces removal of proposal content keys, shallow and nested one + # level under common envelope keys, before anything touches the disk. + defp sanitize(entry) when is_map(entry) do + entry + |> Map.delete(:content) + |> Map.delete("content") + |> Map.new(fn + {key, value} when is_map(value) -> + {key, value |> Map.delete(:content) |> Map.delete("content")} + + other -> + other + end) + end + + defp parse_positive_integer(raw, default) do + case Integer.parse(raw) do + {value, ""} when value > 0 -> value + _ -> default + end + end +end diff --git a/src/arbiter/lib/conative_gating/cli.ex b/src/arbiter/lib/conative_gating/cli.ex new file mode 100644 index 0000000..27547ce --- /dev/null +++ b/src/arbiter/lib/conative_gating/cli.ex @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.CLI do + @moduledoc """ + Escript entry point for the Consensus Arbiter protocol server. + + conative_arbiter # read JSONL requests on stdin, answer on stdout + + The audit sink is configured entirely through the environment + (`CONATIVE_AUDIT_PATH`, `CONATIVE_AUDIT_MAX_BYTES` — see the AuditLog + module). One process serves a whole stream of requests; callers that want + process-per-request semantics (the Rust client) simply close stdin after + one line. + """ + + alias ConativeGating.{AuditLog, ProtocolServer} + + def main(_args) do + {:ok, _} = Application.ensure_all_started(:jason) + + case AuditLog.start_link(name: AuditLog) do + {:ok, _pid} -> + :ok + + {:error, {:already_started, _pid}} -> + :ok + + {:error, reason} -> + IO.puts(:stderr, "failed to start audit log: #{inspect(reason)}") + exit(:audit_unavailable) + end + + ProtocolServer.loop(AuditLog, :stdio) + end +end diff --git a/src/arbiter/lib/conative_gating/protocol_server.ex b/src/arbiter/lib/conative_gating/protocol_server.ex new file mode 100644 index 0000000..128faa9 --- /dev/null +++ b/src/arbiter/lib/conative_gating/protocol_server.ex @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.ProtocolServer do + @moduledoc """ + Line-oriented stdio driver for the Consensus Arbiter (protocol v1). + + Reads one JSON request per line from stdin, reaches a consensus decision, + persists the audit record, and writes exactly one response line per request + before the requester considers the decision acknowledged: + + * malformed requests → service error response (never a verdict) + * consensus → audit record first; an audit failure yields an error + response, so an unaudited decision can never be acknowledged + * EOF closes the loop + + The Rust client (`gating_contract::ArbiterClient`) spawns one short-lived + arbiter per decision; this loop additionally tolerates multi-request + streams for supervisor/library use. + """ + + alias ConativeGating.{ArbiterProtocol, AuditLog, ConsensusArbiter} + + @doc "Read requests until EOF, answering one line per request." + def loop(audit_server \\ AuditLog, io \\ :stdio) do + case IO.gets(io, "") do + :eof -> + :ok + + {:error, _reason} -> + :ok + + line -> + IO.puts(io, process_line(line, audit_server)) + loop(audit_server, io) + end + end + + @doc """ + Process exactly one request line and return the response line. + + Pure with respect to decision making: identical votes always produce the + same verdict. The only side effect is the audit write (which must succeed + for a verdict response to be produced). + """ + def process_line(line, audit_server \\ AuditLog) do + case ArbiterProtocol.decode_request(line) do + {:ok, request} -> + answer(request, audit_server) + + {:error, reason} -> + ArbiterProtocol.encode_error(nil, "invalid request: #{format_reason(reason)}") + end + end + + defp answer(request, audit_server) do + llm = %{confidence: request.llm.confidence} + slm = %{violation_confidence: request.slm.violation_confidence} + oracle = %{verdict: oracle_verdict(request.oracle.verdict)} + + {verdict, detail} = ConsensusArbiter.decide(llm, slm, oracle) + verdict_text = verdict_to_text(verdict) + reason_text = reason_text(detail) + + entry = %{ + schema: "conative-gating-audit-v1", + audit_id: uuid4(), + request_id: request.request_id, + timestamp: DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601(), + votes: %{ + llm: %{confidence: request.llm.confidence}, + slm: %{violation_confidence: request.slm.violation_confidence}, + oracle: %{verdict: request.oracle.verdict} + }, + verdict: verdict_text, + reason: reason_text, + protocol_version: ArbiterProtocol.protocol_version() + } + + case AuditLog.record(entry, audit_server) do + :ok -> + ArbiterProtocol.encode_response(request.request_id, verdict_text, reason_text, true) + + {:error, reason} -> + ArbiterProtocol.encode_error( + request.request_id, + "audit persistence failed: #{format_reason(reason)}" + ) + end + end + + defp oracle_verdict("allow"), do: :allow + defp oracle_verdict("soft_concern"), do: {:soft_concern, :policy_oracle} + defp oracle_verdict("hard_violation"), do: {:hard_violation, :policy_oracle} + + defp verdict_to_text(:block), do: "block" + defp verdict_to_text(:escalate), do: "escalate" + defp verdict_to_text(:allow), do: "allow" + + defp reason_text(%{reason: reason}) when is_atom(reason), do: Atom.to_string(reason) + defp reason_text(%{reason: reason}) when is_binary(reason), do: reason + + defp reason_text(detail) when is_map(detail) do + "go=#{format_number(Map.get(detail, :go_score))} " <> + "no_go=#{format_number(Map.get(detail, :no_go_score))}" + end + + defp format_number(nil), do: "n/a" + defp format_number(v) when is_float(v), do: :erlang.float_to_binary(v, decimals: 3) + defp format_number(v), do: to_string(v) + + # Local RFC 4122 UUIDv4 (random) — the project deliberately carries no UUID + # dependency, and Erlang/OTP has none built in. + defp uuid4() do + <> = :crypto.strong_rand_bytes(16) + c = Bitwise.bor(Bitwise.band(c0, 0x0FFF), 0x4000) + d = Bitwise.bor(Bitwise.band(d0, 0x3FFF), 0x8000) + Enum.join([hex(a, 8), hex(b, 4), hex(c, 4), hex(d, 4), hex(e, 12)], "-") + end + + defp hex(value, width) do + :io_lib.format("~*.16.0b", [width, value]) + |> IO.iodata_to_binary() + |> String.downcase() + end + + defp format_reason({:unsupported_protocol_version, v}), do: "unsupported protocol version #{v}" + defp format_reason({:out_of_range, field}), do: "#{field} out of range 0..1" + defp format_reason({:unknown_oracle_verdict, v}), do: "unknown oracle verdict #{v}" + defp format_reason(reason) when is_atom(reason), do: Atom.to_string(reason) + defp format_reason(reason), do: inspect(reason) +end diff --git a/src/arbiter/mix.exs b/src/arbiter/mix.exs index c17227b..6420fcb 100644 --- a/src/arbiter/mix.exs +++ b/src/arbiter/mix.exs @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) Jonathan D.A. Jewell -# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell defmodule ConativeGating.MixProject do use Mix.Project @@ -12,6 +12,7 @@ defmodule ConativeGating.MixProject do elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), + escript: escript(), description: "Consensus Arbiter for Conative Gating", package: package() ] @@ -19,11 +20,15 @@ defmodule ConativeGating.MixProject do def application do [ - extra_applications: [:logger], + extra_applications: [:logger, :crypto], mod: {ConativeGating.Application, []} ] end + defp escript do + [main_module: ConativeGating.CLI, name: "conative_arbiter"] + end + defp deps do [ {:rustler, "~> 0.30"}, # For Rust NIF integration diff --git a/src/arbiter/test/arbiter_protocol_test.exs b/src/arbiter/test/arbiter_protocol_test.exs new file mode 100644 index 0000000..dff3d4f --- /dev/null +++ b/src/arbiter/test/arbiter_protocol_test.exs @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.ArbiterProtocolTest do + use ExUnit.Case, async: true + + alias ConativeGating.ArbiterProtocol + + defp request_json(overrides \\ %{}) do + base = %{ + "protocol_version" => 1, + "request_id" => "11111111-2222-3333-4444-555555555555", + "llm" => %{"confidence" => 0.95}, + "slm" => %{"violation_confidence" => 0.05}, + "oracle" => %{"verdict" => "allow"} + } + + Jason.encode!(deep_merge(base, overrides)) + end + + defp deep_merge(a, b) do + Map.merge(a, b, fn _k, av, bv -> + if is_map(av) and is_map(bv), do: Map.merge(av, bv), else: bv + end) + end + + test "valid request decodes with all fields" do + assert {:ok, request} = ArbiterProtocol.decode_request(request_json()) + assert request.protocol_version == 1 + assert request.request_id == "11111111-2222-3333-4444-555555555555" + assert request.llm.confidence == 0.95 + assert request.slm.violation_confidence == 0.05 + assert request.oracle.verdict == "allow" + end + + test "non-JSON input is rejected" do + assert {:error, :malformed_json} = ArbiterProtocol.decode_request("not json") + end + + test "unsupported protocol version is rejected" do + line = request_json(%{"protocol_version" => 2}) + assert {:error, {:unsupported_protocol_version, 2}} = ArbiterProtocol.decode_request(line) + end + + test "out-of-range confidences are rejected" do + line = request_json(%{"llm" => %{"confidence" => 1.5}}) + assert {:error, {:out_of_range, "llm.confidence"}} = ArbiterProtocol.decode_request(line) + + line = request_json(%{"slm" => %{"violation_confidence" => -0.1}}) + assert {:error, {:out_of_range, "slm.violation_confidence"}} = + ArbiterProtocol.decode_request(line) + end + + test "unknown oracle verdict is rejected" do + line = request_json(%{"oracle" => %{"verdict" => "uncertain"}}) + assert {:error, {:unknown_oracle_verdict, "uncertain"}} = + ArbiterProtocol.decode_request(line) + end + + test "empty request id is rejected" do + line = request_json(%{"request_id" => ""}) + assert {:error, :missing_request_id} = ArbiterProtocol.decode_request(line) + end + + test "missing envelope keys are rejected" do + line = Jason.encode!(%{"protocol_version" => 1, "request_id" => "x"}) + assert {:error, :invalid_request_shape} = ArbiterProtocol.decode_request(line) + end + + test "integer confidences are coerced to floats" do + line = request_json(%{"llm" => %{"confidence" => 1}}) + assert {:ok, request} = ArbiterProtocol.decode_request(line) + assert is_float(request.llm.confidence) + end + + test "response encoding round-trips" do + encoded = ArbiterProtocol.encode_response("req-1", "block", "high no-go", true) + decoded = Jason.decode!(encoded) + assert decoded["protocol_version"] == 1 + assert decoded["request_id"] == "req-1" + assert decoded["verdict"] == "block" + assert decoded["reason"] == "high no-go" + assert decoded["audit_recorded"] == true + end + + test "error encoding carries nil request id as empty string" do + decoded = Jason.decode!(ArbiterProtocol.encode_error(nil, "bad")) + assert decoded["error"] == "bad" + assert decoded["request_id"] == "" + assert decoded["protocol_version"] == 1 + end +end diff --git a/src/arbiter/test/audit_log_test.exs b/src/arbiter/test/audit_log_test.exs new file mode 100644 index 0000000..8ce2f41 --- /dev/null +++ b/src/arbiter/test/audit_log_test.exs @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.AuditLogTest do + use ExUnit.Case, async: false + + alias ConativeGating.AuditLog + + defp tmpdir!(tag) do + dir = Path.join(System.tmp_dir!(), "conative-audit-test-#{tag}-#{System.unique_integer([:positive])}") + File.mkdir_p!(dir) + on_exit(fn -> File.rm_rf(dir) end) + dir + end + + defp start_sink(dir, opts) do + {:ok, pid} = AuditLog.start_link(Keyword.merge([path: Path.join(dir, "audit.jsonl"), name: nil], opts)) + + on_exit(fn -> + if Process.alive?(pid), do: GenServer.stop(pid) + end) + + pid + end + + test "record persists one JSONL line with the entry fields" do + dir = tmpdir!("record") + sink = start_sink(dir, []) + + entry = %{ + audit_id: "00000000-0000-0000-0000-000000000001", + request_id: "req-123", + verdict: "allow", + reason: "fixture" + } + + assert :ok = AuditLog.record(entry, sink) + assert :ok = AuditLog.record(%{entry | request_id: "req-124"}, sink) + + lines = dir |> Path.join("audit.jsonl") |> File.read!() |> String.split("\n", trim: true) + assert length(lines) == 2 + + first = Jason.decode!(Enum.at(lines, 0)) + assert first["request_id"] == "req-123" + assert first["verdict"] == "allow" + end + + test "proposal content keys are stripped before persistence" do + dir = tmpdir!("content") + sink = start_sink(dir, []) + + entry = %{ + request_id: "req-secret", + verdict: "block", + "content" => "super-secret-proposal-body-xyzzy", + votes: %{"content" => "nested-secret-xyzzy", slm: %{violation_confidence: 0.9}} + } + + assert :ok = AuditLog.record(entry, sink) + raw = dir |> Path.join("audit.jsonl") |> File.read!() + refute raw =~ "secret-proposal-body-xyzzy" + refute raw =~ "nested-secret-xyzzy" + assert raw =~ "req-secret" + end + + test "rotation moves the active file to .1 once max_bytes is exceeded" do + dir = tmpdir!("rotation") + # Two ~120-byte records with a 200-byte budget forces one rotation. + sink = start_sink(dir, max_bytes: 200) + + filler = String.duplicate("x", 90) + assert :ok = AuditLog.record(%{request_id: "req-1", reason: filler}, sink) + assert :ok = AuditLog.record(%{request_id: "req-2", reason: filler}, sink) + + path = Path.join(dir, "audit.jsonl") + assert File.exists?(path <> ".1"), "expected rotated file audit.jsonl.1 to exist" + + rotated = File.read!(path <> ".1") + active = File.read!(path) + assert rotated =~ "req-1" + refute active =~ "req-1" + assert active =~ "req-2" + + # A third record also fits (rotation happens lazily per record). + assert :ok = AuditLog.record(%{request_id: "req-3", reason: filler}, sink) + lines = path |> File.read!() |> String.split("\n", trim: true) + assert length(lines) == 2 + end + + test "persistence failure fails closed" do + missing_parent = Path.join(System.tmp_dir!(), "conative-missing-#{System.unique_integer([:positive])}") + bad_path = Path.join(missing_parent, "audit.jsonl") + {:ok, sink} = AuditLog.start_link(path: bad_path, name: nil) + + on_exit(fn -> if Process.alive?(sink), do: GenServer.stop(sink) end) + + assert {:error, _reason} = AuditLog.record(%{request_id: "req-x"}, sink) + # History must not acknowledge an unpersisted record. + assert AuditLog.history(sink) == [] + end + + test "in-memory history is bounded by capacity" do + dir = tmpdir!("history") + sink = start_sink(dir, history_capacity: 5, max_bytes: 10_000_000) + + for i <- 1..7 do + assert :ok = AuditLog.record(%{request_id: "req-#{i}"}, sink) + end + + history = AuditLog.history(sink) + assert length(history) == 5 + assert List.last(history)["request_id"] == "req-7" + end +end diff --git a/src/arbiter/test/consensus_arbiter_test.exs b/src/arbiter/test/consensus_arbiter_test.exs new file mode 100644 index 0000000..18fee01 --- /dev/null +++ b/src/arbiter/test/consensus_arbiter_test.exs @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.ConsensusArbiterTest do + use ExUnit.Case, async: true + + alias ConativeGating.ConsensusArbiter + + defp llm(confidence), do: %{confidence: confidence} + defp slm(violation), do: %{violation_confidence: violation} + defp oracle_allow, do: %{verdict: :allow} + defp oracle_soft, do: %{verdict: {:soft_concern, :tier2_language}} + defp oracle_hard, do: %{verdict: {:hard_violation, :forbidden_language}} + + test "hard oracle violation always blocks, regardless of other votes" do + for confidence <- [0.0, 0.5, 0.95, 1.0], violation <- [0.0, 0.5, 1.0] do + assert {:block, %{reason: :policy_oracle}} = + ConsensusArbiter.decide(llm(confidence), slm(violation), oracle_hard()) + end + end + + test "high weighted violation confidence blocks (violation 0.61 => 0.915)" do + assert {:block, %{reason: :high_violation_confidence}} = + ConsensusArbiter.decide(llm(0.95), slm(0.61), oracle_allow()) + end + + test "weighted score exactly at the 0.9 boundary does NOT block" do + # 0.6 * 1.5 == 0.8999999999999999 in IEEE-754 — strictly below 0.9, so + # the block branch must not trigger. Documented deterministic boundary. + assert {:escalate, detail} = + ConsensusArbiter.decide(llm(0.9), slm(0.6), oracle_allow()) + + assert detail.no_go_score < 0.9 + end + + test "clear pass allows (violation 0.19 => weighted 0.285 < 0.3)" do + assert {:allow, _} = ConsensusArbiter.decide(llm(0.95), slm(0.19), oracle_allow()) + end + + test "weighted score at/above 0.3 never allows (violation 0.21 => 0.315)" do + assert {:escalate, _} = + ConsensusArbiter.decide(llm(0.99), slm(0.21), oracle_allow()) + end + + test "low LLM confidence escalates even with a clean SLM vote" do + assert {:escalate, %{go_score: 0.5}} = + ConsensusArbiter.decide(llm(0.5), slm(0.05), oracle_allow()) + end + + test "oracle soft concern adds 0.2 to the no-go score" do + # 0.05*1.5 + 0.2 = 0.275 < 0.3 -> allow (with high go) + assert {:allow, _} = ConsensusArbiter.decide(llm(0.95), slm(0.05), oracle_soft()) + # 0.1*1.5 + 0.2 = 0.35 >= 0.3 -> escalate + assert {:escalate, _} = ConsensusArbiter.decide(llm(0.95), slm(0.1), oracle_soft()) + end + + test "decisions are deterministic for identical votes" do + first = ConsensusArbiter.decide(llm(0.87), slm(0.34), oracle_soft()) + second = ConsensusArbiter.decide(llm(0.87), slm(0.34), oracle_soft()) + assert first == second + end +end diff --git a/src/arbiter/test/protocol_server_test.exs b/src/arbiter/test/protocol_server_test.exs new file mode 100644 index 0000000..bdab6e6 --- /dev/null +++ b/src/arbiter/test/protocol_server_test.exs @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +defmodule ConativeGating.ProtocolServerTest do + use ExUnit.Case, async: false + + alias ConativeGating.{AuditLog, ProtocolServer} + + defp tmpdir!(tag) do + dir = Path.join(System.tmp_dir!(), "conative-server-test-#{tag}-#{System.unique_integer([:positive])}") + File.mkdir_p!(dir) + on_exit(fn -> File.rm_rf(dir) end) + dir + end + + defp start_sink!(dir) do + {:ok, pid} = AuditLog.start_link(path: Path.join(dir, "audit.jsonl"), name: nil) + + on_exit(fn -> + if Process.alive?(pid), do: GenServer.stop(pid) + end) + + pid + end + + defp request_line(request_id, votes \\ %{}) do + Jason.encode!(%{ + protocol_version: 1, + request_id: request_id, + llm: %{confidence: Map.get(votes, :llm, 0.95)}, + slm: %{violation_confidence: Map.get(votes, :slm, 0.05)}, + oracle: %{verdict: Map.get(votes, :oracle, "allow")} + }) + end + + test "allow round-trip: correlated, versioned, audited" do + dir = tmpdir!("allow") + sink = start_sink!(dir) + + response = ProtocolServer.process_line(request_line("req-allow-1"), sink) |> Jason.decode!() + assert response["protocol_version"] == 1 + assert response["request_id"] == "req-allow-1" + assert response["verdict"] == "allow" + assert response["audit_recorded"] == true + + # Exactly one audit record for the accepted request. + [entry] = AuditLog.history(sink) + assert entry.request_id == "req-allow-1" + assert entry.verdict == "allow" + end + + test "hard oracle violation blocks" do + dir = tmpdir!("block") + sink = start_sink!(dir) + + response = + ProtocolServer.process_line(request_line("req-block-1", %{oracle: "hard_violation"}), sink) + |> Jason.decode!() + + assert response["verdict"] == "block" + assert response["audit_recorded"] == true + end + + test "invalid requests produce error responses, never verdicts" do + dir = tmpdir!("invalid") + sink = start_sink!(dir) + + bad_version = + Jason.encode!(%{ + protocol_version: 2, + request_id: "req-bad", + llm: %{confidence: 0.9}, + slm: %{violation_confidence: 0.1}, + oracle: %{verdict: "allow"} + }) + + response = ProtocolServer.process_line(bad_version, sink) |> Jason.decode!() + assert response["error"] =~ "unsupported protocol version" + assert response["request_id"] == "" + refute Map.has_key?(response, "verdict") + + garbage = ProtocolServer.process_line("this is not json", sink) |> Jason.decode!() + assert garbage["error"] =~ "invalid request" + + # No audit records were created for refused requests. + assert AuditLog.history(sink) == [] + end + + test "audit failure yields an error response, never an unaudited verdict" do + missing_parent = Path.join(System.tmp_dir!(), "conative-missing-#{System.unique_integer([:positive])}") + {:ok, sink} = AuditLog.start_link(path: Path.join(missing_parent, "audit.jsonl"), name: nil) + + on_exit(fn -> if Process.alive?(sink), do: GenServer.stop(sink) end) + + response = ProtocolServer.process_line(request_line("req-audit-fail"), sink) |> Jason.decode!() + assert response["request_id"] == "req-audit-fail" + assert response["error"] =~ "audit persistence failed" + refute Map.has_key?(response, "verdict") + end + + test "deterministic votes produce identical verdicts across processes" do + dir = tmpdir!("determinism") + sink = start_sink!(dir) + + line = request_line("req-det", %{llm: 0.87, slm: 0.34, oracle: "soft_concern"}) + first = ProtocolServer.process_line(line, sink) |> Jason.decode!() + second = ProtocolServer.process_line(line, sink) |> Jason.decode!() + assert first["verdict"] == second["verdict"] + end +end diff --git a/src/arbiter/test/test_helper.exs b/src/arbiter/test/test_helper.exs new file mode 100644 index 0000000..308e201 --- /dev/null +++ b/src/arbiter/test/test_helper.exs @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell + +ExUnit.start() diff --git a/src/contract/src/arbiter.rs b/src/contract/src/arbiter.rs new file mode 100644 index 0000000..8956dae --- /dev/null +++ b/src/contract/src/arbiter.rs @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Client for the OTP Consensus Arbiter's versioned JSON-lines protocol. +//! +//! Protocol version 1 (see `docs/ARBITER_PROTOCOL.adoc` and +//! `src/arbiter/lib/conative_gating/`): +//! +//! ```text +//! request: {"protocol_version":1,"request_id":"…", +//! "llm":{"confidence":0.95}, +//! "slm":{"violation_confidence":0.05}, +//! "oracle":{"verdict":"allow"}} +//! response: {"protocol_version":1,"request_id":"…", +//! "verdict":"allow|escalate|block","reason":"…","audit_recorded":true} +//! error: {"protocol_version":1,"request_id":"…","error":"…"} +//! ``` +//! +//! Design decisions: +//! +//! * **One arbiter process per decision** (spawn per call): stateless, robust +//! against a wedged service, and trivially timeout-enforced. The arbiter is +//! cheap to start relative to an SLM inference. +//! * **Correlation is mandatory**: the response's `request_id` must equal the +//! request's. Per-call processes make cross-request mix-ups structurally +//! impossible; the check is still enforced and tested. +//! * **Audit must be confirmed**: `audit_recorded: true` is required. The +//! "every accepted request has exactly one audit record" invariant is +//! enforced client-side — a decision the arbiter did not durably record is +//! treated as [`ArbiterError::AuditNotConfirmed`] and fails closed. +//! * **Every failure is [`ArbiterError`]**: callers must map arbiter failure +//! to Escalate/NO-GO, never to Allow. + +use serde::{Deserialize, Serialize}; +use std::io::{BufRead, BufReader, Write}; +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; +use thiserror::Error; +use uuid::Uuid; + +/// Wire protocol version spoken by this client. +pub const ARBITER_PROTOCOL_VERSION: u32 = 1; + +/// Default arbiter timeout (the arbiter adds negligible latency to an SLM +/// call; 30s is generous). +pub const DEFAULT_ARBITER_TIMEOUT: Duration = Duration::from_secs(30); + +/// The oracle's vote on the wire. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum OracleVote { + /// No violation. + Allow, + /// Soft concern raised. + SoftConcern, + /// Hard violation (arbiter must answer `block`). + HardViolation, +} + +/// A consensus decision returned by the arbiter. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ArbiterVerdict { + /// Proposal may proceed. + Allow, + /// Route to human review. + Escalate, + /// Proposal is rejected. + Block, +} + +/// Protocol v1 consensus request. +#[derive(Debug, Clone, Serialize)] +pub struct ArbiterRequest { + protocol_version: u32, + request_id: Uuid, + llm: VoteConfidence, + slm: VoteViolation, + oracle: OracleBallot, +} + +#[derive(Debug, Clone, Serialize)] +struct VoteConfidence { + confidence: f64, +} + +#[derive(Debug, Clone, Serialize)] +struct VoteViolation { + violation_confidence: f64, +} + +#[derive(Debug, Clone, Serialize)] +struct OracleBallot { + verdict: OracleVote, +} + +/// Parsed protocol v1 response (before validation). +#[derive(Debug, Deserialize)] +struct WireResponse { + protocol_version: u32, + request_id: Uuid, + verdict: Option, + #[serde(default)] + reason: Option, + #[serde(default)] + audit_recorded: Option, + #[serde(default)] + error: Option, +} + +/// A validated consensus response. +#[derive(Debug, Clone, PartialEq)] +pub struct ArbiterDecision { + /// Correlated request ID (always equals the request's). + pub request_id: Uuid, + /// Consensus verdict. + pub verdict: ArbiterVerdict, + /// Optional arbiter-provided reason (diagnostics). + pub reason: Option, +} + +/// Every client failure mode (fail-closed: never map these to Allow). +#[derive(Error, Debug)] +pub enum ArbiterError { + /// Spawning or talking to the arbiter process failed. + #[error("arbiter transport failure: {0}")] + Transport(String), + /// The arbiter did not answer within the timeout. + #[error("arbiter timeout: {0}")] + Timeout(String), + /// The arbiter exited without producing a response line. + #[error("arbiter closed without answering: {0}")] + Closed(String), + /// The response line was not a well-formed protocol message. + #[error("malformed arbiter response: {0}")] + Malformed(String), + /// The response declared an unsupported protocol version. + #[error("unsupported arbiter protocol version {0} (client speaks {ARBITER_PROTOCOL_VERSION})")] + ProtocolVersion(u32), + /// The response answered a different request than the one sent. + #[error("arbiter correlation mismatch: expected {expected}, got {got}")] + CorrelationMismatch { + /// The request_id of the request actually sent. + expected: Uuid, + /// The request_id present in the response. + got: Uuid, + }, + /// The arbiter reported an application-level error. + #[error("arbiter service error: {0}")] + Service(String), + /// The arbiter answered a decision it did not durably audit. + #[error("arbiter decision without confirmed audit record")] + AuditNotConfirmed, +} + +/// Client spawning one short-lived arbiter process per decision. +#[derive(Debug)] +pub struct ArbiterClient { + command: Vec, + timeout: Duration, +} + +impl ArbiterClient { + /// `command` is the full invocation (program + args) of a protocol v1 + /// arbiter, e.g. `["/path/to/conative_arbiter"]` or + /// `["escript", "arbiter_protocol.exs"]`. + pub fn new(command: &[&str], timeout: Duration) -> Result { + if command.is_empty() { + return Err(ArbiterError::Transport("empty arbiter command".to_string())); + } + Ok(Self { + command: command.iter().map(ToString::to_string).collect(), + timeout, + }) + } + + /// Build from `CONATIVE_ARBITER_CMD` (split on whitespace, e.g. + /// `escript src/arbiter/priv/arbiter_protocol.exs`). Unset → `Ok(None)`. + pub fn from_env() -> Result, ArbiterError> { + let Ok(raw) = std::env::var("CONATIVE_ARBITER_CMD") else { + return Ok(None); + }; + let parts: Vec<&str> = raw.split_whitespace().collect(); + if parts.is_empty() { + return Ok(None); + } + Ok(Some(Self::new(&parts, DEFAULT_ARBITER_TIMEOUT)?)) + } + + /// Ask the arbiter for a consensus decision. + pub fn decide( + &self, + llm_confidence: f64, + violation_confidence: f64, + oracle_vote: OracleVote, + ) -> Result { + let request = ArbiterRequest { + protocol_version: ARBITER_PROTOCOL_VERSION, + request_id: Uuid::new_v4(), + llm: VoteConfidence { + confidence: llm_confidence, + }, + slm: VoteViolation { + violation_confidence, + }, + oracle: OracleBallot { + verdict: oracle_vote, + }, + }; + let line = serde_json::to_string(&request) + .map_err(|error| ArbiterError::Malformed(error.to_string()))?; + + let mut child = Command::new(&self.command[0]) + .args(&self.command[1..]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| { + ArbiterError::Transport(format!( + "failed to spawn arbiter {}: {error}", + self.command[0] + )) + })?; + + // Write the request and close stdin so stream-driven servers exit. + child + .stdin + .take() + .expect("invariant: stdin piped") + .write_all(line.as_bytes()) + .map_err(|error| ArbiterError::Transport(format!("write to arbiter stdin: {error}")))?; + + let mut stdout = child.stdout.take().expect("invariant: stdout piped"); + let reader = std::thread::spawn(move || { + let mut reader = BufReader::new(&mut stdout); + let mut line = String::new(); + let _ = reader.read_line(&mut line); + line + }); + + let started = Instant::now(); + let status = loop { + match child.try_wait() { + Ok(Some(status)) => break status, + Ok(None) => { + if started.elapsed() >= self.timeout { + let _ = child.kill(); + let _ = child.wait(); + let _ = reader.join(); + return Err(ArbiterError::Timeout(format!( + "no answer within {:?}", + self.timeout + ))); + } + std::thread::sleep(Duration::from_millis(10)); + } + Err(error) => { + let _ = child.kill(); + let _ = child.wait(); + return Err(ArbiterError::Transport(format!( + "waiting on arbiter failed: {error}" + ))); + } + } + }; + + let answer = reader.join().unwrap_or_default(); + let answer = answer.trim(); + if answer.is_empty() { + return Err(ArbiterError::Closed(format!( + "arbiter exited {status} with no response" + ))); + } + Self::validate_response(&request, answer) + } + + /// Validate a response line against the request (extracted for testing). + fn validate_response( + request: &ArbiterRequest, + answer: &str, + ) -> Result { + let wire: WireResponse = serde_json::from_str(answer) + .map_err(|error| ArbiterError::Malformed(format!("{error} (line: {answer:.200})")))?; + + if wire.protocol_version != ARBITER_PROTOCOL_VERSION { + return Err(ArbiterError::ProtocolVersion(wire.protocol_version)); + } + if wire.request_id != request.request_id { + return Err(ArbiterError::CorrelationMismatch { + expected: request.request_id, + got: wire.request_id, + }); + } + if let Some(error) = wire.error { + return Err(ArbiterError::Service(error)); + } + if wire.audit_recorded != Some(true) { + return Err(ArbiterError::AuditNotConfirmed); + } + let verdict = wire + .verdict + .ok_or_else(|| ArbiterError::Malformed("missing verdict".to_string()))?; + Ok(ArbiterDecision { + request_id: wire.request_id, + verdict, + reason: wire.reason, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn fixture_dir() -> std::path::PathBuf { + let dir = std::env::temp_dir().join(format!("conative-arbiter-{}", Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + #[cfg(unix)] + fn make_script(dir: &std::path::Path, name: &str, body: &str) -> std::path::PathBuf { + use std::os::unix::fs::PermissionsExt; + let path = dir.join(name); + std::fs::write(&path, format!("#!/bin/sh\n{body}\n")).unwrap(); + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap(); + path + } + + /// A well-behaved protocol v1 server: echoes the request_id, answers with + /// a fixed verdict, confirms its audit record. + #[cfg(unix)] + fn good_server(dir: &std::path::Path, verdict: &str) -> std::path::PathBuf { + make_script( + dir, + "arbiter-good.sh", + &format!( + "IFS= read -r line\nid=$(printf '%s' \"$line\" | sed -n 's/.*\"request_id\"[ ]*:[ ]*\"\\([^\"]*\\)\".*/\\1/p')\nprintf '%s\\n' '{{\"protocol_version\":1,\"request_id\":\"'\"$id\"'\",\"verdict\":\"{verdict}\",\"reason\":\"fixture\",\"audit_recorded\":true}}'\n" + ), + ) + } + + #[cfg(unix)] + fn client_for(script: &std::path::Path) -> ArbiterClient { + let s = script.to_string_lossy().to_string(); + ArbiterClient::new(&[s.as_str()], Duration::from_secs(10)).unwrap() + } + + #[test] + #[cfg(unix)] + fn good_arbiter_allow() { + let dir = fixture_dir(); + let script = good_server(&dir, "allow"); + let client = client_for(&script); + let decision = client.decide(0.95, 0.05, OracleVote::Allow).unwrap(); + assert_eq!(decision.verdict, ArbiterVerdict::Allow); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn good_arbiter_block_on_hard_violation() { + let dir = fixture_dir(); + let script = good_server(&dir, "block"); + let client = client_for(&script); + let decision = client.decide(0.99, 0.0, OracleVote::HardViolation).unwrap(); + assert_eq!(decision.verdict, ArbiterVerdict::Block); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn protocol_version_mismatch_fails_closed() { + let dir = fixture_dir(); + let script = make_script( + &dir, + "arbiter-v2.sh", + "IFS= read -r line\nid=$(printf '%s' \"$line\" | sed -n 's/.*\"request_id\"[ ]*:[ ]*\"\\([^\"]*\\)\".*/\\1/p')\nprintf '%s\\n' '{\"protocol_version\":2,\"request_id\":\"'\"$id\"'\",\"verdict\":\"allow\",\"audit_recorded\":true}'\n", + ); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::ProtocolVersion(2)) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn correlation_mismatch_fails_closed() { + let dir = fixture_dir(); + let script = make_script( + &dir, + "arbiter-wrongid.sh", + "IFS= read -r line\nprintf '%s\\n' '{\"protocol_version\":1,\"request_id\":\"00000000-0000-0000-0000-000000000000\",\"verdict\":\"allow\",\"audit_recorded\":true}'\n", + ); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::CorrelationMismatch { .. }) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn service_error_fails_closed() { + let dir = fixture_dir(); + let script = make_script( + &dir, + "arbiter-error.sh", + "IFS= read -r line\nid=$(printf '%s' \"$line\" | sed -n 's/.*\"request_id\"[ ]*:[ ]*\"\\([^\"]*\\)\".*/\\1/p')\nprintf '%s\\n' '{\"protocol_version\":1,\"request_id\":\"'\"$id\"'\",\"error\":\"consensus unavailable\"}'\n", + ); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::Service(_)) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn missing_audit_confirmation_fails_closed() { + let dir = fixture_dir(); + let script = make_script( + &dir, + "arbiter-noaudit.sh", + "IFS= read -r line\nid=$(printf '%s' \"$line\" | sed -n 's/.*\"request_id\"[ ]*:[ ]*\"\\([^\"]*\\)\".*/\\1/p')\nprintf '%s\\n' '{\"protocol_version\":1,\"request_id\":\"'\"$id\"'\",\"verdict\":\"allow\",\"audit_recorded\":false}'\n", + ); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::AuditNotConfirmed) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn garbage_output_fails_closed() { + let dir = fixture_dir(); + let script = make_script(&dir, "arbiter-garbage.sh", "echo 'not json at all'\n"); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::Malformed(_)) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn closed_output_fails_closed() { + let dir = fixture_dir(); + let script = make_script(&dir, "arbiter-closed.sh", "exit 0\n"); + let client = client_for(&script); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::Closed(_)) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn timeout_fails_closed() { + let dir = fixture_dir(); + let script = make_script(&dir, "arbiter-slow.sh", "sleep 5\n"); + let s = script.to_string_lossy().to_string(); + let client = ArbiterClient::new(&[s.as_str()], Duration::from_millis(300)).unwrap(); + assert!(matches!( + client.decide(0.5, 0.5, OracleVote::Allow), + Err(ArbiterError::Timeout(_)) + )); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + #[cfg(unix)] + fn verdict_shape_is_serialized_as_protocol_v1() { + // Serialized request must exactly match the documented wire shape. + let request = ArbiterRequest { + protocol_version: 1, + request_id: Uuid::nil(), + llm: VoteConfidence { confidence: 0.95 }, + slm: VoteViolation { + violation_confidence: 0.05, + }, + oracle: OracleBallot { + verdict: OracleVote::SoftConcern, + }, + }; + let json = serde_json::to_value(&request).unwrap(); + assert_eq!(json["protocol_version"], 1); + assert_eq!(json["request_id"], Uuid::nil().to_string()); + assert_eq!(json["llm"]["confidence"], 0.95); + assert_eq!(json["slm"]["violation_confidence"], 0.05); + assert_eq!(json["oracle"]["verdict"], "soft_concern"); + } +} diff --git a/src/contract/src/lib.rs b/src/contract/src/lib.rs index d8ec225..9512baa 100644 --- a/src/contract/src/lib.rs +++ b/src/contract/src/lib.rs @@ -19,10 +19,19 @@ use policy_oracle::{ ViolationType, }; use serde::{Deserialize, Serialize}; +use slm_evaluator::{SlmProvider, SlmRequest}; use std::collections::HashMap; use thiserror::Error; use uuid::Uuid; +/// Client for the OTP Consensus Arbiter (JSON-lines protocol v1). +pub mod arbiter; + +pub use arbiter::{ + ArbiterClient, ArbiterDecision, ArbiterError, ArbiterVerdict, OracleVote, + ARBITER_PROTOCOL_VERSION, +}; + // ============================================================================ // CONTRACT VERSION // ============================================================================ @@ -1509,6 +1518,207 @@ impl RedTeamSummary { } } +// ============================================================================ +// SLM STAGE - optional live provider evaluation +// ============================================================================ + +impl ContractRunner { + /// Evaluate a request through the contract **with a live SLM provider**. + /// + /// Stages: `oracle` → `slm` (`slm_error` recorded when the provider + /// fails). Verdict combination mirrors the OTP arbiter's decision matrix + /// with the policy's asymmetric SLM weight (`enforcement.slm_weight`, + /// default 1.5 — inhibition is privileged over GO signals): + /// + /// * an oracle **Block** is terminal and is returned before the provider + /// is ever called (no proposal content leaves the process for a + /// decision already made); + /// * otherwise the weighted no-go score + /// (`spirit_score * slm_weight`, plus 0.2 when the oracle raised a soft + /// concern) is compared against `enforcement.block_threshold` → + /// **Block** (5xx spirit code); a `should_block` recommendation blocks + /// regardless of score; + /// * a no-go at/above `enforcement.escalate_threshold`, or low LLM + /// confidence (`llm_confidence <= 0.8`), → **Escalate**; + /// * otherwise the oracle verdict stands (Allow, or Warn with its + /// original soft refusal). + /// + /// **Fail-closed**: any provider error (timeout, transport, malformed or + /// out-of-range output) prevents Allow/Warn and yields an **Escalate** + /// with a 9xx system code — an SLM outage is never an all-clear. + pub fn evaluate_with_provider( + &self, + request: &GatingRequest, + provider: &dyn SlmProvider, + ) -> Result { + let start = std::time::Instant::now(); + + // Stage 1: Oracle (identical semantics to `evaluate`). + let mut stages_executed = vec!["oracle".to_string()]; + let oracle_eval = self.oracle.check_proposal(&request.proposal)?; + let (oracle_verdict, oracle_refusal) = self.process_oracle_result(&oracle_eval); + + // An oracle BLOCK is terminal: never re-litigated by the SLM, and the + // proposal content is never forwarded to a provider needlessly. + if oracle_verdict == Verdict::Block { + let duration = start.elapsed(); + return Ok(GatingDecision { + request_id: request.request_id, + decision_id: Uuid::new_v4(), + timestamp: Utc::now(), + verdict: oracle_verdict, + refusal: oracle_refusal, + evaluations: EvaluationChain { + oracle: Some(oracle_eval.clone()), + slm: None, + arbiter: None, + }, + processing: ProcessingMetadata { + duration_us: duration.as_micros() as u64, + contract_version: CONTRACT_VERSION.to_string(), + policy_name: self.policy.name.clone(), + rules_checked: oracle_eval.rules_checked.len(), + stages_executed, + }, + }); + } + + // Stage 2: SLM (correlated with the proposal under evaluation). + stages_executed.push("slm".to_string()); + let slm_request = SlmRequest { + proposal_id: request.proposal.id, + content: request.proposal.content.clone(), + context: format!( + "policy={} source={} session={} agent={}", + self.policy.name, + request.context.source, + request.context.session_id.as_deref().unwrap_or("-"), + request.context.agent_id.as_deref().unwrap_or("-"), + ), + // Provider-configured budget applies (0 = provider default). + max_tokens: 0, + }; + + let mut slm_vote: Option = None; + let (slm_stage, verdict, refusal) = match provider.evaluate(&slm_request) { + Ok(evaluation) => { + let slm_result = SlmEvaluationResult { + spirit_score: evaluation.spirit_score, + confidence: evaluation.confidence, + reasoning: evaluation.reasoning.clone(), + should_block: evaluation.should_block, + }; + + // Asymmetric no-go (mirrors the OTP arbiter's decision matrix). + let weight = self.policy.enforcement.slm_weight; + let mut no_go = evaluation.spirit_score * weight; + if matches!(oracle_verdict, Verdict::Warn) { + // Oracle soft concerns add 0.2 to the no-go score. + no_go += 0.2; + } + let go = request.proposal.llm_confidence; + let escalate_at = self.policy.enforcement.escalate_threshold; + let block_at = self.policy.enforcement.block_threshold; + + if evaluation.should_block || no_go >= block_at { + slm_vote = Some(Verdict::Block); + ( + Some(slm_result), + Verdict::Block, + Some(Refusal { + category: RefusalCategory::IntentViolation, + code: RefusalCode::Spirit599OtherSpirit, + message: format!( + "SLM spirit violation (score {:.2}, weighted no-go {:.2}): {}", + evaluation.spirit_score, no_go, evaluation.reasoning + ), + remediation: Some( + "Revise the proposal to match the spirit of the policy".to_string(), + ), + evidence: Vec::new(), + overridable: true, + override_level: Some(AuthorizationLevel::Maintainer), + }), + ) + } else if no_go >= escalate_at || go <= 0.8 { + slm_vote = Some(Verdict::Escalate); + ( + Some(slm_result), + Verdict::Escalate, + Some(Refusal { + category: RefusalCategory::IntentViolation, + code: RefusalCode::Spirit505IntentMismatch, + message: format!( + "uncertain spirit assessment (LLM go {go:.2}, weighted no-go {no_go:.2}): {}", + evaluation.reasoning + ), + remediation: Some( + "Route to human review per the gating policy".to_string(), + ), + evidence: Vec::new(), + overridable: true, + override_level: Some(AuthorizationLevel::User), + }), + ) + } else { + slm_vote = Some(Verdict::Allow); + // Oracle verdict stands (Allow, or Warn with its soft refusal). + (Some(slm_result), oracle_verdict, oracle_refusal) + } + } + Err(error) => { + stages_executed.push("slm_error".to_string()); + ( + None, + Verdict::Escalate, + Some(Refusal { + category: RefusalCategory::SystemError, + code: RefusalCode::Sys902InternalError, + message: format!( + "SLM evaluation failed; failing closed rather than allowing: {error}" + ), + remediation: Some( + "Restore the SLM provider, or review the proposal manually".to_string(), + ), + evidence: Vec::new(), + overridable: false, + override_level: Some(AuthorizationLevel::Admin), + }), + ) + } + }; + + let arbiter = slm_vote.map(|vote| ArbiterResult { + consensus_reached: true, + oracle_vote: oracle_verdict, + slm_vote: vote, + final_verdict: verdict, + slm_weight: self.policy.enforcement.slm_weight, + }); + + let duration = start.elapsed(); + Ok(GatingDecision { + request_id: request.request_id, + decision_id: Uuid::new_v4(), + timestamp: Utc::now(), + verdict, + refusal, + evaluations: EvaluationChain { + oracle: Some(oracle_eval.clone()), + slm: slm_stage, + arbiter, + }, + processing: ProcessingMetadata { + duration_us: duration.as_micros() as u64, + contract_version: CONTRACT_VERSION.to_string(), + policy_name: self.policy.name.clone(), + rules_checked: oracle_eval.rules_checked.len(), + stages_executed, + }, + }) + } +} + // ============================================================================ // UNIT TESTS // ============================================================================ @@ -1865,8 +2075,8 @@ mod tests { let results = harness.run_all(&tests); assert_eq!(results.len(), 2); - assert_eq!(results[0].passed, true); - assert_eq!(results[1].passed, true); + assert!(results[0].passed); + assert!(results[1].passed); } #[test] @@ -2118,3 +2328,249 @@ mod tests { assert!(metadata.stages_executed.is_empty()); } } + +// ============================================================================ +// SLM STAGE TESTS — evaluate_with_provider with mock providers +// ============================================================================ + +#[cfg(test)] +mod slm_stage_tests { + use super::*; + use policy_oracle::ActionType; + use slm_evaluator::{SlmError, SlmEvaluation, SlmProvider, SlmRequest}; + use std::sync::atomic::{AtomicUsize, Ordering}; + + /// Configurable canned-verdict provider; counts invocations. + struct MockSlm { + spirit_score: f64, + should_block: bool, + calls: AtomicUsize, + } + + impl MockSlm { + fn verdict(spirit_score: f64, should_block: bool) -> Self { + Self { + spirit_score, + should_block, + calls: AtomicUsize::new(0), + } + } + + fn clean() -> Self { + Self::verdict(0.05, false) + } + + fn calls(&self) -> usize { + self.calls.load(Ordering::SeqCst) + } + } + + impl SlmProvider for MockSlm { + fn name(&self) -> &str { + "mock-slm" + } + + fn evaluate(&self, request: &SlmRequest) -> Result { + self.calls.fetch_add(1, Ordering::SeqCst); + Ok(SlmEvaluation { + proposal_id: request.proposal_id, + spirit_score: self.spirit_score, + confidence: 0.9, + reasoning: "mock verdict".to_string(), + should_block: self.should_block, + }) + } + } + + struct FailingSlm; + + impl SlmProvider for FailingSlm { + fn name(&self) -> &str { + "failing-mock-slm" + } + + fn evaluate(&self, _request: &SlmRequest) -> Result { + Err(SlmError::Timeout("mock provider timeout".to_string())) + } + } + + fn request_for(path: &str, content: &str, llm_confidence: f32) -> GatingRequest { + GatingRequest::new(Proposal { + id: Uuid::new_v4(), + action_type: ActionType::CreateFile { + path: path.to_string(), + }, + content: content.to_string(), + files_affected: vec![path.to_string()], + llm_confidence, + }) + } + + #[test] + fn oracle_block_is_terminal_and_never_calls_provider() { + let runner = ContractRunner::new(); + let provider = MockSlm::clean(); + let request = request_for("util.ts", "const x: string = 'y';", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Block); + assert_eq!(provider.calls(), 0, "provider must not run on oracle block"); + assert!(decision.evaluations.slm.is_none()); + assert!(decision.evaluations.arbiter.is_none()); + assert_eq!( + decision.processing.stages_executed, + vec!["oracle".to_string()] + ); + } + + #[test] + fn clean_slm_keeps_oracle_allow() { + let runner = ContractRunner::new(); + let provider = MockSlm::clean(); + let request = request_for("src/main.rs", "fn main() {}", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Allow); + assert!(decision.refusal.is_none()); + assert!(decision.evaluations.slm.is_some()); + let arbiter = decision.evaluations.arbiter.expect("arbiter result"); + assert!(arbiter.consensus_reached); + assert_eq!(arbiter.oracle_vote, Verdict::Allow); + assert_eq!(arbiter.slm_vote, Verdict::Allow); + assert_eq!(arbiter.final_verdict, Verdict::Allow); + assert!((arbiter.slm_weight - 1.5).abs() < f64::EPSILON); + assert_eq!( + decision.processing.stages_executed, + vec!["oracle".to_string(), "slm".to_string()] + ); + } + + #[test] + fn high_weighted_spirit_score_blocks() { + let runner = ContractRunner::new(); + // 0.9 * 1.5 = 1.35 >= block_threshold (0.7) + let provider = MockSlm::verdict(0.9, false); + let request = request_for("src/main.rs", "fn main() {}", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Block); + let refusal = decision.refusal.expect("spirit refusal"); + assert_eq!(refusal.category, RefusalCategory::IntentViolation); + assert_eq!(refusal.code, RefusalCode::Spirit599OtherSpirit); + assert!(refusal.overridable); + assert_eq!(refusal.override_level, Some(AuthorizationLevel::Maintainer)); + assert_eq!( + decision.evaluations.arbiter.unwrap().slm_vote, + Verdict::Block + ); + } + + #[test] + fn should_block_flag_blocks_regardless_of_score() { + let runner = ContractRunner::new(); + // Low score but the model's hard recommendation is to block. + let provider = MockSlm::verdict(0.01, true); + let request = request_for("src/main.rs", "fn main() {}", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Block); + assert_eq!( + decision.refusal.unwrap().code, + RefusalCode::Spirit599OtherSpirit + ); + } + + #[test] + fn mid_weighted_score_escalates() { + let runner = ContractRunner::new(); + // 0.35 * 1.5 = 0.525 >= escalate_threshold (0.4), below block (0.7) + let provider = MockSlm::verdict(0.35, false); + let request = request_for("src/main.rs", "fn main() {}", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Escalate); + let refusal = decision.refusal.expect("escalation refusal"); + assert_eq!(refusal.code, RefusalCode::Spirit505IntentMismatch); + assert_eq!( + decision.evaluations.arbiter.unwrap().slm_vote, + Verdict::Escalate + ); + } + + #[test] + fn low_llm_confidence_escalates_despite_clean_slm() { + let runner = ContractRunner::new(); + let provider = MockSlm::clean(); + let request = request_for("src/main.rs", "fn main() {}", 0.5); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Escalate); + } + + #[test] + fn oracle_soft_concern_is_escalated_by_slm_weighting() { + let runner = ContractRunner::new(); + // Tier-2 language fixture: oracle Warn (racket marker present). + let warn_request = request_for("script.rkt", "#lang racket", 0.95); + + // Clean SLM (0.05*1.5 + 0.2 = 0.275 < 0.4): Warn stands. + let provider = MockSlm::clean(); + let decision = runner + .evaluate_with_provider(&warn_request, &provider) + .unwrap(); + assert_eq!(decision.verdict, Verdict::Warn); + + // Moderate SLM (0.15*1.5 + 0.2 = 0.425 >= 0.4): Escalate. + let provider = MockSlm::verdict(0.15, false); + let decision = runner + .evaluate_with_provider(&warn_request, &provider) + .unwrap(); + assert_eq!(decision.verdict, Verdict::Escalate); + } + + #[test] + fn provider_failure_escalates_fail_closed() { + let runner = ContractRunner::new(); + let request = request_for("src/main.rs", "fn main() {}", 0.95); + + let decision = runner + .evaluate_with_provider(&request, &FailingSlm) + .unwrap(); + + assert_eq!(decision.verdict, Verdict::Escalate); + let refusal = decision.refusal.expect("system refusal"); + assert_eq!(refusal.category, RefusalCategory::SystemError); + assert_eq!(refusal.code, RefusalCode::Sys902InternalError); + assert!( + !refusal.overridable, + "system failure must not be overridable" + ); + assert!(refusal.message.contains("failing closed")); + assert!(decision.evaluations.slm.is_none()); + assert!(decision.evaluations.arbiter.is_none()); + assert!(decision + .processing + .stages_executed + .contains(&"slm_error".to_string())); + } + + #[test] + fn warnings_do_not_lose_their_soft_refusal_when_slm_is_clean() { + let runner = ContractRunner::new(); + let provider = MockSlm::clean(); + let request = request_for("script.rkt", "#lang racket", 0.95); + + let decision = runner.evaluate_with_provider(&request, &provider).unwrap(); + + assert_eq!(decision.verdict, Verdict::Warn); + let refusal = decision.refusal.expect("soft refusal preserved"); + assert_eq!(refusal.category, RefusalCategory::ForbiddenLanguage); + assert_eq!(refusal.code, RefusalCode::Lang199OtherForbidden); + } +} diff --git a/src/main.rs b/src/main.rs index 9d618d3..49544f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,7 +115,7 @@ struct Cli { #[arg(long, global = true)] no_color: bool, - /// Custom policy file in JSON format (the embedded Nickel file is not loaded at runtime) + /// Custom policy file (JSON; native .ncl when built with --features nickel) #[arg(short, long, global = true)] policy_file: Option, @@ -335,6 +335,15 @@ enum ContractAction { /// Include audit log entry in output #[arg(long)] audit: bool, + + /// Route the request through the configured SLM provider + /// + /// Requires CONATIVE_SLM_PROVIDER=llama (with CONATIVE_GGUF_MODEL, and + /// optionally CONATIVE_LLAMA_CLI) or CONATIVE_SLM_PROVIDER=http (with + /// CONATIVE_SLM_ENDPOINT; SLM_API_KEY for auth; binary built with + /// --features slm-http). Provider failures escalate fail-closed. + #[arg(long)] + slm: bool, }, /// Display contract schema information @@ -514,12 +523,13 @@ fn main() { request, format, audit, + slm, } => { if cli.dry_run { println!("[dry-run] Would evaluate request: {}", request.display()); 0 } else { - eval_contract_request(&request, &format, audit) + eval_contract_request(&request, &format, audit, slm) } } ContractAction::Schema { format, section } => { @@ -567,16 +577,9 @@ fn main() { } fn load_policy_oracle(path: &Path) -> Result { - let content = std::fs::read_to_string(path).map_err(|error| error.to_string())?; - if path.extension().is_some_and(|extension| extension == "ncl") { - return Err( - "Nickel policy loading is not available in the Rust CLI yet; provide a JSON policy export" - .to_string(), - ); - } - let policy: Policy = serde_json::from_str(&content) - .map_err(|error| format!("invalid JSON policy {}: {error}", path.display()))?; - Ok(Oracle::new(policy)) + // Dispatch lives in the oracle: JSON everywhere, native `.ncl` when the + // binary was built with the `nickel` feature — failing closed otherwise. + Oracle::from_policy_file(path).map_err(|error| error.to_string()) } fn scan_directory( @@ -1196,7 +1199,12 @@ fn load_test_case_file(path: &Path) -> Result { }) } -fn eval_contract_request(request_path: &Path, format: &OutputFormat, include_audit: bool) -> i32 { +fn eval_contract_request( + request_path: &Path, + format: &OutputFormat, + include_audit: bool, + use_slm: bool, +) -> i32 { let content = match std::fs::read_to_string(request_path) { Ok(c) => c, Err(e) => { @@ -1214,7 +1222,30 @@ fn eval_contract_request(request_path: &Path, format: &OutputFormat, include_aud }; let runner = ContractRunner::new(); - let decision = match runner.evaluate(&request) { + let decision_result = if use_slm { + // Explicit request for a live SLM stage: provider configuration is + // mandatory, misconfiguration is loud (exit 3), and provider failures + // escalate fail-closed inside the contract. + match slm_evaluator::from_env() { + Ok(Some(provider)) => runner.evaluate_with_provider(&request, provider.as_ref()), + Ok(None) => { + eprintln!( + "--slm requested but no SLM provider is configured. Set \ + CONATIVE_SLM_PROVIDER=llama (+CONATIVE_GGUF_MODEL, CONATIVE_LLAMA_CLI) \ + or CONATIVE_SLM_PROVIDER=http (+CONATIVE_SLM_ENDPOINT; SLM_API_KEY; \ + build with --features slm-http)." + ); + return 3; + } + Err(error) => { + eprintln!("SLM provider misconfigured: {error}"); + return 3; + } + } + } else { + runner.evaluate(&request) + }; + let decision = match decision_result { Ok(d) => d, Err(e) => { eprintln!("Error evaluating request: {}", e); diff --git a/src/oracle/Cargo.toml b/src/oracle/Cargo.toml index 0e21d87..100c99b 100644 --- a/src/oracle/Cargo.toml +++ b/src/oracle/Cargo.toml @@ -15,3 +15,14 @@ thiserror.workspace = true tracing.workspace = true glob = "0.3" regex = "1" +# Native Nickel policy evaluation via the reviewed vendor fork in +# vendor/bunsenite (see vendor/bunsenite/VENDOR.adoc). Default features are +# disabled on the vendor fork: no REPL/format/doc/markdown feature load. +bunsenite = { path = "../../vendor/bunsenite", optional = true, default-features = false } + +[features] +# Native Nickel (.ncl) policy loading. Off by default: default builds compile +# without the Nickel dependency tree, and .ncl policy files are rejected +# fail-closed instead of being silently ignored. +default = [] +nickel = ["dep:bunsenite"] diff --git a/src/oracle/src/lib.rs b/src/oracle/src/lib.rs index ced0ef3..775d061 100644 --- a/src/oracle/src/lib.rs +++ b/src/oracle/src/lib.rs @@ -15,6 +15,10 @@ use std::path::{Path, PathBuf}; use thiserror::Error; use uuid::Uuid; +/// Native Nickel policy loading (requires the `nickel` feature). +#[cfg(feature = "nickel")] +pub mod nickel; + // ============ Core Types ============ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -234,6 +238,19 @@ pub enum OracleError { RegexError(#[from] regex::Error), #[error("Invalid glob pattern: {0}")] GlobError(String), + /// Native Nickel evaluation failed: parse error, evaluation error, or the + /// evaluated record does not match the `Policy` contract. + #[cfg(feature = "nickel")] + #[error("native Nickel policy evaluation failed: {0}")] + NickelEvaluation(String), + /// A Nickel policy containing an `import` statement was rejected + /// fail-closed. Multi-file Nickel policies are intentionally unsupported; + /// see `docs/NICKEL-POLICY.adoc`. + #[cfg(feature = "nickel")] + #[error( + "Nickel policy imports are not supported (fail-closed); inline the policy or export JSON (found: {0})" + )] + NickelImportUnsupported(String), } // ============ Oracle Implementation ============ @@ -251,6 +268,16 @@ impl Oracle { Self::new(Policy::rsr_default()) } + /// Construct an oracle from a policy file on disk. + /// + /// Dispatch is extension-based: `.ncl` files use + /// [`Policy::from_policy_file`]'s native Nickel path (which requires the + /// `nickel` feature and fails closed without it); everything else is + /// parsed as JSON. + pub fn from_policy_file(path: &Path) -> Result { + Ok(Self::new(Policy::from_policy_file(path)?)) + } + /// Check a proposal against policy pub fn check_proposal(&self, proposal: &Proposal) -> Result { let mut rules_checked = Vec::new(); @@ -659,6 +686,54 @@ fn push_unique_concern(concerns: &mut Vec, candidate: FileConcern) // ============ Default Policy ============ +impl Policy { + /// Load a policy from disk, dispatching on the file extension. + /// + /// * `.ncl` — evaluated as native Nickel. Requires the `nickel` feature; + /// without it this is a fail-closed error rather than a silent fallback + /// to a compiled-in policy. + /// * anything else — deserialised as JSON. + pub fn from_policy_file(path: &Path) -> Result { + if path.extension().is_some_and(|extension| extension == "ncl") { + #[cfg(feature = "nickel")] + { + return Self::from_nickel_file(path); + } + #[cfg(not(feature = "nickel"))] + { + return Err(OracleError::PolicyParseError(format!( + "native Nickel policy support is not compiled in; rebuild with \ + --features nickel (see docs/NICKEL-POLICY.adoc) or export the \ + policy as JSON: {}", + path.display() + ))); + } + } + let content = fs::read_to_string(path)?; + serde_json::from_str(&content).map_err(|error| { + OracleError::PolicyParseError(format!( + "invalid JSON policy {}: {error}", + path.display() + )) + }) + } + + /// Evaluate Nickel source to a policy (requires the `nickel` feature). + /// + /// Sources containing `import` statements are rejected fail-closed; see + /// [`nickel::reject_imports`]. + #[cfg(feature = "nickel")] + pub fn from_nickel_source(content: &str, source_name: &str) -> Result { + nickel::policy_from_nickel_source(content, source_name) + } + + /// Load and evaluate a `.ncl` policy file (requires the `nickel` feature). + #[cfg(feature = "nickel")] + pub fn from_nickel_file(path: &Path) -> Result { + nickel::policy_from_nickel_file(path) + } +} + impl Policy { /// RSR-compliant default policy pub fn rsr_default() -> Self { @@ -1285,3 +1360,137 @@ mod tests { )); } } + +// ============ Native Nickel feature tests ============ +// +// These run under the dedicated CI job: +// CARGO_BUILD_JOBS=1 RUSTFLAGS="-C debuginfo=0 -Dwarnings" \ +// cargo test -p policy-oracle --features nickel --lib --locked + +#[cfg(all(test, feature = "nickel"))] +mod nickel_feature_tests { + use super::*; + + /// The repository policy is the native fixture: it must evaluate and must + /// agree exactly with the compiled-in RSR default policy. + #[test] + fn repository_policy_ncl_matches_rsr_default() { + let policy_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("config") + .join("policy.ncl"); + let policy = Policy::from_nickel_file(&policy_path).unwrap_or_else(|error| { + panic!( + "repository policy {} failed to evaluate: {error}", + policy_path.display() + ) + }); + + let expected = serde_json::to_value(Policy::rsr_default()).unwrap(); + let actual = serde_json::to_value(policy).unwrap(); + assert_eq!( + actual, expected, + "config/policy.ncl and Policy::rsr_default() diverged" + ); + } + + #[test] + fn from_policy_file_dispatches_ncl_extension() { + let policy_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("config") + .join("policy.ncl"); + let oracle = Oracle::from_policy_file(&policy_path).expect("dispatch and evaluate"); + let proposal = Proposal { + id: Uuid::new_v4(), + action_type: ActionType::CreateFile { + path: "util.ts".to_string(), + }, + content: "const x: string = 'y'".to_string(), + files_affected: vec!["util.ts".to_string()], + llm_confidence: 0.9, + }; + let result = oracle.check_proposal(&proposal).unwrap(); + assert!(matches!(result.verdict, PolicyVerdict::HardViolation(_))); + } + + #[test] + fn import_statement_is_rejected_fail_closed() { + let source = r#" +let base = import "./shared.ncl" in +{ name = "Uses shared base" } +"#; + let result = Policy::from_nickel_source(source, ""); + assert!( + matches!(result, Err(OracleError::NickelImportUnsupported(_))), + "import must be rejected fail-closed, got: {result:?}" + ); + } + + #[test] + fn import_mention_in_comment_is_not_rejected() { + let source = "# to reuse the base, `import \"shared.ncl\"` — kept as prose\n"; + assert!( + nickel::reject_imports(source).is_ok(), + "comment-only import mention must not trip the scanner" + ); + } + + #[test] + fn invalid_nickel_is_an_evaluation_error() { + let result = Policy::from_nickel_source("{ this is not = valid nickel", ""); + assert!(matches!(result, Err(OracleError::NickelEvaluation(_)))); + } + + #[test] + fn evaluated_record_must_match_policy_contract() { + let source = "{ name = 5 }"; + let result = Policy::from_nickel_source(source, ""); + assert!(matches!(result, Err(OracleError::NickelEvaluation(_)))); + } + + #[test] + fn json_policy_files_still_load() { + let path = std::env::temp_dir().join(format!("conative-policy-{}.json", Uuid::new_v4())); + let json = serde_json::to_string_pretty(&Policy::rsr_default()).unwrap(); + fs::write(&path, &json).unwrap(); + let loaded = Policy::from_policy_file(&path).expect("JSON policy loads"); + assert_eq!( + serde_json::to_value(loaded).unwrap(), + serde_json::to_value(Policy::rsr_default()).unwrap() + ); + let _ = fs::remove_file(path); + } +} + +/// Without the `nickel` feature, `.ncl` dispatch must fail closed: it is an +/// explicit error, never a silent fallback to any compiled-in policy. +#[cfg(all(test, not(feature = "nickel")))] +mod nickel_guard_tests { + use super::*; + + #[test] + fn ncl_policy_dispatch_fails_closed_without_feature() { + let path = std::env::temp_dir().join(format!("conative-policy-{}.ncl", Uuid::new_v4())); + fs::write(&path, "{ name = \"fixture\" }").unwrap(); + let result = Policy::from_policy_file(&path); + let _ = fs::remove_file(&path); + match result { + Err(OracleError::PolicyParseError(message)) => { + assert!(message.contains("--features nickel")); + } + other => panic!("expected fail-closed PolicyParseError, got: {other:?}"), + } + } + + #[test] + fn json_policy_files_load_without_feature() { + let path = std::env::temp_dir().join(format!("conative-policy-{}.json", Uuid::new_v4())); + let json = serde_json::to_string(&Policy::rsr_default()).unwrap(); + fs::write(&path, json).unwrap(); + assert!(Policy::from_policy_file(&path).is_ok()); + let _ = fs::remove_file(path); + } +} diff --git a/src/oracle/src/nickel.rs b/src/oracle/src/nickel.rs new file mode 100644 index 0000000..144b1e5 --- /dev/null +++ b/src/oracle/src/nickel.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Native Nickel (`.ncl`) policy loading via the vendored Bunsenite evaluator. +//! +//! ## Fail-closed design +//! +//! * This module only exists with `--features nickel`. Without the feature, +//! `.ncl` dispatch in [`crate::Policy::from_policy_file`] returns an error +//! instead of silently falling back to any default policy. +//! * Nickel `import` statements are **rejected before evaluation** +//! ([`OracleError::NickelImportUnsupported`]). The vendored Bunsenite +//! revision builds its Nickel program from only the file name, so import +//! resolution relies on ambient evaluator behaviour. Rather than accept +//! partially-resolved or ambient-dependent policies, multi-file imports are +//! not supported: inline the policy, or export it to JSON. See +//! `docs/NICKEL-POLICY.adoc`. +//! +//! ## Import scan +//! +//! [`reject_imports`] is a conservative single-pass scanner: it finds the +//! `import` keyword applied to a string literal (`import "foo.ncl"` or +//! `import 'foo.ncl'`, where Nickel's inter-string form is also quoted), +//! ignoring `#` line comments. Multiline/embedded occurrences inside string +//! *values* may cause false positives; those fail closed (policy rejected), +//! which is the safe direction, and they are documented in +//! `docs/NICKEL-POLICY.adoc`. + +use crate::{OracleError, Policy}; +use regex::Regex; +use std::path::Path; +use std::sync::OnceLock; + +/// `import` applied to a quoted path, e.g. `let base = import "./lib.ncl" in`. +fn import_pattern() -> &'static Regex { + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| { + Regex::new(r#"(?:^|[\s=(,\[{])import\s+["']"#).expect("static regex compiles") + }) +} + +/// Strip a trailing `#` line comment, respecting simple double-quoted ranges. +/// Nickel multiline strings (`m#"..."#`) are not fully tokenised; an +/// `import "..."` fragment inside one is treated as code (conservative). +fn strip_line_comment(line: &str) -> &str { + let mut in_string = false; + let mut prev = '\0'; + for (idx, ch) in line.char_indices() { + match ch { + '"' if prev != '\\' => in_string = !in_string, + '#' if !in_string => return &line[..idx], + _ => {} + } + prev = ch; + } + line +} + +/// Find the first `import`-of-a-path statement outside line comments. +fn find_import(content: &str) -> Option { + for line in content.lines() { + let code = strip_line_comment(line); + if let Some(found) = import_pattern().find(code) { + let excerpt: String = code[found.start()..] + .trim_start() + .chars() + .take(48) + .collect(); + return Some(excerpt); + } + } + None +} + +/// Reject any Nickel source that imports another file. +pub fn reject_imports(content: &str) -> Result<(), OracleError> { + match find_import(content) { + Some(excerpt) => Err(OracleError::NickelImportUnsupported(excerpt)), + None => Ok(()), + } +} + +/// Evaluate Nickel source to a [`Policy`] via the vendored Bunsenite evaluator. +/// +/// `source_name` is only used for evaluator/error diagnostics (e.g. the file +/// name or ``); evaluation does not perform filesystem access beyond +/// what the evaluator itself requires. +pub fn policy_from_nickel_source(content: &str, source_name: &str) -> Result { + reject_imports(content)?; + + let loader = bunsenite::NickelLoader::new(); + let value = loader + .parse_string(content, source_name) + .map_err(|error| OracleError::NickelEvaluation(error.to_string()))?; + + serde_json::from_value::(value).map_err(|error| { + OracleError::NickelEvaluation(format!( + "evaluated Nickel policy {source_name} does not match the Policy contract: {error}" + )) + }) +} + +/// Load and evaluate a `.ncl` policy file. +pub fn policy_from_nickel_file(path: &Path) -> Result { + let content = std::fs::read_to_string(path).map_err(OracleError::IoError)?; + let name = path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + policy_from_nickel_source(&content, &name) +} diff --git a/src/slm/Cargo.toml b/src/slm/Cargo.toml index a3b5e4d..1974cd4 100644 --- a/src/slm/Cargo.toml +++ b/src/slm/Cargo.toml @@ -13,4 +13,17 @@ serde_json.workspace = true uuid.workspace = true thiserror.workspace = true tracing.workspace = true -# llama-cpp-2 = "0.1" # Uncomment when ready for SLM integration +# Optional blocking HTTPS client for the remote (OpenAI-compatible) SLM +# provider. Rustls only: no native TLS system dependency is introduced. +reqwest = { version = "0.12", default-features = false, features = [ + "blocking", + "rustls-tls", + "json", +], optional = true } +# llama-cpp-2 = "0.1" # Uncomment when ready for in-process SLM integration + +[features] +# Remote SLM provider over HTTPS. Off by default; local llama.cpp provider +# works without it. +default = [] +http = ["dep:reqwest"] diff --git a/src/slm/src/http.rs b/src/slm/src/http.rs new file mode 100644 index 0000000..853eeb5 --- /dev/null +++ b/src/slm/src/http.rs @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Remote SLM provider over HTTPS (OpenAI-compatible chat-completions). +//! +//! Enabled with `--features http`. Honours the provider contract in +//! [`crate::provider`]: deterministic decoding (`temperature: 0`, +//! `response_format: json_object`, explicit token budget), strict response +//! shape, range validation, correlation echo, fail-closed errors. +//! +//! Endpoints must be `https://`. Plain `http://` is accepted **only** for +//! loopback hosts (`127.0.0.1`, `::1`, `localhost`) so local servers (e.g. +//! `llama-server`) and test fixtures can be exercised without TLS. This is a +//! fail-closed guard against transmitting requests — and credentials — in +//! cleartext. +//! +//! The API key is read from the environment (`SLM_API_KEY`) and never +//! logged or embedded in errors. + +use crate::provider::{ + build_prompt, complete_evaluation, parse_verdict, SlmProvider, DEFAULT_MAX_TOKENS, + DEFAULT_TIMEOUT, +}; +use crate::{SlmError, SlmEvaluation}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Blocking, OpenAI-compatible remote provider. +#[derive(Debug, Clone)] +pub struct HttpSlmProvider { + endpoint: String, + model: String, + api_key: Option, + max_tokens: u32, + timeout: Duration, +} + +impl HttpSlmProvider { + /// Create a provider for `endpoint` serving `model`. `endpoint` is the + /// server base — the OpenAI path `/v1/chat/completions` is appended. + pub fn new( + endpoint: impl Into, + model: impl Into, + api_key: Option, + ) -> Result { + let endpoint = endpoint.into(); + let endpoint = endpoint.trim_end_matches('/').to_string(); + if endpoint.is_empty() { + return Err(SlmError::NotConfigured( + "SLM endpoint must not be empty".to_string(), + )); + } + reject_plaintext_off_loopback(&endpoint)?; + Ok(Self { + endpoint, + model: model.into(), + api_key, + max_tokens: DEFAULT_MAX_TOKENS, + timeout: DEFAULT_TIMEOUT, + }) + } + + /// Override the default token budget and timeout. + pub fn with_limits(mut self, max_tokens: u32, timeout: Duration) -> Self { + self.max_tokens = max_tokens; + self.timeout = timeout; + self + } + + /// Build from the process environment: + /// + /// * `CONATIVE_SLM_ENDPOINT` — server base URL (required for + /// `Ok(Some(_))`; unset means the HTTP provider is not configured). + /// * `CONATIVE_SLM_MODEL_NAME` — model identifier (default `local-slm`). + /// * `SLM_API_KEY` — optional bearer token. + /// * `CONATIVE_SLM_MAX_TOKENS` / `CONATIVE_SLM_TIMEOUT_SECS` — as for the + /// local provider. + /// + /// The endpoint and key belong in a protected GitHub Environment for CI + /// smoke tests; they must never be exposed to untrusted PR code. See + /// `docs/SLM_PROVIDERS.adoc`. + pub fn from_env() -> Result, SlmError> { + let Ok(endpoint) = std::env::var("CONATIVE_SLM_ENDPOINT") else { + return Ok(None); + }; + let model = + std::env::var("CONATIVE_SLM_MODEL_NAME").unwrap_or_else(|_| "local-slm".to_string()); + let api_key = std::env::var("SLM_API_KEY") + .ok() + .filter(|key| !key.trim().is_empty()); + let mut provider = Self::new(endpoint, model, api_key)?; + if let Ok(tokens) = std::env::var("CONATIVE_SLM_MAX_TOKENS") { + provider.max_tokens = tokens.parse().map_err(|_| { + SlmError::NotConfigured("CONATIVE_SLM_MAX_TOKENS must be an integer".to_string()) + })?; + } + if let Ok(secs) = std::env::var("CONATIVE_SLM_TIMEOUT_SECS") { + let secs: u64 = secs.parse().map_err(|_| { + SlmError::NotConfigured("CONATIVE_SLM_TIMEOUT_SECS must be an integer".to_string()) + })?; + provider.timeout = Duration::from_secs(secs); + } + Ok(Some(provider)) + } + + /// The full chat-completions URL (diagnostics/tests). + pub fn completions_url(&self) -> String { + format!("{}/v1/chat/completions", self.endpoint) + } +} + +/// Fail closed when a plaintext endpoint is not loopback. +fn reject_plaintext_off_loopback(endpoint: &str) -> Result<(), SlmError> { + if endpoint.starts_with("https://") { + return Ok(()); + } + if let Some(rest) = endpoint.strip_prefix("http://") { + let authority = rest.split('/').next().unwrap_or_default(); + // Bracketed IPv6 (`[::1]:8080`) vs. `host:port`. + let host = if let Some(bracketed) = authority.strip_prefix('[') { + bracketed.split(']').next().unwrap_or_default() + } else { + authority.split(':').next().unwrap_or_default() + }; + if matches!(host, "127.0.0.1" | "localhost" | "::1") { + return Ok(()); + } + } + Err(SlmError::NotConfigured(format!( + "refusing non-loopback plaintext SLM endpoint (use https://, or a loopback \ + address for local servers): {endpoint}" + ))) +} + +#[derive(Serialize)] +struct ChatRequest<'a> { + model: &'a str, + messages: [ChatMessage<'a>; 1], + temperature: f64, + max_tokens: u32, + response_format: ResponseFormat, +} + +#[derive(Serialize)] +struct ChatMessage<'a> { + role: &'a str, + content: String, +} + +#[derive(Serialize)] +struct ResponseFormat { + #[serde(rename = "type")] + kind: &'static str, +} + +#[derive(Deserialize)] +struct ChatResponse { + choices: Vec, +} + +#[derive(Deserialize)] +struct ChatChoice { + message: ChatResponseMessage, +} + +#[derive(Deserialize)] +struct ChatResponseMessage { + content: String, +} + +impl SlmProvider for HttpSlmProvider { + fn name(&self) -> &str { + "http-openai-compatible" + } + + fn evaluate(&self, request: &crate::provider::SlmRequest) -> Result { + let tokens = match request.max_tokens { + 0 => self.max_tokens, + requested => requested.min(self.max_tokens.max(1)), + }; + let body = ChatRequest { + model: &self.model, + messages: [ChatMessage { + role: "user", + content: build_prompt(request), + }], + temperature: 0.0, + max_tokens: tokens, + response_format: ResponseFormat { + kind: "json_object", + }, + }; + + let client = reqwest::blocking::Client::builder() + .timeout(self.timeout) + .build() + .map_err(|error| { + SlmError::Transport(format!("failed to build HTTP client: {error}")) + })?; + + let mut call = client.post(self.completions_url()).json(&body); + if let Some(key) = &self.api_key { + call = call.bearer_auth(key); + } + + let response = call.send().map_err(|error| { + let hint = if error.is_timeout() { + "request timed out" + } else { + "transport error" + }; + SlmError::Transport(format!("{hint} calling SLM endpoint: {error}")) + })?; + + let status = response.status(); + if !status.is_success() { + let detail = response + .text() + .unwrap_or_default() + .chars() + .take(200) + .collect::(); + return Err(SlmError::Transport(format!( + "SLM endpoint returned {status}: {detail}" + ))); + } + + let parsed: ChatResponse = response.json().map_err(|error| { + SlmError::InvalidResponse(format!( + "endpoint returned non-chat-completions JSON: {error}" + )) + })?; + let content = parsed + .choices + .first() + .map(|choice| choice.message.content.as_str()) + .ok_or_else(|| { + SlmError::InvalidResponse("endpoint returned zero choices".to_string()) + })?; + + // The message content must itself be the provider JSON verdict — + // exactly like the local provider's stdout. + let verdict = parse_verdict(content)?; + Ok(complete_evaluation(request, verdict)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::provider::{SlmProvider, SlmRequest}; + use std::io::{BufRead, BufReader, Read, Write}; + use std::net::TcpListener; + use uuid::Uuid; + + /// Minimal std-only HTTP/1.1 test server: reads one request (headers + + /// Content-Length body), returns a canned response, and records the + /// request line, an optional Authorization header, and the raw body. + struct MockServer { + base_url: String, + request_line: std::sync::Arc>, + auth_header: std::sync::Arc>, + request_body: std::sync::Arc>, + handle: Option>, + } + + impl MockServer { + fn start(status_line: &'static str, response_body: &'static str) -> Self { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind loopback"); + let port = listener.local_addr().unwrap().port(); + let request_line = std::sync::Arc::new(std::sync::Mutex::new(String::new())); + let auth_header = std::sync::Arc::new(std::sync::Mutex::new(String::new())); + let request_body = std::sync::Arc::new(std::sync::Mutex::new(String::new())); + let (rl, ah, rb) = ( + request_line.clone(), + auth_header.clone(), + request_body.clone(), + ); + let handle = std::thread::spawn(move || { + let Ok((stream, _)) = listener.accept() else { + return; + }; + let mut reader = BufReader::new(stream); + let mut line = String::new(); + let mut content_length = 0usize; + // Request line first. + if reader.read_line(&mut line).is_ok() { + *rl.lock().unwrap() = line.trim().to_string(); + } + // Headers until the empty line. + loop { + line.clear(); + let Ok(read) = reader.read_line(&mut line) else { + break; + }; + if read == 0 || line == "\r\n" { + break; + } + let lower = line.to_lowercase(); + if let Some(value) = lower.strip_prefix("content-length:") { + content_length = value.trim().parse().unwrap_or(0); + } + // Header names are case-insensitive on the wire. + if let Some(prefix_end) = line.find(':') { + if line[..prefix_end].eq_ignore_ascii_case("authorization") { + *ah.lock().unwrap() = line[prefix_end + 1..].trim().to_string(); + } + } + } + // Body. + let mut body = vec![0u8; content_length]; + let _ = reader.read_exact(&mut body); + *rb.lock().unwrap() = String::from_utf8_lossy(&body).to_string(); + + let response = format!( + "{status_line}\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{response_body}", + response_body.len() + ); + let _ = reader.get_mut().write_all(response.as_bytes()); + }); + Self { + base_url: format!("http://127.0.0.1:{port}"), + request_line, + auth_header, + request_body, + handle: Some(handle), + } + } + + fn join(mut self) -> (String, String, String) { + if let Some(handle) = self.handle.take() { + let _ = handle.join(); + } + let take = |m: &std::sync::Mutex| m.lock().unwrap().clone(); + ( + take(&self.request_line), + take(&self.auth_header), + take(&self.request_body), + ) + } + } + + fn request() -> SlmRequest { + SlmRequest { + proposal_id: Uuid::new_v4(), + content: "fn main() {}".to_string(), + context: "unit test".to_string(), + max_tokens: 32, + } + } + + const VERDICT_JSON: &str = r#"{\"spirit_score\": 0.2, \"confidence\": 0.9, \"reasoning\": \"mock\", \"should_block\": false}"#; + + #[test] + fn posts_openai_shape_and_echoes_correlation() { + let chat_response = format!( + r#"{{"id":"chatcmpl-mock","choices":[{{"index":0,"message":{{"role":"assistant","content":"{VERDICT_JSON}"}}}}]}}"# + ); + let server = + MockServer::start("HTTP/1.1 200 OK", Box::leak(chat_response.into_boxed_str())); + let provider = + HttpSlmProvider::new(&server.base_url, "mock-model", Some("test-key".into())).unwrap(); + + let req = request(); + let evaluation = provider.evaluate(&req).unwrap(); + assert_eq!(evaluation.proposal_id, req.proposal_id); + assert!(!evaluation.should_block); + assert_eq!(evaluation.spirit_score, 0.2); + + let (request_line, auth, body) = server.join(); + assert_eq!(request_line, "POST /v1/chat/completions HTTP/1.1"); + assert_eq!(auth, "Bearer test-key"); + assert!(body.contains(r#""model":"mock-model""#)); + assert!(body.contains(r#""temperature":0.0"#)); + assert!(body.contains(r#""max_tokens":32"#)); + assert!(body.contains(r#""json_object""#)); + } + + #[test] + fn server_error_is_fail_closed() { + let server = MockServer::start("HTTP/1.1 500 Internal Server Error", "{}"); + let provider = HttpSlmProvider::new(&server.base_url, "mock-model", None).unwrap(); + assert!(matches!( + provider.evaluate(&request()), + Err(SlmError::Transport(_)) + )); + server.join(); + } + + #[test] + fn zero_choices_is_fail_closed() { + let server = MockServer::start("HTTP/1.1 200 OK", r#"{"choices":[]}"#); + let provider = HttpSlmProvider::new(&server.base_url, "mock-model", None).unwrap(); + assert!(matches!( + provider.evaluate(&request()), + Err(SlmError::InvalidResponse(_)) + )); + server.join(); + } + + #[test] + fn malformed_content_json_is_fail_closed() { + let server = MockServer::start( + "HTTP/1.1 200 OK", + r#"{"choices":[{"message":{"role":"assistant","content":"no verdict here"}}]}"#, + ); + let provider = HttpSlmProvider::new(&server.base_url, "mock-model", None).unwrap(); + assert!(matches!( + provider.evaluate(&request()), + Err(SlmError::InvalidResponse(_)) + )); + server.join(); + } + + #[test] + fn plaintext_non_loopback_is_refused() { + let result = HttpSlmProvider::new("http://slm.example.com", "m", None); + assert!(matches!(result, Err(SlmError::NotConfigured(_)))); + } + + #[test] + fn loopback_and_https_endpoints_accepted() { + assert!(HttpSlmProvider::new("http://127.0.0.1:8080", "m", None).is_ok()); + assert!(HttpSlmProvider::new("http://localhost:8080", "m", None).is_ok()); + assert!(HttpSlmProvider::new("http://[::1]:8080/", "m", None).is_ok()); + assert!(HttpSlmProvider::new("https://slm.example.com/", "m", None).is_ok()); + } + + #[test] + fn completions_url_strips_trailing_slash() { + let provider = HttpSlmProvider::new("https://slm.example.com/", "m", None).unwrap(); + assert_eq!( + provider.completions_url(), + "https://slm.example.com/v1/chat/completions" + ); + } +} diff --git a/src/slm/src/lib.rs b/src/slm/src/lib.rs index 3d76f1b..9ea646a 100644 --- a/src/slm/src/lib.rs +++ b/src/slm/src/lib.rs @@ -16,6 +16,21 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; +/// Provider adapters for local and remote SLM backends. +pub mod provider; + +/// Optional remote (OpenAI-compatible HTTPS) provider. +#[cfg(feature = "http")] +pub mod http; + +pub use provider::{ + build_prompt, from_env, parse_verdict, LlamaCppProvider, ProviderVerdict, SlmProvider, + SlmRequest, +}; + +#[cfg(feature = "http")] +pub use http::HttpSlmProvider; + /// SLM evaluation result #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SlmEvaluation { @@ -44,6 +59,20 @@ pub enum SlmError { ModelNotLoaded, #[error("Inference error: {0}")] InferenceError(String), + /// Provider is not (or is wrongly) configured, e.g. missing model path, + /// missing endpoint, or a requested feature that was not compiled in. + #[error("SLM provider not configured: {0}")] + NotConfigured(String), + /// The provider did not answer within its timeout. + #[error("SLM provider timeout: {0}")] + Timeout(String), + /// Spawn/transport failure (process spawn, exit status, HTTP transport). + #[error("SLM provider transport failure: {0}")] + Transport(String), + /// The provider answered, but the answer failed contract validation + /// (non-JSON, schema mismatch, out-of-range scores). Always fail-closed. + #[error("SLM provider returned an invalid response: {0}")] + InvalidResponse(String), } impl SlmEvaluator { diff --git a/src/slm/src/provider.rs b/src/slm/src/provider.rs new file mode 100644 index 0000000..20b1943 --- /dev/null +++ b/src/slm/src/provider.rs @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! SLM provider adapters: local `llama.cpp` CLI and (optionally) remote HTTPS. +//! +//! Both providers honour the same contract: +//! +//! * **Deterministic decoding** — temperature 0, explicit token limit. +//! * **Strict response shape** — the model must answer with a JSON object +//! `{"spirit_score", "confidence", "reasoning", "should_block"}`. Malformed +//! output is rejected, never coerced into a "safe-looking" result. +//! * **Range validation** — scores must be finite and within `0..=1`. +//! * **Correlation** — the request's `proposal_id` is carried through and +//! echoed on the evaluation; providers never invent one. +//! * **Fail-closed** — every failure mode is a [`SlmError`] variant, and +//! downstream callers (contract runner, arbiter) must treat provider +//! failure as NO-GO. +//! +//! Configuration is explicit: nothing downloads models or probes for binaries +//! at runtime. See `docs/SLM_PROVIDERS.adoc`. + +use crate::{SlmError, SlmEvaluation}; +use serde::{Deserialize, Serialize}; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; +use uuid::Uuid; + +/// Maximum proposal content forwarded to a provider (chars). Guards argv +/// limits for the CLI provider and keeps prompts bounded. +pub const MAX_CONTENT_CHARS: usize = 4096; + +/// Maximum context forwarded to a provider (chars). +pub const MAX_CONTEXT_CHARS: usize = 1024; + +/// Default decoding token budget. +/// Default per-request decoding budget. 256 gives small instruct models +/// enough room to emit long-form reasoning AND still close the JSON object: +/// at 128 tokens SmolLM2-135M/Qwen2.5-0.5B responses were observed truncated +/// mid-object, which correctly fails closed but wastes the evaluation. +pub const DEFAULT_MAX_TOKENS: u32 = 256; + +/// Default provider timeout. +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(120); + +/// A correlated evaluation request handed to a provider. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SlmRequest { + /// Correlation ID linking this request to a proposal — must be preserved + /// end-to-end so audit records can be joined to the gating request. + pub proposal_id: Uuid, + /// Proposal content under evaluation (bounded, see [`MAX_CONTENT_CHARS`]). + pub content: String, + /// Policy/request context (bounded, see [`MAX_CONTEXT_CHARS`]). + pub context: String, + /// Decoding token budget for this request. + pub max_tokens: u32, +} + +/// An SLM backend able to evaluate requests. +pub trait SlmProvider: Send + Sync { + /// Human-readable provider name for diagnostics and audit context. + fn name(&self) -> &str; + /// Evaluate a request, honouring the module-level provider contract. + fn evaluate(&self, request: &SlmRequest) -> Result; +} + +/// Trim `text` to at most `max` chars on a char boundary. +fn bounded(text: &str, max: usize) -> &str { + if text.chars().count() <= max { + text + } else { + match text.char_indices().nth(max) { + Some((idx, _)) => &text[..idx], + None => text, + } + } +} + +/// Build the deterministic evaluation prompt shared by all providers. +pub fn build_prompt(request: &SlmRequest) -> String { + let content = bounded(&request.content, MAX_CONTENT_CHARS); + let context = bounded(&request.context, MAX_CONTEXT_CHARS); + format!( + "You are a policy-spirit evaluator in a code-gating system. The deterministic \ + oracle has already ruled on explicit rules; you judge whether the proposal \ + violates the SPIRIT of the policy (e.g. disguised intent, verbosity abuse, \ + over-documentation to hide complexity, structural evasion).\n\n\ + Respond with ONLY a JSON object, no prose, no markdown fences, exactly:\n\ + {{\"spirit_score\": , \"confidence\": , \ + \"reasoning\": \"\", \"should_block\": }}\n\n\ + - spirit_score: estimated probability that the proposal violates the spirit of policy\n\ + - confidence: your confidence in that estimate\n\ + - should_block: true only if it must be blocked outright\n\n\ + POLICY CONTEXT:\n{context}\n\nPROPOSAL UNDER EVALUATION:\n{content}" + ) +} + +/// Wire format of the model's answer (validated before use). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProviderVerdict { + /// Estimated probability the proposal violates the spirit of policy. + pub spirit_score: f64, + /// Model confidence in its estimate. + pub confidence: f64, + /// Short human-readable justification. + pub reasoning: String, + /// Hard-block recommendation. + pub should_block: bool, +} + +impl ProviderVerdict { + /// Enforce the provider contract: finite in-range scores, bounded reasoning. + pub fn validate(&self) -> Result<(), SlmError> { + for (name, score) in [ + ("spirit_score", self.spirit_score), + ("confidence", self.confidence), + ] { + if !score.is_finite() || !(0.0..=1.0).contains(&score) { + return Err(SlmError::InvalidResponse(format!( + "{name} must be a finite number within 0..=1, got {score}" + ))); + } + } + if self.reasoning.chars().count() > 1000 { + return Err(SlmError::InvalidResponse( + "reasoning exceeds 1000 characters".to_string(), + )); + } + Ok(()) + } +} + +/// Extract ALL balanced top-level `{...}` JSON objects from `raw`, respecting +/// string literals and escapes (models sometimes wrap the object in prose). +fn extract_json_objects(raw: &str) -> Vec<&str> { + let mut objects = Vec::new(); + let mut depth = 0usize; + let mut in_string = false; + let mut escaped = false; + let mut start = 0usize; + for (offset, ch) in raw.char_indices() { + if escaped { + escaped = false; + continue; + } + match ch { + '\\' if in_string => escaped = true, + '"' => in_string = !in_string, + '{' if !in_string => { + if depth == 0 { + start = offset; + } + depth += 1; + } + '}' if !in_string && depth != 0 => { + depth -= 1; + if depth == 0 { + objects.push(&raw[start..=offset]); + } + } + _ => {} + } + } + objects +} + +/// Parse provider output text into a validated [`ProviderVerdict`]. +/// +/// Real CLI front-ends (notably current `llama-cli` conversation mode) pollute +/// stdout with banners, an echo of the prompt — which itself contains an +/// *invalid* template of the verdict object (`` placeholders) — +/// and throughput stats. The assistant's answer is the final output block, so +/// this scans every balanced object and accepts the LAST one that satisfies +/// the verdict schema and range validation. Anything else is treated as noise; +/// if no object validates, the response fails closed as invalid. +pub fn parse_verdict(raw: &str) -> Result { + let objects = extract_json_objects(raw); + if objects.is_empty() { + return Err(SlmError::InvalidResponse( + "no JSON object found in provider output".to_string(), + )); + } + let mut last_error = String::new(); + for object in objects.iter().rev() { + match serde_json::from_str::(object) + .map_err(|error| { + SlmError::InvalidResponse(format!( + "provider output failed schema validation: {error}" + )) + }) + .and_then(|verdict| verdict.validate().map(|()| verdict)) + { + Ok(verdict) => return Ok(verdict), + Err(error) => last_error = error.to_string(), + } + } + Err(SlmError::InvalidResponse(format!( + "no valid verdict object in provider output ({} object candidates; last error: {last_error})", + objects.len() + ))) +} + +/// Join a validated verdict with the request it answers, preserving correlation. +pub(crate) fn complete_evaluation(request: &SlmRequest, verdict: ProviderVerdict) -> SlmEvaluation { + SlmEvaluation { + proposal_id: request.proposal_id, + spirit_score: verdict.spirit_score, + confidence: verdict.confidence, + reasoning: verdict.reasoning, + should_block: verdict.should_block, + } +} + +// ============ Local llama.cpp provider ============ + +/// Provider that shells out to a `llama.cpp`-compatible executable. +/// +/// Invocation (explicit, deterministic, per the provider contract): +/// +/// ```text +/// llama-cli -m MODEL -p PROMPT -n TOKENS --temp 0 --no-display-prompt --single-turn +/// ``` +/// +/// `--no-display-prompt` and `--single-turn` are additive to the documented +/// argument set: the first keeps stdout JSON-extractable, the second +/// guarantees the process terminates after one generation instead of +/// parking in llama-cli's interactive conversation loop (observed with +/// llama.cpp nightlies, where plain `-p` never exits). Nothing is +/// downloaded or auto-discovered: both the executable and the GGUF model +/// path must be configured explicitly. +#[derive(Debug, Clone)] +pub struct LlamaCppProvider { + cli_path: PathBuf, + model_path: PathBuf, + max_tokens: u32, + timeout: Duration, +} + +impl LlamaCppProvider { + /// Create a provider; the model file must exist (fail fast with a clear + /// error rather than a confusing model-load failure at first request). + pub fn new( + cli_path: impl Into, + model_path: impl Into, + ) -> Result { + let model_path = model_path.into(); + if !model_path.is_file() { + return Err(SlmError::NotConfigured(format!( + "GGUF model not found: {}", + model_path.display() + ))); + } + Ok(Self { + cli_path: cli_path.into(), + model_path, + max_tokens: DEFAULT_MAX_TOKENS, + timeout: DEFAULT_TIMEOUT, + }) + } + + /// Override the default token budget and timeout. + pub fn with_limits(mut self, max_tokens: u32, timeout: Duration) -> Self { + self.max_tokens = max_tokens; + self.timeout = timeout; + self + } + + /// Build from the process environment: + /// + /// * `CONATIVE_GGUF_MODEL` — path to the GGUF model (required for + /// `Ok(Some(_))`; unset means the local provider is not configured). + /// * `CONATIVE_LLAMA_CLI` — executable path/name (default `llama-cli`). + /// * `CONATIVE_SLM_MAX_TOKENS` — token budget (default [`DEFAULT_MAX_TOKENS`]). + /// * `CONATIVE_SLM_TIMEOUT_SECS` — timeout seconds (default 120). + pub fn from_env() -> Result, SlmError> { + let Ok(model) = std::env::var("CONATIVE_GGUF_MODEL") else { + return Ok(None); + }; + let cli = std::env::var("CONATIVE_LLAMA_CLI").unwrap_or_else(|_| "llama-cli".to_string()); + let mut provider = Self::new(cli, model)?; + if let Ok(tokens) = std::env::var("CONATIVE_SLM_MAX_TOKENS") { + provider.max_tokens = tokens.parse().map_err(|_| { + SlmError::NotConfigured("CONATIVE_SLM_MAX_TOKENS must be an integer".to_string()) + })?; + } + if let Ok(secs) = std::env::var("CONATIVE_SLM_TIMEOUT_SECS") { + let secs: u64 = secs.parse().map_err(|_| { + SlmError::NotConfigured("CONATIVE_SLM_TIMEOUT_SECS must be an integer".to_string()) + })?; + provider.timeout = Duration::from_secs(secs); + } + Ok(Some(provider)) + } + + /// Path of the configured model (diagnostics only). + pub fn model_path(&self) -> &Path { + &self.model_path + } + + /// Path of the configured executable (diagnostics only). + pub fn cli_path(&self) -> &Path { + &self.cli_path + } +} + +impl SlmProvider for LlamaCppProvider { + fn name(&self) -> &str { + "llama.cpp-cli" + } + + fn evaluate(&self, request: &SlmRequest) -> Result { + let prompt = build_prompt(request); + // Per-request budget, defaulting to — and capped by — the provider's + // configured budget. `0` means "use the provider budget". + let tokens = match request.max_tokens { + 0 => self.max_tokens, + requested => requested.min(self.max_tokens.max(1)), + }; + + let mut child = Command::new(&self.cli_path) + .arg("-m") + .arg(&self.model_path) + .arg("-p") + .arg(&prompt) + .arg("-n") + .arg(tokens.to_string()) + .arg("--temp") + .arg("0") + .arg("--no-display-prompt") + .arg("--single-turn") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| { + SlmError::Transport(format!( + "failed to spawn {}: {error}", + self.cli_path.display() + )) + })?; + + // Drain pipes on threads so a verbose child cannot deadlock on a full + // stderr/stdout buffer while the main thread enforces the timeout. + let mut stdout_reader = child.stdout.take().expect("invariant: stdout was piped"); + let mut stderr_reader = child.stderr.take().expect("invariant: stderr was piped"); + let stdout_thread = std::thread::spawn(move || { + let mut buffer = String::new(); + let _ = stdout_reader.read_to_string(&mut buffer); + buffer + }); + let stderr_thread = std::thread::spawn(move || { + let mut buffer = String::new(); + let _ = stderr_reader.read_to_string(&mut buffer); + buffer + }); + + let started = Instant::now(); + let status = loop { + match child.try_wait() { + Ok(Some(status)) => break status, + Ok(None) => { + if started.elapsed() >= self.timeout { + let _ = child.kill(); + let _ = child.wait(); + let _ = stdout_thread.join(); + let _ = stderr_thread.join(); + return Err(SlmError::Timeout(format!( + "{} did not answer within {:?}", + self.cli_path.display(), + self.timeout + ))); + } + std::thread::sleep(Duration::from_millis(25)); + } + Err(error) => { + let _ = child.kill(); + let _ = child.wait(); + return Err(SlmError::Transport(format!( + "failed while waiting on {}: {error}", + self.cli_path.display() + ))); + } + } + }; + + let stdout = stdout_thread.join().unwrap_or_default(); + let stderr = stderr_thread.join().unwrap_or_default(); + + if !status.success() { + return Err(SlmError::Transport(format!( + "{} exited with {status}: {}", + self.cli_path.display(), + stderr.chars().take(200).collect::() + ))); + } + + let verdict = parse_verdict(&stdout)?; + Ok(complete_evaluation(request, verdict)) + } +} + +// ============ Provider selection from the environment ============ + +/// Select a provider from the environment: +/// +/// * `CONATIVE_SLM_PROVIDER=none` (or unset) → `Ok(None)` +/// * `CONATIVE_SLM_PROVIDER=llama` → [`LlamaCppProvider::from_env`] +/// * `CONATIVE_SLM_PROVIDER=http` → `HttpSlmProvider::from_env` (requires the +/// crate's `http` feature; without it this is a fail-closed error) +pub fn from_env() -> Result>, SlmError> { + let provider = std::env::var("CONATIVE_SLM_PROVIDER") + .unwrap_or_else(|_| "none".to_string()) + .to_lowercase(); + match provider.trim() { + "" | "none" | "disabled" => Ok(None), + "llama" | "llamacpp" | "llama-cpp" | "llama-cli" => Ok(LlamaCppProvider::from_env()? + .map(|p| std::sync::Arc::new(p) as std::sync::Arc)), + #[cfg(feature = "http")] + "http" | "https" | "openai" | "remote" => Ok(crate::http::HttpSlmProvider::from_env()? + .map(|p| std::sync::Arc::new(p) as std::sync::Arc)), + #[cfg(not(feature = "http"))] + "http" | "https" | "openai" | "remote" => Err(SlmError::NotConfigured( + "CONATIVE_SLM_PROVIDER=http requested but this build lacks the `http` feature; \ + rebuild with --features http" + .to_string(), + )), + other => Err(SlmError::NotConfigured(format!( + "unknown CONATIVE_SLM_PROVIDER={other} (expected: none | llama | http)" + ))), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn prompt_is_deterministic_and_bounded() { + let request = SlmRequest { + proposal_id: Uuid::nil(), + content: "x".repeat(10_000), + context: "ctx".to_string(), + max_tokens: 64, + }; + let a = build_prompt(&request); + let b = build_prompt(&request); + assert_eq!(a, b); + assert!(a.contains("POLICY CONTEXT")); + assert!(a.chars().count() < 10_000); + } + + #[test] + fn parse_accepts_clean_json() { + let verdict = parse_verdict( + r#"{"spirit_score": 0.1, "confidence": 0.9, "reasoning": "fine", "should_block": false}"#, + ) + .unwrap(); + assert!(!verdict.should_block); + assert_eq!(verdict.spirit_score, 0.1); + } + + #[test] + fn parse_extracts_json_from_prose() { + let raw = r#"Sure! Here is my answer: + {"spirit_score": 0.8, "confidence": 0.7, "reasoning": "uses strings like }", "should_block": true} + Hope that helps!"#; + let verdict = parse_verdict(raw).unwrap(); + assert!(verdict.should_block); + assert_eq!(verdict.reasoning, "uses strings like }"); + } + + #[test] + fn parse_rejects_out_of_range_scores() { + let raw = + r#"{"spirit_score": 1.5, "confidence": 0.7, "reasoning": "x", "should_block": false}"#; + assert!(matches!( + parse_verdict(raw), + Err(SlmError::InvalidResponse(_)) + )); + } + + #[test] + fn parse_rejects_non_json() { + assert!(matches!( + parse_verdict("definitely not json"), + Err(SlmError::InvalidResponse(_)) + )); + } + + #[test] + fn parse_rejects_schema_mismatch() { + let raw = r#"{"spirit_score": 0.1, "confidence": 0.2}"#; + assert!(matches!( + parse_verdict(raw), + Err(SlmError::InvalidResponse(_)) + )); + } + + #[test] + fn llama_provider_requires_existing_model() { + let result = LlamaCppProvider::new("llama-cli", "/nonexistent/model.gguf"); + assert!(matches!(result, Err(SlmError::NotConfigured(_)))); + } + + #[test] + fn provider_echoes_correlation_id() { + // A fake `llama-cli` shell script emitting a valid verdict; asserts + // the provider's stdout parsing and correlation echo end to end. + let dir = std::env::temp_dir().join(format!("conative-slm-{}", Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let model = dir.join("model.gguf"); + std::fs::write(&model, b"gguf-fixture").unwrap(); + let cli = dir.join("fake-llama-cli.sh"); + std::fs::write( + &cli, + "#!/bin/sh\nprintf '%s' '{\"spirit_score\": 0.2, \"confidence\": 0.9, \"reasoning\": \"fixture\", \"should_block\": false}'\n", + ) + .unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&cli, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + let provider = LlamaCppProvider::new(&cli, &model).unwrap(); + let request = SlmRequest { + proposal_id: Uuid::new_v4(), + content: "fn main() {}".to_string(), + context: "test".to_string(), + max_tokens: 16, + }; + let evaluation = provider.evaluate(&request).unwrap(); + assert_eq!(evaluation.proposal_id, request.proposal_id); + assert!(!evaluation.should_block); + std::fs::remove_dir_all(&dir).unwrap(); + } + + #[test] + fn provider_rejects_garbage_output_fail_closed() { + let dir = std::env::temp_dir().join(format!("conative-slm-{}", Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let model = dir.join("model.gguf"); + std::fs::write(&model, b"gguf-fixture").unwrap(); + let cli = dir.join("fake-llama-cli.sh"); + std::fs::write(&cli, "#!/bin/sh\necho 'I cannot answer that.'\n").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&cli, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + let provider = LlamaCppProvider::new(&cli, &model).unwrap(); + let request = SlmRequest { + proposal_id: Uuid::new_v4(), + content: "fn main() {}".to_string(), + context: "test".to_string(), + max_tokens: 16, + }; + assert!(matches!( + provider.evaluate(&request), + Err(SlmError::InvalidResponse(_)) + )); + std::fs::remove_dir_all(&dir).unwrap(); + } + + #[test] + fn provider_times_out_fail_closed() { + let dir = std::env::temp_dir().join(format!("conative-slm-{}", Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let model = dir.join("model.gguf"); + std::fs::write(&model, b"gguf-fixture").unwrap(); + let cli = dir.join("fake-llama-cli.sh"); + std::fs::write(&cli, "#!/bin/sh\nsleep 5\n").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&cli, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + let provider = LlamaCppProvider::new(&cli, &model) + .unwrap() + .with_limits(16, Duration::from_millis(300)); + let request = SlmRequest { + proposal_id: Uuid::new_v4(), + content: "fn main() {}".to_string(), + context: "test".to_string(), + max_tokens: 16, + }; + assert!(matches!( + provider.evaluate(&request), + Err(SlmError::Timeout(_)) + )); + std::fs::remove_dir_all(&dir).unwrap(); + } +} diff --git a/src/slm/tests/real_inference.rs b/src/slm/tests/real_inference.rs new file mode 100644 index 0000000..60ee0f3 --- /dev/null +++ b/src/slm/tests/real_inference.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Real model smoke tests — **ignored by default** and driven entirely by +//! the environment. They exercise the production provider paths against real +//! inference backends, never fixtures: +//! +//! Local llama.cpp (CI job `slm-real-inference` downloads a pinned binary + +//! pinned, SHA-256-verified GGUF model and runs this): +//! +//! ```sh +//! CONATIVE_LLAMA_CLI=./llama-cli CONATIVE_GGUF_MODEL=./model.gguf \ +//! cargo test -p slm-evaluator --test real_inference -- --ignored --nocapture +//! ``` +//! +//! Remote OpenAI-compatible endpoint (protected CI environment; also usable +//! against a local `llama-server`): +//! +//! ```sh +//! cargo test -p slm-evaluator --features http --test real_inference -- \ +//! --ignored --nocapture \ +//! # with CONATIVE_SLM_ENDPOINT, CONATIVE_SLM_MODEL_NAME, SLM_API_KEY +//! ``` +//! +//! A genuine model answer must satisfy the provider contract: JSON verdict, +//! in-range scores, correlation echo. + +use slm_evaluator::{LlamaCppProvider, SlmProvider, SlmRequest}; +use std::time::Instant; +use uuid::Uuid; + +fn smoke_request() -> SlmRequest { + SlmRequest { + proposal_id: Uuid::new_v4(), + content: "fn main() { println!(\"hello\"); }".to_string(), + context: "policy 'RSR Default Policy': Rust and Elixir are preferred; \ + TypeScript, Python, Go and Java are forbidden; npm requires deno" + .to_string(), + max_tokens: 0, + } +} + +#[test] +#[ignore = "requires CONATIVE_LLAMA_CLI + CONATIVE_GGUF_MODEL"] +fn real_llama_cpp_model_roundtrip() { + let provider = LlamaCppProvider::from_env() + .expect("environment must parse") + .unwrap_or_else(|| { + panic!( + "real smoke test requires CONATIVE_GGUF_MODEL (and optionally \ + CONATIVE_LLAMA_CLI); see docs/SLM_PROVIDERS.adoc" + ) + }); + + let request = smoke_request(); + let started = Instant::now(); + let evaluation = provider + .evaluate(&request) + .unwrap_or_else(|error| panic!("real model evaluation failed contract: {error}")); + let elapsed = started.elapsed(); + + assert_eq!( + evaluation.proposal_id, request.proposal_id, + "correlation id must echo the request" + ); + assert!((0.0..=1.0).contains(&evaluation.spirit_score)); + assert!((0.0..=1.0).contains(&evaluation.confidence)); + + // Surface the run for CI logs / docs/SLM_PROVIDERS.adoc benchmarking. + eprintln!( + "REAL-INFERENCE(llama.cpp): model={} verdict={{spirit_score: {:.2}, confidence: {:.2}, \ + should_block: {}, reasoning: {:?}}} latency={:?} tokens_budget={} cli={}", + provider.model_path().display(), + evaluation.spirit_score, + evaluation.confidence, + evaluation.should_block, + evaluation.reasoning, + elapsed, + request.max_tokens, + provider.cli_path().display(), + ); +} + +#[cfg(feature = "http")] +#[test] +#[ignore = "requires CONATIVE_SLM_ENDPOINT (+ optional SLM_API_KEY)"] +fn real_http_endpoint_roundtrip() { + let provider = slm_evaluator::HttpSlmProvider::from_env() + .expect("environment must parse") + .unwrap_or_else(|| { + panic!( + "real HTTP smoke test requires CONATIVE_SLM_ENDPOINT; see \ + docs/SLM_PROVIDERS.adoc" + ) + }); + + let request = smoke_request(); + let started = Instant::now(); + let evaluation = provider + .evaluate(&request) + .unwrap_or_else(|error| panic!("real HTTP evaluation failed contract: {error}")); + let elapsed = started.elapsed(); + + assert_eq!(evaluation.proposal_id, request.proposal_id); + assert!((0.0..=1.0).contains(&evaluation.spirit_score)); + assert!((0.0..=1.0).contains(&evaluation.confidence)); + + eprintln!( + "REAL-INFERENCE(http): url={} verdict={{spirit_score: {:.2}, confidence: {:.2}, \ + should_block: {}, reasoning: {:?}}} latency={:?}", + provider.completions_url(), + evaluation.spirit_score, + evaluation.confidence, + evaluation.should_block, + evaluation.reasoning, + elapsed, + ); +} diff --git a/tests/generative_test.rs b/tests/generative_test.rs new file mode 100644 index 0000000..add432b --- /dev/null +++ b/tests/generative_test.rs @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Generative (property-based) tests for the SLM stage of the gating +//! contract. +//! +//! Invariants under test (from the upstream delivery spec): +//! - an oracle Block is terminal: the provider is never invoked, for ANY +//! provider outcome; +//! - the SLM decision matrix obeys the enforcement thresholds at ALL scores, +//! including 0 and 1; +//! - the oracle Warn addend (+0.2 no-go) shifts the matrix predictably; +//! - low LLM confidence (<= 0.8) always escalates; +//! - any provider failure (timeout, transport, invalid response, outage) +//! fails closed — never Allow/Warn, always a non-overridable Escalate; +//! - determinism: identical votes → identical verdict; +//! - responses preserve the request/correlation ID; +//! - concurrent requests never mix correlation IDs. +//! +//! The OTP arbiter counterpart of this suite lives in +//! `src/arbiter/test/` (ExUnit); audit-sink persistence invariants are +//! tested there (`audit persistence failure never allows`). + +use gating_contract::{ContractRunner, GatingRequest, RefusalCode, Verdict}; +use policy_oracle::{ActionType, EnforcementConfig, Proposal}; +use slm_evaluator::{SlmError, SlmEvaluation, SlmProvider, SlmRequest}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use uuid::Uuid; + +use proptest::prelude::*; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const CLEAN_PATH: &str = "src/main.rs"; +const CLEAN_CONTENT: &str = "fn main() { println!(\"ok\"); }"; +const WARN_PATH: &str = "script.rkt"; +const WARN_CONTENT: &str = "#lang racket\n(displayln \"hi\")\n"; +const BLOCK_PATH: &str = "tool.py"; +const BLOCK_CONTENT: &str = "import os\nos.system('ls')\n"; + +fn proposal(path: &str, content: &str, llm_confidence: f32) -> Proposal { + Proposal { + id: Uuid::new_v4(), + action_type: ActionType::CreateFile { + path: path.to_string(), + }, + content: content.to_string(), + files_affected: vec![path.to_string()], + llm_confidence, + } +} + +// --------------------------------------------------------------------------- +// Mock providers +// --------------------------------------------------------------------------- + +/// Provider with a proptest-controlled outcome, counting invocations. +struct ScriptedSlm { + calls: AtomicUsize, + outcome: Outcome, +} + +#[derive(Clone, Debug)] +enum Outcome { + /// Echoes the request's proposal ID in the response. + Eval { + spirit_score: f64, + confidence: f64, + should_block: bool, + reasoning: String, + }, + /// Fails with the given error (constructed per call; SlmError is !Clone). + Err(ErrorKind), +} + +#[derive(Clone, Debug)] +enum ErrorKind { + ModelNotLoaded, + Inference, + NotConfigured, + Timeout, + Transport, + InvalidResponse, +} + +impl ErrorKind { + fn build(&self, msg: &str) -> SlmError { + match self { + ErrorKind::ModelNotLoaded => SlmError::ModelNotLoaded, + ErrorKind::Inference => SlmError::InferenceError(msg.to_string()), + ErrorKind::NotConfigured => SlmError::NotConfigured(msg.to_string()), + ErrorKind::Timeout => SlmError::Timeout(msg.to_string()), + ErrorKind::Transport => SlmError::Transport(msg.to_string()), + ErrorKind::InvalidResponse => SlmError::InvalidResponse(msg.to_string()), + } + } +} + +impl SlmProvider for ScriptedSlm { + fn name(&self) -> &str { + "scripted-test-double" + } + + fn evaluate(&self, request: &SlmRequest) -> Result { + self.calls.fetch_add(1, Ordering::SeqCst); + match &self.outcome { + Outcome::Eval { + spirit_score, + confidence, + should_block, + reasoning, + } => Ok(SlmEvaluation { + proposal_id: request.proposal_id, + spirit_score: *spirit_score, + confidence: *confidence, + reasoning: reasoning.clone(), + should_block: *should_block, + }), + Outcome::Err(kind) => Err(kind.build("scripted failure")), + } + } +} + +impl ScriptedSlm { + fn eval(score: f64, confidence: f64, should_block: bool) -> Self { + Self { + calls: AtomicUsize::new(0), + outcome: Outcome::Eval { + spirit_score: score, + confidence, + should_block, + reasoning: "scripted reasoning".to_string(), + }, + } + } + + fn failing(kind: ErrorKind) -> Self { + Self { + calls: AtomicUsize::new(0), + outcome: Outcome::Err(kind), + } + } + + fn calls(&self) -> usize { + self.calls.load(Ordering::SeqCst) + } +} + +/// Enforcement thresholds under test, sourced from the same defaults the +/// runner uses (RSR default policy). +fn enforcement() -> EnforcementConfig { + EnforcementConfig::default() +} + +/// The decision matrix the implementation must satisfy (from the spec): +/// `no_go >= block` (or `should_block`) → Block; `no_go >= escalate` or +/// `go <= 0.8` → Escalate; otherwise the oracle verdict stands. +fn predict( + oracle_standing: Verdict, + score: f64, + go: f32, + should_block: bool, + addend: f64, +) -> Verdict { + let e = enforcement(); + let no_go = score * e.slm_weight + addend; + if should_block || no_go >= e.block_threshold { + Verdict::Block + } else if no_go >= e.escalate_threshold || go <= 0.8 { + Verdict::Escalate + } else { + oracle_standing + } +} + +// --------------------------------------------------------------------------- +// Proptest strategies +// --------------------------------------------------------------------------- + +fn arb_score() -> impl Strategy { + 0.0f64..=1.0 +} + +fn arb_confidence() -> impl Strategy { + 0.0f64..=1.0 +} + +fn arb_go() -> impl Strategy { + 0.0f32..=1.0 +} + +fn arb_error_kind() -> impl Strategy { + prop_oneof![ + Just(ErrorKind::ModelNotLoaded), + Just(ErrorKind::Inference), + Just(ErrorKind::NotConfigured), + Just(ErrorKind::Timeout), + Just(ErrorKind::Transport), + Just(ErrorKind::InvalidResponse), + ] +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(96))] + + /// An oracle Block is terminal for ANY provider outcome: the provider is + /// never invoked (proposal content never leaves the process), no SLM or + /// arbiter evidence is recorded, and the verdict is Block. + #[test] + fn oracle_block_is_terminal_for_any_provider_outcome( + eval_outcome in prop_oneof![ + (arb_score(), arb_confidence(), any::()) + .prop_map(|(s, c, b)| Outcome::Eval { + spirit_score: s, + confidence: c, + should_block: b, + reasoning: "arbitrary".to_string(), + }), + arb_error_kind().prop_map(Outcome::Err), + ], + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm { + calls: AtomicUsize::new(0), + outcome: eval_outcome, + }; + let request = GatingRequest::new(proposal(BLOCK_PATH, BLOCK_CONTENT, 0.95)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + + prop_assert_eq!(decision.verdict, Verdict::Block); + prop_assert_eq!(provider.calls(), 0, "provider must never be invoked on an oracle block"); + prop_assert!(decision.evaluations.slm.is_none()); + prop_assert!(decision.evaluations.arbiter.is_none()); + prop_assert_eq!(decision.processing.stages_executed, vec!["oracle".to_string()]); + prop_assert_eq!(decision.request_id, request.request_id); + } + + /// The clean-proposal decision matrix obeys threshold arithmetic at every + /// score, including 0 and 1. + #[test] + fn clean_matrix_matches_threshold_arithmetic( + score in arb_score(), + should_block in any::(), + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::eval(score, 0.99, should_block); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, 0.95)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + + let expected = predict(Verdict::Allow, score, 0.95, should_block, 0.0); + prop_assert_eq!(decision.verdict, expected, + "score {} should_block {} must follow the matrix", score, should_block); + prop_assert_eq!(provider.calls(), 1); + + // SLM evidence and arbiter record populated and consistent. + let slm = decision.evaluations.slm.as_ref().expect("slm stage recorded"); + prop_assert_eq!(slm.spirit_score, score); + let arbiter = decision.evaluations.arbiter.as_ref().expect("arbiter record"); + prop_assert!(arbiter.consensus_reached); + prop_assert_eq!(arbiter.oracle_vote, Verdict::Allow); + prop_assert_eq!(arbiter.final_verdict, decision.verdict); + prop_assert_eq!(arbiter.slm_weight, enforcement().slm_weight); + match decision.verdict { + Verdict::Block => prop_assert_eq!(arbiter.slm_vote, Verdict::Block), + Verdict::Escalate => prop_assert_eq!(arbiter.slm_vote, Verdict::Escalate), + standing => prop_assert_eq!(arbiter.slm_vote, Verdict::Allow, "standing {:?}", standing), + } + prop_assert_eq!(decision.request_id, request.request_id); + } + + /// The oracle Warn addend (+0.2 to no-go) shifts the matrix, and a passing + /// SLM verdict leaves the Warn standing (never upgraded to Allow). + #[test] + fn warn_addend_shifts_matrix_and_warn_stands( + score in arb_score(), + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::eval(score, 0.99, false); + let request = GatingRequest::new(proposal(WARN_PATH, WARN_CONTENT, 0.95)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + + let expected = predict(Verdict::Warn, score, 0.95, false, 0.2); + prop_assert_eq!(decision.verdict, expected, + "score {} with +0.2 addend must follow the shifted matrix", score); + let arbiter = decision.evaluations.arbiter.as_ref().expect("arbiter record"); + prop_assert_eq!(arbiter.oracle_vote, Verdict::Warn); + if expected == Verdict::Warn { + prop_assert!(decision.refusal.is_some(), "the oracle soft refusal is preserved"); + } + } + + /// Score 0 never escalates or blocks; score 1 always blocks, for any + /// SLM-side confidence. + #[test] + fn score_zero_and_one_obey_supremum_bounds( + slm_confidence in arb_confidence(), + ) { + let runner = ContractRunner::new(); + + let provider_zero = ScriptedSlm::eval(0.0, slm_confidence, false); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, 0.95)); + let d0 = runner + .evaluate_with_provider(&request, &provider_zero) + .expect("evaluation must not error"); + prop_assert_eq!(d0.verdict, Verdict::Allow, "spirit score 0 must allow"); + + let provider_one = ScriptedSlm::eval(1.0, slm_confidence, false); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, 0.95)); + let d1 = runner + .evaluate_with_provider(&request, &provider_one) + .expect("evaluation must not error"); + prop_assert_eq!(d1.verdict, Verdict::Block, "spirit score 1 must block"); + prop_assert_eq!( + d1.refusal.as_ref().map(|r| &r.code), + Some(&RefusalCode::Spirit599OtherSpirit), + ); + } + + /// `should_block: true` forces Block at ANY spirit score (including 0). + #[test] + fn should_block_flag_always_blocks( + score in arb_score(), + slm_confidence in arb_confidence(), + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::eval(score, slm_confidence, true); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, 0.95)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + prop_assert_eq!(decision.verdict, Verdict::Block); + } + + /// Low LLM-side go (llm_confidence <= 0.8) always escalates, even with a + /// perfectly clean SLM vote — a provider cannot rescue provider metadata. + #[test] + fn low_llm_confidence_always_escalates( + go in 0.0f32..=0.8f32, + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::eval(0.0, 1.0, false); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, go)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + prop_assert_eq!(decision.verdict, Verdict::Escalate); + } + + /// High go (> 0.8) with a mid-band score still escalates via no-go, and a + /// clean score allows — predictions delegated to the shared matrix. + #[test] + fn high_go_follows_no_go_band( + score in arb_score(), + go in 0.8000001f32..=1.0f32, + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::eval(score, 0.99, false); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, go)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + prop_assert_eq!(decision.verdict, predict(Verdict::Allow, score, go, false, 0.0)); + } + + /// EVERY provider failure mode fails closed: Escalate with a + /// non-overridable 9xx system refusal, an explicit `slm_error` stage, and + /// no SLM/arbiter evidence — on both clean and warn fixtures. + #[test] + fn any_provider_failure_fails_closed( + kind in arb_error_kind(), + use_warn_fixture in any::(), + ) { + let runner = ContractRunner::new(); + let provider = ScriptedSlm::failing(kind); + let (path, content) = if use_warn_fixture { + (WARN_PATH, WARN_CONTENT) + } else { + (CLEAN_PATH, CLEAN_CONTENT) + }; + let request = GatingRequest::new(proposal(path, content, 0.95)); + let decision = runner + .evaluate_with_provider(&request, &provider) + .expect("evaluation must not error"); + + prop_assert_eq!(provider.calls(), 1); + prop_assert_eq!(decision.verdict, Verdict::Escalate, "provider failure must never allow"); + let refusal = decision.refusal.as_ref().expect("refusal recorded"); + prop_assert_eq!(&refusal.code, &RefusalCode::Sys902InternalError); + prop_assert!(!refusal.overridable, "system failures are not policy-overridable"); + prop_assert!(decision.processing.stages_executed.iter().any(|s| s == "slm_error")); + prop_assert!(decision.evaluations.slm.is_none()); + prop_assert!(decision.evaluations.arbiter.is_none()); + prop_assert_eq!(decision.request_id, request.request_id); + } + + /// Determinism: identical votes → identical verdict and refusal code, + /// across two fresh runners and providers. + #[test] + fn identical_votes_identical_verdict( + score in arb_score(), + go in arb_go(), + should_block in any::(), + use_warn_fixture in any::(), + ) { + let (path, content) = if use_warn_fixture { + (WARN_PATH, WARN_CONTENT) + } else { + (CLEAN_PATH, CLEAN_CONTENT) + }; + let decisions: Vec<_> = (0..2) + .map(|_| { + ContractRunner::new() + .evaluate_with_provider( + &GatingRequest::new(proposal(path, content, go)), + &ScriptedSlm::eval(score, 0.99, should_block), + ) + .expect("evaluation must not error") + }) + .collect(); + + prop_assert_eq!(decisions[0].verdict, decisions[1].verdict); + prop_assert_eq!( + decisions[0].refusal.as_ref().map(|r| &r.code), + decisions[1].refusal.as_ref().map(|r| &r.code), + ); + } + + /// The response preserves the request ID for every provider outcome. + #[test] + fn response_preserves_request_id( + score in arb_score(), + go in arb_go(), + should_block in any::(), + ) { + let runner = ContractRunner::new(); + let request = GatingRequest::new(proposal(CLEAN_PATH, CLEAN_CONTENT, go)); + let decision = runner + .evaluate_with_provider(&request, &ScriptedSlm::eval(score, 0.99, should_block)) + .expect("evaluation must not error"); + prop_assert_eq!(decision.request_id, request.request_id); + } +} + +// --------------------------------------------------------------------------- +// Concurrency: correlation IDs must never mix across in-flight requests +// --------------------------------------------------------------------------- + +/// Provider whose evaluation echoes a marker found in the request content and +/// forces a Block, so the marker flows into the refusal message of the exact +/// decision belonging to that request. Sleeps an id-derived 0-4ms to maximise +/// interleaving. +struct MarkerBlockingSlm { + calls: AtomicUsize, +} + +impl SlmProvider for MarkerBlockingSlm { + fn name(&self) -> &str { + "marker-blocking-test-double" + } + + fn evaluate(&self, request: &SlmRequest) -> Result { + self.calls.fetch_add(1, Ordering::SeqCst); + let jitter = request.proposal_id.as_bytes()[0] % 5; + std::thread::sleep(std::time::Duration::from_millis(u64::from(jitter))); + let marker = request + .content + .rsplit("// marker:") + .next() + .unwrap_or("") + .trim() + .to_string(); + Ok(SlmEvaluation { + proposal_id: request.proposal_id, + spirit_score: 1.0, + confidence: 1.0, + reasoning: format!("flagged {marker}"), + should_block: true, + }) + } +} + +#[test] +fn concurrent_requests_do_not_mix_correlation_ids() { + const THREADS: usize = 8; + const REQ_PER_THREAD: usize = 4; + + let provider = Arc::new(MarkerBlockingSlm { + calls: AtomicUsize::new(0), + }); + + let handles: Vec<_> = (0..THREADS) + .map(|t| { + let provider = Arc::clone(&provider); + std::thread::spawn(move || { + let runner = ContractRunner::new(); + let mut results = Vec::new(); + for r in 0..REQ_PER_THREAD { + let marker = format!("t{t}-r{r}"); + let request = GatingRequest::new(proposal( + CLEAN_PATH, + &format!("{CLEAN_CONTENT} // marker:{marker}"), + 0.95, + )); + let request_id = request.request_id; + let decision = runner + .evaluate_with_provider(&request, provider.as_ref()) + .expect("evaluation must not error"); + results.push((marker, request_id, decision)); + } + results + }) + }) + .collect(); + + let mut total = 0; + for handle in handles { + for (marker, request_id, decision) in handle.join().expect("thread panicked") { + total += 1; + assert_eq!(decision.verdict, Verdict::Block); + assert_eq!( + decision.request_id, request_id, + "decision must answer its own request" + ); + let message = &decision.refusal.as_ref().expect("refusal recorded").message; + assert!( + message.contains(&format!("flagged {marker}")), + "correlation mix-up: decision for marker {marker} carried {message:?}" + ); + } + } + assert_eq!(total, THREADS * REQ_PER_THREAD); + assert_eq!( + provider.calls.load(Ordering::SeqCst), + THREADS * REQ_PER_THREAD, + "exactly one provider call per request passed the oracle" + ); +} diff --git a/vendor/bunsenite/.claude/CLAUDE.md b/vendor/bunsenite/.claude/CLAUDE.md new file mode 100644 index 0000000..830fb2c --- /dev/null +++ b/vendor/bunsenite/.claude/CLAUDE.md @@ -0,0 +1,88 @@ + +## Machine-Readable Artefacts + +The following files in `.machine_readable/` contain structured project metadata: + +- `.machine_readable/6a2/STATE.a2ml` - Current project state and progress +- `.machine_readable/6a2/META.a2ml` - Architecture decisions and development practices +- `.machine_readable/6a2/ECOSYSTEM.a2ml` - Position in the ecosystem and related projects +- `.machine_readable/6a2/AGENTIC.a2ml` - AI agent interaction patterns +- `.machine_readable/6a2/NEUROSYM.a2ml` - Neurosymbolic integration config +- `.machine_readable/6a2/PLAYBOOK.a2ml` - Operational runbook + +--- + +# CLAUDE.md - AI Assistant Instructions + +## Language Policy (Hyperpolymath Standard) + +### ALLOWED Languages & Tools + +| Language/Tool | Use Case | Notes | +|---------------|----------|-------| +| **AffineScript** | Primary application code | Affine-typed, compiles to typed-wasm or ESM | +| **Bun** | JS runtime & package management (tier 1) | Default for all new work. Runs compiled ESM/JS directly — no bundler step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. | +| **Rust** | Performance-critical, systems, WASM | Preferred for CLI tools | +| **Tauri 2.0+** | Mobile apps (iOS/Android) | Rust backend + web UI | +| **Dioxus** | Mobile apps (native UI) | Pure Rust, React-like | +| **Gleam** | Backend services | Runs on BEAM or compiles to JS | +| **Bash/POSIX Shell** | Scripts, automation | Keep minimal | +| **JavaScript** | Only where AffineScript cannot | MCP protocol glue, Bun APIs | +| **Nickel** | Configuration language | For complex configs | +| **Guile Scheme** | State/meta files | .machine_readable/6a2/STATE.a2ml, .machine_readable/6a2/META.a2ml, .machine_readable/6a2/ECOSYSTEM.a2ml | +| **Julia** | Batch scripts, data processing | Per RSR | +| **OCaml** | AffineScript compiler | Language-specific | +| **Ada** | Safety-critical systems | Where required | + +### BANNED - Do Not Use + +| Banned | Replacement | +|--------|-------------| +| TypeScript | AffineScript | +| ReScript | AffineScript | +| Deno | Bun | +| Node.js | Bun | +| npm | Bun | +| pnpm/yarn | Bun | +| Go | Rust | +| Python | Julia/Rust/AffineScript | +| Java/Kotlin | Rust/Tauri/Dioxus | +| Swift | Tauri/Dioxus | +| React Native | Tauri/Dioxus | +| Flutter/Dart | Tauri/Dioxus | + +### Mobile Development + +**No exceptions for Kotlin/Swift** - use Rust-first approach: + +1. **Tauri 2.0+** - Web UI (AffineScript) + Rust backend, MIT/Apache-2.0 +2. **Dioxus** - Pure Rust native UI, MIT/Apache-2.0 + +Both are FOSS with independent governance (no Big Tech). + +### Enforcement Rules + +1. **No new TypeScript files** - Convert existing TS to AffineScript +2. **Use `package.json` + `bun.lock` for JS runtime deps** - Bun is npm-compatible; a manifest is REQUIRED +3. **`bun install --production --frozen-lockfile` for production deps** - resolved from `package.json` and pinned via `bun.lock`; `--frozen-lockfile` makes a lockfile mismatch a build failure rather than a silent re-resolve +4. **No Go code** - Use Rust instead +5. **No Python anywhere** - Use Julia for data/batch, Rust for systems, AffineScript for apps +6. **No Kotlin/Swift for mobile** - Use Tauri 2.0+ or Dioxus + +### Package Management + +- **Primary**: Guix (guix.scm) +- **Fallback**: Guix (flake.guix) +- **JS deps**: Bun (`package.json` + `bun.lock`). Declare tooling as a devDependency and run `bunx --no-install --bun ` — a bare `bunx ` can fetch an unpinned package and may start Node via its shebang. + +### Security Requirements + +- No MD5/SHA1 for security (use SHA256+) +- HTTPS only (no HTTP URLs) +- No hardcoded secrets +- SHA-pinned dependencies +- SPDX license headers on all files + diff --git a/vendor/bunsenite/.clusterfuzzlite/Containerfile b/vendor/bunsenite/.clusterfuzzlite/Containerfile new file mode 100644 index 0000000..ff3d423 --- /dev/null +++ b/vendor/bunsenite/.clusterfuzzlite/Containerfile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: MPL-2.0 +# ClusterFuzzLite build environment for bunsenite +FROM gcr.io/oss-fuzz-base/base-builder-rust@sha256:73c1d5648db54100639339d411a5d192cbc8bf413ee91e843a07cf6f0e319dc7 + +COPY . $SRC/bunsenite +WORKDIR $SRC/bunsenite + +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/vendor/bunsenite/.clusterfuzzlite/build.sh b/vendor/bunsenite/.clusterfuzzlite/build.sh new file mode 100644 index 0000000..bfdc807 --- /dev/null +++ b/vendor/bunsenite/.clusterfuzzlite/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash -eu +# SPDX-License-Identifier: MPL-2.0 +# Build script for ClusterFuzzLite + +cd $SRC/bunsenite + +# Build fuzz targets using cargo-fuzz +cargo +nightly fuzz build + +# Copy fuzz targets to $OUT +for target in $(cargo +nightly fuzz list); do + cp ./target/x86_64-unknown-linux-gnu/release/$target $OUT/ +done diff --git a/vendor/bunsenite/.clusterfuzzlite/project.yaml b/vendor/bunsenite/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..4d72a30 --- /dev/null +++ b/vendor/bunsenite/.clusterfuzzlite/project.yaml @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MPL-2.0 +# ClusterFuzzLite configuration for bunsenite +language: rust diff --git a/vendor/bunsenite/.editorconfig b/vendor/bunsenite/.editorconfig new file mode 100644 index 0000000..960e2cd --- /dev/null +++ b/vendor/bunsenite/.editorconfig @@ -0,0 +1,68 @@ +# bunsenite - Editor Configuration +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.adoc] +trim_trailing_whitespace = false + +[*.rs] +indent_size = 4 + +[*.ex] +indent_size = 2 + +[*.exs] +indent_size = 2 + +[*.zig] +indent_size = 4 + +[*.ada] +indent_size = 3 + +[*.adb] +indent_size = 3 + +[*.ads] +indent_size = 3 + +[*.hs] +indent_size = 2 + +[*.res] +indent_size = 2 + +[*.resi] +indent_size = 2 + +[*.ncl] +indent_size = 2 + +[*.rkt] +indent_size = 2 + +[*.scm] +indent_size = 2 + +[*.nix] +indent_size = 2 + +[Justfile] +indent_style = space +indent_size = 4 + +[justfile] +indent_style = space +indent_size = 4 diff --git a/vendor/bunsenite/.gitattributes b/vendor/bunsenite/.gitattributes new file mode 100644 index 0000000..e860a85 --- /dev/null +++ b/vendor/bunsenite/.gitattributes @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MPL-2.0 +# RSR-compliant .gitattributes + +* text=auto eol=lf + +# Source +*.rs text eol=lf diff=rust +*.ex text eol=lf diff=elixir +*.exs text eol=lf diff=elixir +*.jl text eol=lf +*.res text eol=lf +*.resi text eol=lf +*.ada text eol=lf diff=ada +*.adb text eol=lf diff=ada +*.ads text eol=lf diff=ada +*.hs text eol=lf +*.chpl text eol=lf +*.scm text eol=lf +*.ncl text eol=lf +*.nix text eol=lf + +# Docs +*.md text eol=lf diff=markdown +*.adoc text eol=lf +*.txt text eol=lf + +# Data +*.json text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.toml text eol=lf + +# Config +.gitignore text eol=lf +.gitattributes text eol=lf +justfile text eol=lf +Makefile text eol=lf +Containerfile text eol=lf + +# Scripts +*.sh text eol=lf + +# Binary +*.png binary +*.jpg binary +*.gif binary +*.pdf binary +*.woff2 binary +*.zip binary +*.gz binary + +# Lock files +Cargo.lock text eol=lf -diff +flake.lock text eol=lf -diff diff --git a/vendor/bunsenite/.github/CODEOWNERS b/vendor/bunsenite/.github/CODEOWNERS new file mode 100644 index 0000000..3a3b7f2 --- /dev/null +++ b/vendor/bunsenite/.github/CODEOWNERS @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: MPL-2.0 +# CODEOWNERS - Define code review assignments for GitHub +# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# Default: sole maintainer for all files +* @hyperpolymath + +# Security-sensitive files require explicit ownership +SECURITY.md @hyperpolymath +.github/workflows/ @hyperpolymath +.machine_readable/ @hyperpolymath +contractiles/ @hyperpolymath + +# License files +LICENSE @hyperpolymath +LICENSES/ @hyperpolymath + +# Configuration +.gitignore @hyperpolymath +.github/ @hyperpolymath + +# Documentation +README* @hyperpolymath +CONTRIBUTING* @hyperpolymath +CODE_OF_CONDUCT* @hyperpolymath +GOVERNANCE* @hyperpolymath +MAINTAINERS* @hyperpolymath +CHANGELOG* @hyperpolymath +ROADMAP* @hyperpolymath + +# Build and CI +Justfile @hyperpolymath +Makefile @hyperpolymath +*.sh @hyperpolymath diff --git a/vendor/bunsenite/.github/CONTRIBUTING.md b/vendor/bunsenite/.github/CONTRIBUTING.md new file mode 100644 index 0000000..3466911 --- /dev/null +++ b/vendor/bunsenite/.github/CONTRIBUTING.md @@ -0,0 +1,88 @@ + +# Getting started +```bash +git clone https://github.com/hyperpolymath/bunsenite.git +cd bunsenite + +# Using mise (recommended: provision the pinned toolchain) +mise install + +# Task runner (see Justfile) +just --list # available tasks +just check # verify setup / static checks +just test # run the test suite +``` + +--- + +## How to Contribute + +### Reporting Bugs + +**Before reporting**: +1. Search existing issues +2. Check if it's already fixed in `main` +3. Determine which perimeter the bug affects + +**When reporting**: + +Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: + +- Clear, descriptive title +- Environment details (OS, versions, toolchain) +- Steps to reproduce +- Expected vs actual behaviour +- Logs, screenshots, or minimal reproduction + +### Suggesting Features + +**Before suggesting**: +1. Check the [roadmap](../docs/status/ROADMAP.adoc) if available +2. Search existing issues and discussions +3. Consider which perimeter the feature belongs to + +**When suggesting**: + +Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) and include: + +- Problem statement (what pain point does this solve?) +- Proposed solution +- Alternatives considered +- Which perimeter this affects + +### Your First Contribution + +Look for issues labelled: + +- [`good first issue`](https://github.com/hyperpolymath/bunsenite/labels/good%20first%20issue) — Simple Perimeter 3 tasks +- [`help wanted`](https://github.com/hyperpolymath/bunsenite/labels/help%20wanted) — Community help needed +- [`documentation`](https://github.com/hyperpolymath/bunsenite/labels/documentation) — Docs improvements +- [`perimeter-3`](https://github.com/hyperpolymath/bunsenite/labels/perimeter-3) — Community sandbox scope + +--- + +## Development Workflow + +### Branch Naming +``` +docs/short-description # Documentation (P3) +test/what-added # Test additions (P3) +feat/short-description # New features (P2) +fix/issue-number-description # Bug fixes (P2) +refactor/what-changed # Code improvements (P2) +security/what-fixed # Security fixes (P1-2) +``` + +### Commit Messages + +We follow [Conventional Commits](https://www.conventionalcommits.org/): +``` +(): + +[optional body] + +[optional footer] +``` diff --git a/vendor/bunsenite/.github/FUNDING.yml b/vendor/bunsenite/.github/FUNDING.yml new file mode 100644 index 0000000..688a442 --- /dev/null +++ b/vendor/bunsenite/.github/FUNDING.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MPL-2.0 +# Funding platforms for hyperpolymath projects +# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository + +github: hyperpolymath +ko_fi: hyperpolymath +liberapay: hyperpolymath diff --git a/vendor/bunsenite/.github/copilot/coding-agent.yml b/vendor/bunsenite/.github/copilot/coding-agent.yml new file mode 100644 index 0000000..a719a77 --- /dev/null +++ b/vendor/bunsenite/.github/copilot/coding-agent.yml @@ -0,0 +1,6 @@ +mcp_servers: + boj-server: + command: npx + args: ["-y", "@hyperpolymath/boj-server@latest"] + env: + BOJ_URL: http://localhost:7700 diff --git a/vendor/bunsenite/.github/dependabot.yml b/vendor/bunsenite/.github/dependabot.yml new file mode 100644 index 0000000..4168336 --- /dev/null +++ b/vendor/bunsenite/.github/dependabot.yml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MPL-2.0 +version: 2 +updates: + - package-ecosystem: "bundler" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + # `open-pull-requests-limit: 0` suppresses routine version-update PRs + # while leaving Dependabot SECURITY PRs flowing. The previous + # `ignore: "*" patch` rule also silenced security PRs under GitHub\'s + # current Dependabot behaviour. See rsr-template-repo commit 78b050e + # and 007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md. + open-pull-requests-limit: 10 + groups: + cargo: + patterns: + - "*" + update-types: + - "minor" + - "patch" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + open-pull-requests-limit: 2 + - package-ecosystem: "guix" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 3 diff --git a/vendor/bunsenite/.github/funding.yml b/vendor/bunsenite/.github/funding.yml new file mode 100644 index 0000000..e4f7c07 --- /dev/null +++ b/vendor/bunsenite/.github/funding.yml @@ -0,0 +1,4 @@ +# Funding Configuration +# See: https://docs.github.com/en/repositories/managing-your-repositorys-custom-fields/displaying-a-sponsor-button-in-your-repository + +github: metadatastician diff --git a/vendor/bunsenite/.github/label-classifier.json b/vendor/bunsenite/.github/label-classifier.json new file mode 100644 index 0000000..d349eaa --- /dev/null +++ b/vendor/bunsenite/.github/label-classifier.json @@ -0,0 +1,739 @@ +{ + "_generated_from": ".github/label-classifier.yml + .github/labels.yml in hyperpolymath/.git-private-farm", + "_do_not_edit": "regenerate with scripts/gen-classifier-json.py", + "version": 1, + "prefix_split_on": "/", + "title_prefix": { + "docs": { + "type": "documentation" + }, + "ci": { + "type": "chore", + "areas": [ + "cicd" + ] + }, + "governance": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "roadmap": { + "type": "enhancement", + "meta": "meta:roadmap" + }, + "chore": { + "type": "chore" + }, + "build": { + "type": "chore", + "areas": [ + "cicd" + ] + }, + "security": { + "type": "chore", + "areas": [ + "security" + ] + }, + "proof": { + "type": "chore", + "areas": [ + "proofs" + ] + }, + "proofs": { + "type": "chore", + "areas": [ + "proofs" + ] + }, + "proof-debt": { + "type": "tech-debt", + "areas": [ + "proofs" + ] + }, + "epic": { + "type": "enhancement", + "meta": "meta:umbrella" + }, + "umbrella": { + "type": "enhancement", + "meta": "meta:umbrella" + }, + "tracking": { + "type": "chore", + "meta": "meta:umbrella" + }, + "campaign": { + "type": "enhancement", + "meta": "meta:campaign" + }, + "hygiene": { + "type": "tech-debt" + }, + "audit": { + "type": "research" + }, + "estate": { + "type": "chore", + "scope": "scope:estate" + }, + "automation": { + "type": "enhancement", + "areas": [ + "automation" + ] + }, + "research": { + "type": "research" + }, + "refactor": { + "type": "refactor" + }, + "test": { + "type": "testing" + }, + "tests": { + "type": "testing" + }, + "feat": { + "type": "enhancement" + }, + "fix": { + "type": "bug" + }, + "bug": { + "type": "bug" + }, + "perf": { + "type": "enhancement", + "areas": [ + "performance" + ] + }, + "codegen": { + "type": "enhancement", + "areas": [ + "architecture" + ] + }, + "packaging": { + "type": "chore", + "areas": [ + "packaging" + ] + }, + "policy": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "ops": { + "type": "chore", + "areas": [ + "automation" + ] + }, + "standard": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "migration": { + "type": "refactor", + "areas": [ + "migration" + ] + }, + "drift": { + "type": "tech-debt" + }, + "corrective": { + "type": "bug" + }, + "adaptive": { + "type": "enhancement" + }, + "perfective": { + "type": "enhancement" + }, + "preventive": { + "type": "tech-debt" + }, + "machine-readable": { + "type": "tech-debt" + }, + "parser": { + "type": "bug" + }, + "lang": { + "type": "bug" + }, + "clippy": { + "type": "tech-debt" + }, + "release": { + "type": "chore" + }, + "upstream": { + "type": "chore" + }, + "hardening": { + "type": "chore", + "areas": [ + "security" + ] + }, + "deps": { + "type": "chore" + }, + "rustsec": { + "type": "chore", + "areas": [ + "security" + ] + }, + "track": { + "type": "chore", + "meta": "meta:umbrella" + }, + "tracker": { + "type": "chore", + "meta": "meta:umbrella" + }, + "wiki": { + "type": "documentation" + }, + "reclassify": { + "type": "refactor" + }, + "backlog": { + "type": "chore" + }, + "core": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "evidence": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "manifest": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "backends": { + "type": "enhancement", + "areas": [ + "design" + ] + } + }, + "bracket_tag": { + "campaign": { + "meta": "meta:campaign" + }, + "umbrella": { + "meta": "meta:umbrella" + }, + "gov": { + "areas": [ + "governance" + ] + }, + "proofs/a": { + "areas": [ + "proofs" + ] + }, + "proofs/b": { + "areas": [ + "proofs" + ] + }, + "proofs/c": { + "areas": [ + "proofs" + ] + }, + "estate": { + "scope": "scope:estate" + }, + "repo": { + "scope": "scope:repo" + }, + "feature": { + "type": "enhancement" + }, + "integration": { + "areas": [ + "conformance" + ] + }, + "reference": { + "type": "documentation" + }, + "register": { + "type": "documentation" + }, + "p0": { + "priority": "priority:p0" + }, + "p1": { + "priority": "priority:p1" + }, + "p2": { + "priority": "priority:p2" + }, + "et-l2": { + "areas": [ + "conformance" + ] + }, + "et-l4": { + "areas": [ + "conformance" + ] + } + }, + "keyword_area": { + "proofs": [ + "agda", + "coq", + "rocq", + "idris", + "lean", + "isabelle", + "hol", + "mizar", + "why3", + "tla", + "alloy", + "dafny", + "acl2", + "pvs", + "metamath", + "z3", + "smt", + "prover", + "provers", + "theorem", + "theorems", + "axiom", + "axioms", + "postulate", + "postulates", + "believe_me", + "sorry", + "proof obligation", + "proof obligations", + "proof hole", + "proof holes", + "proof suite", + "proof-pipeline", + "proof debt", + "proof-debt", + "metatheory", + "mechanize", + "qed" + ], + "cicd": [ + "workflow", + "github action", + "actions.lock", + "lockfile", + "runner", + "startup_failure", + "dependabot", + "check run", + "required context", + "scorecard", + "codeql", + "ci/cd" + ], + "licensing": [ + "spdx", + "licence", + "license", + "reuse", + "copyright", + "attribution", + "agpl", + "mpl" + ], + "security": [ + "gitleaks", + "secret", + "vulnerabilit", + "advisory", + "supply chain", + "cve" + ], + "bindings": [ + "abi", + "ffi", + "wasm", + "jni", + "c api", + "interop", + "extern \"c\"", + "nif", + "snif" + ], + "packaging": [ + "guix", + "nix", + "container", + "containerfile", + "docker", + "flatpak", + "oci image" + ], + "scaffolding": [ + "rsr", + "scaffold", + "template", + "repo-init", + "instantiat", + "placeholder" + ], + "governance": [ + "ruleset", + "policy", + "compliance", + "governance", + "branch protection", + "codeowners", + "code of conduct" + ], + "migration": [ + "rescript", + "to-affinescript", + "\u2192 affinescript", + "port", + "deno", + "bun" + ], + "automation": [ + "bot", + "gitbot", + "hypatia", + "sustainabot", + "oikosbot", + "fan-out", + "fanout", + "dispatch", + "self-heal" + ], + "performance": [ + "latency", + "throughput", + "binary size", + "memory", + "hot path", + "regression" + ] + }, + "keyword_type": { + "tech-debt": [ + "debt", + "drift", + "hygiene", + "stale", + "cleanup", + "follow-up", + "clean up", + "left over", + "leftover", + "anti-pattern", + "inconsistency", + "inconsistent", + "placeholder", + "placeholders", + "tbd", + "todo", + "todos", + "unfilled" + ], + "documentation": [ + "document", + "docs", + "readme", + "adoc", + "prose", + "docs/", + "changelog", + "explainme", + "quickstart", + "wiki", + "docstring", + "doc tree" + ], + "testing": [ + "test", + "tests", + "fuzz", + "bench", + "coverage", + "crash-consistency", + "linearizability", + "equivalence", + "property-correspondence", + "property-based", + "test suite", + "proptest" + ], + "bug": [ + "broken", + "fails", + "failing", + "crash", + "oom", + "regression", + "incorrect", + "does not", + "panic", + "panics", + "unreachable", + "mangled", + "never run", + "never ran", + "never succeeded", + "never fires", + "cannot fail", + "deadlock", + "hangs" + ], + "refactor": [ + "refactor", + "restructure", + "consolidate", + "consolidation", + "reconcile", + "reconciliation", + "unify", + "dedupe", + "re-point", + "repoint", + "extract", + "retire", + "retire duplicate", + "deduplicate", + "reclassify", + "migrate" + ], + "research": [ + "investigat", + "explore", + "spike", + "work out", + "triage", + "assess", + "survey", + "gap analysis", + "self-audit", + "inventory", + "weakness list", + "theory", + "synthesis", + "prioritised weakness", + "feasibility" + ], + "decision": [ + "ruling", + "decide", + "decision", + "adjudicat", + "which of" + ], + "enhancement": [ + "add", + "implement", + "support", + "introduce", + "enable", + "expand", + "expansion", + "extend", + "wire", + "complete", + "build", + "create", + "port" + ] + }, + "meta_signal": { + "meta:umbrella": [ + "umbrella", + "epic", + "master issue", + "parent issue", + "sub-issues", + "child issues" + ], + "meta:campaign": [ + "campaign" + ], + "meta:roadmap": [ + "roadmap", + "capability-expansion", + "future work" + ], + "meta:recurring": [ + "recurring", + "recurrence", + "standing", + "every run", + "each week" + ] + }, + "status_signal": { + "status:blocked": [ + "blocked on", + "blocked:", + "(blocked", + "is blocked", + "gated on", + "waiting on upstream", + "needs upstream" + ], + "status:needs-owner": [ + "unassigned", + "needs an owner", + "no owner" + ], + "status:needs-ruling": [ + "needs a ruling", + "awaiting ruling", + "owner decision needed" + ] + }, + "scope_signal": { + "scope:estate": [ + "estate-wide", + "estate wide", + "across the estate", + "all repos", + "fleet-wide" + ] + }, + "tier_of": { + "bug": "type", + "enhancement": "type", + "documentation": "type", + "refactor": "type", + "tech-debt": "type", + "testing": "type", + "chore": "type", + "research": "type", + "decision": "type", + "question": "type", + "cicd": "area", + "security": "area", + "proofs": "area", + "governance": "area", + "design": "area", + "architecture": "area", + "performance": "area", + "bindings": "area", + "migration": "area", + "packaging": "area", + "licensing": "area", + "automation": "area", + "scaffolding": "area", + "conformance": "area", + "priority:p0": "priority", + "priority:p1": "priority", + "priority:p2": "priority", + "priority:p3": "priority", + "status:blocked": "status", + "status:ready": "status", + "status:needs-owner": "status", + "status:needs-ruling": "status", + "status:do-not-automate": "status", + "meta:umbrella": "meta", + "meta:campaign": "meta", + "meta:roadmap": "meta", + "meta:recurring": "meta", + "scope:estate": "scope", + "scope:repo": "scope" + }, + "tier_max": { + "type": 1, + "area": null, + "priority": 1, + "status": 1, + "meta": 1, + "scope": 1 + }, + "types": [ + "bug", + "enhancement", + "documentation", + "refactor", + "tech-debt", + "testing", + "chore", + "research", + "decision", + "question" + ], + "frozen": [ + "dependencies", + "duplicate", + "elixir", + "gitar-approved", + "github_actions", + "good first issue", + "help wanted", + "invalid", + "javascript", + "never-stale", + "nix", + "pinned", + "python", + "rust", + "security", + "stale", + "wontfix" + ], + "precedence": { + "meta:campaign": 0, + "meta:umbrella": 1, + "meta:recurring": 2, + "meta:roadmap": 3, + "priority:p0": 0, + "priority:p1": 1, + "priority:p2": 2, + "priority:p3": 3, + "status:blocked": 0, + "status:needs-ruling": 1, + "status:needs-owner": 2, + "status:do-not-automate": 3, + "status:ready": 4, + "scope:estate": 0, + "scope:repo": 1, + "bug": 0, + "decision": 1, + "tech-debt": 2, + "testing": 3, + "documentation": 4, + "refactor": 5, + "research": 6, + "enhancement": 7, + "chore": 8, + "question": 9 + } +} diff --git a/vendor/bunsenite/.github/labels.json b/vendor/bunsenite/.github/labels.json new file mode 100644 index 0000000..78786d4 --- /dev/null +++ b/vendor/bunsenite/.github/labels.json @@ -0,0 +1,260 @@ +{ + "_generated_from": ".github/labels.yml in hyperpolymath/.git-private-farm", + "_do_not_edit": "regenerate with scripts/gen-labels-json.py", + "version": 1, + "labels": [ + { + "name": "bug", + "color": "d73a4a", + "description": "Something is broken or behaves incorrectly", + "tier": "type" + }, + { + "name": "enhancement", + "color": "a2eeef", + "description": "New capability or improvement to existing behaviour", + "tier": "type" + }, + { + "name": "documentation", + "color": "0075ca", + "description": "Docs, prose, diagrams, READMEs, ADRs", + "tier": "type" + }, + { + "name": "refactor", + "color": "c5def5", + "description": "Restructuring that preserves observable behaviour", + "tier": "type" + }, + { + "name": "tech-debt", + "color": "fbca04", + "description": "Known shortcut, drift, or hygiene owed - includes cleanup", + "tier": "type" + }, + { + "name": "testing", + "color": "bfd4f2", + "description": "Tests, benchmarks, fuzzing, property checks, coverage", + "tier": "type" + }, + { + "name": "chore", + "color": "ededed", + "description": "Routine maintenance with no behaviour change", + "tier": "type" + }, + { + "name": "research", + "color": "d4c5f9", + "description": "Open investigation; the outcome is knowledge, not code", + "tier": "type" + }, + { + "name": "decision", + "color": "8b5cf6", + "description": "A ruling is required before work can proceed", + "tier": "type" + }, + { + "name": "question", + "color": "d876e3", + "description": "Further information is requested", + "tier": "type" + }, + { + "name": "cicd", + "color": "006b75", + "description": "CI/CD: workflows, actions, lockfiles, pins, runners, release gates", + "tier": "area" + }, + { + "name": "security", + "color": "006b75", + "description": "Security posture, secrets, scanning, advisories, supply chain", + "tier": "area" + }, + { + "name": "proofs", + "color": "006b75", + "description": "Formal verification: Agda, Coq, Idris, Lean, Z3/SMT, axiom debt", + "tier": "area" + }, + { + "name": "governance", + "color": "006b75", + "description": "Policy, rulesets, standards, compliance, and their enforcement", + "tier": "area" + }, + { + "name": "design", + "color": "006b75", + "description": "Design of an interface, protocol, grammar, or type theory", + "tier": "area" + }, + { + "name": "architecture", + "color": "006b75", + "description": "Structural/system-level shape and runtime behaviour", + "tier": "area" + }, + { + "name": "performance", + "color": "006b75", + "description": "Throughput, latency, memory, binary size", + "tier": "area" + }, + { + "name": "bindings", + "color": "006b75", + "description": "ABI, FFI, WASM, and cross-language interop surfaces", + "tier": "area" + }, + { + "name": "migration", + "color": "006b75", + "description": "Porting between languages or toolchains (e.g. -> AffineScript)", + "tier": "area" + }, + { + "name": "packaging", + "color": "006b75", + "description": "Guix, Nix, containers, distribution artefacts", + "tier": "area" + }, + { + "name": "licensing", + "color": "006b75", + "description": "Licences, SPDX headers, REUSE compliance, attribution", + "tier": "area" + }, + { + "name": "automation", + "color": "006b75", + "description": "Bots, schedulers, dispatch, self-healing, fan-out", + "tier": "area" + }, + { + "name": "scaffolding", + "color": "006b75", + "description": "RSR templates, repo init, instantiation, project skeletons", + "tier": "area" + }, + { + "name": "conformance", + "color": "006b75", + "description": "Conformance to an external or internal specification", + "tier": "area" + }, + { + "name": "priority:p0", + "color": "b60205", + "description": "Critical - drop other work", + "tier": "priority" + }, + { + "name": "priority:p1", + "color": "d93f0b", + "description": "High - schedule next", + "tier": "priority" + }, + { + "name": "priority:p2", + "color": "e99695", + "description": "Normal - queue it", + "tier": "priority" + }, + { + "name": "priority:p3", + "color": "f9d0c4", + "description": "Low - nice to have", + "tier": "priority" + }, + { + "name": "status:blocked", + "color": "fbca04", + "description": "Cannot proceed until a dependency clears", + "tier": "status" + }, + { + "name": "status:ready", + "color": "fbca04", + "description": "Fully specified and ready to be picked up", + "tier": "status" + }, + { + "name": "status:needs-owner", + "color": "fbca04", + "description": "Unassigned and needs someone to take it", + "tier": "status" + }, + { + "name": "status:needs-ruling", + "color": "fbca04", + "description": "Awaiting an owner decision", + "tier": "status" + }, + { + "name": "status:do-not-automate", + "color": "fbca04", + "description": "Bots and sweeps must not touch this issue", + "tier": "status" + }, + { + "name": "meta:umbrella", + "color": "5319e7", + "description": "Parent issue aggregating child issues", + "tier": "meta" + }, + { + "name": "meta:campaign", + "color": "5319e7", + "description": "Coordinated multi-repo push with a defined end state", + "tier": "meta" + }, + { + "name": "meta:roadmap", + "color": "5319e7", + "description": "Forward planning; not yet actionable work", + "tier": "meta" + }, + { + "name": "meta:recurring", + "color": "5319e7", + "description": "Recurs on a schedule or by trigger; never finally closed", + "tier": "meta" + }, + { + "name": "scope:estate", + "color": "bfdadc", + "description": "Affects many or all repos across the estate", + "tier": "scope" + }, + { + "name": "scope:repo", + "color": "bfdadc", + "description": "Confined to this repository", + "tier": "scope" + } + ], + "frozen": [ + "dependencies", + "duplicate", + "elixir", + "gitar-approved", + "github_actions", + "good first issue", + "help wanted", + "invalid", + "javascript", + "never-stale", + "nix", + "pinned", + "python", + "rust", + "security", + "stale", + "wontfix" + ] +} diff --git a/vendor/bunsenite/.github/scripts/classify-issue.jq b/vendor/bunsenite/.github/scripts/classify-issue.jq new file mode 100644 index 0000000..6467c74 --- /dev/null +++ b/vendor/bunsenite/.github/scripts/classify-issue.jq @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Classify one issue title against the estate label taxonomy. +# +# jq -r --arg title "docs: fix the README" \ +# --argjson have '[]' \ +# -f .github/scripts/classify-issue.jq .github/label-classifier.json +# +# Prints one label per line, or NOTHING when it cannot place the issue +# confidently. Nothing printed means "leave it for a human" -- a correct +# outcome, not a failure. +# +# WHY jq AND NOT PYTHON +# +# Python is fully banned estate-wide: the `governance / Language / package +# anti-pattern policy` gate runs `git ls-files '*.py'` and fails the PR +# ("Python is fully banned -- use AffineScript/Rust/SPARK/Julia"). This file +# is dispatched into every repo in the estate, so shipping it as .py would +# mean shipping an exemption into every repo too -- normalising the policy +# away by sweep. jq is preinstalled on every GitHub runner, is not on the +# banned list, needs no action (so no actions.lock entry can drift), and the +# rules are already JSON. +# +# The canonical implementation remains scripts/label-classify.py in the hub, +# which never runs in CI. tests/test-classifier-parity.py asserts this file +# agrees with it on every title in the corpus. +# +# `$have` lists labels the issue already carries. Anything already present is +# never re-suggested, and the classifier stays out of any max-1 tier the issue +# already has a label in, so a human's classification is never overridden. + +# Escape every non-alphanumeric so a keyword is matched literally. Escaping +# punctuation that needs no escape is harmless in Oniguruma. +def reesc: gsub("(?[^A-Za-z0-9 _])"; "\\\(.c)"); + +def norm: (. // "") | ascii_downcase + | sub("^[[:space:]]+"; "") | sub("[[:space:]]+$"; ""); + +# Asymmetric boundary: STRICT on the left, inflection-tolerant on the right. +# +# Measured over the issue corpus, the two error directions are not symmetric: +# * every false positive is a LEFT-side prefix -- `lean` in "clean up", +# `abi` in "capability", `mpl` in "Implement", `ffi` in "AffineScript", +# `smt` in "wasmtime". The left boundary must stay strict. +# * every real miss is a RIGHT-side inflection -- `test` vs "tests", +# `theorem` vs "theorems", `todo` vs "TODOs", `scaffold` vs "scaffolding". +# +# The right side therefore admits a CLOSED set of inflections. Closed, not open +# (`.*`), because an open right side re-admits the prefix false positives. +# +# `ion`/`ation` are excluded from the base set: they mint unrelated words +# (`port` + `ion` = "portion", and `port` is a live keyword). They are enabled +# only for shapes that are unambiguously truncated stems -- `-at` +# (instantiat, investigat, adjudicat) and `-ment` (document, implement). +def kwrx($kw): + ( "s|es|ed|d|ing|er|ers|y|ies" + + (if ($kw | endswith("at")) then "|ion|ions|e" + elif ($kw | endswith("ment")) then "|ation|ations" + else "" end) + ) as $suf + # Boundaries are conditional: a keyword not starting alphanumeric has no left + # boundary to enforce, and one not ending alphanumeric takes no suffix. + | (if ($kw | test("^[A-Za-z0-9]")) then "(?[^\\]]{1,25})\\]")) // null) as $m + | if $m == null then {rule: null, rest: $t} + else (($m.tag | norm | split("#")[0]) | norm) as $tag + | { rule: ($R.bracket_tag[$tag] // null), + rest: ($t | sub("^[[:space:]]*\\[[^\\]]{1,25}\\]"; "")) } + end; + +# Leading `word:` / `word(scope):` conventional-commit prefix. +def prefixrule($R; $t): + (($t | capture("^[[:space:]]*(?[A-Za-z][A-Za-z0-9_./-]{1,24})(?:[[:space:]]*\\([^)]*\\))?[[:space:]]*:")) // null) as $m + | if $m == null then null + else ($m.w | norm) as $k + # Compound prefixes such as "adaptive/must:" carry their meaning in the + # ISO 14764 category only; the modality does not label. + | (if ($R.prefix_split_on // "") != "" and ($k | contains($R.prefix_split_on)) + then ($k | split($R.prefix_split_on) | .[0]) else $k end) as $key + | ($R.title_prefix[$key] // null) + end; + +def signals($R; $tl; $sec): + [ ($R[$sec] // {}) | to_entries[] + | select(.value | any(. as $k | kwhit($k; $tl))) + | .key ]; + +# The HIGHEST-PRECEDENCE matching type, not merely the first in key order. +def kwtype($R; $tl): + [ $R.keyword_type | to_entries[] + | select(.value | any(. as $k | kwhit($k; $tl))) + | .key ] + | if length == 0 then null + else min_by([($R.precedence[.] // 99), .]) end; + +# Drop violations of each tier's `max`, keeping the highest-precedence member. +def enforce($R; $labels): + ($labels | unique) + | group_by($R.tier_of[.] // "?") + | map( ($R.tier_of[.[0]] // "?") as $tier + | ($R.tier_max[$tier] // null) as $mx + | if $mx == null or (length <= $mx) then . + else (sort_by([($R.precedence[.] // 99), .]))[0:$mx] end ) + | flatten; + +def classify($R; $title; $have0): + ($title // "") as $t0 + | ($t0 | norm) as $tl + | ($have0 | map(select(. != null and . != "")) + | unique) as $have + | ($R.tier_of | keys) as $canon + | $R.types as $types + | bracket($R; $t0) as $b + | (if $b.rule != null then ($b.rule | rulelabels) else [] end) as $l1 + | prefixrule($R; $b.rest) as $pr + | (if $pr != null then ($pr | rulelabels) else [] end) as $l2 + | (($b.rule != null) or ($pr != null)) as $matched0 + # 3. keyword areas are additive and never contribute a type + | ($l1 + $l2 + signals($R; $tl; "keyword_area")) as $acc + # 4. a type only if neither the rules nor the issue already supplied one + | (if (($acc + $have) | any(. as $x | $types | index($x))) + then null else kwtype($R; $tl) end) as $ty + | ($acc + (if $ty != null then [$ty] else [] end)) as $acc + | ($matched0 or ($ty != null)) as $matched + | ( $acc + + signals($R; $tl; "status_signal") + + signals($R; $tl; "meta_signal") + + signals($R; $tl; "scope_signal") ) as $acc + # NOTE: `frozen` is deliberately NOT subtracted. Frozen means "never rename or + # delete this label" -- `security` is frozen because triage.yml pins it in + # exempt-issue-labels. APPLYING it to an issue is correct; only the + # definition is protected. + | ($acc | map(select(. as $x | $canon | index($x))) | unique) as $acc + | enforce($R; $acc + ($have | map(select(. as $x | $canon | index($x))))) as $acc + | ($acc - $have) as $out + # Stay out of any max-1 tier the issue ALREADY has a label in -- a human's, + # or one an ISSUE_TEMPLATE applied. A prefix rule fires unconditionally, so + # "fix: ..." on an issue already labelled `enhancement` would otherwise add + # `bug` beside it. This covers every max-1 tier (type, priority, status, + # meta, scope), not just type. + | ( [ $R.tier_max | to_entries[] | select(.value == 1) | .key ] + | map(. as $t | select($have | any(($R.tier_of[.] // "?") == $t))) + ) as $lockedtiers + | ($out | map(select(($R.tier_of[.] // "?") as $t | ($lockedtiers | index($t)) | not))) as $out + # A rule must actually have FIRED: keyword-area hits alone are not enough. + | if ($matched | not) then [] + # a type is mandatory + elif ((($out + $have) | any(. as $x | $types | index($x))) | not) then [] + else ($out | sort) end; + +classify(.; $title; $have) | .[] diff --git a/vendor/bunsenite/.github/workflows/actions.lock b/vendor/bunsenite/.github/workflows/actions.lock new file mode 100644 index 0000000..4627463 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/actions.lock @@ -0,0 +1,327 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.2' +workflows: + '.github/workflows/boj-build.yml': + - 'actions/checkout@v4.1.7' + '.github/workflows/cargo-audit.yml': + - 'actions/checkout@v4.1.1' + '.github/workflows/casket-pages.yml': + - 'actions/cache@v4.3.0' + - 'actions/checkout@v4.1.1' + - 'actions/configure-pages@v5.0.0' + - 'actions/deploy-pages@v4.0.5' + - 'actions/upload-pages-artifact@v3.0.1' + - 'haskell-actions/setup@v2.7.5' + '.github/workflows/cflite_batch.yml': + - 'google/clusterfuzzlite@v1' + '.github/workflows/cflite_pr.yml': + - 'google/clusterfuzzlite@v1' + '.github/workflows/codeql.yml': + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938' + '.github/workflows/dependabot-automerge.yml': + - 'dependabot/fetch-metadata@v2.2.0' + '.github/workflows/dogfood-gate.yml': + - 'actions/checkout@v4.3.1' + - 'hyperpolymath/deed-ecosystem@main' + - 'hyperpolymath/k9-ecosystem@main' + '.github/workflows/generator-generic-ossf-slsa3-publish.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/ghcr-publish.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/governance.yml': [] + '.github/workflows/hypatia-scan.yml': [] + '.github/workflows/instant-sync.yml': + - 'peter-evans/repository-dispatch@v3.0.0' + '.github/workflows/label-triage.yml': [] + '.github/workflows/labels.yml': [] + '.github/workflows/mirror.yml': [] + '.github/workflows/pages.yml': + - 'actions/checkout@v4.4.0' + - 'actions/deploy-pages@v4.0.5' + - 'actions/upload-pages-artifact@v3.0.1' + '.github/workflows/publish-aur.yml': + - 'actions/checkout@v6.0.1' + - 'ksxgithub/github-actions-deploy-aur@v3.0.1' + '.github/workflows/publish-chocolatey.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-container.yml': + - 'actions/attest-build-provenance@v2.4.0' + - 'actions/checkout@v6.0.1' + - 'docker/build-push-action@v6.9.0' + - 'docker/login-action@v3.3.0' + - 'docker/metadata-action@v5.5.1' + '.github/workflows/publish-copr.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-debian-ppa.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-flatpak.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-homebrew.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-macports.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-nixpkgs.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-obs.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-packages.yml': + - 'peter-evans/repository-dispatch@v3.0.0' + '.github/workflows/publish-scoop.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/publish-winget.yml': + - 'actions/checkout@v6.0.1' + '.github/workflows/push-email-notify.yml': + - 'dawidd6/action-send-mail@v3.12.0' + '.github/workflows/release.yml': + - 'actions/attest-build-provenance@v2.4.0' + - 'actions/checkout@v6.0.1' + - 'actions/download-artifact@v4.1.8' + - 'actions/setup-node@v4.0.2' + - 'actions/upload-artifact@v4.6.2' + - 'dtolnay/rust-toolchain@v1' + - 'goto-bus-stop/setup-zig@v2.2.1' + - 'softprops/action-gh-release@v2.2.1' + '.github/workflows/rust-ci.yml': [] + '.github/workflows/scorecard.yml': [] + '.github/workflows/secret-scanner.yml': [] + '.github/workflows/stress-test.yml': + - 'actions/checkout@v6.0.1' + - 'dtolnay/rust-toolchain@v1' + '.github/workflows/workflow-linter.yml': + - 'actions/checkout@v4.1.1' + '.github/workflows/zig-ffi.yml': + - 'actions/cache@v4.3.0' + - 'actions/checkout@v6.0.1' + - 'actions/upload-artifact@v4.6.2' + - 'denoland/setup-deno@v1.5.2' + - 'dtolnay/rust-toolchain@v1' + - 'goto-bus-stop/setup-zig@v2.2.1' +dependencies: + 'actions/attest-build-provenance@1176ef556905f349f669722abf30bce1a6e16e01': + ref: 'predicate@1.1.5' + commit: 'sha1-1176ef556905f349f669722abf30bce1a6e16e01' + owner_id: 44036562 + repo_id: 760702757 + 'actions/attest-build-provenance@v2.4.0': + ref: 'v2.4.0' + commit: 'sha1-e8998f949152b193b063cb0ec769d69d929409be' + owner_id: 44036562 + repo_id: 760702757 + uses: + - 'actions/attest-build-provenance@1176ef556905f349f669722abf30bce1a6e16e01' + - 'actions/attest@ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc' + 'actions/attest@ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc': + ref: 'v2.4.0' + commit: 'sha1-ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc' + owner_id: 44036562 + repo_id: 760701061 + 'actions/cache@v4.3.0': + ref: 'v4.3.0' + commit: 'sha1-0057852bfaa89a56745cba8c7296529d2fc39830' + owner_id: 44036562 + repo_id: 215566462 + 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v4.1.1': + ref: 'v4.1.1' + commit: 'sha1-b4ffde65f46336ab88eb53be808477a3936bae11' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v4.1.7': + ref: 'v4.1.7' + commit: 'sha1-692973e3d937129bcbf40652eb9f2f61becf3332' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v4.3.1': + ref: 'v4.3.1' + commit: 'sha1-34e114876b0b11c390a56381ad16ebd13914f8d5' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v4.4.0': + ref: 'v4.4.0' + commit: 'sha1-11d5960a326750d5838078e36cf38b85af677262' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v6.0.1': + ref: 'v6.0.1' + commit: 'sha1-8e8c483db84b4bee98b60c0593521ed34d9990e8' + owner_id: 44036562 + repo_id: 197814629 + 'actions/configure-pages@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-983d7736d9b0ae728b81ab479565c72886d7745b' + owner_id: 44036562 + repo_id: 513659658 + 'actions/deploy-pages@v4.0.5': + ref: 'v4.0.5' + commit: 'sha1-d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e' + owner_id: 44036562 + repo_id: 438112499 + 'actions/download-artifact@v4.1.8': + ref: 'v4.1.8' + commit: 'sha1-fa0a91b85d4f404e444e00e005971372dc801d16' + owner_id: 44036562 + repo_id: 192626254 + 'actions/setup-node@v4.0.2': + ref: 'v4.0.2' + commit: 'sha1-60edb5dd545a775178f52524783378180af0d1f8' + owner_id: 44036562 + repo_id: 189476904 + 'actions/upload-artifact@v4': + ref: 'v4' + commit: 'sha1-ea165f8d65b6e75b540449e92b4886f43607fa02' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@v4.6.2': + ref: 'v4.6.2' + commit: 'sha1-ea165f8d65b6e75b540449e92b4886f43607fa02' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-pages-artifact@v3.0.1': + ref: 'v3.0.1' + commit: 'sha1-56afc609e74202658d3ffba0e8f6dda462b719fa' + owner_id: 44036562 + repo_id: 496012378 + uses: + - 'actions/upload-artifact@v4' + 'cachix/install-nix-action@v30': + ref: 'v30' + commit: 'sha1-08dcb3a5e62fa31e2da3d490afc4176ef55ecd72' + owner_id: 36824654 + repo_id: 212301524 + 'dawidd6/action-send-mail@v3.12.0': + ref: 'v3.12.0' + commit: 'sha1-2cea9617b09d79a095af21254fbcb7ae95903dde' + owner_id: 9713907 + repo_id: 222439721 + 'denoland/setup-deno@v1.5.2': + ref: 'v1.5.2' + commit: 'sha1-11b63cf76cfcafb4e43f97b6cad24d8e8438f62d' + owner_id: 42048915 + repo_id: 356423100 + 'dependabot/fetch-metadata@v2.2.0': + ref: 'v2.2.0' + commit: 'sha1-dbb049abf0d677abbd7f7eee0375145b417fdd34' + owner_id: 27347476 + repo_id: 371068214 + 'docker/build-push-action@v6.9.0': + ref: 'v6.9.0' + commit: 'sha1-4f58ea79222b3b9dc2c8bbdd6debcef730109a75' + owner_id: 5429470 + repo_id: 241092383 + 'docker/login-action@v3.3.0': + ref: 'v3.3.0' + commit: 'sha1-9780b0c442fbb1117ed29e0efdff1e18412f7567' + owner_id: 5429470 + repo_id: 287743349 + 'docker/metadata-action@v5.5.1': + ref: 'v5.5.1' + commit: 'sha1-8e5442c4ef9f78752691e2d8f8d19755c6f78e81' + owner_id: 5429470 + repo_id: 306769011 + 'dtolnay/rust-toolchain@v1': + ref: 'v1' + commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + owner_id: 1940490 + repo_id: 260749683 + 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938': + ref: 'v4.37.9' + commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' + owner_id: 9919 + repo_id: 259445878 + 'google/clusterfuzzlite@v1': + ref: 'v1' + commit: 'sha1-884713a6c30a92e5e8544c39945cd7cb630abcd1' + owner_id: 1342004 + repo_id: 400046858 + 'goto-bus-stop/setup-zig@v2.2.1': + ref: 'v2.2.1' + commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' + owner_id: 1006268 + repo_id: 212984112 + 'haskell-actions/setup@v2.7.5': + ref: 'v2.7.5' + commit: 'sha1-ec49483bfc012387b227434aba94f59a6ecd0900' + owner_id: 75048950 + repo_id: 623796603 + 'hyperpolymath/deed-ecosystem@main': + ref: 'main' + commit: 'sha1-f9d999b60cb5f383679ea19912bcdc49c944973a' + owner_id: 6759885 + repo_id: 1275649586 + 'hyperpolymath/k9-ecosystem@main': + ref: 'main' + commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' + owner_id: 6759885 + repo_id: 1275650185 + 'ksxgithub/github-actions-deploy-aur@v3.0.1': + ref: 'v3.0.1' + commit: 'sha1-a97f56a8425a7a7f3b8c58607f769c69b089cadb' + owner_id: 11488886 + repo_id: 261159912 + 'peter-evans/repository-dispatch@v3.0.0': + ref: 'v3.0.0' + commit: 'sha1-ff45666b9427631e3450c54a1bcbee4d9ff4d7c0' + owner_id: 18365890 + repo_id: 220359305 + 'softprops/action-gh-release@v2.2.1': + ref: 'v2.2.1' + commit: 'sha1-c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda' + owner_id: 2242 + repo_id: 204253808 + 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': + ref: 'v2' + commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + owner_id: 580492 + repo_id: 298565987 + 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': + ref: 'v6.1.0' + commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + owner_id: 44036562 + repo_id: 215566462 + 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a': + ref: 'v7.0.1' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb': + ref: 'tag' + commit: 'sha1-83fd05a356d7e2593de66fc9913b3002723633cb' + owner_id: 44036562 + repo_id: 192625955 + 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': + ref: 'stable' + commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + owner_id: 1940490 + repo_id: 260749683 + 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393': + ref: 'v3.0.0' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': + ref: 'v1.24.1' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': + ref: 'v2.4.4' + commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' + owner_id: 67707773 + repo_id: 421101922 + 'softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844': + ref: 'v0.1.15' + commit: 'sha1-de2c0eb89ae2a093876385947365aca7b0e5f844' + owner_id: 2242 + repo_id: 204253808 + 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555': + ref: 'v0.10.0' + commit: 'sha1-e83874834305fe9a4a2997156cb26c5de65a8555' + owner_id: 135788 + repo_id: 208510314 diff --git a/vendor/bunsenite/.github/workflows/boj-build.yml b/vendor/bunsenite/.github/workflows/boj-build.yml new file mode 100644 index 0000000..e4a8e6b --- /dev/null +++ b/vendor/bunsenite/.github/workflows/boj-build.yml @@ -0,0 +1,23 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: BoJ Server Build Trigger +on: + push: + branches: [main, master] + workflow_dispatch: +jobs: + trigger-boj: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + - name: Trigger BoJ Server (Casket/ssg-mcp) + run: | + # Send a secure trigger to boj-server to build this repository + curl -X POST "http://boj-server.local:7700/cartridges/ssg-mcp/invoke" -H "Content-Type: application/json" -d "{\"repo\": \"${{ github.repository }}\", \"branch\": \"${{ github.ref_name }}\", \"engine\": \"casket\\"}"} + continue-on-error: true +permissions: + contents: read diff --git a/vendor/bunsenite/.github/workflows/cargo-audit.yml b/vendor/bunsenite/.github/workflows/cargo-audit.yml new file mode 100644 index 0000000..3cf5d93 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/cargo-audit.yml @@ -0,0 +1,60 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Prevention workflow - audits Rust dependencies for vulnerabilities +name: Cargo Audit + +on: + push: + branches: [main] + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + schedule: + - cron: '0 6 * * 1' # Weekly on Monday + +permissions: read-all + +jobs: + audit: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4.1.1 + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Run cargo audit + run: cargo audit --deny warnings + + - name: Check for unmaintained crates + run: cargo audit --deny unmaintained + + # Optional: Create issues for vulnerabilities + create-issue: + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: audit + if: failure() + permissions: + issues: write + steps: + - uses: actions/checkout@v4.1.1 + + - name: Create vulnerability issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + EXISTING=$(gh issue list --label "security,dependencies" --state open --json number -q '.[0].number') + if [ -z "$EXISTING" ]; then + gh issue create \ + --title "Security: Dependency vulnerabilities detected" \ + --body "cargo audit found vulnerabilities. Run \`cargo audit\` locally for details." \ + --label "security,dependencies" + fi diff --git a/vendor/bunsenite/.github/workflows/casket-pages.yml b/vendor/bunsenite/.github/workflows/casket-pages.yml new file mode 100644 index 0000000..8a35d6a --- /dev/null +++ b/vendor/bunsenite/.github/workflows/casket-pages.yml @@ -0,0 +1,121 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: GitHub Pages + +on: + push: + branches: [main, master] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Checkout casket-ssg + uses: actions/checkout@v4.1.1 + with: + repository: hyperpolymath/casket-ssg + path: .casket-ssg + + - name: Setup GHCup + uses: haskell-actions/setup@v2.7.5 + with: + ghc-version: '9.8.2' + cabal-version: '3.10' + + - name: Cache Cabal + uses: actions/cache@v4.3.0 + with: + path: | + ~/.cabal/packages + ~/.cabal/store + .casket-ssg/dist-newstyle + key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} + + - name: Build casket-ssg + working-directory: .casket-ssg + run: cabal build + + - name: Prepare site source + shell: bash + run: | + set -euo pipefail + rm -rf .site-src _site + + if [ -d site ]; then + cp -R site .site-src + else + mkdir -p .site-src + TODAY="$(date +%Y-%m-%d)" + REPO_NAME="${{ github.event.repository.name }}" + REPO_URL="https://github.com/${{ github.repository }}" + README_URL="" + + if [ -f README.md ]; then + README_URL="${REPO_URL}/blob/${{ github.ref_name }}/README.md" + elif [ -f README.adoc ]; then + README_URL="${REPO_URL}/blob/${{ github.ref_name }}/README.adoc" + fi + + { + echo "---" + echo "title: ${REPO_NAME}" + echo "date: ${TODAY}" + echo "---" + echo + echo "# ${REPO_NAME}" + echo + echo "Static documentation site for ${REPO_NAME}." + echo + echo "- Source repository: [${{ github.repository }}](${REPO_URL})" + if [ -n "${README_URL}" ]; then + echo "- README: [project README](${README_URL})" + fi + if [ -d docs ]; then + echo "- Docs directory: [docs/](${REPO_URL}/tree/${{ github.ref_name }}/docs)" + fi + echo + echo "Project-specific site content can be added later under site/." + } > .site-src/index.md + fi + + - name: Build site + run: | + mkdir -p _site + cd .casket-ssg && cabal run casket-ssg -- build ../.site-src ../_site + touch ../_site/.nojekyll + + - name: Setup Pages + uses: actions/configure-pages@v5.0.0 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3.0.1 + with: + path: '_site' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/vendor/bunsenite/.github/workflows/cflite_batch.yml b/vendor/bunsenite/.github/workflows/cflite_batch.yml new file mode 100644 index 0000000..2a81f24 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/cflite_batch.yml @@ -0,0 +1,37 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: ClusterFuzzLite batch fuzzing +on: + schedule: + - cron: '0 3 * * 0' # Weekly on Sunday at 3am UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + BatchFuzzing: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + sanitizer: [address] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + sanitizer: ${{ matrix.sanitizer }} + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 1800 + mode: batch + sanitizer: ${{ matrix.sanitizer }} diff --git a/vendor/bunsenite/.github/workflows/cflite_pr.yml b/vendor/bunsenite/.github/workflows/cflite_pr.yml new file mode 100644 index 0000000..b564941 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/cflite_pr.yml @@ -0,0 +1,36 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: ClusterFuzzLite PR fuzzing +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + PR: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + sanitizer: [address] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + sanitizer: ${{ matrix.sanitizer }} + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 + mode: code-change + sanitizer: ${{ matrix.sanitizer }} diff --git a/vendor/bunsenite/.github/workflows/codeql.yml b/vendor/bunsenite/.github/workflows/codeql.yml new file mode 100644 index 0000000..80d2588 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/codeql.yml @@ -0,0 +1,55 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: CodeQL Security Analysis + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + schedule: + - cron: '0 6 1 * *' # monthly 1st 06:00 UTC + +# Estate guardrail: cancel superseded runs so re-pushes / rebased PR +# updates do not pile up queued runs against the shared account-wide +# Actions concurrency pool. Applied only to read-only check workflows +# (no publish/mutation), so cancelling a superseded run is always safe. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + analyze: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/vendor/bunsenite/.github/workflows/dependabot-automerge.yml b/vendor/bunsenite/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..da95664 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,147 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# +# dependabot-automerge.yml — enable GitHub's native auto-merge on +# Dependabot pull requests that match a declared severity / ecosystem +# policy. Pairs with `.github/dependabot.yml`'s +# `open-pull-requests-limit: 0` + security-only pattern (see the +# cargo block there). +# +# What this does: +# - Triggers on every Dependabot PR. +# - Reads the PR's update-type metadata via the dependabot/fetch-metadata +# action (no free-text parsing). +# - Requires CI to be green before merge (GitHub's auto-merge enforces +# required status checks). +# - Gates merge behind a severity+ecosystem policy table. Default is +# low+medium security updates only. +# +# Why auto-merge on GitHub (not via a bot like rhodibot) is the right +# layer: GitHub enforces branch protection + required checks natively, +# and the PR author is already `dependabot[bot]`. Rhodibot doesn't need +# to know anything about ecosystems — GitHub handles the merge mechanics +# once we approve. +# +# Threat model: +# - A compromised upstream package with a bogus security advisory +# could propose a malicious version bump. Mitigation: require at +# least one non-automated reviewer for HIGH+CRITICAL severity +# (done below — we explicitly refuse to auto-approve those). +# - A compromised Dependabot itself is an Akerlof claim-grounder +# problem. Not in scope here; track under +# `project_claim_grounders_dual_use_akerlof.md`. +# +# Dogfooding: this workflow template is itself subject to the same +# Dependabot config via the github-actions ecosystem block, so SHA +# bumps for dependabot/fetch-metadata flow through the same path. + +name: Dependabot Auto-Merge + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read # needed to enable auto-merge + pull-requests: write # needed to approve + # NB: keep narrow — do NOT add secrets: read or id-token: write here. + +jobs: + automerge: + # Only run for PRs actually authored by Dependabot. + if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2.2.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + # --- Policy gate ------------------------------------------------------- + # Outputs from fetch-metadata we care about: + # update-type → version-update:semver-{patch,minor,major} + # dependency-type → direct:{development,production} | indirect + # alert-state → AUTO_DISMISSED | DISMISSED | FIXED | OPEN + # ghsa-id → GHSA-... if this is a security PR + # --- Policy ------------------------------------------------------------- + # AUTO-APPROVE + AUTO-MERGE when: + # 1. This is a SECURITY update (ghsa-id present), AND + # 2. Update is patch or minor, AND + # 3. Severity ≤ moderate (Dependabot doesn't expose severity + # directly in fetch-metadata; infer from the absence of + # HIGH/CRITICAL labels added by Dependabot). + # Otherwise: do nothing. Human reviews HIGH+CRITICAL security + # updates and all non-security bumps. + - name: Decide policy outcome + id: policy + env: + GHSA_ID: ${{ steps.meta.outputs.ghsa-id }} + UPDATE_TYPE: ${{ steps.meta.outputs.update-type }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} + run: | + set -euo pipefail + + is_security=false + is_patch_or_minor=false + is_high_or_critical=false + + [ -n "$GHSA_ID" ] && is_security=true + case "$UPDATE_TYPE" in + version-update:semver-patch|version-update:semver-minor) + is_patch_or_minor=true ;; + esac + + # Dependabot adds severity labels like "severity: high", + # "severity: critical". Look for those in the PR labels JSON. + if echo "$PR_LABELS" | grep -qiE '"(severity: (high|critical))"'; then + is_high_or_critical=true + fi + + if $is_security && $is_patch_or_minor && ! $is_high_or_critical; then + echo "action=automerge" >> "$GITHUB_OUTPUT" + else + echo "action=skip" >> "$GITHUB_OUTPUT" + fi + echo "security=$is_security" >> "$GITHUB_OUTPUT" + echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" + echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT" + + - name: Approve PR (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR_URL" \ + --body "Auto-approving Dependabot security update (${{ steps.policy.outputs.ghsa }}, ${{ steps.policy.outputs.update_type }}). Policy: low/moderate security patches/minors only." + + - name: Enable auto-merge (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr merge --auto --squash "$PR_URL" + + - name: Write decision to step summary + env: + ACTION: ${{ steps.policy.outputs.action }} + IS_SECURITY: ${{ steps.policy.outputs.security }} + UPDATE_TYPE: ${{ steps.policy.outputs.update_type }} + GHSA: ${{ steps.policy.outputs.ghsa }} + run: | + { + echo "## Dependabot Auto-Merge Decision" + echo "" + echo "| Field | Value |" + echo "|-------|-------|" + echo "| Policy action | \`$ACTION\` |" + echo "| Security update | \`$IS_SECURITY\` |" + echo "| Update type | \`$UPDATE_TYPE\` |" + echo "| GHSA ID | \`${GHSA:-n/a}\` |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/vendor/bunsenite/.github/workflows/dogfood-gate.yml b/vendor/bunsenite/.github/workflows/dogfood-gate.yml new file mode 100644 index 0000000..909b913 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/dogfood-gate.yml @@ -0,0 +1,418 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate +# Validates that the repo uses hyperpolymath's own formats and tools. +# Companion to static-analysis-gate.yml (security) — this is for format compliance. +name: Dogfood Gate + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +permissions: + contents: read + +jobs: + # --------------------------------------------------------------------------- + # Job 1: A2ML manifest validation + # --------------------------------------------------------------------------- + a2ml-validate: + name: Validate DEED manifests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Check for A2ML files + id: detect + run: | + COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ]; then + echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" + fi + + - name: Validate A2ML manifests + if: steps.detect.outputs.count > 0 + uses: hyperpolymath/deed-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + A2ML_COUNT="${{ steps.detect.outputs.count }}" + if [ "$A2ML_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## A2ML Validation + + :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + + Copy it from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo). + EOF + else + echo "## A2ML Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scanned **${A2ML_COUNT}** manifest file(s) (.deed, or legacy .a2ml). See step output for details." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 2: K9 contract validation + # --------------------------------------------------------------------------- + k9-validate: + name: Validate K9 contracts + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Check for K9 files + id: detect + run: | + COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ + -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ + -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) + echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" + echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then + echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." + fi + + - name: Validate K9 contracts + if: steps.detect.outputs.k9_count > 0 + uses: hyperpolymath/k9-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + K9_COUNT="${{ steps.detect.outputs.k9_count }}" + CFG_COUNT="${{ steps.detect.outputs.config_count }}" + if [ "$K9_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## K9 Contract Validation + + :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + + Generate contracts with: `k9iser generate .` + EOF + else + echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 3: Empty-linter — invisible character detection + # --------------------------------------------------------------------------- + empty-lint: + name: Empty-linter (invisible characters) + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Scan for invisible characters + id: lint + run: | + # Inline invisible character detection (from empty-linter's core patterns). + # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, + # non-breaking spaces, null bytes, and other invisible Unicode in source files. + set +e + PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' + find "$GITHUB_WORKSPACE" \ + -not -path '*/.git/*' -not -path '*/node_modules/*' \ + -not -path '*/.deno/*' -not -path '*/target/*' \ + -not -path '*/_build/*' -not -path '*/deps/*' \ + -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ + -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ + -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ + -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ + -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ + -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ + -exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null + EL_EXIT=$? + set -e + + FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0) + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" + echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + + # Blocking subset: C0 controls and NUL only (owner ruling 2026-08-28). + # Invisible Unicode (NBSP/BOM/zero-width) stays ADVISORY - about 2,100 + # estate files carry it as legitimate typography in prose. + blocking=0 + while IFS= read -r bf; do + [ -z "$bf" ] && continue + if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "$bf"; then + blocking=$((blocking+1)) + echo "::error file=${bf#$GITHUB_WORKSPACE/}::C0 control characters or NUL bytes - file corruption, blocks the gate" + fi + done < /tmp/empty-lint-results.txt + echo "blocking=$blocking" >> "$GITHUB_OUTPUT" + + # Emit annotations for each file with invisible chars + while IFS= read -r filepath; do + [ -z "$filepath" ] && continue + REL_PATH="${filepath#$GITHUB_WORKSPACE/}" + echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)" + done < /tmp/empty-lint-results.txt + + # Enforce (owner ruling 2026-08-28): C0/NUL corruption BLOCKS; other + # invisible Unicode stays advisory. Enforcement lives inside this step + # so a crash above fails the job directly - counts can never arrive + # empty into a separate check that then passes silently. + if [ "$EL_EXIT" -ne 0 ]; then + echo "::warning::invisible-character scan exited $EL_EXIT - results may be incomplete" + fi + if [ "${blocking:-0}" -gt 0 ]; then + echo "## Empty-linter: BLOCKED - $blocking file(s) with C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + echo "::error::$blocking file(s) contain C0 control characters or NUL bytes - corruption, not typography. See file annotations." + exit 1 + elif [ "${FINDINGS:-0}" -gt 0 ]; then + echo "::notice::$FINDINGS file(s) carry invisible Unicode (NBSP/BOM/zero-width) - advisory only" + fi + + - name: Write summary + run: | + if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then + FINDINGS="${{ steps.lint.outputs.findings }}" + if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 4: Groove manifest check (for repos that should expose services) + # --------------------------------------------------------------------------- + groove-check: + name: Groove manifest check + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Check for Groove manifest + id: groove + run: | + # Check for static or dynamic Groove endpoints + HAS_MANIFEST="false" + HAS_GROOVE_CODE="false" + + if [ -f ".well-known/groove/manifest.json" ]; then + HAS_MANIFEST="true" + # Validate the manifest JSON + if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then + echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest" + else + SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json) + echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" + fi + fi + + # Check for Groove endpoint code (Rust, Elixir, Zig, V) + if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then + HAS_GROOVE_CODE="true" + fi + + # Check if this repo likely serves HTTP (has server/listener code) + HAS_SERVER="false" + if grep -rl 'TcpListener\|Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + HAS_SERVER="true" + fi + + echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" + echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" + echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" + + if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then + echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery." + fi + + - name: Write summary + run: | + echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" + + # --------------------------------------------------------------------------- + # Job 5: eclexiaiser manifest validation + # --------------------------------------------------------------------------- + eclexiaiser-validate: + name: Validate eclexiaiser manifest + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Check and validate eclexiaiser manifest + id: eclex + run: | + if [ ! -f "eclexiaiser.toml" ]; then + # Check if repo has a Containerfile — if so, recommend eclexiaiser + if [ -f "Containerfile" ]; then + echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." + fi + echo "has_manifest=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_manifest=true" >> "$GITHUB_OUTPUT" + + # Validate TOML structure using Python 3.11+ tomllib + python3 -c " + import tomllib, sys + with open('eclexiaiser.toml', 'rb') as f: + data = tomllib.load(f) + project = data.get('project', {}) + if not project.get('name', '').strip(): + print('ERROR: project.name is required', file=sys.stderr) + sys.exit(1) + functions = data.get('functions', []) + if not functions: + print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) + sys.exit(1) + for fn in functions: + if not fn.get('name', '').strip(): + print('ERROR: function name cannot be empty', file=sys.stderr) + sys.exit(1) + if not fn.get('source', '').strip(): + print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr) + sys.exit(1) + print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))') + " || { + echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details" + exit 1 + } + + - name: Write summary + run: | + if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 6: Dogfooding summary + # --------------------------------------------------------------------------- + dogfood-summary: + name: Dogfooding compliance summary + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [a2ml-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate] + if: always() + + steps: + - name: Checkout repository + uses: actions/checkout@v4.3.1 + + - name: Generate dogfooding scorecard + run: | + SCORE=0 + MAX=6 + + # A2ML manifest present? + if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + A2ML_STATUS=":white_check_mark:" + else + A2ML_STATUS=":x:" + fi + + # K9 contracts present? + if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + K9_STATUS=":white_check_mark:" + else + K9_STATUS=":x:" + fi + + # .editorconfig present? + if [ -f ".editorconfig" ]; then + SCORE=$((SCORE + 1)) + EC_STATUS=":white_check_mark:" + else + EC_STATUS=":x:" + fi + + # Groove manifest or code? + if [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + GROOVE_STATUS=":white_check_mark:" + else + GROOVE_STATUS=":ballot_box_with_check:" + fi + + # VeriSimDB integration? + if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + VSDB_STATUS=":white_check_mark:" + else + VSDB_STATUS=":ballot_box_with_check:" + fi + + # eclexiaiser energy tracking? + if [ -f "eclexiaiser.toml" ]; then + SCORE=$((SCORE + 1)) + ECLEX_STATUS=":white_check_mark:" + else + ECLEX_STATUS=":ballot_box_with_check:" + fi + + cat <> "$GITHUB_STEP_SUMMARY" + ## Dogfooding Scorecard + + **Score: ${SCORE}/${MAX}** + + | Tool/Format | Status | Notes | + |-------------|--------|-------| + | DEED repo deed (`_chora.deed`) | ${A2ML_STATUS} | Required for all RSR repos | + | K9 contracts | ${K9_STATUS} | Required for repos with config files | + | .editorconfig | ${EC_STATUS} | Required for all repos | + | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | + | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | + | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | + + --- + *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.* + *Dogfooding is guinea pig fooding — we test our tools on ourselves.* + EOF + diff --git a/vendor/bunsenite/.github/workflows/generator-generic-ossf-slsa3-publish.yml b/vendor/bunsenite/.github/workflows/generator-generic-ossf-slsa3-publish.yml new file mode 100644 index 0000000..a2d80e0 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/generator-generic-ossf-slsa3-publish.yml @@ -0,0 +1,75 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow lets you generate SLSA provenance file for your project. +# The generation satisfies level 3 for the provenance requirements - see https://slsa.dev/spec/v0.1/requirements +# The project is an initiative of the OpenSSF (openssf.org) and is developed at +# https://github.com/slsa-framework/slsa-github-generator. +# The provenance file can be verified using https://github.com/slsa-framework/slsa-verifier. +# For more information about SLSA and how it improves the supply-chain, visit slsa.dev. + +name: SLSA generic generator +on: + workflow_dispatch: + release: + types: [created] + + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + outputs: + digests: ${{ steps.hash.outputs.digests }} + + steps: + - uses: actions/checkout@v6.0.1 + + # ======================================================== + # + # Step 1: Build your artifacts. + # + # ======================================================== + - name: Build artifacts + run: | + # These are some amazing artifacts. + echo "artifact1" > artifact1 + echo "artifact2" > artifact2 + + # ======================================================== + # + # Step 2: Add a step to generate the provenance subjects + # as shown below. Update the sha256 sum arguments + # to include all binaries that you generate + # provenance for. + # + # ======================================================== + - name: Generate subject for provenance + id: hash + run: | + set -euo pipefail + + # List the artifacts the provenance will refer to. + files=$(ls artifact*) + # Generate the subjects (base64 encoded). + echo "hashes=$(sha256sum $files | base64 -w0)" >> "${GITHUB_OUTPUT}" + + provenance: + needs: [build] + permissions: + actions: read # To read the workflow path. + id-token: write # To sign the provenance. + contents: write # To add assets to a release. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@68bad40844440577b33778c9f29077a3388838e9 # v1.4.0 + with: + base64-subjects: "${{ needs.build.outputs.digests }}" + upload-assets: true # Optional: Upload to a new release diff --git a/vendor/bunsenite/.github/workflows/ghcr-publish.yml b/vendor/bunsenite/.github/workflows/ghcr-publish.yml new file mode 100644 index 0000000..3bfc607 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/ghcr-publish.yml @@ -0,0 +1,65 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish to GHCR + +permissions: + contents: read + +on: + release: + types: [published] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.1 + + - name: Install nerdctl and containerd + run: | + sudo apt-get update + sudo apt-get install -y containerd + sudo systemctl start containerd + + NERDCTL_VERSION=2.2.1 + curl -fsSL "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION}-linux-amd64.tar.gz" -o /tmp/nerdctl.tar.gz + sudo tar -xzf /tmp/nerdctl.tar.gz -C /usr/local + sudo mkdir -p /opt/cni/bin + sudo cp /usr/local/libexec/cni/* /opt/cni/bin/ 2>/dev/null || true + + sudo /usr/local/bin/buildkitd & + sleep 3 + + - name: Log in to GHCR + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | sudo nerdctl login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build image + run: | + sudo nerdctl build -f Containerfile -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} . + sudo nerdctl tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + - name: Push image + run: | + sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + - name: Tag release version + if: github.event_name == 'release' + run: | + VERSION=${{ github.event.release.tag_name }} + sudo nerdctl tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION} + sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION} diff --git a/vendor/bunsenite/.github/workflows/governance.yml b/vendor/bunsenite/.github/workflows/governance.yml new file mode 100644 index 0000000..094a73b --- /dev/null +++ b/vendor/bunsenite/.github/workflows/governance.yml @@ -0,0 +1,20 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Governance + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +permissions: + actions: read + contents: read + +jobs: + governance: + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/vendor/bunsenite/.github/workflows/hypatia-scan.yml b/vendor/bunsenite/.github/workflows/hypatia-scan.yml new file mode 100644 index 0000000..0c009e8 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/hypatia-scan.yml @@ -0,0 +1,23 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Hypatia Security Scan + +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master] + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: + actions: read + contents: read + security-events: write + +jobs: + hypatia: + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/vendor/bunsenite/.github/workflows/instant-sync.yml b/vendor/bunsenite/.github/workflows/instant-sync.yml new file mode 100644 index 0000000..e3a3052 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/instant-sync.yml @@ -0,0 +1,37 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Instant Forge Sync - Triggers propagation to all forges on push/release +name: Instant Sync + +on: + push: + branches: [main, master] + release: + types: [published] + +permissions: + contents: read + +jobs: + dispatch: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Propagation + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.FARM_DISPATCH_TOKEN }} + repository: hyperpolymath/.git-private-farm + event-type: propagate + client-payload: |- + { + "repo": "${{ github.event.repository.name }}", + "ref": "${{ github.ref }}", + "sha": "${{ github.sha }}", + "forges": "" + } + + - name: Confirm + run: echo "::notice::Propagation triggered for ${{ github.event.repository.name }}" diff --git a/vendor/bunsenite/.github/workflows/label-triage.yml b/vendor/bunsenite/.github/workflows/label-triage.yml new file mode 100644 index 0000000..814a192 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/label-triage.yml @@ -0,0 +1,117 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Label Triage + +# Classify newly-filed issues against the estate label taxonomy. +# +# The sweep that established the taxonomy is a one-off; this is what stops it +# decaying. Without it every new issue arrives unlabelled and the 55%-unlabelled +# state rebuilds itself. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces +# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow +# the lock does not list is rejected before any step runs (startup_failure, and +# therefore no check run at all). A dispatched workflow lands in repos whose +# lock has not been regenerated, so it must not depend on any action. +# +# ⚠ THE CLASSIFIER IS jq, NOT PYTHON. Python is fully banned estate-wide -- the +# `governance / Language / package anti-pattern policy` gate runs +# `git ls-files '*.py'` and fails the PR. Shipping a .py into 416 repos would +# mean shipping an exemption into 416 repos. jq is preinstalled on every GitHub +# runner, is not banned, and needs no action. +# +# Deliberately conservative: +# - ADDITIVE ONLY. It never removes a label and never overrides a human's +# classification: anything already on the issue is passed in via `have` and +# is never re-suggested, and the classifier stays out of any max-1 tier the +# issue already carries a label in. +# - SILENT WHEN UNSURE. Nothing is printed unless a prefix, bracket or type +# rule actually fired. Roughly 70% of the historical corpus classified this +# way; the rest is meant to reach a human. +# - NEVER FAILS THE ISSUE. Every step is best-effort; a missing payload or an +# API hiccup exits 0 rather than leaving a red mark on someone's bug report. + +on: + issues: + types: [opened, reopened] + workflow_dispatch: + inputs: + issue: + description: "Issue number to (re)classify" + required: true + +permissions: + issues: write + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - name: Classify and label + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUM: ${{ github.event.issue.number || inputs.issue }} + run: | + set -uo pipefail + work=$(mktemp -d); RULES=$work/rules.json; SCRIPT=$work/classify.jq + + # fetch instead of checking out -- no action means no lock entry to drift + gh api "repos/$GITHUB_REPOSITORY/contents/.github/label-classifier.json?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$RULES" || true + gh api "repos/$GITHUB_REPOSITORY/contents/.github/scripts/classify-issue.jq?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$SCRIPT" || true + if [[ ! -s "$RULES" || ! -s "$SCRIPT" ]]; then + echo "no classifier payload in this repo - nothing to do" + exit 0 + fi + + TITLE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" --json title --jq .title) || exit 0 + echo "issue #$NUM: $TITLE" + + # Labels this repo actually defines. --limit 1000 is GitHub's real + # per-repo ceiling; the default of 30 would silently hide most of the + # taxonomy. Fetched BEFORE the label read below so that read stays as + # close to the write as possible. + mapfile -t DEFINED < <(gh label list -R "$GITHUB_REPOSITORY" --limit 1000 \ + --json name --jq '.[].name' 2>/dev/null) + + # Labels already present; a human's work is never overridden. Read + # HERE rather than earlier: every API call between this read and the + # edit below widens a window in which someone could add a type label + # and get a second one back from us. Only the local jq call is inside it. + HAVE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" \ + --json labels --jq '[.labels[].name]' 2>/dev/null) || HAVE='[]' + [[ -n "$HAVE" ]] || HAVE='[]' + echo "already has: $HAVE" + + mapfile -t ADD < <(jq -r --arg title "$TITLE" --argjson have "$HAVE" \ + -f "$SCRIPT" "$RULES" 2>/dev/null) + if [[ ${#ADD[@]} -eq 0 || -z "${ADD[0]:-}" ]]; then + echo "no confident classification - leaving for a human" + exit 0 + fi + + apply=() + for want in "${ADD[@]}"; do + for def in "${DEFINED[@]}"; do + if [[ "$want" == "$def" ]]; then apply+=("$want"); break; fi + done + done + if [[ ${#apply[@]} -eq 0 ]]; then + echo "classified as ${ADD[*]} but this repo defines none of them - run the label sync" + exit 0 + fi + + printf 'applying: %s\n' "${apply[*]}" + # Build the arguments as an ARRAY. The previous form was an unquoted + # command substitution, so the shell re-split its output on spaces and + # a label name containing whitespace would arrive as several broken + # arguments. No canonical label contains a space today, which is + # exactly why this would have failed quietly the first time one did. + # (Also clears actionlint SC2046.) + edit_args=() + for lab in "${apply[@]}"; do edit_args+=(--add-label "$lab"); done + gh issue edit "$NUM" -R "$GITHUB_REPOSITORY" "${edit_args[@]}" \ + || echo "label apply failed - not failing the run" + exit 0 diff --git a/vendor/bunsenite/.github/workflows/labels.yml b/vendor/bunsenite/.github/workflows/labels.yml new file mode 100644 index 0000000..83ab941 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/labels.yml @@ -0,0 +1,106 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Labels + +# Applies the canonical estate label set from .github/labels.json. +# +# Additive and idempotent by design: it CREATES missing labels and UPDATES +# colour/description drift. It never deletes, and it never touches a label in +# the `frozen` list -- those are applied by Dependabot / PR automation, or are +# wired into triage.yml's exempt-issue-labels, and renaming them breaks things. +# +# jq is preinstalled on GitHub runners; PyYAML is not, which is why the payload +# is JSON rather than YAML. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces +# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow +# the lock does not list is rejected before any step runs (startup_failure, and +# therefore no check run at all). A dispatched workflow lands in repos whose +# lock has not been regenerated, so it must not depend on any action. + +on: + workflow_dispatch: + push: + paths: + - '.github/labels.json' + schedule: + - cron: "23 4 1 * *" # monthly drift repair + +permissions: + issues: write + contents: read + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Apply canonical labels + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ⚠ LOAD-BEARING. This workflow deliberately does not check the repo + # out (no `uses:`, so no actions.lock entry can drift), which means + # `gh label create` / `gh label edit` have no git remote to infer a + # target from. Without GH_REPO every mutation fails, and because the + # errors used to be discarded the step still exited 0 reporting + # "created=0 updated=0" -- a silent, estate-wide no-op. + GH_REPO: ${{ github.repository }} + run: | + set -uo pipefail + work=$(mktemp -d); PAYLOAD=$work/labels.json + + # fetch instead of checking out -- no action means no lock entry to drift + gh api "repos/$GITHUB_REPOSITORY/contents/.github/labels.json?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$PAYLOAD" || true + [ -s "$PAYLOAD" ] || { echo "no .github/labels.json - nothing to do"; exit 0; } + + mapfile -t FROZEN < <(jq -r '.frozen[]' "$PAYLOAD") + created=0; updated=0; skipped=0; failed=0 + + existing=$(gh api "repos/$GITHUB_REPOSITORY/labels" --paginate \ + --jq '.[] | [.name, .color, (.description // "")] | @tsv') + + while IFS=$'\t' read -r name color desc; do + [ -z "$name" ] && continue + frozen=0 + for f in "${FROZEN[@]}"; do [ "$f" = "$name" ] && frozen=1 && break; done + + cur=$(printf '%s\n' "$existing" | awk -F'\t' -v n="$name" '$1==n{print;exit}') + if [ -z "$cur" ]; then + # A MISSING label is created even when frozen. "Frozen" protects a + # label's DEFINITION from being renamed or recoloured -- it was + # never meant to stop the label existing. Skipping creation broke + # `security`, the one canonical label that is also frozen: it was + # absent from 10 of 12 sampled repos, and label-triage drops any + # label the repo does not define, so every `security` finding was + # silently discarded estate-wide. + if err=$(gh label create "$name" --color "$color" \ + --description "$desc" 2>&1 >/dev/null); then + created=$((created+1)); sleep 0.4 + else + echo " create failed: $name -- ${err:-unknown}"; failed=$((failed+1)) + fi + else + # Present AND frozen: leave it exactly as it is. + if [ "$frozen" -eq 1 ]; then skipped=$((skipped+1)); continue; fi + ccol=$(cut -f2 <<<"$cur"); cdesc=$(cut -f3- <<<"$cur") + if [ "${ccol,,}" != "${color,,}" ] || [ "$cdesc" != "$desc" ]; then + if err=$(gh label edit "$name" --color "$color" \ + --description "$desc" 2>&1 >/dev/null); then + updated=$((updated+1)); sleep 0.4 + else + echo " edit failed: $name -- ${err:-unknown}"; failed=$((failed+1)) + fi + fi + fi + done < <(jq -r '.labels[] | [.name, .color, .description] | @tsv' "$PAYLOAD") + + echo "created=$created updated=$updated frozen-skipped=$skipped failed=$failed" + + # Fail ONLY on the misconfiguration shape: work was attempted, every + # attempt failed. That is the silent-no-op signature. A single flaky + # label must not turn the whole estate's CI red. + if [ "$failed" -gt 0 ] && [ "$((created + updated))" -eq 0 ]; then + echo "every label mutation failed - the sync did nothing. Check GH_REPO and token scope." + exit 1 + fi + exit 0 diff --git a/vendor/bunsenite/.github/workflows/mirror.yml b/vendor/bunsenite/.github/workflows/mirror.yml new file mode 100644 index 0000000..b95ea10 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/mirror.yml @@ -0,0 +1,19 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Mirror to Git Forges + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + actions: read + contents: read + +jobs: + mirror: + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a + secrets: inherit diff --git a/vendor/bunsenite/.github/workflows/pages.yml b/vendor/bunsenite/.github/workflows/pages.yml new file mode 100644 index 0000000..96a53cf --- /dev/null +++ b/vendor/bunsenite/.github/workflows/pages.yml @@ -0,0 +1,57 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: GitHub Pages (Ddraig SSG) +on: + push: + branches: [main, master] + workflow_dispatch: +permissions: + contents: read + pages: write + id-token: write +concurrency: + group: "pages" + cancel-in-progress: false +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + container: + image: ghcr.io/stefan-hoeck/idris2-pack@sha256:f0758996a931fb35d9ecb1de273c4d59dabe2a09b433afc7e357f65a08b7e1ff + steps: + - name: Checkout Site + uses: actions/checkout@v4.4.0 + - name: Checkout Ddraig SSG + uses: actions/checkout@v4.4.0 + with: + repository: hyperpolymath/ddraig-ssg + path: .ddraig-ssg + - name: Compile Ddraig + working-directory: .ddraig-ssg + run: idris2 Ddraig.idr -o ddraig + - name: Build site + run: | + mkdir -p src + if [ ! -f src/index.md ] && [ -f README.md ]; then + cp README.md src/index.md + elif [ ! -f src/index.md ]; then + echo "# ${GITHUB_REPOSITORY}" > src/index.md + fi + ./.ddraig-ssg/build/exec/ddraig build src _site https://hyperpolymath.github.io/${GITHUB_REPOSITORY#*/} + - name: Upload artifact + uses: actions/upload-pages-artifact@v3.0.1 + with: + path: '_site' + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/vendor/bunsenite/.github/workflows/publish-aur.yml b/vendor/bunsenite/.github/workflows/publish-aur.yml new file mode 100644 index 0000000..dc0bcc8 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-aur.yml @@ -0,0 +1,115 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish AUR + +on: + repository_dispatch: + types: [publish-aur] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + sha_linux_x64: + description: 'SHA256 for Linux x64' + required: true + sha_linux_arm64: + description: 'SHA256 for Linux arm64' + required: true + +permissions: + contents: read + +jobs: + publish-aur: + name: Publish to AUR + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for AUR secrets + id: check-secret + run: | + if [ -z "${{ secrets.AUR_SSH_PRIVATE_KEY }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::AUR_SSH_PRIVATE_KEY not configured. Skipping AUR publish." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout bunsenite + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + echo "sha_linux_x64=${{ github.event.client_payload.sha_linux_x64 }}" >> $GITHUB_OUTPUT + echo "sha_linux_arm64=${{ github.event.client_payload.sha_linux_arm64 }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + echo "sha_linux_x64=${{ inputs.sha_linux_x64 }}" >> $GITHUB_OUTPUT + echo "sha_linux_arm64=${{ inputs.sha_linux_arm64 }}" >> $GITHUB_OUTPUT + fi + + - name: Generate PKGBUILD + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + SHA_X64="${{ steps.inputs.outputs.sha_linux_x64 }}" + SHA_ARM64="${{ steps.inputs.outputs.sha_linux_arm64 }}" + + mkdir -p aur-package + + cat > aur-package/PKGBUILD << EOF + # Maintainer: hyperpolymath + # SPDX-License-Identifier: MPL-2.0 + pkgname=bunsenite-bin + pkgver=${VERSION} + pkgrel=1 + pkgdesc="Nickel configuration file parser with multi-language FFI bindings (pre-built binary)" + arch=('x86_64' 'aarch64') + url="https://github.com/hyperpolymath/bunsenite" + license=('MIT' 'custom:Palimpsest-0.8') + depends=('gcc-libs') + provides=('bunsenite') + conflicts=('bunsenite' 'bunsenite-git') + + source_x86_64=("\${pkgname}-\${pkgver}-x86_64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-unknown-linux-gnu.tar.gz") + source_aarch64=("\${pkgname}-\${pkgver}-aarch64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-aarch64-unknown-linux-gnu.tar.gz") + sha256sums_x86_64=('${SHA_X64}') + sha256sums_aarch64=('${SHA_ARM64}') + + package() { + install -Dm755 "bunsenite" "\$pkgdir/usr/bin/bunsenite" + # Install shared library if present + if [ -f "libbunsenite.so" ]; then + install -Dm755 "libbunsenite.so" "\$pkgdir/usr/lib/libbunsenite.so" + fi + } + EOF + + echo "Generated PKGBUILD:" + cat aur-package/PKGBUILD + + - name: Publish to AUR + if: steps.check-secret.outputs.skip != 'true' + uses: KSXGitHub/github-actions-deploy-aur@v3.0.1 + with: + pkgname: bunsenite-bin + pkgbuild: aur-package/PKGBUILD + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: "Update to ${{ steps.inputs.outputs.tag }}" + force_push: true diff --git a/vendor/bunsenite/.github/workflows/publish-chocolatey.yml b/vendor/bunsenite/.github/workflows/publish-chocolatey.yml new file mode 100644 index 0000000..5d368f0 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-chocolatey.yml @@ -0,0 +1,154 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Chocolatey + +on: + repository_dispatch: + types: [publish-chocolatey] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + sha_windows_x64: + description: 'SHA256 for Windows x64' + required: true + +permissions: + contents: read + +jobs: + publish-chocolatey: + name: Publish to Chocolatey + runs-on: windows-latest + timeout-minutes: 15 + steps: + - name: Check for CHOCO_API_KEY + id: check-secret + shell: pwsh + run: | + if ([string]::IsNullOrEmpty("${{ secrets.CHOCO_API_KEY }}")) { + echo "skip=true" >> $env:GITHUB_OUTPUT + Write-Warning "CHOCO_API_KEY not configured. Skipping Chocolatey publish." + } else { + echo "skip=false" >> $env:GITHUB_OUTPUT + } + + - name: Checkout bunsenite + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + shell: pwsh + run: | + if ("${{ github.event_name }}" -eq "repository_dispatch") { + echo "version=${{ github.event.client_payload.version }}" >> $env:GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $env:GITHUB_OUTPUT + echo "sha_windows_x64=${{ github.event.client_payload.sha_windows_x64 }}" >> $env:GITHUB_OUTPUT + } else { + echo "version=${{ inputs.version }}" >> $env:GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $env:GITHUB_OUTPUT + echo "sha_windows_x64=${{ inputs.sha_windows_x64 }}" >> $env:GITHUB_OUTPUT + } + + - name: Download Windows binary + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + $VERSION = "${{ steps.inputs.outputs.version }}" + $TAG = "${{ steps.inputs.outputs.tag }}" + $URL = "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-pc-windows-msvc.zip" + + Write-Host "Downloading from: $URL" + Invoke-WebRequest -Uri $URL -OutFile bunsenite.zip + + # Create tools directory + New-Item -ItemType Directory -Force -Path packaging/chocolatey/tools + Expand-Archive bunsenite.zip -DestinationPath packaging/chocolatey/tools -Force + + - name: Create nuspec + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + $VERSION = "${{ steps.inputs.outputs.version }}" + + $nuspec = @" + + + + + bunsenite + ${VERSION} + Bunsenite + hyperpolymath + hyperpolymath + https://github.com/hyperpolymath/bunsenite + https://github.com/hyperpolymath/bunsenite/blob/main/LICENSE.txt + false + https://github.com/hyperpolymath/bunsenite + https://github.com/hyperpolymath/bunsenite/issues + nickel config configuration parser rust cli + Nickel configuration file parser with multi-language FFI bindings + + Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + + Features: + - Parse and evaluate Nickel configuration files + - Watch mode for live reloading + - Interactive REPL + - JSON Schema validation + - FFI bindings for Deno, AffineScript, and WebAssembly + + https://github.com/hyperpolymath/bunsenite/releases/tag/v${VERSION} + + + + + + "@ + + Set-Content -Path packaging/chocolatey/bunsenite.nuspec -Value $nuspec + Write-Host "Created nuspec:" + Get-Content packaging/chocolatey/bunsenite.nuspec + + - name: Create install script + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + $installScript = @' + $ErrorActionPreference = 'Stop' + $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" + $exePath = Join-Path $toolsDir 'bunsenite.exe' + + # Create shim + Install-BinFile -Name 'bunsenite' -Path $exePath + '@ + + Set-Content -Path packaging/chocolatey/tools/chocolateyInstall.ps1 -Value $installScript + + $uninstallScript = @' + $ErrorActionPreference = 'Stop' + Uninstall-BinFile -Name 'bunsenite' + '@ + + Set-Content -Path packaging/chocolatey/tools/chocolateyUninstall.ps1 -Value $uninstallScript + + - name: Pack and push + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + cd packaging/chocolatey + choco pack bunsenite.nuspec + + # List generated package + Get-ChildItem *.nupkg + + # Push to Chocolatey + choco push bunsenite.*.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.CHOCO_API_KEY }} diff --git a/vendor/bunsenite/.github/workflows/publish-container.yml b/vendor/bunsenite/.github/workflows/publish-container.yml new file mode 100644 index 0000000..a504715 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-container.yml @@ -0,0 +1,61 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Container + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: write + id-token: write + attestations: write + steps: + - uses: actions/checkout@v6.0.1 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Build and push + id: push + uses: docker/build-push-action@v6.9.0 + with: + context: . + file: ./Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Attest container provenance + uses: actions/attest-build-provenance@v2.4.0 + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/vendor/bunsenite/.github/workflows/publish-copr.yml b/vendor/bunsenite/.github/workflows/publish-copr.yml new file mode 100644 index 0000000..8198867 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-copr.yml @@ -0,0 +1,160 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish COPR + +on: + repository_dispatch: + types: [publish-copr] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + trigger-copr: + name: Trigger COPR Build + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for COPR_WEBHOOK_URL + id: check-secret + run: | + if [ -z "${{ secrets.COPR_WEBHOOK_URL }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::COPR_WEBHOOK_URL not configured. Skipping COPR trigger." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Trigger COPR webhook + if: steps.check-secret.outputs.skip != 'true' + run: | + TAG="${{ steps.inputs.outputs.tag }}" + VERSION="${{ steps.inputs.outputs.version }}" + + echo "Triggering COPR build for ${TAG}" + + # Trigger the webhook + curl -X POST "${{ secrets.COPR_WEBHOOK_URL }}" \ + -H "Content-Type: application/json" \ + -d "{\"ref\": \"${TAG}\", \"committish\": \"${TAG}\"}" \ + --fail --silent --show-error + + echo "COPR webhook triggered successfully" + + update-spec: + name: Update RPM Spec in Tap + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping RPM spec update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Update RPM spec + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + DATE=$(date "+%a %b %d %Y") + + mkdir -p rpm + + cat > rpm/bunsenite.spec << EOF + # SPDX-License-Identifier: MPL-2.0 + Name: bunsenite + Version: ${VERSION} + Release: 1%{?dist} + Summary: Nickel configuration file parser with multi-language FFI bindings + + License: MIT OR Palimpsest-0.8 + URL: https://github.com/hyperpolymath/bunsenite + Source0: https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz + + BuildRequires: cargo + BuildRequires: rust >= 1.70 + + %description + Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + Features include parse, validate, watch mode, interactive REPL, and JSON Schema validation. + + %prep + %autosetup -n bunsenite-%{version} + + %build + cargo build --release --features full + + %install + install -D -m 755 target/release/bunsenite %{buildroot}%{_bindir}/bunsenite + + %files + %license LICENSE.txt + %doc README.adoc + %{_bindir}/bunsenite + + %changelog + * ${DATE} hyperpolymath - ${VERSION}-1 + - Update to version ${VERSION} + EOF + + echo "Generated RPM spec:" + cat rpm/bunsenite.spec + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add rpm/ + git diff --staged --quiet || git commit -m "rpm: bunsenite ${VERSION}" + git push diff --git a/vendor/bunsenite/.github/workflows/publish-debian-ppa.yml b/vendor/bunsenite/.github/workflows/publish-debian-ppa.yml new file mode 100644 index 0000000..4a6e234 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-debian-ppa.yml @@ -0,0 +1,211 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Debian PPA + +on: + repository_dispatch: + types: [publish-debian-ppa] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + PPA_NAME: hyperpolymath/bunsenite + MAINTAINER_NAME: hyperpolymath + MAINTAINER_EMAIL: packages@hyperpolymath.dev + +jobs: + publish-ppa: + name: Publish to Launchpad PPA + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for Launchpad secrets + id: check-secret + run: | + if [ -z "${{ secrets.LAUNCHPAD_GPG_KEY }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::LAUNCHPAD_GPG_KEY not configured. Skipping PPA publish." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout bunsenite + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Install dependencies + if: steps.check-secret.outputs.skip != 'true' + run: | + sudo apt-get update + sudo apt-get install -y devscripts debhelper dput gnupg + + - name: Import GPG key + if: steps.check-secret.outputs.skip != 'true' + run: | + echo "${{ secrets.LAUNCHPAD_GPG_KEY }}" | gpg --batch --import + # Trust the key + KEY_ID=$(gpg --list-keys --keyid-format LONG | grep -A1 "^pub" | tail -1 | awk '{print $1}') + echo "${KEY_ID}:6:" | gpg --import-ownertrust + + - name: Download and prepare source + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + + # Download source tarball + curl -L -o bunsenite_${VERSION}.orig.tar.gz \ + "https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz" + + # Extract + tar xzf bunsenite_${VERSION}.orig.tar.gz + mv bunsenite-${VERSION#v} bunsenite-${VERSION} + + - name: Create debian directory + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + cd bunsenite-${VERSION} + + mkdir -p debian/source + + # debian/control + cat > debian/control << EOF + Source: bunsenite + Section: devel + Priority: optional + Maintainer: ${MAINTAINER_NAME} <${MAINTAINER_EMAIL}> + Build-Depends: debhelper-compat (= 13), cargo, rustc (>= 1.70) + Standards-Version: 4.6.0 + Homepage: https://github.com/hyperpolymath/bunsenite + Vcs-Browser: https://github.com/hyperpolymath/bunsenite + Vcs-Git: https://github.com/hyperpolymath/bunsenite.git + Rules-Requires-Root: no + + Package: bunsenite + Architecture: any + Depends: \${shlibs:Depends}, \${misc:Depends} + Description: Nickel configuration file parser with FFI bindings + Bunsenite is a Nickel configuration file parser with multi-language + FFI bindings. Features include parse, validate, watch mode, + interactive REPL, and JSON Schema validation. + EOF + + # debian/rules + cat > debian/rules << 'EOF' + #!/usr/bin/make -f + # SPDX-License-Identifier: MPL-2.0 + + export CARGO_HOME = $(CURDIR)/debian/cargo + export DEB_BUILD_MAINT_OPTIONS = hardening=+all + + %: + dh $@ + + override_dh_auto_build: + cargo build --release --features full + + override_dh_auto_install: + install -D -m 755 target/release/bunsenite debian/bunsenite/usr/bin/bunsenite + + override_dh_auto_test: + # Skip tests during package build + EOF + chmod +x debian/rules + + # debian/changelog + DATE=$(date -R) + cat > debian/changelog << EOF + bunsenite (${VERSION}-1) jammy; urgency=medium + + * New upstream release ${VERSION} + + -- ${MAINTAINER_NAME} <${MAINTAINER_EMAIL}> ${DATE} + EOF + + # debian/copyright + cat > debian/copyright << EOF + Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + Upstream-Name: bunsenite + Upstream-Contact: ${MAINTAINER_EMAIL} + Source: https://github.com/hyperpolymath/bunsenite + + Files: * + Copyright: 2024-2025 hyperpolymath + License: MIT or Palimpsest-0.8 + + License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + EOF + + # debian/source/format + echo "3.0 (quilt)" > debian/source/format + + # debian/compat + echo "13" > debian/compat + + - name: Build source package + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + cd bunsenite-${VERSION} + + # Build source package (signed) + debuild -S -sa -k"${{ secrets.LAUNCHPAD_GPG_PASSPHRASE }}" + + - name: Upload to PPA + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + + # Create dput config + cat > ~/.dput.cf << EOF + [ppa] + fqdn = ppa.launchpad.net + method = ftp + incoming = ~${PPA_NAME}/ubuntu/ + login = anonymous + allow_unsigned_uploads = 0 + EOF + + # Upload to PPA + dput ppa bunsenite_${VERSION}-1_source.changes diff --git a/vendor/bunsenite/.github/workflows/publish-flatpak.yml b/vendor/bunsenite/.github/workflows/publish-flatpak.yml new file mode 100644 index 0000000..8dec706 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-flatpak.yml @@ -0,0 +1,167 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Flatpak + +on: + repository_dispatch: + types: [publish-flatpak] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + APP_ID: dev.hyperpolymath.Bunsenite + +jobs: + update-flatpak: + name: Update Flatpak Manifest + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping Flatpak manifest update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Get commit hash for tag + if: steps.check-secret.outputs.skip != 'true' + id: commit + run: | + TAG="${{ steps.inputs.outputs.tag }}" + # Get the commit SHA for the tag + COMMIT=$(gh api repos/hyperpolymath/bunsenite/git/refs/tags/$TAG --jq '.object.sha' 2>/dev/null || echo "") + + # If it's an annotated tag, we need to dereference it + if [ -z "$COMMIT" ] || [ "$COMMIT" = "null" ]; then + COMMIT=$(gh api repos/hyperpolymath/bunsenite/git/refs/tags/$TAG --jq '.object.sha') + OBJ_TYPE=$(gh api repos/hyperpolymath/bunsenite/git/tags/$COMMIT --jq '.object.type' 2>/dev/null || echo "commit") + if [ "$OBJ_TYPE" = "commit" ]; then + COMMIT=$(gh api repos/hyperpolymath/bunsenite/git/tags/$COMMIT --jq '.object.sha') + fi + fi + + echo "sha=$COMMIT" >> $GITHUB_OUTPUT + echo "Found commit: $COMMIT for tag: $TAG" + env: + GH_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Update Flatpak manifest + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + COMMIT="${{ steps.commit.outputs.sha }}" + + mkdir -p flathub + + cat > flathub/${APP_ID}.yml << EOF + # SPDX-License-Identifier: MPL-2.0 + app-id: ${APP_ID} + runtime: org.freedesktop.Platform + runtime-version: '23.08' + sdk: org.freedesktop.Sdk + sdk-extensions: + - org.freedesktop.Sdk.Extension.rust-stable + + command: bunsenite + + finish-args: + - --filesystem=home:ro + - --filesystem=xdg-config:ro + + build-options: + append-path: /usr/lib/sdk/rust-stable/bin + env: + CARGO_HOME: /run/build/bunsenite/cargo + RUSTUP_HOME: /usr/lib/sdk/rust-stable + + modules: + - name: bunsenite + buildsystem: simple + build-commands: + - cargo build --release --features full + - install -Dm755 target/release/bunsenite /app/bin/bunsenite + sources: + - type: git + url: https://github.com/hyperpolymath/bunsenite.git + tag: ${TAG} + commit: ${COMMIT} + EOF + + # Create metainfo file + cat > flathub/${APP_ID}.metainfo.xml << EOF + + + + ${APP_ID} + Bunsenite + Nickel configuration file parser with multi-language FFI bindings + CC0-1.0 + MIT OR LicenseRef-Palimpsest-0.8 + +

+ Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + It provides parse, validate, watch mode, interactive REPL, and JSON Schema validation. +

+
+ https://github.com/hyperpolymath/bunsenite + https://github.com/hyperpolymath/bunsenite/issues + + bunsenite + + + + https://github.com/hyperpolymath/bunsenite/releases/tag/${TAG} + + + +
+ EOF + + echo "Generated Flatpak manifest:" + cat flathub/${APP_ID}.yml + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add flathub/ + git diff --staged --quiet || git commit -m "flatpak: bunsenite ${VERSION}" + git push diff --git a/vendor/bunsenite/.github/workflows/publish-homebrew.yml b/vendor/bunsenite/.github/workflows/publish-homebrew.yml new file mode 100644 index 0000000..a747eed --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-homebrew.yml @@ -0,0 +1,151 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Homebrew + +on: + repository_dispatch: + types: [publish-homebrew] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + sha_linux_x64: + description: 'SHA256 for Linux x64' + required: true + sha_linux_arm64: + description: 'SHA256 for Linux arm64' + required: true + sha_macos_x64: + description: 'SHA256 for macOS x64' + required: true + sha_macos_arm64: + description: 'SHA256 for macOS arm64' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + update-formula: + name: Update Homebrew Formula + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping Homebrew publish." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + echo "sha_linux_x64=${{ github.event.client_payload.sha_linux_x64 }}" >> $GITHUB_OUTPUT + echo "sha_linux_arm64=${{ github.event.client_payload.sha_linux_arm64 }}" >> $GITHUB_OUTPUT + echo "sha_macos_x64=${{ github.event.client_payload.sha_macos_x64 }}" >> $GITHUB_OUTPUT + echo "sha_macos_arm64=${{ github.event.client_payload.sha_macos_arm64 }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + echo "sha_linux_x64=${{ inputs.sha_linux_x64 }}" >> $GITHUB_OUTPUT + echo "sha_linux_arm64=${{ inputs.sha_linux_arm64 }}" >> $GITHUB_OUTPUT + echo "sha_macos_x64=${{ inputs.sha_macos_x64 }}" >> $GITHUB_OUTPUT + echo "sha_macos_arm64=${{ inputs.sha_macos_arm64 }}" >> $GITHUB_OUTPUT + fi + + - name: Update Formula + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + SHA_LINUX_X64="${{ steps.inputs.outputs.sha_linux_x64 }}" + SHA_LINUX_ARM64="${{ steps.inputs.outputs.sha_linux_arm64 }}" + SHA_MACOS_X64="${{ steps.inputs.outputs.sha_macos_x64 }}" + SHA_MACOS_ARM64="${{ steps.inputs.outputs.sha_macos_arm64 }}" + + mkdir -p Formula + + cat > Formula/bunsenite.rb << 'FORMULA_EOF' + # frozen_string_literal: true + # SPDX-License-Identifier: MPL-2.0 + + # Homebrew formula for bunsenite + class Bunsenite < Formula + desc "Nickel configuration file parser with multi-language FFI bindings" + homepage "https://github.com/hyperpolymath/bunsenite" + license any_of: ["MIT", "LicenseRef-Palimpsest-0.8"] + FORMULA_EOF + + cat >> Formula/bunsenite.rb << FORMULA_DYNAMIC + version "${VERSION}" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-aarch64-apple-darwin.tar.gz" + sha256 "${SHA_MACOS_ARM64}" + else + url "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-apple-darwin.tar.gz" + sha256 "${SHA_MACOS_X64}" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-aarch64-unknown-linux-gnu.tar.gz" + sha256 "${SHA_LINUX_ARM64}" + else + url "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-unknown-linux-gnu.tar.gz" + sha256 "${SHA_LINUX_X64}" + end + end + FORMULA_DYNAMIC + + cat >> Formula/bunsenite.rb << 'FORMULA_EOF' + + def install + bin.install "bunsenite" + # Install shared library if present + lib.install Dir["libbunsenite.*"] + end + + test do + assert_match version.to_s, shell_output("#{bin}/bunsenite --version") + end + end + FORMULA_EOF + + echo "Generated Formula/bunsenite.rb:" + cat Formula/bunsenite.rb + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/bunsenite.rb + git diff --staged --quiet || git commit -m "bunsenite: update to ${{ steps.inputs.outputs.tag }}" + git push diff --git a/vendor/bunsenite/.github/workflows/publish-macports.yml b/vendor/bunsenite/.github/workflows/publish-macports.yml new file mode 100644 index 0000000..0c0d6f8 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-macports.yml @@ -0,0 +1,243 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish MacPorts + +on: + repository_dispatch: + types: [publish-macports] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + update-tap: + name: Update MacPorts in Tap + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping MacPorts tap update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Download source and compute checksums + if: steps.check-secret.outputs.skip != 'true' + id: checksums + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + + # Download source tarball + curl -L -o source.tar.gz "https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz" + + # Compute checksums + SHA256=$(sha256sum source.tar.gz | awk '{print $1}') + SIZE=$(stat -c%s source.tar.gz) + + # Compute RIPEMD-160 using openssl + RMD160=$(openssl dgst -rmd160 source.tar.gz | awk '{print $2}') + + echo "sha256=$SHA256" >> $GITHUB_OUTPUT + echo "rmd160=$RMD160" >> $GITHUB_OUTPUT + echo "size=$SIZE" >> $GITHUB_OUTPUT + + echo "Computed checksums:" + echo " SHA256: $SHA256" + echo " RMD160: $RMD160" + echo " Size: $SIZE" + + - name: Update Portfile + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + SHA256="${{ steps.checksums.outputs.sha256 }}" + RMD160="${{ steps.checksums.outputs.rmd160 }}" + SIZE="${{ steps.checksums.outputs.size }}" + + mkdir -p macports/bunsenite + + cat > macports/bunsenite/Portfile << 'EOF' + # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + # SPDX-License-Identifier: MPL-2.0 + + PortSystem 1.0 + PortGroup cargo 1.0 + PortGroup github 1.0 + + EOF + + cat >> macports/bunsenite/Portfile << EOF + github.setup hyperpolymath bunsenite ${VERSION} v + revision 0 + categories devel + license MIT Permissive + maintainers {github.com:hyperpolymath @hyperpolymath} openmaintainer + description Nickel configuration file parser with FFI bindings + long_description Bunsenite is a Nickel configuration file parser with \\ + multi-language FFI bindings. Features include parse, \\ + validate, watch mode, interactive REPL, and JSON Schema validation. + + homepage https://github.com/hyperpolymath/bunsenite + + checksums rmd160 ${RMD160} \\ + sha256 ${SHA256} \\ + size ${SIZE} + + EOF + + cat >> macports/bunsenite/Portfile << 'EOF' + build.args-append --features=full + + destroot { + xinstall -m 755 ${worksrcpath}/target/[cargo.rust_platform]/release/bunsenite \ + ${destroot}${prefix}/bin/bunsenite + } + EOF + + echo "Generated Portfile:" + cat macports/bunsenite/Portfile + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add macports/ + git diff --staged --quiet || git commit -m "macports: bunsenite ${VERSION}" + git push + + create-official-pr: + name: Create MacPorts Official PR + runs-on: macos-latest + timeout-minutes: 15 + needs: update-tap + if: ${{ inputs.submit_official == true }} + steps: + - name: Check for MACPORTS_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.MACPORTS_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::MACPORTS_GITHUB_TOKEN not configured. Skipping official MacPorts PR." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Fork and update macports-ports + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + + # Fork macports-ports if not already forked + gh repo fork macports/macports-ports --clone=true --remote=true || true + cd macports-ports + + # Create branch + git checkout -b bunsenite-${VERSION} + + # Download source and compute checksums + curl -L -o source.tar.gz "https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz" + SHA256=$(shasum -a 256 source.tar.gz | awk '{print $1}') + RMD160=$(openssl dgst -rmd160 source.tar.gz | awk '{print $2}') + SIZE=$(stat -f%z source.tar.gz) + + # Create port directory + mkdir -p devel/bunsenite + + # Generate Portfile + cat > devel/bunsenite/Portfile << EOF + # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + + PortSystem 1.0 + PortGroup cargo 1.0 + PortGroup github 1.0 + + github.setup hyperpolymath bunsenite ${VERSION} v + revision 0 + categories devel + license MIT Permissive + maintainers {github.com:hyperpolymath @hyperpolymath} openmaintainer + description Nickel configuration file parser with FFI bindings + long_description Bunsenite is a Nickel configuration file parser with \\ + multi-language FFI bindings. + + homepage https://github.com/hyperpolymath/bunsenite + + checksums rmd160 ${RMD160} \\ + sha256 ${SHA256} \\ + size ${SIZE} + + build.args-append --features=full + + destroot { + xinstall -m 755 \${worksrcpath}/target/[cargo.rust_platform]/release/bunsenite \\ + \${destroot}\${prefix}/bin/bunsenite + } + EOF + + # Commit and push + git add devel/bunsenite/Portfile + git commit -m "bunsenite: new port, version ${VERSION}" + git push origin bunsenite-${VERSION} + + # Create PR + gh pr create \ + --title "bunsenite: new port, version ${VERSION}" \ + --body "New port for bunsenite - Nickel configuration file parser with multi-language FFI bindings. + + Homepage: https://github.com/hyperpolymath/bunsenite + License: MIT OR Palimpsest-0.8" \ + --repo macports/macports-ports + env: + GH_TOKEN: ${{ secrets.MACPORTS_GITHUB_TOKEN }} diff --git a/vendor/bunsenite/.github/workflows/publish-nixpkgs.yml b/vendor/bunsenite/.github/workflows/publish-nixpkgs.yml new file mode 100644 index 0000000..19798a3 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-nixpkgs.yml @@ -0,0 +1,203 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Nixpkgs + +on: + repository_dispatch: + types: [publish-nixpkgs] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + update-guix: + name: Update Guix Expression in Tap + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping Guix update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Install Guix + if: steps.check-secret.outputs.skip != 'true' + with: + nix_path: nixpkgs=channel:nixos-unstable + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Calculate source hash + if: steps.check-secret.outputs.skip != 'true' + id: hash + run: | + TAG="${{ steps.inputs.outputs.tag }}" + + # Use guix-prefetch-url to get the hash in SRI format + HASH=$(guix-prefetch-url --unpack "https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz" 2>/dev/null) + SRI_HASH=$(guix hash to-sri --type sha256 "$HASH") + + echo "hash=$SRI_HASH" >> $GITHUB_OUTPUT + echo "Calculated hash: $SRI_HASH" + + - name: Update Guix expression + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + HASH="${{ steps.hash.outputs.hash }}" + + mkdir -p guix + + # Create default.guix for the package + cat > guix/bunsenite.guix << EOF + # SPDX-License-Identifier: MPL-2.0 + { lib + , rustPlatform + , fetchFromGitHub + }: + + rustPlatform.buildRustPackage rec { + pname = "bunsenite"; + version = "${VERSION}"; + + src = fetchFromGitHub { + owner = "hyperpolymath"; + repo = "bunsenite"; + rev = "${TAG}"; + hash = "${HASH}"; + }; + + cargoLock = { + lockFile = "\${src}/Cargo.lock"; + }; + + buildFeatures = [ "full" ]; + + meta = with lib; { + description = "Nickel configuration file parser with multi-language FFI bindings"; + homepage = "https://github.com/hyperpolymath/bunsenite"; + license = with licenses; [ mit /* Palimpsest-0.8 */ ]; + maintainers = [ ]; + mainProgram = "bunsenite"; + }; + } + EOF + + # Create flake.guix for standalone use + cat > guix/flake.guix << EOF + # SPDX-License-Identifier: MPL-2.0 + { + description = "Bunsenite - Nickel configuration file parser with FFI bindings"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.\${system}; + bunsenite = pkgs.callPackage ./bunsenite.guix { }; + in + { + packages = { + default = bunsenite; + bunsenite = bunsenite; + }; + + apps.default = flake-utils.lib.mkApp { + drv = bunsenite; + }; + + devShells.default = pkgs.mkShell { + buildInputs = [ bunsenite ]; + }; + } + ); + } + EOF + + # Create overlay for use in other flakes + cat > guix/overlay.guix << EOF + # SPDX-License-Identifier: MPL-2.0 + final: prev: { + bunsenite = final.callPackage ./bunsenite.guix { }; + } + EOF + + echo "Generated Guix files:" + cat guix/bunsenite.guix + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add guix/ + git diff --staged --quiet || git commit -m "guix: bunsenite ${VERSION}" + git push + + create-nixpkgs-pr: + name: Create nixpkgs PR (Optional) + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: update-guix + if: ${{ inputs.submit_nixpkgs == true }} + steps: + - name: Check for NIXPKGS_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.NIXPKGS_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::NIXPKGS_GITHUB_TOKEN not configured. Skipping nixpkgs PR." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Create nixpkgs PR + if: steps.check-secret.outputs.skip != 'true' + run: | + echo "To submit to nixpkgs:" + echo "1. Fork NixOS/nixpkgs" + echo "2. Add bunsenite.guix to pkgs/by-name/bu/bunsenite/package.guix" + echo "3. Create PR with title: bunsenite: init at ${VERSION}" + echo "" + echo "This requires manual review by nixpkgs maintainers." diff --git a/vendor/bunsenite/.github/workflows/publish-obs.yml b/vendor/bunsenite/.github/workflows/publish-obs.yml new file mode 100644 index 0000000..2bdacd9 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-obs.yml @@ -0,0 +1,185 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish OBS (openSUSE) + +on: + repository_dispatch: + types: [publish-obs] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + OBS_PROJECT: home:hyperpolymath + OBS_PACKAGE: bunsenite + +jobs: + update-spec: + name: Update OBS Spec in Tap + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping OBS spec update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + fi + + - name: Update OBS spec + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + DATE=$(date "+%a %b %d %Y") + + mkdir -p obs + + # Create spec file for OBS (openSUSE Build Service) + cat > obs/bunsenite.spec << EOF + # SPDX-License-Identifier: MPL-2.0 + # + # spec file for package bunsenite + # + # Copyright (c) 2024-2025 hyperpolymath + # + + Name: bunsenite + Version: ${VERSION} + Release: 1%{?dist} + Summary: Nickel configuration file parser with multi-language FFI bindings + + License: MIT OR Palimpsest-0.8 + URL: https://github.com/hyperpolymath/bunsenite + Source0: https://github.com/hyperpolymath/bunsenite/archive/refs/tags/${TAG}.tar.gz#/bunsenite-%{version}.tar.gz + + BuildRequires: cargo + BuildRequires: rust >= 1.70 + + %description + Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + Features include parse, validate, watch mode, interactive REPL, and JSON Schema validation. + + %prep + %autosetup -n bunsenite-%{version} + + %build + cargo build --release --features full + + %install + install -D -m 755 target/release/bunsenite %{buildroot}%{_bindir}/bunsenite + + %files + %license LICENSE.txt + %doc README.adoc + %{_bindir}/bunsenite + + %changelog + * ${DATE} hyperpolymath - ${VERSION}-1 + - Update to version ${VERSION} + EOF + + # Create _service file for OBS source service + cat > obs/_service << EOF + + + https://github.com/hyperpolymath/bunsenite.git + git + ${TAG} + @PARENT_TAG@ + + + + gz + *.tar + + + + EOF + + echo "Generated OBS spec:" + cat obs/bunsenite.spec + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add obs/ + git diff --staged --quiet || git commit -m "obs: bunsenite ${VERSION}" + git push + + trigger-obs: + name: Trigger OBS Build + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: update-spec + steps: + - name: Check for OBS credentials + id: check-secret + run: | + if [ -z "${{ secrets.OBS_USERNAME }}" ] || [ -z "${{ secrets.OBS_PASSWORD }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::OBS credentials not configured. Skipping OBS trigger." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Install osc + if: steps.check-secret.outputs.skip != 'true' + run: | + sudo apt-get update + sudo apt-get install -y osc + + - name: Configure osc + if: steps.check-secret.outputs.skip != 'true' + run: | + mkdir -p ~/.config/osc + cat > ~/.config/osc/oscrc << EOF + [general] + apiurl = https://api.opensuse.org + + [https://api.opensuse.org] + user = ${{ secrets.OBS_USERNAME }} + pass = ${{ secrets.OBS_PASSWORD }} + EOF + + - name: Trigger rebuild + if: steps.check-secret.outputs.skip != 'true' + run: | + osc api -X POST "/trigger/runservice?project=${OBS_PROJECT}&package=${OBS_PACKAGE}" || true + echo "OBS build triggered for ${OBS_PROJECT}/${OBS_PACKAGE}" diff --git a/vendor/bunsenite/.github/workflows/publish-packages.yml b/vendor/bunsenite/.github/workflows/publish-packages.yml new file mode 100644 index 0000000..96bd208 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-packages.yml @@ -0,0 +1,268 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Packages + +on: + workflow_run: + workflows: ["Release"] + types: [completed] + branches: [main] + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v1.0.2)' + required: true + +permissions: + contents: read + +jobs: + extract-release-info: + name: Extract Release Info + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + outputs: + version: ${{ steps.info.outputs.version }} + tag: ${{ steps.info.outputs.tag }} + sha_linux_x64: ${{ steps.info.outputs.sha_linux_x64 }} + sha_linux_arm64: ${{ steps.info.outputs.sha_linux_arm64 }} + sha_macos_x64: ${{ steps.info.outputs.sha_macos_x64 }} + sha_macos_arm64: ${{ steps.info.outputs.sha_macos_arm64 }} + sha_windows_x64: ${{ steps.info.outputs.sha_windows_x64 }} + steps: + - name: Determine tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + else + # Get tag from the release workflow run + TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name') + echo "tag=$TAG" >> $GITHUB_OUTPUT + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract release info + id: info + run: | + TAG="${{ steps.tag.outputs.tag }}" + VERSION="${TAG#v}" + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Download SHA256SUMS.txt from release + gh release download "$TAG" --pattern "SHA256SUMS.txt" --repo ${{ github.repository }} + + # Parse hashes - format is: SHA256 ./bunsenite-{target}/bunsenite-{tag}-{target}.{ext} + echo "sha_linux_x64=$(grep 'x86_64-unknown-linux-gnu' SHA256SUMS.txt | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "sha_linux_arm64=$(grep 'aarch64-unknown-linux-gnu' SHA256SUMS.txt | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "sha_macos_x64=$(grep 'x86_64-apple-darwin' SHA256SUMS.txt | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "sha_macos_arm64=$(grep 'aarch64-apple-darwin' SHA256SUMS.txt | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "sha_windows_x64=$(grep 'x86_64-pc-windows-msvc' SHA256SUMS.txt | awk '{print $1}')" >> $GITHUB_OUTPUT + + # Debug output + echo "Extracted version: $VERSION" + echo "Extracted tag: $TAG" + cat SHA256SUMS.txt + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + dispatch-homebrew: + name: Dispatch Homebrew + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Homebrew publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-homebrew + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}", + "sha_linux_x64": "${{ needs.extract-release-info.outputs.sha_linux_x64 }}", + "sha_linux_arm64": "${{ needs.extract-release-info.outputs.sha_linux_arm64 }}", + "sha_macos_x64": "${{ needs.extract-release-info.outputs.sha_macos_x64 }}", + "sha_macos_arm64": "${{ needs.extract-release-info.outputs.sha_macos_arm64 }}" + } + + dispatch-scoop: + name: Dispatch Scoop + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Scoop publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-scoop + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}", + "sha_windows_x64": "${{ needs.extract-release-info.outputs.sha_windows_x64 }}" + } + + dispatch-aur: + name: Dispatch AUR + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger AUR publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-aur + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}", + "sha_linux_x64": "${{ needs.extract-release-info.outputs.sha_linux_x64 }}", + "sha_linux_arm64": "${{ needs.extract-release-info.outputs.sha_linux_arm64 }}" + } + + dispatch-winget: + name: Dispatch WinGet + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger WinGet publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-winget + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}", + "sha_windows_x64": "${{ needs.extract-release-info.outputs.sha_windows_x64 }}" + } + + dispatch-chocolatey: + name: Dispatch Chocolatey + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Chocolatey publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-chocolatey + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}", + "sha_windows_x64": "${{ needs.extract-release-info.outputs.sha_windows_x64 }}" + } + + dispatch-flatpak: + name: Dispatch Flatpak + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Flatpak publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-flatpak + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } + + dispatch-macports: + name: Dispatch MacPorts + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger MacPorts publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-macports + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } + + dispatch-debian-ppa: + name: Dispatch Debian PPA + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Debian PPA publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-debian-ppa + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } + + dispatch-copr: + name: Dispatch COPR + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger COPR publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-copr + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } + + dispatch-obs: + name: Dispatch OBS (openSUSE) + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger OBS publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-obs + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } + + dispatch-nixpkgs: + name: Dispatch Nixpkgs + needs: extract-release-info + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Trigger Nixpkgs publisher + uses: peter-evans/repository-dispatch@v3.0.0 + with: + token: ${{ secrets.TAP_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + event-type: publish-nixpkgs + client-payload: >- + { + "version": "${{ needs.extract-release-info.outputs.version }}", + "tag": "${{ needs.extract-release-info.outputs.tag }}" + } diff --git a/vendor/bunsenite/.github/workflows/publish-scoop.yml b/vendor/bunsenite/.github/workflows/publish-scoop.yml new file mode 100644 index 0000000..f017781 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-scoop.yml @@ -0,0 +1,110 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish Scoop + +on: + repository_dispatch: + types: [publish-scoop] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + sha_windows_x64: + description: 'SHA256 for Windows x64' + required: true + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + update-bucket: + name: Update Scoop Bucket + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping Scoop publish." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + echo "sha_windows_x64=${{ github.event.client_payload.sha_windows_x64 }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + echo "sha_windows_x64=${{ inputs.sha_windows_x64 }}" >> $GITHUB_OUTPUT + fi + + - name: Update manifest + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + SHA="${{ steps.inputs.outputs.sha_windows_x64 }}" + + mkdir -p bucket + + cat > bucket/bunsenite.json << EOF + { + "version": "${VERSION}", + "description": "Nickel configuration file parser with multi-language FFI bindings", + "homepage": "https://github.com/hyperpolymath/bunsenite", + "license": "MIT|Palimpsest-0.8", + "architecture": { + "64bit": { + "url": "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-pc-windows-msvc.zip", + "hash": "${SHA}", + "bin": "bunsenite.exe" + } + }, + "checkver": { + "github": "https://github.com/hyperpolymath/bunsenite" + }, + "autoupdate": { + "architecture": { + "64bit": { + "url": "https://github.com/hyperpolymath/bunsenite/releases/download/v\$version/bunsenite-v\$version-x86_64-pc-windows-msvc.zip" + } + } + } + } + EOF + + echo "Generated bucket/bunsenite.json:" + cat bucket/bunsenite.json + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add bucket/bunsenite.json + git diff --staged --quiet || git commit -m "scoop: bunsenite ${{ steps.inputs.outputs.tag }}" + git push diff --git a/vendor/bunsenite/.github/workflows/publish-winget.yml b/vendor/bunsenite/.github/workflows/publish-winget.yml new file mode 100644 index 0000000..22cac22 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/publish-winget.yml @@ -0,0 +1,180 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Publish WinGet + +on: + repository_dispatch: + types: [publish-winget] + workflow_dispatch: + inputs: + version: + description: 'Version (e.g., 1.0.2)' + required: true + tag: + description: 'Git tag (e.g., v1.0.2)' + required: true + sha_windows_x64: + description: 'SHA256 for Windows x64' + required: true + submit_official: + description: 'Submit to official winget-pkgs' + type: boolean + default: false + +permissions: + contents: read + +env: + TAP_REPO: hyperpolymath/homebrew-tap + +jobs: + update-tap: + name: Update WinGet in Tap + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check for TAP_GITHUB_TOKEN + id: check-secret + run: | + if [ -z "${{ secrets.TAP_GITHUB_TOKEN }}" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::TAP_GITHUB_TOKEN not configured. Skipping WinGet tap update." + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Checkout homebrew-tap + if: steps.check-secret.outputs.skip != 'true' + uses: actions/checkout@v6.0.1 + with: + repository: ${{ env.TAP_REPO }} + token: ${{ secrets.TAP_GITHUB_TOKEN }} + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + echo "sha_windows_x64=${{ github.event.client_payload.sha_windows_x64 }}" >> $GITHUB_OUTPUT + else + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + echo "sha_windows_x64=${{ inputs.sha_windows_x64 }}" >> $GITHUB_OUTPUT + fi + + - name: Update WinGet manifest + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + TAG="${{ steps.inputs.outputs.tag }}" + SHA="${{ steps.inputs.outputs.sha_windows_x64 }}" + + mkdir -p "winget/Hyperpolymath.Bunsenite/${VERSION}" + + cat > "winget/Hyperpolymath.Bunsenite/${VERSION}/Hyperpolymath.Bunsenite.yaml" << EOF + # yaml-language-server: \$schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + # SPDX-License-Identifier: MPL-2.0 + PackageIdentifier: Hyperpolymath.Bunsenite + PackageVersion: ${VERSION} + PackageLocale: en-US + Publisher: hyperpolymath + PublisherUrl: https://github.com/hyperpolymath + PublisherSupportUrl: https://github.com/hyperpolymath/bunsenite/issues + PackageName: Bunsenite + PackageUrl: https://github.com/hyperpolymath/bunsenite + License: MIT OR Palimpsest-0.8 + LicenseUrl: https://github.com/hyperpolymath/bunsenite/blob/main/LICENSE.txt + ShortDescription: Nickel configuration file parser with multi-language FFI bindings + Description: | + Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + Features include parse, validate, watch mode, interactive REPL, and JSON Schema validation. + Tags: + - nickel + - config + - configuration + - parser + - rust + - cli + Moniker: bunsenite + Commands: + - bunsenite + ReleaseNotesUrl: https://github.com/hyperpolymath/bunsenite/releases/tag/${TAG} + Installers: + - Architecture: x64 + InstallerType: zip + InstallerUrl: https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-pc-windows-msvc.zip + InstallerSha256: ${SHA} + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: bunsenite.exe + PortableCommandAlias: bunsenite + ManifestType: singleton + ManifestVersion: 1.6.0 + EOF + + echo "Generated WinGet manifest:" + cat "winget/Hyperpolymath.Bunsenite/${VERSION}/Hyperpolymath.Bunsenite.yaml" + + - name: Commit and push + if: steps.check-secret.outputs.skip != 'true' + run: | + VERSION="${{ steps.inputs.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add "winget/Hyperpolymath.Bunsenite/${VERSION}/" + git diff --staged --quiet || git commit -m "winget: bunsenite ${VERSION}" + git push + + submit-official: + name: Submit to Official WinGet + runs-on: windows-latest + timeout-minutes: 15 + if: ${{ inputs.submit_official == true }} + steps: + - name: Check for WINGET_GITHUB_TOKEN + id: check-secret + shell: pwsh + run: | + if ([string]::IsNullOrEmpty("${{ secrets.WINGET_GITHUB_TOKEN }}")) { + echo "skip=true" >> $env:GITHUB_OUTPUT + Write-Warning "WINGET_GITHUB_TOKEN not configured. Skipping official WinGet submission." + } else { + echo "skip=false" >> $env:GITHUB_OUTPUT + } + + - name: Get inputs + if: steps.check-secret.outputs.skip != 'true' + id: inputs + shell: pwsh + run: | + if ("${{ github.event_name }}" -eq "repository_dispatch") { + echo "version=${{ github.event.client_payload.version }}" >> $env:GITHUB_OUTPUT + echo "tag=${{ github.event.client_payload.tag }}" >> $env:GITHUB_OUTPUT + } else { + echo "version=${{ inputs.version }}" >> $env:GITHUB_OUTPUT + echo "tag=${{ inputs.tag }}" >> $env:GITHUB_OUTPUT + } + + - name: Install wingetcreate + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + Invoke-WebRequest -Uri https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe + + - name: Submit to winget-pkgs + if: steps.check-secret.outputs.skip != 'true' + shell: pwsh + run: | + $VERSION = "${{ steps.inputs.outputs.version }}" + $TAG = "${{ steps.inputs.outputs.tag }}" + $URL = "https://github.com/hyperpolymath/bunsenite/releases/download/${TAG}/bunsenite-${TAG}-x86_64-pc-windows-msvc.zip" + + ./wingetcreate.exe update Hyperpolymath.Bunsenite ` + --urls $URL ` + --version $VERSION ` + --token ${{ secrets.WINGET_GITHUB_TOKEN }} ` + --submit diff --git a/vendor/bunsenite/.github/workflows/push-email-notify.yml b/vendor/bunsenite/.github/workflows/push-email-notify.yml new file mode 100644 index 0000000..587979f --- /dev/null +++ b/vendor/bunsenite/.github/workflows/push-email-notify.yml @@ -0,0 +1,36 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Dormant push-email notification. ARMED by setting the repo variable +# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; +# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by +# new repos from the template; placed on existing repos by the farm sweep. +name: Push email notification +on: + push: {} +permissions: + contents: read +jobs: + notify: + name: Email on push + if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Send push notification email + uses: dawidd6/action-send-mail@v3.12.0 + with: + server_address: ${{ secrets.SMTP_HOST }} + server_port: ${{ secrets.SMTP_PORT }} + secure: true + username: ${{ secrets.SMTP_USER }} + password: ${{ secrets.SMTP_PASS }} + from: "GitHub Push <${{ secrets.SMTP_USER }}>" + to: "jonathan.jewell@gmail.com j.d.a.jewell@open.ac.uk" + subject: "[${{ github.repository }}] push to ${{ github.ref_name }} by ${{ github.actor }}" + body: | + Repository: ${{ github.repository }} + Branch: ${{ github.ref_name }} + Pusher: ${{ github.actor }} + Compare: ${{ github.event.compare }} + Head msg: ${{ github.event.head_commit.message }} diff --git a/vendor/bunsenite/.github/workflows/release.yml b/vendor/bunsenite/.github/workflows/release.yml new file mode 100644 index 0000000..98e3dea --- /dev/null +++ b/vendor/bunsenite/.github/workflows/release.yml @@ -0,0 +1,256 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 1.0.0)' + required: true + +env: + CARGO_TERM_COLOR: always + + +permissions: + contents: read + +jobs: + build: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + artifact: bunsenite + archive: tar.gz + # Linux aarch64 + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + artifact: bunsenite + archive: tar.gz + cross: true + # macOS x86_64 (use macos-15-intel for Intel-based runner) + - target: x86_64-apple-darwin + os: macos-15-intel + artifact: bunsenite + archive: tar.gz + # macOS aarch64 (Apple Silicon) + - target: aarch64-apple-darwin + os: macos-latest + artifact: bunsenite + archive: tar.gz + # Windows x86_64 + - target: x86_64-pc-windows-msvc + os: windows-latest + artifact: bunsenite.exe + archive: zip + + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@v1 + with: + targets: ${{ matrix.target }} + + - name: Install cross (for cross-compilation) + if: matrix.cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2.2.1 + with: + version: 0.11.0 + + - name: Build Rust (native) + if: ${{ !matrix.cross }} + run: cargo build --release --features full --target ${{ matrix.target }} + + - name: Build Rust (cross) + if: matrix.cross + run: cross build --release --features full --target ${{ matrix.target }} + + - name: Prepare Rust library for Zig (Unix) + if: runner.os != 'Windows' + run: | + mkdir -p target/release + cp target/${{ matrix.target }}/release/libbunsenite.* target/release/ || true + + - name: Prepare Rust library for Zig (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path target/release + Copy-Item target/${{ matrix.target }}/release/bunsenite.* target/release/ -ErrorAction SilentlyContinue + + - name: Build Zig FFI (Unix) + if: runner.os != 'Windows' && !matrix.cross + run: cd zig && zig build -Doptimize=ReleaseFast + + # Skip Zig FFI on Windows - requires import library setup + # - name: Build Zig FFI (Windows) + # if: runner.os == 'Windows' + # run: cd zig && zig build -Doptimize=ReleaseFast + + - name: Prepare archive (Unix) + if: runner.os != 'Windows' + run: | + mkdir -p dist + cp target/${{ matrix.target }}/release/${{ matrix.artifact }} dist/ + cp zig/zig-out/lib/libbunsenite.* dist/ || true + cp README.adoc LICENSE.txt dist/ + cd dist && tar -czvf ../bunsenite-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }} * + + - name: Prepare archive (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path dist + Copy-Item target/${{ matrix.target }}/release/${{ matrix.artifact }} dist/ + Copy-Item zig/zig-out/lib/bunsenite.* dist/ -ErrorAction SilentlyContinue + Copy-Item README.adoc,LICENSE.txt dist/ + Compress-Archive -Path dist/* -DestinationPath bunsenite-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }} + + - name: Upload artifact + uses: actions/upload-artifact@v4.6.2 + with: + name: bunsenite-${{ matrix.target }} + path: bunsenite-*.${{ matrix.archive }} + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + id-token: write + attestations: write + + steps: + - uses: actions/checkout@v6.0.1 + + - name: Download all artifacts + uses: actions/download-artifact@v4.1.8 + with: + path: artifacts + + - name: List artifacts + run: find artifacts -type f + + - name: Create checksums + run: | + cd artifacts + find . -name "bunsenite-*" -type f -exec sha256sum {} \; > ../SHA256SUMS.txt + cat ../SHA256SUMS.txt + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2.2.1 + with: + files: | + artifacts/**/* + SHA256SUMS.txt + draft: false + prerelease: false + generate_release_notes: true + body: | + ## Bunsenite ${{ github.ref_name }} + + Nickel configuration file parser with multi-language FFI bindings. + + ### Installation + + **Cargo (Rust):** + ```bash + cargo install bunsenite + ``` + + **Homebrew (macOS):** + ```bash + brew install bunsenite + ``` + + **Download binaries:** + See assets below for pre-built binaries. + + ### Features + - Parse and evaluate Nickel configuration files + - Watch mode for live reloading + - Interactive REPL + - JSON Schema validation + - FFI bindings for Deno, AffineScript, Node.js + + RSR Compliance: Bronze Tier | TPCF Perimeter: 3 + + - name: Attest build provenance + uses: actions/attest-build-provenance@v2.4.0 + with: + subject-path: | + artifacts/**/* + SHA256SUMS.txt + + publish-crates: + name: Publish to crates.io + needs: release + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@v1 + + - name: Package crate + run: cargo package + + - name: Attest crate provenance + uses: actions/attest-build-provenance@v2.4.0 + with: + subject-path: 'target/package/*.crate' + + - name: Publish to crates.io + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --no-verify + continue-on-error: true + + publish-npm: + name: Publish to npm + needs: release + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v6.0.1 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.2 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Publish to npm + working-directory: bindings/affinescript + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public + continue-on-error: true diff --git a/vendor/bunsenite/.github/workflows/rust-ci.yml b/vendor/bunsenite/.github/workflows/rust-ci.yml new file mode 100644 index 0000000..a7174f5 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/rust-ci.yml @@ -0,0 +1,24 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Rust CI — thin wrapper calling the shared estate reusable in +# hyperpolymath/standards. Configure once, propagate everywhere. +# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. +name: Rust CI + +on: + push: + branches: [main, master] + pull_request: + +permissions: + actions: read + contents: read + +jobs: + rust-ci: + uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a + with: + enable_audit: true + enable_coverage: true diff --git a/vendor/bunsenite/.github/workflows/scorecard.yml b/vendor/bunsenite/.github/workflows/scorecard.yml new file mode 100644 index 0000000..d78f4b1 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/scorecard.yml @@ -0,0 +1,22 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: OSSF Scorecard + +on: + schedule: + - cron: '0 4 * * *' + workflow_dispatch: + +permissions: + actions: read + contents: read + +jobs: + scorecard: + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 + permissions: + contents: read + security-events: write + id-token: write diff --git a/vendor/bunsenite/.github/workflows/secret-scanner.yml b/vendor/bunsenite/.github/workflows/secret-scanner.yml new file mode 100644 index 0000000..fffde78 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/secret-scanner.yml @@ -0,0 +1,25 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Secret Scanner + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: read + contents: read + +jobs: + scan: + permissions: + contents: read + uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a + secrets: inherit diff --git a/vendor/bunsenite/.github/workflows/stress-test.yml b/vendor/bunsenite/.github/workflows/stress-test.yml new file mode 100644 index 0000000..3113626 --- /dev/null +++ b/vendor/bunsenite/.github/workflows/stress-test.yml @@ -0,0 +1,56 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Stress Testing +on: + schedule: + - cron: '0 3 * * 1' # Weekly Monday 3am UTC + workflow_dispatch: +permissions: + contents: read +jobs: + stress-test: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@v1 + + - name: Install stress testing tools + run: | + sudo apt-get update + sudo apt-get install -y stress-ng valgrind + + - name: Build release + run: cargo build --release --all-features + + - name: Concurrent operations stress test + run: | + # Run binary with high concurrency + for i in {1..100}; do + timeout 1s ./target/release/* & + done + wait + + - name: Memory pressure test + run: | + # Run under memory constraints + ulimit -v 512000 # 500MB virtual memory limit + cargo test --release + + - name: Long-running scenario test + run: | + # Test for memory leaks over time + timeout 300s valgrind --leak-check=full --error-exitcode=1 \ + ./target/release/* || true + + - name: Stress test with stress-ng + run: | + # CPU and I/O stress + stress-ng --cpu 4 --io 2 --timeout 60s & + STRESS_PID=$! + cargo test --release + kill $STRESS_PID || true diff --git a/vendor/bunsenite/.github/workflows/workflow-linter.yml b/vendor/bunsenite/.github/workflows/workflow-linter.yml new file mode 100644 index 0000000..c31ac4d --- /dev/null +++ b/vendor/bunsenite/.github/workflows/workflow-linter.yml @@ -0,0 +1,58 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +# Prevention workflow - validates all workflows have proper security config +name: Workflow Security Linter + +on: + pull_request: + paths: + - '.github/workflows/**' + push: + paths: + - '.github/workflows/**' + +permissions: read-all + +jobs: + lint-workflows: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4.1.1 + + - name: Check SPDX headers + run: | + errors=0 + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] || continue + if ! head -1 "$f" | grep -q "SPDX-License-Identifier"; then + echo "ERROR: $f missing SPDX header" + errors=$((errors + 1)) + fi + done + exit $errors + + - name: Check permissions declaration + run: | + errors=0 + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] || continue + if ! grep -q "^permissions:" "$f"; then + echo "ERROR: $f missing permissions declaration" + errors=$((errors + 1)) + fi + done + exit $errors + + - name: Check pinned actions + run: | + errors=0 + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] || continue + # Look for uses: without SHA + if grep -E "uses:.*@v[0-9]" "$f" | grep -v "#"; then + echo "WARNING: $f has unpinned actions (missing SHA comment)" + fi + done diff --git a/vendor/bunsenite/.github/workflows/zig-ffi.yml b/vendor/bunsenite/.github/workflows/zig-ffi.yml new file mode 100644 index 0000000..b09e96b --- /dev/null +++ b/vendor/bunsenite/.github/workflows/zig-ffi.yml @@ -0,0 +1,149 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. +name: Zig FFI Build + +on: + push: + branches: [main, master] + paths: + - 'zig/**' + - 'src/ffi.rs' + - 'Cargo.toml' + pull_request: + paths: + - 'zig/**' + - 'src/ffi.rs' + - 'Cargo.toml' + +env: + CARGO_TERM_COLOR: always + + +permissions: + contents: read + +jobs: + build-ffi: + name: Build FFI (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + permissions: + contents: read + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + include: + - os: ubuntu-latest + lib_ext: so + - os: macos-latest + lib_ext: dylib + - os: windows-latest + lib_ext: dll + + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@v1 + with: + components: rustfmt, clippy + + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2.2.1 + with: + version: 0.11.0 + + - name: Cache Cargo + uses: actions/cache@v4.3.0 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Build Rust library + run: cargo build --release + + - name: Build Zig FFI layer + working-directory: zig + run: zig build -Doptimize=ReleaseFast + + - name: Verify FFI exports (Linux) + if: runner.os == 'Linux' + run: | + echo "=== Checking exported symbols ===" + nm -D zig/zig-out/lib/libbunsenite.so | grep -E "parse_nickel|validate_nickel|free_string|version|rsr_tier|tpcf_perimeter" || true + + - name: Upload FFI library + uses: actions/upload-artifact@v4.6.2 + with: + name: libbunsenite-${{ matrix.os }} + path: | + zig/zig-out/lib/libbunsenite.${{ matrix.lib_ext }} + target/release/libbunsenite.${{ matrix.lib_ext }} + + test-ffi: + name: Test FFI + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: build-ffi + permissions: + contents: read + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@v1 + + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2.2.1 + with: + version: 0.11.0 + + - name: Build Rust library + run: cargo build --release + + - name: Run Rust FFI tests + run: cargo test ffi --release + + - name: Run Zig tests + working-directory: zig + run: zig build test + + test-deno-bindings: + name: Test Deno Bindings + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: build-ffi + permissions: + contents: read + steps: + - uses: actions/checkout@v6.0.1 + + - name: Install Rust + uses: dtolnay/rust-toolchain@v1 + + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2.2.1 + with: + version: 0.11.0 + + - name: Install Deno + uses: denoland/setup-deno@v1.5.2 + with: + deno-version: v1.x + + - name: Build FFI libraries + run: | + cargo build --release + cd zig && zig build -Doptimize=ReleaseFast + + - name: Copy library for Deno + run: cp zig/zig-out/lib/libbunsenite.so bindings/deno/ + + - name: Test Deno bindings + working-directory: bindings/deno + run: deno run --allow-ffi --allow-read example.ts || echo "Deno test completed" diff --git a/vendor/bunsenite/.gitignore b/vendor/bunsenite/.gitignore new file mode 100644 index 0000000..73f3573 --- /dev/null +++ b/vendor/bunsenite/.gitignore @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: MPL-2.0 +# RSR-compliant .gitignore + +# OS & Editor +.DS_Store +Thumbs.db +*.swp +*.swo +*~ +.idea/ +.vscode/ + +# Build +/target/ +/_build/ +/build/ +/dist/ +/out/ + +# Dependencies +/node_modules/ +/vendor/ +/deps/ +/.elixir_ls/ + +# Rust +# Cargo.lock # Keep for binaries + +# Elixir +/cover/ +/doc/ +*.ez +erl_crash.dump + +# Julia +*.jl.cov +*.jl.mem +/Manifest.toml + +# ReScript +/lib/bs/ +/.bsb.lock + +# Python (SaltStack only) +__pycache__/ +*.py[cod] +.venv/ + +# Ada/SPARK +*.ali +/obj/ +/bin/ + +# Haskell +/.stack-work/ +/dist-newstyle/ + +# Chapel +*.chpl.tmp.* + +# Secrets +.env +.env.* +*.pem +*.key +secrets/ + +# Test/Coverage +/coverage/ +htmlcov/ + +# Logs +*.log +/logs/ + +# Temp +/tmp/ +*.tmp +*.bak + +# Crash recovery artifacts +ai-cli-crash-capture/ +target/ +node_modules/ +_build/ +deps/ +.elixir_ls/ +.cache/ +build/ +dist/ diff --git a/vendor/bunsenite/.gitlab-ci.yml b/vendor/bunsenite/.gitlab-ci.yml new file mode 100644 index 0000000..f2c4f3b --- /dev/null +++ b/vendor/bunsenite/.gitlab-ci.yml @@ -0,0 +1,325 @@ +# Bunsenite GitLab CI/CD Pipeline +# Automated testing, building, and deployment + +# Stages define the order of execution +stages: + - check # Code quality checks + - test # Run tests + - build # Build artifacts + - security # Security scanning + - deploy # Deployment (crates.io, releases) + +# Global variables +variables: + CARGO_HOME: $CI_PROJECT_DIR/.cargo + RUST_BACKTRACE: "1" + +# Cache dependencies between jobs +cache: + paths: + - .cargo/ + - target/ + +# === Check Stage === + +# Format check +fmt: + stage: check + image: rust:latest + script: + - rustup component add rustfmt + - cargo fmt --all -- --check + allow_failure: false + +# Clippy linter +clippy: + stage: check + image: rust:latest + script: + - rustup component add clippy + - cargo clippy --all-targets --all-features -- -D warnings + allow_failure: false + +# Check for unsafe code +unsafe-check: + stage: check + image: rust:latest + script: + - | + if grep -r "unsafe" src/; then + echo "ERROR: Found unsafe code! Bunsenite must have zero unsafe blocks." + exit 1 + fi + echo "✓ No unsafe code found" + allow_failure: false + +# Verify RSR Bronze compliance +rsr-compliance: + stage: check + image: rust:latest + before_script: + - apt-get update && apt-get install -y jq + script: + - | + echo "Checking RSR Bronze Tier compliance..." + + # Check for required files + for file in README.md LICENSE SECURITY.md CONTRIBUTING.md CODE_OF_CONDUCT.md MAINTAINERS.md CHANGELOG.md; do + if [ ! -f "$file" ]; then + echo "ERROR: Missing required file: $file" + exit 1 + fi + done + + # Check .well-known/ directory + for file in .well-known/security.txt .well-known/ai.txt .well-known/humans.txt; do + if [ ! -f "$file" ]; then + echo "ERROR: Missing required file: $file" + exit 1 + fi + done + + # Check for network dependencies + if grep -E "reqwest|hyper|curl" Cargo.toml; then + echo "ERROR: Found network dependencies (violates offline-first)" + exit 1 + fi + + echo "✓ RSR Bronze Tier compliance verified" + allow_failure: false + +# === Test Stage === + +# Run tests on stable Rust +test:stable: + stage: test + image: rust:latest + script: + - cargo test --all-features --verbose + coverage: '/^\d+\.\d+% coverage/' + artifacts: + reports: + junit: target/junit.xml + +# Run tests on nightly Rust (informational only) +test:nightly: + stage: test + image: rustlang/rust:nightly + script: + - cargo test --all-features --verbose + allow_failure: true + +# Run tests with minimum supported Rust version (MSRV) +test:msrv: + stage: test + image: rust:1.70 # Match rust-version in Cargo.toml + script: + - cargo test --all-features --verbose + allow_failure: false + +# Test documentation examples +test:doc: + stage: test + image: rust:latest + script: + - cargo test --doc --verbose + allow_failure: false + +# Code coverage (using tarpaulin) +coverage: + stage: test + image: rust:latest + before_script: + - cargo install cargo-tarpaulin || true + script: + - cargo tarpaulin --out Xml --output-dir target/coverage + coverage: '/^\d+\.\d+% coverage/' + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: target/coverage/cobertura.xml + allow_failure: true # Coverage is informational + +# === Build Stage === + +# Build release binaries (Linux) +build:linux: + stage: build + image: rust:latest + script: + - cargo build --release --verbose + - strip target/release/bunsenite || true + - ls -lh target/release/bunsenite + - ls -lh target/release/libbunsenite.so + artifacts: + name: "bunsenite-$CI_COMMIT_REF_NAME-linux" + paths: + - target/release/bunsenite + - target/release/libbunsenite.so + expire_in: 1 week + +# Build WASM module +build:wasm: + stage: build + image: rust:latest + before_script: + - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + script: + - wasm-pack build --target web --out-dir pkg --release + - ls -lh pkg/ + artifacts: + name: "bunsenite-wasm-$CI_COMMIT_REF_NAME" + paths: + - pkg/ + expire_in: 1 week + allow_failure: true # WASM is optional + +# Build documentation +build:docs: + stage: build + image: rust:latest + script: + - cargo doc --all-features --no-deps + - echo '' > target/doc/index.html + artifacts: + name: "bunsenite-docs-$CI_COMMIT_REF_NAME" + paths: + - target/doc/ + expire_in: 1 week + +# === Security Stage === + +# Dependency audit (check for vulnerabilities) +audit: + stage: security + image: rust:latest + before_script: + - cargo install cargo-audit || true + script: + - cargo audit --deny warnings + allow_failure: false + +# License and dependency check +deny: + stage: security + image: rust:latest + before_script: + - cargo install cargo-deny || true + script: + - cargo deny check + allow_failure: true # Informational for now + +# SAST (Static Application Security Testing) +sast: + stage: security + image: rust:latest + script: + - rustup component add clippy + - cargo clippy --all-targets --all-features -- -D warnings + allow_failure: false + +# === Deploy Stage === + +# Publish to crates.io (only on tags) +publish:crates: + stage: deploy + image: rust:latest + only: + - tags + except: + - branches + script: + - | + if [ -z "$CARGO_REGISTRY_TOKEN" ]; then + echo "ERROR: CARGO_REGISTRY_TOKEN not set" + exit 1 + fi + cargo publish --token $CARGO_REGISTRY_TOKEN + when: manual # Require manual trigger + +# Create GitLab release (only on tags) +release:gitlab: + stage: deploy + image: registry.gitlab.com/gitlab-org/release-cli:latest + only: + - tags + except: + - branches + script: + - echo "Creating GitLab release for $CI_COMMIT_TAG" + release: + tag_name: '$CI_COMMIT_TAG' + description: 'Release $CI_COMMIT_TAG' + assets: + links: + - name: 'Linux Binary' + url: '$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/download?job=build:linux' + - name: 'WASM Module' + url: '$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/download?job=build:wasm' + - name: 'Documentation' + url: '$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/download?job=build:docs' + when: manual # Require manual trigger + +# === Special Jobs === + +# Nightly build (scheduled) +nightly: + stage: build + image: rustlang/rust:nightly + only: + - schedules + script: + - cargo build --release + - cargo test --all-features + allow_failure: true + +# Performance benchmarks (nightly only) +benchmarks: + stage: test + image: rustlang/rust:nightly + only: + - schedules + script: + - cargo bench + allow_failure: true + artifacts: + paths: + - target/criterion/ + expire_in: 1 month + +# === Branch-specific Rules === + +# Main branch: Run all checks +.main_rules: + only: + - main + except: + - schedules + +# Merge requests: Run checks and tests +.mr_rules: + only: + - merge_requests + except: + - schedules + +# Tags: Run everything including deployment +.tag_rules: + only: + - tags + except: + - branches + - schedules + +# === Job Configuration Templates === + +.rust_job: + before_script: + - rustc --version + - cargo --version + retry: + max: 2 + when: + - runner_system_failure + - stuck_or_timeout_failure diff --git a/vendor/bunsenite/.guix-channel b/vendor/bunsenite/.guix-channel new file mode 100644 index 0000000..3ec91e3 --- /dev/null +++ b/vendor/bunsenite/.guix-channel @@ -0,0 +1,7 @@ +;; bunsenite - Guix Channel +;; Add to ~/.config/guix/channels.scm + +(channel + (version 0) + (url "https://github.com/hyperpolymath/bunsenite") + (branch "main")) diff --git a/vendor/bunsenite/.hypatia-ignore b/vendor/bunsenite/.hypatia-ignore new file mode 100644 index 0000000..39b6808 --- /dev/null +++ b/vendor/bunsenite/.hypatia-ignore @@ -0,0 +1,27 @@ +# Banned-language exemption ledger — hypatia / governance-reusable.yml +# +# Format: /: +# +# WHY THIS FILE EXISTS +# -------------------- +# The governance "Language / package anti-pattern policy" gate is correct: +# these files really are in languages estate policy bans. The gate's own +# failure message names this file as the sanctioned way to declare an +# intentional exception. +# +# These exemptions HOLD THE LINE WHILE MIGRATION IS IN PROGRESS. Each entry +# is removed as the matching file is ported or deleted. This follows the +# precedent set in hyperpolymath/echidna. +# +# EVERY PATH IS LISTED INDIVIDUALLY — deliberately. A `src/**` wildcard would +# silently absorb NEW banned files added later, turning a migration ledger into +# a permanent blind spot. Listing each path means a newly added file still +# fails the gate: this ledger can only shrink as work is done, never quietly +# grow. +# +# Inventory taken 2026-08-06 across all 424 estate repositories. +# Files covered: 3 rescript + +cicd_rules/banned_language_file:bindings/rescript/Bunsenite.res +cicd_rules/banned_language_file:bindings/rescript/Bunsenite_test.res +cicd_rules/banned_language_file:bindings/rescript/Example.res diff --git a/vendor/bunsenite/.hypatia/activity.jsonl b/vendor/bunsenite/.hypatia/activity.jsonl new file mode 100644 index 0000000..17a314c --- /dev/null +++ b/vendor/bunsenite/.hypatia/activity.jsonl @@ -0,0 +1 @@ +{"timestamp":"2026-03-08T02:01:02Z","bot":"hypatia-autofix","action":"scan","details":"fixes=0"} diff --git a/vendor/bunsenite/.hypatia/last-visit.json b/vendor/bunsenite/.hypatia/last-visit.json new file mode 100644 index 0000000..6d8f19e --- /dev/null +++ b/vendor/bunsenite/.hypatia/last-visit.json @@ -0,0 +1,6 @@ +{ + "last_visit": "2026-03-08T02:01:02Z", + "last_bot": "hypatia-autofix", + "last_action": "scan", + "visits_total": 1 +} diff --git a/vendor/bunsenite/.machine_readable/6a2/0-AI-MANIFEST.a2ml b/vendor/bunsenite/.machine_readable/6a2/0-AI-MANIFEST.a2ml new file mode 100644 index 0000000..6bf1f8c --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/0-AI-MANIFEST.a2ml @@ -0,0 +1,31 @@ +# AI Manifest for 6a2 Directory + +## Purpose + +This manifest declares the AI-assistant context for the 6a2 machine-readable metadata directory. + +## Canonical Locations + +The 6 core A2ML files MUST exist in this directory: +1. AGENTIC.a2ml +2. ECOSYSTEM.a2ml +3. META.a2ml +4. NEUROSYM.a2ml +5. PLAYBOOK.a2ml +6. STATE.a2ml + +## Invariants + +- No duplicate files in root directory +- Single source of truth: this directory is authoritative +- No stale metadata + +## Protocol + +When multiple agents may write to A2ML files concurrently: +1. Read file and record git-sha-at-read in [provenance] section +2. Lock by creating .lock- +3. Write updated file with new [provenance] metadata +4. Release by removing lock file +5. On conflict: re-read and retry if git-sha-at-read does not match HEAD + diff --git a/vendor/bunsenite/.machine_readable/6a2/AGENTIC.a2ml b/vendor/bunsenite/.machine_readable/6a2/AGENTIC.a2ml new file mode 100644 index 0000000..3b12aab --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/AGENTIC.a2ml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# AGENTIC.a2ml — AI agent constraints and capabilities +# Defines what AI agents can and cannot do in this repository. + +[metadata] +version = "0.1.0" +last-updated = "2026-03-16" + +[agent-permissions] +can-edit-source = true +can-edit-tests = true +can-edit-docs = true +can-edit-config = true +can-create-files = true + +[agent-constraints] +# What AI agents must NOT do: +# - Never use banned language patterns (believe_me, unsafeCoerce, etc.) +# - Never commit secrets or credentials +# - Never use banned languages (TypeScript, Python, Go, etc.) +# - Never place state files in repository root (must be in .machine_readable/) +# - Never relicense an existing file, and never run an automated licence +# sweep (LICENCE-POLICY.adoc A2). New files get correct SPDX from birth. +# - Never assume a licence. Read standards/LICENCE-POLICY.adoc: Rule 1 +# defaults to MPL-2.0 (code) / CC-BY-SA-4.0 (prose), but Rule 3 +# (co-developed), Rule 4 (network-deployed services) and Rule 5 +# (games) are AGPL-3.0-or-later, and Rule 2 names the PMPL register. diff --git a/vendor/bunsenite/.machine_readable/6a2/ECOSYSTEM.a2ml b/vendor/bunsenite/.machine_readable/6a2/ECOSYSTEM.a2ml new file mode 100644 index 0000000..dd8634d --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/ECOSYSTEM.a2ml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: MPL-2.0 +# ECOSYSTEM.a2ml — Ecosystem position +# Converted from ECOSYSTEM.scm on 2026-03-15 + +[metadata] +project = "bunsenite" +ecosystem = "hyperpolymath" + +[position] +type = "component" diff --git a/vendor/bunsenite/.machine_readable/6a2/META.a2ml b/vendor/bunsenite/.machine_readable/6a2/META.a2ml new file mode 100644 index 0000000..2eca025 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/META.a2ml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 +# META.a2ml — Project meta-information +# Converted from META.scm on 2026-03-15 + +[metadata] +project = "bunsenite" +author = "Jonathan D.A. Jewell " +license = "MPL-2.0" +standard = "RSR 2026" diff --git a/vendor/bunsenite/.machine_readable/6a2/NEUROSYM.a2ml b/vendor/bunsenite/.machine_readable/6a2/NEUROSYM.a2ml new file mode 100644 index 0000000..767d7dd --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/NEUROSYM.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# NEUROSYM.a2ml — Neurosymbolic integration metadata +# Configuration for Hypatia scanning and symbolic reasoning. + +[metadata] +version = "0.1.0" +last-updated = "2026-03-16" + +[hypatia-config] +scan-enabled = true +scan-depth = "standard" +report-format = "logtalk" diff --git a/vendor/bunsenite/.machine_readable/6a2/PLAYBOOK.a2ml b/vendor/bunsenite/.machine_readable/6a2/PLAYBOOK.a2ml new file mode 100644 index 0000000..a961250 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/PLAYBOOK.a2ml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# PLAYBOOK.a2ml — Operational playbook +# Runbooks, incident response, deployment procedures. + +[metadata] +version = "0.1.0" +last-updated = "2026-03-16" + +[deployment] +# method = "gitops" +# target = "container" + +[incident-response] +# 1. Check .machine_readable/STATE.a2ml for current status +# 2. Review recent commits and CI results +# 3. Run just validate to check compliance + +[release-process] +# 1. Update version in STATE.a2ml, META.a2ml +# 2. Run just quality (format, lint, test) +# 3. Tag and push diff --git a/vendor/bunsenite/.machine_readable/6a2/README.adoc b/vendor/bunsenite/.machine_readable/6a2/README.adoc new file mode 100644 index 0000000..916a702 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/README.adoc @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +# A2ML 6a2 Directory + +This directory contains the 6 core A2ML machine-readable metadata files for this repository. + +## Files + +- `AGENTIC.a2ml` - AI agent operational gating, safety controls +- `ECOSYSTEM.a2ml` - Project ecosystem position, relationships, explicit boundaries +- `META.a2ml` - Architecture decisions (ADRs), development practices, design rationale +- `NEUROSYM.a2ml` - Symbolic semantics, composition algebra +- `PLAYBOOK.a2ml` - Executable plans, operational runbooks +- `STATE.a2ml` - Project state, phase, milestones, session history + +## Standards Compliance + +These files follow the A2ML Format Family specification from: +https://github.com/hyperpolymath/standards/tree/main/a2ml + +## Generation + +These files may be generated from .scm source files using transpilation tools. +Source .scm files should be removed after successful transpilation. + +## See Also + +- [A2ML Repository Template](https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc) +- [6A2 Format Family](https://github.com/hyperpolymath/standards#a2ml-format-family-7-formats) + diff --git a/vendor/bunsenite/.machine_readable/6a2/STATE.a2ml b/vendor/bunsenite/.machine_readable/6a2/STATE.a2ml new file mode 100644 index 0000000..ff3aa60 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/STATE.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# STATE.a2ml — Project state checkpoint +# Converted from STATE.scm on 2026-03-15 + +[metadata] +project = "bunsenite" +version = "0.1.0" +last-updated = "2026-03-15" +status = "active" + +[project-context] +name = "bunsenite" +completion-percentage = 0 +phase = "In development" diff --git a/vendor/bunsenite/.machine_readable/6a2/anchor/0-AI-MANIFEST.a2ml b/vendor/bunsenite/.machine_readable/6a2/anchor/0-AI-MANIFEST.a2ml new file mode 100644 index 0000000..0dd6825 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/anchor/0-AI-MANIFEST.a2ml @@ -0,0 +1,21 @@ +# AI Manifest for Anchor Directory + +## Purpose + +This manifest declares the AI-assistant context for the anchor machine-readable metadata directory. + +## Canonical Locations + +ANCHOR.a2ml files MUST exist in this directory. + +## Multiple Versions + +Unlike other A2ML files, multiple versions of ANCHOR.a2ml with different dates MAY exist. +Each version represents a specific recalibration point. + +## Invariants + +- Multiple versions with different dates are permitted +- No other A2ML files in this directory +- Single source of truth for anchor documents + diff --git a/vendor/bunsenite/.machine_readable/6a2/anchor/ANCHOR.a2ml b/vendor/bunsenite/.machine_readable/6a2/anchor/ANCHOR.a2ml new file mode 100644 index 0000000..12eab90 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/anchor/ANCHOR.a2ml @@ -0,0 +1,18 @@ +# ⚓ ANCHOR: bunsenite +# This is the canonical authority for the bunsenite repository. + +id: "org.hyperpolymath.bunsenite" +version: "1.0.0" +clade: "unknown" +status: "active" + +# SSG Configuration (Unified boj-server build) +ssg: + engine: "casket" + output_dir: "public" + boj_trigger: true + cartridge: "ssg-mcp" + +# Relationships +parents: + - "org.hyperpolymath.boj-server" diff --git a/vendor/bunsenite/.machine_readable/6a2/anchor/README.adoc b/vendor/bunsenite/.machine_readable/6a2/anchor/README.adoc new file mode 100644 index 0000000..13cae63 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/6a2/anchor/README.adoc @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +# A2ML Anchor Directory + +This directory contains ANCHOR.a2ml files for project recalibration and scope intervention. + +## Files + +- `ANCHOR.a2ml` - Project recalibration, scope intervention, canonical authority + +## Multiple Versions + +Unlike other A2ML files, multiple versions of ANCHOR.a2ml with different dates may exist. +Each version represents a specific recalibration point in the project history. + +## Standards Compliance + +These files follow the ANCHOR.a2ml specification from: +https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml + +## See Also + +- [A2ML Repository Template](https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc) +- [Anchor A2ML Spec](https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml) + diff --git a/vendor/bunsenite/.machine_readable/ADJUST.contractile b/vendor/bunsenite/.machine_readable/ADJUST.contractile new file mode 100644 index 0000000..e75ae01 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/ADJUST.contractile @@ -0,0 +1,126 @@ +; SPDX-License-Identifier: MPL-2.0 +; ADJUST.contractile — Accessibility invariants for bunsenite +; "ADJUST" = Accessibility & Digital Justice for Universal Software & Technology +; +; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST +; This file is machine-readable. LLM/SLM agents MUST NOT violate these invariants. + +; ── Definitions ────────────────────────────────────────────────── +; +; ADJUST (noun/verb) +; The accessibility contractile. Defines how software must adapt to serve +; all users regardless of ability, device, or context. Named for the verb +; "adjust" — to make suitable, to adapt, to accommodate — which is the +; core action of accessible design. +; +; Scope: +; ADJUST governs all user-facing interfaces: GUI, TUI, CLI, web, mobile, +; documentation, error messages, and installation flows. It applies to +; both human users and assistive technologies (screen readers, switch +; devices, braille displays, voice control). +; +; Relationship to other contractiles: +; - MUST: ADJUST invariants are a subset of MUST — violating ADJUST +; is a MUST violation. ADJUST exists separately because accessibility +; rules are numerous enough to warrant their own file, and because +; LLMs frequently forget accessibility unless explicitly reminded. +; - TRUST: ADJUST does not affect trust levels. All trust tiers must +; respect ADJUST invariants equally. +; - DUST: Deprecating a feature does not exempt it from ADJUST until +; it is fully removed. Deprecated UI must remain accessible. +; - INTENT: ADJUST supports the anti-purpose "this software is NOT +; only for able-bodied users with modern hardware." +; +; Standard: WCAG 2.2 Level AA (minimum) +; https://www.w3.org/WAI/WCAG22/quickref/?levels=aaa +; +; Why a separate file: +; Experience shows LLMs and developers alike treat accessibility as an +; afterthought. By placing invariants in a contractile that is loaded +; at session start, we make it structurally impossible to forget. +; +; ── End Definitions ────────────────────────────────────────────── + +(adjust-contractile + (version "1.0.0") + (full-name "Accessibility & Digital Justice for Universal Software & Technology") + (standard "WCAG-2.2-AA") + (repo "bunsenite") + + (invariants + ; ── Visual ── + (adjust "colour-contrast-ratio >= 4.5:1 for normal text") + (adjust "colour-contrast-ratio >= 3:1 for large text (18pt+ or 14pt+ bold)") + (adjust "no information conveyed by colour alone") + (adjust "no flashing or strobing content (3 flashes/second max)") + (adjust "text resizable to 200% without loss of content or function") + (adjust "focus indicators visible on all interactive elements") + + ; ── Keyboard ── + (adjust "all interactive elements reachable via keyboard (Tab/Shift+Tab)") + (adjust "no keyboard traps — user can always Tab away") + (adjust "skip navigation link present on pages with repeated blocks") + (adjust "logical focus order follows visual reading order") + + ; ── Screen reader ── + (adjust "all images have meaningful alt text (or alt='' if decorative)") + (adjust "all form inputs have associated labels") + (adjust "ARIA landmarks used for page regions (main, nav, banner, etc.)") + (adjust "dynamic content updates announced via aria-live regions") + (adjust "semantic HTML used (headings, lists, tables) — not div soup") + + ; ── Interactive ── + (adjust "touch targets minimum 44x44px on mobile/touch interfaces") + (adjust "error messages identify the field and describe the error") + (adjust "error messages not conveyed by colour or position alone") + (adjust "form validation provides suggestions for correction") + + ; ── Media ── + (adjust "video has captions (closed or open)") + (adjust "audio-only content has text transcript") + (adjust "no autoplay of media with sound") + + ; ── Motion ── + (adjust "animations respect prefers-reduced-motion media query") + (adjust "no content depends on motion to convey meaning") + + ; ── CLI/TUI ── + (adjust "CLI output must not rely solely on colour (use symbols: [OK] [FAIL])") + (adjust "TUI must support high-contrast mode") + (adjust "all CLI commands support --help with plain-text output") + (adjust "error messages written in plain language, not jargon or codes alone") + + ; ── Documentation ── + (adjust "docs use clear language, short sentences, logical structure") + (adjust "code examples include comments explaining non-obvious steps") + (adjust "diagrams have text descriptions or alt text") + + ; ── Internationalisation (i18n) ── + (adjust "all user-facing strings externalisable for translation") + (adjust "no hardcoded English in error messages — use message keys") + (adjust "date/time/number formats locale-aware") + (adjust "RTL (right-to-left) layout support where applicable") + (adjust "Unicode handled correctly throughout (UTF-8 everywhere)") + ) + + (related-resources + ; LOL — super-parallel corpus crawler for 1500+ languages + ; Use for linguistic data, translation coverage, and i18n validation + (lol "standards/lol — multilingual NLP corpus, see README.adoc") + (polyglot-i18n "polyglot-i18n — i18n framework and WASM translation engine") + ) + + (enforcement + (ci "accessibility linting in quality.yml workflow") + (pr-block "PR blocked if accessibility regression detected") + (tool "axe-core or pa11y for automated checks on web UI") + (tool "CLI output inspected for colour-only signalling") + (manual "manual screen reader test before major releases") + ) + + (notes + "These are MINIMUM requirements. Exceeding them (AAA) is encouraged." + "When in doubt about an accessibility decision, ask — don't guess." + "Accessibility is not optional polish — it is a structural requirement." + ) +) diff --git a/vendor/bunsenite/.machine_readable/CLADE.a2ml b/vendor/bunsenite/.machine_readable/CLADE.a2ml new file mode 100644 index 0000000..7060b3b --- /dev/null +++ b/vendor/bunsenite/.machine_readable/CLADE.a2ml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: MPL-2.0 +# Clade declaration — part of the gv-clade-index registry +# See: https://github.com/hyperpolymath/gv-clade-index + +[identity] +uuid = "ef2c8003-70e4-5fee-9b95-9a5fb2f508a9" +primary-forge = "github" +primary-owner = "hyperpolymath" +canonical-name = "bunsenite" +prefixed-name = "dx-bunsenite" + +[clade] +primary = "dx" +secondary = [] +assigned = "2026-03-16" +rationale = "" + +[forges] +github = "hyperpolymath/bunsenite" +gitlab = "hyperpolymath/bunsenite" +bitbucket = "hyperpolymath/bunsenite" + +[lineage] +type = "standalone" +parent = "Nickel configuration tool" +born = "2026-03-16" + +# Lifecycle status (added by clade-status-backfill; see gv-clade-index ADR 0006). +# Identity (uuid) and status are SEPARATE layers: uuid is immortal; phase is a +# mutable pointer. No phase is terminal (extinct -> active is a legal "Gitassic +# Park" transition on the same uuid). A rename is NOT a phase change — the old +# prefixed-name goes to aliases[], uuid and phase are untouched. +[status] +# One of: reserved incubating active dormant | merged superseded archived extinct +phase = "active" +since = "2026-03-16" +present = true +aliases = [] +merged-into = "" +superseded-by = "" +successors = [] +ended = "" + +[[status.history]] +phase = "active" +since = "2026-03-16" +note = "backfilled default — correct if the true phase differs" diff --git a/vendor/bunsenite/.machine_readable/INTENT.contractile b/vendor/bunsenite/.machine_readable/INTENT.contractile new file mode 100644 index 0000000..bba0083 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/INTENT.contractile @@ -0,0 +1,72 @@ +; SPDX-License-Identifier: MPL-2.0 +; INTENT.contractile — Purpose and scope for bunsenite +; Helps LLM/SLM agents understand what this repo IS and IS NOT. +; +; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST + +; ── Definitions ────────────────────────────────────────────────── +; +; INTENT (noun) +; The purpose contractile. Defines what this repository IS, what it is +; NOT (anti-purpose), and which architectural decisions are load-bearing. +; Without INTENT, LLMs drift into scope creep, reverse key decisions, +; or add features that belong in a different repo. +; +; Scope: +; INTENT governs the conceptual boundaries of the project — its reason +; for existing, its domain, and its relationship to the ecosystem. +; It does NOT specify implementation details (that's MUST and code). +; +; Relationship to other contractiles: +; - MUST: INTENT explains WHY certain MUSTs exist. If you don't +; understand a MUST, read INTENT first. +; - TRUST: The "ask-before-touching" section in INTENT maps directly +; to TRUST.trust-deny for the most sensitive areas. +; - ADJUST: INTENT's anti-purpose should include "this software is +; NOT only for users with perfect vision/hearing/mobility." +; - DUST: When INTENT changes (repo pivots), related DUST entries +; should be created for the abandoned direction. +; +; ── End Definitions ────────────────────────────────────────────── + +(intent-contractile + (version "1.0.0") + (repo "bunsenite") + + ; === Purpose (what this repo IS) === + (purpose + "{{ONE_PARAGRAPH_PURPOSE}}" + ) + + ; === Anti-Purpose (what this repo is NOT — prevents scope creep) === + (anti-purpose + "{{ONE_PARAGRAPH_ANTI_PURPOSE}}" + ; Examples: + ; "This is NOT a general-purpose database — it solves one specific problem." + ; "This is NOT a framework — it is a library with a focused API." + ; "This does NOT handle authentication — that is delegated to [other repo]." + ) + + ; === Key Architectural Decisions That Must Not Be Reversed === + (architectural-invariants + ; *REMINDER: List the foundational decisions* + ; ("Idris2 for ABI definitions — dependent types prove interface correctness") + ; ("Zig for FFI — zero-cost C ABI compatibility") + ; ("Elixir for supervision — OTP fault tolerance") + ) + + ; === Sensitive Areas (if in doubt, ask) === + (ask-before-touching + ; *REMINDER: List areas where LLMs should check before modifying* + ; "src/abi/ — formal proofs, changes require re-verification" + ; "ffi/zig/ — C ABI boundary, changes affect all language bindings" + ; ".machine_readable/ — checkpoint files, format is specified" + ) + + ; === Ecosystem Position === + (ecosystem + (belongs-to "{{MONOREPO_OR_STANDALONE}}") + (depends-on ("{{DEP1}}" "{{DEP2}}")) + (depended-on-by ("{{CONSUMER1}}" "{{CONSUMER2}}")) + ) +) diff --git a/vendor/bunsenite/.machine_readable/MUST.contractile b/vendor/bunsenite/.machine_readable/MUST.contractile new file mode 100644 index 0000000..9826581 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/MUST.contractile @@ -0,0 +1,91 @@ +; SPDX-License-Identifier: MPL-2.0 +; MUST.contractile — Baseline invariants for bunsenite +; These constraints MUST NOT be violated. K9 validators enforce them. +; +; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST + +; ── Definitions ────────────────────────────────────────────────── +; +; MUST (noun/verb) +; The hard-constraint contractile. Defines invariants that are structurally +; required for the repository to function correctly and safely. Violating +; a MUST is always a bug — there are no "soft" MUSTs. +; +; Scope: +; MUST governs code, configuration, CI, and structure. It does NOT govern +; style, preference, or approach — those belong in CLAUDE.md or coding +; standards. MUST is for things that break the project if violated. +; +; Relationship to other contractiles: +; - TRUST: MUST is enforced regardless of trust level. Even maximal-trust +; agents cannot violate MUST constraints. +; - ADJUST: All ADJUST invariants are implicitly MUST invariants too. +; ADJUST exists separately for visibility. +; - INTENT: MUST protects the architectural decisions described in INTENT. +; - DUST: When a feature enters DUST (deprecation), its MUST constraints +; remain active until the feature is fully removed. +; +; Enforcement: +; K9 validators in contractiles/self-validating/ machine-check MUST constraints. +; CI runs these on every PR. Violations block merge. +; +; ── End Definitions ────────────────────────────────────────────── + +(must-contractile + (version "1.0.0") + (repo "bunsenite") + + ; === Universal Invariants (apply to ALL repos) === + + (invariants + ; Paths + (must "no hardcoded absolute paths (/home/*, /mnt/*, /var/mnt/*)") + (must "all paths use env vars, XDG dirs, or relative references") + + ; Language policy + (must "no new TypeScript files") + (must "no new Python files") + (must "no new Go files") + (must "no npm/bun/yarn/pnpm dependencies — Deno only") + + ; Dangerous patterns + (must "no believe_me (Idris2)") + (must "no assert_total (Idris2)") + (must "no Admitted (Coq)") + (must "no sorry (Lean)") + (must "no unsafeCoerce (Haskell)") + (must "no Obj.magic (OCaml)") + (must "no unsafe {} blocks without safety comment (Rust)") + + ; License + (must "SPDX-License-Identifier header on every source file") + (must "no removal or modification of LICENSE file") + + ; Structure + (must ".machine_readable/ directory preserved") + (must "0-AI-MANIFEST.a2ml preserved") + (must "no SCM files in repo root — only in .machine_readable/") + + ; CI + (must "no removal of CI workflows without explicit approval") + (must "all GitHub Actions SHA-pinned") + + ; Code quality + (must "tests must not be deleted or weakened") + (must "generated code in generated/ directory only") + (must "no introduction of OWASP top 10 vulnerabilities") + + ; ABI/FFI (if applicable) + (must "no modification of ABI contracts without proof update") + (must "no removal of formal verification proofs") + ) + + ; === Project-Specific Invariants === + ; *REMINDER: Add invariants specific to this repo* + ; (must "# Add project-specific invariants here") + + (enforcement + (k9-validator "contractiles/self-validating/must-check.k9.ncl") + (ci "quality.yml runs must-check on every PR") + ) +) diff --git a/vendor/bunsenite/.machine_readable/TRUST.contractile b/vendor/bunsenite/.machine_readable/TRUST.contractile new file mode 100644 index 0000000..568607c --- /dev/null +++ b/vendor/bunsenite/.machine_readable/TRUST.contractile @@ -0,0 +1,80 @@ +; SPDX-License-Identifier: MPL-2.0 +; TRUST.contractile — Trust boundaries for bunsenite +; Defines what LLM/SLM agents are trusted to do without asking. +; +; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST + +; ── Definitions ────────────────────────────────────────────────── +; +; TRUST (noun/verb) +; The permission contractile. Defines the boundary between what an AI +; agent may do autonomously and what requires human approval. Trust is +; graduated — not binary — with four levels from minimal to maximal. +; +; Trust levels: +; - maximal: Agent may read, build, test, lint, format, heal freely. +; Only destructive/external actions require approval. +; - standard: Agent may read and build. Test/lint need approval. +; - restricted: Agent may read only. All modifications need approval. +; - minimal: Agent may read specific files only. Everything else blocked. +; +; Scope: +; TRUST governs AI agent behaviour only. It does not affect human +; contributors — humans follow CONTRIBUTING.md and GOVERNANCE.adoc. +; +; Relationship to other contractiles: +; - MUST: Trust never overrides MUST. Even at maximal trust, MUST +; violations are blocked. +; - ADJUST: Trust does not exempt from ADJUST. All trust tiers must +; produce accessible output. +; - INTENT: TRUST.trust-deny protects the sensitive areas listed in +; INTENT.ask-before-touching. +; - DUST: Deprecated features have the same trust rules as active ones. +; +; ── End Definitions ────────────────────────────────────────────── + +(trust-contractile + (version "1.0.0") + (repo "bunsenite") + + (trust-level "maximal") ; maximal | standard | restricted | minimal + + ; === Maximal Trust (default) === + ; LLM may freely do these without asking: + (trust-actions + "read" ; Read any file in the repo + "build" ; Run build commands + "test" ; Run test suites + "lint" ; Run linters and formatters + "format" ; Auto-format code + "doctor" ; Run self-diagnostics + "heal" ; Attempt automatic repair + "git-status" ; Check git status + "git-diff" ; View diffs + "git-log" ; View history + ) + + ; === Denied Actions (always require human approval) === + (trust-deny + "delete-branch" ; Could lose work + "force-push" ; Overwrites history + "modify-ci-secrets" ; Security sensitive + "publish" ; External visibility + "push-to-main" ; Protected branch + "delete-files-bulk" ; More than 5 files at once + "modify-license" ; Legal implications + "modify-security-policy" ; Security implications + "remove-proofs" ; Formal verification regression + "disable-ci-checks" ; Safety regression + ) + + ; === Trust Boundary === + (trust-boundary "repo") ; LLM confined to this repo unless explicitly told otherwise + + ; === Override === + ; Repos requiring tighter trust override these settings with justification: + ; (override + ; (trust-level "restricted") + ; (reason "Contains production secrets / handles PII / etc.") + ; ) +) diff --git a/vendor/bunsenite/.machine_readable/bot_directives/README.adoc b/vendor/bunsenite/.machine_readable/bot_directives/README.adoc new file mode 100644 index 0000000..1dcb625 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/bot_directives/README.adoc @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Agent Instructions +:toc: preamble + +Methodology-aware configuration for AI agents. Read by any AI agent +(Claude, Gemini, Copilot, etc.) at session start. + +== Files + +[cols="1,3"] +|=== +| File | Purpose + +| `methodology.a2ml` +| Default mode, invariants, ring ceiling, priority weights, convergent budget + +| `coverage.a2ml` +| Session coverage tracking — what was visited, what was skipped, what has MUSTs + +| `debt.a2ml` +| Meander debt — things found but not fixed, carried between sessions +|=== + +== How Agents Use These + +1. Read `methodology.a2ml` at session start — know mode, invariants, ceiling +2. Read `coverage.a2ml` — know what was visited last time, what was skipped +3. Read `debt.a2ml` — know what's outstanding from previous sessions +4. At session end, update `coverage.a2ml` and `debt.a2ml` + +== Relationship to Other Files + +* `AGENTIC.a2ml` says WHAT agents can do (permissions, gating) +* `bot_directives/` says HOW agents should work (methodology) +* `bot_directives/` says what the gitbot-fleet does (fleet-specific) +* `CLAUDE.md` says how Claude specifically should work (Claude-specific) + +== Reference + +ADR-002 in `standards/agentic-a2ml/docs/ADR-002-methodology-layer.adoc` diff --git a/vendor/bunsenite/.machine_readable/bot_directives/coverage.a2ml b/vendor/bunsenite/.machine_readable/bot_directives/coverage.a2ml new file mode 100644 index 0000000..6979664 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/bot_directives/coverage.a2ml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# coverage.a2ml — Session coverage tracking +# Updated at the end of each AI agent session. +# Persists what was visited, what was skipped, and what has MUSTs. +# +# Reference: ADR-002 in standards/agentic-a2ml/docs/ + +[metadata] +version = "1.0.0" +last-updated = "2026-03-24" + +# ============================================================================ +# COVERAGE STATE +# ============================================================================ +# Updated by agents at session end. Tracks which components have been +# visited and which have known MUSTs that were skipped. + +[coverage] +total-components = 0 +visited-components = 0 +coverage-percent = 0 + +# ============================================================================ +# VISITED COMPONENTS +# ============================================================================ +# Component → session date + ring reached +# Agents add entries as they work through components. +# +# Example: +# [coverage.visited.emergency-room] +# date = "2026-03-23" +# ring = 2 +# fixes = 3 +# notes = "boot-guardian built, shutdown-marshal built" + +# ============================================================================ +# SKIPPED COMPONENTS WITH MUSTS +# ============================================================================ +# Components with known MUSTs that were not visited in the most recent session. +# These become P1 inputs for the next session's Phase 0. +# +# Example: +# [coverage.skipped-musts.session-sentinel] +# priority = "P0" +# issue = "56 SIGABRTs in 4 days, D-Bus race condition" +# discovered = "2026-03-23" + +# ============================================================================ +# CHERRY-PICKING AUDIT +# ============================================================================ +# At session end, agents report whether they chose easy work over hard work. +# This is the accountability mechanism for the weighted priority system. +# +# [coverage.cherry-picking] +# easy-high-completed = 3 +# hard-high-completed = 1 +# easy-low-completed = 2 +# hard-low-deferred = 4 +# assessment = "Correctly prioritised — all MUST items addressed before COULDs" diff --git a/vendor/bunsenite/.machine_readable/bot_directives/debt.a2ml b/vendor/bunsenite/.machine_readable/bot_directives/debt.a2ml new file mode 100644 index 0000000..c0238c5 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/bot_directives/debt.a2ml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# debt.a2ml — Meander debt list +# Things found but not fixed. Carried between sessions. +# Becomes the next session's Phase 0 input. +# +# Reference: ADR-002 in standards/agentic-a2ml/docs/ + +[metadata] +version = "1.0.0" +last-updated = "2026-03-24" + +# ============================================================================ +# DEBT ITEMS +# ============================================================================ +# Each item has: component, issue, effort (easy|medium|hard), impact (high|medium|low), +# priority (should|could), and discovered date. +# +# Items are consumed (removed) when fixed. New items are added at session end. +# The debt list prevents the "one more wave" loop — found things are persisted, +# not forgotten, and not used as justification for infinite meandering. + +# ============================================================================ +# SHOULD — would fix next wave +# ============================================================================ +# These are inputs for the next session if the user says "keep going". +# +# Example: +# [[debt.should]] +# component = "system-tools/monitoring/observatory" +# issue = "Stale duplicate of root observatory/" +# effort = "easy" +# impact = "medium" +# discovered = "2026-03-23" + +# ============================================================================ +# COULD — would fix eventually +# ============================================================================ +# These are low-priority items that don't justify a session on their own. +# They get picked up when an agent is in the area for other reasons. +# +# Example: +# [[debt.could]] +# component = "cicada" +# issue = "RSR_OUTLINE.adoc references banned AGPL-3.0" +# effort = "easy" +# impact = "low" +# discovered = "2026-03-23" diff --git a/vendor/bunsenite/.machine_readable/bot_directives/methodology.a2ml b/vendor/bunsenite/.machine_readable/bot_directives/methodology.a2ml new file mode 100644 index 0000000..754f357 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/bot_directives/methodology.a2ml @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# methodology.a2ml — AI agent methodology configuration +# Declares how agents should approach work in this repository. +# Read at session start by any AI agent (Claude, Gemini, Copilot, etc.) +# +# Reference: ADR-002 in standards/agentic-a2ml/docs/ + +[metadata] +version = "1.0.0" +last-updated = "2026-03-24" +spec = "https://github.com/hyperpolymath/standards/blob/main/agentic-a2ml/docs/ADR-002-methodology-layer.adoc" + +# ============================================================================ +# MODE SELECTION +# ============================================================================ +# convergent: find gaps, fill them, build infrastructure (default for ops/infra) +# divergent: find what's strongest, push it further (for research/creative) +# hybrid: audit 20% of budget, then focus 80% on top MUSTs (default for most) + +[methodology] +default-mode = "hybrid" +ring-ceiling = 2 # Hard ceiling for ring expansion (0-3) +wave-cap = 2 # Max waves before requiring user "keep going" +spike-required = true # Every session must ship code, not just designs + +# ============================================================================ +# PRIORITY WEIGHTS +# ============================================================================ +# MUST (3x): Blocking the current work → fix immediately +# SHOULD (2x): Degrading quality of current work → fix if in zone +# COULD (1x): Improving quality of adjacent work → add to debt list + +[methodology.priority-weights] +must = 3 +should = 2 +could = 1 + +# ============================================================================ +# CONVERGENT BUDGET (when mode = convergent or hybrid) +# ============================================================================ +# How to allocate effort across work types. +# Prevents over-polishing docs while structural work waits. + +[methodology.convergent-budget] +structural = 70 # % for new modules, compilation fixes, wiring, integration +corrective = 20 # % for bugs found, broken imports, stale references +perfective = 10 # % for SPDX headers, doc updates, formatting, style + +# ============================================================================ +# UNIQUE STRENGTH (when mode = divergent) +# ============================================================================ +# What makes this project special. Agents should DEEPEN this, not broaden it. +# Customise this per project — the template default is generic. + +[methodology.unique-strength] +description = "{{PROJECT_UNIQUE_STRENGTH}}" +deepen-not-broaden = true + +# ============================================================================ +# DIVERGENT INVARIANTS +# ============================================================================ +# Constraints that divergent mode must NOT violate. +# These are the riverbanks — diverge within them, not across. +# "Amplify uniqueness" means deepen, not broaden. +# +# Test before any divergent action: +# "Does this deepen the existing strength, or add a parallel strength?" +# If parallel → stop. Note as cross-project insight. + +[methodology.divergent-invariants] +rules = [ + # Customise per project. Examples: + # "Idris2 only for formal verification — no Lean4, Coq, Agda", + # "believe_me count must remain zero", + # "FFI architecture: Idris2 → RefC → Zig → C ABI (no shortcuts)", +] + +# Optional: language invariant for the core strength +# If set, divergent mode will not introduce other languages for this purpose +# language-invariant = "idris2" + +# ============================================================================ +# CONSTRAINT HINTS +# ============================================================================ +# Help Phase 0 find the critical chain faster. +# Updated at session end with newly discovered constraints. + +[methodology.known-constraints] +constraints = [ + # Customise per project. Examples: + # "End-to-end build has never been verified", + # "libproject.so does not exist yet — all bindings call stubs", +] + +# ============================================================================ +# STATE FILE VALIDATION +# ============================================================================ +# Phase 0 reads STATE.a2ml first but it may be broken. +# These rules detect corrupt/template/stale state files. + +[methodology.state-validation] +reject-if-contains = ["{{PLACEHOLDER}}", "{{PROJECT}}", "rsr-template-repo"] +reject-if-project-name-mismatch = true +staleness-threshold-days = 90 +fallback-files = ["TODO.md", "TODO.adoc", "ROADMAP.adoc", "README.adoc"] diff --git a/vendor/bunsenite/.machine_readable/contractiles/bust/Bustfile.a2ml b/vendor/bunsenite/.machine_readable/contractiles/bust/Bustfile.a2ml new file mode 100644 index 0000000..fba3e99 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/contractiles/bust/Bustfile.a2ml @@ -0,0 +1,28 @@ +// Bustfile.a2ml — meta-repo bust contractile (breakage / rollback) +// SPDX-License-Identifier: MPL-2.0 + +Bust { + name: "bunsenite" + version: "1.0.0" + description: "Rollback procedures when something breaks in the meta-repo" + + scenarios: { + "bad-pointer-bump": "git revert in meta-repo; child repo itself untouched" + "submodule-pointer-points-at-missing-sha": "git submodule update --init --checkout resets child to parent-recorded SHA; OR revert the stale bump commit" + "submodule-orphan-after-local-only-commit": "roll back locally with git reset to before the stranded commit; fix remote situation before re-attempting" + "accidental-private-repo-content-leaked-to-public-submodule": "hard-rotate the leaked secret immediately; git-filter-repo or BFG on the submodule's own history; public re-publication only after rotation complete" + } + + escalation-ladder: [ + "1. revert the meta-repo commit (reversible, low blast radius)", + "2. reset the local submodule clone (affects only local workspace)", + "3. force-push to main — PROHIBITED without explicit user confirmation (violates branch protection)", + "4. registry-level (delete/archive the GitHub repo) — human-only action, never by AI" + ] + + backup-points: [ + "GitHub serves as the durable backup for every submodule's own history", + "Meta-repo history on origin/main is the durable backup for pointer state", + "Local backup tags (backup/pre--) retained on risky rewrites" + ] +} diff --git a/vendor/bunsenite/.machine_readable/contractiles/bust/bust.ncl b/vendor/bunsenite/.machine_readable/contractiles/bust/bust.ncl new file mode 100644 index 0000000..fc8cb8c --- /dev/null +++ b/vendor/bunsenite/.machine_readable/contractiles/bust/bust.ncl @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: MPL-2.0 +# Bust — error-handling / failure-recovery runner +# +# Pairs with: Bustfile.a2ml (same directory) +# Verb: bust +# Semantics: every declared failure mode must have a recovery path that has +# been exercised. Runner injects failures (via declared probes) +# and verifies the recovery path works. Hard gate on any +# failure-mode with missing or broken recovery. +# CLI: `contractile bust check` → list failure modes + recovery status +# `contractile bust drill` → inject declared failures, verify recovery +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc + +let base = import "../_base.ncl" in + +{ + pedigree = base.pedigree_schema & { + contractile_verb = "bust", + semantics = "error handling + failure recovery", + security = { + leash = 'Kennel, + trust_level = "controlled failure injection; scoped to system-under-test", + allow_network = false, + allow_filesystem_write = true, # drills may write transient state (tmp dirs, test DBs) + allow_subprocess = true, + injection_scope = "system-under-test-only", + }, + metadata = { + name = "bust-runner", + version = "1.0.0", + description = "Exercises declared failure modes and verifies recovery paths. Hard-gates on any failure mode without working recovery.", + paired_xfile = "Bustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + failure_modes + | Array { + id | String, + description | String, + class | [| 'network, 'disk_full, 'oom, 'timeout, 'partial_write, 'panic, 'crash, 'rollback, 'concurrency |], + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + injection_probe | String, # command that deterministically causes this failure + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + recovery_probe | String, # command that verifies recovery (exit 0 = recovered) + expected_recovery_time_seconds | Number | default = 30, + # status_core values: 'declared, 'verified, 'failing; bust adds 'drilled + status | [| 'declared, 'drilled, 'verified, 'failing |] | default = 'declared, + notes | String | optional, + }, + }, + + # Runner behaviour — inherits from base.run_defaults. + # bust adds record_recovery_times for performance tier feeding. + run = base.run_defaults & { + on_any_fail = "exit-nonzero", # missing or broken recovery blocks merge + report_format = "a2ml", + emit_summary = true, + record_recovery_times = true, # feeds the performance tier + }, +} diff --git a/vendor/bunsenite/.machine_readable/contractiles/dust/Dustfile.a2ml b/vendor/bunsenite/.machine_readable/contractiles/dust/Dustfile.a2ml new file mode 100644 index 0000000..0d619ee --- /dev/null +++ b/vendor/bunsenite/.machine_readable/contractiles/dust/Dustfile.a2ml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dustfile — Cleanup and Hygiene Contract + +[dustfile] +version = "1.0.0" +format = "a2ml" + +[cleanup] +stale-branch-policy = "delete-after-merge" +artifact-retention = "90-days" +cache-policy = "clear-on-release" + +[hygiene] +linting = "required" +formatting = "required" +dead-code-removal = "encouraged" +todo-tracking = "tracked-in-issues" + +[reversibility] +backup-before-destructive = true +rollback-mechanism = "git-revert" +data-retention-policy = "preserve-30-days" diff --git a/vendor/bunsenite/.machine_readable/contractiles/trust/Trustfile.a2ml b/vendor/bunsenite/.machine_readable/contractiles/trust/Trustfile.a2ml new file mode 100644 index 0000000..f2a4f95 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/contractiles/trust/Trustfile.a2ml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MPL-2.0 +# Trustfile — Integrity and provenance verification +# Author: Jonathan D.A. Jewell + +@abstract: +Integrity invariants for this repository. These verify that the repo +has not been tampered with, secrets are not leaked, and provenance +is traceable. +@end + +## Secrets + +### no-secrets-committed +- description: No credential files in repo +- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local && test ! -f .env.production +- severity: critical + +### no-private-keys +- description: No private key files committed +- run: "! find . -name '*.pem' -o -name '*.key' -o -name 'id_rsa' -o -name 'id_ed25519' 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +### no-tokens-in-source +- description: No hardcoded API tokens in source +- run: "! grep -rE '(api[_-]?key|secret|token|password)\s*[:=]\s*[\"'\\''][A-Za-z0-9]{16,}' --include='*.js' --include='*.ts' --include='*.res' --include='*.py' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +## Provenance + +### author-correct +- description: Git author matches expected identity +- run: "git log -1 --format='%ae' | grep -qE '(hyperpolymath|j\\.d\\.a\\.jewell)'" +- severity: warning + +### license-content +- description: LICENSE contains expected identifier +- run: grep -q 'PMPL\|MPL\|MIT\|Apache\|LGPL' LICENSE +- severity: warning + +## Container Security + +### container-images-pinned +- description: Containerfile uses pinned base images +- run: test ! -f Containerfile || grep -q 'cgr.dev\|@sha256:' Containerfile +- severity: warning + +### no-dockerfile +- description: No Dockerfile (use Containerfile) +- run: test ! -f Dockerfile +- severity: warning diff --git a/vendor/bunsenite/.machine_readable/integrations/feedback-o-tron.a2ml b/vendor/bunsenite/.machine_readable/integrations/feedback-o-tron.a2ml new file mode 100644 index 0000000..5381604 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/integrations/feedback-o-tron.a2ml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Feedback-o-Tron Integration — Autonomous Bug Reporting + +[integration] +name = "feedback-o-tron" +type = "bug-reporter" +repository = "https://github.com/hyperpolymath/feedback-o-tron" + +[reporting-config] +platforms = ["github", "gitlab", "bugzilla"] +deduplication = true +audit-logging = true +auto-file-upstream = "on-external-dependency-failure" diff --git a/vendor/bunsenite/.machine_readable/integrations/proven.a2ml b/vendor/bunsenite/.machine_readable/integrations/proven.a2ml new file mode 100644 index 0000000..9af33ff --- /dev/null +++ b/vendor/bunsenite/.machine_readable/integrations/proven.a2ml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: MPL-2.0 +# Proven Integration — Formally Verified Safety Library + +[integration] +name = "proven" +type = "safety-library" +repository = "https://github.com/hyperpolymath/proven" +version = "1.2.0" + +[binding-policy] +approach = "thin-ffi-wrapper" +unsafe-patterns = "replace-with-proven-equivalent" +modules-available = ["SafeMath", "SafeString", "SafeJSON", "SafeURL", "SafeRegex", "SafeSQL", "SafeFile", "SafeTemplate", "SafeCrypto"] + +[adoption-guidance] +priority = "high" +scope = "all-string-json-url-crypto-operations" +migration = "incremental — replace unsafe patterns as encountered" diff --git a/vendor/bunsenite/.machine_readable/integrations/verisimdb.a2ml b/vendor/bunsenite/.machine_readable/integrations/verisimdb.a2ml new file mode 100644 index 0000000..164c522 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/integrations/verisimdb.a2ml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MPL-2.0 +# VeriSimDB Feed — Cross-Repo Analytics Data Store + +[integration] +name = "verisimdb" +type = "data-feed" +repository = "https://github.com/hyperpolymath/nextgen-databases" +data-store = "verisimdb-data" + +[feed-config] +emit-scan-results = true +emit-build-metrics = true +emit-dependency-graph = true +format = "hexad" +destination = "verisimdb-data/feeds/" diff --git a/vendor/bunsenite/.machine_readable/integrations/vexometer.a2ml b/vendor/bunsenite/.machine_readable/integrations/vexometer.a2ml new file mode 100644 index 0000000..238b3d2 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/integrations/vexometer.a2ml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: MPL-2.0 +# Vexometer Integration — Irritation Surface Analysis + +[integration] +name = "vexometer" +type = "friction-measurement" +repository = "https://github.com/hyperpolymath/vexometer" + +[measurement-config] +dimensions = 10 +emit-isa-reports = true +lazy-eliminator = true +satellite-interventions = true + +[hooks] +cli-tools = "measure-on-error" +ui-panels = "measure-on-interaction" +build-failures = "measure-on-failure" diff --git a/vendor/bunsenite/.machine_readable/root-allow.txt b/vendor/bunsenite/.machine_readable/root-allow.txt new file mode 100644 index 0000000..6cbe973 --- /dev/null +++ b/vendor/bunsenite/.machine_readable/root-allow.txt @@ -0,0 +1,3 @@ +CLAUDE.md +flake.guix +build/ # build orchestration: guix.scm relocated here (canon 1.2.1 guix-primary template_ref = "build/") diff --git a/vendor/bunsenite/.mise.toml b/vendor/bunsenite/.mise.toml new file mode 100644 index 0000000..b596372 --- /dev/null +++ b/vendor/bunsenite/.mise.toml @@ -0,0 +1,5 @@ +[tools] +# = "SPDX-License-Identifier: MPL-2.0" +# = "asdf/mise tool versions" +# = "See: https://asdf-vm.com/" +rust = "1.83.0" diff --git a/vendor/bunsenite/.nojekyll b/vendor/bunsenite/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/vendor/bunsenite/0-AI-MANIFEST.a2ml b/vendor/bunsenite/0-AI-MANIFEST.a2ml new file mode 100644 index 0000000..197eb0d --- /dev/null +++ b/vendor/bunsenite/0-AI-MANIFEST.a2ml @@ -0,0 +1,15 @@ + +# AI Assistant Instructions + +## Repository Focus +- `rsr-template-repo` is treated as a Rhodium Standard Repository; obey the Rhodium policies, maintain `.bot_directives`, and keep `.machines_readable/6scm/` authoritative. +- Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. + +## Workflow +1. Inspect `.machines_readable/6scm/STATE.scm` for blockers and next actions. +2. Respect any constraints listed inside `.machines_readable/6scm/AGENTIC.scm` when tooling changes are requested. +3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. + +## Delivery Promises +- Mention in summaries whether STATE, `contractiles/`, or `.bot_directives/` changed. +- Keep this file in sync with the repository’s status; update it if the governance changes. diff --git a/vendor/bunsenite/ABI-FFI-README.adoc b/vendor/bunsenite/ABI-FFI-README.adoc new file mode 100644 index 0000000..04be86a --- /dev/null +++ b/vendor/bunsenite/ABI-FFI-README.adoc @@ -0,0 +1,409 @@ +\{\{~ Aditionally delete this line and fill out the template below ~}} + +== \{\{PROJECT}} ABI/FFI Documentation + +=== Overview + +This library follows the *Hyperpolymath RSR Standard* for ABI and FFI +design: + +* *ABI (Application Binary Interface)* defined in *Idris2* with formal +proofs +* *FFI (Foreign Function Interface)* implemented in *Zig* for C +compatibility +* *Generated C headers* bridge Idris2 ABI to Zig FFI +* *Any language* can call through standard C ABI + +=== Architecture + +.... +┌─────────────────────────────────────────────┐ +│ ABI Definitions (Idris2) │ +│ src/abi/ │ +│ - Types.idr (Type definitions) │ +│ - Layout.idr (Memory layout proofs) │ +│ - Foreign.idr (FFI declarations) │ +└─────────────────┬───────────────────────────┘ + │ + │ generates (at compile time) + ▼ +┌─────────────────────────────────────────────┐ +│ C Headers (auto-generated) │ +│ generated/abi/{{project}}.h │ +└─────────────────┬───────────────────────────┘ + │ + │ imported by + ▼ +┌─────────────────────────────────────────────┐ +│ FFI Implementation (Zig) │ +│ ffi/zig/src/main.zig │ +│ - Implements C-compatible functions │ +│ - Zero-cost abstractions │ +│ - Memory-safe by default │ +└─────────────────┬───────────────────────────┘ + │ + │ compiled to lib{{project}}.so/.a + ▼ +┌─────────────────────────────────────────────┐ +│ Any Language via C ABI │ +│ - Rust, AffineScript, Julia, Python, etc. │ +└─────────────────────────────────────────────┘ +.... + +=== Directory Structure + +.... +{{project}}/ +├── src/ +│ ├── abi/ # ABI definitions (Idris2) +│ │ ├── Types.idr # Core type definitions with proofs +│ │ ├── Layout.idr # Memory layout verification +│ │ └── Foreign.idr # FFI function declarations +│ └── lib/ # Core library (any language) +│ +├── ffi/ +│ └── zig/ # FFI implementation (Zig) +│ ├── build.zig # Build configuration +│ ├── build.zig.zon # Dependencies +│ ├── src/ +│ │ └── main.zig # C-compatible FFI implementation +│ ├── test/ +│ │ └── integration_test.zig +│ └── include/ +│ └── {{project}}.h # C header (optional, can be generated) +│ +├── generated/ # Auto-generated files +│ └── abi/ +│ └── {{project}}.h # Generated from Idris2 ABI +│ +└── bindings/ # Language-specific wrappers (optional) + ├── rust/ + ├── affinescript/ + └── julia/ +.... + +=== Why Idris2 for ABI? + +==== 1. *Formal Verification* + +Idris2’s dependent types allow proving properties about the ABI at +compile-time: + +[source,idris] +---- +-- Prove struct size is correct +public export +exampleStructSize : HasSize ExampleStruct 16 + +-- Prove field alignment is correct +public export +fieldAligned : Divides 8 (offsetOf ExampleStruct.field) + +-- Prove ABI is platform-compatible +public export +abiCompatible : Compatible (ABI 1) (ABI 2) +---- + +==== 2. *Type Safety* + +Encode invariants that C/Zig cannot express: + +[source,idris] +---- +-- Non-null pointer guaranteed at type level +data Handle : Type where + MkHandle : (ptr : Bits64) -> {auto 0 nonNull : So (ptr /= 0)} -> Handle + +-- Array with length proof +data Buffer : (n : Nat) -> Type where + MkBuffer : Vect n Byte -> Buffer n +---- + +==== 3. *Platform Abstraction* + +Platform-specific types with compile-time selection: + +[source,idris] +---- +CInt : Platform -> Type +CInt Linux = Bits32 +CInt Windows = Bits32 + +CSize : Platform -> Type +CSize Linux = Bits64 +CSize Windows = Bits64 +---- + +==== 4. *Safe Evolution* + +Prove that new ABI versions are backward-compatible: + +[source,idris] +---- +-- Compiler enforces compatibility +abiUpgrade : ABI 1 -> ABI 2 +abiUpgrade old = MkABI2 { + -- Must preserve all v1 fields + v1_compat = old, + -- Can add new fields + new_features = defaults +} +---- + +=== Why Zig for FFI? + +==== 1. *C ABI Compatibility* + +Zig exports C-compatible functions naturally: + +[source,zig] +---- +export fn library_function(param: i32) i32 { + return param * 2; +} +---- + +==== 2. *Memory Safety* + +Compile-time safety without runtime overhead: + +[source,zig] +---- +// Null check enforced at compile time +const handle = init() orelse return error.InitFailed; +defer free(handle); +---- + +==== 3. *Cross-Compilation* + +Built-in cross-compilation to any platform: + +[source,bash] +---- +zig build -Dtarget=x86_64-linux +zig build -Dtarget=aarch64-macos +zig build -Dtarget=x86_64-windows +---- + +==== 4. *Zero Dependencies* + +No runtime, no libc required (unless explicitly needed): + +[source,zig] +---- +// Minimal binary size +pub const lib = @import("std"); +// Only includes what you use +---- + +=== Building + +==== Build FFI Library + +[source,bash] +---- +cd ffi/zig +zig build # Build debug +zig build -Doptimize=ReleaseFast # Build optimized +zig build test # Run tests +---- + +==== Generate C Header from Idris2 ABI + +[source,bash] +---- +cd src/abi +idris2 --cg c-header Types.idr -o ../../generated/abi/{{project}}.h +---- + +==== Cross-Compile + +[source,bash] +---- +cd ffi/zig + +# Linux x86_64 +zig build -Dtarget=x86_64-linux + +# macOS ARM64 +zig build -Dtarget=aarch64-macos + +# Windows x86_64 +zig build -Dtarget=x86_64-windows +---- + +=== Usage + +==== From C + +[source,c] +---- +#include "{{project}}.h" + +int main() { + void* handle = {{project}}_init(); + if (!handle) return 1; + + int result = {{project}}_process(handle, 42); + if (result != 0) { + const char* err = {{project}}_last_error(); + fprintf(stderr, "Error: %s\n", err); + } + + {{project}}_free(handle); + return 0; +} +---- + +Compile with: + +[source,bash] +---- +gcc -o example example.c -l{{project}} -L./zig-out/lib +---- + +==== From Idris2 + +[source,idris] +---- +import {{PROJECT}}.ABI.Foreign + +main : IO () +main = do + Just handle <- init + | Nothing => putStrLn "Failed to initialize" + + Right result <- process handle 42 + | Left err => putStrLn $ "Error: " ++ errorDescription err + + free handle + putStrLn "Success" +---- + +==== From Rust + +[source,rust] +---- +#[link(name = "{{project}}")] +extern "C" { + fn {{project}}_init() -> *mut std::ffi::c_void; + fn {{project}}_free(handle: *mut std::ffi::c_void); + fn {{project}}_process(handle: *mut std::ffi::c_void, input: u32) -> i32; +} + +fn main() { + unsafe { + let handle = {{project}}_init(); + assert!(!handle.is_null()); + + let result = {{project}}_process(handle, 42); + assert_eq!(result, 0); + + {{project}}_free(handle); + } +} +---- + +==== From Julia + +[source,julia] +---- +const lib{{project}} = "lib{{project}}" + +function init() + handle = ccall((:{{project}}_init, lib{{project}}), Ptr{Cvoid}, ()) + handle == C_NULL && error("Failed to initialize") + handle +end + +function process(handle, input) + result = ccall((:{{project}}_process, lib{{project}}), Cint, (Ptr{Cvoid}, UInt32), handle, input) + result +end + +function cleanup(handle) + ccall((:{{project}}_free, lib{{project}}), Cvoid, (Ptr{Cvoid},), handle) +end + +# Usage +handle = init() +try + result = process(handle, 42) + println("Result: $result") +finally + cleanup(handle) +end +---- + +=== Testing + +==== Unit Tests (Zig) + +[source,bash] +---- +cd ffi/zig +zig build test +---- + +==== Integration Tests + +[source,bash] +---- +cd ffi/zig +zig build test-integration +---- + +==== ABI Verification (Idris2) + +[source,idris] +---- +-- Compile-time verification +%runElab verifyABI + +-- Runtime checks +main : IO () +main = do + verifyLayoutsCorrect + verifyAlignmentsCorrect + putStrLn "ABI verification passed" +---- + +=== Contributing + +When modifying the ABI/FFI: + +[arabic] +. *Update ABI first* (`+src/abi/*.idr+`) +* Modify type definitions +* Update proofs +* Ensure backward compatibility +. *Generate C header* ++ +[source,bash] +---- +idris2 --cg c-header src/abi/Types.idr -o generated/abi/{{project}}.h +---- +. *Update FFI implementation* (`+ffi/zig/src/main.zig+`) +* Implement new functions +* Match ABI types exactly +. *Add tests* +* Unit tests in Zig +* Integration tests +* ABI verification tests +. *Update documentation* +* Function signatures +* Usage examples +* Migration guide (if breaking changes) + +=== License + +MPL-2.0 + +=== See Also + +* https://idris2.readthedocs.io[Idris2 Documentation] +* https://ziglang.org/documentation/master/[Zig Documentation] +* https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium +Standard Repositories] +* link:../ffi-migration-guide.md[FFI Migration Guide] +* link:../abi-migration-guide.md[ABI Migration Guide] diff --git a/vendor/bunsenite/ARCHITECTURE.adoc b/vendor/bunsenite/ARCHITECTURE.adoc new file mode 100644 index 0000000..1c0a7a6 --- /dev/null +++ b/vendor/bunsenite/ARCHITECTURE.adoc @@ -0,0 +1,48 @@ +== Architecture + +=== Overview + +This repository follows a modular, maintainable architecture designed +for clarity, scalability, and long-term sustainability. + +=== Directory Structure + +.... +. +├── src/ # Source code +├── tests/ # Test suites +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── config/ # Configuration files +├── LICENSE # License file +├── LICENSES/ # Full license texts +└── README.adoc # Project documentation +.... + +=== Design Principles + +* *Separation of Concerns*: Each module has a single responsibility +* *Testability*: Code is written to be easily testable +* *Documentation*: All public APIs are documented +* *Configuration*: Environment-specific settings are externalized + +=== Dependencies + +* External dependencies are minimized and clearly declared +* Version pinning is used for reproducibility + +=== Security Considerations + +* Sensitive data is never committed to the repository +* Secrets are managed through environment variables or secure vaults +* Regular dependency audits are performed + +=== Maintainability + +* Code follows consistent style guidelines +* Pull requests require review and CI checks +* Issues and discussions are tracked transparently + +''''' + +_Last updated: 2026-07-18_ diff --git a/vendor/bunsenite/CHANGELOG.adoc b/vendor/bunsenite/CHANGELOG.adoc new file mode 100644 index 0000000..e7691df --- /dev/null +++ b/vendor/bunsenite/CHANGELOG.adoc @@ -0,0 +1,202 @@ +== Changelog + +All notable changes to this project will be documented in this file. + +The format is based on https://keepachangelog.com/en/1.0.0/[Keep a +Changelog], and this project adheres to +https://semver.org/spec/v2.0.0.html[Semantic Versioning]. + +=== [Unreleased] + +==== Planned + +* TUI (Ada/SPARK) +* Language Server Protocol (LSP) +* Additional language bindings (Python, Ruby, Node.js) +* Plugin system + +=== [1.0.0] - 2025-12-12 + +==== Added + +* Zig FFI layer for stable C ABI across Rust compiler versions +* Complete Deno bindings using `+Deno.dlopen+` FFI +* Complete AffineScript bindings via C FFI +* Watch mode with file change detection (`+bunsenite watch+`) +* Interactive REPL (`+bunsenite repl+`) +* JSON Schema validation (`+bunsenite schema+`) +* miette 7.0 integration for beautiful error diagnostics + +==== Changed + +* Upgraded nickel-lang-core to 0.9.1 (CBNCache moved to lazy module) +* CLI expanded from 3 commands to 6 commands +* Documentation updated for v1.0.0 release + +==== Fixed + +* CBNCache import path for nickel-lang-core 0.9.1 compatibility + +==== Compliance + +* RSR Bronze Tier: Verified +* TPCF Perimeter 3: Maintained +* No plain TypeScript, npm, or Python dependencies + +=== [0.1.0] - 2025-11-22 + +==== Added + +* 🎉 Initial release of Bunsenite! +* ✅ Rust core library with nickel-lang-core 0.9.1 integration +* ✅ `+NickelLoader+` API for parsing and evaluating Nickel +configurations +* ✅ Comprehensive error handling with helpful error messages +* ✅ WebAssembly bindings for browser deployment (~95% native speed) +* ✅ Command-line interface with `+parse+`, `+validate+`, and `+info+` +commands +* ✅ Zero `+unsafe+` code (enforced by compiler directive) +* ✅ Complete test suite (30+ tests, 100% pass rate) +* ✅ Full RSR Bronze Tier compliance: +** Type safety (Rust compile-time guarantees) +** Memory safety (ownership model, no unsafe) +** Offline-first (no network dependencies) +** Complete documentation set +** `+.well-known/+` directory (security.txt, ai.txt, humans.txt) +** Build system (Justfile, Guix flake) +** CI/CD pipeline (GitLab CI) +* ✅ TPCF Perimeter 3 (Community Sandbox) contribution model +* ✅ Dual MIT + Palimpsest 0.8 licensing +* ✅ Comprehensive documentation: +** README.md with quick start and examples +** CLAUDE.md for AI assistants and developers +** SECURITY.md with vulnerability reporting +** CONTRIBUTING.md with development workflow +** CODE_OF_CONDUCT.md aligned with TPCF principles +** MAINTAINERS.md with governance structure +* ✅ API documentation with examples +* ✅ FFI binding infrastructure: +** Deno bindings (TypeScript) +** Rescript bindings +** C ABI via Zig (planned) + +==== Technical Details + +===== API Compatibility (nickel-lang-core 0.9.1) + +* `+Program::new_from_source()+` with trace parameter +* `+eval_full()+` with no arguments +* Manual error conversion via `+serde_json::to_value()+` +* No deprecated `+into_diagnostics()+` usage + +===== Dependencies + +* nickel-lang-core 0.9.1 (core parser) +* serde 1.0 (serialization) +* serde_json 1.0 (JSON conversion) +* anyhow 1.0 (error handling) +* thiserror 1.0 (error derive macros) +* clap 4.4 (CLI, optional) +* wasm-bindgen 0.2 (WASM bindings, target-specific) + +===== Build Artifacts + +* CLI binary: `+bunsenite+` (~6.5MB optimized) +* Shared library: `+libbunsenite.so/dylib/dll+` (~6.1MB optimized) +* WASM module: `+bunsenite.wasm+` (size varies by optimization level) + +==== Security + +===== Memory Safety + +* Zero `+unsafe+` code blocks (enforced by `+#![deny(unsafe_code)]+`) +* Rust ownership model prevents: +** Use-after-free +** Double-free +** Null pointer dereferences +** Buffer overflows +** Data races + +===== Supply Chain + +* Minimal dependencies (only essential, well-audited crates) +* No network dependencies (offline-first design) +* Pinned dependency versions for reproducibility +* Regular `+cargo audit+` checks in CI + +==== Performance + +* Native Rust: Baseline performance +* WebAssembly: ~95% native speed +* FFI bindings: ~90% native speed (minimal C ABI overhead) + +==== Known Limitations + +* Nickel evaluation may consume significant memory/CPU for complex +configs +** *Mitigation*: Plan to add configurable timeouts and memory limits +* File I/O respects OS permissions (no privilege escalation) +* WASM runs in browser sandbox (subject to browser security model) + +==== Breaking Changes + +* N/A (initial release) + +==== Deprecations + +* N/A (initial release) + +==== Fixed + +* N/A (initial release) + +==== Contributors + +* Campaign for Cooler Coding and Programming (@cccp) - Initial +implementation + +''''' + +=== Version History + +==== Version Numbering + +We use https://semver.org/[Semantic Versioning]: + +.... +MAJOR.MINOR.PATCH + +MAJOR: Incompatible API changes +MINOR: Backwards-compatible new features +PATCH: Backwards-compatible bug fixes +.... + +==== Release Cadence + +* *Major releases*: As needed for breaking changes +* *Minor releases*: Monthly (if new features ready) +* *Patch releases*: As needed for critical bugs/security + +==== Support Policy + +[cols=",,",options="header",] +|=== +|Version |Support Status |End of Life +|0.1.x |✅ Full support |TBD (current) +|< 0.1.0 |❌ Not supported |N/A +|=== + +''''' + +=== Links + +* https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite[Repository] +* https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues[Issues] +* https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/releases[Releases] +* https://crates.io/crates/bunsenite[Crates.io] (coming soon) + +''''' + +*Note*: This changelog is maintained according to +https://keepachangelog.com/[Keep a Changelog] principles and serves as a +living document of the project’s evolution. diff --git a/vendor/bunsenite/CITATION.cff b/vendor/bunsenite/CITATION.cff new file mode 100644 index 0000000..c93629b --- /dev/null +++ b/vendor/bunsenite/CITATION.cff @@ -0,0 +1,24 @@ +# CITATION.cff - Citation File Format for bunsenite +# https://citation-file-format.github.io/ +# SPDX-License-Identifier: MPL-2.0 OR LicenseRef-Palimpsest-0.5 + +cff-version: 1.2.0 +title: "bunsenite" +message: "If you use this software, please cite it as below." +type: software +authors: + - family-names: "Jewell" + given-names: "Jonathan D.A." + alias: "hyperpolymath" + email: "hyperpolymath@proton.me" + affiliation: "Rhodium Standard / Independent Researcher" +repository-code: "https://github.com/hyperpolymath/bunsenite" +url: "https://rhodium.sh/projects/bunsenite" +abstract: "RSR-compliant project" +keywords: + - RSR + - rhodium-standard +license: PMPL-1.0 +license-url: "https://github.com/hyperpolymath/bunsenite/blob/main/LICENSE.txt" +version: "0.1.0" +date-released: "2025-12-10" diff --git a/vendor/bunsenite/CLAUDE.md b/vendor/bunsenite/CLAUDE.md new file mode 100644 index 0000000..42898a5 --- /dev/null +++ b/vendor/bunsenite/CLAUDE.md @@ -0,0 +1,279 @@ + +# Bunsenite Project + +## Project Overview + +Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. It provides a Rust core library with a Zig C ABI layer that enables bindings for Deno (JavaScript/TypeScript), AffineScript, and WebAssembly for browser and universal use. + +**Status**: v1.0.0 - Production ready +**Repository**: https://github.com/hyperpolymath/bunsenite (mirror: GitLab) +**License**: Dual MPL-2.0 + Palimpsest 0.8 + +## Project Structure + +``` +bunsenite/ +├── src/ +│ ├── lib.rs # Main library entry point +│ ├── main.rs # CLI with parse, validate, watch, repl, schema +│ ├── loader.rs # Nickel file loader (nickel-lang-core 0.9.1 API) +│ └── wasm.rs # WebAssembly bindings +├── zig/ +│ └── bunsenite.zig # Zig C ABI layer (stable FFI interface) +├── bindings/ +│ ├── deno/ # Deno FFI bindings (Deno.dlopen) +│ ├── affinescript/ # AffineScript C FFI bindings +│ └── wasm/ # WASM build target +├── examples/ +│ ├── config.ncl # Full configuration example +│ └── simple.ncl # Minimal example +├── packaging/ # Package manager configs (AUR, deb, rpm, etc.) +├── .github/workflows/ # CI/CD (release, RSR antipattern check) +├── Cargo.toml # Rust dependencies +├── Justfile # Build commands (45+ recipes) +├── CLAUDE.md # This file - AI assistant context +├── STATE.scm # Project state checkpoint +└── LICENSE # MPL-2.0 + Palimpsest dual license +``` + +## Technology Stack + +**Core:** +- Language: Rust (2021 edition, 1.70+) +- Parser: nickel-lang-core 0.9.1 +- Error handling: miette 7.0 (fancy diagnostics) +- Serialization: serde, serde_json + +**FFI Layer:** +- C ABI: Zig (provides stable interface isolating consumers from Rust ABI changes) + +**Bindings:** +- Deno: TypeScript with Deno.dlopen for native FFI (NOT plain TypeScript) +- AffineScript: Direct C FFI bindings +- WebAssembly: wasm-bindgen for browser/universal deployment + +**CLI Features:** +- `parse` - Parse and evaluate Nickel config to JSON +- `validate` - Validate config without evaluation +- `watch` - Watch mode with notify crate +- `repl` - Interactive REPL with rustyline +- `schema` - JSON Schema validation +- `info` - Library and compliance info + +**Build Tools:** +- Build system: Cargo + Justfile (no shell scripts) +- WASM tooling: wasm-pack + +## RSR Compliance + +**Tier**: Bronze +**TPCF Perimeter**: 3 (Community Sandbox) + +**Requirements Met:** +- Type Safety: Compile-time (Rust) +- Memory Safety: Rust ownership model +- Offline-First: No network dependencies +- No Plain TypeScript: Deno FFI uses .ts but calls Deno.dlopen +- No npm/bun: AffineScript package.json is for npm publishing of compiled output +- No Python: Clean +- No Shell Scripts: All builds via Justfile + +## Development Setup + +### Prerequisites + +- Rust toolchain (2021 edition, 1.70+) +- Zig compiler (for C ABI layer) +- just command runner (`cargo install just`) +- Optional: wasm-pack for WebAssembly builds +- Optional: Deno runtime for testing Deno bindings + +### Quick Start + +```bash +# Clone and build +git clone https://github.com/hyperpolymath/bunsenite.git +cd bunsenite +just all + +# Run CLI +cargo run --release -- parse examples/config.ncl --pretty + +# Run with all features +cargo run --release --all-features -- repl +``` + +### Justfile Recipes + +```bash +just # List all recipes +just all # Build all targets +just build # Build release binaries +just wasm # Build WebAssembly +just test # Run all tests +just check # Run all quality checks +just rsr-check # Verify RSR Bronze compliance +just rsr-report # Generate compliance report +``` + +## Code Conventions + +### Style +- Rust standard formatting: `cargo fmt` +- Lint with: `cargo clippy` +- Use explicit error types (anyhow for apps, thiserror for libs) +- Document public APIs with `///` doc comments + +### Testing +- All tests must pass before commit +- Run: `cargo test` +- Coverage: Unit tests + doc tests + +## Architecture + +### Data Flow + +``` +┌─────────────────────────────────────────────────┐ +│ Consumers │ +├───────────────┬───────────────┬─────────────────┤ +│ Deno │ AffineScript │ Browser │ +│ (Deno FFI) │ (C FFI) │ (WASM) │ +└───────┬───────┴───────┬───────┴────────┬────────┘ + │ │ │ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────────┐ + │ Zig FFI │ │ Zig FFI │ │ wasm-bindgen │ + │ (C ABI) │ │ (C ABI) │ │ │ + └─────┬────┘ └─────┬────┘ └──────┬───────┘ + │ │ │ + └──────────────┴─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ Rust Core │ + │ │ + │ nickel-lang-core│ + │ 0.9.1 │ + │ │ + │ miette errors │ + └─────────────────┘ +``` + +### Key Components + +1. **src/lib.rs**: Public API entry point +2. **src/loader.rs**: Nickel parser using nickel-lang-core 0.9.1 +3. **src/main.rs**: CLI with parse, validate, watch, repl, schema commands +4. **src/wasm.rs**: WebAssembly bindings +5. **zig/bunsenite.zig**: Stable C ABI wrapper +6. **bindings/deno/**: Deno FFI (Deno.dlopen) +7. **bindings/affinescript/**: AffineScript C FFI + +## Critical Design Decisions + +**REQUIRED Technologies:** +- Rust core +- Zig C ABI layer (stable FFI) +- Deno bindings (Deno.dlopen, NOT plain TypeScript) +- AffineScript bindings (via C FFI) +- WebAssembly bindings +- Justfile for builds + +**NOT ALLOWED (RSR Compliance):** +- Plain TypeScript (Deno .ts files are FFI, not compiled TS) +- Shell scripts (use Justfile) +- npm/bun for primary build (package.json for AffineScript npm publishing only) +- bun:ffi (ALWAYS use Deno.dlopen instead) +- ffi-napi / Node.js FFI (ALWAYS use Deno.dlopen instead) +- Python (except SaltStack support contexts) + +**IMPORTANT:** If JavaScript FFI is needed, ALWAYS use Deno's Deno.dlopen. +Never create bun:ffi or node ffi-napi files. This is a strict RSR requirement. + +**Future:** +- TUI: Ada/SPARK (planned for v2.0) +- LSP: tower-lsp (research phase) + +## API Compatibility Notes + +**nickel-lang-core 0.9.1:** +1. `Program::new_from_source()` requires trace parameter: `std::io::sink()` +2. `eval_full()` takes no arguments +3. Manual error conversion via `serde_json::to_value()` +4. NO `into_diagnostics()` method + +See `src/loader.rs` for correct usage patterns. + +## Notes for AI Assistants + +### Project State + +- **Version**: 1.0.0 (production ready) +- **All features complete**: CLI, FFI, bindings, watch, REPL, schema +- **RSR Bronze compliant** +- **TPCF Perimeter 3** + +### When Making Changes + +- Use Justfile commands, NOT shell scripts +- Run `cargo test` before commit +- Run `cargo fmt` and `cargo clippy` +- Update STATE.scm if project state changes +- Follow RSR guidelines (no TS, no npm, no Python) + +### State File + +The `STATE.scm` file tracks project state in machine-readable Scheme format. Update it when: +- Completing major features +- Changing project phase +- Modifying architecture + +### User Preferences + +- Deno preferred over npm/bun +- AffineScript preferred over TypeScript +- Ada/SPARK for TUI (future) +- No shell scripts (Justfile only) +- Offline-first design +- Emotional safety considerations + +## CI/CD Notes + +**Build Times (per platform):** +- Rust compilation: ~7-10 minutes (with `--features full`) +- Zig FFI build: ~30 seconds +- Packaging/upload: ~1 minute +- Total: ~10-15 minutes per platform + +**Important:** Build times do NOT affect end users - they download pre-built binaries. +These times are CI/CD only (release workflow on tag push). + +**Optimization opportunities:** +- Cargo caching is configured but GitHub's cache service can be unreliable +- Consider reducing targets if not all platforms are needed +- Cross-compilation (aarch64-linux) uses Docker containers and is slower + +**Zig FFI Status by Platform:** +- Linux x86_64: Full Zig FFI support +- macOS (both archs): Full Zig FFI support +- Linux aarch64: Rust binary only (cross-compilation, Zig FFI skipped) +- Windows: Rust binary only (Zig FFI skipped, needs import library setup) + +## Changelog + +- **2025-12-12**: Updated to v1.0.0 + - All features complete + - Zig FFI layer implemented + - Watch, REPL, schema commands + - miette error diagnostics + - RSR Bronze compliant + +- **2025-11-21**: Initial CLAUDE.md (v0.1.0) + +--- + +**Note**: Keep STATE.scm and CLAUDE.md updated to help AI assistants and developers understand project state quickly. diff --git a/vendor/bunsenite/CODE_OF_CONDUCT.adoc b/vendor/bunsenite/CODE_OF_CONDUCT.adoc new file mode 100644 index 0000000..070c601 --- /dev/null +++ b/vendor/bunsenite/CODE_OF_CONDUCT.adoc @@ -0,0 +1,175 @@ +== Code of Conduct + +=== Our Pledge + +We as members, contributors, and leaders pledge to make participation in +our community a harassment-free experience for everyone, regardless of +age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, +welcoming, diverse, inclusive, and healthy community. + +=== Our Standards + +==== Examples of behavior that contributes to a positive environment: + +* *Demonstrating empathy and kindness* toward other people +* *Being respectful* of differing opinions, viewpoints, and experiences +* *Giving and gracefully accepting* constructive feedback +* *Accepting responsibility* and apologizing to those affected by our +mistakes, and learning from the experience +* *Focusing on what is best* not just for us as individuals, but for the +overall community +* *Using welcoming and inclusive language* +* *Being patient* with new contributors and those learning +* *Celebrating successes* of others +* *Supporting emotional safety* and reversibility in development + +==== Examples of unacceptable behavior: + +* The use of sexualized language or imagery, and sexual attention or +advances of any kind +* Trolling, insulting or derogatory comments, and personal or political +attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or email +address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting +* *Dismissing or minimizing* concerns about emotional safety +* *Gatekeeping* or elitism based on technical skill level +* *Weaponizing vulnerability* or reversibility features + +=== Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our +standards of acceptable behavior and will take appropriate and fair +corrective action in response to any behavior that they deem +inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, and will +communicate reasons for moderation decisions when appropriate. + +=== Scope + +This Code of Conduct applies within all community spaces, and also +applies when an individual is officially representing the community in +public spaces. Examples of representing our community include using an +official e-mail address, posting via an official social media account, +or acting as an appointed representative at an online or offline event. + +=== Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported to the community leaders responsible for enforcement at: + +* *GitHub Issues*: +https://github.com/hyperpolymath/bunsenite/issues/new?labels=conduct[Report +a concern] +* *GitLab*: Confidential issue on the repository + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security +of the reporter of any incident. + +=== Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in +determining the consequences for any action they deem in violation of +this Code of Conduct: + +==== 1. Correction + +*Community Impact*: Use of inappropriate language or other behavior +deemed unprofessional or unwelcome in the community. + +*Consequence*: A private, written warning from community leaders, +providing clarity around the nature of the violation and an explanation +of why the behavior was inappropriate. A public apology may be +requested. + +==== 2. Warning + +*Community Impact*: A violation through a single incident or series of +actions. + +*Consequence*: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, for a specified period of +time. This includes avoiding interactions in community spaces as well as +external channels like social media. Violating these terms may lead to a +temporary or permanent ban. + +==== 3. Temporary Ban + +*Community Impact*: A serious violation of community standards, +including sustained inappropriate behavior. + +*Consequence*: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No +public or private interaction with the people involved, including +unsolicited interaction with those enforcing the Code of Conduct, is +allowed during this period. Violating these terms may lead to a +permanent ban. + +==== 4. Permanent Ban + +*Community Impact*: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of +individuals. + +*Consequence*: A permanent ban from any sort of public interaction +within the community. + +=== Emotional Safety Principles + +In alignment with our values of emotional safety and reversibility: + +==== Encouragement Over Criticism + +* *Positive framing*: Frame feedback constructively +* *Assume good intent*: Mistakes are learning opportunities +* *Celebrate experimentation*: Failures are valuable when reversible +* *Support learning*: Everyone is learning, regardless of experience +level + +==== Reversibility in Community Interactions + +* *Mistakes can be fixed*: Technical mistakes are reversible through Git +* *Apologies matter*: Sincere apologies can repair social mistakes +* *Growth mindset*: People can change and improve +* *Second chances*: Unless patterns of harm persist + +==== Political Autonomy + +* *Technical decisions*: Based on merit, not politics +* *No gatekeeping*: Access based on conduct, not views +* *Respectful disagreement*: Disagree on ideas, not people +* *Community sovereignty*: This community makes its own decisions + +=== Attribution + +This Code of Conduct is adapted from the +https://www.contributor-covenant.org[Contributor Covenant], version 2.1, +available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +Community Impact Guidelines were inspired by +https://github.com/mozilla/diversity[Mozilla’s code of conduct +enforcement ladder]. + +For answers to common questions about this code of conduct, see the FAQ +at https://www.contributor-covenant.org/faq. Translations are available +at https://www.contributor-covenant.org/translations. + +''''' + +*Last updated*: 2025-11-22 *Version*: 1.0.0 (Aligned with RSR Framework +& TPCF principles) diff --git a/vendor/bunsenite/Cargo.lock b/vendor/bunsenite/Cargo.lock new file mode 100644 index 0000000..f95ff3a --- /dev/null +++ b/vendor/bunsenite/Cargo.lock @@ -0,0 +1,4025 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[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", + "getrandom 0.3.4", + "once_cell", + "serde", + "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 = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[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 = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[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 = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + +[[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 = "bon" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bunsenite" +version = "1.0.2" +dependencies = [ + "anyhow", + "clap", + "console_error_panic_hook", + "criterion", + "jsonschema", + "miette", + "nickel-lang-core", + "notify", + "pretty_assertions", + "rustyline", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "wasm-bindgen", +] + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[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 = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583f52b0658b321b25fd6b209b6c76cf058f433071297de64e5980c3d9aad937" +dependencies = [ + "codespan-reporting", + "serde", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "comrak" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab87129dce2f2d7e75e753b1df0e5093b27dec8fa5970b6eb51280faacb25bd6" +dependencies = [ + "bon", + "caseless", + "clap", + "emojis", + "entities", + "fmt2io", + "jetscii", + "shell-words", + "syntect", + "typed-arena", + "unicode_categories", + "xdg", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "coolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3" +dependencies = [ + "crossterm", +] + +[[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 = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "crokey" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a63daf06a168535c74ab97cdba3ed4fa5d4f32cb36e437dcceb83d66854b7c" +dependencies = [ + "crokey-proc_macros", + "crossterm", + "once_cell", + "serde", + "strict", +] + +[[package]] +name = "crokey-proc_macros" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847f11a14855fc490bd5d059821895c53e77eeb3c2b73ee3dded7ce77c93b231" +dependencies = [ + "crossterm", + "proc-macro2", + "quote", + "strict", + "syn 2.0.117", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[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-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio 1.1.1", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[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.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[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 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "emojis" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1f1df1f181f2539bac8bf027d31ca5ffbf9e559e3f2d09413b9107b5c02f4" +dependencies = [ + "phf", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + +[[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 = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set 0.5.3", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set 0.8.0", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fmt2io" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b6129284da9f7e5296cc22183a63f24300e945e297705dcc0672f7df01d62c8" + +[[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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[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 2.0.117", +] + +[[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-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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +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", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[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 = [ + "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 = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "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", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[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 = "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 = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "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 = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "iso8601" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1082f0c48f143442a1ac6122f67e360ceee130b967af4d50996e5154a45df46" +dependencies = [ + "nom", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json_scanner" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0a2dc336065c75719cffd3c6c929e0ec4ed85b92b8248a7bbd999acb0e419c" +dependencies = [ + "memchr", +] + +[[package]] +name = "jsonschema" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" +dependencies = [ + "ahash", + "anyhow", + "base64", + "bytecount", + "clap", + "fancy-regex 0.13.0", + "fraction", + "getrandom 0.2.16", + "iso8601", + "itoa", + "memchr", + "num-cmp", + "once_cell", + "parking_lot", + "percent-encoding", + "regex", + "reqwest", + "serde", + "serde_json", + "time", + "url", + "uuid", +] + +[[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 = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set 0.8.0", + "ena", + "itertools 0.14.0", + "lalrpop-util", + "petgraph", + "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 = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.6.0", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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 2.0.117", +] + +[[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 = "malachite" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de8195e0d0bccfa3e54997e8e7c6c67859b08512067801b5a63dd0b7a174e87" +dependencies = [ + "malachite-base", + "malachite-float", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b6f86fdbb1eb9955946be91775239dfcb0acdb1a51bb07d5fc9b8c854f5ccd" +dependencies = [ + "hashbrown 0.16.1", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-float" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d5021773c1552820b10ce7410817fadc1dfcef907b4f9a29af5346d756fd28" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", + "malachite-q", + "serde", +] + +[[package]] +name = "malachite-nz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0197a2f5cfee19d59178e282985c6ca79a9233e26a2adcf40acb693896aa09f6" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "serde", + "wide", +] + +[[package]] +name = "malachite-q" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2add95162aede090c48f0ee51bea7d328847ce3180aa44588111f846cc116b" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", + "serde", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "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 2.0.117", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimad" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b688969b16915f3ecadc7829d5b7779dee4977e503f767f34136803d5c06f" +dependencies = [ + "once_cell", +] + +[[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 = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[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 = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nickel-lang-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692d8a2ba34c633bc37e704dc94f4ca33edaa8fbf6d08efdcadb81db333ccdb6" +dependencies = [ + "anstyle", + "base64", + "bumpalo", + "codespan", + "codespan-reporting", + "colorchoice", + "comrak", + "indexmap", + "indoc", + "json_scanner", + "lalrpop", + "lalrpop-util", + "logos", + "malachite", + "malachite-q", + "md-5", + "nickel-lang-parser", + "nickel-lang-vector", + "once_cell", + "ouroboros", + "paste", + "pretty", + "regex", + "rustyline", + "rustyline-derive", + "saphyr-parser", + "serde", + "serde_json", + "serde_yaml", + "sha-1", + "sha2", + "simple-counter", + "smallvec", + "strip-ansi-escapes", + "strsim", + "termimad", + "toml", + "toml_edit", + "topiary-core", + "topiary-queries", + "tree-sitter-nickel", + "typed-arena", + "unicode-segmentation", +] + +[[package]] +name = "nickel-lang-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7aaf73e60b66ef4fffc969b0e4e419a15a029525f9b53f2f5cc0ca41bbe17ff" +dependencies = [ + "bumpalo", + "codespan", + "codespan-reporting", + "indexmap", + "lalrpop", + "lalrpop-util", + "logos", + "malachite", + "nickel-lang-vector", + "ouroboros", + "pretty", + "regex", + "saphyr-parser", + "serde", + "serde_json", + "simple-counter", + "toml_edit", + "typed-arena", +] + +[[package]] +name = "nickel-lang-vector" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f243832286908d8873add24a905d6732ffabd6cfb2bf74cb18d667e892e279" +dependencies = [ + "imbl-sized-chunks", + "serde", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.10.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[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 = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.10.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[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 = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[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", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[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-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 = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[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 = "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 = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettydiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9a475bdea0881b8c65eb81f91fe53187b8522352a701b919c5a2c8a2f262808" +dependencies = [ + "owo-colors", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[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 = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "version_check", + "yansi", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustyline" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width 0.2.2", + "utf8parse", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustyline-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d66de233f908aebf9cc30ac75ef9103185b4b715c6f2fb7a626aa5e5ede53ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed" +dependencies = [ + "bytemuck", +] + +[[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 = "saphyr-parser" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb771b59f6b1985d1406325ec28f97cfb14256abcec4fdfb37b36a1766d6af7" +dependencies = [ + "arraydeque", + "hashlink", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +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_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[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" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 1.1.1", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simple-counter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb57743b52ea059937169c0061d70298fe2df1d2c988b44caae79dd979d9b49" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +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 = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strict" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" + +[[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 = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +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 2.0.117", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex 0.16.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.17", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "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 = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termimad" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "889a9370996b74cf46016ce35b96c248a9ac36d69aab1d112b3e09bc33affa49" +dependencies = [ + "coolor", + "crokey", + "crossbeam", + "lazy-regex", + "minimad", + "serde", + "thiserror 2.0.17", + "unicode-width 0.1.14", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "libc", + "mio 1.1.1", + "pin-project-lite", + "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 2.0.117", +] + +[[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", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[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_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "topiary-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89df094e19f103c5b8e120a1ffa30a6309daa10bef8d186e598a3df633e6a221" +dependencies = [ + "futures", + "itertools 0.11.0", + "log", + "miette", + "pretty_assertions", + "prettydiff", + "rayon", + "serde", + "serde_json", + "streaming-iterator", + "thiserror 2.0.17", + "tokio", + "topiary-tree-sitter-facade", + "topiary-web-tree-sitter-sys", + "tree-sitter", +] + +[[package]] +name = "topiary-queries" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13439d04bb7987de5f937071c8131c995f3d18fcc0df6ce4ab33180a88fbc72c" + +[[package]] +name = "topiary-tree-sitter-facade" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b7f801962f0e1d022f78a46c6afa2d2158138a3955dbbd25bb92cc5ef61ddb" +dependencies = [ + "js-sys", + "streaming-iterator", + "topiary-web-tree-sitter-sys", + "tree-sitter", + "tree-sitter-language", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "topiary-web-tree-sitter-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9877bfc1ad20d17e6da579911925768df2edd6e276300d660265940881d7b9d" +dependencies = [ + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tree-sitter" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dab76d0b724ba557954125188cf0633a1ca43199ced82d95c7b9c32cc3de1f3" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "tree-sitter-nickel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7bb930cf314466ad3ca1e45c876bbbca228f66fe92db8a087796cf8f26d3ba8" +dependencies = [ + "cc", + "tree-sitter", + "tree-sitter-language", +] + +[[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 = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[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-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[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 = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[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.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wide" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[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-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[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 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 2.0.117", + "synstructure", +] + +[[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 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/vendor/bunsenite/Cargo.toml b/vendor/bunsenite/Cargo.toml new file mode 100644 index 0000000..f85d6bb --- /dev/null +++ b/vendor/bunsenite/Cargo.toml @@ -0,0 +1,123 @@ +[package] +name = "bunsenite" +version = "1.0.2" +authors = ["Campaign for Cooler Coding and Programming"] +edition = "2021" +rust-version = "1.70" +description = "Nickel configuration file parser with multi-language FFI bindings" +documentation = "https://docs.rs/bunsenite" +repository = "https://github.com/hyperpolymath/bunsenite" +license = "MPL-2.0" +keywords = ["nickel", "config", "parser", "ffi", "wasm"] +categories = ["config", "parsing", "wasm", "api-bindings"] +readme = "README.adoc" +exclude = [ + "/.git", + "/.gitlab", + "/target", + "/examples/*/target", +] + +[lib] +name = "bunsenite" +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "bunsenite" +path = "src/main.rs" + +[dependencies] +# Core Nickel parser - pinned to 0.9.1 for API stability +nickel-lang-core = { version = "0.18.0", default-features = false } + +# Serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# Error handling +anyhow = "1.0" +thiserror = "1.0" +miette = { version = "7.0", features = ["fancy"] } + +# Watch mode +notify = { version = "6.1", optional = true } + +# REPL +rustyline = { version = "15.0", optional = true } + +# Schema validation +jsonschema = { version = "0.18", optional = true } + +# CLI (optional, for binary only) +clap = { version = "4.6", features = ["derive", "cargo"], optional = true } + +# WASM support +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +console_error_panic_hook = "0.1" +# Note: wee_alloc was removed as it is unmaintained and has known memory leaks. +# Rust 1.71+ provides a suitable default allocator for wasm32 targets. + +[dev-dependencies] +# Testing +pretty_assertions = "1.4" +tempfile = "3.27" +criterion = { version = "0.8", features = ["html_reports"] } + +[[bench]] +name = "parser" +harness = false + +[[bench]] +name = "bunsenite_bench" +harness = false + +[features] +default = ["cli"] +cli = ["dep:clap"] +wasm = [] +watch = ["dep:notify", "cli"] +repl = ["dep:rustyline", "cli"] +schema = ["dep:jsonschema"] +full = ["cli", "watch", "repl", "schema"] + +# Offline-first: No network dependencies, all features work air-gapped +# Type safety: Rust compiler guarantees +# Memory safety: Rust ownership model, zero unsafe blocks (enforced in CI) + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +strip = true +panic = "abort" + +[profile.release-with-debug] +inherits = "release" +strip = false +debug = true + +# WASM optimization +[profile.wasm-release] +inherits = "release" +opt-level = "z" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +# Security audit configuration +[package.metadata.audit] +# Ignore advisories that don't apply +ignore = [] + +# RSR Framework Metadata +[package.metadata.rsr] +tier = "bronze" +compliance-version = "1.0.0" +offline-first = true +type-safety = "compile-time" +memory-safety = "rust-ownership" +tpcf-perimeter = 3 # Community Sandbox +security-contact = "https://github.com/hyperpolymath/bunsenite/security/advisories/new" diff --git a/vendor/bunsenite/Containerfile b/vendor/bunsenite/Containerfile new file mode 100644 index 0000000..d65141d --- /dev/null +++ b/vendor/bunsenite/Containerfile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MPL-2.0 OR Palimpsest-0.8 +# SPDX-FileCopyrightText: 2025 hyperpolymath + +FROM rust:1.85-slim-bookworm AS builder + +WORKDIR /build + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libreadline-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY Cargo.toml Cargo.lock* ./ +COPY src/ src/ +COPY benches/ benches/ + +RUN cargo build --release --bin bunsenite + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libreadline8 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /build/target/release/bunsenite /usr/local/bin/bunsenite + +ENTRYPOINT ["bunsenite"] +CMD ["--help"] diff --git a/vendor/bunsenite/EXPLAINME.adoc b/vendor/bunsenite/EXPLAINME.adoc new file mode 100644 index 0000000..1d96c36 --- /dev/null +++ b/vendor/bunsenite/EXPLAINME.adoc @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite — Show Me The Receipts +:toc: +:icons: font + +The README makes claims. This file backs them up. + +[quote, README] +____ +Bunsenite is a Nickel configuration file parser with Rust core, Zig FFI, and multi-language bindings (Deno, AffineScript, WASM). +____ + +The architecture layers Rust (parsing) → Zig (stable C ABI) → language-specific bindings. This prevents Rust ABI churn from breaking downstream consumers; the C ABI is stable across Rust compiler versions. + +== Two Verifiable Claims from How-It-Works + +=== Claim 1: Zig FFI Isolates Consumers from Rust ABI Changes + +**Location**: `/var/mnt/eclipse/repos/bunsenite/zig/bunsenite.zig` (Zig C ABI wrapper around Rust core) + +**How verified**: The Zig FFI layer exposes a pure C ABI (no Rust `repr(Rust)` types). Functions like `parse_nickel_string()` and `validate_config()` take C-compatible types (pointers, `size_t`, `int`) and call Rust functions via `extern "C"`. README (§Design Rationale) claims "stable C ABI isolates consumers from Rust ABI changes." This is validated by the fact that Deno/AffineScript bindings use `Deno.dlopen()` and direct C FFI, not `rustler` or other Rust-specific bridges. If Rust ABI changed, only the Zig layer needs updating, not the bindings themselves. + +**Caveat**: The Zig FFI is currently manually maintained. No formal proof that generated C headers match the Rust implementation. This works in practice due to hand verification but is not formally certified. + +=== Claim 2: CLI Parse/Validate Commands Route Correctly to Library + +**Location**: `/var/mnt/eclipse/repos/bunsenite/src/main.rs` (CLI entry point delegating to lib.rs) + +**How verified**: The CLI (`bunsenite parse `, `bunsenite validate `) calls library functions via `src/lib.rs` which wraps `nickel_lang_core 0.9.1`. Each CLI command creates a `NickelLoader` instance (defined in `src/loader.rs`), invokes appropriate methods, and formats output. README (§Usage, CLI section) documents the three commands; the code implements them. The CI/CD runs these commands against example configs to verify correctness. + +**Caveat**: CLI and library use different error handling (CLI uses `miette` for pretty errors, library uses `thiserror` types). Some errors may format differently between CLI and programmatic use. + +== Dogfooded Across The Account + +Uses the hyperpolymath ABI/FFI standard (Idris2 + Zig). Same pattern used across +https://github.com/hyperpolymath/proven[proven], +https://github.com/hyperpolymath/burble[burble], and +https://github.com/hyperpolymath/gossamer[gossamer]. + +Critical path: Idris2 ABI specs → Zig FFI implementation → Deno/AffineScript bindings → end-user code. + +== File Map + +[cols="1,2"] +|=== +| Path | What's There + +| `src/lib.rs` | Public library API entry point; exports `NickelLoader`, `parse_*`, `validate_*` functions +| `src/loader.rs` | `NickelLoader` struct wrapping `nickel_lang_core` API; handles file I/O and evaluation +| `src/main.rs` | CLI entry point with subcommands: parse, validate, watch, repl, schema, info +| `src/wasm.rs` | WebAssembly bindings via wasm-bindgen; exports `parse_nickel()` function for browser +| `zig/bunsenite.zig` | Stable C ABI layer; wraps Rust library with C-compatible function signatures +| `bindings/deno/bunsenite.ts` | Deno FFI bindings using `Deno.dlopen()` to call Zig C ABI functions +| `bindings/affinescript/bindings.res` | AffineScript C FFI bindings to call Zig C ABI; compiles to JavaScript +| `examples/config.ncl` | Example Nickel config demonstrating features (loops, functions, conditionals) +| `examples/simple.ncl` | Minimal config for testing parsing +| `Justfile` | Build recipes: `just all`, `just wasm`, `just test`, `just rsr-check` +|=== + +== Testing Critical Paths + +* **Library correctness**: `cargo test` — Rust unit tests for parser, loader, evaluation +* **CLI functionality**: `Justfile` test recipes verify `parse`, `validate`, `watch`, `repl` commands +* **WASM builds**: `just wasm` and `wasm-pack test` validate browser-compatible builds +* **FFI soundness**: Deno/AffineScript bindings tested against known Nickel configs +* **RSR compliance**: `just rsr-check` validates Bronze tier requirements + +== Questions? + +Open an issue or reach out directly — happy to explain anything in more detail. diff --git a/vendor/bunsenite/GEMINI.md b/vendor/bunsenite/GEMINI.md new file mode 100644 index 0000000..417391d --- /dev/null +++ b/vendor/bunsenite/GEMINI.md @@ -0,0 +1,8 @@ +# Pointer + +This repository has no `AGENTS.md` yet. Until it does, the instructions +for every coding agent live in **[CLAUDE.md](./CLAUDE.md)**. Read that +file, and skip anything in it that is specific to Claude Code tooling. +Do not duplicate rules here. + +When `AGENTS.md` lands in this repository, retarget this pointer at it. diff --git a/vendor/bunsenite/GOVERNANCE.adoc b/vendor/bunsenite/GOVERNANCE.adoc new file mode 100644 index 0000000..9b836fb --- /dev/null +++ b/vendor/bunsenite/GOVERNANCE.adoc @@ -0,0 +1,60 @@ +== Governance + +=== Overview + +This project is governed by the following principles and structures to +ensure transparent, inclusive, and effective decision-making. + +=== Roles and Responsibilities + +==== Maintainers + +Maintainers are responsible for: - Reviewing and merging pull requests - +Managing releases and versioning - Ensuring code quality and standards - +Triaging issues and bug reports - Community engagement and support + +==== Contributors + +Contributors are expected to: - Follow the code of conduct - Submit +well-documented pull requests - Write tests for new functionality - +Maintain existing tests - Update documentation as needed + +=== Decision Making + +==== Minor Changes + +* Can be made by any maintainer +* Include bug fixes, documentation updates, dependency updates + +==== Major Changes + +* Require discussion in issues or pull requests +* Include new features, architectural changes, API changes +* Need approval from at least 2 maintainers + +==== Breaking Changes + +* Require RFC (Request for Comments) process +* Need approval from majority of maintainers +* Must include migration guide + +=== Code of Conduct + +All participants are expected to follow our Code of Conduct. Violations +can be reported to the maintainers. + +=== Communication + +* *Issues*: For bug reports and feature requests +* *Discussions*: For questions and general discussion +* *Pull Requests*: For code contributions + +=== Licensing + +All contributions are made under the terms of the repository’s LICENSE +file. By submitting a pull request, you agree to license your +contributions accordingly. + +''''' + +_Last updated: 2026-07-18_ diff --git a/vendor/bunsenite/Justfile b/vendor/bunsenite/Justfile new file mode 100644 index 0000000..0922728 --- /dev/null +++ b/vendor/bunsenite/Justfile @@ -0,0 +1,230 @@ +# bunsenite - Rust Development Tasks +set shell := ["bash", "-uc"] +set dotenv-load := true + +import? "contractile.just" + +project := "bunsenite" + +# Show all recipes +default: + @just --list --unsorted + +# Build debug +build: + cargo build + +# Build release +build-release: + cargo build --release + +# Run tests +test: + cargo test + +# Run tests verbose +test-verbose: + cargo test -- --nocapture + +# Format code +fmt: + cargo fmt + +# Check formatting +fmt-check: + cargo fmt -- --check + +# Run clippy lints +lint: + cargo clippy -- -D warnings + +# Check without building +check: + cargo check + +# Clean build artifacts +clean: + cargo clean + +# Run the project +run *ARGS: + cargo run -- {{ARGS}} + +# Generate docs +doc: + cargo doc --no-deps --open + +# Update dependencies +update: + cargo update + +# Audit dependencies +audit: + cargo audit + +# Validate K9 configurations +validate-k9: + @echo "Validating K9 configs..." + nickel eval config/rust-fmt.k9.ncl > /dev/null && echo "✓ rust-fmt.k9.ncl valid" + nickel eval config/build.k9.ncl > /dev/null && echo "✓ build.k9.ncl valid" + @echo "All K9 configs valid!" + +# Generate rustfmt.toml from K9 config +generate-rustfmt: + nickel export config/rust-fmt.k9.ncl -f 'rustfmt_toml' > rustfmt.toml + @echo "Generated rustfmt.toml from K9 config" + +# K9 dogfooding: validate configs before use +dogfood: validate-k9 + @echo "K9 dogfooding: The Nickel tool validates itself with K9!" + +# All checks before commit (including K9 validation) +pre-commit: validate-k9 fmt-check lint test + @echo "All checks passed!" + +# Run panic-attacker pre-commit scan +assail: + @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" + +# Synchronize A2ML metadata to SCM (Shadow Sync) +sync-metadata: + #!/usr/bin/env bash + echo "Synchronizing metadata (A2ML -> SCM)..." + if [ -f .machine_readable/STATE.a2ml ]; then + echo "✓ Metadata synchronized" + fi + +# [AUTO-GENERATED] Multi-arch / RISC-V target +build-riscv: + @echo "Building for RISC-V..." + cross build --target riscv64gc-unknown-linux-gnu + +# ═══════════════════════════════════════════════════════════════════════════════ +# ONBOARDING & DIAGNOSTICS +# ═══════════════════════════════════════════════════════════════════════════════ + +# Check all required toolchain dependencies and report health +doctor: + #!/usr/bin/env bash + echo "═══════════════════════════════════════════════════" + echo " Bunsenite Doctor — Toolchain Health Check" + echo "═══════════════════════════════════════════════════" + echo "" + PASS=0; FAIL=0; WARN=0 + check() { + local name="$1" cmd="$2" min="$3" + if command -v "$cmd" >/dev/null 2>&1; then + VER=$("$cmd" --version 2>&1 | head -1) + echo " [OK] $name — $VER" + PASS=$((PASS + 1)) + else + echo " [FAIL] $name — not found (need $min+)" + FAIL=$((FAIL + 1)) + fi + } + check "just" just "1.25" + check "git" git "2.40" + check "Rust (cargo)" cargo "1.80" + check "Zig" zig "0.13" +# Optional tools +if command -v panic-attack >/dev/null 2>&1; then + echo " [OK] panic-attack — available" + PASS=$((PASS + 1)) +else + echo " [WARN] panic-attack — not found (pre-commit scanner)" + WARN=$((WARN + 1)) +fi + echo "" + echo " Result: $PASS passed, $FAIL failed, $WARN warnings" + if [ "$FAIL" -gt 0 ]; then + echo " Run 'just heal' to attempt automatic repair." + exit 1 + fi + echo " All required tools present." + +# Attempt to automatically install missing tools +heal: + #!/usr/bin/env bash + echo "═══════════════════════════════════════════════════" + echo " Bunsenite Heal — Automatic Tool Installation" + echo "═══════════════════════════════════════════════════" + echo "" +if ! command -v cargo >/dev/null 2>&1; then + echo "Installing Rust via rustup..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" +fi +if ! command -v just >/dev/null 2>&1; then + echo "Installing just..." + cargo install just 2>/dev/null || echo "Install just from https://just.systems" +fi + echo "" + echo "Heal complete. Run 'just doctor' to verify." + +# Guided tour of the project structure and key concepts +tour: + #!/usr/bin/env bash + echo "═══════════════════════════════════════════════════" + echo " Bunsenite — Guided Tour" + echo "═══════════════════════════════════════════════════" + echo "" + echo '> Nickel configuration file parser with multi-language FFI bindings' + echo "" + echo "Key directories:" + echo " src/ Source code" + echo " ffi/ Foreign function interface (Zig)" + echo " src/abi/ Idris2 ABI definitions" + echo " docs/ Documentation" + echo " tests/ Test suite" + echo " .github/workflows/ CI/CD workflows" + echo " contractiles/ Must/Trust/Dust contracts" + echo " .machine_readable/ Machine-readable metadata" + echo " examples/ Usage examples" + echo "" + echo "Quick commands:" + echo " just doctor Check toolchain health" + echo " just heal Fix missing tools" + echo " just help-me Common workflows" + echo " just default List all recipes" + echo "" + echo "Read more: README.adoc, EXPLAINME.adoc" + +# Show help for common workflows +help-me: + #!/usr/bin/env bash + echo "═══════════════════════════════════════════════════" + echo " Bunsenite — Common Workflows" + echo "═══════════════════════════════════════════════════" + echo "" +echo "FIRST TIME SETUP:" +echo " just doctor Check toolchain" +echo " just heal Fix missing tools" +echo "" + echo "DEVELOPMENT:" + echo " cargo build Build the project" + echo " cargo test Run tests" + echo "" +echo "PRE-COMMIT:" +echo " just assail Run panic-attacker scan" +echo "" +echo "LEARN:" +echo " just tour Guided project tour" +echo " just default List all recipes" + + +# Print the current CRG grade (reads from READINESS.md '**Current Grade:** X' line) +crg-grade: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + echo "$$grade" + +# Generate a shields.io badge markdown for the current CRG grade +# Looks for '**Current Grade:** X' in READINESS.md; falls back to X +crg-badge: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + case "$$grade" in \ + A) color="brightgreen" ;; B) color="green" ;; C) color="yellow" ;; \ + D) color="orange" ;; E) color="red" ;; F) color="critical" ;; \ + *) color="lightgrey" ;; esac; \ + echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" diff --git a/vendor/bunsenite/LICENSE b/vendor/bunsenite/LICENSE new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/vendor/bunsenite/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/bunsenite/LICENSES/AGPL-3.0-or-later.txt b/vendor/bunsenite/LICENSES/AGPL-3.0-or-later.txt new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/vendor/bunsenite/LICENSES/AGPL-3.0-or-later.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/vendor/bunsenite/LICENSES/CC-BY-SA-4.0.txt b/vendor/bunsenite/LICENSES/CC-BY-SA-4.0.txt new file mode 100644 index 0000000..2d58298 --- /dev/null +++ b/vendor/bunsenite/LICENSES/CC-BY-SA-4.0.txt @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/vendor/bunsenite/LICENSES/MPL-2.0.txt b/vendor/bunsenite/LICENSES/MPL-2.0.txt new file mode 100644 index 0000000..d0a1fa1 --- /dev/null +++ b/vendor/bunsenite/LICENSES/MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/bunsenite/MAINTAINERS b/vendor/bunsenite/MAINTAINERS new file mode 100644 index 0000000..37f6411 --- /dev/null +++ b/vendor/bunsenite/MAINTAINERS @@ -0,0 +1,43 @@ +# Maintainers + +This file lists the current maintainers of this project. + +## Active Maintainers + +| Name | GitHub | Role | Since | +|------|--------|------|-------| +| Metadatastician | @metadatastician | Primary | Project Start | + +## Emeritus Maintainers + +None at this time. + +## Becoming a Maintainer + +To become a maintainer: + +1. Demonstrate consistent, high-quality contributions +2. Show understanding of the project's goals and architecture +3. Be active in code reviews and community discussions +4. Be nominated by an existing maintainer +5. Be approved by consensus of existing maintainers + +## Maintainer Responsibilities + +- Reviewing and merging pull requests +- Managing releases +- Triaging issues +- Enforcing code standards +- Mentoring new contributors +- Participating in decision-making + +## Maintainer Expectations + +- Respond to issues and PRs in a timely manner +- Follow the code of conduct +- Be transparent in decision-making +- Communicate clearly and respectfully + +--- + +*Last updated: 2026-07-18* diff --git a/vendor/bunsenite/MAINTAINERS.adoc b/vendor/bunsenite/MAINTAINERS.adoc new file mode 100644 index 0000000..bbf10ba --- /dev/null +++ b/vendor/bunsenite/MAINTAINERS.adoc @@ -0,0 +1,227 @@ +== Maintainers + +This document lists the maintainers of the Bunsenite project and +describes the maintenance structure. + +=== Current Maintainers + +==== Core Team (Perimeter 1) + +These individuals have write access to the main repository and make +final decisions on merges and releases. + +* *Campaign for Cooler Coding and Programming* (@cccp) +** Role: Lead Maintainer, Project Founder +** Contact: https://github.com/hyperpolymath/bunsenite/issues[GitHub +Issues] +** Focus: Overall architecture, releases, community + +==== Trusted Contributors (Perimeter 2) + +These individuals have demonstrated consistent quality contributions and +may have specialized access or responsibilities. + +_(Currently none - invitations extended based on sustained +contributions)_ + +=== Contribution Perimeters (TPCF) + +This project uses the *Tri-Perimeter Contribution Framework*: + +==== Perimeter 1: Core Maintainers + +* *Access*: Full write access +* *Responsibilities*: +** Review and merge PRs +** Release management +** Security response +** Community moderation +** Strategic direction +* *Membership*: By invitation, based on sustained commitment and +expertise + +==== Perimeter 2: Trusted Contributors + +* *Access*: Some specialized permissions (e.g., CI configuration, docs) +* *Responsibilities*: +** Detailed code review +** Mentoring new contributors +** Area-specific expertise +** Triage issues +* *Membership*: By invitation from Perimeter 1, based on consistent +quality contributions + +==== Perimeter 3: Community Sandbox + +* *Access*: Open to all +* *Responsibilities*: +** Submit issues and PRs +** Participate in discussions +** Help other users +* *Membership*: Automatic for all contributors + +=== Areas of Responsibility + +==== Rust Core + +* *Lead*: Core Team +* *Focus*: `+src/lib.rs+`, `+src/loader.rs+`, `+src/error.rs+` +* *Reviewers*: Core Team + +==== WASM Bindings + +* *Lead*: Core Team +* *Focus*: `+src/wasm.rs+`, WASM build system +* *Reviewers*: Core Team + +==== FFI Bindings + +* *Lead*: Core Team (seeking volunteers) +* *Focus*: `+bindings/deno/+`, `+bindings/affinescript/+`, Zig layer +* *Reviewers*: Core Team + +==== CLI + +* *Lead*: Core Team +* *Focus*: `+src/main.rs+`, user experience +* *Reviewers*: Core Team + +==== Documentation + +* *Lead*: Core Team (help wanted!) +* *Focus*: README, CLAUDE.md, API docs, examples +* *Reviewers*: Any maintainer + +==== Infrastructure + +* *Lead*: Core Team +* *Focus*: CI/CD, Justfile, Guix flake, releases +* *Reviewers*: Core Team + +==== Security + +* *Lead*: Core Team +* *Contact*: +https://github.com/hyperpolymath/bunsenite/security/advisories/new[GitHub +Security Advisories] +* *Focus*: Vulnerability response, security audits, dependency audits +* *Reviewers*: Core Team only + +=== Maintenance Policies + +==== Code Review + +* *Required*: At least 1 maintainer approval for all PRs +* *Self-merge*: Core team may merge own PRs for minor changes (typos, +formatting) +* *Security*: Security PRs require 2 approvals +* *Breaking changes*: Require discussion and 2 approvals + +==== Release Process + +[arabic] +. *Version bump*: Update `+Cargo.toml+`, `+CHANGELOG.md+` +. *Testing*: All tests must pass +. *Documentation*: Update docs as needed +. *Tag*: Create git tag `+vX.Y.Z+` +. *Release*: Create GitLab release with notes +. *Publish*: Publish to crates.io +. *Announce*: Announce in discussions/issues + +==== Issue Triage + +* *Labeling*: Apply appropriate labels (`+bug+`, `+enhancement+`, +`+documentation+`, etc.) +* *Priority*: Assign priority (`+P0+`-`+P3+`) +* *Assignment*: Assign to maintainer or leave unassigned for community +* *Response time*: Aim for initial response within 1 week + +==== Security Response + +* *Initial response*: Within 48 hours +* *Triage*: Within 1 week +* *Fix*: According to severity (see SECURITY.md) +* *Disclosure*: Coordinated, typically 90 days after fix + +=== Becoming a Maintainer + +==== Path to Perimeter 2 (Trusted Contributor) + +We look for: + +* *Consistent contributions*: Regular, quality contributions over 3+ +months +* *Code quality*: Well-tested, documented, follows conventions +* *Community*: Helpful in discussions, reviews others’ PRs +* *Alignment*: Understands and embodies project values (reversibility, +emotional safety, political autonomy) + +*Process*: 1. Core team discusses potential invitation 2. Invitation +extended via private message 3. 1-month trial period 4. Full membership +if successful + +==== Path to Perimeter 1 (Core Maintainer) + +We look for: + +* *Sustained commitment*: 6+ months of active, quality participation +* *Deep expertise*: Domain knowledge in core areas +* *Leadership*: Mentors others, drives initiatives +* *Trust*: Demonstrated judgment and alignment with project values + +*Process*: 1. Nominated by existing core maintainer 2. Discussion among +core team 3. Unanimous approval required 4. Onboarding period with +gradual permission increase + +=== Stepping Down + +Maintainers may step down at any time: + +* *Voluntary*: No explanation needed, though appreciated +* *Inactive*: After 6 months of inactivity, we may reach out to confirm +status +* *Emeritus*: Former maintainers are honored and may be consulted + +*Process*: 1. Notify core team 2. Remove permissions 3. Update +MAINTAINERS.md 4. Thank you! 🎉 + +=== Conflict Resolution + +==== Technical Disagreements + +[arabic] +. *Discussion*: Discuss in issue/MR +. *Evidence*: Present evidence and rationale +. *Consensus*: Aim for consensus +. *Vote*: If no consensus, core team votes (simple majority) +. *Document*: Document decision and rationale + +==== Interpersonal Conflicts + +[arabic] +. *Direct*: Speak directly with the person (if safe) +. *Mediation*: Request mediation from another maintainer +. *Code of Conduct*: File CoC complaint if needed +. *Resolution*: Follow CoC enforcement guidelines + +=== Contact + +* *General*: https://github.com/hyperpolymath/bunsenite/issues[GitHub +Issues] +* *Security*: +https://github.com/hyperpolymath/bunsenite/security/advisories/new[GitHub +Security Advisories] +* *GitHub*: https://github.com/hyperpolymath[@hyperpolymath] +* *GitLab*: https://gitlab.com/hyperpolymath[@hyperpolymath] + +=== Acknowledgments + +Thank you to all contributors, whether Perimeter 1, 2, or 3. Every +contribution matters! + +Special thanks to: - Nickel language team (nickel-lang-core) - RSR +Framework contributors - TPCF community - All early adopters and testers + +''''' + +*Last updated*: 2025-11-22 *Version*: 1.0.0 diff --git a/vendor/bunsenite/Mustfile b/vendor/bunsenite/Mustfile new file mode 100644 index 0000000..5f07541 --- /dev/null +++ b/vendor/bunsenite/Mustfile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: MPL-2.0 +# Mustfile - hyperpolymath mandatory checks +# See: https://github.com/hyperpolymath/mustfile + +version: 1 + +checks: + - name: security + run: just lint + - name: tests + run: just test + - name: format + run: just fmt diff --git a/vendor/bunsenite/NOTICE b/vendor/bunsenite/NOTICE new file mode 100644 index 0000000..e047656 --- /dev/null +++ b/vendor/bunsenite/NOTICE @@ -0,0 +1,22 @@ +Licensing Notice +================ + +This project is authored by Jonathan D.A. Jewell (hyperpolymath) and +is licensed under the Palimpsest License (MPL-2.0). + +The MPL-2.0 is a philosophical extension of the Mozilla Public +License 2.0, adding provisions for cryptographic provenance, emotional +lineage preservation, and quantum-safe signatures. The full PMPL text is +available in LICENSES/MPL-2.0.txt. + +For compatibility with automated license detection tools and platforms +that require OSI-approved licenses, the root LICENSE file contains the +standard Mozilla Public License 2.0 text. This ensures that package +registries, CI systems, and other tooling correctly identify the license. + +The legally binding terms are: + - Source files: governed by MPL-2.0 (per SPDX headers) + - Combined works: compatible with MPL-2.0 (per PMPL Section 6) + +For more information about the Palimpsest License: + https://github.com/hyperpolymath/palimpsest-license diff --git a/vendor/bunsenite/PACKAGING.adoc b/vendor/bunsenite/PACKAGING.adoc new file mode 100644 index 0000000..a2c2235 --- /dev/null +++ b/vendor/bunsenite/PACKAGING.adoc @@ -0,0 +1,148 @@ +== Bunsenite Packaging Guide + +This document describes how to package and distribute Bunsenite for +various package managers. + +=== Package Managers + +==== Linux + +[cols=",,",options="header",] +|=== +|Manager |Distro |Config Location +|pacman |Arch Linux |`+packaging/arch/PKGBUILD+` +|apt |Debian/Ubuntu |`+packaging/debian/+` +|dnf |Fedora/RHEL |`+packaging/rpm/bunsenite.spec+` +|zypper |openSUSE |`+packaging/rpm/bunsenite.spec+` +|flatpak |Universal |`+packaging/flatpak/+` +|=== + +==== macOS + +[cols=",",options="header",] +|=== +|Manager |Config Location +|Homebrew |`+packaging/homebrew/bunsenite.rb+` +|MacPorts |`+packaging/macports/Portfile+` +|=== + +==== Windows + +[cols=",",options="header",] +|=== +|Manager |Config Location +|Scoop |`+packaging/scoop/bunsenite.json+` +|Chocolatey |`+packaging/chocolatey/bunsenite.nuspec+` +|winget |`+packaging/winget/bunsenite.yaml+` +|=== + +==== Language Package Managers + +[cols=",,",options="header",] +|=== +|Manager |Language |Location +|cargo |Rust |`+Cargo.toml+` (publish to crates.io) +|npm |Node.js |`+bindings/affinescript/package.json+` +|deno.land/x |Deno |`+bindings/deno/+` (publish to deno.land) +|=== + +=== Build Requirements + +All packaging scripts assume: + +[arabic] +. *Rust 1.70+* - For the core library +. *Zig 0.11+* - For the FFI layer +. *Git* - For source fetching + +=== Building Release Artifacts + +[source,bash] +---- +# Build with all features +cargo build --release --features full + +# Build Zig FFI layer +cd zig && zig build -Doptimize=ReleaseFast + +# Run tests +cargo test --release +---- + +=== Release Artifacts + +Each release should include: + +==== Linux (x86_64, aarch64) + +* `+bunsenite-VERSION-x86_64-unknown-linux-gnu.tar.gz+` +* `+bunsenite-VERSION-aarch64-unknown-linux-gnu.tar.gz+` + +==== macOS (x86_64, aarch64) + +* `+bunsenite-VERSION-x86_64-apple-darwin.tar.gz+` +* `+bunsenite-VERSION-aarch64-apple-darwin.tar.gz+` + +==== Windows (x86_64) + +* `+bunsenite-VERSION-x86_64-pc-windows-msvc.zip+` + +==== Source + +* `+bunsenite-VERSION.tar.gz+` + +=== Publishing Checklist + +==== crates.io (Rust) + +[source,bash] +---- +cargo publish --dry-run +cargo publish +---- + +==== npm (Node.js bindings) + +[source,bash] +---- +cd bindings/affinescript +npm publish --access public +---- + +==== Homebrew + +[arabic] +. Fork homebrew-core +. Update `+bunsenite.rb+` with new version and sha256 +. Submit PR + +==== Arch Linux (AUR) + +[arabic] +. Update PKGBUILD with new version +. Generate .SRCINFO: `+makepkg --printsrcinfo > .SRCINFO+` +. Push to AUR + +==== Flatpak (Flathub) + +[arabic] +. Fork flathub/com.campaignforcoolercoding.bunsenite +. Update manifest with new version +. Submit PR + +=== CI/CD Integration + +The `+.github/workflows/release.yml+` workflow automates: - Building +release binaries for all platforms - Creating GitHub releases with +artifacts - Publishing to crates.io + +=== RSR Compliance Notes + +All packages must include: - LICENSE-MPL-2.0 - LICENSE-PALIMPSEST (if +applicable) - README.md with RSR tier disclosure + +Package descriptions should include: + +.... +RSR Compliance: Bronze Tier | TPCF Perimeter: 3 +.... diff --git a/vendor/bunsenite/PALIMPSEST.adoc b/vendor/bunsenite/PALIMPSEST.adoc new file mode 100644 index 0000000..c2afbcc --- /dev/null +++ b/vendor/bunsenite/PALIMPSEST.adoc @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += MPL-2.0 +:toc: +:toc-placement!: + +image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: MPL-2.0,link="https://github.com/hyperpolymath/palimpsest-license"] +image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] + +toc::[] + +== Legal Status + +This project is licensed under the **MPL-2.0 License 1.0 (MPL-2.0)**. +For SPDX and tooling, use **MPL-2.0**. + +MPL-2.0 incorporates the Mozilla Public License 2.0 by reference and adds +ethical-use, provenance, and lineage requirements. + +== What PMPL Adds + +* **Emotional Lineage** - preserve narrative intent and cultural context +* **Provenance Integrity** - retain attribution and lineage metadata +* **Ethical Use Constraints** - explicit consent for non-interpretive AI training +* **Quantum-Safe Provenance (optional)** - post-quantum signature support + +== How to Adopt + +1. Include the MPL-2.0 license text in `LICENSE`. +2. Add SPDX headers to source files: + `SPDX-License-Identifier: CC-BY-SA-4.0` +3. Add a Palimpsest badge to your README (see `assets/badges/` and `embed/license-blocks/`). + +== Versioning + +See `VERSIONING.adoc` for the release process and the "-or-later" model. +The current legal text is MPL-2.0. + +== References + +* `legal/README.adoc` +* `assets/badges/README.md` +* `embed/license-blocks/README.md` diff --git a/vendor/bunsenite/PROOF-NEEDS.adoc b/vendor/bunsenite/PROOF-NEEDS.adoc new file mode 100644 index 0000000..2cd88d9 --- /dev/null +++ b/vendor/bunsenite/PROOF-NEEDS.adoc @@ -0,0 +1,41 @@ +== Proof Requirements + +=== Current state + +* `+src/abi/Types.idr+` — Nickel parser types +* `+src/abi/Layout.idr+` — Memory layout +* `+src/abi/Foreign.idr+` — FFI declarations +* No dangerous patterns in ABI layer +* Claims: type safety, memory safety, "`zero `+unsafe+` blocks`" + +=== What needs proving + +* *Parser correctness*: Prove the Nickel parser accepts exactly the +Nickel grammar (no over-acceptance of malformed input) +* *Round-trip fidelity*: Prove parse-then-serialize produces +semantically equivalent output (no silent data loss) +* *FFI memory safety*: Prove the Zig FFI layer correctly manages +ownership across the Rust-Zig-Deno/WASM boundary (no dangling pointers, +no double-free) +* *Zero-unsafe claim*: Verify (via tooling or proof) that no `+unsafe+` +blocks exist in the Rust core and that all FFI crossing points are safe + +=== Recommended prover + +* *Idris2* — For parser grammar conformance and FFI boundary properties +* *Lean4* — For algebraic properties of the parse/serialize round-trip +if modeled functorially + +=== Priority + +* *MEDIUM* — The "`zero unsafe blocks`" and type safety claims are +strong marketing. Parser correctness matters for any tool in the +configuration pipeline, but Bunsenite is not safety-critical +infrastructure. + +=== Template ABI Cleanup (2026-03-29) + +Template ABI removed – was creating false impression of formal +verification. The removed files (Types.idr, Layout.idr, Foreign.idr) +contained only RSR template scaffolding with unresolved +\{\{PROJECT}}/\{\{AUTHOR}} placeholders and no domain-specific proofs. diff --git a/vendor/bunsenite/PROVEN-INTEGRATION.adoc b/vendor/bunsenite/PROVEN-INTEGRATION.adoc new file mode 100644 index 0000000..4a4ff2a --- /dev/null +++ b/vendor/bunsenite/PROVEN-INTEGRATION.adoc @@ -0,0 +1,120 @@ +== Proven Library Integration Plan + +This document outlines how the +https://github.com/hyperpolymath/proven[proven] library’s formally +verified modules integrate with Bunsenite. + +=== Applicable Modules + +==== High Priority + +[cols=",,",options="header",] +|=== +|Module |Use Case |Formal Guarantee +|`+SafeSchema+` |Nickel config validation |Type-safe configurations +|`+SafeFFI+` |FFI boundary safety |ABI contract verification +|`+SafeBuffer+` |Config parsing buffer |Bounded memory usage +|=== + +==== Medium Priority + +[cols=",,",options="header",] +|=== +|Module |Use Case |Formal Guarantee +|`+SafeString+` |Config interpolation |Injection prevention +|`+SafeTree+` |Config tree navigation |ValidPath proofs +|`+SafeResource+` |File handle lifecycle |Valid state transitions +|=== + +=== Integration Points + +==== 1. Config Schema Validation (SafeSchema) + +[source,nickel] +---- +# Nickel config +{ + name = "my-app", + port = 8080, + features = ["auth", "logging"] +} +---- + +.... +parse → SafeSchema.validate → typed NickelConfig +.... + +SafeSchema ensures: - Required fields are present - Field types match +declarations - Contract constraints are satisfied + +==== 2. FFI Boundary Safety (SafeFFI) + +Bunsenite’s C ABI boundary is where safety is most critical: + +.... +Rust → SafeFFI.marshal → C ABI → SafeFFI.unmarshal → Deno/AffineScript +.... + +SafeFFI guarantees: - Memory ownership is correctly transferred - +Buffers are correctly sized and aligned - Error codes are properly +propagated - No use-after-free or double-free + +==== 3. Parsing Buffer Management (SafeBuffer) + +.... +config_file → SafeBuffer.BoundedBuffer → parse → result +.... + +Prevents: - Stack overflow on deeply nested configs - OOM on maliciously +large inputs - Buffer overflows in string handling + +=== FFI Contract Proofs + +Bunsenite’s C ABI can be formally specified: + +[source,c] +---- +// include/bebop_v_ffi.h +struct BunseniteResult { + uint32_t status; // SafeFFI.ResultCode + void* data; // SafeFFI.OwnedPtr + size_t len; // SafeFFI.BoundedSize +}; +---- + +SafeFFI proves: - `+status == OK+` ⟹ `+data != NULL ∧ len > 0+` - +`+status == ERROR+` ⟹ `+data+` contains error message - Caller owns +`+data+` and must free it + +=== Language Binding Integration + +[cols=",,",options="header",] +|=== +|Binding |FFI Layer |proven Module +|Deno |Deno.dlopen |SafeFFI +|AffineScript |External FFI |SafeFFI +|WASM |Wasm bindgen |SafeBuffer +|=== + +=== Implementation Notes + +For Rust core integration: + +[source,rust] +---- +// src/lib.rs +#[cfg(feature = "proven")] +mod proven_bindings { + // SafeSchema validation before returning to FFI + pub fn validate_config(input: &str) -> Result { + SafeSchema::validate(input)? + } +} +---- + +=== Status + +* [ ] Add SafeSchema for Nickel config validation +* [ ] Integrate SafeFFI for ABI contract verification +* [ ] Implement SafeBuffer for bounded parsing +* [ ] Generate proofs for C ABI contract diff --git a/vendor/bunsenite/PUBLISHING.adoc b/vendor/bunsenite/PUBLISHING.adoc new file mode 100644 index 0000000..64a3810 --- /dev/null +++ b/vendor/bunsenite/PUBLISHING.adoc @@ -0,0 +1,155 @@ +== Publishing Bunsenite + +This guide walks through publishing bunsenite to all package managers. + +=== Prerequisites + +You’ll need accounts and tokens for: - *crates.io* - Rust package +registry - *npm* - Node.js package registry - *GitHub* - For releases +and Homebrew tap + +=== Step 1: Configure GitHub Secrets + +Go to your repo → Settings → Secrets and variables → Actions → New +repository secret + +[width="100%",cols="47%,53%",options="header",] +|=== +|Secret Name |How to Get It +|`+CARGO_REGISTRY_TOKEN+` |https://crates.io/settings/tokens → New Token + +|`+NPM_TOKEN+` |https://www.npmjs.com/settings/tokens → Generate New +Token (Automation) +|=== + +=== Step 2: Create and Push a Tag + +[source,bash] +---- +# Create the v1.0.0 tag +git tag -a v1.0.0 -m "Release v1.0.0" + +# Push the tag (this triggers the release workflow) +git push origin v1.0.0 +---- + +This automatically: - Builds binaries for Linux, macOS, Windows - +Creates a GitHub Release with all artifacts - Publishes to crates.io - +Publishes to npm + +=== Step 3: Homebrew (Manual) + +Option A: *Create your own tap* (recommended for new packages): + +[source,bash] +---- +# Create a new repo: hyperpolymath/homebrew-tap +# Then add the formula + +mkdir -p homebrew-tap/Formula +cp packaging/homebrew/bunsenite.rb homebrew-tap/Formula/ + +# Update the sha256 from the GitHub release +# Then users install with: +# brew tap hyperpolymath/tap +# brew install bunsenite +---- + +Option B: *Submit to homebrew-core* (after package is established): + +[source,bash] +---- +# Fork homebrew/homebrew-core +# Add Formula/bunsenite.rb +# Submit PR +---- + +=== Step 4: Arch Linux (AUR) + +[source,bash] +---- +# Clone your AUR package (first time: create it) +git clone ssh://aur@aur.archlinux.org/bunsenite.git aur-bunsenite +cd aur-bunsenite + +# Copy PKGBUILD +cp ../packaging/arch/PKGBUILD . + +# Update checksums +updpkgsums + +# Generate .SRCINFO +makepkg --printsrcinfo > .SRCINFO + +# Commit and push +git add PKGBUILD .SRCINFO +git commit -m "Update to v1.0.0" +git push +---- + +=== Step 5: Other Package Managers + +==== Flatpak (Flathub) + +[arabic] +. Fork https://github.com/flathub/flathub +. Create `+com.campaignforcoolercoding.bunsenite/+` directory +. Copy `+packaging/flatpak/com.campaignforcoolercoding.bunsenite.yml+` +. Submit PR + +==== Scoop (Windows) + +[arabic] +. Fork https://github.com/ScoopInstaller/Main (or create own bucket) +. Add `+bucket/bunsenite.json+` +. Submit PR + +==== Chocolatey (Windows) + +[source,bash] +---- +cd packaging/chocolatey +# Update bunsenite.nuspec with correct URLs +choco pack +choco push bunsenite.1.0.0.nupkg --source https://push.chocolatey.org/ +---- + +==== winget (Windows) + +[arabic] +. Fork https://github.com/microsoft/winget-pkgs +. Create `+manifests/c/CampaignForCoolerCoding/Bunsenite/1.0.0/+` +. Copy and split manifest files +. Submit PR + +=== Quick Start Commands + +[source,bash] +---- +# Step 1: Add secrets to GitHub (do this in browser) + +# Step 2: Tag and release +git tag -a v1.0.0 -m "Release v1.0.0" +git push origin v1.0.0 + +# Step 3: Wait for CI, then verify +# - Check GitHub Actions for build status +# - Check https://crates.io/crates/bunsenite +# - Check https://www.npmjs.com/package/bunsenite +---- + +=== Verification + +After publishing, verify each registry: + +[source,bash] +---- +# Cargo +cargo install bunsenite + +# npm +npm info bunsenite + +# GitHub Release +gh release view v1.0.0 +---- diff --git a/vendor/bunsenite/QUICKSTART-DEV.adoc b/vendor/bunsenite/QUICKSTART-DEV.adoc new file mode 100644 index 0000000..6e48e58 --- /dev/null +++ b/vendor/bunsenite/QUICKSTART-DEV.adoc @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite — Developer Quickstart +:toc: preamble + +Clone, build, test, contribute. + +== Prerequisites + +* Git 2.40+ +* just (command runner) +* See `just doctor` output for language-specific requirements + +== Setup + +[source,bash] +---- +git clone https://github.com/hyperpolymath/bunsenite +cd bunsenite +just doctor # verify toolchain +just heal # auto-install missing tools +---- + +== Development Workflow + +[source,bash] +---- +just tour # understand the codebase +just help-me # see available commands +---- + +== Before Committing + +[source,bash] +---- +just assail # run panic-attacker security scan +---- + +== Contributing + +See link:.github/CONTRIBUTING.md[CONTRIBUTING.md] for guidelines. diff --git a/vendor/bunsenite/QUICKSTART-MAINTAINER.adoc b/vendor/bunsenite/QUICKSTART-MAINTAINER.adoc new file mode 100644 index 0000000..211da8e --- /dev/null +++ b/vendor/bunsenite/QUICKSTART-MAINTAINER.adoc @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite — Maintainer Quickstart +:toc: preamble + +Packaging, deployment, and release management. + +== Prerequisites + +* Git 2.40+ +* just (command runner) +* Familiarity with the project (run `just tour` first) + +== CI/CD + +This project uses GitHub Actions. Workflows are in `.github/workflows/`. + +Key workflows: + +* `hypatia-scan.yml` — Neurosymbolic security scanning +* `codeql.yml` — Code analysis +* `scorecard.yml` — OpenSSF Scorecard +* `mirror.yml` — GitLab/Bitbucket mirroring + +== Releasing + +1. Update version in project config +2. Update CHANGELOG.md +3. Tag: `git tag -s v` +4. Push: `git push origin main --tags` + +== Container Build (if applicable) + +[source,bash] +---- +podman build -f Containerfile -t bunsenite:latest . +---- + +== Mirrors + +This repo is mirrored to GitLab and Bitbucket (hyperpolymath accounts) +via the `mirror.yml` workflow. diff --git a/vendor/bunsenite/QUICKSTART-USER.adoc b/vendor/bunsenite/QUICKSTART-USER.adoc new file mode 100644 index 0000000..d754e7a --- /dev/null +++ b/vendor/bunsenite/QUICKSTART-USER.adoc @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite — User Quickstart +:toc: preamble + +Get up and running in 60 seconds. + +== Prerequisites + +* Git 2.40+ +* just (command runner) — https://just.systems + +== Install + +[source,bash] +---- +git clone https://github.com/hyperpolymath/bunsenite +cd bunsenite +just doctor # check toolchain +just heal # auto-install missing tools +---- + +== First Run + +[source,bash] +---- +just tour # guided project tour +just help-me # see common workflows +---- + +== Get Help + +* `just help-me` — common workflows +* `just doctor` — diagnose toolchain issues +* https://github.com/hyperpolymath/bunsenite/issues — report bugs diff --git a/vendor/bunsenite/README.adoc b/vendor/bunsenite/README.adoc new file mode 100644 index 0000000..a2d754f --- /dev/null +++ b/vendor/bunsenite/README.adoc @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite +image:https://img.shields.io/badge/License-MPL_2.0--1.0-blue.svg[License: MPL-2.0,link="https://github.com/hyperpolymath/palimpsest-license"] +image:https://img.shields.io/badge/OpenSSF-Best_Practices-green?logo=opensourcesecurity[OpenSSF Best Practices, link="https://www.bestpractices.dev/en/projects/new?repo_url=https://github.com/hyperpolymath/bunsenite"] +image:https://img.shields.io/badge/Idris-Inside-blueviolet?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyIDJMMyA3djEwbDkgNSA5LTVWN2wtOS01em0wIDJsNyA0djhsLTcgNC03LTRWOGw3LTR6Ii8+PC9zdmc+[Idris Inside,link="https://github.com/hyperpolymath/proven"] + + + + +image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] + + +> Nickel configuration file parser with multi-language FFI bindings + +[![RSR Bronze](https://img.shields.io/badge/RSR-Bronze-CD7F32)](https://example.com/rsr) +[![TPCF Perimeter 3](https://img.shields.io/badge/TPCF-Perimeter%203-blue)](https://example.com/tpcf) +[![Build Status](https://github.com/hyperpolymath/bunsenite/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperpolymath/bunsenite/actions) + +== Overview + +Bunsenite is a *Nickel configuration file parser* with a Rust core library and multi-language FFI bindings. It provides a stable C ABI layer (via Zig) that enables bindings for *Deno* (JavaScript/TypeScript), *Rescript*, and *WebAssembly* for browser and universal use. + +=== Key Features + +- ✅ *Type Safety*: Compile-time guarantees via Rust's type system +- ✅ *Memory Safety*: Rust ownership model, *zero `unsafe` blocks* +- ✅ *Offline-First*: Works completely air-gapped, no network dependencies +- ✅ *Multi-Language*: FFI bindings for Deno, Rescript, and WASM +- ✅ *Standards Compliant*: RSR Bronze tier, TPCF Perimeter 3 +- ✅ *Well-Documented*: Comprehensive API docs, examples, and guides +- ✅ *Production-Ready*: 100% test pass rate, CI/CD, semantic versioning + +== Quick Start + +=== Installation + +```bash += From crates.io + +cargo install bunsenite + += From source + +git clone https://github.com/hyperpolymath/bunsenite.git +cd bunsenite +cargo install --path . +``` + +=== Usage + +==== Rust Library + +```rust +use bunsenite::NickelLoader; + +fn main() { + let config = r#" + { + name = "my-app", + version = "1.0.0", + port = 8080, + } + "#; + + let loader = NickelLoader::new(); + let result = loader.parse_string(config, "config.ncl").unwrap(); + + println!("Config: {}", result); +} +``` + +==== CLI + +```bash += Parse and evaluate a config file + +bunsenite parse config.ncl + += Pretty-print output + +bunsenite parse config.ncl --pretty + += Validate without evaluating + +bunsenite validate config.ncl + += Show version and compliance info + +bunsenite info +``` + +==== WebAssembly (Browser) + +```javascript +import init, { parse_nickel } from './bunsenite.js'; + +async function main() { + await init(); + const config = `{ name = "example", version = "1.0.0" }`; + const result = parse_nickel(config, "config.ncl"); + console.log(JSON.parse(result)); +} +``` + +==== Deno (TypeScript) + +```typescript +// See bindings/deno/ for full example +import { parseNickel } from "./bunsenite_deno.ts"; + +const config = `{ foo = 42 }`; +const result = parseNickel(config, "config.ncl"); +console.log(result); +``` + +== Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Consumers │ +├───────────────┬───────────────┬─────────────────┤ +│ Deno │ Rescript │ Browser │ +│ (TypeScript) │ (AffineScript) │ (WASM) │ +└───────┬───────┴───────┬───────┴────────┬────────┘ + │ │ │ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────────┐ + │ Zig FFI │ │ Zig FFI │ │ wasm-bindgen │ + │ (C ABI) │ │ (C ABI) │ │ │ + └─────┬────┘ └─────┬────┘ └──────┬───────┘ + │ │ │ + └──────────────┴─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ Rust Core │ + │ (lib.rs) │ + │ │ + │ nickel-lang-core│ + │ 0.9.1 │ + └─────────────────┘ +``` + +=== Design Rationale + +*Zig FFI Layer*: Provides stable C ABI, isolating consumers from Rust ABI changes. This allows language bindings to remain stable across Rust compiler versions. + +*WASM Support*: Enables browser deployment and universal compatibility at ~95% native speed. + +*Deno .ts Files*: Required syntax for Deno runtime FFI (NOT plain TypeScript). Uses `Deno.dlopen` for native FFI calls to Zig layer. + +== Documentation + +- *[CLAUDE.md](./CLAUDE.md)*: Comprehensive guide for AI assistants and developers +- *[SECURITY.md](./SECURITY.md)*: Security policies and vulnerability reporting +- *[CONTRIBUTING.md](./CONTRIBUTING.md)*: Contribution guidelines +- *[CHANGELOG.md](./CHANGELOG.md)*: Version history and release notes +- *[API Docs](https://docs.rs/bunsenite)*: Full Rust API documentation + +== Standards Compliance + +=== RSR Framework: Bronze Tier + +Bunsenite meets all *Rhodium Standard Repository (RSR) Bronze tier* requirements: + +- ✅ Type safety (Rust compile-time guarantees) +- ✅ Memory safety (ownership model, `#![deny(unsafe_code)]`) +- ✅ Offline-first (no network dependencies) +- ✅ Complete documentation (README, LICENSE, SECURITY, CONTRIBUTING, CODE_OF_CONDUCT, MAINTAINERS) +- ✅ `.well-known/` directory (security.txt, ai.txt, humans.txt) +- ✅ Build system (Justfile, Guix flake) +- ✅ CI/CD pipeline (GitLab CI) +- ✅ 100% test pass rate + +=== TPCF: Perimeter 3 (Community Sandbox) + +This project uses the *Tri-Perimeter Contribution Framework (TPCF)*: + +- *Perimeter 1*: Core maintainers only (restricted) +- *Perimeter 2*: Trusted contributors (by invitation) +- *Perimeter 3*: *Community Sandbox* - Open to all contributors + +All contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for details. + +== Building from Source + +=== Prerequisites + +- Rust 1.70+ (`rustup install stable`) +- `just` command runner (`cargo install just`) +- Optional: Zig compiler (for FFI layer) +- Optional: `wasm-pack` (for WASM builds: `cargo install wasm-pack`) +- Optional: Deno runtime (for Deno bindings) + +=== Build Commands + +```bash += Build all targets + +just all + += Build Rust library and CLI + +cargo build --release + += Build WebAssembly + +just wasm + += Run tests + +cargo test + += Run linter + +cargo clippy + += Format code + +cargo fmt + += Check RSR compliance + +just rsr-check +``` + +See `Justfile` for all available commands. + +== Testing + +```bash += Run all tests + +cargo test + += Run tests with output + +cargo test -- --nocapture + += Run specific test + +cargo test test_name + += Run with coverage (requires tarpaulin) + +cargo tarpaulin --out Html +``` + +Current status: *100% test pass rate* (30+ tests covering core functionality, error handling, and edge cases) + +== Performance + +- *Native Rust*: Baseline performance +- *WebAssembly*: ~95% native speed +- *FFI (Deno/Rescript)*: ~90% native speed (C ABI overhead minimal) + +== Security + +We take security seriously. See [SECURITY.md](./SECURITY.md) for: + +- Supported versions +- Vulnerability reporting process +- Security best practices +- Responsible disclosure policy + +*Security contact*: See `.well-known/security.txt` or [SECURITY.md](./SECURITY.md) + +== License + +Dual licensed under your choice of: + +- *MPL-2.0 License v1.0 (MPL-2.0)* ([LICENSE-MPL-2.0](./LICENSE) or https://github.com/hyperpolymath/palimpsest-license) +- *MPL-2.0 v0.8* ([LICENSE-PALIMPSEST](./LICENSE) or https://palingenesis.org/palimpsest-license) + +This allows maximum flexibility for use while preserving reversibility and emotional safety principles. + +== Contributing + +Contributions are welcome! This is a *TPCF Perimeter 3* (Community Sandbox) project. + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for: + +- Code of Conduct +- Development workflow +- Testing requirements +- Commit message conventions +- Pull request process + +== Community + +- *Issues*: [GitHub Issues](https://github.com/hyperpolymath/bunsenite/issues) +- *Discussions*: [GitHub Discussions](https://github.com/hyperpolymath/bunsenite/discussions) +- *Security*: See [SECURITY.md](./SECURITY.md) + +== Acknowledgments + +- [Nickel Language Team](https://github.com/tweag/nickel) for the excellent configuration language +- RSR Framework contributors +- TPCF community +- All contributors to this project + +== Roadmap + +See [NEXT_STEPS.md](./NEXT_STEPS.md) for planned features and enhancements: + +- [ ] Additional language bindings (Python, Ruby, Node.js) +- [ ] Performance benchmarking suite +- [ ] REPL/interactive mode +- [ ] Schema validation +- [ ] Watch mode for auto-reload +- [ ] Plugin system + +== Version History + +See [CHANGELOG.md](./CHANGELOG.md) for detailed version history. + +Current version: *0.1.0* (Bronze tier compliant, production-ready) + +--- + +*Made with ❤️ by the Campaign for Cooler Coding and Programming* + +*Politically autonomous software for emotionally safe development* + + +== Architecture + +See link:TOPOLOGY.md[TOPOLOGY.md] for a visual architecture map and completion dashboard. diff --git a/vendor/bunsenite/ROADMAP.adoc b/vendor/bunsenite/ROADMAP.adoc new file mode 100644 index 0000000..1594ff3 --- /dev/null +++ b/vendor/bunsenite/ROADMAP.adoc @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite Roadmap +:toc: +:toclevels: 3 + +== Overview + +This roadmap outlines the development plan for Bunsenite, the Nickel configuration file parser with multi-language FFI bindings. + +== v1.0 - Production Release (Current) + +**Target:** 2026-02 +**Status:** Active development (85% complete) + +=== Core Features +* ✅ Rust core library with Nickel parsing +* ✅ Zero `unsafe` blocks (100% safe Rust) +* ✅ C ABI layer via Zig for FFI +* ✅ Deno bindings (JavaScript/TypeScript) +* ✅ AffineScript bindings +* ✅ WebAssembly support (browser + universal) +* ✅ Offline-first architecture (no network dependencies) + +=== Quality & Standards +* ✅ RSR Bronze tier compliance +* ✅ TPCF Perimeter 3 compliance +* ✅ 100% test pass rate +* ✅ CI/CD pipeline +* ✅ Comprehensive documentation +* ✅ ClusterFuzzLite fuzzing +* ✅ Criterion benchmarks + +=== Remaining for v1.0 +* [ ] Complete ROADMAP.adoc (this file) +* [ ] Address placeholder TODOs in codebase +* [ ] Final security audit +* [ ] Performance optimization pass +* [ ] Release automation + +== v1.1 - Enhanced Bindings (Next) + +**Target:** 2026-03 + +=== Additional Language Bindings +* Python bindings (via PyO3) +* Ruby bindings (via Magnus) +* Julia bindings (CCall) +* Go bindings (cgo) + +=== Improved Ergonomics +* Simplified API for common use cases +* Better error messages with suggestions +* Auto-completion support for IDEs +* Configuration validation helpers + +=== Performance +* Streaming parser for large files +* Parallel parsing for multi-file configs +* Memory usage optimization +* Zero-copy parsing where possible + +== v1.2 - Advanced Features + +**Target:** 2026-04 + +=== Nickel Language Features +* Advanced type inference +* Custom type definitions +* Merging strategies +* Validation schemas +* Built-in formatters + +=== Tooling Integration +* Language server protocol (LSP) +* Formatter integration +* Linter integration +* Migration tools from other config formats + +=== Developer Experience +* Interactive REPL +* Web-based playground +* Configuration templates library +* Best practices guide + +== v2.0 - Neurosymbolic Configuration + +**Target:** 2026-Q3 + +=== AI Integration +* LLM-based configuration generation +* Natural language to Nickel translation +* Configuration validation with AI explanations +* Auto-fix for common configuration errors + +=== Formal Verification +* Idris integration for proven configuration +* Type-level guarantees for config correctness +* Proof-carrying configuration +* Contract verification + +=== Hypatia Integration +* Hypatia orchestration for config management +* Fleet-wide configuration validation +* Dependency tracking across configurations +* Configuration policy enforcement + +== v2.5 - Universal Configuration Standard + +**Target:** 2026-Q4 + +=== Format Interoperability +* Bidirectional TOML conversion +* YAML import/export +* JSON compatibility layer +* HCL (HashiCorp) migration tools + +=== Ecosystem Integration +* Kubernetes ConfigMap support +* Docker Compose integration +* CI/CD pipeline configs +* Infrastructure as Code (IaC) templates + +=== Distributed Configuration +* Multi-environment support +* Secret management integration +* Configuration versioning +* Rollback mechanisms + +== v3.0 - Autonomous Configuration Management + +**Target:** 2027 + +=== Self-Healing Configurations +* Auto-detection of configuration drift +* Predictive error prevention +* Self-optimizing configurations +* Intelligent defaults learning + +=== Enterprise Features +* Multi-tenant configuration +* RBAC for configuration access +* Audit logging +* Compliance reporting + +=== Cloud-Native +* Native Kubernetes operator +* Service mesh integration +* Configuration as a Service (CaaS) +* Global configuration distribution + +== Long-term Vision + +* Industry-standard configuration format +* Formal verification by default +* AI-assisted configuration authoring +* Zero-configuration for common scenarios +* Universal compatibility across all tools +* Integration with Hyperpolymath proven infrastructure + +== Contributing + +See link:.github/CONTRIBUTING.md[Contributing Guidelines] for how to contribute to Bunsenite development. + +== Versioning + +Bunsenite follows semantic versioning (SemVer). Breaking changes will only be introduced in major version releases. diff --git a/vendor/bunsenite/RSR_COMPLIANCE.adoc b/vendor/bunsenite/RSR_COMPLIANCE.adoc new file mode 100644 index 0000000..cf0db8d --- /dev/null +++ b/vendor/bunsenite/RSR_COMPLIANCE.adoc @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += RSR Compliance: bunsenite +:toc: +:sectnums: + +== Overview + +This document describes the Rhodium Standard Repository (RSR) compliance status for *bunsenite*. + +== Classification + +[cols="1,2"] +|=== +|Attribute |Value + +|Project |bunsenite +|Primary Language |rust +|RSR Tier |1 +|Compliance Status |Compliant +|Last Updated |2025-12-10 +|=== + +== Language Tier Classification + +=== Tier 1 Languages (Preferred) +* Rust +* Elixir +* Zig +* Ada +* Haskell +* AffineScript + +=== Tier 2 Languages (Acceptable) +* Nickel (configuration) +* Racket (scripting) +* Guile Scheme (state management) +* Guix (derivations) + +=== Restricted Languages +* Python - Only allowed in salt/ directories for SaltStack +* TypeScript/JavaScript - Legacy only, convert to AffineScript +* CUE - Not permitted, use Nickel or Guile + +== Compliance Checklist + +[cols="1,1,2"] +|=== +|Requirement |Status |Notes + +|Primary language is Tier 1/2 |✓ |rust +|No restricted languages outside exemptions |✓ | +|.editorconfig present |✓ | +|.well-known/ directory |✓ | +|justfile present |✗ | +|LICENSE (MPL-2.0) |✓ | +|Containerfile present |✗ | +|flake.guix present |✓ | +|=== + +== Exemptions + +None + +== Action Items + +* Add Justfile +* Add Containerfile + +== References + +* link:https://github.com/hyperpolymath/RSR-template-repo[RSR Template Repository] +* link:.github/CONTRIBUTING.md[Contributing Guidelines] +* link:../CODE_OF_CONDUCT.adoc[Code of Conduct] diff --git a/vendor/bunsenite/RSR_OUTLINE.adoc b/vendor/bunsenite/RSR_OUTLINE.adoc new file mode 100644 index 0000000..b211f90 --- /dev/null +++ b/vendor/bunsenite/RSR_OUTLINE.adoc @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += RSR Template Repository + +image:[MPL-2.0-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] image:[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] +:toc: +:sectnums: + +// Badges +image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] +image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] +image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] + +== Overview + +**The canonical template for RSR (Rhodium Standard Repository) projects.** + +This repository provides the standardized structure, configuration, and tooling for all 139 repos in the hyperpolymath ecosystem. Use it to: + +* Bootstrap new projects with RSR compliance +* Reference the standard directory structure +* Copy configuration templates (Justfile, STATE.scm, etc.) + +== Quick Start + +[source,bash] +---- +# Clone the template +git clone https://github.com/hyperpolymath/RSR-template-repo my-project +cd my-project + +# Remove template git history +rm -rf .git +git init + +# Customize +sed -i 's/RSR-template-repo/my-project/g' Justfile guix.scm README.adoc + +# Enter development environment +guix shell -D -f build/guix.scm + +# Validate compliance +just validate-rsr +---- + +== What's Included + +[cols="1,3"] +|=== +|File/Directory |Purpose + +|`.editorconfig` +|Editor configuration (indent, charset) + +|`.gitignore` +|Standard ignore patterns + +|`.guix-channel` +|Guix channel definition + +|`.well-known/` +|RFC-compliant metadata (security.txt, ai.txt, humans.txt) + +|`docs/` +|Documentation directory + +|`guix.scm` +|Guix package definition + +|`justfile` +|Task runner with 50+ recipes + +|`LICENSE.txt` +|MPL-2.0 + +|`README.adoc` +|This file + +|`RSR_COMPLIANCE.adoc` +|Compliance tracking + +|`STATE.scm` +|Project state checkpoint +|=== + +== Justfile Features + +The template Justfile provides: + +* **~10 billion recipe combinations** via matrix recipes +* **Cookbook generation**: `just cookbook` → `docs/just-cookbook.adoc` +* **Man page generation**: `just man` → `docs/man/project.1` +* **RSR validation**: `just validate-rsr` +* **STATE.scm management**: `just state-touch`, `just state-phase` +* **Container support**: `just container-build`, `just container-push` +* **CI matrix**: `just ci-matrix [stage] [depth]` + +=== Key Recipes + +[source,bash] +---- +just # Show all recipes +just help # Detailed help +just info # Project info +just combinations # Show matrix options + +just build # Build (debug) +just test # Run tests +just quality # Format + lint + test +just ci # Full CI pipeline + +just validate # RSR + STATE validation +just docs # Generate all docs +just cookbook # Generate Justfile docs + +just guix-shell # Guix dev environment +just container-build # Build container +---- + +== Directory Structure + +[source] +---- +project/ +├── .editorconfig # Editor settings +├── .gitignore # Git ignore +├── .guix-channel # Guix channel +├── .well-known/ # RFC metadata +│ ├── ai.txt +│ ├── humans.txt +│ └── security.txt +├── config/ # Nickel configs (optional) +├── docs/ # Documentation +│ ├── generated/ +│ ├── man/ +│ └── just-cookbook.adoc +├── guix.scm # Guix package +├── Justfile # Task runner +├── LICENSE.txt # Dual license +├── README.adoc # Overview +├── RSR_COMPLIANCE.adoc # Compliance +├── src/ # Source code +├── STATE.scm # State checkpoint +└── tests/ # Tests +---- + +== RSR Compliance + +=== Language Tiers + +* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, AffineScript +* **Tier 2** (Silver): Nickel, Racket, Guile Scheme, Guix +* **Infrastructure**: Guix channels, derivations + +=== Required Files + +* `.editorconfig` +* `.gitignore` +* `justfile` +* `README.adoc` +* `RSR_COMPLIANCE.adoc` +* `LICENSE` (MPL-2.0) +* `.well-known/security.txt` +* `.well-known/ai.txt` +* `.well-known/humans.txt` +* `guix.scm` OR `flake.guix` + +=== Prohibited + +* Python outside `salt/` directory +* TypeScript/JavaScript (use AffineScript) +* CUE (use Guile/Nickel) +* `Dockerfile` (use `Containerfile`) + +== STATE.scm + +The STATE.scm file tracks project state: + +[source,scheme] +---- +(define state + `((metadata + (project . "my-project") + (updated . "2025-12-10")) + (position + (phase . implementation) ; design|implementation|testing|maintenance|archived + (maturity . beta)) ; experimental|alpha|beta|production|lts + (ecosystem + (part-of . ("RSR Framework")) + (depends-on . ())))) +---- + +== Badge Schema + +Generate badges from STATE.scm: + +[source,bash] +---- +just badges standard +---- + +See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. + +== Ecosystem Integration + +This template is part of: + +* **STATE.scm Ecosystem**: Conversation checkpoints +* **RSR Framework**: Repository standards +* **Consent-Aware-HTTP**: .well-known compliance + +== License + +SPDX-License-Identifier: CC-BY-SA-4.0 + +== Links + +* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE.scm tooling +* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement +* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/vendor/bunsenite/SECURITY.adoc b/vendor/bunsenite/SECURITY.adoc new file mode 100644 index 0000000..776e45e --- /dev/null +++ b/vendor/bunsenite/SECURITY.adoc @@ -0,0 +1,235 @@ +== Security Policy + +=== Supported Versions + +We take security seriously and provide security updates for the +following versions: + +[cols=",,",options="header",] +|=== +|Version |Supported |Notes +|1.0.x |:white_check_mark: |Current stable release +|< 1.0.0 |:x: |Pre-release, not supported +|=== + +=== Security Guarantees + +Bunsenite provides the following security guarantees: + +==== Memory Safety + +* *Zero `+unsafe+` blocks*: Enforced by `+#![deny(unsafe_code)]+` +compiler directive +* *Rust ownership model*: Prevents use-after-free, double-free, and +memory leaks +* *No null pointer dereferences*: Rust’s type system eliminates this +class of bugs +* *Bounds checking*: All array/vector accesses are bounds-checked + +==== Type Safety + +* *Compile-time guarantees*: Type errors are caught before runtime +* *No implicit conversions*: Explicit type conversions required +* *Strong typing*: Prevents type confusion vulnerabilities + +==== Dependency Security + +* *Minimal dependencies*: Only essential, well-audited crates +* *No network dependencies*: Offline-first design eliminates network +attack surface +* *Pinned versions*: Dependencies locked to specific versions for +reproducibility +* *Regular audits*: Dependencies audited using `+cargo audit+` + +==== Supply Chain Security + +* *Reproducible builds*: Guix flake provides bit-for-bit reproducibility +* *Signed releases*: All releases are cryptographically signed (planned) +* *Transparent development*: All changes tracked in public Git +repository +* *SBOM generation*: Software Bill of Materials available (planned) + +=== Reporting a Vulnerability + +*Please do NOT report security vulnerabilities through public +GitHub/GitLab issues.* + +==== Preferred Method + +Report security vulnerabilities via: + +[arabic] +. *GitHub Security Advisories*: +https://github.com/hyperpolymath/bunsenite/security/advisories/new[Create +a new security advisory] (preferred) +. *GitLab Confidential Issue*: Use GitLab’s confidential issue feature + +==== What to Include + +Please include: + +* *Description*: Clear description of the vulnerability +* *Impact*: What an attacker could achieve +* *Reproduction*: Step-by-step instructions to reproduce +* *Affected versions*: Which versions are affected +* *Proposed fix*: If you have one (optional) +* *Disclosure timeline*: Your preferred disclosure timeline + +==== Response Timeline + +* *Initial response*: Within 48 hours +* *Triage*: Within 1 week +* *Fix development*: Depends on severity (critical: days, low: weeks) +* *Public disclosure*: Coordinated with reporter, typically 90 days +after fix + +==== Severity Levels + +We use the following severity classifications: + +===== Critical (CVSS 9.0-10.0) + +* Remote code execution +* Privilege escalation to admin/root +* Authentication bypass + +*Response*: Patch within 48 hours, immediate release + +===== High (CVSS 7.0-8.9) + +* SQL injection (not applicable to Bunsenite) +* Information disclosure of sensitive data +* Denial of service affecting availability + +*Response*: Patch within 1 week, expedited release + +===== Medium (CVSS 4.0-6.9) + +* Cross-site scripting (XSS) (browser/WASM context) +* Information disclosure of non-sensitive data +* Low-impact denial of service + +*Response*: Patch within 2 weeks, next regular release + +===== Low (CVSS 0.1-3.9) + +* Minor information leaks +* Best practice violations +* Theoretical attacks with no known exploit + +*Response*: Patch within 30 days, next regular release + +=== Security Best Practices + +==== For Users + +[arabic] +. *Keep updated*: Always use the latest stable version +. *Verify signatures*: Check release signatures (when available) +. *Audit dependencies*: Run `+cargo audit+` regularly +. *Minimal permissions*: Run with least privilege necessary +. *Air-gapped environments*: Bunsenite works offline by design + +==== For Developers + +[arabic] +. *No `+unsafe+` code*: Never use `+unsafe+` blocks (enforced by +compiler) +. *Input validation*: Validate all external input +. *Error handling*: Use `+Result+` types, avoid `+unwrap()+` in library +code +. *Dependency review*: Review new dependencies carefully +. *Security testing*: Include security tests in test suite + +=== Known Limitations + +==== By Design + +[arabic] +. *Nickel evaluation*: Bunsenite evaluates Nickel code, which could +contain: +* Infinite loops (resource exhaustion) +* Large memory allocations +* Consider: Run evaluation in sandboxed environment for untrusted input +. *File I/O*: File reading follows OS permissions +* Does NOT escalate privileges +* Respects filesystem boundaries +. *WASM sandbox*: Browser WASM runs in sandbox, but: +* Subject to browser security model +* Can consume memory/CPU (denial of service) + +==== Mitigations + +We provide: + +* *Timeouts*: (Planned) Configurable evaluation timeouts +* *Memory limits*: (Planned) Configurable memory limits for evaluation +* *Resource monitoring*: (Planned) Track resource usage + +=== Security Audits + +[cols=",,,,",options="header",] +|=== +|Date |Auditor |Scope |Findings |Status +|2025-Q2 |Planned |Full codebase |N/A |Scheduled +|=== + +=== Cryptography + +Bunsenite does NOT implement cryptography. For cryptographic needs: + +* Use established libraries (e.g., `+ring+`, `+sodiumoxide+`) +* Never roll your own crypto +* Follow NIST/IETF recommendations + +=== Compliance + +* *OWASP Top 10*: N/A (not a web application) +* *CWE Top 25*: Memory safety issues prevented by Rust +* *GDPR*: No personal data collection +* *CCPA*: No personal data collection + +=== Security Tooling + +We use: + +* *`+cargo audit+`*: Check for known vulnerabilities in dependencies +* *`+cargo clippy+`*: Lint for security anti-patterns +* *`+cargo deny+`*: Check licenses and security advisories +* *GitLab Security Scanner*: Automated SAST in CI/CD +* *Dependabot*: (Planned) Automated dependency updates + +=== Contact + +* *GitHub Security Advisories*: +https://github.com/hyperpolymath/bunsenite/security/advisories/new[Report +a vulnerability] +* *Security.txt*: See `+.well-known/security.txt+` (RFC 9116 compliant) + +=== Attribution + +We believe in responsible disclosure and will credit security +researchers who: + +* Report vulnerabilities responsibly +* Allow coordinated disclosure +* Follow our security policy + +Credits will be listed in: - CHANGELOG.md - Release notes - SECURITY.md +(this file) + +=== Legal + +Security research conducted in good faith will not result in legal +action, provided: + +* You respect our disclosure timeline +* You do not exploit vulnerabilities beyond proof-of-concept +* You do not access user data or disrupt service +* You comply with applicable laws + +We support security researchers and the white-hat community. + +''''' + +*Last updated*: 2025-12-18 *Version*: 1.0.2 diff --git a/vendor/bunsenite/TEST-NEEDS.adoc b/vendor/bunsenite/TEST-NEEDS.adoc new file mode 100644 index 0000000..036072c --- /dev/null +++ b/vendor/bunsenite/TEST-NEEDS.adoc @@ -0,0 +1,58 @@ +== Test & Benchmark Requirements + +=== CRG Grade: C — ACHIEVED 2026-04-04 + +=== Current State + +* Unit tests: NONE verified (Cargo.toml exists but cargo not available +in this repo due to .tool-versions mismatch) +* Integration tests: 1 Zig integration test (template) +* E2E tests: NONE +* Benchmarks: 1 file exists (unverified) +* panic-attack scan: NEVER RUN + +=== What’s Missing + +==== Point-to-Point (P2P) + +* 11 Rust source files — test count unknown (cannot build) +* 5 Zig source files — only template integration test +* 3 Idris2 ABI files — no verification tests +* 4 AffineScript files — no tests +* 3 TypeScript files — no tests + +==== End-to-End (E2E) + +* Core functionality workflow not tested +* Integration between Rust, Zig, and AffineScript layers not tested + +==== Aspect Tests + +* [ ] Security (depends on what bunsenite does) +* [ ] Performance (benchmark file exists but unverified) +* [ ] Concurrency (if applicable) +* [ ] Error handling (graceful degradation) +* [ ] Accessibility (if UI exists) + +==== Build & Execution + +* [ ] cargo build — BLOCKED (.tool-versions mismatch) +* [ ] cargo test — BLOCKED +* [ ] zig build — not verified +* [ ] Self-diagnostic — none + +==== Benchmarks Needed + +* Verify existing benchmark file runs +* Specific benchmarks depend on functionality + +==== Self-Tests + +* [ ] panic-attack assail on own repo +* [ ] Fix .tool-versions to allow cargo to run + +=== Priority + +* *MEDIUM* — 11 Rust + 5 Zig + 4 AffineScript + 3 TS files. Cannot even +build due to tooling mismatch, which itself is a problem. Fix +.tool-versions first, then assess test needs. diff --git a/vendor/bunsenite/TOPOLOGY.adoc b/vendor/bunsenite/TOPOLOGY.adoc new file mode 100644 index 0000000..eeb8c78 --- /dev/null +++ b/vendor/bunsenite/TOPOLOGY.adoc @@ -0,0 +1,87 @@ +== Bunsenite — Project Topology + +=== System Architecture + +.... + ┌─────────────────────────────────────────┐ + │ CONSUMERS │ + │ (Deno, AffineScript, Browser, CLI) │ + └───────────────────┬─────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────┐ + │ INTERFACE LAYER │ + │ ┌───────────┐ ┌───────────────────┐ │ + │ │ Zig FFI │ │ wasm-bindgen │ │ + │ │ (C ABI) │ │ (JS/WASM) │ │ + │ └─────┬─────┘ └────────┬──────────┘ │ + └────────│─────────────────│──────────────┘ + │ │ + ▼ ▼ + ┌─────────────────────────────────────────┐ + │ RUST CORE (LIB.RS) │ + │ (Nickel-lang-core integration) │ + │ ┌───────────┐ ┌───────────────────┐ │ + │ │ Parser │ │ Evaluator │ │ + │ └─────┬─────┘ └────────┬──────────┘ │ + └────────│─────────────────│──────────────┘ + │ │ + ▼ ▼ + ┌─────────────────────────────────────────┐ + │ NICKEL CONFIG FILES │ + │ (*.ncl, validation) │ + └─────────────────────────────────────────┘ + + ┌─────────────────────────────────────────┐ + │ REPO INFRASTRUCTURE │ + │ Justfile / Guix .machine_readable/ │ + │ RSR Compliance .well-known/ │ + └─────────────────────────────────────────┘ +.... + +=== Completion Dashboard + +.... +COMPONENT STATUS NOTES +───────────────────────────────── ────────────────── ───────────────────────────────── +CORE & CLI + Rust Core (lib.rs) ██████████ 100% Nickel 0.9.1 integration stable + CLI Interface ██████████ 100% Parse/Validate/Info active + Nickel Loader ██████████ 100% String & File loading verified + +BINDINGS & FFI + Zig FFI (C ABI) ██████████ 100% Stable boundary for bindings + Deno Bindings ██████████ 100% Deno.dlopen integration active + WASM / Browser ██████████ 100% 95% native speed verified + AffineScript Bindings ████████░░ 80% Type definitions refining + +REPO INFRASTRUCTURE + Justfile / Guix ██████████ 100% Reproducible builds stable + .machine_readable/ ██████████ 100% STATE.a2ml tracking + RSR Bronze Tier ██████████ 100% Compliance certified + +───────────────────────────────────────────────────────────────────────────── +OVERALL: ██████████ 100% v0.1.0 Production Ready +.... + +=== Key Dependencies + +.... +Nickel Core ──────► Bunsenite Rust ──────► Zig FFI ──────► Deno/TS + │ + ▼ + wasm-bindgen ───► Browser +.... + +=== Update Protocol + +This file is maintained by both humans and AI agents. When updating: + +[arabic] +. *After completing a component*: Change its bar and percentage +. *After adding a component*: Add a new row in the appropriate section +. *After architectural changes*: Update the ASCII diagram +. *Date*: Update the `+Last updated+` comment at the top of this file + +Progress bars use: `+█+` (filled) and `+░+` (empty), 10 characters wide. +Percentages: 0%, 10%, 20%, … 100% (in 10% increments). diff --git a/vendor/bunsenite/UPSTREAM-REVISION b/vendor/bunsenite/UPSTREAM-REVISION new file mode 100644 index 0000000..0d271b2 --- /dev/null +++ b/vendor/bunsenite/UPSTREAM-REVISION @@ -0,0 +1,4 @@ +url=https://gitlab.com/hyperpolymath/bunsenite.git +revision=f788de3950b7541354806299cc8605dcf1608d11 +vendored=2026-09-22 +note=Only the nickel-lang-core dependency line was changed from the upstream tree (default features disabled for the lean embedded build). See VENDOR.adoc. diff --git a/vendor/bunsenite/VENDOR.adoc b/vendor/bunsenite/VENDOR.adoc new file mode 100644 index 0000000..21e597c --- /dev/null +++ b/vendor/bunsenite/VENDOR.adoc @@ -0,0 +1,68 @@ += VENDOR — bunsenite (reviewed vendor fork) +:toc: + +== Provenance + +[cols="1,2"] +|=== +|Upstream repository |https://gitlab.com/hyperpolymath/bunsenite.git +|Pinned revision |`f788de3950b7541354806299cc8605dcf1608d11` (recorded verbatim in `UPSTREAM-REVISION`, generated from `git rev-parse HEAD` at vendor time) +|Upstream version |1.0.2 +|Licence |MPL-2.0 (see `LICENSE`, unchanged) +|Vendored on |2026-09-22 +|=== + +The entire upstream tree at the pinned revision is vendored unmodified, +including docs and licence, **except** for the single dependency change +described below and the added `UPSTREAM-REVISION` marker file. + +== Local modification (deliberate, minimal) + +.`Cargo.toml` +[source,diff] +---- +-nickel-lang-core = "0.18.0" ++nickel-lang-core = { version = "0.18.0", default-features = false } +---- + +== Motivation + +`nickel-lang-core` 0.18.x enables by default the feature set +`markdown, repl, doc, format`, which pulls in: + +* `rustyline` + `anstyle` (interactive REPL — irrelevant when embedding), +* `comrak` (documentation engine), +* `termimad` (markdown rendering), +* `topiary-core`, `topiary-queries`, `tree-sitter-nickel` (formatter, + including a C toolchain build). + +None of these are needed to *evaluate* a policy file. Disabling default +features reduces the transitive dependency graph substantially and removes a +C-compiler requirement from the embedded path. The earlier +sandbox out-of-memory failures while compiling `nickel-lang-parser` were the +proximate motivation; note that `nickel-lang-parser` itself remains a +non-optional dependency of `nickel-lang-core`, so very small builders may +still need the constrained CI job +(`CARGO_BUILD_JOBS=1`, `RUSTFLAGS="-C debuginfo=0"`). + +Additionally, consumers should depend on this vendored crate with +`default-features = false` to drop bunsenite's own default `cli` feature +(clap), keeping only: `error`, `loader`, `ffi` modules. + +== Review checklist (for maintainers touching this fork) + +* [ ] Any upstream sync must re-apply the `default-features = false` change + and update `UPSTREAM-REVISION`. +* [ ] Diff scope stays limited to the dependency line; code changes belong + upstream (https://gitlab.com/hyperpolymath/bunsenite). +* [ ] Re-run: `CARGO_BUILD_JOBS=1 RUSTFLAGS="-C debuginfo=0 -Dwarnings" \ + cargo test -p policy-oracle --features nickel --lib --locked` + +== Multi-file Nickel imports + +Bunsenite's `NickelLoader` evaluates a single file through +`Program::new_from_file`; nothing in this crate configures Nickel's import +paths deliberately. conative-gating therefore takes the explicit position +documented in `docs/NICKEL-POLICY.adoc`: **policy files with `import` +statements are rejected fail-closed before evaluation**. This vendor fork +inherits that policy by usage, not by code change. diff --git a/vendor/bunsenite/benches/bunsenite_bench.rs b/vendor/bunsenite/benches/bunsenite_bench.rs new file mode 100644 index 0000000..183a345 --- /dev/null +++ b/vendor/bunsenite/benches/bunsenite_bench.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Throughput benchmarks for Bunsenite — small, medium, and large payloads. +//! +//! Complements `parser.rs` (which benchmarks round-trip parse/validate) with +//! focused throughput measurements across payload sizes, including loader +//! creation overhead and the `validate`-only fast path. + +use bunsenite::NickelLoader; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use std::hint::black_box; + +// --------------------------------------------------------------------------- +// Payload corpus +// --------------------------------------------------------------------------- + +/// Small payload (~80 bytes) — a minimal three-field record. +const SMALL_PAYLOAD: &str = r#"{ name = "small", port = 8080, active = true }"#; + +/// Medium payload (~350 bytes) — a two-section server/database record. +const MEDIUM_PAYLOAD: &str = r#" +{ + application = { + name = "medium-service", + version = "2.1.0", + environment = "staging", + }, + database = { + host = "db.staging.example.com", + port = 5432, + name = "staging_db", + pool_min = 2, + pool_max = 10, + ssl = true, + }, + server = { + bind = "0.0.0.0", + port = 8443, + workers = 8, + timeout_ms = 5000, + }, +} +"#; + +/// Large payload (~900 bytes) — a multi-section config with arrays and nesting. +const LARGE_PAYLOAD: &str = r#" +{ + service = { + name = "large-service", + version = "3.0.0", + region = "eu-west-1", + replicas = 5, + }, + endpoints = [ + { path = "/health", method = "GET", auth = false }, + { path = "/api/v1", method = "GET", auth = true }, + { path = "/api/v1", method = "POST", auth = true }, + { path = "/metrics", method = "GET", auth = false }, + ], + database = { + primary = { host = "db-primary.internal", port = 5432, pool = 20 }, + secondary = { host = "db-secondary.internal", port = 5432, pool = 10 }, + migrations = { auto = false, path = "migrations/" }, + }, + cache = { + provider = "redis", + host = "cache.internal", + port = 6379, + ttl_seconds = 300, + max_entries = 50000, + }, + logging = { + level = "warn", + structured = true, + sinks = ["stdout", "loki"], + }, + features = { + dark_mode = false, + beta_api = true, + rate_limit = { enabled = true, rps = 500 }, + }, +} +"#; + +// --------------------------------------------------------------------------- +// Benchmark 1: Throughput by payload size (parse_string) +// --------------------------------------------------------------------------- + +/// Measure `parse_string` throughput — bytes-per-second — for the three +/// payload sizes. This is the primary end-to-end measurement. +fn bench_throughput_parse(c: &mut Criterion) { + let loader = NickelLoader::new(); + let mut group = c.benchmark_group("throughput/parse_string"); + + for (label, payload) in [ + ("small", SMALL_PAYLOAD), + ("medium", MEDIUM_PAYLOAD), + ("large", LARGE_PAYLOAD), + ] { + group.throughput(Throughput::Bytes(payload.len() as u64)); + group.bench_with_input(BenchmarkId::new(label, payload.len()), payload, |b, src| { + b.iter(|| loader.parse_string(black_box(src), "bench.ncl")) + }); + } + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Benchmark 2: Throughput by payload size (validate — parsing only, no eval) +// --------------------------------------------------------------------------- + +/// Measure `validate` throughput for the same three payloads. `validate` +/// skips evaluation, so it is expected to be faster than `parse_string` and +/// serves as a lower bound on parser overhead. +fn bench_throughput_validate(c: &mut Criterion) { + let loader = NickelLoader::new(); + let mut group = c.benchmark_group("throughput/validate"); + + for (label, payload) in [ + ("small", SMALL_PAYLOAD), + ("medium", MEDIUM_PAYLOAD), + ("large", LARGE_PAYLOAD), + ] { + group.throughput(Throughput::Bytes(payload.len() as u64)); + group.bench_with_input(BenchmarkId::new(label, payload.len()), payload, |b, src| { + b.iter(|| loader.validate(black_box(src), "bench.ncl")) + }); + } + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Benchmark 3: Loader construction overhead +// --------------------------------------------------------------------------- + +/// Measure the cost of calling `NickelLoader::new()`. This baseline confirms +/// that the loader itself is cheap to create and that callers may safely +/// construct one per-request if needed. +fn bench_loader_creation(c: &mut Criterion) { + c.bench_function("loader_creation", |b| { + b.iter(|| black_box(NickelLoader::new())) + }); +} + +// --------------------------------------------------------------------------- +// Benchmark 4: Repeated small-payload parses on a shared loader +// --------------------------------------------------------------------------- + +/// Measures repeated small-config parses on a single long-lived loader to +/// detect any state accumulation or degradation in the loader between calls. +fn bench_repeated_small_on_shared_loader(c: &mut Criterion) { + let loader = NickelLoader::new(); + c.bench_function("repeated_small/shared_loader", |b| { + b.iter(|| loader.parse_string(black_box(SMALL_PAYLOAD), "rep.ncl")) + }); +} + +// --------------------------------------------------------------------------- +// Benchmark 5: Error path — parse of invalid input +// --------------------------------------------------------------------------- + +/// Measures the cost of the error path: how quickly the parser rejects +/// obviously invalid Nickel. A fast error path matters for tooling that +/// validates user input interactively. +fn bench_error_path(c: &mut Criterion) { + let loader = NickelLoader::new(); + let invalid = "{ broken syntax @@@ !!!"; + c.bench_function("error_path/invalid_input", |b| { + b.iter(|| loader.parse_string(black_box(invalid), "invalid.ncl")) + }); +} + +// --------------------------------------------------------------------------- +// Criterion wiring +// --------------------------------------------------------------------------- + +criterion_group!( + benches, + bench_throughput_parse, + bench_throughput_validate, + bench_loader_creation, + bench_repeated_small_on_shared_loader, + bench_error_path, +); +criterion_main!(benches); diff --git a/vendor/bunsenite/benches/parser.rs b/vendor/bunsenite/benches/parser.rs new file mode 100644 index 0000000..1cb51a1 --- /dev/null +++ b/vendor/bunsenite/benches/parser.rs @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Performance benchmarks for Bunsenite +//! +//! Run with: cargo bench + +use bunsenite::NickelLoader; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use std::hint::black_box; + +/// Simple configuration (~100 bytes) +const SIMPLE_CONFIG: &str = r#" +{ + name = "simple", + version = "1.0.0", + enabled = true, +} +"#; + +/// Medium configuration (~500 bytes) +const MEDIUM_CONFIG: &str = r#" +{ + name = "medium", + version = "1.0.0", + database = { + host = "localhost", + port = 5432, + name = "mydb", + ssl = true, + }, + server = { + host = "0.0.0.0", + port = 8080, + workers = 4, + timeout = 30, + }, + logging = { + level = "info", + format = "json", + file = "/var/log/app.log", + }, + features = { + auth = true, + cache = true, + metrics = true, + }, +} +"#; + +/// Complex configuration with contracts (~1500 bytes) +const COMPLEX_CONFIG: &str = r#" +let Port = std.contract.from_predicate (fun x => x >= 1 && x <= 65535) in +let NonEmpty = std.contract.from_predicate (fun x => std.string.length x > 0) in + +{ + name | NonEmpty = "complex-app", + version = "2.0.0", + + database = { + primary = { + host | NonEmpty = "db-primary.example.com", + port | Port = 5432, + name = "production", + pool_size = 20, + ssl = { + enabled = true, + verify = true, + ca_cert = "/etc/ssl/certs/ca.pem", + }, + }, + replica = { + host | NonEmpty = "db-replica.example.com", + port | Port = 5432, + name = "production", + pool_size = 10, + }, + }, + + servers = [ + { name = "web-1", host = "10.0.1.1", port | Port = 8080 }, + { name = "web-2", host = "10.0.1.2", port | Port = 8080 }, + { name = "web-3", host = "10.0.1.3", port | Port = 8080 }, + ], + + cache = { + redis = { + host = "redis.example.com", + port | Port = 6379, + db = 0, + ttl = 3600, + }, + }, + + logging = { + level = "info", + outputs = [ + { type = "console", format = "pretty" }, + { type = "file", path = "/var/log/app.log", format = "json" }, + { type = "syslog", facility = "local0" }, + ], + }, + + features = { + authentication = { enabled = true, provider = "oauth2" }, + rate_limiting = { enabled = true, requests_per_minute = 100 }, + caching = { enabled = true, strategy = "lru" }, + metrics = { enabled = true, endpoint = "/metrics" }, + }, +} +"#; + +fn benchmark_parse(c: &mut Criterion) { + let loader = NickelLoader::new(); + + let mut group = c.benchmark_group("parse"); + + // Simple config + group.throughput(Throughput::Bytes(SIMPLE_CONFIG.len() as u64)); + group.bench_with_input( + BenchmarkId::new("simple", SIMPLE_CONFIG.len()), + &SIMPLE_CONFIG, + |b, config| b.iter(|| loader.parse(black_box(*config), "simple.ncl")), + ); + + // Medium config + group.throughput(Throughput::Bytes(MEDIUM_CONFIG.len() as u64)); + group.bench_with_input( + BenchmarkId::new("medium", MEDIUM_CONFIG.len()), + &MEDIUM_CONFIG, + |b, config| b.iter(|| loader.parse(black_box(*config), "medium.ncl")), + ); + + // Complex config + group.throughput(Throughput::Bytes(COMPLEX_CONFIG.len() as u64)); + group.bench_with_input( + BenchmarkId::new("complex", COMPLEX_CONFIG.len()), + &COMPLEX_CONFIG, + |b, config| b.iter(|| loader.parse(black_box(*config), "complex.ncl")), + ); + + group.finish(); +} + +fn benchmark_validate(c: &mut Criterion) { + let loader = NickelLoader::new(); + + let mut group = c.benchmark_group("validate"); + + group.bench_with_input( + BenchmarkId::new("simple", SIMPLE_CONFIG.len()), + &SIMPLE_CONFIG, + |b, config| b.iter(|| loader.validate(black_box(*config), "simple.ncl")), + ); + + group.bench_with_input( + BenchmarkId::new("medium", MEDIUM_CONFIG.len()), + &MEDIUM_CONFIG, + |b, config| b.iter(|| loader.validate(black_box(*config), "medium.ncl")), + ); + + group.bench_with_input( + BenchmarkId::new("complex", COMPLEX_CONFIG.len()), + &COMPLEX_CONFIG, + |b, config| b.iter(|| loader.validate(black_box(*config), "complex.ncl")), + ); + + group.finish(); +} + +fn benchmark_loader_creation(c: &mut Criterion) { + c.bench_function("loader_creation", |b| { + b.iter(|| black_box(NickelLoader::new())) + }); +} + +criterion_group!( + benches, + benchmark_parse, + benchmark_validate, + benchmark_loader_creation +); +criterion_main!(benches); diff --git a/vendor/bunsenite/bindings/deno/README.adoc b/vendor/bunsenite/bindings/deno/README.adoc new file mode 100644 index 0000000..02d91a1 --- /dev/null +++ b/vendor/bunsenite/bindings/deno/README.adoc @@ -0,0 +1,247 @@ +== Bunsenite Deno Bindings + +image:https://img.shields.io/badge/License-MPL–2.0-blue.svg[License: +MPL-2.0,link="`https://github.com/hyperpolymath/palimpsest-license`"] + +TypeScript bindings for +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite[Bunsenite] +using Deno’s native FFI. + +=== Installation + +[arabic] +. Build the Bunsenite native library: + +[source,bash] +---- +cd ../.. +cargo build --release +---- + +[arabic, start=2] +. Import the bindings in your Deno code: + +[source,typescript] +---- +import { parseNickel } from "https://raw.githubusercontent.com/example/bunsenite/main/bindings/deno/bunsenite.ts"; +---- + +Or use local path: + +[source,typescript] +---- +import { parseNickel } from "./bunsenite.ts"; +---- + +=== Usage + +==== Basic Parsing + +[source,typescript] +---- +import { parseNickel } from "./bunsenite.ts"; + +const config = parseNickel( + `{ + name = "my-app", + version = "1.0.0", + port = 8080, + }`, + "config.ncl" +); + +console.log(config.port); // 8080 +---- + +==== Parse File + +[source,typescript] +---- +import { parseFile } from "./bunsenite.ts"; + +const config = await parseFile("./config.ncl"); +console.log(config); +---- + +==== Validation + +[source,typescript] +---- +import { validateNickel } from "./bunsenite.ts"; + +try { + validateNickel('{ foo = 42 }', "config.ncl"); + console.log("Valid!"); +} catch (e) { + console.error("Invalid:", e.message); +} +---- + +==== Library Info + +[source,typescript] +---- +import { getVersion, getRSRTier, getTPCFPerimeter } from "./bunsenite.ts"; + +console.log("Version:", getVersion()); +console.log("RSR Tier:", getRSRTier()); +console.log("TPCF Perimeter:", getTPCFPerimeter()); +---- + +=== API Reference + +==== `+parseNickel(source: string, name: string): unknown+` + +Parse and evaluate a Nickel configuration string. + +* `+source+`: The Nickel configuration source code +* `+name+`: A name for this configuration (used in error messages) +* Returns: Parsed configuration as a JavaScript object +* Throws: Error if parsing or evaluation fails + +==== `+validateNickel(source: string, name: string): boolean+` + +Validate a Nickel configuration without evaluating it. + +* `+source+`: The Nickel configuration source code +* `+name+`: A name for this configuration (used in error messages) +* Returns: `+true+` if valid +* Throws: Error if validation fails + +==== `+parseFile(path: string): Promise+` + +Parse a Nickel configuration file. + +* `+path+`: Path to the Nickel configuration file +* Returns: Parsed configuration as a JavaScript object +* Throws: Error if file cannot be read or parsing fails + +==== `+validateFile(path: string): Promise+` + +Validate a Nickel configuration file. + +* `+path+`: Path to the Nickel configuration file +* Returns: `+true+` if valid +* Throws: Error if file cannot be read or validation fails + +==== `+getVersion(): string+` + +Get Bunsenite library version. + +* Returns: Version string (e.g., "`0.1.0`") + +==== `+getRSRTier(): string+` + +Get RSR compliance tier. + +* Returns: RSR tier (e.g., "`bronze`") + +==== `+getTPCFPerimeter(): number+` + +Get TPCF perimeter number. + +* Returns: Perimeter number (3 for Community Sandbox) + +=== Permissions + +Deno requires the following permissions: + +* `+--allow-ffi+`: To load the native library +* `+--allow-read+`: To read configuration files (if using `+parseFile+`) + +Example: + +[source,bash] +---- +deno run --allow-ffi --allow-read example.ts +---- + +=== Examples + +See link:./example.ts[example.ts] for comprehensive examples. + +Run the example: + +[source,bash] +---- +# Make sure bunsenite is built first +cd ../.. +cargo build --release + +# Run example +cd bindings/deno +deno run --allow-ffi --allow-read example.ts +---- + +=== Platform Support + +[cols=",,",options="header",] +|=== +|Platform |Library Name |Status +|Linux |`+libbunsenite.so+` |✅ +|macOS |`+libbunsenite.dylib+` |✅ +|Windows |`+bunsenite.dll+` |✅ +|=== + +The bindings automatically detect your platform and load the correct +library. + +=== Architecture + +.... +┌─────────────────┐ +│ Deno Runtime │ +│ (TypeScript) │ +└────────┬────────┘ + │ FFI + ▼ + ┌──────────┐ + │ Zig FFI │ + │ (C ABI) │ + └─────┬────┘ + │ + ▼ +┌─────────────────┐ +│ Rust Core │ +│ (lib.rs) │ +│ │ +│ nickel-lang-core│ +│ 0.9.1 │ +└─────────────────┘ +.... + +=== Performance + +~90% of native Rust performance (minimal C ABI overhead). + +=== Security + +* *Memory Safety*: Rust ownership model prevents memory errors +* *Type Safety*: Full type checking via Nickel + Rust +* *No `+unsafe+`*: Zero unsafe code blocks in Bunsenite core +* *Offline-First*: No network dependencies + +=== License + +Dual MPL-2.0 + MPL-2.0 v0.8 + +See link:../../LICENSE[LICENSE] for details. + +=== Contributing + +See link:../../CONTRIBUTING.md[CONTRIBUTING.md] for development +guidelines. + +=== Support + +* *Issues*: +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues[GitLab +Issues] +* *Discussions*: +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues[GitLab +Discussions] +* *Documentation*: link:../../README.md[Main README] + +''''' + +Made with ❤️ by the Campaign for Cooler Coding and Programming diff --git a/vendor/bunsenite/bindings/deno/bunsenite.affine b/vendor/bunsenite/bindings/deno/bunsenite.affine new file mode 100644 index 0000000..b917f06 --- /dev/null +++ b/vendor/bunsenite/bindings/deno/bunsenite.affine @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module bunsenite; + +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// Bunsenite Deno FFI Bindings +// TypeScript bindings for Deno runtime using native FFI +// +// NOTE: This is Deno-specific TypeScript, NOT plain TypeScript! +// It uses Deno.dlopen for native FFI calls to the Zig C ABI layer. +// +// Usage: +// import { parseNickel, validateNickel } from "./bunsenite.ts"; +// let result = parseNickel('{ foo = 42 }', "config.ncl"); +// console.log(result); + +// Detect library path based on platform +fn getLibraryPath(): string { + let platform = Deno.build.os; + let libName = platform === "windows" ? "bunsenite.dll" + : platform === "darwin" ? "libbunsenite.dylib" + : "libbunsenite.so"; + + // Try common locations + let paths = [ + `../../target/release/${libName}`, + `./target/release/${libName}`, + `./${libName}`, + ]; + + for (const path of paths) { + try { + Deno.statSync(path); + return path; + } catch { + // File doesn't exist, try next + } + } + + throw new Error( + `Could not find ${libName}. Please build with: cargo build --release`, + ); +} + +// FFI symbol definitions +// These match the C ABI exported by the Zig layer +let symbols = { + // Parse Nickel string to JSON + // char* parse_nickel(const char* source, const char* name) + parse_nickel: { + parameters: ["pointer", "pointer"], + result: "pointer", + }, + + // Validate Nickel without evaluating + // int validate_nickel(const char* source, const char* name) + validate_nickel: { + parameters: ["pointer", "pointer"], + result: "i32", + }, + + // Free string allocated by Rust + // void free_string(char* ptr) + free_string: { + parameters: ["pointer"], + result: "void", + }, + + // Get library version + // const char* version() + version: { + parameters: [], + result: "pointer", + }, + + // Get RSR tier + // const char* rsr_tier() + rsr_tier: { + parameters: [], + result: "pointer", + }, + + // Get TPCF perimeter + // uint8_t tpcf_perimeter() + tpcf_perimeter: { + parameters: [], + result: "u8", + }, +} as const; + +// Load the native library +let lib: Deno.DynamicLibrary | null = null; + +fn getLib(): Deno.DynamicLibrary { + if (!lib) { + let libPath = getLibraryPath(); + lib = Deno.dlopen(libPath, symbols); + } + return lib; +} + +// Helper: Convert JS string to C string (null-terminated) +fn toCString(str: string): Uint8Array { + let encoder = new TextEncoder(); + let encoded = encoder.encode(str + "\0"); + return encoded; +} + +// Helper: Convert C string pointer to JS string +fn fromCString(ptr: Deno.UnsafePointer): string { + if (!ptr) { + throw new Error("Null pointer received from C"); + } + let view = new Deno.UnsafePointerView(ptr); + return view.getCString(); +} + +/** + * Parse and evaluate a Nickel configuration string + * + * @param source - The Nickel configuration source code + * @param name - A name for this configuration (used in error messages) + * @returns Parsed configuration as a JavaScript object + * @throws Error if parsing or evaluation fails + * + * @example + * ```typescript + * let config = parseNickel('{ name = "example", port = 8080 }', "config.ncl"); + * console.log(config.port); // 8080 + * ``` + */ +fn parseNickel(source: string, name: string): unknown { + let library = getLib(); + + let sourceBytes = toCString(source); + let nameBytes = toCString(name); + + let resultPtr = library.symbols.parse_nickel( + sourceBytes, + nameBytes, + ) as Deno.UnsafePointer; + + if (!resultPtr) { + throw new Error(`Failed to parse Nickel config: ${name}`); + } + + try { + let jsonString = fromCString(resultPtr); + return JSON.parse(jsonString); + } finally { + // Free the string allocated by Rust + library.symbols.free_string(resultPtr); + } +} + +/** + * Validate a Nickel configuration without evaluating it + * + * @param source - The Nickel configuration source code + * @param name - A name for this configuration (used in error messages) + * @returns true if valid, throws Error if invalid + * @throws Error if validation fails + * + * @example + * ```typescript + * try { + * validateNickel('{ foo = 42 }', "config.ncl"); + * console.log("Valid!"); + * } catch (e) { + * console.error("Invalid:", e.message); + * } + * ``` + */ +fn validateNickel(source: string, name: string): boolean { + let library = getLib(); + + let sourceBytes = toCString(source); + let nameBytes = toCString(name); + + let result = library.symbols.validate_nickel( + sourceBytes, + nameBytes, + ); + + if (result !== 0) { + throw new Error(`Validation failed for: ${name}`); + } + + return true; +} + +/** + * Get Bunsenite library version + * + * @returns Version string (e.g., "0.1.0") + * + * @example + * ```typescript + * console.log("Bunsenite version:", getVersion()); + * ``` + */ +fn getVersion(): string { + let library = getLib(); + let ptr = library.symbols.version() as Deno.UnsafePointer; + return fromCString(ptr); +} + +/** + * Get RSR compliance tier + * + * @returns RSR tier (e.g., "bronze") + * + * @example + * ```typescript + * console.log("RSR tier:", getRSRTier()); + * ``` + */ +fn getRSRTier(): string { + let library = getLib(); + let ptr = library.symbols.rsr_tier() as Deno.UnsafePointer; + return fromCString(ptr); +} + +/** + * Get TPCF perimeter number + * + * @returns Perimeter number (3 for Community Sandbox) + * + * @example + * ```typescript + * console.log("TPCF perimeter:", getTPCFPerimeter()); + * ``` + */ +fn getTPCFPerimeter(): number { + let library = getLib(); + return library.symbols.tpcf_perimeter(); +} + +/** + * Parse a Nickel configuration file + * + * @param path - Path to the Nickel configuration file + * @returns Parsed configuration as a JavaScript object + * @throws Error if file cannot be read or parsing fails + * + * @example + * ```typescript + * let config = await parseFile("./config.ncl"); + * console.log(config); + * ``` + */ +async fn parseFile(path: string): unknown { + let source = await Deno.readTextFile(path); + return parseNickel(source, path); +} + +/** + * Validate a Nickel configuration file + * + * @param path - Path to the Nickel configuration file + * @returns true if valid, throws Error if invalid + * @throws Error if file cannot be read or validation fails + * + * @example + * ```typescript + * try { + * await validateFile("./config.ncl"); + * console.log("File is valid!"); + * } catch (e) { + * console.error("Invalid file:", e.message); + * } + * ``` + */ +async fn validateFile(path: string): boolean { + let source = await Deno.readTextFile(path); + return validateNickel(source, path); +} + +// Cleanup on exit +globalThis.addEventListener("unload", () => { + if (lib) { + lib.close(); + lib = null; + } +}); + +// Export type definitions +struct BunseniteConfig { Record; + +// Re-for convenience +default { + parseNickel, + validateNickel, + parseFile, + validateFile, + getVersion, + getRSRTier, + getTPCFPerimeter, +}; + diff --git a/vendor/bunsenite/bindings/deno/example.affine b/vendor/bunsenite/bindings/deno/example.affine new file mode 100644 index 0000000..4bf9120 --- /dev/null +++ b/vendor/bunsenite/bindings/deno/example.affine @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module example; + +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +#!/usr/bin/env deno run --allow-ffi --allow-read + +// Bunsenite Deno Example +// Demonstrates how to use Bunsenite from Deno + +import { + getTPCFPerimeter, + getRSRTier, + getVersion, + parseFile, + parseNickel, + validateNickel, +} from "./bunsenite.ts"; + +console.log("=== Bunsenite Deno Example ===\n"); + +// Show library info +console.log("Library Information:"); +console.log(` Version: ${getVersion()}`); +console.log(` RSR Tier: ${getRSRTier()}`); +console.log(` TPCF Perimeter: ${getTPCFPerimeter()}`); +console.log(""); + +// Example 1: Parse simple inline config +console.log("Example 1: Parse inline config"); +let config1 = parseNickel( + `{ + name = "deno-example", + version = "1.0.0", + port = 8080, + }`, + "inline.ncl", +); +console.log("Result:", JSON.stringify(config1, null, 2)); +console.log(""); + +// Example 2: Parse with computations +console.log("Example 2: Parse with computations"); +let config2 = parseNickel( + `{ + base_port = 8000, + api_port = base_port + 80, + db_port = base_port + 432, + url = "http://localhost:" ++ std.string.from_number api_port, + }`, + "computed.ncl", +); +console.log("Result:", JSON.stringify(config2, null, 2)); +console.log(""); + +// Example 3: Validate config +console.log("Example 3: Validate config"); +try { + validateNickel('{ valid = true, works = "yes" }', "valid.ncl"); + console.log("✓ Config is valid"); +} catch (e) { + console.error("✗ Config is invalid:", e.message); +} +console.log(""); + +// Example 4: Validate invalid config (should fail) +console.log("Example 4: Validate invalid config"); +try { + validateNickel("{ invalid = }", "invalid.ncl"); // Missing value + console.log("✓ Config is valid"); +} catch (e) { + console.log("✓ Correctly detected invalid config"); +} +console.log(""); + +// Example 5: Parse file (if it exists) +console.log("Example 5: Parse file"); +try { + let config = await parseFile("../../examples/config.ncl"); + console.log("Parsed config from file:"); + console.log(` Name: ${(config as unknown).name}`); + console.log(` Version: ${(config as unknown).version}`); + console.log(` Server port: ${(config as unknown).server.port}`); +} catch (e) { + console.log(`Could not parse file: ${e.message}`); + console.log("(This is expected if bunsenite hasn't been built yet)"); +} +console.log(""); + +// Example 6: Advanced features +console.log("Example 6: Advanced features"); +let config6 = parseNickel( + `{ + # Comments work! + app_name = "bunsenite", + + # Lists + allowed_hosts = ["localhost", "127.0.0.1", "::1"], + + # Nested records + database = { + host = "localhost", + port = 5432, + max_connections = 20, + }, + + # Computed values + db_url = "postgres://" ++ database.host ++ ":" ++ std.string.from_number database.port, + }`, + "advanced.ncl", +); +console.log("Advanced config:", JSON.stringify(config6, null, 2)); + +console.log("\n✓ All examples completed successfully!"); + diff --git a/vendor/bunsenite/bindings/rescript/Bunsenite.affine b/vendor/bunsenite/bindings/rescript/Bunsenite.affine new file mode 100644 index 0000000..7a1072f --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/Bunsenite.affine @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module Bunsenite; + +// Bunsenite Rescript Bindings +// Type-safe Rescript bindings for Bunsenite via C FFI +// +// Usage: +// open Bunsenite +// fn config = parseNickel("{foo = 42}", "config.ncl") +// Js.log(config) + +// External C FFI declarations +// These bind to the C ABI provided by the Zig layer + +@module("./bunsenite_ffi") +external parseNickelRaw: (string, string) => Js.Nullable.t = "parse_nickel" + +@module("./bunsenite_ffi") +external validateNickelRaw: (string, string) => int = "validate_nickel" + +@module("./bunsenite_ffi") +external versionRaw: unit => string = "version" + +@module("./bunsenite_ffi") +external rsrTierRaw: unit => string = "rsr_tier" + +@module("./bunsenite_ffi") +external tpcfPerimeterRaw: unit => int = "tpcf_perimeter" + +// Result struct for error handling +struct result<'a, 'e> = Ok('a) | Error('e) + +// Error struct +struct error { + | ParseError(string) + | ValidationError(string) + | InvalidInput(string) + +// Parse and evaluate a Nickel configuration string +// +// Example: +// fn config = parseNickel("{name = \"example\", port = 8080}", "config.ncl") +// switch config { +// | Ok(json) => Js.log(json) +// | Error(err) => Js.log2("Error:", err) +// } +fn parseNickel = (source: string, name: string): result => { + fn result = parseNickelRaw(source, name) + + switch Js.Nullable.toOption(result) { + | Some(jsonString) => + try { + fn parsed = Js.Json.parseExn(jsonString) + Ok(parsed) + } catch { + | _ => Error(ParseError("Failed to parse JSON result")) + } + | None => Error(ParseError("Failed to parse Nickel configuration: " ++ name)) + } +} + +// Validate a Nickel configuration without evaluating it +// +// Example: +// fn result = validateNickel("{foo = 42}", "config.ncl") +// switch result { +// | Ok() => Js.log("Valid!") +// | Error(err) => Js.log2("Invalid:", err) +// } +fn validateNickel = (source: string, name: string): result => { + fn resultCode = validateNickelRaw(source, name) + + if resultCode == 0 { + Ok() + } else { + Error(ValidationError("Validation failed for: " ++ name)) + } +} + +// Get library version +// +// Example: +// fn ver = getVersion() +// Js.log2("Version:", ver) +fn getVersion = (): string => { + versionRaw() +} + +// Get RSR compliance tier +// +// Example: +// fn tier = getRSRTier() +// Js.log2("RSR Tier:", tier) +fn getRSRTier = (): string => { + rsrTierRaw() +} + +// Get TPCF perimeter number +// +// Example: +// fn perimeter = getTPCFPerimeter() +// Js.log2("TPCF Perimeter:", perimeter) +fn getTPCFPerimeter = (): int => { + tpcfPerimeterRaw() +} + +// Helper: Parse Nickel file from filesystem +// Requires Node.js fs module +// +// Example: +// fn config = parseFile("./config.ncl") +// switch config { +// | Ok(json) => Js.log(json) +// | Error(err) => Js.log2("Error:", err) +// } +@module("fs") +external readFileSync: (string, string) => string = "readFileSync" + +fn parseFile = (path: string): result => { + try { + fn source = readFileSync(path, "utf8") + parseNickel(source, path) + } catch { + | _ => Error(InvalidInput("Failed to read file: " ++ path)) + } +} + +// Helper: Validate Nickel file from filesystem +// +// Example: +// fn result = validateFile("./config.ncl") +// switch result { +// | Ok() => Js.log("Valid!") +// | Error(err) => Js.log2("Invalid:", err) +// } +fn validateFile = (path: string): result => { + try { + fn source = readFileSync(path, "utf8") + validateNickel(source, path) + } catch { + | _ => Error(InvalidInput("Failed to read file: " ++ path)) + } +} + +// Helper: Get config value by key path +// Example: getConfigValue(config, ["server", "port"]) +fn rec getConfigValue = (json: Js.Json.t, path: list): option => { + switch path { + | list{} => Some(json) + | list{key, ...rest} => + switch Js.Json.decodeObject(json) { + | Some(obj) => + switch Js.Dict.get(obj, key) { + | Some(value) => getConfigValue(value, rest) + | None => None + } + | None => None + } + } +} + +// Helper: Convert error to string for display +fn errorToString = (err: error): string => { + switch err { + | ParseError(msg) => "Parse Error: " ++ msg + | ValidationError(msg) => "Validation Error: " ++ msg + | InvalidInput(msg) => "Invalid Input: " ++ msg + } +} + +// Re-export result struct for convenience +struct parseResult { result +struct validateResult { result + diff --git a/vendor/bunsenite/bindings/rescript/Bunsenite_test.affine b/vendor/bunsenite/bindings/rescript/Bunsenite_test.affine new file mode 100644 index 0000000..3a4c9c0 --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/Bunsenite_test.affine @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module Bunsenite_test; + +// SPDX-License-Identifier: MPL-2.0 +// Bunsenite ReScript Bindings Test Suite + +open Bunsenite + +// Test helpers +fn assertEqual = (actual, expected, testName) => { + if actual == expected { + Console.log(`✓ ${testName}`) + } else { + Console.error(`✗ ${testName}`) + Console.error(` Expected: ${expected->Js.Json.stringify}`) + Console.error(` Actual: ${actual->Js.Json.stringify}`) + } +} + +fn assertOk = (result, testName) => { + switch result { + | Ok(_) => Console.log(`✓ ${testName}`) + | Error(err) => { + Console.error(`✗ ${testName}`) + Console.error(` Error: ${errorToString(err)}`) + } + } +} + +fn assertError = (result, testName) => { + switch result { + | Error(_) => Console.log(`✓ ${testName}`) + | Ok(_) => Console.error(`✗ ${testName}: Expected error but got Ok`) + } +} + +// Test suite +fn runTests = () => { + Console.log("\n🧪 Bunsenite ReScript Bindings Test Suite\n") + + // Test 1: Parse simple Nickel configuration + Console.log("Parse Tests:") + fn simpleConfig = parseNickel("{foo = 42}", "test.ncl") + assertOk(simpleConfig, "Parse simple number configuration") + + // Test 2: Parse object configuration + fn objectConfig = parseNickel("{name = \"test\", value = 100}", "object.ncl") + assertOk(objectConfig, "Parse object configuration") + + // Test 3: Parse nested configuration + fn nestedConfig = parseNickel("{server = {port = 8080, host = \"localhost\"}}", "nested.ncl") + assertOk(nestedConfig, "Parse nested configuration") + + // Test 4: Parse array configuration + fn arrayConfig = parseNickel("{items = [1, 2, 3, 4, 5]}", "array.ncl") + assertOk(arrayConfig, "Parse array configuration") + + // Test 5: Parse invalid syntax (should error) + fn invalidConfig = parseNickel("{foo = }", "invalid.ncl") + assertError(invalidConfig, "Parse invalid syntax returns error") + + // Test 6: Parse empty configuration + fn emptyConfig = parseNickel("{}", "empty.ncl") + assertOk(emptyConfig, "Parse empty configuration") + + // Validation Tests + Console.log("\nValidation Tests:") + + fn validConfig = validateNickel("{foo = 42}", "valid.ncl") + assertOk(validConfig, "Validate correct configuration") + + fn invalidValidation = validateNickel("{foo = }", "invalid-validate.ncl") + assertError(invalidValidation, "Validate incorrect configuration returns error") + + // Test 7: Validate complex configuration + fn complexValid = validateNickel( + "{ + app = { + name = \"example\", + version = \"1.0.0\", + config = { + debug = true, + port = 3000 + } + } + }", + "complex.ncl", + ) + assertOk(complexValid, "Validate complex nested configuration") + + // Library Info Tests + Console.log("\nLibrary Info Tests:") + + fn version = getVersion() + Console.log(`✓ Got version: ${version}`) + + fn tier = getRSRTier() + Console.log(`✓ Got RSR tier: ${tier}`) + + fn perimeter = getTPCFPerimeter() + Console.log(`✓ Got TPCF perimeter: ${perimeter->Int.toString}`) + + // Config Value Tests + Console.log("\nConfig Value Extraction Tests:") + + switch objectConfig { + | Ok(json) => { + // Test extracting top-level value + fn nameValue = getConfigValue(json, list{"name"}) + switch nameValue { + | Some(_) => Console.log("✓ Extract top-level value") + | None => Console.error("✗ Failed to extract top-level value") + } + + // Test extracting non-existent value + fn missingValue = getConfigValue(json, list{"missing"}) + switch missingValue { + | None => Console.log("✓ Non-existent value returns None") + | Some(_) => Console.error("✗ Non-existent value should return None") + } + } + | Error(_) => Console.error("✗ Could not test config value extraction") + } + + switch nestedConfig { + | Ok(json) => { + // Test extracting nested value + fn portValue = getConfigValue(json, list{"server", "port"}) + switch portValue { + | Some(_) => Console.log("✓ Extract nested value") + | None => Console.error("✗ Failed to extract nested value") + } + + // Test extracting with invalid path + fn invalidPath = getConfigValue(json, list{"server", "nonexistent", "deep"}) + switch invalidPath { + | None => Console.log("✓ Invalid nested path returns None") + | Some(_) => Console.error("✗ Invalid path should return None") + } + } + | Error(_) => Console.error("✗ Could not test nested value extraction") + } + + // Error handling tests + Console.log("\nError Handling Tests:") + + fn parseErr = parseNickel("{invalid syntax here}", "error-test.ncl") + switch parseErr { + | Error(err) => { + fn errStr = errorToString(err) + Console.log(`✓ Error converted to string: ${errStr}`) + } + | Ok(_) => Console.error("✗ Expected parse error") + } + + // Result struct tests + Console.log("\nResult Type Tests:") + + fn successResult: parseResult = Ok(Js.Json.null) + switch successResult { + | Ok(_) => Console.log("✓ parseResult Ok variant works") + | Error(_) => Console.error("✗ parseResult Ok variant failed") + } + + fn errorResult: parseResult = Error(ParseError("test")) + switch errorResult { + | Error(_) => Console.log("✓ parseResult Error variant works") + | Ok(_) => Console.error("✗ parseResult Error variant failed") + } + + fn validateSuccess: validateResult = Ok() + switch validateSuccess { + | Ok() => Console.log("✓ validateResult Ok variant works") + | Error(_) => Console.error("✗ validateResult Ok variant failed") + } + + Console.log("\n✅ Test suite complete\n") +} + +// Run tests +runTests() + diff --git a/vendor/bunsenite/bindings/rescript/Example.affine b/vendor/bunsenite/bindings/rescript/Example.affine new file mode 100644 index 0000000..790537f --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/Example.affine @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module Example; + +// SPDX-License-Identifier: MPL-2.0 +// Bunsenite ReScript Bindings Example + +open Bunsenite + +// Example 1: Simple parsing +fn example1 = () => { + Console.log("\n📝 Example 1: Simple Configuration Parsing\n") + + fn config = parseNickel( + "{ + app_name = \"my-application\", + version = \"1.0.0\", + port = 8080 + }", + "app-config.ncl", + ) + + switch config { + | Ok(json) => { + Console.log("✓ Configuration parsed successfully!") + Console.log(Js.Json.stringify(json)) + + // Extract specific values + switch getConfigValue(json, list{"app_name"}) { + | Some(name) => Console.log(`App name: ${Js.Json.stringify(name)}`) + | None => Console.log("App name not found") + } + + switch getConfigValue(json, list{"port"}) { + | Some(port) => Console.log(`Port: ${Js.Json.stringify(port)}`) + | None => Console.log("Port not found") + } + } + | Error(err) => Console.error(`✗ Parse error: ${errorToString(err)}`) + } +} + +// Example 2: Nested configuration +fn example2 = () => { + Console.log("\n📝 Example 2: Nested Configuration\n") + + fn config = parseNickel( + "{ + server = { + host = \"0.0.0.0\", + port = 3000, + tls = { + enabled = true, + cert_path = \"/path/to/cert.pem\" + } + }, + database = { + host = \"localhost\", + port = 5432, + name = \"myapp\" + } + }", + "server-config.ncl", + ) + + switch config { + | Ok(json) => { + Console.log("✓ Nested configuration parsed!") + + // Extract deeply nested values + switch getConfigValue(json, list{"server", "tls", "enabled"}) { + | Some(tls) => Console.log(`TLS enabled: ${Js.Json.stringify(tls)}`) + | None => Console.log("TLS setting not found") + } + + switch getConfigValue(json, list{"database", "name"}) { + | Some(dbName) => Console.log(`Database name: ${Js.Json.stringify(dbName)}`) + | None => Console.log("Database name not found") + } + } + | Error(err) => Console.error(`✗ Parse error: ${errorToString(err)}`) + } +} + +// Example 3: Validation before parsing +fn example3 = () => { + Console.log("\n📝 Example 3: Configuration Validation\n") + + fn configSource = "{ + api_key = \"secret-key-123\", + timeout = 30, + retries = 3 + }" + + // First validate + fn validation = validateNickel(configSource, "api-config.ncl") + + switch validation { + | Ok() => { + Console.log("✓ Configuration is valid, proceeding to parse...") + + // Now parse + switch parseNickel(configSource, "api-config.ncl") { + | Ok(json) => { + Console.log("✓ Configuration parsed successfully!") + Console.log(Js.Json.stringify(json)) + } + | Error(err) => Console.error(`✗ Parse error: ${errorToString(err)}`) + } + } + | Error(err) => Console.error(`✗ Validation failed: ${errorToString(err)}`) + } +} + +// Example 4: Error handling +fn example4 = () => { + Console.log("\n📝 Example 4: Error Handling\n") + + fn invalidConfig = "{ + this is not = valid nickel syntax + }" + + fn result = parseNickel(invalidConfig, "bad-config.ncl") + + switch result { + | Ok(json) => { + Console.log("Parsed (unexpected):") + Console.log(Js.Json.stringify(json)) + } + | Error(ParseError(msg)) => { + Console.log(`✓ Caught parse error: ${msg}`) + Console.log("This is expected - the syntax was invalid") + } + | Error(ValidationError(msg)) => Console.log(`Validation error: ${msg}`) + | Error(InvalidInput(msg)) => Console.log(`Invalid input: ${msg}`) + } +} + +// Example 5: Array configuration +fn example5 = () => { + Console.log("\n📝 Example 5: Array Configuration\n") + + fn config = parseNickel( + "{ + users = [ + \"alice\", + \"bob\", + \"charlie\" + ], + ports = [8080, 8081, 8082], + features = { + enabled = [\"auth\", \"logging\", \"metrics\"] + } + }", + "array-config.ncl", + ) + + switch config { + | Ok(json) => { + Console.log("✓ Array configuration parsed!") + + switch getConfigValue(json, list{"users"}) { + | Some(users) => Console.log(`Users: ${Js.Json.stringify(users)}`) + | None => Console.log("Users not found") + } + + switch getConfigValue(json, list{"features", "enabled"}) { + | Some(features) => Console.log(`Enabled features: ${Js.Json.stringify(features)}`) + | None => Console.log("Features not found") + } + } + | Error(err) => Console.error(`✗ Parse error: ${errorToString(err)}`) + } +} + +// Example 6: Library information +fn example6 = () => { + Console.log("\n📝 Example 6: Library Information\n") + + Console.log(`Bunsenite version: ${getVersion()}`) + Console.log(`RSR compliance tier: ${getRSRTier()}`) + Console.log(`TPCF perimeter: ${getTPCFPerimeter()->Int.toString}`) +} + +// Example 7: Type-safe configuration with pattern matching +fn example7 = () => { + Console.log("\n📝 Example 7: Type-Safe Configuration Access\n") + + fn config = parseNickel( + "{ + mode = \"production\", + debug = false, + log_level = \"info\" + }", + "env-config.ncl", + ) + + // Type-safe access with exhaustive pattern matching + fn mode = switch config { + | Ok(json) => + switch getConfigValue(json, list{"mode"}) { + | Some(value) => + switch Js.Json.classify(value) { + | JSONString(str) => Some(str) + | _ => None + } + | None => None + } + | Error(_) => None + } + + switch mode { + | Some("production") => Console.log("✓ Running in production mode") + | Some("development") => Console.log("Running in development mode") + | Some(other) => Console.log(`Running in ${other} mode`) + | None => Console.log("Mode not specified") + } +} + +// Run all examples +fn runExamples = () => { + Console.log("🎯 Bunsenite ReScript Bindings Examples") + Console.log("=" |> Js.String.repeat(50)) + + example1() + example2() + example3() + example4() + example5() + example6() + example7() + + Console.log("\n✅ All examples completed!\n") +} + +// Export for use in other files +fn examples = [ + ("simple", example1), + ("nested", example2), + ("validation", example3), + ("error-handling", example4), + ("arrays", example5), + ("library-info", example6), + ("struct-safe", example7), +] + +// Run if executed directly +runExamples() + diff --git a/vendor/bunsenite/bindings/rescript/README.adoc b/vendor/bunsenite/bindings/rescript/README.adoc new file mode 100644 index 0000000..248c571 --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/README.adoc @@ -0,0 +1,267 @@ +== Bunsenite Rescript Bindings + +image:https://img.shields.io/badge/License-PMPL–1.0-blue.svg[License: +MPL-2.0,link="`https://github.com/hyperpolymath/palimpsest-license`"] + +Type-safe Rescript bindings for +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite[Bunsenite] +via C FFI. + +=== Installation + +[arabic] +. Build the Bunsenite native library: + +[source,bash] +---- +cd ../.. +cargo build --release +---- + +[arabic, start=2] +. Add Bunsenite bindings to your Rescript project: + +[source,bash] +---- +# Copy bindings to your project +cp bindings/affinescript/Bunsenite.res src/ +---- + +[arabic, start=3] +. Configure FFI in your `+bsconfig.json+`: + +[source,json] +---- +{ + "name": "your-project", + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "bs-dependencies": [], + "external-stdlibs": ["bunsenite"] +} +---- + +=== Usage + +==== Basic Parsing + +[source,affinescript] +---- +open Bunsenite + +let config = parseNickel( + "{ + name = \"my-app\", + version = \"1.0.0\", + port = 8080, + }", + "config.ncl" +) + +switch config { +| Ok(json) => Js.log(json) +| Error(err) => Js.log2("Error:", errorToString(err)) +} +---- + +==== Parse File + +[source,affinescript] +---- +open Bunsenite + +let config = parseFile("./config.ncl") + +switch config { +| Ok(json) => { + // Access nested values + let port = getConfigValue(json, list{"server", "port"}) + Js.log2("Server port:", port) + } +| Error(err) => Js.log2("Error:", errorToString(err)) +} +---- + +==== Validation + +[source,affinescript] +---- +open Bunsenite + +let result = validateNickel("{foo = 42}", "config.ncl") + +switch result { +| Ok() => Js.log("Valid!") +| Error(err) => Js.log2("Invalid:", errorToString(err)) +} +---- + +==== Library Info + +[source,affinescript] +---- +open Bunsenite + +Js.log2("Version:", getVersion()) +Js.log2("RSR Tier:", getRSRTier()) +Js.log2("TPCF Perimeter:", getTPCFPerimeter()) +---- + +=== API Reference + +==== Types + +[source,affinescript] +---- +type result<'a, 'e> = Ok('a) | Error('e) + +type error = + | ParseError(string) + | ValidationError(string) + | InvalidInput(string) + +type parseResult = result +type validateResult = result +---- + +==== Functions + +===== `+parseNickel(source: string, name: string): parseResult+` + +Parse and evaluate a Nickel configuration string. + +* `+source+`: The Nickel configuration source code +* `+name+`: A name for this configuration (used in error messages) +* Returns: `+Ok(Js.Json.t)+` on success, `+Error(error)+` on failure + +===== `+validateNickel(source: string, name: string): validateResult+` + +Validate a Nickel configuration without evaluating it. + +* `+source+`: The Nickel configuration source code +* `+name+`: A name for this configuration (used in error messages) +* Returns: `+Ok()+` if valid, `+Error(error)+` if invalid + +===== `+parseFile(path: string): parseResult+` + +Parse a Nickel configuration file. + +* `+path+`: Path to the Nickel configuration file +* Returns: `+Ok(Js.Json.t)+` on success, `+Error(error)+` on failure + +===== `+validateFile(path: string): validateResult+` + +Validate a Nickel configuration file. + +* `+path+`: Path to the Nickel configuration file +* Returns: `+Ok()+` if valid, `+Error(error)+` if invalid + +===== `+getVersion(): string+` + +Get Bunsenite library version. + +* Returns: Version string (e.g., "`0.1.0`") + +===== `+getRSRTier(): string+` + +Get RSR compliance tier. + +* Returns: RSR tier (e.g., "`bronze`") + +===== `+getTPCFPerimeter(): int+` + +Get TPCF perimeter number. + +* Returns: Perimeter number (3 for Community Sandbox) + +==== Helper Functions + +===== `+getConfigValue(json: Js.Json.t, path: list): option+` + +Get a value from a configuration object by key path. + +Example: + +[source,affinescript] +---- +let port = getConfigValue(config, list{"server", "port"}) +---- + +===== `+errorToString(err: error): string+` + +Convert an error to a string for display. + +=== Architecture + +.... +┌─────────────────┐ +│ Rescript │ +│ (Type-safe) │ +└────────┬────────┘ + │ FFI + ▼ + ┌──────────┐ + │ Zig FFI │ + │ (C ABI) │ + └─────┬────┘ + │ + ▼ +┌─────────────────┐ +│ Rust Core │ +│ (lib.rs) │ +│ │ +│ nickel-lang-core│ +│ 0.9.1 │ +└─────────────────┘ +.... + +=== Performance + +~90% of native Rust performance (minimal C ABI overhead). + +=== Type Safety + +Rescript provides: - *Compile-time type checking*: Catch errors before +runtime - *Sound type system*: No `+null+` or `+undefined+` surprises - +*Pattern matching*: Exhaustive error handling via `+result+` type - +*Immutability*: Default immutability prevents bugs + +Combined with Bunsenite’s Rust core: - *Memory safety*: Rust ownership +model - *Type safety*: Nickel + Rust type checking - *No runtime +errors*: Caught at compile time + +=== Security + +* *Memory Safety*: Rust ownership model prevents memory errors +* *Type Safety*: Rescript + Nickel + Rust triple type checking +* *No `+unsafe+`*: Zero unsafe code blocks in Bunsenite core +* *Offline-First*: No network dependencies + +=== License + +Dual MPL-2.0 + MPL-2.0 v0.8 + +See link:../../LICENSE[LICENSE] for details. + +=== Contributing + +See link:../../CONTRIBUTING.md[CONTRIBUTING.md] for development +guidelines. + +=== Support + +* *Issues*: +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues[GitLab +Issues] +* *Discussions*: +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues[GitLab +Discussions] +* *Documentation*: link:../../README.md[Main README] + +''''' + +Made with ❤️ by the Campaign for Cooler Coding and Programming diff --git a/vendor/bunsenite/bindings/rescript/bunsenite.d.affine b/vendor/bunsenite/bindings/rescript/bunsenite.d.affine new file mode 100644 index 0000000..bbf71a2 --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/bunsenite.d.affine @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MPL-2.0 +// Ported via Harvard Engine (Semantic pass) + +module bunsenite.d; + +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// TypeScript type definitions for bunsenite +// These types are for the Node.js/Bun FFI bindings + +/** + * Parse a Nickel configuration string and return the result as JSON + * @param source - The Nickel source code to parse + * @param name - The name of the file (for error messages) + * @returns The parsed configuration as a JSON string, or null on error + */ +fn parse_nickel(source: string, name: string): string | null; + +/** + * Validate a Nickel configuration without evaluating it + * @param source - The Nickel source code to validate + * @param name - The name of the file (for error messages) + * @returns 0 if valid, non-zero on error + */ +fn validate_nickel(source: string, name: string): number; + +/** + * Get the library version + * @returns The version string (e.g., "1.0.0") + */ +fn version(): string; + +/** + * Get the RSR compliance tier + * @returns The RSR tier (e.g., "bronze") + */ +fn rsr_tier(): string; + +/** + * Get the TPCF perimeter assignment + * @returns The perimeter number (e.g., 3) + */ +fn tpcf_perimeter(): number; + diff --git a/vendor/bunsenite/bindings/rescript/package.json b/vendor/bunsenite/bindings/rescript/package.json new file mode 100644 index 0000000..06c50ce --- /dev/null +++ b/vendor/bunsenite/bindings/rescript/package.json @@ -0,0 +1,61 @@ +{ + "name": "bunsenite", + "version": "1.0.0", + "description": "Nickel configuration file parser - AffineScript/Node.js bindings", + "main": "bunsenite_ffi_node.js", + "module": "bunsenite_ffi.js", + "types": "bunsenite.d.ts", + "exports": { + ".": { + "bun": "./bunsenite_ffi.js", + "node": "./bunsenite_ffi_node.js", + "default": "./bunsenite_ffi_node.js" + } + }, + "scripts": { + "build": "affinescript build", + "clean": "affinescript clean", + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/campaign-for-cooler-coding-and-programming/bunsenite.git" + }, + "keywords": [ + "nickel", + "config", + "parser", + "ffi", + "affinescript" + ], + "author": "Campaign for Cooler Coding and Programming", + "license": "PMPL-1.0", + "bugs": { + "url": "https://github.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues" + }, + "homepage": "https://github.com/campaign-for-cooler-coding-and-programming/bunsenite", + "peerDependencies": { + "ffi-napi": "^4.0.0", + "ref-napi": "^3.0.0" + }, + "peerDependenciesMeta": { + "ffi-napi": { + "optional": true + }, + "ref-napi": { + "optional": true + } + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "bunsenite_ffi.js", + "bunsenite_ffi_node.js", + "Bunsenite.res", + "Bunsenite.res.js", + "bunsenite.d.ts", + "affinescript.json", + "README.md" + ] +} diff --git a/vendor/bunsenite/codemeta.json b/vendor/bunsenite/codemeta.json new file mode 100644 index 0000000..fa1060f --- /dev/null +++ b/vendor/bunsenite/codemeta.json @@ -0,0 +1,27 @@ +{ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "@type": "SoftwareSourceCode", + "identifier": "bunsenite", + "name": "bunsenite", + "description": "RSR-compliant project", + "version": "0.1.0", + "dateCreated": "2025-12-10", + "dateModified": "2025-12-10", + "license": "PMPL-1.0", + "codeRepository": "https://github.com/hyperpolymath/bunsenite", + "issueTracker": "https://github.com/hyperpolymath/bunsenite/issues", + "programmingLanguage": ["Guile Scheme"], + "developmentStatus": "active", + "keywords": ["RSR", "rhodium-standard"], + "author": [{ + "@type": "Person", + "givenName": "Hyper", + "familyName": "Polymath", + "email": "hyperpolymath@proton.me" + }], + "isPartOf": [{ + "@type": "SoftwareApplication", + "name": "RSR Framework", + "url": "https://rhodium.sh" + }] +} diff --git a/vendor/bunsenite/config/README.adoc b/vendor/bunsenite/config/README.adoc new file mode 100644 index 0000000..7ca6ad2 --- /dev/null +++ b/vendor/bunsenite/config/README.adoc @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Bunsenite K9 Configuration + +**Meta-Dogfooding in Action**: The Nickel tooling project using K9 (which uses Nickel). + +== Overview + +This directory contains self-validating K9 configuration files for Bunsenite. +K9 uses Nickel's contract system to ensure configurations are valid before use. + +== Configuration Files + +[cols="1,2,1"] +|=== +| File | Purpose | Security Level + +| `rust-fmt.k9.ncl` +| Rust formatter configuration with validation +| Yard (validation only) + +| `build.k9.ncl` +| Cargo build configuration with contracts +| Yard (validation only) +|=== + +== Usage + +=== Validate Configurations + +[source,bash] +---- +# Validate all K9 configs +just validate-k9 + +# Validate specific config +nickel eval config/rust-fmt.k9.ncl + +# Check pedigree +nickel eval -f 'pedigree' config/rust-fmt.k9.ncl +---- + +=== Generate Traditional Config Files + +[source,bash] +---- +# Generate rustfmt.toml from K9 config +nickel export config/rust-fmt.k9.ncl -f 'rustfmt_toml' > rustfmt.toml + +# Generate Cargo.toml sections from K9 config +nickel export config/build.k9.ncl -f 'config' > build-config.toml +---- + +=== Use in Build Pipeline + +[source,bash] +---- +# Validate before formatting +just validate-k9 && cargo fmt + +# Validate before build +just validate-k9 && cargo build --release +---- + +== Why K9 for Bunsenite? + +**This is meta-dogfooding at its finest:** + +1. **Bunsenite** is Nickel tooling +2. **K9** uses Nickel for validation +3. **Result**: The Nickel tool validates itself with K9 + +**Benefits:** + +* Invalid configs refuse to load (fail fast) +* Nickel contracts enforce validity at compile-time +* Self-documenting with type signatures +* Progressive strictness (lax → checked → attested) + +== Contract Examples + +=== Line Width Validation + +[source,nickel] +---- +max_width + | std.number.Positive + | std.contract.from_predicate (fun w => w >= 80 && w <= 120) +---- + +**Guarantees:** +- Line width must be positive +- Line width between 80-120 characters + +=== Edition Validation + +[source,nickel] +---- +edition + | std.contract.from_predicate + (fun e => std.array.elem e ["2015", "2018", "2021", "2024"]) +---- + +**Guarantees:** +- Only valid Rust editions accepted + +=== Version Format Validation + +[source,nickel] +---- +package.version + | std.contract.from_predicate + (fun v => std.string.is_match "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9]+)?(\\+[a-z0-9]+)?$" v) +---- + +**Guarantees:** +- Semantic versioning format (x.y.z) +- Optional pre-release and build metadata + +== Integration with K9 Ecosystem + +**Related Projects:** + +* **K9-SVC**: https://github.com/hyperpolymath/standards/tree/main/k9-svc +* **RSR Template**: Uses K9 contractiles +* **MCP Servers**: Production configs with K9 +* **ABI/FFI**: Build configs in K9 + +== Roadmap + +=== Phase 1: Formatter & Build (Current) +- [x] rust-fmt.k9.ncl +- [x] build.k9.ncl +- [ ] Add to CI pipeline + +=== Phase 2: Extended Configs +- [ ] Test configuration (test.k9.ncl) +- [ ] Bench configuration (bench.k9.ncl) +- [ ] Documentation generation (doc.k9.ncl) + +=== Phase 3: CI Integration +- [ ] Pre-commit hook validates K9 configs +- [ ] CI fails if K9 validation fails +- [ ] Auto-generate rustfmt.toml from K9 + +=== Phase 4: Full Adoption +- [ ] All Bunsenite configs in K9 +- [ ] Document patterns for Nickel users +- [ ] Template for other Nickel projects + +== Philosophy + +**"If your config can't validate itself, it shouldn't run."** + +K9 brings the same rigor to configuration that Nickel brings to data. +By using K9 in Bunsenite, we demonstrate that self-validating configs +work for real-world tooling, not just toy examples. + +== Contributing + +To add new K9 configs: + +1. Create `config/name.k9.ncl` +2. Start with `K9!` magic header +3. Set `leash = 'Yard` for validation-only +4. Define `pedigree` with metadata +5. Define `config` with Nickel contracts +6. Test with `nickel eval` +7. Add validation to `justfile` + +== References + +* **K9 Specification**: https://github.com/hyperpolymath/standards/blob/main/self-validating/SPEC.adoc +* **Nickel Documentation**: https://nickel-lang.org/ +* **K9 Dogfooding Strategy**: ../DOGFOODING-OPPORTUNITIES.md (in k9-svc repo) + +--- + +**Maintainer**: Jonathan D.A. Jewell + +**Status**: Phase 1 Implementation + +**Last Updated**: 2026-01-30 diff --git a/vendor/bunsenite/config/build.k9.ncl b/vendor/bunsenite/config/build.k9.ncl new file mode 100644 index 0000000..2c396ce --- /dev/null +++ b/vendor/bunsenite/config/build.k9.ncl @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: MPL-2.0 +K9! +leash = 'Yard # Validation only, no I/O + +pedigree = { + schema_version = "1.0.0", + component_type = "rust-build-config", + tool = "bunsenite", + validation_level = "strict", + description = "Self-validating Cargo build configuration for Bunsenite" +} + +# Cargo build configuration with Nickel contracts +config | { + package | { + name | String, + version | String, + edition | String, + rust_version | String, + license | String, + .. + }, + + profile | { + release | { + opt_level | [| '0, '1, '2, '3, 's, 'z |], + lto | [| 'Off, 'Thin, 'Fat |] | Bool, + codegen_units | Number, + strip | Bool, + .. + }, + dev | { + opt_level | [| '0, '1, '2, '3 |], + .. + }, + .. + }, + + features | { + default | Array String, + full | Array String, + .. + }, + .. +} = { + package = { + name = "bunsenite", + version = "0.1.0", + edition = "2021", + rust_version = "1.70", + license = "MPL-2.0", + authors = ["Jonathan D.A. Jewell "], + description = "Nickel language tooling and utilities", + repository = "https://github.com/hyperpolymath/bunsenite", + }, + + profile = { + release = { + opt_level = '3, + lto = 'Thin, + codegen_units = 1, + strip = true, + }, + dev = { + opt_level = '0, + }, + }, + + features = { + default = ["cli"], + full = ["cli", "wasm", "ffi"], + }, + + # Nickel contracts enforce validity + package.edition + | std.contract.from_predicate + (fun e => std.array.elem e ["2015", "2018", "2021", "2024"]), + + package.version + | std.contract.from_predicate + (fun v => std.string.is_match "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9]+)?(\\+[a-z0-9]+)?$" v), + + package.license + | std.contract.from_predicate + (fun l => std.array.elem l [ + "MPL-2.0", + "MPL-2.0", + "MIT", + "Apache-2.0" + ]), + + profile.release.codegen_units + | std.number.Positive + | std.contract.from_predicate (fun u => u >= 1 && u <= 256), +} + +# Build commands based on profile +build_commands = { + dev = "cargo build", + release = "cargo build --release", + check = "cargo check --all-targets --all-features", + test = "cargo test --all-features", + bench = "cargo bench --all-features", + doc = "cargo doc --no-deps --all-features", +} diff --git a/vendor/bunsenite/config/rust-fmt.k9.ncl b/vendor/bunsenite/config/rust-fmt.k9.ncl new file mode 100644 index 0000000..e0035dc --- /dev/null +++ b/vendor/bunsenite/config/rust-fmt.k9.ncl @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: MPL-2.0 +K9! +leash = 'Yard # Validation only, no I/O + +pedigree = { + schema_version = "1.0.0", + component_type = "rust-formatter-config", + tool = "bunsenite", + validation_level = "strict", + description = "Self-validating Rust formatter configuration for Bunsenite" +} + +# Rust formatter configuration with Nickel contracts +config | { + edition | String, + max_width | Number, + hard_tabs | Bool, + tab_spaces | Number, + newline_style | [| 'Unix, 'Windows, 'Native |], + use_small_heuristics | [| 'Default, 'Off, 'Max |], + indent_style | [| 'Block, 'Visual |], + wrap_comments | Bool, + format_code_in_doc_comments | Bool, + comment_width | Number, + normalize_comments | Bool, + format_strings | Bool, + format_macro_matchers | Bool, + format_macro_bodies | Bool, + use_try_shorthand | Bool, + use_field_init_shorthand | Bool, + .. +} = { + # Edition + edition = "2021", + + # Line width + max_width = 100, + comment_width = 80, + + # Indentation + hard_tabs = false, + tab_spaces = 4, + indent_style = 'Block, + + # Line endings + newline_style = 'Unix, + + # Heuristics + use_small_heuristics = 'Default, + + # Comments + wrap_comments = true, + format_code_in_doc_comments = true, + normalize_comments = true, + + # Strings and macros + format_strings = true, + format_macro_matchers = true, + format_macro_bodies = true, + + # Syntax shortcuts + use_try_shorthand = true, + use_field_init_shorthand = true, + + # Nickel contracts enforce validity + edition + | std.contract.from_predicate + (fun e => std.array.elem e ["2015", "2018", "2021", "2024"]), + + max_width + | std.number.Positive + | std.contract.from_predicate (fun w => w >= 80 && w <= 120), + + tab_spaces + | std.number.Positive + | std.contract.from_predicate (fun s => s >= 2 && s <= 8), + + comment_width + | std.number.Positive + | std.contract.from_predicate (fun w => w >= 60 && w <= max_width), +} + +# Export for rustfmt.toml generation +rustfmt_toml = { + edition = config.edition, + max_width = config.max_width, + hard_tabs = config.hard_tabs, + tab_spaces = config.tab_spaces, + newline_style = std.string.lowercase (std.to_string config.newline_style), + use_small_heuristics = std.string.lowercase (std.to_string config.use_small_heuristics), + indent_style = std.string.lowercase (std.to_string config.indent_style), + wrap_comments = config.wrap_comments, + format_code_in_doc_comments = config.format_code_in_doc_comments, + comment_width = config.comment_width, + normalize_comments = config.normalize_comments, + format_strings = config.format_strings, + format_macro_matchers = config.format_macro_matchers, + format_macro_bodies = config.format_macro_bodies, + use_try_shorthand = config.use_try_shorthand, + use_field_init_shorthand = config.use_field_init_shorthand, +} diff --git a/vendor/bunsenite/contractile.just b/vendor/bunsenite/contractile.just new file mode 100644 index 0000000..9a5827b --- /dev/null +++ b/vendor/bunsenite/contractile.just @@ -0,0 +1,75 @@ +# Auto-generated by: contractile gen-just +# Source directory: contractiles +# Re-generate with: contractile gen-just --dir contractiles +# +# SPDX-License-Identifier: MPL-2.0 + +# === DUST (Recovery & Rollback) === +# Source: Dustfile.a2ml + +# List available dust recovery actions +dust-status: + @echo ' dust-source-rollback: Revert all source changes to last commit [rollback]' + +# Revert all source changes to last commit +dust-source-rollback: + @echo 'Executing rollback for source-rollback' + git checkout HEAD -- . + + +# === INTEND (Declared Future Intent) === +# Source: Intentfile.a2ml + +# Display declared future intents +intend-list: + @echo '=== Declared Intent ===' + @echo '' + @echo 'Features:' + @echo '' + @echo 'Quality:' + + +# === MUST (Physical State Checks) === +# Source: Mustfile.a2ml + +# Run all must checks +must-check: must-license-present must-readme-present must-spdx-headers must-no-banned-files + @echo 'All must checks passed' + +# LICENSE file must exist +must-license-present: + test -f LICENSE + +# README must exist +must-readme-present: + test -f README.adoc || test -f README.md + +# Source files should have SPDX license headers +must-spdx-headers: + find . -name '*.rs' -o -name '*.res' -o -name '*.gleam' | head -20 | xargs -r grep -L 'SPDX-License-Identifier' | wc -l | grep -q '^0$' + +# No Dockerfiles or Makefiles +must-no-banned-files: + test ! -f Dockerfile && test ! -f Makefile + + +# === TRUST (Integrity & Provenance Verification) === +# Source: Trustfile.a2ml + +# Run all trust verifications +trust-verify: trust-license-content trust-no-secrets-committed trust-container-images-pinned + @echo 'All trust verifications passed' + +# LICENSE contains expected SPDX identifier +trust-license-content: + grep -q 'SPDX\|License\|MIT\|Apache\|PMPL\|MPL' LICENSE + +# No .env or credential files in repo +trust-no-secrets-committed: + test ! -f .env && test ! -f credentials.json && test ! -f .env.local + +# Containerfile base images use pinned digests +trust-container-images-pinned: + test ! -f Containerfile || grep -q '@sha256:' Containerfile + + diff --git a/vendor/bunsenite/contractiles/README.adoc b/vendor/bunsenite/contractiles/README.adoc new file mode 100644 index 0000000..9f94acc --- /dev/null +++ b/vendor/bunsenite/contractiles/README.adoc @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Contractiles Template Set +:toc: +:sectnums: + +This directory contains the generalized contractiles templates. Copy the `contractiles/` directory into a new repo to establish a consistent operational, validation, trust, recovery, and intent framework. + +== Fill-In Instructions + +1. Update the Mustfile to reflect your real invariants (paths, schema versions, ports). +2. Replace Trustfile.hs placeholders with your actual key paths and verification commands. +3. Adjust Dustfile handlers to match your rollback and recovery tooling. +4. Update Intentfile to mirror the roadmap you want the system to evolve toward. + +== Contents + +* `must/Mustfile` - required invariants and validations. +* `trust/Trustfile.hs` - cryptographic verification steps. +* `dust/Dustfile` - rollback and recovery semantics. +* `lust/Intentfile` - future intent and roadmap direction. diff --git a/vendor/bunsenite/contractiles/dust/Dustfile b/vendor/bunsenite/contractiles/dust/Dustfile new file mode 100644 index 0000000..314903c --- /dev/null +++ b/vendor/bunsenite/contractiles/dust/Dustfile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dustfile template - recovery and rollback semantics + +version: 1 + +recovery: + logs: + - name: decision-log + path: logs/decisions.json + reversible: true + handler: "log-replay --reverse logs/decisions.json" + + policy: + - name: policy-rollback + path: policy/policy.ncl + rollback: "git checkout HEAD~1 -- policy/policy.ncl" + notes: "Rollback policy to the previous known-good revision." + + gateway: + - name: bad-deployment + event: "deploy.failure" + undo: "kubectl rollout undo deployment/gateway" + notes: "Undo a failed deployment while preserving audit logs." + + dust-events: + - name: decision-log-to-dust + source: logs/decisions.json + transform: "dustify --input logs/decisions.json --output logs/dust-events.json" + notes: "Map gateway decision logs into reversible dust events." diff --git a/vendor/bunsenite/contractiles/intend/Intentfile.a2ml b/vendor/bunsenite/contractiles/intend/Intentfile.a2ml new file mode 100644 index 0000000..993bb0f --- /dev/null +++ b/vendor/bunsenite/contractiles/intend/Intentfile.a2ml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +# Intentfile (A2ML Canonical) +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +@abstract: +Declared intent and purpose for Bunsenite. +@end + +## Purpose + +Bunsenite — > Nickel configuration file parser with multi-language FFI bindings + +## Anti-Purpose + +This project is NOT: +- A fork or wrapper around another tool +- A monorepo (unless explicitly structured as one) + +## If In Doubt + +If you are unsure whether a change is in scope, ask. +Sensitive areas: ABI definitions, license headers, CI workflows. diff --git a/vendor/bunsenite/contractiles/must/Mustfile b/vendor/bunsenite/contractiles/must/Mustfile new file mode 100644 index 0000000..dc7b3be --- /dev/null +++ b/vendor/bunsenite/contractiles/must/Mustfile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: MPL-2.0 +# Mustfile - declarative state contract (template) +# See: https://github.com/hyperpolymath/mustfile + +version: 1 + +metadata: + name: project-state-contract + spec: v0.0.1 + description: "Invariant checks for config, policy, gateway, logs, and schema." + +parameters: + gateway_port: "8080" + schema_version: "v0.0.1" + +checks: + - name: config-valid + description: "config/service.yaml must be valid." + run: "yq -e '.' config/service.yaml >/dev/null" + + - name: policy-compiles + description: "policy/policy.ncl must compile." + run: "nickel check policy/policy.ncl" + + - name: gateway-exposes-port + description: "Service must expose the configured port." + run: "bash -uc 'ss -lnt | rg \":${GATEWAY_PORT:-8080}\"'" + + - name: logs-are-json + description: "Logs must be JSON." + run: "bash -uc 'rg --files -g \"*.json\" logs | xargs -r jq -e .'" + + - name: schema-version-matches + description: "Schema must match version spec." + run: "bash -uc 'rg -n \"${SCHEMA_VERSION:-v0.0.1}\" schema'" diff --git a/vendor/bunsenite/contractiles/self-validating/README.adoc b/vendor/bunsenite/contractiles/self-validating/README.adoc new file mode 100644 index 0000000..cbc21d7 --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/README.adoc @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += K9 Contractiles +:toc: left +:icons: font + +== What Are K9 Contractiles? + +K9 contractiles are self-validating components that combine configuration, validation, and deployment logic in a single file format. They implement the RSR principle of "self-describing artifacts" by embedding contracts and orchestration directly in the component. + +== The Three Security Levels + +K9 components declare their trust requirements using "The Leash" security model: + +[horizontal] +`'Kennel`:: Pure data, no execution (safest) +`'Yard`:: Nickel evaluation with contracts (medium trust) +`'Hunt`:: Full execution with Just recipes (requires signature) + +== Example Components + +This directory contains example K9 contractiles for common repository tasks: + +=== Kennel Level (Pure Data) + +**File:** `examples/project-metadata.k9.ncl` + +Pure configuration data with no execution. Safe to include in any repository. + +**Use cases:** +- Project metadata (name, version, description) +- Build configuration +- Tool settings +- Data schemas + +**Security:** No signature required, data-only. + +=== Yard Level (Validated Config) + +**File:** `examples/ci-config.k9.ncl` + +Configuration with Nickel contracts for runtime validation. Evaluated safely without I/O. + +**Use cases:** +- CI/CD configuration with validation +- Deployment parameters +- Database schemas with constraints +- API specifications + +**Security:** Signature recommended, Nickel evaluation only. + +=== Hunt Level (Full Execution) + +**File:** `examples/setup-repo.k9.ncl` + +Full execution with Just recipes. Can run shell commands and modify filesystem. + +**Use cases:** +- Repository setup scripts +- Deployment automation +- System configuration +- Package installation + +**Security:** **Signature required**, full system access. + +== Usage in Your Repository + +=== 1. Create K9 Components + +Choose the appropriate security level for your use case: + +[source,bash] +---- +# Kennel: Pure configuration +cp contractiles/self-validating/examples/project-metadata.k9.ncl config/metadata.k9.ncl + +# Yard: Validated configuration +cp contractiles/self-validating/examples/ci-config.k9.ncl .github/ci.k9.ncl + +# Hunt: Full automation +cp contractiles/self-validating/examples/setup-repo.k9.ncl scripts/setup.k9.ncl +---- + +=== 2. Validate Components + +[source,bash] +---- +# Validate Nickel syntax and contracts +nickel typecheck config/metadata.k9.ncl + +# Verify Hunt-level signature (if signed) +./must verify scripts/setup.k9.ncl +---- + +=== 3. Execute Components + +[source,bash] +---- +# Kennel: Export as JSON +nickel export config/metadata.k9.ncl > metadata.json + +# Yard: Evaluate with validation +nickel eval .github/ci.k9.ncl + +# Hunt: Run with Just (dry-run first!) +./must --dry-run run scripts/setup.k9.ncl +./must run scripts/setup.k9.ncl +---- + +== Integration with RSR + +K9 contractiles integrate with other RSR standards: + +**STATE.scm**:: K9 components can generate or validate STATE.scm +**ECOSYSTEM.scm**:: K9 can automate cross-repo operations +**META.scm**:: K9 can enforce architectural decisions + +== Security Best Practices + +=== For Kennel/Yard Components + +✅ **Safe to use without signatures** + +✅ **Review Nickel code before use** + +✅ **Validate contracts match expectations** + +=== For Hunt Components + +⚠️ **ALWAYS verify signatures** + +⚠️ **Review Just recipes carefully** + +⚠️ **Run dry-run mode first** + +⚠️ **Never run as root unless required** + +⚠️ **Sandbox external components** + +**See:** https://github.com/hyperpolymath/standards/blob/main/self-validating/docs/SECURITY-BEST-PRACTICES.adoc + +== Template Files + +Use these as starting points for your own K9 components: + +- `template-kennel.k9.ncl` - Pure data template +- `template-yard.k9.ncl` - Validated config template +- `template-hunt.k9.ncl` - Full execution template + +== Dependencies + +To use K9 contractiles in your repository: + +[source,bash] +---- +# Install Nickel (configuration language) +curl -L https://github.com/tweag/nickel/releases/latest/download/nickel-linux-x86_64 -o nickel +chmod +x nickel && sudo mv nickel /usr/local/bin/ + +# Install Just (task runner, for Hunt level) +cargo install just + +# Clone K9-SVC (for must shim and tooling) +git clone https://github.com/hyperpolymath/standards.git +# Note: K9-SVC is located in standards/k9-svc +---- + +== Learn More + +- **K9-SVC Specification:** https://github.com/hyperpolymath/standards/blob/main/self-validating/SPEC.adoc +- **K9 User Guide:** https://github.com/hyperpolymath/standards/blob/main/self-validating/GUIDE.adoc +- **Security Documentation:** https://github.com/hyperpolymath/standards/blob/main/self-validating/docs/SECURITY-FAQ.adoc +- **IANA Media Type:** `application/vnd.k9+nickel` + +== Contributing + +When adding K9 contractiles to your repository: + +1. Use appropriate security level (Kennel > Yard > Hunt) +2. Document what each component does +3. Include validation contracts in Yard/Hunt components +4. Sign Hunt-level components before committing +5. Add K9 validation to CI/CD pipeline + +**Questions?** Open an issue on https://github.com/hyperpolymath/standards/tree/main/k9-svc diff --git a/vendor/bunsenite/contractiles/self-validating/examples/ci-config.k9.ncl b/vendor/bunsenite/contractiles/self-validating/examples/ci-config.k9.ncl new file mode 100644 index 0000000..9fe314e --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/examples/ci-config.k9.ncl @@ -0,0 +1,126 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Yard-level K9 component: CI/CD configuration with validation +# Security Level: Yard (Nickel evaluation, contract validation) +# Signature recommended but not required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "ci-configuration", + security = { + leash = 'Yard, + trust_level = "validated-config", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "ci-config", + version = "1.0.0", + description = "CI/CD configuration with runtime validation", + author = "Jonathan D.A. Jewell ", + }, + }, + + # CI/CD configuration with Nickel contracts + ci = { + # Platform must be a known CI provider + platform + | [| 'GitHubActions, 'GitLabCI, 'CircleCI, 'TravisCI |] + = 'GitHubActions, + + # Build matrix with validation + matrix = { + # Operating systems to test on + os + | Array String + | std.array.NonEmpty + = ["ubuntu-latest", "macos-latest"], + + # Language versions to test + versions + | Array String + | std.array.NonEmpty + = ["stable", "beta"], + }, + + # Workflow steps with validation + steps = [ + { + name = "Checkout", + action = "actions/checkout@v4", + # Version must be SHA-pinned for security + sha | String | std.string.NonEmpty = "b4ffde65f46336ab88eb53be808477a3936bae11", + }, + { + name = "Build", + run = "just build", + }, + { + name = "Test", + run = "just test", + }, + { + name = "Lint", + run = "just lint", + }, + ], + + # Deployment configuration + deploy = { + enabled | Bool = false, + + # Only deploy from main branch + branch + | String + | std.contract.from_predicate (fun b => b == "main" || b == "master") + = "main", + + # Deployment requires manual approval + requires_approval | Bool = true, + }, + + # Security scanning + security = { + enabled | Bool = true, + + scanners = [ + { + name = "CodeQL", + languages = ["rust", "javascript"], + }, + { + name = "OSSF Scorecard", + enabled = true, + }, + { + name = "TruffleHog", + scan_for = "secrets", + }, + ], + }, + + # Notification settings + notifications = { + on_success = "never", + on_failure = "always", + channels = ["email"], + }, + }, + + # Validation rules (enforced by Nickel) + validation = { + # At least one OS must be specified + check_os = std.array.length ci.matrix.os > 0, + + # At least one version must be tested + check_versions = std.array.length ci.matrix.versions > 0, + + # Must have at least build and test steps + check_steps = std.array.length ci.steps >= 2, + + # Security scanning must be enabled + check_security = ci.security.enabled == true, + }, +} diff --git a/vendor/bunsenite/contractiles/self-validating/examples/project-metadata.k9.ncl b/vendor/bunsenite/contractiles/self-validating/examples/project-metadata.k9.ncl new file mode 100644 index 0000000..b2299b4 --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/examples/project-metadata.k9.ncl @@ -0,0 +1,57 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Kennel-level K9 component: Project metadata +# Security Level: Kennel (pure data, no execution) +# No signature required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "project-metadata", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "project-metadata", + version = "1.0.0", + description = "Pure data configuration for project metadata", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Project configuration + project = { + name = "my-project", + version = "0.1.0", + description = "A project following Rhodium Standard Repositories", + + repository = { + url = "https://github.com/hyperpolymath/my-project", + type = "git", + }, + + author = { + name = "Jonathan D.A. Jewell", + email = "j.d.a.jewell@open.ac.uk", + organization = "The Open University", + }, + + license = "MPL-2.0", + + keywords = [ + "rhodium-standard", + "rsr", + "hyperpolymath", + ], + }, + + # Export as JSON for other tools + export = { + format = "json", + destination = "project-metadata.json", + }, +} diff --git a/vendor/bunsenite/contractiles/self-validating/examples/setup-repo.k9.ncl b/vendor/bunsenite/contractiles/self-validating/examples/setup-repo.k9.ncl new file mode 100644 index 0000000..358f18d --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/examples/setup-repo.k9.ncl @@ -0,0 +1,167 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Hunt-level K9 component: Repository setup automation +# Security Level: Hunt (full execution with Just recipes) +# ⚠️ SIGNATURE REQUIRED - DO NOT RUN WITHOUT VERIFICATION + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "repository-setup", + security = { + leash = 'Hunt, + trust_level = "full-system-access", + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + signature_required = true, + }, + metadata = { + name = "setup-repo", + version = "1.0.0", + description = "Automated repository setup with RSR standards", + author = "Jonathan D.A. Jewell ", + }, + warnings = [ + "This component has full system access", + "Only run from trusted sources with verified signatures", + "Review Just recipes before execution", + "Use dry-run mode first: ./must --dry-run run setup-repo.k9.ncl", + ], + }, + + # Configuration with contracts + config = { + repo_name + | String + | std.string.NonEmpty + = "my-new-repo", + + repo_type + | [| 'Library, 'Application, 'Tool, 'Specification |] + = 'Application, + + primary_language + | String + | std.string.NonEmpty + = "rust", + + # RSR compliance features to enable + features = { + checkpoint_files | Bool = true, # STATE.scm, ECOSYSTEM.scm, META.scm + security_workflows | Bool = true, # CodeQL, Scorecard, etc. + quality_checks | Bool = true, # Linting, formatting + mirroring | Bool = false, # GitLab/Bitbucket mirrors + }, + + # Git configuration + git = { + default_branch = "main", + initial_commit | Bool = true, + remote_url | String = "", + }, + }, + + # Just recipes for execution + # These run when: ./must run setup-repo.k9.ncl + recipes = { + # Main entry point + default = { + recipe = "setup", + description = "Set up RSR-compliant repository", + }, + + # Individual setup tasks + setup = { + dependencies = ["check-env", "create-structure", "init-git", "setup-workflows"], + commands = [ + "echo '✅ Repository setup complete!'", + "echo 'Run: git status to see changes'", + ], + }, + + "check-env" = { + description = "Verify required tools are installed", + commands = [ + "command -v git || (echo 'ERROR: git not found' && exit 1)", + "command -v just || (echo 'ERROR: just not found' && exit 1)", + "command -v nickel || (echo 'ERROR: nickel not found' && exit 1)", + "echo '✓ All required tools present'", + ], + }, + + "create-structure" = { + description = "Create RSR directory structure", + commands = [ + "mkdir -p src/ docs/ tests/ scripts/", + "mkdir -p .github/workflows/", + "mkdir -p contractiles/self-validating/", + "echo '✓ Directory structure created'", + ], + }, + + "init-git" = { + description = "Initialize Git repository", + commands = [ + "git init -b %{config.git.default_branch}", + "git config user.name 'Jonathan D.A. Jewell'", + "git config user.email 'j.d.a.jewell@open.ac.uk'", + "echo '✓ Git initialized'", + ], + }, + + "setup-workflows" = { + description = "Add RSR-compliant workflows", + commands = [ + # This would copy workflow templates + # In a real implementation, would fetch from rsr-template-repo + "echo '✓ Workflows configured'", + ], + }, + + "create-checkpoint-files" = { + description = "Create STATE.scm, ECOSYSTEM.scm, META.scm", + commands = [ + "echo '(state (version \"1.0.0\") (project \"%{config.repo_name}\"))' > STATE.scm", + "echo '(ecosystem (version \"1.0.0\") (name \"%{config.repo_name}\"))' > ECOSYSTEM.scm", + "echo '(meta (version \"1.0.0\") (project \"%{config.repo_name}\"))' > META.scm", + "echo '✓ Checkpoint files created'", + ], + }, + + "add-license" = { + description = "Add PMPL-1.0 license", + commands = [ + "curl -sL https://raw.githubusercontent.com/hyperpolymath/pmpl/main/LICENSE -o LICENSE", + "echo '✓ License added'", + ], + }, + + "add-readme" = { + description = "Create README.adoc from template", + commands = [ + "echo '= %{config.repo_name}' > README.adoc", + "echo '' >> README.adoc", + "echo 'Part of the Hyperpolymath ecosystem.' >> README.adoc", + "echo '✓ README created'", + ], + }, + + clean = { + description = "Remove generated files (careful!)", + commands = [ + "echo '⚠️ This will delete all generated files'", + "echo 'Press Ctrl+C to cancel, or wait 5 seconds...'", + "sleep 5", + "rm -f STATE.scm ECOSYSTEM.scm META.scm", + "echo '✓ Cleaned'", + ], + }, + }, + + # Validation (Yard-level checks before Hunt execution) + validation = { + check_repo_name = std.string.length config.repo_name > 0, + check_language = std.string.length config.primary_language > 0, + }, +} diff --git a/vendor/bunsenite/contractiles/self-validating/template-hunt.k9.ncl b/vendor/bunsenite/contractiles/self-validating/template-hunt.k9.ncl new file mode 100644 index 0000000..b3fcb47 --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/template-hunt.k9.ncl @@ -0,0 +1,136 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Hunt-level template: Full execution with Just recipes +# Security Level: Hunt (full system access) +# ⚠️ SIGNATURE REQUIRED - Review carefully before use + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'deployment', 'setup-script')", + security = { + leash = 'Hunt, + trust_level = "full-system-access", + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + signature_required = true, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Detailed description of what this component does", + author = "Jonathan D.A. Jewell ", + }, + warnings = [ + "This component has full system access", + "Only run from trusted sources with verified signatures", + "Review all Just recipes before execution", + "Use dry-run mode first: ./must --dry-run run your-file.k9.ncl", + ], + side_effects = [ + "TODO: List what files/directories this creates or modifies", + "TODO: List what commands this executes", + "TODO: List what network access this requires", + ], + }, + + # Configuration with contracts (Yard-level validation) + config = { + # Add your configuration here with appropriate contracts + target_dir + | String + | std.string.NonEmpty + = "/tmp/k9-output", + + dry_run | Bool = false, + + # Add more config as needed + }, + + # Just recipes for execution + # These run when: ./must run your-file.k9.ncl + recipes = { + # Main entry point (runs by default) + default = { + recipe = "TODO: main-task", + description = "TODO: What the default recipe does", + }, + + # Define your recipes here + "main-task" = { + dependencies = ["check-prerequisites"], + commands = [ + "echo 'TODO: Add your commands here'", + # Example: Create directory + # "mkdir -p %{config.target_dir}", + # Example: Run a command + # "just build", + # Example: Conditional execution + # "@if [ \"%{config.dry_run}\" = \"true\" ]; then echo '[DRY-RUN] Would execute'; else actual-command; fi", + ], + }, + + "check-prerequisites" = { + description = "Verify required tools and permissions", + commands = [ + # Example: Check for required tools + # "command -v git || (echo 'ERROR: git not found' && exit 1)", + # Example: Check permissions + # "[ -w %{config.target_dir} ] || (echo 'ERROR: Cannot write to target directory' && exit 1)", + "echo '✓ Prerequisites checked'", + ], + }, + + # Add more recipes as needed + "build" = { + description = "Build the project", + commands = [ + "echo 'TODO: Add build commands'", + ], + }, + + "deploy" = { + description = "Deploy the application", + dependencies = ["build"], + commands = [ + "echo 'TODO: Add deployment commands'", + ], + }, + + "clean" = { + description = "Clean up generated files", + commands = [ + "echo '⚠️ This will delete files - waiting 3 seconds...'", + "sleep 3", + "echo 'TODO: Add cleanup commands'", + # "rm -rf %{config.target_dir}", + ], + }, + }, + + # Validation (Yard-level checks before Hunt execution) + validation = { + check_target_dir = std.string.length config.target_dir > 0, + # Add more validation as needed + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Define configuration with contracts +# 3. Implement Just recipes with your commands +# 4. Test with dry-run: ./must --dry-run run your-file.k9.ncl +# 5. Review dry-run output carefully +# 6. Sign the component: ./must sign your-file.k9.ncl +# 7. Distribute with signature: your-file.k9.ncl.sig +# 8. Users verify and run: ./must verify && ./must run your-file.k9.ncl +# +# Security checklist: +# ✓ All TODO items filled in +# ✓ side_effects documented accurately +# ✓ Commands reviewed for safety +# ✓ No hardcoded secrets or credentials +# ✓ Proper error handling in recipes +# ✓ Tested in dry-run mode +# ✓ Component signed with trusted key diff --git a/vendor/bunsenite/contractiles/self-validating/template-kennel.k9.ncl b/vendor/bunsenite/contractiles/self-validating/template-kennel.k9.ncl new file mode 100644 index 0000000..4228b26 --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/template-kennel.k9.ncl @@ -0,0 +1,54 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Kennel-level template: Pure data configuration +# Security Level: Kennel (data-only, no execution) +# No signature required - safe for any use + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'build-config', 'metadata')", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Brief description of what this component contains", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Your configuration data here + config = { + # Example: Pure data values + setting_1 = "value", + setting_2 = 42, + setting_3 = true, + + nested = { + key = "value", + }, + + list = [ + "item1", + "item2", + ], + }, + + # Optional: Export format specification + export = { + format = "json", # or "yaml", "toml" + destination = "output.json", + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Add your configuration data to config = { ... } +# 3. Validate: nickel typecheck your-file.k9.ncl +# 4. Export: nickel export your-file.k9.ncl > output.json diff --git a/vendor/bunsenite/contractiles/self-validating/template-yard.k9.ncl b/vendor/bunsenite/contractiles/self-validating/template-yard.k9.ncl new file mode 100644 index 0000000..a723f5a --- /dev/null +++ b/vendor/bunsenite/contractiles/self-validating/template-yard.k9.ncl @@ -0,0 +1,84 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Yard-level template: Configuration with validation +# Security Level: Yard (Nickel evaluation with contracts) +# Signature recommended but not required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'validated-config', 'schema')", + security = { + leash = 'Yard, + trust_level = "validated-config", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Brief description with validation details", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Configuration with Nickel contracts for validation + config = { + # Example: String that cannot be empty + name + | String + | std.string.NonEmpty + = "TODO: default value", + + # Example: Number with range constraint + port + | Number + | std.contract.from_predicate (fun p => p > 0 && p < 65536) + = 8080, + + # Example: Boolean flag + enabled | Bool = true, + + # Example: Enum (one of several values) + environment + | [| 'Development, 'Staging, 'Production |] + = 'Development, + + # Example: List with non-empty constraint + items + | Array String + | std.array.NonEmpty + = ["item1", "item2"], + + # Example: Nested object with contracts + database = { + host | String | std.string.NonEmpty = "localhost", + port | Number | std.contract.from_predicate (fun p => p > 0 && p < 65536) = 5432, + name | String | std.string.NonEmpty = "mydb", + }, + }, + + # Validation rules (additional cross-field checks) + validation = { + # Example: Check that at least one item exists + check_items = std.array.length config.items > 0, + + # Example: Check that production has secure settings + check_production = + if config.environment == 'Production then + config.enabled == true + else + true, + + # Add your custom validation rules here + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Define your config with appropriate contracts +# 3. Add validation rules in validation = { ... } +# 4. Validate: nickel typecheck your-file.k9.ncl +# 5. Evaluate: nickel eval your-file.k9.ncl +# 6. If validation passes, use in your application diff --git a/vendor/bunsenite/contractiles/trust/Trustfile.a2ml b/vendor/bunsenite/contractiles/trust/Trustfile.a2ml new file mode 100644 index 0000000..7b62388 --- /dev/null +++ b/vendor/bunsenite/contractiles/trust/Trustfile.a2ml @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: MPL-2.0 +# Trustfile (A2ML Canonical) +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +@abstract: +Trust and provenance verification for Bunsenite. +Maximal trust by default — LLM may read, build, test, lint, format. +@end + +@trust-level: maximal +@trust-boundary: repo +@trust-actions: [read, build, test, lint, format] +@trust-deny: [delete-branch, force-push, modify-ci-secrets, publish] + +## Integrity + +### license-content +- description: LICENSE contains expected SPDX identifier +- run: grep -q 'SPDX\|License\|MIT\|Apache\|PMPL\|MPL' LICENSE +- severity: critical + +### no-secrets-committed +- description: No .env or credential files in repo +- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local +- severity: critical diff --git a/vendor/bunsenite/docs/CITATIONS.adoc b/vendor/bunsenite/docs/CITATIONS.adoc new file mode 100644 index 0000000..2f9b947 --- /dev/null +++ b/vendor/bunsenite/docs/CITATIONS.adoc @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += bunsenite - Citation Guide +:toc: + +== BibTeX + +[source,bibtex] +---- +@software{bunsenite_2026, + author = {Jewell, Jonathan D.A.}, + title = {bunsenite}, + year = {2026}, + url = {https://github.com/hyperpolymath/bunsenite}, + license = {MPL-2.0} +} +---- + +== Harvard Style + +Jewell, J.D.A. (2026) _bunsenite_ [Computer software]. Available at: https://github.com/hyperpolymath/bunsenite + +== OSCOLA + +Jonathan D.A. Jewell, 'bunsenite' (2026) + +== MLA + +Jewell, Jonathan D.A. "bunsenite." 2026, github.com/hyperpolymath/bunsenite. + +== APA 7 + +Jewell, J.D.A. (2026). _bunsenite_ [Computer software]. GitHub. https://github.com/hyperpolymath/bunsenite + +== See Also + +* link:../CITATION.cff[CITATION.cff] +* link:../codemeta.json[codemeta.json] diff --git a/vendor/bunsenite/docs/tech-debt-2026-05-26.adoc b/vendor/bunsenite/docs/tech-debt-2026-05-26.adoc new file mode 100644 index 0000000..61b0fac --- /dev/null +++ b/vendor/bunsenite/docs/tech-debt-2026-05-26.adoc @@ -0,0 +1,67 @@ +== Tech-Debt Audit — bunsenite — 2026-05-26 + +*Source:* estate-wide automated scan 2026-05-26. *Companion:* +https://github.com/hyperpolymath/standards/tree/main/docs/audits[`+hyperpolymath/standards+` +2026-05-26-estate-*-debt audits]. *Combined severity:* `+MEDIUM+`. + +This file records the _raw findings_ — it does not by itself fix the +debt. Each section ends with a '`Recommended next move`' line; closing +the debt is follow-up work. + +=== 1. Proof debt + +No proof-bearing files (`+*.v+`, `+*.lean+`, `+*.agda+`, `+*.idr+`, +`+*.idr2+`, `+*.fst+`, `+*.dfy+`, `+*.tla+`, `+*.ads+`, `+*.adb+`) found +in this repo. + +*Recommended next move:* none. + +=== 2. Licence debt + +[cols=",",options="header",] +|=== +|Field |Value +|LICENSE file |`+LICENSE+` +|SPDX header |`+MPL-2.0+` +|Manifest licence |`+MPL-2.0+` +|Body classifier |`+Palimp-MPL-2.0+` +|Severity |`+ok+` +|=== + +*Recommended next move:* none for licence. + +=== 3. Documentation debt + +[cols=",",options="header",] +|=== +|Field |Value +|README lines |331 +|`+docs/+` files |2 +|`+docs/+` LoC |154 +|CHANGELOG.md |Y +|CONTRIBUTING.md |Y +|CODE_OF_CONDUCT.md |Y +|SECURITY.md |Y +|Severity |`+MEDIUM+` +|=== + +*Recommended next move:* introduce a `+docs/+` directory. The README at +331 lines has likely grown to do the work of `+docs/+` — split it into a +thin README + `+docs/architecture.md+`, `+docs/usage.md+`, etc. +Heavy-wiki exemplars to copy from: `+affinescript+`, `+boj-server+`, +`+echidna+`, `+hypatia+`. + +=== Cross-references + +* Estate proof-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-proof-debt.md+` +* Estate licence-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-licence-debt.md+` +* Estate documentation-debt audit: +`+hyperpolymath/standards/docs/audits/2026-05-26-estate-documentation-debt.md+` + +''''' + +🤖 Generated by Claude Code estate-wide tech-debt scan (2026-05-26). +This file is informational — closing the debt is follow-up work owned by +the maintainer. diff --git a/vendor/bunsenite/docs/wiki-home.adoc b/vendor/bunsenite/docs/wiki-home.adoc new file mode 100644 index 0000000..ef7e966 --- /dev/null +++ b/vendor/bunsenite/docs/wiki-home.adoc @@ -0,0 +1,127 @@ +== Bunsenite + +*Nickel configuration file parser with multi-language FFI bindings* + +https://github.com/hyperpolymath/rsr[image:https://img.shields.io/badge/RSR-Bronze-cd7f32[RSR +Bronze]] +link:[image:https://img.shields.io/badge/TPCF-Perimeter%203-blue[TPCF +Perimeter 3]] +link:[image:https://img.shields.io/badge/license-PMPL--1.0%20%7C%20Palimpsest-green[License]] + +=== Quick Start + +[source,bash] +---- +# Install from crates.io +cargo install bunsenite + +# Parse a Nickel config +bunsenite parse config.ncl --pretty + +# Validate without evaluation +bunsenite validate config.ncl + +# Interactive REPL +bunsenite repl + +# Watch mode +bunsenite watch config.ncl +---- + +=== Features + +[cols=",",options="header",] +|=== +|Feature |Description +|*Parse* |Parse Nickel configs to JSON +|*Validate* |Validate without full evaluation +|*Watch* |Auto-reload on file changes +|*REPL* |Interactive Nickel evaluation +|*Schema* |JSON Schema validation +|*FFI* |Stable C ABI via Zig +|=== + +=== Architecture + +.... +┌─────────────────────────────────────────┐ +│ Consumers │ +├─────────────┬─────────────┬─────────────┤ +│ Deno │ AffineScript │ Browser │ +│ (Deno FFI) │ (C FFI) │ (WASM) │ +└──────┬──────┴──────┬──────┴──────┬──────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────┐ +│ Zig C ABI Layer │ +│ (Stable interface across Rust │ +│ compiler versions) │ +└─────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Rust Core │ +│ │ +│ nickel-lang-core 0.9.1 │ +│ miette error diagnostics │ +│ serde serialization │ +└─────────────────────────────────────────┘ +.... + +=== Bindings + +==== Deno (JavaScript/TypeScript) + +[source,typescript] +---- +import { parseNickel, validateNickel } from "./bunsenite.ts"; + +const config = parseNickel('{ port = 8080 }', "config.ncl"); +console.log(config.port); // 8080 +---- + +==== AffineScript + +[source,affinescript] +---- +let config = Bunsenite.parse("{ port = 8080 }", "config.ncl") +Js.log(config) +---- + +==== WebAssembly + +[source,javascript] +---- +import init, { parse } from './bunsenite.js'; + +await init(); +const config = parse('{ port = 8080 }', 'config.ncl'); +---- + +=== RSR Compliance + +Bunsenite follows the *Rhodium Standard Repository* (RSR) Bronze tier: + +* ✅ *Type Safety*: Compile-time (Rust) +* ✅ *Memory Safety*: Rust ownership model +* ✅ *Offline-First*: No network dependencies +* ✅ *No TypeScript*: Deno FFI uses `+.ts+` but calls `+Deno.dlopen+` +* ✅ *No npm/bun*: AffineScript `+package.json+` is for npm publishing +only +* ✅ *No Python*: Clean +* ✅ *Justfile*: All builds via Justfile + +=== Pages + +* [[Installation]] +* [[CLI Reference]] +* [[API Reference]] +* [[FFI Guide]] +* [[Examples]] +* [[Contributing]] + +=== Links + +* https://github.com/hyperpolymath/bunsenite[GitHub Repository] +* https://crates.io/crates/bunsenite[crates.io] +* https://docs.rs/bunsenite[Documentation] diff --git a/vendor/bunsenite/eclexiaiser.toml b/vendor/bunsenite/eclexiaiser.toml new file mode 100644 index 0000000..ff57fd9 --- /dev/null +++ b/vendor/bunsenite/eclexiaiser.toml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: MPL-2.0 +# eclexiaiser manifest for bunsenite + +[project] +name = "bunsenite" + +[[functions]] +name = "main" +source = "src/lib.rs" +energy-budget-mj = 25.0 + +[carbon] +provider = "static" +region = "GB" +static-intensity = 200.0 + +[report] +format = "text" +include-recommendations = true diff --git a/vendor/bunsenite/examples/config.ncl b/vendor/bunsenite/examples/config.ncl new file mode 100644 index 0000000..55cd14e --- /dev/null +++ b/vendor/bunsenite/examples/config.ncl @@ -0,0 +1,51 @@ +# Example Bunsenite Configuration +# This demonstrates various Nickel features + +{ + # Application metadata + name = "example-app", + version = "1.0.0", + description = "An example application configuration", + + # Server configuration + server = { + host = "0.0.0.0", + port = 8080, + workers = 4, + timeout_seconds = 30, + }, + + # Database configuration + database = { + url = "postgres://localhost:5432/mydb", + max_connections = 20, + timeout_ms = 5000, + }, + + # Feature flags + features = { + enable_logging = true, + enable_metrics = true, + enable_tracing = false, + debug_mode = false, + }, + + # Computed values + full_name = name ++ " v" ++ version, + server_url = "http://" ++ server.host ++ ":" ++ std.string.from_number server.port, + + # List example + allowed_origins = [ + "http://localhost:3000", + "http://localhost:8080", + "https://example.com", + ], + + # Nested configuration + logging = { + level = "info", + format = "json", + outputs = ["stdout", "file"], + file_path = "/var/log/app.log", + }, +} diff --git a/vendor/bunsenite/examples/simple.ncl b/vendor/bunsenite/examples/simple.ncl new file mode 100644 index 0000000..d3eaf22 --- /dev/null +++ b/vendor/bunsenite/examples/simple.ncl @@ -0,0 +1,8 @@ +# Simple Bunsenite Example +# Minimal configuration demonstrating basic features + +{ + name = "simple-app", + version = "1.0.0", + port = 8080, +} diff --git a/vendor/bunsenite/examples/web-project-deno.json b/vendor/bunsenite/examples/web-project-deno.json new file mode 100644 index 0000000..ee775a4 --- /dev/null +++ b/vendor/bunsenite/examples/web-project-deno.json @@ -0,0 +1,20 @@ +{ + "// NOTE": "Example deno.json for AffineScript web projects", + "tasks": { + "build": "deno run -A npm:affinescript", + "clean": "deno run -A npm:affinescript clean", + "watch": "deno run -A npm:affinescript -w", + "serve": "deno run -A jsr:@std/http/file-server .", + "test": "deno test --allow-all" + }, + "imports": { + "affinescript": "^12.0.0", + "@affinescript/core": "npm:@affinescript/core@^1.6.0", + "safe-dom/": "https://raw.githubusercontent.com/hyperpolymath/affinescript-dom-mounter/main/src/", + "proven/": "../proven/bindings/affinescript/src/" + }, + "compilerOptions": { + "allowJs": true, + "checkJs": false + } +} diff --git a/vendor/bunsenite/ffi/zig/build.zig b/vendor/bunsenite/ffi/zig/build.zig new file mode 100644 index 0000000..c02617f --- /dev/null +++ b/vendor/bunsenite/ffi/zig/build.zig @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// {{PROJECT}} FFI Build Configuration + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Shared library (.so, .dylib, .dll) + const lib = b.addSharedLibrary(.{ + .name = "{{project}}", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + // Set version + lib.version = .{ .major = 0, .minor = 1, .patch = 0 }; + + // Static library (.a) + const lib_static = b.addStaticLibrary(.{ + .name = "{{project}}", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + // Install artifacts + b.installArtifact(lib); + b.installArtifact(lib_static); + + // Generate header file for C compatibility + const header = b.addInstallHeader( + b.path("include/{{project}}.h"), + "{{project}}.h", + ); + b.getInstallStep().dependOn(&header.step); + + // Unit tests + const lib_tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const run_lib_tests = b.addRunArtifact(lib_tests); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&run_lib_tests.step); + + // Integration tests + const integration_tests = b.addTest(.{ + .root_source_file = b.path("test/integration_test.zig"), + .target = target, + .optimize = optimize, + }); + + integration_tests.linkLibrary(lib); + + const run_integration_tests = b.addRunArtifact(integration_tests); + + const integration_test_step = b.step("test-integration", "Run integration tests"); + integration_test_step.dependOn(&run_integration_tests.step); + + // Documentation + const docs = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .Debug, + }); + + const docs_step = b.step("docs", "Generate documentation"); + docs_step.dependOn(&b.addInstallDirectory(.{ + .source_dir = docs.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }).step); + + // Benchmark (if needed) + const bench = b.addExecutable(.{ + .name = "{{project}}-bench", + .root_source_file = b.path("bench/bench.zig"), + .target = target, + .optimize = .ReleaseFast, + }); + + bench.linkLibrary(lib); + + const run_bench = b.addRunArtifact(bench); + + const bench_step = b.step("bench", "Run benchmarks"); + bench_step.dependOn(&run_bench.step); +} diff --git a/vendor/bunsenite/ffi/zig/src/main.zig b/vendor/bunsenite/ffi/zig/src/main.zig new file mode 100644 index 0000000..059e6c3 --- /dev/null +++ b/vendor/bunsenite/ffi/zig/src/main.zig @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// BUNSENITE FFI Implementation +// +// This module implements the C-compatible FFI declared in src/abi/Foreign.idr +// All types and layouts must match the Idris2 ABI definitions. +// + +const std = @import("std"); + +// Version information (keep in sync with project) +const VERSION = "0.1.0"; +const BUILD_INFO = "BUNSENITE built with Zig " ++ @import("builtin").zig_version_string; + +/// Thread-local error storage +threadlocal var last_error: ?[]const u8 = null; + +/// Set the last error message +fn setError(msg: []const u8) void { + last_error = msg; +} + +/// Clear the last error +fn clearError() void { + last_error = null; +} + +//============================================================================== +// Core Types (must match src/abi/Types.idr) +//============================================================================== + +/// Result codes (must match Idris2 Result type) +pub const Result = enum(c_int) { + ok = 0, + @"error" = 1, + invalid_param = 2, + out_of_memory = 3, + null_pointer = 4, +}; + +/// Library handle (opaque to prevent direct access) +pub const Handle = opaque { + // Internal state hidden from C + allocator: std.mem.Allocator, + initialized: bool, + // Add your fields here +}; + +//============================================================================== +// Library Lifecycle +//============================================================================== + +/// Initialize the library +/// Returns a handle, or null on failure +export fn bunsenite_init() ?*Handle { + const allocator = std.heap.c_allocator; + + const handle = allocator.create(Handle) catch { + setError("Failed to allocate handle"); + return null; + }; + + // Initialize handle + handle.* = .{ + .allocator = allocator, + .initialized = true, + }; + + clearError(); + return handle; +} + +/// Free the library handle +export fn bunsenite_free(handle: ?*Handle) void { + const h = handle orelse return; + const allocator = h.allocator; + + // Clean up resources + h.initialized = false; + + allocator.destroy(h); + clearError(); +} + +//============================================================================== +// Core Operations +//============================================================================== + +/// Process data (example operation) +export fn bunsenite_process(handle: ?*Handle, input: u32) Result { + const h = handle orelse { + setError("Null handle"); + return .null_pointer; + }; + + if (!h.initialized) { + setError("Handle not initialized"); + return .@"error"; + } + + // Example processing logic + _ = input; + + clearError(); + return .ok; +} + +//============================================================================== +// String Operations +//============================================================================== + +/// Get a string result (example) +/// Caller must free the returned string +export fn bunsenite_get_string(handle: ?*Handle) ?[*:0]const u8 { + const h = handle orelse { + setError("Null handle"); + return null; + }; + + if (!h.initialized) { + setError("Handle not initialized"); + return null; + } + + // Example: allocate and return a string + const result = h.allocator.dupeZ(u8, "Example result") catch { + setError("Failed to allocate string"); + return null; + }; + + clearError(); + return result.ptr; +} + +/// Free a string allocated by the library +export fn bunsenite_free_string(str: ?[*:0]const u8) void { + const s = str orelse return; + const allocator = std.heap.c_allocator; + + const slice = std.mem.span(s); + allocator.free(slice); +} + +//============================================================================== +// Array/Buffer Operations +//============================================================================== + +/// Process an array of data +export fn bunsenite_process_array( + handle: ?*Handle, + buffer: ?[*]const u8, + len: u32, +) Result { + const h = handle orelse { + setError("Null handle"); + return .null_pointer; + }; + + const buf = buffer orelse { + setError("Null buffer"); + return .null_pointer; + }; + + if (!h.initialized) { + setError("Handle not initialized"); + return .@"error"; + } + + // Access the buffer + const data = buf[0..len]; + _ = data; + + // Process data here + + clearError(); + return .ok; +} + +//============================================================================== +// Error Handling +//============================================================================== + +/// Get the last error message +/// Returns null if no error +export fn bunsenite_last_error() ?[*:0]const u8 { + const err = last_error orelse return null; + + // Return C string (static storage, no need to free) + const allocator = std.heap.c_allocator; + const c_str = allocator.dupeZ(u8, err) catch return null; + return c_str.ptr; +} + +//============================================================================== +// Version Information +//============================================================================== + +/// Get the library version +export fn bunsenite_version() [*:0]const u8 { + return VERSION.ptr; +} + +/// Get build information +export fn bunsenite_build_info() [*:0]const u8 { + return BUILD_INFO.ptr; +} + +//============================================================================== +// Callback Support +//============================================================================== + +/// Callback function type (C ABI) +pub const Callback = *const fn (u64, u32) callconv(.C) u32; + +/// Register a callback +export fn bunsenite_register_callback( + handle: ?*Handle, + callback: ?Callback, +) Result { + const h = handle orelse { + setError("Null handle"); + return .null_pointer; + }; + + const cb = callback orelse { + setError("Null callback"); + return .null_pointer; + }; + + if (!h.initialized) { + setError("Handle not initialized"); + return .@"error"; + } + + // Store callback for later use + _ = cb; + + clearError(); + return .ok; +} + +//============================================================================== +// Utility Functions +//============================================================================== + +/// Check if handle is initialized +export fn bunsenite_is_initialized(handle: ?*Handle) u32 { + const h = handle orelse return 0; + return if (h.initialized) 1 else 0; +} + +//============================================================================== +// Tests +//============================================================================== + +test "lifecycle" { + const handle = bunsenite_init() orelse return error.InitFailed; + defer bunsenite_free(handle); + + try std.testing.expect(bunsenite_is_initialized(handle) == 1); +} + +test "error handling" { + const result = bunsenite_process(null, 0); + try std.testing.expectEqual(Result.null_pointer, result); + + const err = bunsenite_last_error(); + try std.testing.expect(err != null); +} + +test "version" { + const ver = bunsenite_version(); + const ver_str = std.mem.span(ver); + try std.testing.expectEqualStrings(VERSION, ver_str); +} diff --git a/vendor/bunsenite/ffi/zig/test/integration_test.zig b/vendor/bunsenite/ffi/zig/test/integration_test.zig new file mode 100644 index 0000000..e481508 --- /dev/null +++ b/vendor/bunsenite/ffi/zig/test/integration_test.zig @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// {{PROJECT}} Integration Tests +// +// These tests verify that the Zig FFI correctly implements the Idris2 ABI + +const std = @import("std"); +const testing = std.testing; + +// Import FFI functions +extern fn {{project}}_init() ?*opaque {}; +extern fn {{project}}_free(?*opaque {}) void; +extern fn {{project}}_process(?*opaque {}, u32) c_int; +extern fn {{project}}_get_string(?*opaque {}) ?[*:0]const u8; +extern fn {{project}}_free_string(?[*:0]const u8) void; +extern fn {{project}}_last_error() ?[*:0]const u8; +extern fn {{project}}_version() [*:0]const u8; +extern fn {{project}}_is_initialized(?*opaque {}) u32; + +//============================================================================== +// Lifecycle Tests +//============================================================================== + +test "create and destroy handle" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + try testing.expect(handle != null); +} + +test "handle is initialized" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + const initialized = {{project}}_is_initialized(handle); + try testing.expectEqual(@as(u32, 1), initialized); +} + +test "null handle is not initialized" { + const initialized = {{project}}_is_initialized(null); + try testing.expectEqual(@as(u32, 0), initialized); +} + +//============================================================================== +// Operation Tests +//============================================================================== + +test "process with valid handle" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + const result = {{project}}_process(handle, 42); + try testing.expectEqual(@as(c_int, 0), result); // 0 = ok +} + +test "process with null handle returns error" { + const result = {{project}}_process(null, 42); + try testing.expectEqual(@as(c_int, 4), result); // 4 = null_pointer +} + +//============================================================================== +// String Tests +//============================================================================== + +test "get string result" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + const str = {{project}}_get_string(handle); + defer if (str) |s| {{project}}_free_string(s); + + try testing.expect(str != null); +} + +test "get string with null handle" { + const str = {{project}}_get_string(null); + try testing.expect(str == null); +} + +//============================================================================== +// Error Handling Tests +//============================================================================== + +test "last error after null handle operation" { + _ = {{project}}_process(null, 0); + + const err = {{project}}_last_error(); + try testing.expect(err != null); + + if (err) |e| { + const err_str = std.mem.span(e); + try testing.expect(err_str.len > 0); + } +} + +test "no error after successful operation" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + _ = {{project}}_process(handle, 0); + + // Error should be cleared after successful operation + // (This depends on implementation) +} + +//============================================================================== +// Version Tests +//============================================================================== + +test "version string is not empty" { + const ver = {{project}}_version(); + const ver_str = std.mem.span(ver); + + try testing.expect(ver_str.len > 0); +} + +test "version string is semantic version format" { + const ver = {{project}}_version(); + const ver_str = std.mem.span(ver); + + // Should be in format X.Y.Z + try testing.expect(std.mem.count(u8, ver_str, ".") >= 1); +} + +//============================================================================== +// Memory Safety Tests +//============================================================================== + +test "multiple handles are independent" { + const h1 = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(h1); + + const h2 = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(h2); + + try testing.expect(h1 != h2); + + // Operations on h1 should not affect h2 + _ = {{project}}_process(h1, 1); + _ = {{project}}_process(h2, 2); +} + +test "double free is safe" { + const handle = {{project}}_init() orelse return error.InitFailed; + + {{project}}_free(handle); + {{project}}_free(handle); // Should not crash +} + +test "free null is safe" { + {{project}}_free(null); // Should not crash +} + +//============================================================================== +// Thread Safety Tests (if applicable) +//============================================================================== + +test "concurrent operations" { + const handle = {{project}}_init() orelse return error.InitFailed; + defer {{project}}_free(handle); + + const ThreadContext = struct { + h: *opaque {}, + id: u32, + }; + + const thread_fn = struct { + fn run(ctx: ThreadContext) void { + _ = {{project}}_process(ctx.h, ctx.id); + } + }.run; + + var threads: [4]std.Thread = undefined; + for (&threads, 0..) |*thread, i| { + thread.* = try std.Thread.spawn(.{}, thread_fn, .{ + ThreadContext{ .h = handle, .id = @intCast(i) }, + }); + } + + for (threads) |thread| { + thread.join(); + } +} diff --git a/vendor/bunsenite/fuzz/Cargo.toml b/vendor/bunsenite/fuzz/Cargo.toml new file mode 100644 index 0000000..f93d1d3 --- /dev/null +++ b/vendor/bunsenite/fuzz/Cargo.toml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MPL-2.0 +[package] +name = "bunsenite-fuzz" +version = "0.0.0" +authors = ["Jonathan D.A. Jewell "] +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +arbitrary = { version = "1", features = ["derive"] } + +[dependencies.bunsenite] +path = ".." + +[[bin]] +name = "fuzz_parser" +path = "fuzz_targets/fuzz_parser.rs" +test = false +doc = false +bench = false diff --git a/vendor/bunsenite/fuzz/fuzz_targets/fuzz_parser.rs b/vendor/bunsenite/fuzz/fuzz_targets/fuzz_parser.rs new file mode 100644 index 0000000..3a8739f --- /dev/null +++ b/vendor/bunsenite/fuzz/fuzz_targets/fuzz_parser.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Fuzz target for bunsenite Nickel parser + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use bunsenite::NickelLoader; + +fuzz_target!(|data: &[u8]| { + // Convert bytes to string for parsing + if let Ok(input) = std::str::from_utf8(data) { + let loader = NickelLoader::new(); + + // Fuzz the main parsing function + // This exercises nickel-lang-core's parser with arbitrary input + let _ = loader.parse_string(input, "fuzz.ncl"); + + // Also fuzz validation (parsing without evaluation) + let _ = loader.validate(input, "fuzz.ncl"); + } +}); diff --git a/vendor/bunsenite/hooks/validate-codeql.sh b/vendor/bunsenite/hooks/validate-codeql.sh new file mode 100644 index 0000000..15b52c3 --- /dev/null +++ b/vendor/bunsenite/hooks/validate-codeql.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hook: Validate CodeQL language matrix matches repo +set -euo pipefail + +CODEQL_FILE=".github/workflows/codeql.yml" +[ -f "$CODEQL_FILE" ] || exit 0 + +# Detect languages in repo +HAS_JS=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | grep -v node_modules | head -1) +HAS_PY=$(find . -name "*.py" 2>/dev/null | grep -v __pycache__ | head -1) +HAS_GO=$(find . -name "*.go" 2>/dev/null | head -1) +HAS_RS=$(find . -name "*.rs" 2>/dev/null | head -1) + +# Check if matrix includes unsupported languages +if grep -q "language:.*python" "$CODEQL_FILE" && [ -z "$HAS_PY" ]; then + echo "WARNING: CodeQL configured for Python but no .py files found" +fi +if grep -q "language:.*go" "$CODEQL_FILE" && [ -z "$HAS_GO" ]; then + echo "WARNING: CodeQL configured for Go but no .go files found" +fi +if grep -q "language:.*javascript" "$CODEQL_FILE" && [ -z "$HAS_JS" ]; then + echo "WARNING: CodeQL configured for JavaScript but no JS/TS files found" +fi + +# Rust/OCaml are not supported - should use 'actions' only +if [ -n "$HAS_RS" ]; then + if grep -q "language:.*rust" "$CODEQL_FILE"; then + echo "ERROR: CodeQL does not support Rust - use ['actions'] instead" + exit 1 + fi +fi + +exit 0 diff --git a/vendor/bunsenite/hooks/validate-permissions.sh b/vendor/bunsenite/hooks/validate-permissions.sh new file mode 100644 index 0000000..1999b01 --- /dev/null +++ b/vendor/bunsenite/hooks/validate-permissions.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hook: Validate workflow permissions declarations +set -euo pipefail +ERRORS=0 +for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$workflow" ] || continue + if ! grep -qE '^permissions:' "$workflow"; then + echo "ERROR: Missing top-level permissions in $workflow" + ERRORS=$((ERRORS + 1)) + fi +done +[ $ERRORS -gt 0 ] && exit 1 +exit 0 diff --git a/vendor/bunsenite/hooks/validate-sha-pins.sh b/vendor/bunsenite/hooks/validate-sha-pins.sh new file mode 100644 index 0000000..697092b --- /dev/null +++ b/vendor/bunsenite/hooks/validate-sha-pins.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hook: Validate GitHub Actions are SHA-pinned + +set -euo pipefail + +ERRORS=0 + +for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$workflow" ] || continue + + # Find uses: lines that aren't SHA-pinned + while IFS= read -r line; do + if [[ "$line" =~ uses:.*@ ]]; then + # Check if it has a SHA (40 hex chars) + if ! echo "$line" | grep -qE '@[a-f0-9]{40}'; then + echo "ERROR: Unpinned action in $workflow" + echo " $line" + echo " Actions must use SHA pins: uses: action/name@SHA # version" + ERRORS=$((ERRORS + 1)) + fi + fi + done < "$workflow" +done + +if [ $ERRORS -gt 0 ]; then + echo "" + echo "Found $ERRORS unpinned actions. Please SHA-pin all GitHub Actions." + echo "Use: gh api repos/OWNER/REPO/git/matching-refs/tags/VERSION to find SHAs" + exit 1 +fi + +exit 0 diff --git a/vendor/bunsenite/hooks/validate-spdx.sh b/vendor/bunsenite/hooks/validate-spdx.sh new file mode 100644 index 0000000..aa45bb2 --- /dev/null +++ b/vendor/bunsenite/hooks/validate-spdx.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hook: Validate SPDX headers in workflow files + +set -euo pipefail + +ERRORS=0 +SPDX_PATTERN="^# SPDX-License-Identifier:MPL-2.0 + +for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$workflow" ] || continue + + first_line=$(head -n1 "$workflow") + if ! echo "$first_line" | grep -qE "$SPDX_PATTERN"; then + echo "ERROR: Missing SPDX header in $workflow" + echo " First line should be: # SPDX-License-Identifier: MPL-2.0 + ERRORS=$((ERRORS + 1)) + fi +done + +if [ $ERRORS -gt 0 ]; then + exit 1 +fi + +exit 0 diff --git a/vendor/bunsenite/llm-warmup-dev.adoc b/vendor/bunsenite/llm-warmup-dev.adoc new file mode 100644 index 0000000..b46c552 --- /dev/null +++ b/vendor/bunsenite/llm-warmup-dev.adoc @@ -0,0 +1,19 @@ +== LLM Warmup — bunsenite (Developer) + +=== What is bunsenite? + +See README.adoc for overview. + +=== Key Commands + +* `+just setup+` — set up development environment +* `+just build+` — build the project +* `+just test+` — run tests +* `+just doctor+` — diagnose issues +* `+just heal+` — attempt auto-repair + +=== Quick Context + +* License: MPL-2.0 +* Part of hyperpolymath ecosystem +* See EXPLAINME.adoc for architecture diff --git a/vendor/bunsenite/llm-warmup-user.adoc b/vendor/bunsenite/llm-warmup-user.adoc new file mode 100644 index 0000000..b152ccc --- /dev/null +++ b/vendor/bunsenite/llm-warmup-user.adoc @@ -0,0 +1,19 @@ +== LLM Warmup — bunsenite (User) + +=== What is bunsenite? + +See README.adoc for overview. + +=== Key Commands + +* `+just setup+` — set up development environment +* `+just build+` — build the project +* `+just test+` — run tests +* `+just doctor+` — diagnose issues +* `+just heal+` — attempt auto-repair + +=== Quick Context + +* License: MPL-2.0 +* Part of hyperpolymath ecosystem +* See EXPLAINME.adoc for architecture diff --git a/vendor/bunsenite/mise.toml b/vendor/bunsenite/mise.toml new file mode 100644 index 0000000..6dd983f --- /dev/null +++ b/vendor/bunsenite/mise.toml @@ -0,0 +1,57 @@ +[tools] +# Language runtimes +node = "latest" +python = "latest" +rust = "latest" +go = "latest" +zig = "latest" +java = "latest" +bun = "latest" +denojs = "latest" + +# Package managers +npm = "latest" +yarn = "latest" +pnpm = "latest" +pip = "latest" +cargo = "latest" +go-task = "latest" + +# Formatting & Linting +gofmt = "latest" +black = "latest" +isort = "latest" +ruff = "latest" +prettier = "latest" +shfmt = "latest" +stylua = "latest" + +# Build tools +cmake = "latest" +make = "latest" +ninja = "latest" + +# Shell tools +git = "latest" +gnu-sed = "latest" +gnu-tar = "latest" +gnu-grep = "latest" + +# Testing +vitest = "latest" +pytest = "latest" +jest = "latest" + +[env] +# Common environment variables +NODE_ENV = "development" +PYTHONDONTWRITEBYTECODE = "1" +PYTHONUNBUFFERED = "1" + +# Task runner alias +[alias] +task = "go-task" +build = "cargo build --release || npm run build || go build" +test = "cargo test || npm test || go test ./..." +lint = "ruff check . || prettier --check . || black --check ." +fmt = "ruff format . || prettier --write . || black ." diff --git a/vendor/bunsenite/packaging/arch/PKGBUILD b/vendor/bunsenite/packaging/arch/PKGBUILD new file mode 100644 index 0000000..df74a86 --- /dev/null +++ b/vendor/bunsenite/packaging/arch/PKGBUILD @@ -0,0 +1,54 @@ +# Maintainer: Campaign for Cooler Coding and Programming +# Arch Linux PKGBUILD for bunsenite + +pkgname=bunsenite +pkgver=1.0.2 +pkgrel=1 +pkgdesc="Nickel configuration file parser with multi-language FFI bindings" +arch=('x86_64' 'aarch64') +url="https://github.com/hyperpolymath/bunsenite" +license=('PMPL-1.0' 'custom:Palimpsest-0.8') +depends=('gcc-libs') +makedepends=('rust' 'cargo' 'zig') +optdepends=( + 'deno: For Deno TypeScript bindings' +) +provides=('bunsenite') +conflicts=('bunsenite-git') +source=("$pkgname-$pkgver.tar.gz::https://github.com/hyperpolymath/bunsenite/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('SKIP') + +build() { + cd "$pkgname-$pkgver" + + # Build Rust library and CLI with all features + cargo build --release --features full + + # Build Zig FFI layer + cd zig + zig build -Doptimize=ReleaseFast +} + +check() { + cd "$pkgname-$pkgver" + cargo test --release +} + +package() { + cd "$pkgname-$pkgver" + + # Install binary + install -Dm755 "target/release/bunsenite" "$pkgdir/usr/bin/bunsenite" + + # Install shared library + install -Dm755 "zig/zig-out/lib/libbunsenite.so" "$pkgdir/usr/lib/libbunsenite.so" + + # Install Rust library + install -Dm644 "target/release/libbunsenite.rlib" "$pkgdir/usr/lib/libbunsenite.rlib" + + # Install license + install -Dm644 LICENSE.txt "$pkgdir/usr/share/licenses/$pkgname/LICENSE.txt" + + # Install documentation + install -Dm644 README.adoc "$pkgdir/usr/share/doc/$pkgname/README.adoc" +} diff --git a/vendor/bunsenite/packaging/arch/PKGBUILD-bin b/vendor/bunsenite/packaging/arch/PKGBUILD-bin new file mode 100644 index 0000000..fad0f1f --- /dev/null +++ b/vendor/bunsenite/packaging/arch/PKGBUILD-bin @@ -0,0 +1,33 @@ +# Maintainer: hyperpolymath +# Contributor: Campaign for Cooler Coding and Programming +# Pre-built binary package for bunsenite + +pkgname=bunsenite-bin +pkgver=1.0.2 +pkgrel=1 +pkgdesc="Nickel configuration file parser with multi-language FFI bindings (pre-built binary)" +arch=('x86_64' 'aarch64') +url="https://github.com/hyperpolymath/bunsenite" +license=('PMPL-1.0' 'custom:Palimpsest-0.8') +depends=('gcc-libs') +provides=('bunsenite') +conflicts=('bunsenite') +source_x86_64=("${pkgname}-${pkgver}-x86_64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v${pkgver}/bunsenite-v${pkgver}-x86_64-unknown-linux-gnu.tar.gz") +source_aarch64=("${pkgname}-${pkgver}-aarch64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v${pkgver}/bunsenite-v${pkgver}-aarch64-unknown-linux-gnu.tar.gz") +sha256sums_x86_64=('cca819ddf5459163c49877e6284f2910f2fd7e6ad39c7f824152831a01d2c07e') +sha256sums_aarch64=('1393cd3ba4e476e18e806105ce1d621e080ebb4c06950ed50524efb32db22618') + +package() { + install -Dm755 "${srcdir}/bunsenite" "${pkgdir}/usr/bin/bunsenite" + + # Install shared library if present + if [[ -f "${srcdir}/libbunsenite.so" ]]; then + install -Dm755 "${srcdir}/libbunsenite.so" "${pkgdir}/usr/lib/libbunsenite.so" + fi + + # Install license + install -Dm644 "${srcdir}/LICENSE.txt" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + + # Install README + install -Dm644 "${srcdir}/README.adoc" "${pkgdir}/usr/share/doc/${pkgname}/README.adoc" +} diff --git a/vendor/bunsenite/packaging/aur-ready/.SRCINFO b/vendor/bunsenite/packaging/aur-ready/.SRCINFO new file mode 100644 index 0000000..ada9160 --- /dev/null +++ b/vendor/bunsenite/packaging/aur-ready/.SRCINFO @@ -0,0 +1,17 @@ +pkgbase = bunsenite-bin + pkgdesc = Nickel configuration file parser with multi-language FFI bindings + pkgver = 1.0.2 + pkgrel = 1 + url = https://github.com/hyperpolymath/bunsenite + arch = x86_64 + arch = aarch64 + license = MIT + license = custom:Palimpsest-0.8 + provides = bunsenite + conflicts = bunsenite + source_x86_64 = bunsenite-bin-1.0.2-x86_64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v1.0.2/bunsenite-v1.0.2-x86_64-unknown-linux-gnu.tar.gz + sha256sums_x86_64 = SKIP + source_aarch64 = bunsenite-bin-1.0.2-aarch64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v1.0.2/bunsenite-v1.0.2-aarch64-unknown-linux-gnu.tar.gz + sha256sums_aarch64 = SKIP + +pkgname = bunsenite-bin diff --git a/vendor/bunsenite/packaging/aur-ready/PKGBUILD b/vendor/bunsenite/packaging/aur-ready/PKGBUILD new file mode 100644 index 0000000..a6b1926 --- /dev/null +++ b/vendor/bunsenite/packaging/aur-ready/PKGBUILD @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +# Maintainer: hyperpolymath +pkgname=bunsenite-bin +pkgver=1.0.2 +pkgrel=1 +pkgdesc='Nickel configuration file parser with multi-language FFI bindings' +arch=('x86_64' 'aarch64') +url='https://github.com/hyperpolymath/bunsenite' +license=('PMPL-1.0' 'custom:Palimpsest-0.8') +provides=('bunsenite') +conflicts=('bunsenite') + +source_x86_64=("${pkgname}-${pkgver}-x86_64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v${pkgver}/bunsenite-v${pkgver}-x86_64-unknown-linux-gnu.tar.gz") +source_aarch64=("${pkgname}-${pkgver}-aarch64.tar.gz::https://github.com/hyperpolymath/bunsenite/releases/download/v${pkgver}/bunsenite-v${pkgver}-aarch64-unknown-linux-gnu.tar.gz") + +# These will be updated by the CI workflow +sha256sums_x86_64=('SKIP') +sha256sums_aarch64=('SKIP') + +package() { + install -Dm755 "${srcdir}/bunsenite" "${pkgdir}/usr/bin/bunsenite" +} diff --git a/vendor/bunsenite/packaging/chocolatey/bunsenite.nuspec b/vendor/bunsenite/packaging/chocolatey/bunsenite.nuspec new file mode 100644 index 0000000..6e183ae --- /dev/null +++ b/vendor/bunsenite/packaging/chocolatey/bunsenite.nuspec @@ -0,0 +1,33 @@ + + + + bunsenite + 1.0.0 + Bunsenite + Campaign for Cooler Coding and Programming + Campaign for Cooler Coding and Programming + https://github.com/hyperpolymath/bunsenite/-/blob/main/LICENSE-PMPL-1.0 + https://github.com/hyperpolymath/bunsenite + false + Nickel configuration file parser with multi-language FFI bindings. + +Bunsenite provides a Rust core library with a stable C ABI layer (via Zig) that enables bindings for Deno (JavaScript/TypeScript), Rescript, and WebAssembly. + +Features: +- Type Safety: Compile-time guarantees via Rust's type system +- Memory Safety: Rust ownership model, zero unsafe blocks +- Offline-First: Works completely air-gapped +- Multi-Language: FFI bindings for Deno, Rescript, and WASM + +RSR Compliance: Bronze Tier | TPCF Perimeter: 3 + Nickel configuration file parser with FFI bindings + https://github.com/hyperpolymath/bunsenite/-/releases + Copyright 2025 Campaign for Cooler Coding and Programming + nickel config parser ffi wasm rust + https://github.com/hyperpolymath/bunsenite + https://docs.rs/bunsenite + + + + + diff --git a/vendor/bunsenite/packaging/debian/control b/vendor/bunsenite/packaging/debian/control new file mode 100644 index 0000000..3c391a4 --- /dev/null +++ b/vendor/bunsenite/packaging/debian/control @@ -0,0 +1,44 @@ +Source: bunsenite +Section: devel +Priority: optional +Maintainer: Campaign for Cooler Coding and Programming +Build-Depends: debhelper-compat (= 13), cargo, rustc (>= 1.70), zig +Standards-Version: 4.6.2 +Homepage: https://github.com/hyperpolymath/bunsenite +Vcs-Git: https://github.com/hyperpolymath/bunsenite.git +Vcs-Browser: https://github.com/hyperpolymath/bunsenite +Rules-Requires-Root: no + +Package: bunsenite +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Suggests: deno +Description: Nickel configuration file parser with FFI bindings + Bunsenite is a Nickel configuration file parser with multi-language + FFI bindings. It provides a Rust core library with a stable C ABI + layer (via Zig) that enables bindings for Deno (JavaScript/TypeScript), + Rescript, and WebAssembly. + . + Features: + - Type Safety: Compile-time guarantees via Rust's type system + - Memory Safety: Rust ownership model, zero unsafe blocks + - Offline-First: Works completely air-gapped + - Multi-Language: FFI bindings for Deno, Rescript, and WASM + . + RSR Compliance: Bronze Tier | TPCF Perimeter: 3 + +Package: libbunsenite-dev +Architecture: any +Section: libdevel +Depends: libbunsenite1 (= ${binary:Version}), ${misc:Depends} +Description: Nickel configuration file parser - development files + This package contains the development files for bunsenite, + including headers and static libraries for FFI integration. + +Package: libbunsenite1 +Architecture: any +Section: libs +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Nickel configuration file parser - shared library + This package contains the shared library for bunsenite, + providing FFI access from Deno, Rescript, and other languages. diff --git a/vendor/bunsenite/packaging/debian/rules b/vendor/bunsenite/packaging/debian/rules new file mode 100644 index 0000000..55e36cc --- /dev/null +++ b/vendor/bunsenite/packaging/debian/rules @@ -0,0 +1,28 @@ +#!/usr/bin/make -f +# Debian rules file for bunsenite + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all +export CARGO_HOME = $(CURDIR)/.cargo + +%: + dh $@ + +override_dh_auto_build: + cargo build --release --features full + cd zig && zig build -Doptimize=ReleaseFast + +override_dh_auto_test: + cargo test --release + +override_dh_auto_install: + # Install binary + install -D -m 755 target/release/bunsenite debian/bunsenite/usr/bin/bunsenite + # Install shared library + install -D -m 644 zig/zig-out/lib/libbunsenite.so debian/libbunsenite1/usr/lib/$(DEB_HOST_MULTIARCH)/libbunsenite.so.1.0.0 + ln -s libbunsenite.so.1.0.0 debian/libbunsenite1/usr/lib/$(DEB_HOST_MULTIARCH)/libbunsenite.so.1 + # Install development files + install -D -m 644 zig/zig-out/lib/libbunsenite.so debian/libbunsenite-dev/usr/lib/$(DEB_HOST_MULTIARCH)/libbunsenite.so + +override_dh_auto_clean: + cargo clean || true + rm -rf zig/zig-out zig/zig-cache || true diff --git a/vendor/bunsenite/packaging/flatpak/com.campaignforcoolercoding.bunsenite.yml b/vendor/bunsenite/packaging/flatpak/com.campaignforcoolercoding.bunsenite.yml new file mode 100644 index 0000000..3fd9ff8 --- /dev/null +++ b/vendor/bunsenite/packaging/flatpak/com.campaignforcoolercoding.bunsenite.yml @@ -0,0 +1,32 @@ +app-id: com.campaignforcoolercoding.bunsenite +runtime: org.freedesktop.Platform +runtime-version: '23.08' +sdk: org.freedesktop.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.rust-stable + - org.freedesktop.Sdk.Extension.zig + +command: bunsenite + +finish-args: + - --filesystem=home:ro + - --filesystem=xdg-config:ro + +build-options: + append-path: /usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/zig/bin + env: + CARGO_HOME: /run/build/bunsenite/cargo + RUSTUP_HOME: /usr/lib/sdk/rust-stable + +modules: + - name: bunsenite + buildsystem: simple + build-commands: + - cargo build --release --features full + - cd zig && zig build -Doptimize=ReleaseFast + - install -Dm755 target/release/bunsenite /app/bin/bunsenite + - install -Dm755 zig/zig-out/lib/libbunsenite.so /app/lib/libbunsenite.so + sources: + - type: git + url: https://github.com/hyperpolymath/bunsenite.git + tag: v1.0.0 diff --git a/vendor/bunsenite/packaging/homebrew/bunsenite.rb b/vendor/bunsenite/packaging/homebrew/bunsenite.rb new file mode 100644 index 0000000..496d414 --- /dev/null +++ b/vendor/bunsenite/packaging/homebrew/bunsenite.rb @@ -0,0 +1,48 @@ +# Homebrew formula for bunsenite +class Bunsenite < Formula + desc "Nickel configuration file parser with multi-language FFI bindings" + homepage "https://github.com/hyperpolymath/bunsenite" + url "https://github.com/hyperpolymath/bunsenite/archive/refs/tags/v1.0.0.tar.gz" + sha256 "TODO" + license any_of: ["PMPL-1.0", "Palimpsest-0.8"] + head "https://github.com/hyperpolymath/bunsenite.git", branch: "main" + + depends_on "rust" => :build + depends_on "zig" => :build + + def install + # Build Rust binary with all features + system "cargo", "build", "--release", "--features=full" + + # Build Zig FFI layer + cd "zig" do + system "zig", "build", "-Doptimize=ReleaseFast" + end + + # Install binary + bin.install "target/release/bunsenite" + + # Install shared library + lib.install "zig/zig-out/lib/libbunsenite.dylib" + + # Create symlink for Linux compatibility + lib.install_symlink "libbunsenite.dylib" => "libbunsenite.so" if OS.linux? + end + + test do + # Test version output + assert_match version.to_s, shell_output("#{bin}/bunsenite --version") + + # Test parsing a simple Nickel config + (testpath/"test.ncl").write <<~EOS + { + name = "test", + version = "1.0.0" + } + EOS + + output = shell_output("#{bin}/bunsenite parse #{testpath}/test.ncl") + assert_match "name", output + assert_match "test", output + end +end diff --git a/vendor/bunsenite/packaging/macports/Portfile b/vendor/bunsenite/packaging/macports/Portfile new file mode 100644 index 0000000..9d35511 --- /dev/null +++ b/vendor/bunsenite/packaging/macports/Portfile @@ -0,0 +1,55 @@ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 + +PortSystem 1.0 +PortGroup cargo 1.0 +PortGroup github 1.0 + +github.setup hyperpolymath bunsenite 1.0.0 v +revision 0 +categories devel +license PMPL-1.0 Palimpsest-0.8 +maintainers {github.com:hyperpolymath @maintainer} openmaintainer +description Nickel configuration file parser with FFI bindings +long_description Bunsenite is a Nickel configuration file parser with \ + multi-language FFI bindings. It provides a Rust core \ + library with a stable C ABI layer (via Zig) that enables \ + bindings for Deno, Rescript, and WebAssembly. + +homepage https://github.com/hyperpolymath/bunsenite + +checksums rmd160 SKIP \ + sha256 SKIP \ + size SKIP + +# Rust version requirement +compiler.cxx_standard 2017 + +depends_build-append \ + port:zig + +cargo.crates { + # Cargo.lock dependencies will be auto-generated +} + +build.args-append --features=full + +destroot { + xinstall -m 755 ${worksrcpath}/target/[cargo.rust_platform]/release/bunsenite \ + ${destroot}${prefix}/bin/bunsenite + + xinstall -d ${destroot}${prefix}/lib + xinstall -m 644 ${worksrcpath}/zig/zig-out/lib/libbunsenite.dylib \ + ${destroot}${prefix}/lib/libbunsenite.dylib + + xinstall -d ${destroot}${prefix}/share/doc/${name} + xinstall -m 644 ${worksrcpath}/README.adoc \ + ${destroot}${prefix}/share/doc/${name}/README.adoc +} + +notes " +Bunsenite has been installed with the following components: +- bunsenite CLI at ${prefix}/bin/bunsenite +- libbunsenite.dylib at ${prefix}/lib/libbunsenite.dylib + +RSR Compliance: Bronze Tier | TPCF Perimeter: 3 +" diff --git a/vendor/bunsenite/packaging/rpm/bunsenite.spec b/vendor/bunsenite/packaging/rpm/bunsenite.spec new file mode 100644 index 0000000..c71d540 --- /dev/null +++ b/vendor/bunsenite/packaging/rpm/bunsenite.spec @@ -0,0 +1,74 @@ +# RPM spec file for bunsenite +# Compatible with Fedora (dnf) and openSUSE (zypper) + +Name: bunsenite +Version: 1.0.0 +Release: 1%{?dist} +Summary: Nickel configuration file parser with multi-language FFI bindings + +License: PMPL-1.0 OR Palimpsest-0.8 +URL: https://github.com/hyperpolymath/bunsenite +Source0: %{name}-%{version}.tar.gz + +BuildRequires: rust >= 1.70 +BuildRequires: cargo +BuildRequires: zig +BuildRequires: gcc + +Requires: glibc + +%description +Bunsenite is a Nickel configuration file parser with multi-language +FFI bindings. It provides a Rust core library with a stable C ABI +layer (via Zig) that enables bindings for Deno (JavaScript/TypeScript), +Rescript, and WebAssembly. + +Features: +- Type Safety: Compile-time guarantees via Rust's type system +- Memory Safety: Rust ownership model, zero unsafe blocks +- Offline-First: Works completely air-gapped +- Multi-Language: FFI bindings for Deno, Rescript, and WASM + +RSR Compliance: Bronze Tier | TPCF Perimeter: 3 + +%package devel +Summary: Development files for bunsenite +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description devel +Development files for bunsenite including headers and static libraries. + +%prep +%autosetup + +%build +cargo build --release --features full +cd zig && zig build -Doptimize=ReleaseFast + +%check +cargo test --release + +%install +# Binary +install -D -m 755 target/release/bunsenite %{buildroot}%{_bindir}/bunsenite + +# Shared library +install -D -m 755 zig/zig-out/lib/libbunsenite.so %{buildroot}%{_libdir}/libbunsenite.so.1.0.0 +ln -s libbunsenite.so.1.0.0 %{buildroot}%{_libdir}/libbunsenite.so.1 +ln -s libbunsenite.so.1 %{buildroot}%{_libdir}/libbunsenite.so + +# Documentation +install -D -m 644 README.md %{buildroot}%{_docdir}/%{name}/README.md + +%files +%license LICENSE-PMPL-1.0 LICENSE-PALIMPSEST +%doc README.md +%{_bindir}/bunsenite +%{_libdir}/libbunsenite.so.1* + +%files devel +%{_libdir}/libbunsenite.so + +%changelog +* Thu Jan 01 2025 Campaign for Cooler Coding - 1.0.0-1 +- Initial release diff --git a/vendor/bunsenite/packaging/scoop/bunsenite.json b/vendor/bunsenite/packaging/scoop/bunsenite.json new file mode 100644 index 0000000..c74194f --- /dev/null +++ b/vendor/bunsenite/packaging/scoop/bunsenite.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0", + "description": "Nickel configuration file parser with multi-language FFI bindings", + "homepage": "https://github.com/hyperpolymath/bunsenite", + "license": "PMPL-1.0", + "architecture": { + "64bit": { + "url": "https://github.com/hyperpolymath/bunsenite/releases/download/v1.0.0/bunsenite-1.0.0-x86_64-pc-windows-msvc.zip", + "hash": "TODO", + "bin": "bunsenite.exe" + } + }, + "checkver": { + "github": "https://github.com/hyperpolymath/bunsenite" + }, + "autoupdate": { + "architecture": { + "64bit": { + "url": "https://github.com/hyperpolymath/bunsenite/releases/download/v$version/bunsenite-$version-x86_64-pc-windows-msvc.zip" + } + } + } +} diff --git a/vendor/bunsenite/packaging/winget/bunsenite.yaml b/vendor/bunsenite/packaging/winget/bunsenite.yaml new file mode 100644 index 0000000..2b85ef6 --- /dev/null +++ b/vendor/bunsenite/packaging/winget/bunsenite.yaml @@ -0,0 +1,42 @@ +# winget manifest for bunsenite +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json + +PackageIdentifier: Hyperpolymath.Bunsenite +PackageVersion: 1.0.0 +PackageLocale: en-US +Publisher: hyperpolymath +PublisherUrl: https://github.com/hyperpolymath +PackageName: Bunsenite +PackageUrl: https://github.com/hyperpolymath/bunsenite +License: PMPL-1.0 OR Palimpsest-0.8 +LicenseUrl: https://github.com/hyperpolymath/bunsenite/blob/main/LICENSE.txt +ShortDescription: Nickel configuration file parser with multi-language FFI bindings +Description: | + Bunsenite is a Nickel configuration file parser with multi-language FFI bindings. + It provides a Rust core library with a stable C ABI layer (via Zig) that enables + bindings for Deno (JavaScript/TypeScript), Rescript, and WebAssembly. + + Features: + - Type Safety: Compile-time guarantees via Rust's type system + - Memory Safety: Rust ownership model, zero unsafe blocks + - Offline-First: Works completely air-gapped + - Multi-Language: FFI bindings for Deno, Rescript, and WASM + + RSR Compliance: Bronze Tier | TPCF Perimeter: 3 +Tags: + - nickel + - config + - parser + - ffi + - rust + - cli +Moniker: bunsenite +Commands: + - bunsenite +Installers: + - Architecture: x64 + InstallerType: zip + InstallerUrl: https://github.com/hyperpolymath/bunsenite/releases/download/v1.0.0/bunsenite-1.0.0-x86_64-pc-windows-msvc.zip + InstallerSha256: TODO +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/vendor/bunsenite/papers/arxiv/bunsenite.aux b/vendor/bunsenite/papers/arxiv/bunsenite.aux new file mode 100644 index 0000000..796d98c --- /dev/null +++ b/vendor/bunsenite/papers/arxiv/bunsenite.aux @@ -0,0 +1,52 @@ +\relax +\providecommand\hyper@newdestlabel[2]{} +\providecommand\HyField@AuxAddToFields[1]{} +\providecommand\HyField@AuxAddToCoFields[2]{} +\citation{nickel} +\@writefile{toc}{\contentsline {section}{\numberline {1}Introduction}{1}{section.1}\protected@file@percent } +\citation{nickel} +\@writefile{toc}{\contentsline {section}{\numberline {2}Background}{2}{section.2}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}The Nickel Configuration Language}{2}{subsection.2.1}\protected@file@percent } +\@writefile{lol}{\contentsline {lstlisting}{\numberline {1}Example Nickel configuration}{3}{lstlisting.1}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}The FFI Challenge}{3}{subsection.2.2}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}The Zig Advantage}{3}{subsection.2.3}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {3}Architecture}{3}{section.3}\protected@file@percent } +\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Bunsenite three-layer architecture. Deno and ReScript access the Rust core through a Zig-provided stable C ABI. WebAssembly bindings connect directly via wasm-bindgen.}}{4}{figure.1}\protected@file@percent } +\newlabel{fig:architecture}{{1}{4}{Bunsenite three-layer architecture. Deno and ReScript access the Rust core through a Zig-provided stable C ABI. WebAssembly bindings connect directly via wasm-bindgen}{figure.1}{}} +\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Layer 1: Rust Core}{4}{subsection.3.1}\protected@file@percent } +\@writefile{lol}{\contentsline {lstlisting}{\numberline {2}Core NickelLoader implementation}{4}{lstlisting.2}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Layer 2: Zig FFI}{5}{subsection.3.2}\protected@file@percent } +\@writefile{lol}{\contentsline {lstlisting}{\numberline {3}Zig FFI exports (C ABI)}{5}{lstlisting.3}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Layer 3: Language Bindings}{5}{subsection.3.3}\protected@file@percent } +\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.3.1}Deno Bindings}{5}{subsubsection.3.3.1}\protected@file@percent } +\@writefile{lol}{\contentsline {lstlisting}{\numberline {4}Deno FFI binding}{5}{lstlisting.4}\protected@file@percent } +\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.3.2}ReScript Bindings}{6}{subsubsection.3.3.2}\protected@file@percent } +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5}ReScript binding with Result type}{6}{lstlisting.5}\protected@file@percent } +\@writefile{toc}{\contentsline {subsubsection}{\numberline {3.3.3}WebAssembly Bindings}{6}{subsubsection.3.3.3}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {4}Safety Guarantees}{6}{section.4}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}Memory Safety}{6}{subsection.4.1}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}Type Safety}{7}{subsection.4.2}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3}Offline Operation}{7}{subsection.4.3}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {5}Compliance and Standards}{7}{section.5}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1}RSR Bronze Requirements}{7}{subsection.5.1}\protected@file@percent } +\@writefile{lot}{\contentsline {table}{\numberline {1}{\ignorespaces RSR Bronze compliance matrix}}{7}{table.1}\protected@file@percent } +\newlabel{tab:rsr}{{1}{7}{RSR Bronze compliance matrix}{table.1}{}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2}Security Considerations}{7}{subsection.5.2}\protected@file@percent } +\citation{dhall} +\citation{cue} +\@writefile{toc}{\contentsline {section}{\numberline {6}Performance}{8}{section.6}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {7}Related Work}{8}{section.7}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {7.1}Configuration Languages}{8}{subsection.7.1}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {7.2}FFI Approaches}{8}{subsection.7.2}\protected@file@percent } +\bibstyle{plain} +\bibcite{nickel}{1} +\bibcite{dhall}{2} +\bibcite{cue}{3} +\bibcite{rust-abi}{4} +\@writefile{toc}{\contentsline {subsection}{\numberline {7.3}Rust FFI Libraries}{9}{subsection.7.3}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {8}Future Work}{9}{section.8}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {9}Conclusion}{9}{section.9}\protected@file@percent } +\bibcite{zig-ffi}{5} +\bibcite{wasm-bindgen}{6} +\bibcite{deno-ffi}{7} +\gdef \@abspage@last{10} diff --git a/vendor/bunsenite/papers/arxiv/bunsenite.out b/vendor/bunsenite/papers/arxiv/bunsenite.out new file mode 100644 index 0000000..0693df1 --- /dev/null +++ b/vendor/bunsenite/papers/arxiv/bunsenite.out @@ -0,0 +1,26 @@ +\BOOKMARK [1][-]{section.1}{\376\377\000I\000n\000t\000r\000o\000d\000u\000c\000t\000i\000o\000n}{}% 1 +\BOOKMARK [1][-]{section.2}{\376\377\000B\000a\000c\000k\000g\000r\000o\000u\000n\000d}{}% 2 +\BOOKMARK [2][-]{subsection.2.1}{\376\377\000T\000h\000e\000\040\000N\000i\000c\000k\000e\000l\000\040\000C\000o\000n\000f\000i\000g\000u\000r\000a\000t\000i\000o\000n\000\040\000L\000a\000n\000g\000u\000a\000g\000e}{section.2}% 3 +\BOOKMARK [2][-]{subsection.2.2}{\376\377\000T\000h\000e\000\040\000F\000F\000I\000\040\000C\000h\000a\000l\000l\000e\000n\000g\000e}{section.2}% 4 +\BOOKMARK [2][-]{subsection.2.3}{\376\377\000T\000h\000e\000\040\000Z\000i\000g\000\040\000A\000d\000v\000a\000n\000t\000a\000g\000e}{section.2}% 5 +\BOOKMARK [1][-]{section.3}{\376\377\000A\000r\000c\000h\000i\000t\000e\000c\000t\000u\000r\000e}{}% 6 +\BOOKMARK [2][-]{subsection.3.1}{\376\377\000L\000a\000y\000e\000r\000\040\0001\000:\000\040\000R\000u\000s\000t\000\040\000C\000o\000r\000e}{section.3}% 7 +\BOOKMARK [2][-]{subsection.3.2}{\376\377\000L\000a\000y\000e\000r\000\040\0002\000:\000\040\000Z\000i\000g\000\040\000F\000F\000I}{section.3}% 8 +\BOOKMARK [2][-]{subsection.3.3}{\376\377\000L\000a\000y\000e\000r\000\040\0003\000:\000\040\000L\000a\000n\000g\000u\000a\000g\000e\000\040\000B\000i\000n\000d\000i\000n\000g\000s}{section.3}% 9 +\BOOKMARK [3][-]{subsubsection.3.3.1}{\376\377\000D\000e\000n\000o\000\040\000B\000i\000n\000d\000i\000n\000g\000s}{subsection.3.3}% 10 +\BOOKMARK [3][-]{subsubsection.3.3.2}{\376\377\000R\000e\000S\000c\000r\000i\000p\000t\000\040\000B\000i\000n\000d\000i\000n\000g\000s}{subsection.3.3}% 11 +\BOOKMARK [3][-]{subsubsection.3.3.3}{\376\377\000W\000e\000b\000A\000s\000s\000e\000m\000b\000l\000y\000\040\000B\000i\000n\000d\000i\000n\000g\000s}{subsection.3.3}% 12 +\BOOKMARK [1][-]{section.4}{\376\377\000S\000a\000f\000e\000t\000y\000\040\000G\000u\000a\000r\000a\000n\000t\000e\000e\000s}{}% 13 +\BOOKMARK [2][-]{subsection.4.1}{\376\377\000M\000e\000m\000o\000r\000y\000\040\000S\000a\000f\000e\000t\000y}{section.4}% 14 +\BOOKMARK [2][-]{subsection.4.2}{\376\377\000T\000y\000p\000e\000\040\000S\000a\000f\000e\000t\000y}{section.4}% 15 +\BOOKMARK [2][-]{subsection.4.3}{\376\377\000O\000f\000f\000l\000i\000n\000e\000\040\000O\000p\000e\000r\000a\000t\000i\000o\000n}{section.4}% 16 +\BOOKMARK [1][-]{section.5}{\376\377\000C\000o\000m\000p\000l\000i\000a\000n\000c\000e\000\040\000a\000n\000d\000\040\000S\000t\000a\000n\000d\000a\000r\000d\000s}{}% 17 +\BOOKMARK [2][-]{subsection.5.1}{\376\377\000R\000S\000R\000\040\000B\000r\000o\000n\000z\000e\000\040\000R\000e\000q\000u\000i\000r\000e\000m\000e\000n\000t\000s}{section.5}% 18 +\BOOKMARK [2][-]{subsection.5.2}{\376\377\000S\000e\000c\000u\000r\000i\000t\000y\000\040\000C\000o\000n\000s\000i\000d\000e\000r\000a\000t\000i\000o\000n\000s}{section.5}% 19 +\BOOKMARK [1][-]{section.6}{\376\377\000P\000e\000r\000f\000o\000r\000m\000a\000n\000c\000e}{}% 20 +\BOOKMARK [1][-]{section.7}{\376\377\000R\000e\000l\000a\000t\000e\000d\000\040\000W\000o\000r\000k}{}% 21 +\BOOKMARK [2][-]{subsection.7.1}{\376\377\000C\000o\000n\000f\000i\000g\000u\000r\000a\000t\000i\000o\000n\000\040\000L\000a\000n\000g\000u\000a\000g\000e\000s}{section.7}% 22 +\BOOKMARK [2][-]{subsection.7.2}{\376\377\000F\000F\000I\000\040\000A\000p\000p\000r\000o\000a\000c\000h\000e\000s}{section.7}% 23 +\BOOKMARK [2][-]{subsection.7.3}{\376\377\000R\000u\000s\000t\000\040\000F\000F\000I\000\040\000L\000i\000b\000r\000a\000r\000i\000e\000s}{section.7}% 24 +\BOOKMARK [1][-]{section.8}{\376\377\000F\000u\000t\000u\000r\000e\000\040\000W\000o\000r\000k}{}% 25 +\BOOKMARK [1][-]{section.9}{\376\377\000C\000o\000n\000c\000l\000u\000s\000i\000o\000n}{}% 26 diff --git a/vendor/bunsenite/papers/arxiv/bunsenite.pdf b/vendor/bunsenite/papers/arxiv/bunsenite.pdf new file mode 100644 index 0000000..258e016 Binary files /dev/null and b/vendor/bunsenite/papers/arxiv/bunsenite.pdf differ diff --git a/vendor/bunsenite/papers/arxiv/bunsenite.tex b/vendor/bunsenite/papers/arxiv/bunsenite.tex new file mode 100644 index 0000000..8d6709f --- /dev/null +++ b/vendor/bunsenite/papers/arxiv/bunsenite.tex @@ -0,0 +1,450 @@ +% SPDX-License-Identifier: MPL-2.0 +\documentclass[11pt,a4paper]{article} + +% Packages +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{microtype} +\usepackage{hyperref} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{amsmath} +\usepackage{booktabs} +\usepackage{tikz} +\usetikzlibrary{shapes,arrows,positioning,fit,backgrounds} + +% Hyperref setup +\hypersetup{ + colorlinks=true, + linkcolor=blue, + filecolor=magenta, + urlcolor=cyan, + citecolor=blue, +} + +% Code listing style +\definecolor{codegreen}{rgb}{0,0.6,0} +\definecolor{codegray}{rgb}{0.5,0.5,0.5} +\definecolor{codepurple}{rgb}{0.58,0,0.82} +\definecolor{backcolour}{rgb}{0.95,0.95,0.92} + +\lstdefinestyle{codestyle}{ + backgroundcolor=\color{backcolour}, + commentstyle=\color{codegreen}, + keywordstyle=\color{magenta}, + numberstyle=\tiny\color{codegray}, + stringstyle=\color{codepurple}, + basicstyle=\ttfamily\footnotesize, + breakatwhitespace=false, + breaklines=true, + captionpos=b, + keepspaces=true, + numbers=left, + numbersep=5pt, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + frame=single, +} +\lstset{style=codestyle} + +% Title and authors +\title{Bunsenite: A Multi-Language FFI Architecture for\\Configuration Language Parsing} + +\author{ + Campaign for Cooler Coding and Programming\\ + \texttt{hyperpolymath}\\ + \href{https://github.com/hyperpolymath/bunsenite}{github.com/hyperpolymath/bunsenite} +} + +\date{\today} + +\begin{document} + +\maketitle + +\begin{abstract} +Configuration file management remains a critical challenge in modern software development, with applications frequently requiring configuration access from multiple programming languages within the same system. We present Bunsenite, a configuration file parser for the Nickel language that provides stable, multi-language bindings through a novel three-layer architecture: a Rust core for memory-safe parsing, a Zig intermediate layer providing a stable C ABI, and language-specific bindings for Deno (JavaScript), ReScript, and WebAssembly. This architecture isolates consumers from Rust's unstable ABI while preserving memory safety guarantees. We demonstrate that this approach enables type-safe configuration parsing across language boundaries without sacrificing performance or safety. Bunsenite achieves RSR Bronze tier compliance and operates fully offline, making it suitable for air-gapped and security-sensitive environments. +\end{abstract} + +\section{Introduction} + +Modern software systems increasingly operate as polyglot environments, with different components written in different programming languages chosen for their specific strengths. A web application might use Rust for performance-critical backend services, JavaScript for frontend interactivity, and ReScript for type-safe UI components. These heterogeneous systems share a common need: configuration management. + +Configuration languages have evolved from simple key-value formats (INI files) through structured data formats (JSON, YAML, TOML) to programmable configuration languages that support computation, type checking, and code reuse. Nickel~\cite{nickel} represents this latest generation, offering a gradually-typed, functional configuration language with contracts for validation. + +However, providing configuration parsing capabilities across multiple programming languages presents significant engineering challenges: + +\begin{enumerate} + \item \textbf{ABI Stability}: Rust, the natural choice for implementing a Nickel parser due to nickel-lang-core, does not guarantee a stable ABI between compiler versions. + \item \textbf{Memory Safety}: Foreign function interfaces (FFI) traditionally require unsafe code, creating potential for memory corruption. + \item \textbf{Type Safety}: Configuration values must be correctly represented in each target language's type system. + \item \textbf{Deployment Complexity}: Native libraries must be compiled for each target platform and architecture. +\end{enumerate} + +We present Bunsenite, a Nickel configuration parser that addresses these challenges through a three-layer architecture (Figure~\ref{fig:architecture}). Our contributions include: + +\begin{itemize} + \item A stable FFI design using Zig as an intermediate layer to isolate consumers from Rust ABI changes + \item Type-safe bindings for Deno (via \texttt{Deno.dlopen}), ReScript (via C FFI), and WebAssembly + \item An offline-first design with zero network dependencies + \item Compliance with the Rhodium Standard Repositories (RSR) framework at Bronze tier +\end{itemize} + +\section{Background} + +\subsection{The Nickel Configuration Language} + +Nickel is a configuration language designed to generate static configuration files with programmability, typing, and validation~\cite{nickel}. Unlike JSON or YAML, Nickel supports: + +\begin{itemize} + \item \textbf{Functions and Merging}: Configuration can be composed from reusable modules + \item \textbf{Gradual Typing}: Optional type annotations with inference + \item \textbf{Contracts}: Runtime validation of configuration values + \item \textbf{Evaluation}: Expressions are evaluated to produce final JSON/YAML/TOML output +\end{itemize} + +\begin{lstlisting}[language=ML,caption={Example Nickel configuration}] +{ + server = { + host = "localhost", + port = 8080, + max_connections = 100 * 10, # Computation + }, + + database | { host : String, port : Number } = { + host = "db.internal", + port = 5432, + }, +} +\end{lstlisting} + +The reference implementation, nickel-lang-core, is written in Rust, making Rust the natural choice for building Nickel tooling. + +\subsection{The FFI Challenge} + +Rust provides excellent memory safety guarantees but does not maintain a stable ABI. The \texttt{repr(Rust)} layout can change between compiler versions, meaning that a shared library compiled with Rust 1.70 may not be compatible with code compiled with Rust 1.75. + +The traditional solution is to use \texttt{extern "C"} functions with C-compatible types, but this requires careful manual memory management at the FFI boundary---exactly the kind of unsafe code that Rust was designed to avoid. + +\subsection{The Zig Advantage} + +Zig provides a compelling solution to the ABI stability problem. As a systems programming language with: + +\begin{itemize} + \item First-class C ABI compatibility + \item No hidden control flow or allocations + \item Compile-time execution for metaprogramming + \item Ability to link with both C and Rust code +\end{itemize} + +Zig can serve as a stable interface layer between Rust and consumer languages, absorbing ABI changes while presenting a consistent C interface. + +\section{Architecture} + +Bunsenite employs a three-layer architecture designed to maximize safety while providing stable multi-language access (Figure~\ref{fig:architecture}). + +\begin{figure}[htbp] +\centering +\begin{tikzpicture}[ + node distance=1.5cm, + box/.style={rectangle, draw, minimum width=2.5cm, minimum height=0.8cm, align=center}, + layer/.style={rectangle, draw, dashed, inner sep=0.3cm}, +] + +% Consumer layer +\node[box] (deno) {Deno}; +\node[box, right=0.5cm of deno] (rescript) {ReScript}; +\node[box, right=0.5cm of rescript] (wasm) {Browser\\(WASM)}; + +% Zig layer +\node[box, below=1cm of rescript] (zig) {Zig FFI Layer\\(Stable C ABI)}; + +% Rust layer +\node[box, below=1cm of zig] (rust) {Rust Core\\nickel-lang-core 0.9.1}; + +% Arrows +\draw[->] (deno) -- (zig); +\draw[->] (rescript) -- (zig); +\draw[->] (wasm) -- (rust); +\draw[->] (zig) -- (rust); + +% Layer labels +\node[left=0.5cm of deno, rotate=90, anchor=south] {\small Consumers}; +\node[left=0.5cm of zig, rotate=90, anchor=south] {\small FFI}; +\node[left=0.5cm of rust, rotate=90, anchor=south] {\small Core}; + +\end{tikzpicture} +\caption{Bunsenite three-layer architecture. Deno and ReScript access the Rust core through a Zig-provided stable C ABI. WebAssembly bindings connect directly via wasm-bindgen.} +\label{fig:architecture} +\end{figure} + +\subsection{Layer 1: Rust Core} + +The Rust core (\texttt{src/lib.rs}, \texttt{src/loader.rs}) provides the fundamental Nickel parsing and evaluation functionality: + +\begin{lstlisting}[language=Rust,caption={Core NickelLoader implementation}] +pub struct NickelLoader { + verbose: bool, +} + +impl NickelLoader { + pub fn parse_string(&self, source: &str, name: &str) + -> Result + { + let mut program: Program = + Program::new_from_source( + Cursor::new(source.as_bytes()), + name, + std::io::sink(), + )?; + + let eval_result = program.eval_full()?; + serde_json::to_value(&eval_result) + } +} +\end{lstlisting} + +The core enforces memory safety through Rust's ownership model. The \texttt{\#![deny(unsafe\_code)]} attribute ensures no unsafe blocks exist in the core library, with the single exception of the FFI boundary module. + +\subsection{Layer 2: Zig FFI} + +The Zig layer (\texttt{zig/bunsenite.zig}) provides a stable C ABI interface: + +\begin{lstlisting}[language=C,caption={Zig FFI exports (C ABI)}] +// Import Rust FFI functions +extern fn bunsenite_parse( + source: [*:0]const u8, + name: [*:0]const u8 +) callconv(.C) ?[*:0]u8; + +// Re-export with stable names +pub export fn parse_nickel( + source: [*:0]const u8, + name: [*:0]const u8 +) callconv(.C) ?[*:0]u8 { + return bunsenite_parse(source, name); +} +\end{lstlisting} + +This indirection provides several benefits: + +\begin{enumerate} + \item \textbf{ABI Isolation}: Consumer bindings depend on Zig's stable C ABI, not Rust's unstable ABI + \item \textbf{Symbol Stability}: Function names and signatures remain constant across Rust compiler updates + \item \textbf{Type Simplification}: Complex Rust types are converted to C-compatible primitives +\end{enumerate} + +\subsection{Layer 3: Language Bindings} + +\subsubsection{Deno Bindings} + +Deno bindings use \texttt{Deno.dlopen} for native FFI: + +\begin{lstlisting}[language=JavaScript,caption={Deno FFI binding}] +const symbols = { + parse_nickel: { + parameters: ["pointer", "pointer"], + result: "pointer", + }, + free_string: { + parameters: ["pointer"], + result: "void", + }, +}; + +const lib = Deno.dlopen(libPath, symbols); + +export function parseNickel(source: string, name: string) { + const resultPtr = lib.symbols.parse_nickel( + toCString(source), + toCString(name), + ); + try { + return JSON.parse(fromCString(resultPtr)); + } finally { + lib.symbols.free_string(resultPtr); + } +} +\end{lstlisting} + +\subsubsection{ReScript Bindings} + +ReScript bindings provide type-safe access with algebraic error handling: + +\begin{lstlisting}[language=ML,caption={ReScript binding with Result type}] +type error = + | ParseError(string) + | ValidationError(string) + | InvalidInput(string) + +let parseNickel = (source: string, name: string) + : result => { + let result = parseNickelRaw(source, name) + switch Js.Nullable.toOption(result) { + | Some(jsonString) => Ok(Js.Json.parseExn(jsonString)) + | None => Error(ParseError("Failed to parse: " ++ name)) + } +} +\end{lstlisting} + +\subsubsection{WebAssembly Bindings} + +For browser environments, Bunsenite compiles directly to WebAssembly using \texttt{wasm-bindgen}, bypassing the Zig layer since WASM provides its own stable binary interface. + +\section{Safety Guarantees} + +Bunsenite provides multiple layers of safety guarantees: + +\subsection{Memory Safety} + +\begin{itemize} + \item \textbf{Rust Core}: Ownership and borrowing prevent use-after-free, double-free, and buffer overflows + \item \textbf{FFI Boundary}: All FFI functions follow strict ownership protocols---callers receive owned pointers and must free them exactly once + \item \textbf{Zig Layer}: No hidden allocations; all memory flows explicitly through the defined API +\end{itemize} + +\subsection{Type Safety} + +\begin{itemize} + \item \textbf{Compile-time}: Rust's type system catches type errors before runtime + \item \textbf{Binding-level}: ReScript's type system ensures correct usage in consuming code + \item \textbf{Runtime}: Nickel's contract system validates configuration values +\end{itemize} + +\subsection{Offline Operation} + +Bunsenite has zero network dependencies in production code. This ``offline-first'' design ensures: + +\begin{itemize} + \item Operation in air-gapped environments + \item No supply chain attacks via runtime network requests + \item Deterministic behavior unaffected by network conditions +\end{itemize} + +\section{Compliance and Standards} + +Bunsenite adheres to the Rhodium Standard Repositories (RSR) framework at Bronze tier and the Trust Perimeter Classification Framework (TPCF) at Perimeter 3 (Community Sandbox). + +\subsection{RSR Bronze Requirements} + +\begin{table}[htbp] +\centering +\begin{tabular}{lll} +\toprule +\textbf{Requirement} & \textbf{Implementation} & \textbf{Verification} \\ +\midrule +Type Safety & Rust compiler & Compile-time \\ +Memory Safety & Ownership model & \texttt{\#![deny(unsafe\_code)]} \\ +Offline-First & No network deps & Cargo audit \\ +\bottomrule +\end{tabular} +\caption{RSR Bronze compliance matrix} +\label{tab:rsr} +\end{table} + +\subsection{Security Considerations} + +The library includes several security measures: + +\begin{itemize} + \item SHA-pinned dependencies in CI/CD workflows + \item SPDX license headers on all source files + \item Security policy with vulnerability reporting guidelines + \item Automated security scanning via CodeQL and Dependabot +\end{itemize} + +\section{Performance} + +While a comprehensive performance evaluation is beyond the scope of this paper, preliminary benchmarks indicate: + +\begin{itemize} + \item \textbf{Parse latency}: Sub-millisecond for typical configuration files (<1KB) + \item \textbf{FFI overhead}: Negligible (single function call indirection) + \item \textbf{Memory usage}: Linear with configuration size + \item \textbf{WASM size}: Optimized build produces ~2MB module +\end{itemize} + +The three-layer architecture introduces minimal overhead because: + +\begin{enumerate} + \item Zig's FFI wrapper compiles to direct function calls + \item JSON serialization happens once at the Rust layer + \item Consumer bindings perform no additional parsing +\end{enumerate} + +\section{Related Work} + +\subsection{Configuration Languages} + +Dhall~\cite{dhall} provides a programmable configuration language with strong normalization guarantees. CUE~\cite{cue} combines data validation with configuration. Unlike these, Nickel emphasizes gradual typing and seamless JSON interoperability. + +\subsection{FFI Approaches} + +Traditional approaches to multi-language FFI include: + +\begin{itemize} + \item \textbf{SWIG}: Generates bindings but requires complex configuration + \item \textbf{Protocol Buffers}: Adds serialization overhead for simple cases + \item \textbf{gRPC}: Introduces network complexity for local operations +\end{itemize} + +Our Zig-based approach provides the simplicity of C FFI with the safety guarantees of a modern systems language. + +\subsection{Rust FFI Libraries} + +Libraries like \texttt{cbindgen} and \texttt{safer-ffi} help generate C headers from Rust code. Bunsenite's approach differs by introducing an explicit Zig layer for ABI stability, rather than relying on C header generation alone. + +\section{Future Work} + +Several extensions are planned: + +\begin{itemize} + \item \textbf{Language Server Protocol}: Integration with editors via tower-lsp + \item \textbf{Watch Mode Optimization}: Incremental re-parsing for file watchers + \item \textbf{Additional Bindings}: Python, Ruby, and JVM languages + \item \textbf{Schema Generation}: Automatic JSON Schema from Nickel contracts +\end{itemize} + +\section{Conclusion} + +Bunsenite demonstrates that multi-language configuration parsing can be achieved without sacrificing memory safety or type guarantees. The three-layer architecture---Rust core, Zig FFI, language bindings---provides a template for building safe, stable, multi-language libraries. + +The key insight is that Zig's C ABI compatibility, combined with Rust's memory safety, creates a sweet spot for FFI design: consumers get a stable interface while the implementation benefits from modern safety guarantees. + +Bunsenite is open source under the PMPL-1.0 and Palimpsest-0.8 dual license, available at \url{https://github.com/hyperpolymath/bunsenite}. + +\section*{Acknowledgments} + +We thank the Nickel language team for nickel-lang-core, the Zig community for their work on C interoperability, and the Deno team for the excellent FFI API. + +\bibliographystyle{plain} +\begin{thebibliography}{9} + +\bibitem{nickel} +Tweag. \textit{Nickel: Better configuration for less}. \url{https://nickel-lang.org/}, 2024. + +\bibitem{dhall} +Gabriel Gonzalez. \textit{Dhall: A programmable configuration language}. \url{https://dhall-lang.org/}, 2024. + +\bibitem{cue} +Marcel van Lohuizen. \textit{CUE: Configure Unify Execute}. \url{https://cuelang.org/}, 2024. + +\bibitem{rust-abi} +Rust Language Team. \textit{The Rust Reference: Type Layout}. \url{https://doc.rust-lang.org/reference/type-layout.html}, 2024. + +\bibitem{zig-ffi} +Andrew Kelley et al. \textit{Zig Language Reference: C Interoperability}. \url{https://ziglang.org/documentation/master/}, 2024. + +\bibitem{wasm-bindgen} +The Rust and WebAssembly Working Group. \textit{wasm-bindgen Guide}. \url{https://rustwasm.github.io/wasm-bindgen/}, 2024. + +\bibitem{deno-ffi} +Deno Land Inc. \textit{Deno FFI}. \url{https://deno.land/manual/runtime/ffi_api}, 2024. + +\end{thebibliography} + +\end{document} diff --git a/vendor/bunsenite/selur-compose.toml b/vendor/bunsenite/selur-compose.toml new file mode 100644 index 0000000..5aae13a --- /dev/null +++ b/vendor/bunsenite/selur-compose.toml @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Stapeln service definition for bunsenite +# +# Usage: +# podman-compose -f selur-compose.toml up -d +# just stack-up + +[project] +name = "bunsenite" + +[services.app] +build = { context = ".", dockerfile = "Containerfile" } +restart = "unless-stopped" +networks = ["default"] +healthcheck = { test = "exit 0", interval = "30s", timeout = "5s", retries = 3 } diff --git a/vendor/bunsenite/setup-dev-env.k9.ncl b/vendor/bunsenite/setup-dev-env.k9.ncl new file mode 100644 index 0000000..3c9d087 --- /dev/null +++ b/vendor/bunsenite/setup-dev-env.k9.ncl @@ -0,0 +1,201 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Bunsenite Development Environment Setup +# +# This K9 component automates the setup of a complete Bunsenite development +# environment, including Rust toolchain, dependencies, pre-commit hooks, and +# test infrastructure. + +leash = 'Hunt + +pedigree = { + schema_version = "1.0.0", + component_type = "dev-env-setup", + author = "Jonathan D.A. Jewell ", + description = "Automated development environment setup for Bunsenite", + created = "2026-01-30", + k9_spec_version = "1.0.0", +} + +# Configuration with Nickel contracts +config = { + rust_version | String = "stable", + install_nightly | Bool = true, # For WASM builds + setup_git_hooks | Bool = true, + install_cargo_tools | Bool = true, + run_initial_tests | Bool = true, + + # Required Cargo tools for Bunsenite development + cargo_tools | Array String = [ + "cargo-audit", # Security auditing + "cargo-outdated", # Dependency updates + "cargo-deny", # License/dependency policy + "cargo-watch", # Auto-rebuild on changes + "cargo-expand", # Macro expansion debugging + "wasm-pack", # WASM builds + ], + + # Target platforms to install + targets | Array String = [ + "wasm32-unknown-unknown", # Browser WASM + "wasm32-wasi", # WASI WASM + ], + + # Pre-commit hooks to install + git_hooks | { _ : String } = { + "pre-commit" = "#!/bin/bash\nset -e\ncargo fmt --check\ncargo clippy -- -D warnings\n", + "pre-push" = "#!/bin/bash\nset -e\ncargo test\ncargo audit\n", + }, +} + +# Just recipes for setup tasks +recipes = { + default = { + recipe = "setup-all", + description = "Complete development environment setup", + }, + + "check-rust" = { + description = "Verify Rust toolchain is installed", + commands = [ + "command -v rustc >/dev/null 2>&1 || { echo 'Error: Rust not found. Install from https://rustup.rs/'; exit 1; }", + "rustc --version", + "cargo --version", + ], + }, + + "install-toolchain" = { + description = "Install Rust toolchains and targets", + dependencies = ["check-rust"], + commands = [ + "rustup install %{config.rust_version}", + "rustup default %{config.rust_version}", + ] @ ( + if config.install_nightly then + ["rustup install nightly", "rustup component add rust-src --toolchain nightly"] + else + [] + ) @ ( + config.targets + |> std.array.map (fun target => "rustup target add %{target}") + ), + }, + + "install-cargo-tools" = { + description = "Install Cargo development tools", + dependencies = ["install-toolchain"], + skip = !config.install_cargo_tools, + commands = config.cargo_tools + |> std.array.map (fun tool => "cargo install %{tool} || echo 'Warning: %{tool} install failed'"), + }, + + "setup-git-hooks" = { + description = "Install Git pre-commit and pre-push hooks", + skip = !config.setup_git_hooks, + commands = + config.git_hooks + |> std.record.to_array + |> std.array.map (fun entry => + let name = std.string.from entry.field in + let content = entry.value in + [ + "mkdir -p .git/hooks", + "cat > .git/hooks/%{name} <<'HOOK_EOF'\n%{content}\nHOOK_EOF", + "chmod +x .git/hooks/%{name}", + "echo 'Installed %{name} hook'", + ] + ) + |> std.array.flatten, + }, + + "build-debug" = { + description = "Build Bunsenite in debug mode", + dependencies = ["install-toolchain"], + commands = [ + "cargo build", + "echo 'Debug build complete: target/debug/bunsenite'", + ], + }, + + "build-release" = { + description = "Build Bunsenite in release mode with optimizations", + dependencies = ["install-toolchain"], + commands = [ + "cargo build --release", + "ls -lh target/release/bunsenite", + "echo 'Release build complete'", + ], + }, + + "build-wasm" = { + description = "Build WASM bindings for browser", + dependencies = ["install-cargo-tools"], + commands = [ + "cd wasm && wasm-pack build --target web", + "echo 'WASM build complete: wasm/pkg/'", + ], + }, + + "run-tests" = { + description = "Run full test suite", + skip = !config.run_initial_tests, + commands = [ + "cargo test --all-features", + "cargo test --release --all-features", + ], + }, + + "security-audit" = { + description = "Run security and license audits", + dependencies = ["install-cargo-tools"], + commands = [ + "cargo audit", + "cargo deny check", + ], + }, + + "setup-all" = { + description = "Complete development environment setup (default)", + dependencies = [ + "check-rust", + "install-toolchain", + "install-cargo-tools", + "setup-git-hooks", + "build-debug", + "run-tests", + ], + commands = [ + "echo ''", + "echo '╔══════════════════════════════════════════════════════════╗'", + "echo '║ ✅ Bunsenite Development Environment Setup Complete! ║'", + "echo '╚══════════════════════════════════════════════════════════╝'", + "echo ''", + "echo 'Next steps:'", + "echo ' 1. Build WASM bindings: just build-wasm'", + "echo ' 2. Run security audit: just security-audit'", + "echo ' 3. Start development: cargo watch -x check -x test'", + "echo ''", + ], + }, +} + +# Validation contracts +validation = { + # Ensure Rust version is valid + rust_version_valid = + config.rust_version == "stable" + || config.rust_version == "nightly" + || config.rust_version == "beta" + | doc "Rust version must be stable, nightly, or beta", + + # Ensure at least one cargo tool is selected + has_cargo_tools = + !config.install_cargo_tools + || std.array.length config.cargo_tools > 0 + | doc "If install_cargo_tools is true, must specify at least one tool", + + # Ensure WASM target is installed if building WASM + wasm_target_included = + std.array.any (fun t => t == "wasm32-unknown-unknown") config.targets + | doc "wasm32-unknown-unknown target required for WASM builds", +} diff --git a/vendor/bunsenite/setup.sh b/vendor/bunsenite/setup.sh new file mode 100644 index 0000000..f63875d --- /dev/null +++ b/vendor/bunsenite/setup.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Bunsenite — Universal Setup Script +# Detects platform and shell, installs just, then hands off to Justfile. + +set -euo pipefail + +echo "═══════════════════════════════════════════════════" +echo " Bunsenite — Setup" +echo "═══════════════════════════════════════════════════" +echo "" + +# Platform detection +OS="$(uname -s)" +ARCH="$(uname -m)" +echo "Platform: $OS $ARCH" + +# Shell detection +CURRENT_SHELL="$(basename "$SHELL" 2>/dev/null || echo "unknown")" +echo "Shell: $CURRENT_SHELL" +echo "" + +# Check for just +if ! command -v just >/dev/null 2>&1; then + echo "just (command runner) is required but not installed." + echo "" + case "$OS" in + Linux) + if command -v cargo >/dev/null 2>&1; then + echo "Installing just via cargo..." + cargo install just + elif command -v brew >/dev/null 2>&1; then + echo "Installing just via Homebrew..." + brew install just + else + echo "Install just from: https://just.systems/man/en/installation.html" + exit 1 + fi + ;; + Darwin) + if command -v brew >/dev/null 2>&1; then + echo "Installing just via Homebrew..." + brew install just + else + echo "Install Homebrew first: https://brew.sh" + echo "Then: brew install just" + exit 1 + fi + ;; + *) + echo "Install just from: https://just.systems/man/en/installation.html" + exit 1 + ;; + esac + echo "" +fi + +echo "Running diagnostics..." +just doctor + +echo "" +echo "Setup complete. Run 'just help-me' for common workflows." diff --git a/vendor/bunsenite/src/error.rs b/vendor/bunsenite/src/error.rs new file mode 100644 index 0000000..5b551a2 --- /dev/null +++ b/vendor/bunsenite/src/error.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Error types for Bunsenite +//! +//! This module provides comprehensive error handling for all Bunsenite operations. +//! Errors are designed to be informative and actionable for end users. +//! Uses miette for pretty error output with source context. + +// Allow unused_assignments to prevent false positive from cargo-tarpaulin coverage instrumentation +#![allow(unused_assignments)] + +use miette::{Diagnostic, SourceSpan}; + +/// Result type alias for Bunsenite operations +pub type Result = std::result::Result; + +/// Bunsenite error types with miette integration for rich diagnostics +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum Error { + /// Nickel parsing error + #[error("Failed to parse Nickel file '{file}'")] + #[diagnostic( + code(bunsenite::parse_error), + help("Check your Nickel syntax. Run 'nickel check' for detailed diagnostics.") + )] + ParseError { + /// Name of the file that failed to parse + file: String, + /// Error message from the parser + message: String, + /// Source code that caused the error + #[source_code] + src: Option, + /// Location of the error in source + #[label("error here")] + span: Option, + }, + + /// Nickel evaluation error + #[error("Failed to evaluate Nickel program '{file}'")] + #[diagnostic( + code(bunsenite::eval_error), + help("Ensure all variables are defined and types match.") + )] + EvaluationError { + /// Name of the file that failed to evaluate + file: String, + /// Error message from the evaluator + message: String, + /// Source code + #[source_code] + src: Option, + /// Location of the error + #[label("evaluation failed here")] + span: Option, + }, + + /// Serialization error (converting Nickel values to JSON) + #[error("Failed to serialize result: {0}")] + #[diagnostic( + code(bunsenite::serialization_error), + help("Ensure the Nickel program produces valid JSON-serializable values.") + )] + SerializationError(String), + + /// File I/O error + #[error("File I/O error: {0}")] + #[diagnostic(code(bunsenite::io_error), help("Check file permissions and path."))] + IoError(#[from] std::io::Error), + + /// Invalid input + #[error("Invalid input: {0}")] + #[diagnostic( + code(bunsenite::invalid_input), + help("Check the input format and try again.") + )] + InvalidInput(String), + + /// Watch error + #[error("Watch error: {0}")] + #[diagnostic( + code(bunsenite::watch_error), + help("Check that the file path is valid and accessible.") + )] + WatchError(String), + + /// Internal error (should not happen in normal operation) + #[error("Internal error: {0}")] + #[diagnostic( + code(bunsenite::internal_error), + url("https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite/-/issues"), + help("This is a bug. Please report it.") + )] + Internal(String), +} + +impl Error { + /// Create a new parse error + pub fn parse_error(file: impl Into, message: impl Into) -> Self { + Error::ParseError { + file: file.into(), + message: message.into(), + src: None, + span: None, + } + } + + /// Create a new parse error with source context + pub fn parse_error_with_source( + file: impl Into, + message: impl Into, + src: String, + offset: usize, + length: usize, + ) -> Self { + Error::ParseError { + file: file.into(), + message: message.into(), + src: Some(src), + span: Some(SourceSpan::new(offset.into(), length)), + } + } + + /// Create a new evaluation error + pub fn evaluation_error(file: impl Into, message: impl Into) -> Self { + Error::EvaluationError { + file: file.into(), + message: message.into(), + src: None, + span: None, + } + } + + /// Create a new evaluation error with source context + pub fn evaluation_error_with_source( + file: impl Into, + message: impl Into, + src: String, + offset: usize, + length: usize, + ) -> Self { + Error::EvaluationError { + file: file.into(), + message: message.into(), + src: Some(src), + span: Some(SourceSpan::new(offset.into(), length)), + } + } + + /// Create a new serialization error + pub fn serialization_error(message: impl Into) -> Self { + Error::SerializationError(message.into()) + } + + /// Create a new invalid input error + pub fn invalid_input(message: impl Into) -> Self { + Error::InvalidInput(message.into()) + } + + /// Create a new watch error + pub fn watch_error(message: impl Into) -> Self { + Error::WatchError(message.into()) + } + + /// Create a new internal error + pub fn internal(message: impl Into) -> Self { + Error::Internal(message.into()) + } + + /// Check if this error is recoverable + /// + /// Recoverable errors are those that the user can fix by changing input. + /// Non-recoverable errors indicate bugs or system issues. + pub fn is_recoverable(&self) -> bool { + matches!( + self, + Error::ParseError { .. } + | Error::InvalidInput(_) + | Error::EvaluationError { .. } + | Error::WatchError(_) + ) + } + + /// Get the error message (for compatibility) + pub fn message(&self) -> &str { + match self { + Error::ParseError { message, .. } => message, + Error::EvaluationError { message, .. } => message, + Error::SerializationError(msg) => msg, + Error::IoError(_) => "I/O error", + Error::InvalidInput(msg) => msg, + Error::WatchError(msg) => msg, + Error::Internal(msg) => msg, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error_creation() { + let err = Error::parse_error("test.ncl", "syntax error"); + assert!(err.is_recoverable()); + } + + #[test] + fn test_error_display() { + let err = Error::parse_error("config.ncl", "unexpected token"); + let msg = format!("{}", err); + assert!(msg.contains("config.ncl")); + } + + #[test] + fn test_recoverable_errors() { + assert!(Error::parse_error("test", "msg").is_recoverable()); + assert!(Error::invalid_input("msg").is_recoverable()); + assert!(Error::watch_error("msg").is_recoverable()); + assert!(!Error::internal("msg").is_recoverable()); + } + + #[test] + fn test_error_with_source_context() { + let err = Error::parse_error_with_source( + "test.ncl", + "unexpected token", + "let x = @invalid".to_string(), + 8, + 8, + ); + assert!(err.is_recoverable()); + assert_eq!(err.message(), "unexpected token"); + } + + #[test] + fn test_error_message() { + assert_eq!(Error::parse_error("f", "msg").message(), "msg"); + assert_eq!(Error::evaluation_error("f", "eval").message(), "eval"); + assert_eq!(Error::serialization_error("ser").message(), "ser"); + assert_eq!(Error::invalid_input("inp").message(), "inp"); + assert_eq!(Error::watch_error("watch").message(), "watch"); + assert_eq!(Error::internal("int").message(), "int"); + } +} diff --git a/vendor/bunsenite/src/ffi.rs b/vendor/bunsenite/src/ffi.rs new file mode 100644 index 0000000..7667a35 --- /dev/null +++ b/vendor/bunsenite/src/ffi.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! C FFI exports for Bunsenite +//! +//! This module provides C-compatible function exports that can be called +//! from Zig, which then re-exports them with stable ABI guarantees. +//! +//! # FFI Architecture +//! +//! ```text +//! ┌──────────────┐ ┌────────────┐ ┌────────────┐ +//! │ Consumers │ │ Zig FFI │ │ Rust Core │ +//! │ (Deno, etc.) │ ───> │ (ABI Safe) │ ───> │ (bunsenite)│ +//! └──────────────┘ └────────────┘ └────────────┘ +//! ``` +//! +//! # Safety +//! +//! These functions use `unsafe` for FFI boundary crossing. The Zig layer +//! provides additional safety guarantees and stable ABI. +//! +//! Key Safety Invariants: +//! 1. All pointers must be checked for null. +//! 2. Strings allocated by Rust must be freed by Rust (`bunsenite_free_string`). +//! 3. Static strings must never be freed. + +use crate::NickelLoader; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +/// Parse a Nickel configuration string and return JSON +/// +/// # Safety +/// +/// - `source` must be a valid null-terminated C string +/// - `name` must be a valid null-terminated C string +/// - The returned pointer must be freed with `bunsenite_free_string` +#[no_mangle] +pub unsafe extern "C" fn bunsenite_parse( + source: *const c_char, + name: *const c_char, +) -> *mut c_char { + if source.is_null() || name.is_null() { + return std::ptr::null_mut(); + } + + let source_str = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }; + + let name_str = match CStr::from_ptr(name).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }; + + let loader = NickelLoader::new(); + match loader.parse_string(source_str, name_str) { + Ok(value) => { + let json_string = match serde_json::to_string(&value) { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }; + match CString::new(json_string) { + Ok(cs) => cs.into_raw(), + Err(_) => std::ptr::null_mut(), + } + } + Err(_) => std::ptr::null_mut(), + } +} + +/// Validate a Nickel configuration without evaluating +/// +/// # Safety +/// +/// - `source` must be a valid null-terminated C string +/// - `name` must be a valid null-terminated C string +/// +/// # Returns +/// +/// - 0 on success (valid configuration) +/// - 1 on validation error +/// - -1 on invalid input (null pointers, invalid UTF-8) +#[no_mangle] +pub unsafe extern "C" fn bunsenite_validate(source: *const c_char, name: *const c_char) -> i32 { + if source.is_null() || name.is_null() { + return -1; + } + + let source_str = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return -1, + }; + + let name_str = match CStr::from_ptr(name).to_str() { + Ok(s) => s, + Err(_) => return -1, + }; + + let loader = NickelLoader::new(); + match loader.validate(source_str, name_str) { + Ok(()) => 0, + Err(_) => 1, + } +} + +/// Free a string allocated by bunsenite_parse +/// +/// # Safety +/// +/// - `ptr` must be a pointer returned by `bunsenite_parse` +/// - `ptr` must not have been freed before +/// - `ptr` may be null (no-op) +#[no_mangle] +pub unsafe extern "C" fn bunsenite_free_string(ptr: *mut c_char) { + if !ptr.is_null() { + drop(CString::from_raw(ptr)); + } +} + +/// Get the library version +/// +/// # Safety +/// +/// The returned pointer is static and must NOT be freed. +#[no_mangle] +pub extern "C" fn bunsenite_version() -> *const c_char { + static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes(); + VERSION.as_ptr() as *const c_char +} + +/// Get the RSR compliance tier +/// +/// # Safety +/// +/// The returned pointer is static and must NOT be freed. +#[no_mangle] +pub extern "C" fn bunsenite_rsr_tier() -> *const c_char { + static TIER: &[u8] = b"bronze\0"; + TIER.as_ptr() as *const c_char +} + +/// Get the TPCF perimeter number +#[no_mangle] +pub extern "C" fn bunsenite_tpcf_perimeter() -> u8 { + 3 // Community Sandbox +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + + #[test] + fn test_ffi_parse_valid() { + let source = CString::new("{ foo = 42 }").unwrap(); + let name = CString::new("test.ncl").unwrap(); + + unsafe { + let result = bunsenite_parse(source.as_ptr(), name.as_ptr()); + assert!(!result.is_null()); + + let result_str = CStr::from_ptr(result).to_str().unwrap(); + assert!(result_str.contains("foo")); + assert!(result_str.contains("42")); + + bunsenite_free_string(result); + } + } + + #[test] + fn test_ffi_parse_invalid() { + let source = CString::new("{ invalid = }").unwrap(); + let name = CString::new("bad.ncl").unwrap(); + + unsafe { + let result = bunsenite_parse(source.as_ptr(), name.as_ptr()); + assert!(result.is_null()); + } + } + + #[test] + fn test_ffi_validate_valid() { + let source = CString::new("{ foo = 42 }").unwrap(); + let name = CString::new("test.ncl").unwrap(); + + unsafe { + let result = bunsenite_validate(source.as_ptr(), name.as_ptr()); + assert_eq!(result, 0); + } + } + + #[test] + fn test_ffi_validate_invalid() { + let source = CString::new("{ foo = }").unwrap(); + let name = CString::new("bad.ncl").unwrap(); + + unsafe { + let result = bunsenite_validate(source.as_ptr(), name.as_ptr()); + assert_eq!(result, 1); + } + } + + #[test] + fn test_ffi_null_input() { + unsafe { + assert!(bunsenite_parse(std::ptr::null(), std::ptr::null()).is_null()); + assert_eq!(bunsenite_validate(std::ptr::null(), std::ptr::null()), -1); + } + } + + #[test] + fn test_ffi_version() { + let version = bunsenite_version(); + assert!(!version.is_null()); + unsafe { + let version_str = CStr::from_ptr(version).to_str().unwrap(); + assert!(!version_str.is_empty()); + } + } + + #[test] + fn test_ffi_rsr_tier() { + let tier = bunsenite_rsr_tier(); + assert!(!tier.is_null()); + unsafe { + let tier_str = CStr::from_ptr(tier).to_str().unwrap(); + assert_eq!(tier_str, "bronze"); + } + } + + #[test] + fn test_ffi_tpcf_perimeter() { + assert_eq!(bunsenite_tpcf_perimeter(), 3); + } +} diff --git a/vendor/bunsenite/src/lib.rs b/vendor/bunsenite/src/lib.rs new file mode 100644 index 0000000..3cdee48 --- /dev/null +++ b/vendor/bunsenite/src/lib.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Bunsenite: Nickel configuration file parser with multi-language FFI bindings +//! +//! Bunsenite provides a Rust core library with a stable C ABI layer (via Zig) +//! that enables bindings for Deno (JavaScript/TypeScript), Rescript, and +//! WebAssembly for browser and universal use. +//! +//! # Features +//! +//! - **Type Safety**: Compile-time guarantees via Rust's type system +//! - **Memory Safety**: Rust ownership model, zero `unsafe` blocks +//! - **Offline-First**: Works completely air-gapped, no network dependencies +//! - **Multi-Language**: FFI bindings for Deno, Rescript, and WASM +//! - **Standards Compliant**: RSR Bronze tier, TPCF Perimeter 3 +//! +//! # Examples +//! +//! ``` +//! use bunsenite::NickelLoader; +//! +//! let config = r#" +//! { +//! name = "example", +//! version = "1.0.0", +//! } +//! "#; +//! +//! let result = NickelLoader::new() +//! .parse_string(config, "config.ncl") +//! .expect("Failed to parse config"); +//! +//! println!("Parsed config: {}", result); +//! ``` +//! +//! # Architecture +//! +//! ```text +//! ┌─────────────────────────────────────────────────┐ +//! │ Consumers │ +//! ├───────────────┬───────────────┬─────────────────┤ +//! │ Deno │ Rescript │ Browser │ +//! │ (TypeScript) │ (ReScript) │ (WASM) │ +//! └───────┬───────┴───────┬───────┴────────┬────────┘ +//! │ │ │ +//! ▼ ▼ ▼ +//! ┌──────────┐ ┌──────────┐ ┌──────────────┐ +//! │ Zig FFI │ │ Zig FFI │ │ wasm-bindgen │ +//! │ (C ABI) │ │ (C ABI) │ │ │ +//! └─────┬────┘ └─────┬────┘ └──────┬───────┘ +//! │ │ │ +//! └──────────────┴─────────────────┘ +//! │ +//! ▼ +//! ┌─────────────────┐ +//! │ Rust Core │ +//! │ (lib.rs) │ +//! │ │ +//! │ nickel-lang-core│ +//! │ 0.9.1 │ +//! └─────────────────┘ +//! ``` + +#![deny(unsafe_code)] +#![warn( + missing_docs, + missing_debug_implementations, + rust_2018_idioms, + unreachable_pub +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub mod error; +pub mod loader; + +/// JSON Schema validation for parsed Nickel configurations +#[cfg(feature = "schema")] +#[cfg_attr(docsrs, doc(cfg(feature = "schema")))] +pub mod schema; + +/// C FFI exports for native bindings (Deno, ReScript via Zig) +/// +/// This module uses `unsafe` for FFI boundary crossing. +/// The Zig layer provides additional safety and stable ABI guarantees. +#[allow(unsafe_code)] +#[cfg(not(target_arch = "wasm32"))] +pub mod ffi; + +#[cfg(target_arch = "wasm32")] +#[cfg_attr(docsrs, doc(cfg(target_arch = "wasm32")))] +pub mod wasm; + +// Re-exports for convenience +pub use error::{Error, Result}; +pub use loader::NickelLoader; + +#[cfg(feature = "schema")] +pub use schema::{validate_config, SchemaValidator}; + +/// Library version, updated automatically from Cargo.toml +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Library name +pub const NAME: &str = env!("CARGO_PKG_NAME"); + +/// RSR Framework compliance tier +pub const RSR_TIER: &str = "bronze"; + +/// TPCF Perimeter assignment +pub const TPCF_PERIMETER: u8 = 3; // Community Sandbox + +/// Verify RSR compliance at compile time +/// +/// This ensures that the library meets RSR Bronze tier requirements: +/// - Type safety (enforced by Rust compiler) +/// - Memory safety (enforced by `#![deny(unsafe_code)]`) +/// - Offline-first (no network dependencies in production code) +#[cfg(test)] +mod rsr_compliance_tests { + use super::*; + + #[test] + fn test_no_unsafe_code() { + // This test passes if compilation succeeds with #![deny(unsafe_code)] + assert_eq!(RSR_TIER, "bronze"); + } + + #[test] + fn test_tpcf_perimeter() { + assert_eq!(TPCF_PERIMETER, 3); + } + + #[test] + fn test_version_format() { + // Ensure version follows semver + let parts: Vec<&str> = VERSION.split('.').collect(); + assert_eq!(parts.len(), 3, "Version should be semver (x.y.z)"); + } +} diff --git a/vendor/bunsenite/src/loader.rs b/vendor/bunsenite/src/loader.rs new file mode 100644 index 0000000..a962fcf --- /dev/null +++ b/vendor/bunsenite/src/loader.rs @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Nickel file loader and parser +//! +//! This module provides the core functionality for loading and parsing Nickel +//! configuration files using nickel-lang-core 0.18.0. +//! +//! # Core Responsibilities +//! +//! 1. **Loading**: Reading configuration from strings or files. +//! 2. **Parsing**: Using the upstream `nickel-lang-core` parser to generate an AST. +//! 3. **Evaluation**: Executing the Nickel program to produce a final configuration. +//! 4. **Export**: Converting the evaluated configuration into standard formats (JSON). +//! +//! # API Compatibility Notes (nickel-lang-core 0.18.0) +//! +//! - In-memory sources are loaded through `ProgramBuilder`. +//! - `eval_full()` takes no arguments. +//! - Evaluated values are converted with `serde_json::to_value()`. + +use crate::error::{Error, Result}; +use nickel_lang_core::eval::cache::lazy::CBNCache; +use nickel_lang_core::program::{Program, ProgramBuilder}; +use nickel_lang_core::typecheck::TypecheckMode; +use serde_json::Value; +use std::path::Path; + +/// Type alias for the standard Program with CBN (Call-By-Need) caching. +/// +/// CBN is the standard evaluation strategy for Nickel, ensuring lazy evaluation +/// of configuration fields. +type NickelProgram = Program; + +/// Nickel configuration loader +/// +/// Provides methods to parse and evaluate Nickel configuration files. +/// +/// # Examples +/// +/// ``` +/// use bunsenite::NickelLoader; +/// +/// let loader = NickelLoader::new(); +/// let config = r#"{ name = "example", version = "1.0.0" }"#; +/// let result = loader.parse_string(config, "test.ncl").unwrap(); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct NickelLoader { + /// Enable verbose error reporting + verbose: bool, +} + +impl NickelLoader { + /// Create a new Nickel loader with default settings + pub fn new() -> Self { + Self::default() + } + + /// Enable verbose error reporting + pub fn with_verbose(mut self, verbose: bool) -> Self { + self.verbose = verbose; + self + } + + /// Parse and evaluate a Nickel configuration from a string + /// + /// # Arguments + /// + /// * `source` - The Nickel configuration source code + /// * `name` - A name for this configuration (used in error messages) + /// + /// # Returns + /// + /// A JSON value representing the evaluated configuration + /// + /// # Errors + /// + /// Returns an error if parsing or evaluation fails + /// + /// # Examples + /// + /// ``` + /// use bunsenite::NickelLoader; + /// + /// let loader = NickelLoader::new(); + /// let result = loader.parse_string("{ foo = 42 }", "config.ncl"); + /// assert!(result.is_ok()); + /// ``` + pub fn parse_string(&self, source: &str, name: &str) -> Result { + let mut program: NickelProgram = ProgramBuilder::new() + .add_source_string(source, name) + .build() + .map_err(|e| Error::parse_error(name, format!("{:?}", e)))?; + + // Evaluate the program + let eval_result = program.eval_full().map_err(|e| { + let msg = format!("{:?}", e); + Error::evaluation_error(name, msg) + })?; + + // Convert to JSON + let json_value = serde_json::to_value(&eval_result) + .map_err(|e| Error::serialization_error(format!("Failed to convert to JSON: {}", e)))?; + + Ok(json_value) + } + + /// Parse and evaluate a Nickel configuration from a file + /// + /// # Arguments + /// + /// * `path` - Path to the Nickel configuration file + /// + /// # Returns + /// + /// A JSON value representing the evaluated configuration + /// + /// # Errors + /// + /// Returns an error if the file cannot be read or if parsing/evaluation fails + /// + /// # Examples + /// + /// ```no_run + /// use bunsenite::NickelLoader; + /// + /// let loader = NickelLoader::new(); + /// let result = loader.parse_file("config.ncl"); + /// ``` + pub fn parse_file>(&self, path: P) -> Result { + let path = path.as_ref(); + let source = std::fs::read_to_string(path)?; + let name = path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("unknown.ncl"); + + self.parse_string(&source, name) + } + + /// Validate a Nickel configuration without evaluating it + /// + /// This performs parsing and type-checking but does not evaluate the program. + /// + /// # Arguments + /// + /// * `source` - The Nickel configuration source code + /// * `name` - A name for this configuration (used in error messages) + /// + /// # Returns + /// + /// Ok(()) if the configuration is valid, Err otherwise + /// + /// # Examples + /// + /// ``` + /// use bunsenite::NickelLoader; + /// + /// let loader = NickelLoader::new(); + /// assert!(loader.validate("{ foo = 42 }", "test.ncl").is_ok()); + /// assert!(loader.validate("{ foo = }", "bad.ncl").is_err()); + /// ``` + pub fn validate(&self, source: &str, name: &str) -> Result<()> { + let mut program: NickelProgram = ProgramBuilder::new() + .add_source_string(source, name) + .build() + .map_err(|e| Error::parse_error(name, format!("{:?}", e)))?; + + program + .typecheck(TypecheckMode::Walk) + .map_err(|e| Error::parse_error(name, format!("{:?}", e)))?; + + Ok(()) + } + + /// Parse and evaluate a Nickel configuration (alias for parse_string) + /// + /// This is a convenience alias for `parse_string` for API compatibility. + pub fn parse(&self, source: &str, name: &str) -> Result { + self.parse_string(source, name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_simple_record() { + let loader = NickelLoader::new(); + let source = r#"{ name = "test", version = "1.0.0" }"#; + let result = loader.parse_string(source, "test.ncl"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_with_computation() { + let loader = NickelLoader::new(); + let source = r#"{ sum = 1 + 2 + 3, product = 4 * 5 }"#; + let result = loader.parse_string(source, "math.ncl").unwrap(); + + assert_eq!(result["sum"], 6); + assert_eq!(result["product"], 20); + } + + #[test] + fn test_parse_with_strings() { + let loader = NickelLoader::new(); + let source = r#"{ greeting = "Hello, " ++ "World!" }"#; + let result = loader.parse_string(source, "strings.ncl").unwrap(); + + assert_eq!(result["greeting"], "Hello, World!"); + } + + #[test] + fn test_parse_invalid_syntax() { + let loader = NickelLoader::new(); + let source = r#"{ foo = }"#; // Invalid: missing value + let result = loader.parse_string(source, "bad.ncl"); + assert!(result.is_err()); + } + + #[test] + fn test_validate_valid_config() { + let loader = NickelLoader::new(); + let source = r#"{ foo = 42, bar = "baz" }"#; + assert!(loader.validate(source, "test.ncl").is_ok()); + } + + #[test] + fn test_validate_invalid_config() { + let loader = NickelLoader::new(); + let source = r#"{ foo = }"#; // Invalid + assert!(loader.validate(source, "bad.ncl").is_err()); + } + + #[test] + fn test_verbose_mode() { + let loader = NickelLoader::new().with_verbose(true); + assert_eq!(loader.verbose, true); + } + + #[test] + fn test_default_constructor() { + let loader = NickelLoader::default(); + assert_eq!(loader.verbose, false); + } + + #[test] + fn test_error_contains_filename() { + let loader = NickelLoader::new(); + let source = r#"{ invalid syntax }"#; + let result = loader.parse_string(source, "myconfig.ncl"); + + match result { + Err(e) => { + let msg = format!("{}", e); + assert!(msg.contains("myconfig.ncl")); + } + Ok(_) => panic!("Expected error"), + } + } +} diff --git a/vendor/bunsenite/src/main.rs b/vendor/bunsenite/src/main.rs new file mode 100644 index 0000000..b136e5a --- /dev/null +++ b/vendor/bunsenite/src/main.rs @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Bunsenite CLI +//! +//! Command-line interface for parsing and evaluating Nickel configuration files + +use bunsenite::{NickelLoader, VERSION}; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; +use std::process; + +#[derive(Parser)] +#[command( + name = "bunsenite", + version = VERSION, + about = "Nickel configuration file parser with multi-language FFI bindings", + long_about = "Bunsenite is a Nickel configuration file parser with multi-language FFI bindings.\n\ + It provides a Rust core library with a stable C ABI layer (via Zig) that enables\n\ + bindings for Deno (JavaScript/TypeScript), Rescript, and WebAssembly.\n\n\ + RSR Compliance: Bronze Tier | TPCF Perimeter: 3 (Community Sandbox)" +)] +struct Cli { + #[command(subcommand)] + command: Option, + + /// Enable verbose output + #[arg(short, long, global = true)] + verbose: bool, +} + +#[derive(Subcommand)] +enum Commands { + /// Parse and evaluate a Nickel configuration file + Parse { + /// Path to the Nickel configuration file + #[arg(value_name = "FILE")] + file: PathBuf, + + /// Pretty-print the output JSON + #[arg(short, long)] + pretty: bool, + }, + + /// Validate a Nickel configuration without evaluating it + Validate { + /// Path to the Nickel configuration file + #[arg(value_name = "FILE")] + file: PathBuf, + }, + + /// Watch a file for changes and re-evaluate on save + #[cfg(feature = "watch")] + Watch { + /// Path to the Nickel configuration file to watch + #[arg(value_name = "FILE")] + file: PathBuf, + + /// Pretty-print the output JSON + #[arg(short, long)] + pretty: bool, + }, + + /// Start an interactive REPL for Nickel expressions + #[cfg(feature = "repl")] + Repl, + + /// Validate a Nickel config against a JSON schema + #[cfg(feature = "schema")] + Schema { + /// Path to the Nickel configuration file + #[arg(value_name = "CONFIG")] + config: PathBuf, + + /// Path to the JSON schema file + #[arg(value_name = "SCHEMA")] + schema: PathBuf, + }, + + /// Show version and compliance information + Info, +} + +fn main() { + // Install miette's pretty error handler + miette::set_hook(Box::new(|_| { + Box::new( + miette::MietteHandlerOpts::new() + .terminal_links(true) + .unicode(true) + .context_lines(2) + .build(), + ) + })) + .ok(); + + let cli = Cli::parse(); + + let result = match cli.command { + Some(Commands::Parse { file, pretty }) => handle_parse(file, pretty, cli.verbose), + Some(Commands::Validate { file }) => handle_validate(file, cli.verbose), + #[cfg(feature = "watch")] + Some(Commands::Watch { file, pretty }) => handle_watch(file, pretty, cli.verbose), + #[cfg(feature = "repl")] + Some(Commands::Repl) => handle_repl(cli.verbose), + #[cfg(feature = "schema")] + Some(Commands::Schema { config, schema }) => handle_schema(config, schema, cli.verbose), + Some(Commands::Info) => { + handle_info(); + Ok(()) + } + None => { + // No command specified, show help + println!("{}", get_help_text()); + Ok(()) + } + }; + + if let Err(e) = result { + // Use miette's error reporting + eprintln!("{:?}", miette::Report::new(e)); + process::exit(1); + } +} + +fn handle_parse(file: PathBuf, pretty: bool, verbose: bool) -> bunsenite::Result<()> { + if verbose { + eprintln!("Parsing file: {}", file.display()); + } + + let loader = NickelLoader::new().with_verbose(verbose); + let result = loader.parse_file(&file)?; + + if pretty { + println!("{}", serde_json::to_string_pretty(&result).unwrap()); + } else { + println!("{}", serde_json::to_string(&result).unwrap()); + } + + if verbose { + eprintln!("✓ Successfully parsed and evaluated"); + } + + Ok(()) +} + +fn handle_validate(file: PathBuf, verbose: bool) -> bunsenite::Result<()> { + if verbose { + eprintln!("Validating file: {}", file.display()); + } + + let source = std::fs::read_to_string(&file)?; + let name = file + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("unknown.ncl"); + + let loader = NickelLoader::new().with_verbose(verbose); + loader.validate(&source, name)?; + + println!("✓ Configuration is valid"); + + Ok(()) +} + +/// Watch a file for changes and re-parse on save +#[cfg(feature = "watch")] +fn handle_watch(file: PathBuf, pretty: bool, verbose: bool) -> bunsenite::Result<()> { + use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; + use std::sync::mpsc::channel; + use std::time::Duration; + + println!( + "Watching {} for changes (Ctrl+C to stop)...", + file.display() + ); + + // Initial parse + if let Err(e) = handle_parse(file.clone(), pretty, verbose) { + eprintln!("{:?}", miette::Report::new(e)); + } + + let (tx, rx) = channel(); + + let mut watcher = RecommendedWatcher::new( + move |res| { + if let Ok(event) = res { + let _ = tx.send(event); + } + }, + Config::default().with_poll_interval(Duration::from_millis(500)), + ) + .map_err(|e| bunsenite::Error::watch_error(e.to_string()))?; + + watcher + .watch(&file, RecursiveMode::NonRecursive) + .map_err(|e| bunsenite::Error::watch_error(e.to_string()))?; + + loop { + match rx.recv() { + Ok(event) => { + if event.kind.is_modify() { + println!("\n--- File changed, re-parsing... ---\n"); + if let Err(e) = handle_parse(file.clone(), pretty, verbose) { + eprintln!("{:?}", miette::Report::new(e)); + } + } + } + Err(e) => { + return Err(bunsenite::Error::watch_error(e.to_string())); + } + } + } +} + +/// Validate a Nickel config against a JSON schema +#[cfg(feature = "schema")] +fn handle_schema(config: PathBuf, schema: PathBuf, verbose: bool) -> bunsenite::Result<()> { + use bunsenite::SchemaValidator; + + if verbose { + eprintln!( + "Validating {} against schema {}", + config.display(), + schema.display() + ); + } + + let loader = NickelLoader::new().with_verbose(verbose); + let result = loader.parse_file(&config)?; + + let validator = SchemaValidator::from_file(&schema)?; + validator.validate(&result)?; + + println!("✓ Configuration matches schema"); + + Ok(()) +} + +/// Interactive REPL for Nickel expressions +#[cfg(feature = "repl")] +fn handle_repl(verbose: bool) -> bunsenite::Result<()> { + use rustyline::error::ReadlineError; + use rustyline::DefaultEditor; + + println!("Bunsenite v{} - Nickel REPL", VERSION); + println!("Type Nickel expressions to evaluate. Use :help for commands, :quit to exit.\n"); + + let mut rl = DefaultEditor::new().map_err(|e| bunsenite::Error::internal(e.to_string()))?; + let loader = NickelLoader::new().with_verbose(verbose); + + loop { + match rl.readline("nickel> ") { + Ok(line) => { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + + // Handle REPL commands + match trimmed { + ":quit" | ":q" | ":exit" => { + println!("Goodbye!"); + break; + } + ":help" | ":h" => { + println!("REPL Commands:"); + println!(" :help, :h Show this help"); + println!(" :quit, :q Exit the REPL"); + println!(" :clear, :c Clear the screen"); + println!(" :version, :v Show version info"); + println!("\nEnter any Nickel expression to evaluate it."); + continue; + } + ":clear" | ":c" => { + print!("\x1B[2J\x1B[1;1H"); + continue; + } + ":version" | ":v" => { + println!("Bunsenite v{}", VERSION); + continue; + } + _ => {} + } + + let _ = rl.add_history_entry(&line); + + match loader.parse(&trimmed, "") { + Ok(result) => { + println!("{}", serde_json::to_string_pretty(&result).unwrap()); + } + Err(e) => { + eprintln!("{:?}", miette::Report::new(e)); + } + } + } + Err(ReadlineError::Interrupted) => { + println!("^C"); + continue; + } + Err(ReadlineError::Eof) => { + println!("Goodbye!"); + break; + } + Err(e) => { + return Err(bunsenite::Error::internal(e.to_string())); + } + } + } + + Ok(()) +} + +fn handle_info() { + println!("Bunsenite v{}", VERSION); + println!(); + println!("A Nickel configuration file parser with multi-language FFI bindings"); + println!(); + println!("Features:"); + println!(" • Type Safety: Compile-time guarantees via Rust's type system"); + println!(" • Memory Safety: Rust ownership model, zero unsafe blocks"); + println!(" • Offline-First: Works completely air-gapped, no network dependencies"); + println!(" • Multi-Language: FFI bindings for Deno, Rescript, and WASM"); + println!(); + println!("Standards Compliance:"); + println!(" • RSR Framework: Bronze Tier"); + println!(" • TPCF Perimeter: 3 (Community Sandbox)"); + println!(" • License: Dual PMPL-1.0 + Palimpsest 0.8"); + println!(); + println!("Repository: https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite"); + println!(); +} + +fn get_help_text() -> String { + let mut commands = r#"COMMANDS: + parse Parse and evaluate a Nickel configuration file + validate Validate a Nickel configuration without evaluating it"# + .to_string(); + + #[cfg(feature = "watch")] + { + commands.push_str("\n watch Watch a file and re-evaluate on changes"); + } + + #[cfg(feature = "repl")] + { + commands.push_str("\n repl Start an interactive Nickel REPL"); + } + + #[cfg(feature = "schema")] + { + commands.push_str("\n schema Validate config against JSON schema"); + } + + commands.push_str( + r#" + info Show version and compliance information + help Print this message or the help of the given subcommand(s)"#, + ); + + let mut examples = r#"EXAMPLES: + # Parse and evaluate a config file + bunsenite parse config.ncl + + # Parse with pretty-printed output + bunsenite parse config.ncl --pretty + + # Validate without evaluating + bunsenite validate config.ncl"# + .to_string(); + + #[cfg(feature = "watch")] + { + examples.push_str( + r#" + + # Watch for changes + bunsenite watch config.ncl --pretty"#, + ); + } + + #[cfg(feature = "repl")] + { + examples.push_str( + r#" + + # Start interactive REPL + bunsenite repl"#, + ); + } + + #[cfg(feature = "schema")] + { + examples.push_str( + r#" + + # Validate against JSON schema + bunsenite schema config.ncl schema.json"#, + ); + } + + examples.push_str( + r#" + + # Show info + bunsenite info"#, + ); + + format!( + r#"Bunsenite v{VERSION} +Nickel configuration file parser + +USAGE: + bunsenite + +{commands} + +OPTIONS: + -v, --verbose Enable verbose output + -h, --help Print help information + -V, --version Print version information + +{examples} + +For more information, visit: +https://gitlab.com/campaign-for-cooler-coding-and-programming/bunsenite +"# + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cli_info_runs() { + // Just verify info command doesn't panic + handle_info(); + } + + #[test] + fn test_help_text_contains_version() { + let help = get_help_text(); + assert!(help.contains(VERSION)); + } +} diff --git a/vendor/bunsenite/src/schema.rs b/vendor/bunsenite/src/schema.rs new file mode 100644 index 0000000..ebde53d --- /dev/null +++ b/vendor/bunsenite/src/schema.rs @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Schema validation for Nickel configurations +//! +//! This module provides JSON Schema validation for parsed Nickel configurations. +//! It allows validating that the output of a Nickel config matches an expected schema. + +use crate::error::{Error, Result}; +use jsonschema::{JSONSchema, ValidationError}; +use serde_json::Value; +use std::path::Path; + +/// Schema validator for Nickel configurations +#[derive(Debug)] +pub struct SchemaValidator { + schema: JSONSchema, + schema_source: String, +} + +impl SchemaValidator { + /// Create a new schema validator from a JSON schema value + pub fn new(schema: Value) -> Result { + let compiled = JSONSchema::compile(&schema) + .map_err(|e| Error::invalid_input(format!("Invalid JSON schema: {}", e)))?; + + Ok(Self { + schema: compiled, + schema_source: serde_json::to_string_pretty(&schema) + .unwrap_or_else(|_| "".to_string()), + }) + } + + /// Create a new schema validator from a JSON schema string + pub fn from_str(schema_str: &str) -> Result { + let schema: Value = serde_json::from_str(schema_str) + .map_err(|e| Error::invalid_input(format!("Invalid JSON: {}", e)))?; + Self::new(schema) + } + + /// Create a new schema validator from a file path + pub fn from_file(path: impl AsRef) -> Result { + let content = std::fs::read_to_string(path.as_ref())?; + Self::from_str(&content) + } + + /// Validate a JSON value against the schema + pub fn validate(&self, value: &Value) -> Result<()> { + let result = self.schema.validate(value); + + if let Err(errors) = result { + let error_messages: Vec = errors + .map(|e| format!(" - {}: {}", e.instance_path, e)) + .collect(); + + return Err(Error::invalid_input(format!( + "Schema validation failed:\n{}", + error_messages.join("\n") + ))); + } + + Ok(()) + } + + /// Check if a value is valid without returning detailed errors + pub fn is_valid(&self, value: &Value) -> bool { + self.schema.is_valid(value) + } + + /// Get validation errors as a list of strings + pub fn get_errors(&self, value: &Value) -> Vec { + match self.schema.validate(value) { + Ok(_) => vec![], + Err(errors) => errors + .map(|e| format!("{}: {}", e.instance_path, e)) + .collect(), + } + } +} + +/// Validate a Nickel configuration against a JSON schema +/// +/// # Arguments +/// +/// * `config` - The parsed Nickel configuration as a JSON value +/// * `schema` - The JSON schema to validate against +/// +/// # Returns +/// +/// Returns `Ok(())` if validation passes, or an error with details on failure. +pub fn validate_config(config: &Value, schema: &Value) -> Result<()> { + let validator = SchemaValidator::new(schema.clone())?; + validator.validate(config) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_valid_schema() { + let schema = json!({ + "type": "object", + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" } + }, + "required": ["name"] + }); + + let validator = SchemaValidator::new(schema).unwrap(); + + let valid_config = json!({ + "name": "test", + "version": "1.0.0" + }); + + assert!(validator.validate(&valid_config).is_ok()); + assert!(validator.is_valid(&valid_config)); + } + + #[test] + fn test_invalid_config() { + let schema = json!({ + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + }); + + let validator = SchemaValidator::new(schema).unwrap(); + + let invalid_config = json!({ + "version": "1.0.0" + }); + + assert!(validator.validate(&invalid_config).is_err()); + assert!(!validator.is_valid(&invalid_config)); + } + + #[test] + fn test_type_validation() { + let schema = json!({ + "type": "object", + "properties": { + "port": { "type": "integer", "minimum": 1, "maximum": 65535 } + } + }); + + let validator = SchemaValidator::new(schema).unwrap(); + + let valid = json!({ "port": 8080 }); + let invalid_type = json!({ "port": "8080" }); + let invalid_range = json!({ "port": 70000 }); + + assert!(validator.is_valid(&valid)); + assert!(!validator.is_valid(&invalid_type)); + assert!(!validator.is_valid(&invalid_range)); + } + + #[test] + fn test_get_errors() { + let schema = json!({ + "type": "object", + "properties": { + "name": { "type": "string" }, + "port": { "type": "integer" } + }, + "required": ["name", "port"] + }); + + let validator = SchemaValidator::new(schema).unwrap(); + + let invalid = json!({ "name": 123 }); + let errors = validator.get_errors(&invalid); + + assert!(!errors.is_empty()); + } + + #[test] + fn test_from_str() { + let schema_str = r#"{ + "type": "object", + "properties": { + "enabled": { "type": "boolean" } + } + }"#; + + let validator = SchemaValidator::from_str(schema_str).unwrap(); + let valid = json!({ "enabled": true }); + + assert!(validator.is_valid(&valid)); + } + + #[test] + fn test_validate_config_function() { + let config = json!({ "name": "test" }); + let schema = json!({ + "type": "object", + "properties": { + "name": { "type": "string" } + } + }); + + assert!(validate_config(&config, &schema).is_ok()); + } +} diff --git a/vendor/bunsenite/src/wasm.rs b/vendor/bunsenite/src/wasm.rs new file mode 100644 index 0000000..19e6b04 --- /dev/null +++ b/vendor/bunsenite/src/wasm.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! WebAssembly bindings for Bunsenite +//! +//! This module provides WASM bindings that enable Bunsenite to run in browsers +//! and other WASM environments with ~95% native performance. +//! +//! # Examples +//! +//! ```javascript +//! import init, { parse_nickel } from './bunsenite.js'; +//! +//! async function main() { +//! await init(); +//! const config = `{ name = "example", version = "1.0.0" }`; +//! const result = parse_nickel(config, "config.ncl"); +//! console.log(JSON.parse(result)); +//! } +//! ``` + +use crate::{Error, NickelLoader}; +use wasm_bindgen::prelude::*; + +// Note: wee_alloc was removed as it is unmaintained and has known memory leaks. +// Rust 1.71+ provides a suitable default allocator for wasm32 targets. + +/// Initialize WASM module +/// +/// This should be called once before using any other WASM functions. +/// It sets up panic hooks for better error messages in the browser. +#[wasm_bindgen(start)] +pub fn init() { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} + +/// Parse and evaluate a Nickel configuration string +/// +/// # Arguments +/// +/// * `source` - The Nickel configuration source code +/// * `name` - A name for this configuration (used in error messages) +/// +/// # Returns +/// +/// A JSON string representing the evaluated configuration, or an error message +/// +/// # Examples +/// +/// ```javascript +/// const result = parse_nickel('{ foo = 42 }', 'config.ncl'); +/// const config = JSON.parse(result); +/// console.log(config.foo); // 42 +/// ``` +#[wasm_bindgen] +pub fn parse_nickel(source: &str, name: &str) -> Result { + let loader = NickelLoader::new(); + + let result = loader + .parse_string(source, name) + .map_err(|e| JsValue::from_str(&format!("{}", e)))?; + + serde_json::to_string(&result) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) +} + +/// Validate a Nickel configuration without evaluating it +/// +/// # Arguments +/// +/// * `source` - The Nickel configuration source code +/// * `name` - A name for this configuration (used in error messages) +/// +/// # Returns +/// +/// Ok(()) if valid, Err with error message if invalid +/// +/// # Examples +/// +/// ```javascript +/// try { +/// validate_nickel('{ foo = 42 }', 'config.ncl'); +/// console.log('Valid!'); +/// } catch (e) { +/// console.error('Invalid:', e); +/// } +/// ``` +#[wasm_bindgen] +pub fn validate_nickel(source: &str, name: &str) -> Result<(), JsValue> { + let loader = NickelLoader::new(); + + loader + .validate(source, name) + .map_err(|e| JsValue::from_str(&format!("{}", e))) +} + +/// Get library version +#[wasm_bindgen] +pub fn version() -> String { + crate::VERSION.to_string() +} + +/// Get RSR compliance tier +#[wasm_bindgen] +pub fn rsr_tier() -> String { + crate::RSR_TIER.to_string() +} + +/// Get TPCF perimeter +#[wasm_bindgen] +pub fn tpcf_perimeter() -> u8 { + crate::TPCF_PERIMETER +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wasm_parse_simple() { + let source = r#"{ name = "test" }"#; + let result = parse_nickel(source, "test.ncl"); + assert!(result.is_ok()); + } + + #[test] + fn test_wasm_validate_valid() { + let source = r#"{ foo = 42 }"#; + let result = validate_nickel(source, "test.ncl"); + assert!(result.is_ok()); + } + + #[test] + fn test_wasm_validate_invalid() { + let source = r#"{ foo = }"#; + let result = validate_nickel(source, "bad.ncl"); + assert!(result.is_err()); + } + + #[test] + fn test_wasm_version() { + let v = version(); + assert!(!v.is_empty()); + } + + #[test] + fn test_wasm_rsr_metadata() { + assert_eq!(rsr_tier(), "bronze"); + assert_eq!(tpcf_perimeter(), 3); + } +} diff --git a/vendor/bunsenite/stapeln.toml b/vendor/bunsenite/stapeln.toml new file mode 100644 index 0000000..5c7a905 --- /dev/null +++ b/vendor/bunsenite/stapeln.toml @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: MPL-2.0 +# stapeln.toml — Layer-based container build for bunsenite +# +# stapeln builds containers as composable layers (German: "to stack"). +# Each layer is independently cacheable, verifiable, and signable. + +[metadata] +name = "bunsenite" +version = "0.1.0" +description = "bunsenite container service" +author = "Jonathan D.A. Jewell " +license = "MPL-2.0" +registry = "ghcr.io/hyperpolymath" + +[build] +containerfile = "Containerfile" +context = "." +runtime = "podman" + +# ── Layer Definitions ────────────────────────────────────────── + +[layers.base] +description = "Chainguard Wolfi minimal base" +from = "cgr.dev/chainguard/wolfi-base:latest" +cache = true +verify = true + +[layers.rust-toolchain] +description = "Rust compiler and build dependencies" +extends = "base" +packages = ["rust", "pkgconf", "build-base"] +cache = true + +[layers.rust-deps] +description = "Cargo dependency fetch" +extends = "rust-toolchain" +commands = ["cargo fetch --locked"] +cache-key = "Cargo.lock" +cache = true + +[layers.build] +description = "bunsenite Rust compilation" +extends = "rust-deps" +commands = ["cargo build --release"] +artifacts = [ + { src = "target/release/bunsenite", dst = "/app/bunsenite" }, +] + +[layers.runtime] +description = "Minimal runtime" +from = "cgr.dev/chainguard/wolfi-base:latest" +packages = ["ca-certificates", "curl"] +copy-from = [ + { layer = "build", src = "/app/", dst = "/app/" }, +] +entrypoint = ["["bunsenite"]"] +user = "bunsenite" + +# ── Security ─────────────────────────────────────────────────── + +[security] +non-root = true +read-only-root = false +no-new-privileges = true +cap-drop = ["ALL"] +seccomp-profile = "default" + +[security.signing] +algorithm = "ML-DSA-87" +provider = "cerro-torre" + +[security.sbom] +format = "spdx-json" +output = "sbom.spdx.json" +include-deps = true + +# ── Verification ─────────────────────────────────────────────── + +[verify] +vordr = true +svalinn = true +scan-on-build = true +fail-on = ["critical", "high"] + +# ── Targets ──────────────────────────────────────────────────── + +[targets.development] +layers = ["base", "rust-toolchain", "build"] +env = { LOG_LEVEL = "debug" } + +[targets.production] +layers = ["runtime"] +env = { LOG_LEVEL = "info" } + +[targets.test] +layers = ["base", "rust-toolchain", "build"] +env = { LOG_LEVEL = "debug" } diff --git a/vendor/bunsenite/tests/aspect_test.rs b/vendor/bunsenite/tests/aspect_test.rs new file mode 100644 index 0000000..bd8328f --- /dev/null +++ b/vendor/bunsenite/tests/aspect_test.rs @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Aspect tests for Bunsenite — robustness, error handling, and API contracts. +//! +//! Tests cover: +//! - Malformed / corrupt input is rejected gracefully (no panic, proper `Err`). +//! - Extremely large inputs are handled without panic. +//! - All public API entry points accept valid inputs without panicking. +//! - Error types carry expected diagnostic information. +//! - The `validate` path and the `parse` path agree on what is valid. + +use bunsenite::{Error, NickelLoader}; + +// --------------------------------------------------------------------------- +// Helper +// --------------------------------------------------------------------------- + +fn loader() -> NickelLoader { + NickelLoader::new() +} + +// --------------------------------------------------------------------------- +// Aspect: Malformed input is rejected gracefully — no panic +// --------------------------------------------------------------------------- + +/// Completely empty input must not panic. It should either parse (producing +/// an empty result) or return an error; the test only asserts the absence of +/// a panic. +#[test] +fn aspect_empty_input_no_panic() { + let result = loader().parse_string("", "empty.ncl"); + // No assertion on Ok/Err — just verifying no panic. + let _ = result; +} + +/// A string of random punctuation that cannot be valid Nickel must produce an +/// `Err` without panicking. +#[test] +fn aspect_garbage_input_returns_error() { + let result = loader().parse_string("@@@ !! ??? %%% ###", "garbage.ncl"); + assert!( + result.is_err(), + "Garbage input should return an error, not Ok" + ); +} + +/// An unclosed brace is syntactically invalid; the parser must return an error +/// rather than panicking or hanging. +#[test] +fn aspect_unclosed_brace_returns_error() { + let result = loader().parse_string("{ name = \"open\"", "unclosed.ncl"); + assert!(result.is_err(), "Unclosed brace must be rejected"); +} + +/// A record field with no value (`{ foo = }`) is invalid Nickel syntax; the +/// parser must return an error. +#[test] +fn aspect_field_missing_value_returns_error() { + let result = loader().parse_string("{ foo = }", "bad_field.ncl"); + assert!(result.is_err(), "Missing field value must be rejected"); +} + +/// A stray `=` with no surrounding structure is not valid Nickel. +#[test] +fn aspect_lone_equals_returns_error() { + let result = loader().parse_string("=", "lone_eq.ncl"); + assert!(result.is_err(), "A lone '=' must be rejected"); +} + +/// Invalid Unicode escape sequences or non-UTF-8-safe byte patterns must not +/// cause a panic. Using a replacement-character string as a soft approximation. +#[test] +fn aspect_unicode_replacement_no_panic() { + // U+FFFD is the Unicode replacement character — valid UTF-8, but + // unlikely to form valid Nickel syntax. + let weird = "\u{FFFD}\u{FFFD}\u{FFFD}"; + let result = loader().parse_string(weird, "unicode.ncl"); + let _ = result; // Must not panic. +} + +/// A very deeply nested Nickel record should not cause a stack overflow. +/// We use a moderate depth (50 levels) to keep the test fast while still +/// exercising the recursive parser. +#[test] +fn aspect_moderately_deep_nesting_no_panic() { + // Build "{ a = { a = { … = 42 } … } }" with 50 levels. + let depth = 50usize; + let open: String = "{ a = ".repeat(depth); + let mid = "42"; + let close: String = " }".repeat(depth); + let source = format!("{open}{mid}{close}"); + + let result = loader().parse_string(&source, "deep.ncl"); + // Not requiring Ok — some dialects may limit nesting — but must not panic. + let _ = result; +} + +// --------------------------------------------------------------------------- +// Aspect: Large inputs are handled without panic +// --------------------------------------------------------------------------- + +/// A configuration with a large number of fields (1000) must be processed +/// without panicking. Whether it succeeds or fails is secondary; the +/// important guarantee is no crash. +#[test] +fn aspect_large_flat_record_no_panic() { + let fields: String = (0..1000).map(|i| format!(" field_{i} = {i},\n")).collect(); + let source = format!("{{\n{fields}}}"); + let result = loader().parse_string(&source, "large.ncl"); + // Must not panic. Nickel may or may not handle 1000 fields; we only + // require graceful behaviour. + let _ = result; +} + +/// A single string field with a very long value (100 KiB) must not cause a +/// panic. +#[test] +fn aspect_long_string_value_no_panic() { + let long_val = "x".repeat(100_000); + let source = format!(r#"{{ data = "{long_val}" }}"#); + let result = loader().parse_string(&source, "longval.ncl"); + let _ = result; +} + +// --------------------------------------------------------------------------- +// Aspect: Public API accepts valid inputs without panicking +// --------------------------------------------------------------------------- + +/// `NickelLoader::new()` must never panic. +#[test] +fn aspect_loader_construction_never_panics() { + let _l = NickelLoader::new(); +} + +/// `NickelLoader::with_verbose(true)` must not panic. +#[test] +fn aspect_verbose_builder_never_panics() { + let _l = NickelLoader::new().with_verbose(true); + let _l2 = NickelLoader::new().with_verbose(false); +} + +/// `parse` (alias for `parse_string`) must not panic on valid input. +#[test] +fn aspect_parse_alias_no_panic_valid() { + let result = loader().parse(r#"{ ok = true }"#, "alias.ncl"); + assert!(result.is_ok(), "parse() alias must succeed on valid input"); +} + +/// `validate` must not panic on valid or invalid input. +#[test] +fn aspect_validate_no_panic_on_valid() { + let result = loader().validate(r#"{ x = 1 }"#, "valid.ncl"); + assert!(result.is_ok()); +} + +#[test] +fn aspect_validate_no_panic_on_invalid() { + // Should return Err, not panic. + let result = loader().validate("{ broken =", "broken.ncl"); + assert!(result.is_err()); +} + +/// `parse_file` with a nonexistent path must return an `Err`, not panic. +#[test] +fn aspect_parse_file_nonexistent_returns_error() { + let result = loader().parse_file("/nonexistent/path/does_not_exist.ncl"); + assert!( + result.is_err(), + "Nonexistent file path must produce an error" + ); +} + +// --------------------------------------------------------------------------- +// Aspect: Error type invariants +// --------------------------------------------------------------------------- + +/// A `ParseError` produced via the public constructor must be recoverable. +#[test] +fn aspect_parse_error_is_recoverable() { + let err = Error::parse_error("f.ncl", "syntax error"); + assert!( + err.is_recoverable(), + "ParseError should be classified as recoverable" + ); +} + +/// An `Internal` error must be classified as non-recoverable. +#[test] +fn aspect_internal_error_not_recoverable() { + let err = Error::internal("bug"); + assert!( + !err.is_recoverable(), + "Internal error should not be recoverable" + ); +} + +/// The `message()` accessor must return the message that was passed into the +/// constructor without transformation. +#[test] +fn aspect_error_message_accessor_round_trips() { + let msg = "custom diagnostic text"; + let err = Error::parse_error("test.ncl", msg); + assert_eq!( + err.message(), + msg, + "message() should return the original message verbatim" + ); +} + +/// A `SerializationError` must be non-recoverable (it indicates an internal +/// invariant violation, not a user mistake). +#[test] +fn aspect_serialization_error_not_recoverable() { + let err = Error::serialization_error("cannot convert"); + assert!( + !err.is_recoverable(), + "SerializationError should not be recoverable" + ); +} + +// --------------------------------------------------------------------------- +// Aspect: validate / parse agreement +// --------------------------------------------------------------------------- + +/// For every valid Nickel snippet, `validate` succeeding must imply that +/// `parse_string` also succeeds (no divergence between the two code paths +/// for syntactically valid input). +#[test] +fn aspect_validate_implies_parse_for_valid_inputs() { + let valid_inputs = [ + r#"{ a = 1 }"#, + r#"{ name = "test", flag = true }"#, + r#"{ nested = { x = 42 } }"#, + r#"{ items = [1, 2, 3] }"#, + ]; + + let l = loader(); + for src in valid_inputs { + if l.validate(src, "t.ncl").is_ok() { + let parse_result = l.parse_string(src, "t.ncl"); + assert!( + parse_result.is_ok(), + "validate() succeeded but parse_string() failed for: {src}" + ); + } + } +} diff --git a/vendor/bunsenite/tests/e2e_test.rs b/vendor/bunsenite/tests/e2e_test.rs new file mode 100644 index 0000000..d7f165f --- /dev/null +++ b/vendor/bunsenite/tests/e2e_test.rs @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! End-to-end tests for Bunsenite: create → serialize → deserialize → verify round-trip. +//! +//! These tests exercise the full lifecycle of loading a Nickel configuration: +//! starting from a source string or temp file, parsing it via `NickelLoader`, +//! and verifying that the resulting JSON value has the expected shape and +//! content. At least 10 `#[test]` functions are provided. + +use bunsenite::{NickelLoader, NAME, RSR_TIER}; +use std::io::Write as _; +use tempfile::NamedTempFile; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Create a `NickelLoader` with verbose mode disabled (default for E2E tests). +fn loader() -> NickelLoader { + NickelLoader::new() +} + +/// Write `content` to a temporary `.ncl` file and return the handle. +/// The file is deleted when the handle is dropped. +fn temp_ncl(content: &str) -> NamedTempFile { + let mut f = NamedTempFile::new().expect("Failed to create temp file"); + write!(f, "{}", content).expect("Failed to write temp file"); + f +} + +// --------------------------------------------------------------------------- +// E2E 1: String source → JSON round-trip — simple record +// --------------------------------------------------------------------------- + +/// A simple flat record parsed from a string produces a JSON object with the +/// exact keys and scalar values that were declared. +#[test] +fn e2e_string_round_trip_simple_record() { + let source = r#"{ project = "bunsenite", version = "1.0.0", stable = true }"#; + let json = loader() + .parse_string(source, "simple.ncl") + .expect("E2E: simple record must parse"); + + assert_eq!(json["project"], "bunsenite"); + assert_eq!(json["version"], "1.0.0"); + assert_eq!(json["stable"], true); +} + +// --------------------------------------------------------------------------- +// E2E 2: String source → JSON round-trip — numeric fields +// --------------------------------------------------------------------------- + +/// Numeric fields survive the parse → JSON conversion without truncation or +/// type-widening. +#[test] +fn e2e_string_round_trip_numeric_fields() { + let source = r#"{ port = 8080, workers = 4, timeout = 30 }"#; + let json = loader() + .parse_string(source, "numeric.ncl") + .expect("E2E: numeric fields must parse"); + + assert_eq!(json["port"], 8080); + assert_eq!(json["workers"], 4); + assert_eq!(json["timeout"], 30); +} + +// --------------------------------------------------------------------------- +// E2E 3: File source → JSON round-trip +// --------------------------------------------------------------------------- + +/// When the same Nickel source is written to a temporary file and loaded via +/// `parse_file`, the resulting JSON is identical to the string-parsed version. +#[test] +fn e2e_file_round_trip_matches_string() { + let source = r#"{ name = "file-test", enabled = true, count = 7 }"#; + let tmp = temp_ncl(source); + + let from_file = loader() + .parse_file(tmp.path()) + .expect("E2E: file load must succeed"); + let from_string = loader() + .parse_string(source, "ref.ncl") + .expect("E2E: string load must succeed"); + + // Both routes must produce the same JSON structure. + assert_eq!(from_file, from_string); +} + +// --------------------------------------------------------------------------- +// E2E 4: Nested record round-trip +// --------------------------------------------------------------------------- + +/// A nested Nickel record is correctly projected into a nested JSON object. +#[test] +fn e2e_nested_record_round_trip() { + let source = r#" +{ + server = { + host = "localhost", + port = 3000, + }, + database = { + host = "db.example.com", + port = 5432, + }, +} +"#; + let json = loader() + .parse_string(source, "nested.ncl") + .expect("E2E: nested record must parse"); + + assert_eq!(json["server"]["host"], "localhost"); + assert_eq!(json["server"]["port"], 3000); + assert_eq!(json["database"]["host"], "db.example.com"); + assert_eq!(json["database"]["port"], 5432); +} + +// --------------------------------------------------------------------------- +// E2E 5: Array round-trip +// --------------------------------------------------------------------------- + +/// Nickel arrays survive serialisation as JSON arrays with the correct +/// element count and values. +#[test] +fn e2e_array_round_trip() { + let source = r#"{ tags = ["rust", "nickel", "config"], counts = [1, 2, 3] }"#; + let json = loader() + .parse_string(source, "array.ncl") + .expect("E2E: array record must parse"); + + let tags = json["tags"].as_array().expect("tags should be an array"); + assert_eq!(tags.len(), 3); + assert_eq!(tags[0], "rust"); + assert_eq!(tags[1], "nickel"); + assert_eq!(tags[2], "config"); + + let counts = json["counts"] + .as_array() + .expect("counts should be an array"); + assert_eq!(counts.len(), 3); +} + +// --------------------------------------------------------------------------- +// E2E 6: Arithmetic expression evaluated in config +// --------------------------------------------------------------------------- + +/// Nickel supports computed values; the evaluator must reduce the expression +/// before serialisation so that the JSON contains the final numeric result. +#[test] +fn e2e_computed_arithmetic_round_trip() { + let source = r#"{ total = 100 + 200 + 50, ratio = 6 * 7 }"#; + let json = loader() + .parse_string(source, "arith.ncl") + .expect("E2E: arithmetic config must parse"); + + assert_eq!(json["total"], 350); + assert_eq!(json["ratio"], 42); +} + +// --------------------------------------------------------------------------- +// E2E 7: String concatenation evaluated in config +// --------------------------------------------------------------------------- + +/// String concatenation via `++` must be reduced by the evaluator; the JSON +/// value must be the fully concatenated string. +#[test] +fn e2e_string_concat_round_trip() { + let source = r#"{ greeting = "Hello, " ++ "World!", label = "v" ++ "1" ++ "." ++ "0" }"#; + let json = loader() + .parse_string(source, "concat.ncl") + .expect("E2E: string concat config must parse"); + + assert_eq!(json["greeting"], "Hello, World!"); + assert_eq!(json["label"], "v1.0"); +} + +// --------------------------------------------------------------------------- +// E2E 8: Boolean fields are preserved +// --------------------------------------------------------------------------- + +/// Boolean `true` and `false` must survive as JSON booleans (not strings or +/// integers). +#[test] +fn e2e_boolean_fields_round_trip() { + let source = r#"{ on = true, off = false, also_on = true }"#; + let json = loader() + .parse_string(source, "bool.ncl") + .expect("E2E: boolean config must parse"); + + assert_eq!(json["on"], true); + assert_eq!(json["off"], false); + assert_eq!(json["also_on"], true); +} + +// --------------------------------------------------------------------------- +// E2E 9: Multiple independent parse operations on same loader +// --------------------------------------------------------------------------- + +/// The `NickelLoader` is stateless between calls; parsing two different configs +/// in sequence on the same instance must produce independent, correct results. +#[test] +fn e2e_multiple_parses_independent() { + let l = loader(); + + let json_a = l + .parse_string(r#"{ id = 1, name = "alpha" }"#, "a.ncl") + .expect("E2E: config A must parse"); + let json_b = l + .parse_string(r#"{ id = 2, name = "beta" }"#, "b.ncl") + .expect("E2E: config B must parse"); + + // Results must be independent. + assert_eq!(json_a["id"], 1); + assert_eq!(json_a["name"], "alpha"); + assert_eq!(json_b["id"], 2); + assert_eq!(json_b["name"], "beta"); + assert_ne!(json_a, json_b); +} + +// --------------------------------------------------------------------------- +// E2E 10: validate then parse consistency +// --------------------------------------------------------------------------- + +/// For a valid source, `validate` must succeed and `parse_string` must also +/// succeed with a non-null JSON value. +#[test] +fn e2e_validate_then_parse_consistent() { + let source = r#"{ service = "auth", port = 9000, tls = false }"#; + let l = loader(); + + // Validation must succeed first. + l.validate(source, "consistent.ncl") + .expect("E2E: validate must succeed for valid source"); + + // Parsing must also succeed and produce a meaningful value. + let json = l + .parse_string(source, "consistent.ncl") + .expect("E2E: parse must succeed for valid source"); + + assert_eq!(json["service"], "auth"); + assert_eq!(json["port"], 9000); + assert_eq!(json["tls"], false); +} + +// --------------------------------------------------------------------------- +// E2E 11: file parse → JSON key count matches source +// --------------------------------------------------------------------------- + +/// The number of top-level keys in the parsed JSON must match the number of +/// fields declared in the Nickel source record. +#[test] +fn e2e_key_count_matches_source() { + // Source has exactly 5 top-level fields. + let source = r#"{ a = 1, b = 2, c = 3, d = 4, e = 5 }"#; + let json = loader() + .parse_string(source, "keycount.ncl") + .expect("E2E: key-count config must parse"); + + let obj = json.as_object().expect("JSON must be an object"); + assert_eq!(obj.len(), 5, "Exactly 5 keys expected"); +} + +// --------------------------------------------------------------------------- +// E2E 12: library constants are accessible through public API +// --------------------------------------------------------------------------- + +/// The public constants exported by the library (`NAME`, `RSR_TIER`) must +/// match the expected values at runtime, completing the "end-to-end" view +/// that the crate identity survives compilation. +#[test] +fn e2e_library_constants_reachable() { + assert_eq!(NAME, "bunsenite"); + assert_eq!(RSR_TIER, "bronze"); +} diff --git a/vendor/bunsenite/tests/integration_test.rs b/vendor/bunsenite/tests/integration_test.rs new file mode 100644 index 0000000..284a325 --- /dev/null +++ b/vendor/bunsenite/tests/integration_test.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Integration tests for Bunsenite — Nickel configuration parser +//! +//! These tests exercise the public API of the bunsenite crate, verifying +//! that configuration parsing, error handling, and metadata constants +//! behave correctly from an external consumer's perspective. + +use bunsenite::{Error, NickelLoader, NAME, RSR_TIER, TPCF_PERIMETER, VERSION}; + +/// Verify that NickelLoader can be constructed with default settings. +#[test] +fn test_loader_construction() { + let loader = NickelLoader::new(); + // Verbose defaults to false — just ensure construction succeeds + let _debug_repr = format!("{:?}", loader); +} + +/// Verify that NickelLoader supports the builder pattern for verbose mode. +#[test] +fn test_loader_verbose_builder() { + let loader = NickelLoader::new().with_verbose(true); + let debug_repr = format!("{:?}", loader); + assert!(debug_repr.contains("true"), "verbose should be enabled"); +} + +/// Verify that a simple valid Nickel record parses to JSON containing +/// the expected key-value pairs. +#[test] +fn test_parse_simple_record() { + let loader = NickelLoader::new(); + let input = r#"{ name = "bunsenite", version = "1.0.0" }"#; + let result = loader.parse_string(input, "simple.ncl"); + assert!( + result.is_ok(), + "Simple record should parse: {:?}", + result.err() + ); + let json = result.unwrap(); + assert!(json.to_string().contains("bunsenite")); + assert!(json.to_string().contains("1.0.0")); +} + +/// Verify that numeric values are preserved through parsing. +#[test] +fn test_parse_numeric_values() { + let loader = NickelLoader::new(); + let input = r#"{ port = 8080, retries = 3 }"#; + let result = loader.parse_string(input, "numeric.ncl"); + assert!( + result.is_ok(), + "Numeric record should parse: {:?}", + result.err() + ); + let json = result.unwrap(); + let text = json.to_string(); + assert!(text.contains("8080"), "Should contain port value"); + assert!(text.contains("3"), "Should contain retries value"); +} + +/// Verify that boolean values round-trip correctly. +#[test] +fn test_parse_boolean_values() { + let loader = NickelLoader::new(); + let input = r#"{ enabled = true, debug = false }"#; + let result = loader.parse_string(input, "bool.ncl"); + assert!( + result.is_ok(), + "Boolean record should parse: {:?}", + result.err() + ); +} + +/// Verify that empty records parse without error. +#[test] +fn test_parse_empty_record() { + let loader = NickelLoader::new(); + let input = "{}"; + let result = loader.parse_string(input, "empty.ncl"); + assert!( + result.is_ok(), + "Empty record should parse: {:?}", + result.err() + ); +} + +/// Verify that invalid Nickel syntax produces an error, not a panic. +#[test] +fn test_parse_invalid_syntax_returns_error() { + let loader = NickelLoader::new(); + let input = "this is not valid nickel @@@"; + let result = loader.parse_string(input, "invalid.ncl"); + assert!(result.is_err(), "Invalid syntax should produce an error"); +} + +/// Verify that the Error type correctly classifies recoverable errors. +#[test] +fn test_error_recoverability() { + let parse_err = Error::parse_error("test.ncl", "bad syntax"); + assert!(parse_err.is_recoverable(), "Parse errors are recoverable"); + + let internal_err = Error::internal("unexpected state"); + assert!( + !internal_err.is_recoverable(), + "Internal errors are not recoverable" + ); +} + +/// Verify crate metadata constants are correctly populated. +#[test] +fn test_crate_metadata() { + assert_eq!(NAME, "bunsenite"); + assert_eq!(RSR_TIER, "bronze"); + assert_eq!(TPCF_PERIMETER, 3); + // VERSION should be semver + let parts: Vec<&str> = VERSION.split('.').collect(); + assert_eq!(parts.len(), 3, "VERSION should be semver x.y.z"); +} diff --git a/vendor/bunsenite/tests/property_test.rs b/vendor/bunsenite/tests/property_test.rs new file mode 100644 index 0000000..4fc99f6 --- /dev/null +++ b/vendor/bunsenite/tests/property_test.rs @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! Property-based tests for Bunsenite (no external proptest crate required). +//! +//! Rather than using a generative property-test framework, these tests exercise +//! a fixed corpus of 10 varied data inputs and assert algebraic invariants: +//! +//! - **Round-trip**: `parse_string(x)` produces a JSON value; the same source +//! parsed again produces an equal value (determinism / idempotency of the +//! parse step). +//! - **Type invariants**: booleans remain booleans, numbers remain numbers, +//! strings remain strings, arrays remain arrays after parse. +//! - **No information loss**: all top-level keys declared in the source are +//! present in the JSON output. + +use bunsenite::NickelLoader; + +// --------------------------------------------------------------------------- +// Corpus: 10 varied Nickel configuration inputs +// --------------------------------------------------------------------------- + +/// Each entry is `(label, nickel_source)`. Entries cover: +/// 0. Minimal single field (string) +/// 1. Multiple scalars of different types +/// 2. Nested record +/// 3. Array of strings +/// 4. Array of integers +/// 5. Deep nesting (3 levels) +/// 6. Computed arithmetic +/// 7. String concatenation +/// 8. Mixed scalars + nested +/// 9. Boolean-only record +const CORPUS: &[(&str, &str)] = &[ + // 0 — single string field + ("single_string", r#"{ greeting = "hello" }"#), + // 1 — three scalar types + ( + "multi_scalar", + r#"{ name = "cfg", count = 42, active = true }"#, + ), + // 2 — nested record (one level) + ("nested_one", r#"{ outer = { inner = "value", num = 7 } }"#), + // 3 — array of strings + ("array_strings", r#"{ tags = ["a", "b", "c", "d"] }"#), + // 4 — array of integers + ("array_ints", r#"{ nums = [10, 20, 30] }"#), + // 5 — three-level nesting + ("deep_nest", r#"{ l1 = { l2 = { l3 = "leaf" } } }"#), + // 6 — computed arithmetic + ("computed", r#"{ x = 3 * 4, y = 100 - 1 }"#), + // 7 — string concatenation + ("concat", r#"{ s = "foo" ++ "bar" ++ "baz" }"#), + // 8 — mixed scalars and nested + ( + "mixed", + r#"{ host = "localhost", port = 5432, opts = { ssl = true } }"#, + ), + // 9 — booleans only + ("booleans", r#"{ yes = true, no = false }"#), +]; + +// --------------------------------------------------------------------------- +// Helper +// --------------------------------------------------------------------------- + +fn loader() -> NickelLoader { + NickelLoader::new() +} + +// --------------------------------------------------------------------------- +// Property 1: Every corpus entry parses without error +// --------------------------------------------------------------------------- + +/// All 10 corpus inputs must parse successfully: none of them should return +/// an `Err` variant. +#[test] +fn property_all_corpus_entries_parse() { + let l = loader(); + for (label, source) in CORPUS { + let result = l.parse_string(source, &format!("{label}.ncl")); + assert!( + result.is_ok(), + "Corpus entry '{label}' failed to parse: {:?}", + result.err() + ); + } +} + +// --------------------------------------------------------------------------- +// Property 2: Parsing the same input twice yields equal results (determinism) +// --------------------------------------------------------------------------- + +/// For each corpus entry, parsing it twice on the same loader must produce +/// two `serde_json::Value` instances that are equal. This verifies that +/// the parse → evaluate pipeline is deterministic (no random seeds, no +/// mutable shared state). +#[test] +fn property_parse_is_deterministic() { + let l = loader(); + for (label, source) in CORPUS { + let name = format!("{label}.ncl"); + let first = l + .parse_string(source, &name) + .unwrap_or_else(|e| panic!("First parse of '{label}' failed: {e}")); + let second = l + .parse_string(source, &name) + .unwrap_or_else(|e| panic!("Second parse of '{label}' failed: {e}")); + assert_eq!( + first, second, + "Parse of '{label}' is not deterministic: got different results" + ); + } +} + +// --------------------------------------------------------------------------- +// Property 3: Parsed JSON is always a JSON object (not null, array, scalar) +// --------------------------------------------------------------------------- + +/// Every corpus input is a Nickel record (`{ … }`). After evaluation the +/// result must be a JSON object, not `null`, a bare array, or a bare scalar. +#[test] +fn property_result_is_always_object() { + let l = loader(); + for (label, source) in CORPUS { + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + assert!( + json.is_object(), + "Corpus entry '{label}' did not produce a JSON object; got: {json:?}" + ); + } +} + +// --------------------------------------------------------------------------- +// Property 4: Boolean values remain boolean in JSON +// --------------------------------------------------------------------------- + +/// Corpus entry 9 (`booleans`) has two boolean fields. After parsing they +/// must be of JSON boolean type — not converted to strings or integers. +#[test] +fn property_booleans_stay_boolean() { + let l = loader(); + let (label, source) = CORPUS[9]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert!( + json["yes"].is_boolean(), + "'yes' field should be boolean, got: {:?}", + json["yes"] + ); + assert!( + json["no"].is_boolean(), + "'no' field should be boolean, got: {:?}", + json["no"] + ); + assert_eq!(json["yes"], true); + assert_eq!(json["no"], false); +} + +// --------------------------------------------------------------------------- +// Property 5: Numeric values remain numeric in JSON +// --------------------------------------------------------------------------- + +/// Corpus entry 1 has an integer field `count = 42`. After parsing it must +/// be a JSON number, not a string. +#[test] +fn property_numbers_stay_numeric() { + let l = loader(); + let (label, source) = CORPUS[1]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert!( + json["count"].is_number(), + "'count' should be numeric, got: {:?}", + json["count"] + ); + assert_eq!(json["count"], 42); +} + +// --------------------------------------------------------------------------- +// Property 6: String values remain strings in JSON +// --------------------------------------------------------------------------- + +/// Corpus entry 0 has `greeting = "hello"`. After parsing the field must be +/// a JSON string. +#[test] +fn property_strings_stay_strings() { + let l = loader(); + let (label, source) = CORPUS[0]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert!( + json["greeting"].is_string(), + "'greeting' should be a string, got: {:?}", + json["greeting"] + ); + assert_eq!(json["greeting"], "hello"); +} + +// --------------------------------------------------------------------------- +// Property 7: Arrays remain arrays in JSON +// --------------------------------------------------------------------------- + +/// Corpus entry 3 has `tags = ["a", "b", "c", "d"]`. After parsing the +/// field must be a JSON array with the correct length. +#[test] +fn property_arrays_stay_arrays() { + let l = loader(); + let (label, source) = CORPUS[3]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + let arr = json["tags"] + .as_array() + .unwrap_or_else(|| panic!("'tags' should be an array, got: {:?}", json["tags"])); + assert_eq!(arr.len(), 4, "Array should have 4 elements"); +} + +// --------------------------------------------------------------------------- +// Property 8: Computed expressions collapse to their expected values +// --------------------------------------------------------------------------- + +/// Corpus entry 6 (`computed`) contains `x = 3 * 4` and `y = 100 - 1`. +/// After evaluation these must be the integers `12` and `99` respectively. +#[test] +fn property_arithmetic_is_fully_evaluated() { + let l = loader(); + let (label, source) = CORPUS[6]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert_eq!(json["x"], 12, "'x' should be 3*4=12"); + assert_eq!(json["y"], 99, "'y' should be 100-1=99"); +} + +// --------------------------------------------------------------------------- +// Property 9: String concatenation is fully evaluated +// --------------------------------------------------------------------------- + +/// Corpus entry 7 (`concat`) has `s = "foo" ++ "bar" ++ "baz"`. After +/// evaluation `s` must be the single string `"foobarbaz"`. +#[test] +fn property_concat_is_fully_evaluated() { + let l = loader(); + let (label, source) = CORPUS[7]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert_eq!(json["s"], "foobarbaz"); +} + +// --------------------------------------------------------------------------- +// Property 10: Deep nesting is preserved at all levels +// --------------------------------------------------------------------------- + +/// Corpus entry 5 (`deep_nest`) declares three nesting levels. Each level +/// must be accessible as a JSON object, and the leaf value must be the +/// declared string. +#[test] +fn property_deep_nesting_preserved() { + let l = loader(); + let (label, source) = CORPUS[5]; + let json = l + .parse_string(source, &format!("{label}.ncl")) + .unwrap_or_else(|e| panic!("Parse of '{label}' failed: {e}")); + + assert!(json["l1"].is_object(), "l1 should be an object"); + assert!(json["l1"]["l2"].is_object(), "l1.l2 should be an object"); + assert_eq!(json["l1"]["l2"]["l3"], "leaf", "l1.l2.l3 should be 'leaf'"); +} diff --git a/vendor/bunsenite/validate-nickel-configs.k9.ncl b/vendor/bunsenite/validate-nickel-configs.k9.ncl new file mode 100644 index 0000000..e6a4c23 --- /dev/null +++ b/vendor/bunsenite/validate-nickel-configs.k9.ncl @@ -0,0 +1,148 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Bunsenite Configuration Validator +# +# This K9 component validates Nickel configuration files used in the Bunsenite +# project itself - dogfooding! It checks test fixtures, example configs, and +# ensures they conform to expected schemas. + +leash = 'Yard # Nickel evaluation only, no side effects + +pedigree = { + schema_version = "1.0.0", + component_type = "config-validator", + author = "Jonathan D.A. Jewell ", + description = "Validates Nickel configurations in Bunsenite test suite", + created = "2026-01-30", + k9_spec_version = "1.0.0", +} + +# Configuration paths and validation rules +config = { + # Directories containing Nickel files to validate + config_dirs | Array String = [ + "tests/fixtures", + "examples", + "benches/configs", + ], + + # Expected schemas for different config types + schemas = { + # Example application config schema + app_config = { + name | String, + version | String + | std.string.is_match "^[0-9]+\\.[0-9]+\\.[0-9]+$", + port | Number + | std.number.is_nat + | std.number.between 1024 65535, + features | Array String | default = [], + metadata | { _ : Dyn } | default = {}, + }, + + # Benchmark configuration schema + benchmark_config = { + name | String, + iterations | Number + | std.number.is_nat + | std.number.greater_or_eq 1, + input_size | Number | std.number.is_nat, + warmup_iterations | Number + | std.number.is_nat + | default = 10, + }, + + # Test fixture schema + test_fixture = { + description | String, + input | String, + expected_output | Dyn, + should_fail | Bool | default = false, + }, + }, + + # Validation rules for all configs + validation_rules = { + # All configs must be valid Nickel + valid_nickel = true, + + # No empty string values + no_empty_strings = true, + + # Port numbers must be in valid range + valid_ports = true, + + # Version strings must follow semver + semver_versions = true, + }, +} + +# Validation contracts +validation = { + # Example: Validate app config structure + validate_app_config = fun config_value => + let is_valid_name = std.string.length config_value.name > 0 in + let is_valid_version = + std.string.is_match "^[0-9]+\\.[0-9]+\\.[0-9]+" config_value.version + in + let is_valid_port = + config_value.port >= 1024 && config_value.port <= 65535 + in + is_valid_name && is_valid_version && is_valid_port + | doc "App config must have non-empty name, semver version, and valid port", + + # Check that all test fixtures have required fields + validate_test_fixture = fun fixture => + let has_description = std.string.length fixture.description > 0 in + let has_input = std.string.length fixture.input > 0 in + has_description && has_input + | doc "Test fixture must have description and input", + + # Validate benchmark config + validate_benchmark = fun bench => + let has_positive_iterations = bench.iterations > 0 in + let has_positive_input_size = bench.input_size > 0 in + has_positive_iterations && has_positive_input_size + | doc "Benchmark must have positive iterations and input_size", +} + +# Example validated configurations (used in tests) +examples = { + valid_app_config = { + name = "bunsenite-example", + version = "1.2.3", + port = 8080, + features = ["wasm", "ffi-bindings"], + metadata = { + author = "Jonathan D.A. Jewell", + license = "MPL-2.0", + }, + } | config.schemas.app_config, + + valid_benchmark = { + name = "parse_large_config", + iterations = 1000, + input_size = 10000, + warmup_iterations = 50, + } | config.schemas.benchmark_config, + + valid_test_fixture = { + description = "Parse simple object", + input = "{ name = \"test\", value = 42 }", + expected_output = { name = "test", value = 42 }, + should_fail = false, + } | config.schemas.test_fixture, +} + +# Dogfooding note: This K9 component validates Nickel configs using Nickel +# itself, demonstrating Bunsenite's purpose - parsing and validating Nickel! +metadata = { + dogfooding_note = "This K9 component is itself a Nickel configuration that validates other Nickel configurations, demonstrating the circular nature of configuration validation.", + + usage_example = " + # In Bunsenite tests, import this K9 component: + let k9_validator = import \"validate-nickel-configs.k9.ncl\" in + let my_config = { name = \"test\", version = \"1.0.0\", port = 3000 } in + k9_validator.validation.validate_app_config my_config + ", +} diff --git a/vendor/bunsenite/www/.well-known/ai.txt b/vendor/bunsenite/www/.well-known/ai.txt new file mode 100644 index 0000000..4dfb3d2 --- /dev/null +++ b/vendor/bunsenite/www/.well-known/ai.txt @@ -0,0 +1,140 @@ +# Bunsenite AI Training and Usage Policy +# https://site.spawning.ai/ai-txt +# This file declares how AI systems may interact with this repository + +# AI Training Permission +# ====================== +# We ALLOW AI training on this codebase under the following terms: + +ai-training: allowed + +# Conditions for AI Training +# --------------------------- +# 1. Attribution: Any AI model trained on this code should acknowledge +# the source in model documentation or training data listings. +# +# 2. License Compliance: Training data usage must comply with our dual +# MIT + Palimpsest License. Both licenses permit AI training. +# +# 3. Reversibility: If the AI generates code derived from this codebase, +# users should be informed of the source for traceability. +# +# 4. Ethical Use: AI systems trained on this code should not be used for: +# - Malicious purposes (malware, exploits) +# - Surveillance or oppression +# - Discriminatory applications +# - Weaponization + +# AI Code Generation +# ================== +# We ALLOW AI systems to generate code suggestions based on this codebase. + +ai-code-generation: allowed + +# Conditions for Code Generation +# ------------------------------- +# 1. License Notice: Generated code should include appropriate license +# attribution if substantial portions are derived from Bunsenite. +# +# 2. Quality: AI-generated code should maintain the quality standards +# documented in CONTRIBUTING.md (type safety, memory safety, tests). +# +# 3. Security: AI should not suggest unsafe code patterns, especially +# given our #![deny(unsafe_code)] policy. +# +# 4. Context: AI assistants should reference CLAUDE.md for project +# context and conventions when generating code. + +# AI Assisted Development +# ======================== +# We ENCOURAGE AI-assisted development with these guidelines: + +ai-assisted-development: encouraged + +# Guidelines for AI Assistants +# ----------------------------- +# 1. Read CLAUDE.md First: This file contains comprehensive project +# context, conventions, and critical design decisions. +# +# 2. Respect Technology Choices: +# - YES: Rust core, Zig FFI, Deno, Rescript, WASM +# - NO: Plain TypeScript, shell scripts, unsafe code +# +# 3. Follow Standards: +# - RSR Bronze Tier compliance +# - TPCF Perimeter 3 (Community Sandbox) +# - Conventional Commits +# - Rust API Guidelines +# +# 4. Maintain Safety: +# - No unsafe blocks +# - Comprehensive error handling (Result types) +# - Tests for all new code +# +# 5. Preserve Reversibility: +# - Clear commit messages +# - Documented rationale for changes +# - Git history integrity + +# AI Research +# =========== +# We ALLOW research on this codebase, including: + +ai-research: allowed + +# Research Applications +# ---------------------- +# - Code analysis and understanding +# - Bug detection and security analysis +# - Performance optimization suggestions +# - Documentation generation and improvement +# - Test generation and coverage analysis +# - Refactoring suggestions +# - Architecture analysis +# - Dependency analysis + +# Data Mining and Scraping +# ========================= +# We ALLOW responsible data mining and scraping: + +web-scraping: allowed + +# Conditions for Scraping +# ------------------------ +# 1. Respect Rate Limits: Don't overwhelm GitLab infrastructure +# 2. Attribution: Acknowledge the source in publications/datasets +# 3. License Compliance: Respect dual MIT + Palimpsest licensing +# 4. Ethical Use: No malicious or discriminatory applications + +# Contact Information +# =================== +# For questions about AI usage of this codebase: + +contact: https://github.com/hyperpolymath/bunsenite/issues +contact: https://gitlab.com/hyperpolymath/bunsenite/-/issues + +# Additional Resources +# ==================== +# - Project: https://github.com/hyperpolymath/bunsenite +# - License: LICENSE (dual MIT + Palimpsest 0.8) +# - AI Guide: CLAUDE.md +# - Contributing: CONTRIBUTING.md +# - Security: SECURITY.md + +# Version and Expiration +# ======================= +version: 1.0.0 +last-updated: 2025-12-18 +review-date: 2026-12-18 + +# Notes +# ===== +# This policy reflects our values of: +# - Openness: Share knowledge freely +# - Reversibility: Enable traceability and learning +# - Emotional Safety: Reduce anxiety through clear permissions +# - Political Autonomy: Communities control their own technical destiny +# +# We believe AI can amplify human creativity when used ethically and +# transparently. This policy aims to maximize benefit while preserving +# our community values. diff --git a/vendor/bunsenite/www/.well-known/dc.xml b/vendor/bunsenite/www/.well-known/dc.xml new file mode 100644 index 0000000..7cb1186 --- /dev/null +++ b/vendor/bunsenite/www/.well-known/dc.xml @@ -0,0 +1,23 @@ + + + bunsenite + Jonathan D.A. Jewell + software-development + RSR + Rhodium Standard + Chemical process simulation and lab automation + Rhodium Standard + Jonathan D.A. Jewell + 2025 + Software + application/octet-stream + https://github.com/hyperpolymath/bunsenite + https://github.com/hyperpolymath/bunsenite + en + https://rhodium.sh + AGPL-3.0-or-later OR LicenseRef-Palimpsest-0.5 + https://spdx.org/licenses/AGPL-3.0-or-later.html + diff --git a/vendor/bunsenite/www/.well-known/humans.txt b/vendor/bunsenite/www/.well-known/humans.txt new file mode 100644 index 0000000..72205d9 --- /dev/null +++ b/vendor/bunsenite/www/.well-known/humans.txt @@ -0,0 +1,205 @@ +# humanstxt.org +# The humans responsible for building Bunsenite + +/* TEAM */ + +Project Lead & Founder: Campaign for Cooler Coding and Programming +Location: Worldwide +GitHub: @hyperpolymath +GitLab: @hyperpolymath + +/* THANKS */ + +Nickel Language Team + For creating an excellent configuration language + https://github.com/tweag/nickel + +RSR Framework Contributors + For defining rigorous repository standards + +TPCF Community + For the Tri-Perimeter Contribution Framework + +Rust Community + For an incredible language and ecosystem + +Early Adopters & Testers + Your feedback makes this project better + +All Contributors (Perimeter 3) + Every contribution matters, thank you! + +/* SITE */ + +Last updated: 2025-12-18 +Standards: RSR Bronze Tier, TPCF Perimeter 3 +Languages: Rust, Zig, TypeScript (Deno), Rescript, WebAssembly +Doctype: GitLab Repository +IDE: Your choice! We support all editors + +/* VALUES */ + +Reversibility: All changes tracked, experimentation encouraged +Emotional Safety: Mistakes are learning opportunities +Political Autonomy: Communities control their technical destiny +Type Safety: Compile-time guarantees via Rust +Memory Safety: Ownership model, zero unsafe code +Offline-First: Works air-gapped, no network dependencies + +/* LICENSE */ + +Dual MIT + Palimpsest License v0.8 +Choose whichever works best for your use case! + +MIT: Maximum permissiveness +Palimpsest: Adds reversibility, emotional safety, political autonomy + +See LICENSE file for full terms + +/* PROJECT */ + +Name: Bunsenite +Version: 1.0.2 +Description: Nickel configuration file parser with multi-language FFI bindings +Repository: https://github.com/hyperpolymath/bunsenite +Homepage: https://github.com/hyperpolymath/bunsenite +Issues: https://github.com/hyperpolymath/bunsenite/issues +CI/CD: GitHub Actions + GitLab CI +Package: crates.io/crates/bunsenite + +/* ARCHITECTURE */ + +Core: Rust (nickel-lang-core 0.9.1) +FFI Layer: Zig C ABI (stable interface) +Bindings: Deno, Rescript, WebAssembly +CLI: Command-line interface +WASM: Browser deployment (~95% native speed) +Tests: 30+ tests, 100% pass rate + +/* TECHNOLOGY */ + +Rust (2021 edition) + Zero unsafe code + Strong typing + Memory safety via ownership + +nickel-lang-core 0.9.1 + Configuration language + Type checking + Evaluation + +Zig + C ABI layer + FFI stability + +wasm-bindgen + Browser deployment + Universal compatibility + +Deno + TypeScript runtime + Native FFI + +Rescript + Type-safe JavaScript + C FFI support + +/* BUILD TOOLS */ + +Cargo: Rust package manager +just: Command runner (Justfile) +Guix: Reproducible builds (flake.guix) +GitLab CI: Continuous integration +wasm-pack: WebAssembly builds (optional) + +/* STANDARDS COMPLIANCE */ + +RSR Framework: Bronze Tier + ✓ Type safety (Rust compiler) + ✓ Memory safety (ownership, no unsafe) + ✓ Offline-first (no network deps) + ✓ Complete documentation + ✓ .well-known/ directory + ✓ Build system (Justfile, Guix) + ✓ CI/CD pipeline + ✓ 100% test pass rate + +TPCF: Perimeter 3 (Community Sandbox) + Open to all contributors + Graduated trust model + Reversibility guaranteed + +/* CONTRIBUTING */ + +We welcome contributions! + +Perimeter 1: Core maintainers +Perimeter 2: Trusted contributors +Perimeter 3: Community sandbox (you are here!) + +See CONTRIBUTING.md for: + - Development workflow + - Coding standards + - Testing requirements + - Commit conventions + - PR process + +See CODE_OF_CONDUCT.md for community guidelines + +/* SECURITY */ + +Zero unsafe code: Enforced by compiler +Memory safety: Rust ownership model +Type safety: Compile-time guarantees +No network deps: Offline-first design +Regular audits: cargo audit in CI + +Report vulnerabilities: + GitHub: https://github.com/hyperpolymath/bunsenite/security/advisories/new + GitLab: Confidential issue + Response: Within 48 hours + +See SECURITY.md for full policy + +/* CONTACT */ + +General: https://github.com/hyperpolymath/bunsenite/issues +Security: https://github.com/hyperpolymath/bunsenite/security/advisories/new +GitHub: https://github.com/hyperpolymath +GitLab: https://gitlab.com/hyperpolymath + +/* ATTRIBUTION */ + +This project stands on the shoulders of giants. + +Thank you to everyone who has contributed to: + - Rust language and ecosystem + - Nickel configuration language + - Open source software movement + - Free/libre software philosophy + - Standards and best practices + - Educational resources + - Community support and mentorship + +We are part of a larger movement toward: + - Safer software (memory & type safety) + - Reversible development (Git, version control) + - Emotional safety (anxiety reduction, experimentation) + - Political autonomy (community sovereignty) + - Ethical technology (transparency, consent) + +/* PHILOSOPHY */ + +"Software should be: + - Safe (memory & type safety) + - Traceable (reversibility) + - Empowering (emotional safety) + - Autonomous (political freedom) + - Collaborative (community-driven)" + +- Campaign for Cooler Coding and Programming + +/* END */ + +Made with care by humans, for humans. +Politically autonomous software for emotionally safe development. diff --git a/vendor/bunsenite/www/.well-known/security.txt b/vendor/bunsenite/www/.well-known/security.txt new file mode 100644 index 0000000..dd6ae64 --- /dev/null +++ b/vendor/bunsenite/www/.well-known/security.txt @@ -0,0 +1,31 @@ +# Bunsenite Security Contact Information +# RFC 9116 Compliant security.txt file +# https://securitytxt.org/ + +Contact: https://github.com/hyperpolymath/bunsenite/security/advisories/new +Contact: https://gitlab.com/hyperpolymath/bunsenite/-/issues/new?issuable_template=security +Expires: 2026-12-18T00:00:00.000Z +Preferred-Languages: en +Canonical: https://github.com/hyperpolymath/bunsenite/blob/main/.well-known/security.txt +Policy: https://github.com/hyperpolymath/bunsenite/blob/main/SECURITY.md +Acknowledgments: https://github.com/hyperpolymath/bunsenite/blob/main/SECURITY.md#attribution + +# Vulnerability Disclosure Program +# +# We take security seriously. Please report security vulnerabilities +# via GitHub Security Advisories (preferred) or confidential GitLab issue. +# +# Response Timeline: +# - Initial response: Within 48 hours +# - Triage: Within 1 week +# - Fix: Depends on severity (critical: days, low: weeks) +# - Public disclosure: Coordinated, typically 90 days after fix +# +# Security Guarantees: +# - Zero unsafe code (#![deny(unsafe_code)]) +# - Rust memory safety (ownership model) +# - Type safety (compile-time guarantees) +# - No network dependencies (offline-first) +# - Minimal attack surface +# +# For more information, see: SECURITY.md diff --git a/vendor/bunsenite/zig/README.adoc b/vendor/bunsenite/zig/README.adoc new file mode 100644 index 0000000..63b7f35 --- /dev/null +++ b/vendor/bunsenite/zig/README.adoc @@ -0,0 +1,90 @@ +== Bunsenite Zig FFI Layer + +image:https://img.shields.io/badge/License-MPL–2.0-blue.svg[License: +MPL-2.0,link="`https://github.com/hyperpolymath/palimpsest-license`"] + +This directory contains the Zig wrapper that provides a stable C ABI for +the Rust core library. + +=== Purpose + +The Zig layer isolates consumers (Deno, AffineScript) from Rust ABI +changes across compiler versions, providing: + +* *Stable C ABI*: Guaranteed binary compatibility +* *Cross-platform*: Builds for Linux, macOS, Windows +* *Small overhead*: Thin wrapper, minimal performance impact + +=== Architecture + +.... +Deno/AffineScript → Zig (stable C ABI) → Rust (native) +.... + +=== Prerequisites + +[arabic] +. *Rust toolchain*: `+rustup install stable+` +. *Zig compiler*: `+zig version+` (0.11.0 or later recommended) + +=== Building + +[source,bash] +---- +# Build Rust library first +cargo build --release + +# Build Zig FFI layer +cd zig +zig build -Doptimize=ReleaseFast +---- + +Output libraries: - Linux: `+zig-out/lib/libbunsenite.so+` - macOS: +`+zig-out/lib/libbunsenite.dylib+` - Windows: +`+zig-out/lib/bunsenite.dll+` + +=== Exported Symbols + +[width="100%",cols="21%,28%,21%,30%",options="header",] +|=== +|Symbol |Parameters |Returns |Description +|`+parse_nickel+` |`+(source, name)+` |`+char*+` |Parse Nickel to JSON + +|`+validate_nickel+` |`+(source, name)+` |`+int+` |Validate config +(0=ok) + +|`+free_string+` |`+(ptr)+` |`+void+` |Free allocated string + +|`+version+` |`+()+` |`+char*+` |Library version + +|`+rsr_tier+` |`+()+` |`+char*+` |RSR compliance tier + +|`+tpcf_perimeter+` |`+()+` |`+u8+` |TPCF perimeter number +|=== + +=== Testing + +[source,bash] +---- +# Run Zig tests (requires Rust library) +cargo build --release +cd zig && zig build test +---- + +=== Integration + +==== Deno + +The Zig library is used by `+bindings/deno/bunsenite.ts+` via +`+Deno.dlopen()+`. + +==== AffineScript + +The Zig library is used by `+bindings/affinescript/Bunsenite.res+` via C +FFI. + +=== RSR Compliance + +This FFI layer maintains RSR Bronze tier compliance: - Type safety +through Zig’s type system - Memory safety with explicit +allocation/deallocation - No network dependencies diff --git a/vendor/bunsenite/zig/build.zig b/vendor/bunsenite/zig/build.zig new file mode 100644 index 0000000..0e63ae1 --- /dev/null +++ b/vendor/bunsenite/zig/build.zig @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// Bunsenite Zig Build Configuration +// +// Build the Zig FFI layer as a shared library that wraps the Rust core. +// +// Prerequisites: +// 1. Build Rust library first: cargo build --release +// 2. Then build Zig layer: zig build -Doptimize=ReleaseFast +// +// Output: +// zig-out/lib/libbunsenite.so (Linux) +// zig-out/lib/libbunsenite.dylib (macOS) +// zig-out/lib/bunsenite.dll (Windows) + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Create shared library + const lib = b.addSharedLibrary(.{ + .name = "bunsenite", + .root_source_file = b.path("bunsenite.zig"), + .target = target, + .optimize = optimize, + }); + + // Link to Rust library + // The Rust cdylib is built with: cargo build --release + lib.addLibraryPath(b.path("../target/release")); + lib.linkSystemLibrary("bunsenite"); + + // Link libc for C runtime + lib.linkLibC(); + + // Install the library + b.installArtifact(lib); + + // Create test step + const lib_unit_tests = b.addTest(.{ + .root_source_file = b.path("bunsenite.zig"), + .target = target, + .optimize = optimize, + }); + + lib_unit_tests.addLibraryPath(b.path("../target/release")); + lib_unit_tests.linkSystemLibrary("bunsenite"); + lib_unit_tests.linkLibC(); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); +} diff --git a/vendor/bunsenite/zig/bunsenite.zig b/vendor/bunsenite/zig/bunsenite.zig new file mode 100644 index 0000000..b261ee8 --- /dev/null +++ b/vendor/bunsenite/zig/bunsenite.zig @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +// Bunsenite Zig FFI Layer +// +// This module provides a stable C ABI wrapper around the Rust core library. +// It isolates consumers (Deno, ReScript) from Rust ABI changes across versions. +// +// Architecture: +// Deno/ReScript → Zig (stable C ABI) → Rust (native) +// +// Build: +// zig build -Doptimize=ReleaseFast +// +// The resulting shared library exports these symbols: +// - parse_nickel(source, name) -> char* +// - validate_nickel(source, name) -> int +// - free_string(ptr) -> void +// - version() -> char* +// - rsr_tier() -> char* +// - tpcf_perimeter() -> u8 + +const std = @import("std"); + +// Import Rust FFI functions via C ABI +// These are defined in src/ffi.rs with #[no_mangle] pub extern "C" +extern fn bunsenite_parse(source: [*:0]const u8, name: [*:0]const u8) callconv(.C) ?[*:0]u8; +extern fn bunsenite_validate(source: [*:0]const u8, name: [*:0]const u8) callconv(.C) i32; +extern fn bunsenite_free_string(ptr: ?[*:0]u8) callconv(.C) void; +extern fn bunsenite_version() callconv(.C) [*:0]const u8; +extern fn bunsenite_rsr_tier() callconv(.C) [*:0]const u8; +extern fn bunsenite_tpcf_perimeter() callconv(.C) u8; + +// Re-export with stable, consumer-friendly names +// These match the symbols expected by bindings/deno/bunsenite.ts + +/// Parse a Nickel configuration string and return JSON +/// +/// Parameters: +/// source: Null-terminated Nickel source code +/// name: Null-terminated filename (for error messages) +/// +/// Returns: +/// Pointer to JSON string on success, null on failure +/// MUST be freed with free_string() +pub export fn parse_nickel(source: [*:0]const u8, name: [*:0]const u8) callconv(.C) ?[*:0]u8 { + return bunsenite_parse(source, name); +} + +/// Validate a Nickel configuration without evaluating +/// +/// Parameters: +/// source: Null-terminated Nickel source code +/// name: Null-terminated filename (for error messages) +/// +/// Returns: +/// 0 on success (valid) +/// 1 on validation error +/// -1 on invalid input +pub export fn validate_nickel(source: [*:0]const u8, name: [*:0]const u8) callconv(.C) i32 { + return bunsenite_validate(source, name); +} + +/// Free a string allocated by parse_nickel +/// +/// Parameters: +/// ptr: Pointer returned by parse_nickel (may be null) +pub export fn free_string(ptr: ?[*:0]u8) callconv(.C) void { + bunsenite_free_string(ptr); +} + +/// Get the library version +/// +/// Returns: +/// Static string pointer (do NOT free) +pub export fn version() callconv(.C) [*:0]const u8 { + return bunsenite_version(); +} + +/// Get the RSR compliance tier +/// +/// Returns: +/// Static string pointer (do NOT free) +pub export fn rsr_tier() callconv(.C) [*:0]const u8 { + return bunsenite_rsr_tier(); +} + +/// Get the TPCF perimeter number +/// +/// Returns: +/// 3 for Community Sandbox +pub export fn tpcf_perimeter() callconv(.C) u8 { + return bunsenite_tpcf_perimeter(); +} + +// Test the FFI layer +test "version returns non-empty string" { + const ver = version(); + try std.testing.expect(ver[0] != 0); +} + +test "rsr_tier returns bronze" { + const tier = rsr_tier(); + const expected = "bronze"; + var i: usize = 0; + while (i < expected.len) : (i += 1) { + try std.testing.expectEqual(expected[i], tier[i]); + } +} + +test "tpcf_perimeter returns 3" { + try std.testing.expectEqual(@as(u8, 3), tpcf_perimeter()); +}