From 089618521399fb18fbaa18586cba0f8f4e5756f8 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:41:56 +0000 Subject: [PATCH 1/2] chore(deps): re-point trust kernel to echidna-core Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .machine_readable/descriptiles/STATE.a2ml | 17 +++++++-- Cargo.lock | 44 ++++++++++++++++++++--- Cargo.toml | 9 ++--- docs/ECHIDNA-INTEGRATION.adoc | 30 ++++++++-------- src/trust/axiom_tracker.rs | 4 +-- src/trust/confidence.rs | 6 ++-- 6 files changed, 76 insertions(+), 34 deletions(-) diff --git a/.machine_readable/descriptiles/STATE.a2ml b/.machine_readable/descriptiles/STATE.a2ml index ec6586c..6afef11 100644 --- a/.machine_readable/descriptiles/STATE.a2ml +++ b/.machine_readable/descriptiles/STATE.a2ml @@ -5,9 +5,9 @@ [metadata] project = "echidnabot" version = "0.1.0" -last-updated = "2026-06-02" +last-updated = "2026-10-09" status = "active" -session = "Production-hardening wave 2026-06-02: JSON logging (#63) + graceful shutdown (#64) + OpenTelemetry tracing (#65) + Docker Compose stack (#69) + Codeberg adapter scaffold (#74) + canonical-source two-purpose split (#55) + contractile family completion (#77 + #78); rsa phantom dep / sqlx default-features trim (#46); SHA-pin bumps (#50); workflow routing through standards reusables (#54); governance SHA pin (#53); echo-types audit (#48); panic-attack classifications (#47); doc maxout (#49); CITATION harmonised (#72). Test count 142+ (137 baseline + 6 new obs + scatter)." +session = "Issue #173: re-point shared trust kernel to echidna-core 0.2.0 on upstream main; local slug and contract parser retained. Build and test blocked: Cargo is not installed in this sandbox; draft PR pending verification." [project-context] name = "Echidnabot" @@ -441,3 +441,16 @@ file-4 = "Two commits (7052195 + 1b465ba) on main branch with detailed commit me [history.retired-root-state] last-updated = "2026-04-26" # seam test: 7a mode-routing + 7b corpus-bridge end-to-end session = "seam test DONE (tests/seam_test.rs, 15 tests); smoke.rs mode_selector bug fixed; lifecycle.rs unused-import cleaned; historical suite counts recorded as 137 lib + 17 lifecycle + 32 integration + 12 property + 15 seam + 8 smoke (sum 221; the retired 184 aggregate was inconsistent and is not a current test result)" + +[session-2026-10-09-trust-kernel] +issue = "#173" +upstream-rev = "645d7a2489c644706210e171acba6f52741ad3b8" +upstream-provenance = "GitHub commits/main API returned this full rev on 2026-10-09; echidna-core 0.2.0 re-exports echidna-core-creusot as trust." +changes = [ + "Re-point Cargo.toml and both trust-module import/doc paths to echidna-core::trust; no algorithm changes.", + "Remove stale rename follow-ups; preserve main-only pin policy, local ProverKind slug and local prove_result parser.", + "Update Cargo.lock using pinned upstream manifests and strum 0.28.0 / strum_macros 0.28.0 records from its Cargo.lock; Cargo regeneration remains unverified.", +] +validation = "cargo build and cargo test both attempted, exit 127: cargo: command not found. Trust unit tests not run." +coordination = "#166 remains open, but this checkout already pins tracing-opentelemetry 0.34 and rustls 0.23.45. No OTel or TLS dependency changes made; no security or CI pass claimed." +follow-up = "Run cargo build and cargo test with repository-pinned Rust 1.99.0, confirm lockfile resolution and trust unit tests before marking draft PR ready." diff --git a/Cargo.lock b/Cargo.lock index 1f76085..17e128b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,7 +198,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "strum", + "strum 0.27.2", "syn 2.0.117", "thiserror", ] @@ -1109,9 +1109,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "echidna-core-spark" +name = "echidna-core" +version = "0.2.0" +source = "git+https://github.com/hyperpolymath/echidna?rev=645d7a2489c644706210e171acba6f52741ad3b8#645d7a2489c644706210e171acba6f52741ad3b8" +dependencies = [ + "anyhow", + "echidna-core-creusot", + "serde", + "serde_json", + "serde_json_canonicalizer", + "strum 0.28.0", +] + +[[package]] +name = "echidna-core-creusot" version = "0.1.0" -source = "git+https://github.com/hyperpolymath/echidna?rev=b761b3a832981be51d4e88076ef1b90fe5037e9c#b761b3a832981be51d4e88076ef1b90fe5037e9c" +source = "git+https://github.com/hyperpolymath/echidna?rev=645d7a2489c644706210e171acba6f52741ad3b8#645d7a2489c644706210e171acba6f52741ad3b8" dependencies = [ "serde", ] @@ -1130,7 +1143,7 @@ dependencies = [ "clap", "config", "criterion", - "echidna-core-spark", + "echidna-core", "gitbot-shared-context", "hex", "hmac 0.13.0", @@ -4072,7 +4085,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -4087,6 +4109,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 4a9ac68..cf6a860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,12 +30,9 @@ gitbot-shared-context = { version = "0.1.0", git = "https://github.com/hyperpoly # ECHIDNA trust kernel. Canonical trust-level algorithm and source-level # axiom scanner, shared with ECHIDNA so echidnabot no longer re-implements -# them. Pinned by full rev (immutable). At this rev the crate is still named -# `echidna-core-spark`; it is being renamed `echidna-core-creusot` upstream, -# and the trust types are meant to move into `echidna-core` proper. Follow-up: -# re-point this line once that lands on echidna `main` (see -# docs/ECHIDNA-INTEGRATION.adoc). -echidna-core-spark = { git = "https://github.com/hyperpolymath/echidna", rev = "b761b3a832981be51d4e88076ef1b90fe5037e9c" } +# them via echidna-core 0.2.0's stable `trust` surface. Pinned by full rev +# on echidna `main` (see docs/ECHIDNA-INTEGRATION.adoc). +echidna-core = { git = "https://github.com/hyperpolymath/echidna", rev = "645d7a2489c644706210e171acba6f52741ad3b8" } # Minimum-version handshake with the ECHIDNA server. semver = "1" diff --git a/docs/ECHIDNA-INTEGRATION.adoc b/docs/ECHIDNA-INTEGRATION.adoc index cea8ecd..deca593 100644 --- a/docs/ECHIDNA-INTEGRATION.adoc +++ b/docs/ECHIDNA-INTEGRATION.adoc @@ -13,30 +13,28 @@ _proved_ mean different things and none implies the next. `src/trust/confidence.rs` and `src/trust/axiom_tracker.rs` no longer carry their own algorithms. -* The trust *level* comes from `echidna_core_spark::compute_trust_level` +* The trust *level* comes from `echidna_core::trust::compute_trust_level` (ECHIDNA's trust kernel). echidnabot only maps its inputs onto ECHIDNA's `TrustFactors`. `certificate_verified` is always `false`, because echidnabot never checks a certificate itself. * The proof-*source* axiom scan is ECHIDNA's - `echidna_core_spark::axiom_tracker::AxiomTracker`, called through + `echidna_core::trust::axiom_tracker::AxiomTracker`, called through `AxiomTracker::scan_source`. * The prover-*output* text scan (`AxiomTracker::scan`, for example Lean's "declaration uses 'sorry'") stays local. ECHIDNA has no equivalent. -The dependency is a git dependency pinned by full rev -(`b761b3a832981be51d4e88076ef1b90fe5037e9c`, on echidna `main`). A path -dependency would break standalone builds. - -Status: implemented and tested (unit tests in both modules). The kernel's -Creusot annotations are ECHIDNA's. echidnabot does not claim they are -proved. - -=== Follow-up: crate rename - -At the pinned rev the crate is named `echidna-core-spark`. It is not SPARK. -Upstream intends to rename it (`echidna-core-creusot`) and to move the trust -types into `echidna-core`. When either change lands on echidna `main`, update -the `Cargo.toml` line and the `use` paths. Do not pin to an unmerged branch. +The dependency is `echidna-core` 0.2.0, whose stable `trust` surface +re-exports the `echidna-core-creusot` kernel unchanged (including `pareto`). +It is a git dependency pinned by full rev +(`645d7a2489c644706210e171acba6f52741ad3b8`, verified on echidna `main` +on 2026-10-09). Do not pin to an unmerged branch. A path dependency would +break standalone builds. + +Status: implemented, with existing unit tests in both modules. Verification +of the 2026-10-09 dependency re-point is pending: `cargo build` and +`cargo test` could not run in the editing sandbox (Cargo is not installed). +The kernel's Creusot annotations are ECHIDNA's. echidnabot does not claim +they are proved. == Prover identifiers diff --git a/src/trust/axiom_tracker.rs b/src/trust/axiom_tracker.rs index 92de012..6a06ff9 100644 --- a/src/trust/axiom_tracker.rs +++ b/src/trust/axiom_tracker.rs @@ -19,14 +19,14 @@ //! Two scanners live here and they read different things: //! //! - [`AxiomTracker::scan_source`] reads the **proof source** and delegates to -//! ECHIDNA's canonical scanner (`echidna_core_spark::axiom_tracker`), which +//! ECHIDNA's canonical scanner (`echidna_core::trust::axiom_tracker`), which //! skips comment lines and ECHIDNA's own scaffold markers. This is the //! primary signal and is shared with ECHIDNA, not re-implemented. //! - [`AxiomTracker::scan`] reads the **prover's output text** (for example //! Lean's "declaration uses 'sorry'"). ECHIDNA has no equivalent, so this //! stays local, as a fallback for when the source is unavailable. -use echidna_core_spark::axiom_tracker::{ +use echidna_core::trust::axiom_tracker::{ AxiomTracker as EchidnaAxiomTracker, AxiomUsage, DangerLevel, }; use serde::{Deserialize, Serialize}; diff --git a/src/trust/confidence.rs b/src/trust/confidence.rs index 5b350f0..f5c3d09 100644 --- a/src/trust/confidence.rs +++ b/src/trust/confidence.rs @@ -4,7 +4,7 @@ //! Proof confidence level assessment //! //! The trust-level *algorithm* is ECHIDNA's, not echidnabot's: every call -//! goes through [`echidna_core_spark::compute_trust_level`] (the Creusot- +//! goes through [`echidna_core::trust::compute_trust_level`] (the Creusot- //! annotated kernel shared with ECHIDNA). This module only translates //! echidnabot's inputs (prover slug, status, artefacts, axiom scan) into //! ECHIDNA's [`TrustFactors`] and wraps the answer in a report. @@ -15,8 +15,8 @@ //! `echidna.prove.result/1` object, that is the *receipt* and is preferred; //! see [`crate::dispatcher::TrustSource`]. -use echidna_core_spark::axiom_tracker::DangerLevel; -use echidna_core_spark::{compute_trust_level, ProverClass, TrustFactors, TrustLevel}; +use echidna_core::trust::axiom_tracker::DangerLevel; +use echidna_core::trust::{compute_trust_level, ProverClass, TrustFactors, TrustLevel}; use serde::{Deserialize, Serialize}; use crate::dispatcher::{ProofStatus, ProverKind}; From d16f7fb1f365d7bb6d4f8a8c26eb838c35f2382d Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:50:30 +0000 Subject: [PATCH 2/2] chore(dev): add mise Guix and Wolfi Podman environments Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .containerignore | 16 +++ .machine_readable/descriptiles/STATE.a2ml | 20 ++- Containerfile | 69 +++++----- Justfile | 101 +++++--------- README.adoc | 7 + docs/DEVELOPMENT.adoc | 156 ++++++++++++++++++++++ guix/channels.scm | 14 ++ guix/manifest.scm | 10 ++ mise.toml | 74 ++++------ scripts/bootstrap.sh | 46 +++++++ scripts/dev-container.sh | 22 +++ tests/dev-tools.sh | 71 ++++++++++ 12 files changed, 455 insertions(+), 151 deletions(-) create mode 100644 .containerignore create mode 100644 docs/DEVELOPMENT.adoc create mode 100644 guix/channels.scm create mode 100644 guix/manifest.scm create mode 100755 scripts/bootstrap.sh create mode 100755 scripts/dev-container.sh create mode 100644 tests/dev-tools.sh diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..6a7c387 --- /dev/null +++ b/.containerignore @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: MPL-2.0 +.git +.env +.env.* +!.env.example +*.pem +*.key +secrets +.cache +.local +target +build +dist +out +node_modules +*.log diff --git a/.machine_readable/descriptiles/STATE.a2ml b/.machine_readable/descriptiles/STATE.a2ml index 6afef11..e95891f 100644 --- a/.machine_readable/descriptiles/STATE.a2ml +++ b/.machine_readable/descriptiles/STATE.a2ml @@ -7,7 +7,7 @@ project = "echidnabot" version = "0.1.0" last-updated = "2026-10-09" status = "active" -session = "Issue #173: re-point shared trust kernel to echidna-core 0.2.0 on upstream main; local slug and contract parser retained. Build and test blocked: Cargo is not installed in this sandbox; draft PR pending verification." +session = "Issue #173 follow-up: owner-requested fresh-checkout tooling via mise, Guix and rootless Podman/Wolfi. Offline shell checks pass; installation and real Rust/container/Guix validation blocked by sandbox tooling and network restrictions. PR #174 remains draft." [project-context] name = "Echidnabot" @@ -454,3 +454,21 @@ changes = [ validation = "cargo build and cargo test both attempted, exit 127: cargo: command not found. Trust unit tests not run." coordination = "#166 remains open, but this checkout already pins tracing-opentelemetry 0.34 and rustls 0.23.45. No OTel or TLS dependency changes made; no security or CI pass claimed." follow-up = "Run cargo build and cargo test with repository-pinned Rust 1.99.0, confirm lockfile resolution and trust unit tests before marking draft PR ready." + +[session-2026-10-09-development-environment] +request = "Owner explicitly requested tool installation and repo-integrated mise, Guix environment settings and a Podman environment on Chainguard Wolfi; Guix provided as an explicit opt-in despite older deprecation guidance." +changes = [ + "Replace generic mise template with Just 1.58.0, rust-toolchain.toml discovery and Cargo tasks; bootstrap checksum-pins mise 2026.10.6 for Linux x86_64/aarch64.", + "Wire real Cargo build/test/format/lint/fetch and development-container Just recipes; add fresh-checkout documentation and README entry point.", + "Add authenticated pinned Guix channel plus native-prerequisite manifest, with documented FHS shell for downloaded Rust tools.", + "Use Chainguard Wolfi for development, builder and runtime Containerfile stages; add rootless Podman wrapper, named build caches and build-context exclusions.", + "Add offline script control-flow tests; no new .a2ml files.", +] +validation = "PASS: bash -n on all three shell scripts, tests/dev-tools.sh (mock install, failed download/checksum, Podman arguments and missing-image failure), git diff --check." +blockers = [ + "Mise and Just GitHub binary downloads fail at release-assets.githubusercontent.com; static.rust-lang.org and cgr.dev are unreachable under sandbox allowlist. No tools successfully installed.", + "Bootstrap preflight also finds CMake missing; pkg-config, Cargo, Just, Guix and Podman are absent. Rust build/tests and real container/Guix evaluation remain unverified.", + "Wolfi image digest cannot be verified here; rolling tag/APK repositories documented explicitly, not claimed fully reproducible.", + "PR #174 at 0896185 reports Cargo check failure (exit 101), Cargo test skipped; log download blocked at actions log host, so cause remains undiagnosed. Do not claim CI green or assume #166 caused it.", +] +follow-up = "Run bootstrap, cargo build/test and trust tests, Guix FHS environment and Podman build/run on an unrestricted host; inspect PR Cargo check logs before readying #174." diff --git a/Containerfile b/Containerfile index 12df388..ad2f876 100644 --- a/Containerfile +++ b/Containerfile @@ -1,40 +1,45 @@ # SPDX-License-Identifier: MPL-2.0 -# Build stage -FROM docker.io/library/rust:1.83-slim AS builder - -WORKDIR /build - -# Install build dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - pkg-config \ - libsqlite3-dev \ - && rm -rf /var/lib/apt/lists/* - -# Copy source -COPY . . - -# Build release binary -RUN cargo build --release - -# Runtime stage -FROM cgr.dev/chainguard/wolfi-base:latest - +# Override WOLFI_IMAGE with cgr.dev/chainguard/wolfi-base@sha256: +# for a reviewed, immutable release build. Never invent an unverified digest. +ARG WOLFI_IMAGE=cgr.dev/chainguard/wolfi-base:latest +FROM ${WOLFI_IMAGE} AS development + +RUN apk add --no-cache bash ca-certificates curl git build-base cmake \ + pkgconf sqlite-dev tar gzip xz coreutils libstdc++ + +ENV MISE_DATA_DIR=/opt/mise \ + MISE_CONFIG_DIR=/opt/mise-config \ + MISE_CACHE_DIR=/tmp/mise-cache \ + RUSTUP_HOME=/opt/rustup \ + CARGO_HOME=/opt/cargo \ + MISE_BIN_DIR=/usr/local/bin +ENV PATH="/opt/cargo/bin:/opt/mise/shims:${PATH}" +WORKDIR /workspace +COPY mise.toml rust-toolchain.toml ./ +COPY scripts/bootstrap.sh scripts/bootstrap.sh +RUN bash scripts/bootstrap.sh \ + && ln -s "$(mise which just)" /usr/local/bin/just \ + && chmod -R a+rX /opt/rustup /opt/cargo /opt/mise \ + && adduser -D -u 1000 dev \ + && mkdir -p /home/dev/.cargo /workspace/target \ + && chown -R dev:dev /home/dev /workspace + +ENV CARGO_HOME=/home/dev/.cargo HOME=/home/dev +USER dev +CMD ["bash"] + +FROM development AS builder +COPY --chown=dev:dev . . +RUN cargo build --locked --release + +FROM ${WOLFI_IMAGE} AS runtime LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/echidnabot" LABEL org.opencontainers.image.description="Proof-aware CI bot that orchestrates ECHIDNA for theorem proof verification" LABEL org.opencontainers.image.licenses="MPL-2.0" - -# Install runtime dependencies -RUN apk add --no-cache sqlite-libs ca-certificates - -# Copy binary from builder -COPY --from=builder /build/target/release/echidnabot /usr/local/bin/echidnabot - -# Create non-root user -RUN adduser -D -u 1000 echidna +RUN apk add --no-cache sqlite-libs ca-certificates libstdc++ \ + && adduser -D -u 1000 echidna +COPY --from=builder /workspace/target/release/echidnabot /usr/local/bin/echidnabot USER echidna - WORKDIR /home/echidna - EXPOSE 8080 - ENTRYPOINT ["/usr/local/bin/echidnabot"] diff --git a/Justfile b/Justfile index a21a842..70a3fd1 100644 --- a/Justfile +++ b/Justfile @@ -20,9 +20,9 @@ set positional-arguments := true import? "build/contractile.just" # Project metadata — customize these -project := "rsr-template-repo" +project := "echidnabot" OWNER := "hyperpolymath" -REPO := "rsr-template-repo" +REPO := "echidnabot" version := "0.1.0" tier := "infrastructure" # 1 | 2 | infrastructure @@ -83,24 +83,11 @@ import? "build/just/assess.just" # Build the project (debug mode) build *args: - @echo "Building {{project}} (debug)..." - # TODO: Replace with your build command - # Examples: - # cargo build {{args}} # Rust - # mix compile {{args}} # Elixir - # zig build {{args}} # Zig - # deno task build {{args}} # Deno/AffineScript - @echo "Build complete" + cargo build --locked {{args}} # Build in release mode with optimizations build-release *args: - @echo "Building {{project}} (release)..." - # TODO: Replace with your release build command - # Examples: - # cargo build --release {{args}} - # MIX_ENV=prod mix compile {{args}} - # zig build -Doptimize=ReleaseFast {{args}} - @echo "Release build complete" + cargo build --locked --release {{args}} # Build and watch for changes (requires entr or similar) build-watch: @@ -127,24 +114,15 @@ clean-all: clean # Run all tests test *args: - @echo "Running tests..." - # TODO: Replace with your test command - # Examples: - # cargo test {{args}} - # mix test {{args}} - # zig build test {{args}} - # deno test {{args}} - @echo "Tests passed!" + cargo test --locked {{args}} # Run tests with verbose output test-verbose: - @echo "Running tests (verbose)..." - # TODO: Replace with verbose test command + cargo test --locked -- --nocapture # Smoke test test-smoke: - @echo "Smoke test..." - # TODO: Add basic sanity checks + cargo test --locked --test smoke # Run end-to-end tests (full pipeline: build → run → verify) e2e: @@ -227,31 +205,15 @@ fix: fmt # Format all source files [reversible: git checkout] fmt: - @echo "Formatting source files..." - # TODO: Replace with your formatter - # Examples: - # cargo fmt - # mix format - # gleam format - # deno fmt + cargo fmt --all # Check formatting without changes fmt-check: - @echo "Checking formatting..." - # TODO: Replace with your format check - # Examples: - # cargo fmt --check - # mix format --check-formatted - # gleam format --check + cargo fmt --all -- --check # Run linter lint: - @echo "Linting source files..." - # TODO: Replace with your linter - # Examples: - # cargo clippy -- -D warnings - # mix credo --strict - # gleam check + cargo clippy --locked --all-targets -- -D warnings # ═══════════════════════════════════════════════════════════════════════════════ # RUN & EXECUTE @@ -278,13 +240,7 @@ install: build-release # Install/check all dependencies deps: - @echo "Checking dependencies..." - # TODO: Replace with your dependency check - # Examples: - # cargo check - # mix deps.get - # gleam deps download - @echo "All dependencies satisfied" + cargo fetch --locked # Audit dependencies for vulnerabilities deps-audit: @@ -426,19 +382,7 @@ container-init: # Build container image via cerro-torre pipeline container-build *args: - #!/usr/bin/env bash - if [ -f "container/ct-build.sh" ]; then - cd container && ./ct-build.sh {{args}} - elif [ -f "container/Containerfile" ]; then - podman build -t {{project}}:latest -f container/Containerfile . - elif [ -f "build/Containerfile" ]; then - podman build -t {{project}}:latest -f build/Containerfile . - elif [ -f "Containerfile" ]; then - podman build -t {{project}}:latest -f Containerfile . - else - echo "No Containerfile found in container/, build/, or project root" - exit 1 - fi + podman build -t localhost/echidnabot:latest -f Containerfile {{args}} . # Verify compose configuration container-verify: @@ -779,3 +723,24 @@ handover-human path=".": secret-scan-trufflehog: @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true + +# Install the repository-pinned mise, Rust/Cargo and Just tools +bootstrap: + bash scripts/bootstrap.sh + +# Build the Chainguard Wolfi development image (requires only Podman) +dev-container-build: + podman build --target development -t localhost/echidnabot-dev -f Containerfile . + +# Open a rootless development shell, or execute a command in it +dev-container *args: + bash scripts/dev-container.sh {{args}} + +# Run both trust-module unit test paths +trust-test: + cargo test --locked --lib trust:: + +# Offline control-flow tests for development setup scripts (not real tool builds) +dev-tools-test: + bash -n scripts/bootstrap.sh scripts/dev-container.sh tests/dev-tools.sh + bash tests/dev-tools.sh diff --git a/README.adoc b/README.adoc index edd52bd..e9a913f 100644 --- a/README.adoc +++ b/README.adoc @@ -29,6 +29,13 @@ Part of the https://github.com/hyperpolymath/gitbot-fleet[gitbot-fleet] (Tier-1 Verifier role), coordinated by https://github.com/hyperpolymath/hypatia[Hypatia]. +=== Build and development setup + +See link:docs/DEVELOPMENT.adoc[Development environment] for a fresh-checkout +setup using pinned mise/Rust/Just tools, a Guix build environment, or a +rootless Podman development container based on Chainguard Wolfi. +The setup requires internet access; toolchain binaries are not vendored. + === Why does it exist? You’re writing formally verified software — proofs in Coq, Lean, Agda, diff --git a/docs/DEVELOPMENT.adoc b/docs/DEVELOPMENT.adoc new file mode 100644 index 0000000..1d1fa78 --- /dev/null +++ b/docs/DEVELOPMENT.adoc @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MPL-2.0 += Development environment +:toc: + +A standalone checkout can fetch both git dependencies; no sibling fleet +checkout is required. Choose the native, Guix, or Podman route below. +All routes need internet access for their first install/build. This repository +contains setup recipes, not vendored toolchain binaries or an offline SDK. + +== Tool ownership + +* `rust-toolchain.toml`: exact Rust version, Cargo, rustfmt and Clippy. + Mise reads this file; there is no second Rust version to drift. +* `mise.toml`: Just 1.58.0 and build/test/check tasks. Unrelated language + runtimes and fallback commands have been removed. +* `scripts/bootstrap.sh`: mise 2026.10.6, with SHA-256 checksums for Linux + x86_64 and aarch64 release archives. Checksums came from the upstream + release asset metadata. It installs into `$HOME/.local/bin` by default + (`MISE_BIN_DIR` overrides that location), without sudo or shell-profile edits. +* `Cargo.lock`: application dependencies. Normal commands use `--locked`; + dependency updates must deliberately regenerate and review this file. + +== Native Linux + +Install the native compiler/linker, Git, CA certificates, curl, archive tools, +CMake, pkg-config and SQLite headers through your OS package manager. +For Debian/Ubuntu: + +[source,sh] +---- +sudo apt-get update +sudo apt-get install -y build-essential cmake pkg-config libsqlite3-dev \ + git curl ca-certificates tar gzip xz-utils unzip +bash scripts/bootstrap.sh +export PATH="$HOME/.local/bin:$PATH" +mise run build +mise run test +mise run trust-test +mise run check +# Equivalent Just entry points, with the mise-managed tools on PATH: +mise exec -- just build +mise exec -- just test +---- + +The bootstrap fails on missing prerequisites or failed downloads; it never +prints a build/test success without executing the command. It explicitly +trusts this checkout's mise configuration, so inspect that configuration first. +It can be rerun after updating pins. For an interactive shell, use +`eval "$(mise activate bash)"` (or your shell's equivalent). + +Downloads require GitHub and its release-asset CDN, Rust's distribution +servers, crates.io's index/static download hosts and the git dependency +repositories. A GitHub-only network allowlist is insufficient. + +== Guix build environment (explicit opt-in) + +This environment is provided at the owner's request despite the older +estate-wide Guix deprecation guidance. It does not change the release build +system or make echidnabot a Guix package. `guix/manifest.scm` supplies native +build prerequisites; mise/rustup still supply the repository-pinned Rust. + +Install Guix on the host first. `guix/channels.scm` pins an authenticated Guix +GitHub-mirror revision (927070a6, dated 2025-11-16); it is not a claim to track +current Guix releases. Channel/package updates need separate review. Use this +file, not the legacy root `.guix-channel`, for the environment. + +On Linux with unprivileged user namespaces enabled: + +[source,sh] +---- +mkdir -p "$HOME/.cache/echidnabot-guix" +guix time-machine -C guix/channels.scm -- shell \ + --container --emulate-fhs --network \ + --share="$HOME/.cache/echidnabot-guix=/tools" \ + -m guix/manifest.scm -- env HOME=/tools bash +# Inside the Guix shell, in the shared checkout: +bash scripts/bootstrap.sh +export PATH="$HOME/.local/bin:$PATH" +mise run build +mise run test +---- + +The FHS container is intentional: mise and rustup distribute conventional +Linux binaries, which should not be assumed to run in a pure non-FHS Guix +shell. Tool downloads persist under the shared `/tools` home; source edits +and `target/` persist in the checkout. This requires access to Guix source +and substitute servers as well as the toolchain hosts above. Guix channel +and environment evaluation have not yet been exercised in the editing sandbox. + +== Rootless Podman on Chainguard Wolfi + +Install Podman using your host's supported packages (for example, +`sudo apt-get install podman uidmap slirp4netns fuse-overlayfs` on Debian). +Configure subordinate UID/GID ranges per your distribution's rootless Podman +guide. On macOS/Windows, initialise and start a Podman Linux VM first. +You do not need Rust, mise or Just on the host for this route. + +[source,sh] +---- +# Build the development target and enter a shell: +bash scripts/dev-container.sh --build +# Or run non-interactively after building: +bash scripts/dev-container.sh cargo build --locked +bash scripts/dev-container.sh cargo test --locked +bash scripts/dev-container.sh cargo test --locked --lib trust:: +bash scripts/dev-container.sh just fmt-check +# Application/runtime image (final Containerfile stage): +podman build -t localhost/echidnabot:latest -f Containerfile . +podman run --rm localhost/echidnabot:latest --help +---- + +All stages derive from `cgr.dev/chainguard/wolfi-base`. The development image +includes native build tools and the same mise-installed Rust/Just pins as the +host setup. The builder compiles with `--locked`; the final non-root runtime +image contains the application and runtime libraries, not the toolchains. + +The wrapper maps the caller to container UID/GID 1000 with `--userns=keep-id`, +mounts the checkout read/write at `/workspace`, and uses named volumes +`echidnabot-dev-target` and `echidnabot-dev-cargo` for build/dependency caches. +It does not mount a container-engine socket or require privileged mode. +`:Z` gives the checkout a private SELinux label: do not use the same checkout +concurrently from other labelled containers. `:U` changes ownership only on +the named cache volumes, not the source bind mount. +To reset caches after exiting all development containers: + +[source,sh] +---- +podman volume rm echidnabot-dev-target echidnabot-dev-cargo +---- + +The Wolfi default tag and APK repositories are rolling, so this is not yet a +bit-for-bit reproducible image. For release builds supply a reviewed image +digest with `--build-arg WOLFI_IMAGE=cgr.dev/chainguard/wolfi-base@sha256:...`. +No digest is invented here: the registry was unreachable from the editing +sandbox. Full reproducibility would also require an APK repository snapshot. +Rebuild the development image when the Rust or Just pin changes. + +== Validation boundaries + +`cargo test --locked --lib trust::` covers the trust-level and source-axiom +paths changed for #173. Always run the full build and test commands too. +The current Rust CI wrapper documents a separate library-suite hang; do not +silently substitute a narrower test run and claim the full suite passed. +Live integration tests need an ECHIDNA server and relevant provers; these +are not bundled with this development environment. Build tools do not imply +that proof execution or the fleet's governance tooling is installed. + +In particular, `just validate-rsr` is not a defined recipe in this checkout; +installing Just does not create it. The inherited Justfile still contains +other estate-template recipes outside the build/test/format/lint paths wired +here. Do not treat their placeholder messages as verification evidence. + +The editing sandbox could not fetch mise/Just release assets, Rust distributions +or Wolfi images, and has no Podman or Guix. Shell syntax and mock-based wrapper +checks can be run there, but Rust build/tests, real tool installation, Guix +evaluation and container build/run remain pending in an unrestricted environment. diff --git a/guix/channels.scm b/guix/channels.scm new file mode 100644 index 0000000..a0096ee --- /dev/null +++ b/guix/channels.scm @@ -0,0 +1,14 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; Explicit development-environment channel, not an echidnabot package channel. +;; GitHub mirror revision inspected on 2026-10-09. Bump deliberately. +(use-modules (guix channels)) + +(list (channel + (name 'guix) + (url "https://github.com/guix-mirror/guix") + (commit "927070a6f64da86a9c1694fe55c4316b2f9a4e11") + (introduction + (make-channel-introduction + "9edb3f66fd807b096b48283debdcddccfea34bad" + (openpgp-fingerprint + "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))) diff --git a/guix/manifest.scm b/guix/manifest.scm new file mode 100644 index 0000000..2cf6691 --- /dev/null +++ b/guix/manifest.scm @@ -0,0 +1,10 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; Native build prerequisites. Rust/Just versions remain owned by mise and +;; rust-toolchain.toml, not by the Guix channel's Rust package version. +(use-modules (guix profiles)) + +(specifications->manifest + '("bash" "coreutils" "findutils" "diffutils" "grep" "sed" + "tar" "gzip" "xz" "unzip" "which" + "git" "curl" "nss-certs" + "gcc-toolchain" "make" "cmake" "pkg-config" "sqlite" "zlib")) diff --git a/mise.toml b/mise.toml index 6dd983f..26395a5 100644 --- a/mise.toml +++ b/mise.toml @@ -1,57 +1,31 @@ -[tools] -# Language runtimes -node = "latest" -python = "latest" -rust = "latest" -go = "latest" -zig = "latest" -java = "latest" -bun = "latest" -denojs = "latest" +# SPDX-License-Identifier: MPL-2.0 +# Rust (including Cargo, rustfmt and Clippy) comes from rust-toolchain.toml. +[settings] +idiomatic_version_file_enable_tools = ["rust"] -# Package managers -npm = "latest" -yarn = "latest" -pnpm = "latest" -pip = "latest" -cargo = "latest" -go-task = "latest" +[tools] +just = "1.58.0" -# Formatting & Linting -gofmt = "latest" -black = "latest" -isort = "latest" -ruff = "latest" -prettier = "latest" -shfmt = "latest" -stylua = "latest" +[tasks.build] +description = "Build with the committed dependency lock" +run = "cargo build --locked" -# Build tools -cmake = "latest" -make = "latest" -ninja = "latest" +[tasks.test] +description = "Run the full Rust test suite" +run = "cargo test --locked" -# Shell tools -git = "latest" -gnu-sed = "latest" -gnu-tar = "latest" -gnu-grep = "latest" +[tasks.trust-test] +description = "Run both shared trust-kernel integration paths" +run = "cargo test --locked --lib trust::" -# Testing -vitest = "latest" -pytest = "latest" -jest = "latest" +[tasks.check] +description = "Check formatting and lint all Rust targets" +run = ["cargo fmt --all -- --check", "cargo clippy --locked --all-targets -- -D warnings"] -[env] -# Common environment variables -NODE_ENV = "development" -PYTHONDONTWRITEBYTECODE = "1" -PYTHONUNBUFFERED = "1" +[tasks.dev-container] +description = "Build the Wolfi development image with Podman" +run = "podman build --target development -t localhost/echidnabot-dev -f Containerfile ." -# 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 ." +[tasks.dev-tools-test] +description = "Offline bootstrap and Podman-wrapper control-flow tests" +run = ["bash -n scripts/bootstrap.sh scripts/dev-container.sh tests/dev-tools.sh", "bash tests/dev-tools.sh"] diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..dcd1dbc --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Install the pinned mise binary and this repository's Rust/Just toolchain. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +version=2026.10.6 +bin_dir=${MISE_BIN_DIR:-"$HOME/.local/bin"} +for cmd in curl tar gzip sha256sum cc cmake pkg-config git; do + command -v "$cmd" >/dev/null || { + printf 'Missing prerequisite: %s. See docs/DEVELOPMENT.adoc.\n' "$cmd" >&2 + exit 1 + } +done +case "$(uname -s)/$(uname -m)" in + Linux/x86_64) + arch=x64 + checksum=5135da6b71e6857efb22b2eb8d0fbc0061d061005a4994f1e7f8975d548a3e92 + ;; + Linux/aarch64|Linux/arm64) + arch=arm64 + checksum=60f0e34ea2088e822797393ed3d3b50d58dd9b45687006b31ac66ef68e99a2f4 + ;; + *) + echo 'Bootstrap supports Linux x86_64/aarch64; on other hosts use a Podman Linux VM.' >&2 + exit 1 + ;; +esac +mkdir -p "$bin_dir" +if [[ ! -x "$bin_dir/mise" ]] || [[ $("$bin_dir/mise" --version) != "$version "* ]]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + curl --fail --location --retry 3 --connect-timeout 20 --max-time 300 \ + "https://github.com/jdx/mise/releases/download/v$version/mise-v$version-linux-$arch.tar.gz" \ + --output "$tmp/mise.tar.gz" + printf '%s %s\n' "$checksum" "$tmp/mise.tar.gz" | sha256sum --check --status + tar -xzf "$tmp/mise.tar.gz" -C "$tmp" mise/bin/mise + install -m 0755 "$tmp/mise/bin/mise" "$bin_dir/mise" +fi +export PATH="$bin_dir:$PATH" +# Explicit invocation means the user has elected to trust this checkout. +mise trust "$PWD/mise.toml" +mise install +mise exec -- cargo --version +mise exec -- just --version +printf '\nReady. Add %s to PATH, then run: mise run build && mise run test\n' "$bin_dir" diff --git a/scripts/dev-container.sh b/scripts/dev-container.sh new file mode 100755 index 0000000..03b1b4e --- /dev/null +++ b/scripts/dev-container.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Rootless Podman development shell; no host Rust installation required. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." +command -v podman >/dev/null || { echo 'Install Podman first; see docs/DEVELOPMENT.adoc.' >&2; exit 1; } +image=${ECHIDNABOT_DEV_IMAGE:-localhost/echidnabot-dev} +if [[ ${1:-} == --build ]]; then + shift + podman build --target development --tag "$image" --file Containerfile . +fi +if ! podman image exists "$image"; then + echo 'Development image missing. Run scripts/dev-container.sh --build.' >&2 + exit 1 +fi +args=(--rm --userns=keep-id:uid=1000,gid=1000 --workdir /workspace + --volume "$PWD:/workspace:Z" + --volume echidnabot-dev-target:/workspace/target:U + --volume echidnabot-dev-cargo:/home/dev/.cargo:U) +if [[ -t 0 && -t 1 ]]; then args+=(-it); fi +if [[ $# == 0 ]]; then set -- bash; fi +exec podman run "${args[@]}" "$image" "$@" diff --git a/tests/dev-tools.sh b/tests/dev-tools.sh new file mode 100644 index 0000000..da31b4c --- /dev/null +++ b/tests/dev-tools.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Offline control-flow tests only: these do NOT validate downloaded tools/images. +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." +repo=$PWD +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +mkdir -p "$tmp/bin" "$tmp/installed" +export TOOL_TEST_LOG="$tmp/calls" +export PATH="$tmp/bin:$PATH" +export MISE_BIN_DIR="$tmp/installed" + +for tool in cmake pkg-config cc; do + printf '#!/bin/sh\nexit 0\n' > "$tmp/bin/$tool" + chmod +x "$tmp/bin/$tool" +done +cat > "$tmp/installed/mise" <<'MOCK' +#!/usr/bin/env bash +set -eu +if [[ $1 == --version ]]; then echo '2026.10.6 linux-x64'; exit; fi +printf '%s\n' "$*" >> "$TOOL_TEST_LOG" +MOCK +chmod +x "$tmp/installed/mise" +bash scripts/bootstrap.sh > "$tmp/output" +grep -Fx "trust $repo/mise.toml" "$TOOL_TEST_LOG" +grep -Fx 'install' "$TOOL_TEST_LOG" +grep -Fx 'exec -- cargo --version' "$TOOL_TEST_LOG" +grep -Fx 'exec -- just --version' "$TOOL_TEST_LOG" + +# A failed download must fail closed, without claiming readiness. +rm "$tmp/installed/mise" +printf '#!/bin/sh\nexit 22\n' > "$tmp/bin/curl" +chmod +x "$tmp/bin/curl" +if bash scripts/bootstrap.sh > "$tmp/output" 2>&1; then + echo 'FAIL: failed download reported success' >&2; exit 1 +fi +if grep -q Ready "$tmp/output"; then exit 1; fi + +# An untrusted archive must not be extracted/installed. +cat > "$tmp/bin/curl" <<'MOCK' +#!/usr/bin/env bash +while [[ $# -gt 0 ]]; do + if [[ $1 == --output ]]; then printf 'bad archive' > "$2"; exit 0; fi + shift +done +exit 1 +MOCK +if bash scripts/bootstrap.sh > "$tmp/output" 2>&1; then + echo 'FAIL: invalid checksum accepted' >&2; exit 1 +fi +test ! -e "$tmp/installed/mise" + +cat > "$tmp/bin/podman" <<'MOCK' +#!/usr/bin/env bash +printf '%s\n' "$*" >> "$TOOL_TEST_LOG" +if [[ $1 == image && ${MOCK_IMAGE_MISSING:-0} == 1 ]]; then exit 1; fi +MOCK +chmod +x "$tmp/bin/podman" +: > "$TOOL_TEST_LOG" +bash scripts/dev-container.sh --build cargo test --locked --lib trust:: +grep -Fx 'build --target development --tag localhost/echidnabot-dev --file Containerfile .' "$TOOL_TEST_LOG" +grep -F -- '--userns=keep-id:uid=1000,gid=1000' "$TOOL_TEST_LOG" +grep -F -- "--volume $repo:/workspace:Z" "$TOOL_TEST_LOG" +grep -F -- 'localhost/echidnabot-dev cargo test --locked --lib trust::' "$TOOL_TEST_LOG" +: > "$TOOL_TEST_LOG" +if MOCK_IMAGE_MISSING=1 bash scripts/dev-container.sh > "$tmp/output" 2>&1; then + echo 'FAIL: missing image accepted' >&2; exit 1 +fi +if grep -q '^run ' "$TOOL_TEST_LOG"; then exit 1; fi +printf 'PASS: offline development-tool control-flow tests (no real builds performed)\n'