Add static build and reproducibility compare gates

ober

b51e1bc025edf4c30e220f1125976a0db20fd554

diff --git a/Makefile b/Makefile
index e79664f..5685741 100644
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,10 @@ SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct)
 export SOURCE_DATE_EPOCH
 HOST_UNAME_S := $(shell uname -s)
 HOST_UNAME_M := $(shell uname -m)
+STATIC ?= 0
+STATIC_ENABLED := $(filter 1 yes true on,$(STATIC))
 CHEZ_BUILD_DIR ?= $(JERBOA_HOME)/build/chez
-CHEZ_PREFIX ?= $(JERBOA_HOME)/.chez
+CHEZ_PREFIX ?= $(if $(STATIC_ENABLED),$(JERBOA_HOME)/.chez-static,$(JERBOA_HOME)/.chez)
 SCHEME ?= $(CHEZ_PREFIX)/bin/scheme
 
 # Detect host machine type from the vendored configure script (e.g. tarm64osx
@@ -26,6 +28,7 @@ CHEZ_HARDEN ?= --enable-harden
 endif
 
 CHEZ_INSTALL_FLAGS = \
+		$(if $(STATIC_ENABLED),--static) \
 		$(CHEZ_HARDEN) \
 		--installprefix=$(CHEZ_PREFIX) \
 		--installbin=$(CHEZ_PREFIX)/bin \
@@ -57,7 +60,7 @@ TYPED_LLVMIR_PARITY_FN ?= sample_typed_llvmir_smoke::main
 LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi)
 TYPED_WRAPPER_DIR ?= build/typed/jerboa
 
-.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push
+.PHONY: help chez static-supported-check chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report reproducibility-compare release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push
 .PHONY: check-cross-tools docker fuzz-websocket-fuzz jlsp jlsp-freebsd-amd64 \
         jlsp-install jlsp-linux-amd64 jlsp-portable jmcp-freebsd-amd64 \
         jmcp-freebsd-arm64 jmcp-linux-amd64 jmcp-linux-arm64 \
@@ -111,6 +114,7 @@ help:
 	@echo "  typed-test       Run Typed Jerboa front-end tests"
 	@echo "  typed-clean      Remove generated typed build artifacts"
 	@echo "  native           Build Rust native library"
+	@echo "  STATIC=1         Build native ELF/BSD Chez/binaries from .chez-static"
 	@echo "  clean            Remove compiled .so and .wpo artifacts"
 	@echo "  clean-native     Remove Rust build artifacts"
 	@echo "  audit            Run production security/release gate checks"
@@ -118,6 +122,7 @@ help:
 	@echo "  pure-audit       Scan jerboa-* repos for non-pure runtime surfaces"
 	@echo "  sbom             Write local SBOM/toolchain evidence under dist/sbom"
 	@echo "  reproducibility-report  Compare two local multicall builds"
+	@echo "  reproducibility-compare Compare two independent reproducibility reports"
 	@echo "  release-evidence Write release-readiness evidence under dist/release-evidence"
 	@echo ""
 	@echo "Cross-platform binaries (one static jerboa + jerbuild/jmcp/jlsp symlinks):"
@@ -212,7 +217,15 @@ help:
 	@echo "  docker-build     Build jerboa21/jerboa base image"
 	@echo "  docker-push      Push base image to Docker Hub"
 
-chez: $(SCHEME)
+static-supported-check:
+	@if [ -n "$(STATIC_ENABLED)" ]; then \
+		case "$(CHEZ_UNAME_S)" in \
+			Linux|FreeBSD|OpenBSD|NetBSD) ;; \
+			*) echo "ERROR: STATIC=1 native builds are supported only on ELF/BSD hosts; use the portable/cross Linux musl targets for static release artifacts." >&2; exit 1 ;; \
+		esac; \
+	fi
+
+chez: static-supported-check $(SCHEME)
 
 # Two-phase build:
 #   1. Configure with --pb and run `make bootquick XM=$(CHEZ_MACHINE_TYPE)` so
@@ -242,7 +255,7 @@ build: chez
 BINARY_ENTRY ?= support/binary-entry.ss
 BINARY_OUTPUT ?= jerboa-bin
 binary: chez build
-	SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT)
+	SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) BINARY_STATIC=$(STATIC) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT)
 
 # ── jerbuild standalone binary ───────────────────────────────────────────────
 # Builds a self-contained ./jerbuild that bundles Chez + the Jerboa stdlib
@@ -253,7 +266,7 @@ binary: chez build
 #   ./jerbuild <src> <lib>       # transpile
 .PHONY: jerbuild jerbuild-smoke
 jerbuild: chez build
-	SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) support/build-jerbuild.sh
+	SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) JERBUILD_STATIC=$(STATIC) support/build-jerbuild.sh
 
 # End-to-end smoke test: with no JERBOA_HOME and only ./jerbuild, transpile a
 # small (export greet) module, then exec a script that imports + calls it.
@@ -527,6 +540,11 @@ sbom:
 reproducibility-report:
 	JERBOA_REPRO_DIR="$(REPRO_DIR)" support/reproducibility-report.sh
 
+reproducibility-compare:
+	@test -n "$(REPRO_A)" || { echo "ERROR: set REPRO_A=path/to/first/reproducibility-report" >&2; exit 1; }
+	@test -n "$(REPRO_B)" || { echo "ERROR: set REPRO_B=path/to/second/reproducibility-report" >&2; exit 1; }
+	support/reproducibility-compare.sh "$(REPRO_A)" "$(REPRO_B)"
+
 release-evidence:
 	mkdir -p "$(EVIDENCE_DIR)"
 	git rev-parse HEAD > "$(EVIDENCE_DIR)/git-commit.txt"
@@ -778,7 +796,7 @@ mcp-test-binary: jmcp mcp-check
 # Native self-contained binary for the host (./jmcp). Embeds data/*.sexp.
 jmcp: chez build mcp-check
 	@echo "=== Building native ./jmcp (host) ==="
-	@JERBOA_HOME=$(JERBOA_HOME) SCHEME=$(SCHEME) JMCP_OUTPUT=jmcp mcp/build-jmcp.sh
+	@JERBOA_HOME=$(JERBOA_HOME) SCHEME=$(SCHEME) JMCP_OUTPUT=jmcp JMCP_STATIC=$(STATIC) mcp/build-jmcp.sh
 
 # Cross-built binaries reuse jerboa's own cross prefixes (.chez-cross-<m>) and
 # xpatch (build/chez/xc-<m>/s/xpatch). The native host build (make jmcp) covers
diff --git a/data/anti-patterns.sexp b/data/anti-patterns.sexp
index affab2a..7384f32 100644
--- a/data/anti-patterns.sexp
+++ b/data/anti-patterns.sexp
@@ -1256,21 +1256,21 @@
      "jerboa_run_tests"))
  (("advice"
     .
-    "Treat std sandbox modules as non-enforcing unless the scanner and runtime evidence prove otherwise. Either remove the dependency and state that kernel sandboxing is not claimed, or implement a verified native enforcement layer and add target-specific tests/evidence. Rerun jerboa_security_scan at medium severity or stricter.")
+    "Treat sandbox enforcement claims as platform-specific unless the scanner, docs, and runtime tests prove the exact path. Current std seccomp/Landlock modules use real Linux kernel enforcement, but static-musl/raw-libc fallback paths and unsupported kernels still need explicit evidence or degraded-status wording. Rerun jerboa_security_scan at medium severity or stricter.")
    ("avoid"
      .
-     "Do not import Jerboa std seccomp/Landlock stubs and document them as real production sandbox enforcement without proof that they enforce kernel policy on the target platform.")
+     "Do not document seccomp/Landlock as production enforcement for every target without proof that the selected module and build mode enforce kernel policy on that target.")
    ("id" . "std-security-stub-production-claim")
    ("kinds" "security" "ffi" "docs")
    ("pattern"
      .
      "std security (seccomp|landlock)|seccomp-.*install|landlock-.*install")
    ("severity" . "high")
-   ("tags" "seccomp" "landlock" "sandbox" "stubs" "production"
+   ("tags" "seccomp" "landlock" "sandbox" "kernel" "production"
      "scanner")
    ("title"
      .
-     "Do Not Claim Std Sandbox Stubs As Production Enforcement")
+     "Do Not Overclaim Sandbox Enforcement Across Targets")
    ("tools"
      "jerboa_security_scan"
      "rg"
diff --git a/data/changelog.sexp b/data/changelog.sexp
index fddb795..80ea595 100644
--- a/data/changelog.sexp
+++ b/data/changelog.sexp
@@ -2,7 +2,19 @@
    .
    "Machine-readable changelog of Jerboa API drift. Consumers (LLM tooling, lints, jerboa_verify) use this to invalidate stale recommendations and to suggest migrations when a symbol is renamed or relocated.")
   ("entries"
-    (("added" "*sandbox-max-memory-size*"
+    (("added")
+      ("date" . "2026-06-25")
+      ("modules_added")
+      ("moved")
+      ("notes"
+        .
+        "Build/release metadata now exposes STATIC=1 as a guarded native static profile for Linux/FreeBSD/OpenBSD/NetBSD. The profile installs Chez under .chez-static, passes --static to the vendored Chez configure step, and propagates static link mode to binary, jerbuild, and jmcp. make reproducibility-compare now compares two independent reproducibility-report directories for cross-builder evidence. The stale seccomp-landlock-stub-production security rule was removed because current std security seccomp/Landlock modules use real kernel enforcement; static-musl/raw-libc caveats remain covered by sandbox-ffi-raw-libc-static-noop.")
+      ("removed" ("seccomp-landlock-stub-production"))
+      ("renamed")
+      ("tier_changes")
+      ("tools_added" "reproducibility-compare")
+      ("version" . "v0.2.3"))
+     (("added" "*sandbox-max-memory-size*"
         "sandbox-config-max-memory-size")
       ("date" . "2026-06-25")
       ("modules_added")
diff --git a/data/cookbooks.sexp b/data/cookbooks.sexp
index 0151a1e..6597238 100644
--- a/data/cookbooks.sexp
+++ b/data/cookbooks.sexp
@@ -6495,4 +6495,15 @@
      "std-security-sandbox" "rlimit")
    ("title"
      .
-     "Cap run-safe Child Memory with max-memory-size")))
+     "Cap run-safe Child Memory with max-memory-size"))
+ (("code"
+    .
+    "# Native static build on supported ELF/BSD hosts.\n# Uses .chez-static so the dynamic .chez install is not reused.\nmake chez STATIC=1\nmake binary STATIC=1\nmake jerbuild STATIC=1\nmake jmcp STATIC=1\n\n# Local two-pass reproducibility report.\nmake reproducibility-report\n\n# Compare reports from two independent builders.\nmake reproducibility-compare \\\n  REPRO_A=dist/reproducibility \\\n  REPRO_B=/path/to/other/reproducibility") ("id" . "native-static-and-repro-compare") ("imports")
+   ("notes"
+     .
+     "STATIC=1 is intentionally rejected for native macOS because Mach-O static system binaries are not a supported release target. Use jerboa-portable or Linux musl cross/release targets for static release artifacts. reproducibility-compare expects each input directory to be produced by make reproducibility-report and checks status=match, git commit, SOURCE_DATE_EPOCH, program image mode, binary/program/bundle hashes when present, symlink manifests, source manifests, and object-cache manifests.")
+   ("tags" "makefile" "static" "reproducibility"
+     "release-evidence" "chez" "jmcp")
+   ("title"
+     .
+     "Build Native Static Artifacts and Compare Reproducibility Reports")))
diff --git a/data/security-rules.sexp b/data/security-rules.sexp
index f8a89f8..5431d21 100644
--- a/data/security-rules.sexp
+++ b/data/security-rules.sexp
@@ -394,18 +394,6 @@
    ("title"
      .
      "FASL deserialization in privilege separation channel"))
- (("id" . "seccomp-landlock-stub-production")
-   ("message"
-     .
-     "seccomp-install! and landlock-install! are currently stubs that record policy but do NOT enforce it. Using them creates a false sense of security.")
-   ("pattern" . "seccomp-install!|landlock-install!")
-   ("remediation"
-     .
-     "These functions now print a WARNING to stderr. Do not rely on them for actual sandboxing until real kernel enforcement is implemented. Use privsep + restricted environments for actual isolation.")
-   ("scope" . "scheme") ("severity" . "medium")
-   ("title"
-     .
-     "Seccomp/Landlock stub used in production without enforcement"))
  (("id" . "fork-without-child-limit")
    ("message"
      .
diff --git a/docs/bundling-chez.md b/docs/bundling-chez.md
index dd99c38..7f86fe4 100644
--- a/docs/bundling-chez.md
+++ b/docs/bundling-chez.md
@@ -19,7 +19,7 @@ part of the normal build. This doc records how that works and what's left.
 | Cross-compilation | **Done** — `make chez-cross` for Linux/FreeBSD/macOS-x86 targets |
 | Docker | **Done** — image builds the same `vendor/ChezScheme` (glibc + a `--static` musl variant) |
 | `--static` musl | **Done** — `support/musl-chez-build*.sh`, Docker musl stage |
-| `--static` everywhere | **Partial** — Linux musl and FreeBSD release paths are wired; a single native `STATIC=1` knob is still not generalized |
+| Native `STATIC=1` | **Done for ELF/BSD** — `make ... STATIC=1` uses `.chez-static`, passes Chez `--static`, and links supported native binaries statically on Linux/FreeBSD/OpenBSD/NetBSD; macOS rejects the knob explicitly because Mach-O static system binaries are not a supported release target |
 | Licensing / attribution | **Done** — `LICENSE-CHEZ` + README + `--version`; all bundled deps permissive; lz4 reduced to BSD `lib/` + LICENSE (zero GPL) |
 | Distribution story | **Done** — `jerboa-portable`, `release-artifact(s)`, signing, SBOM, and release evidence targets exist |
 
@@ -117,11 +117,15 @@ dirs; supports multiple independent instances.
    `make -C lz4/lib liblz4.a` (exit 0). That `lib/Makefile` edit is a local
    divergence to re-apply on any lz4 re-vendor.
 
-2. **Generalize `--static` (partial).** Static, hermetic Chez is wired for
-   Linux/musl (`musl-chez-build*.sh`, Docker musl, `jerboa-linux-*`) and the
-   FreeBSD release path uses the system static toolchain. A single native knob
-   (e.g. `make chez STATIC=1`, including macOS behavior that does not depend on
-   `dlopen`) still does not exist.
+2. **Generalize `--static` (done for supported native static targets).**
+   Static, hermetic Chez is wired for Linux/musl (`musl-chez-build*.sh`,
+   Docker musl, `jerboa-linux-*`) and the FreeBSD release path uses the system
+   static toolchain. Native `make ... STATIC=1` now installs Chez under
+   `.chez-static`, passes `--static` to the vendored Chez configure step, and
+   propagates the static link mode to `binary`, `jerbuild`, and `jmcp` on
+   Linux/FreeBSD/OpenBSD/NetBSD. macOS is intentionally out of scope for native
+   static system binaries; the build fails early there instead of silently
+   producing a dynamic Mach-O.
 
 3. **Distribution story (done).** `make jerboa-portable` stages the supported
    target directories, `make release-artifact` packages one target, and
diff --git a/docs/release-artifacts.md b/docs/release-artifacts.md
index a952844..49ba92b 100644
--- a/docs/release-artifacts.md
+++ b/docs/release-artifacts.md
@@ -106,6 +106,12 @@ information. Release promotion requires the reproducibility report to record
 default program image kind is `compile-program`; WPO artifacts require separate
 opt-in evidence before they can be promoted.
 
+To compare reports from two independent builders:
+
+```sh
+make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility
+```
+
 Before uploading production artifacts, sign them:
 
 ```sh
diff --git a/docs/release-security.md b/docs/release-security.md
index 0e7f89b..908a3da 100644
--- a/docs/release-security.md
+++ b/docs/release-security.md
@@ -84,6 +84,16 @@ with `binary_status=match`, `program_image_status=match`, `bundle_status=match`,
 `link_status=match`, `source_manifest_status=match`,
 `object_cache_equivalence_status=match`, and `status=match`.
 
+Independent-builder evidence is checked with:
+
+```bash
+make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility
+```
+
+That target compares the two report statuses, git commit, `SOURCE_DATE_EPOCH`,
+binary hash, program image hash when present, bundle hash when present, symlink
+manifest, and tracked source manifest. It exits nonzero on mismatch.
+
 Current local evidence records `binary_status=match`,
 `program_image_kind=compile-program`, `program_image_status=match`,
 `program_so_status=match`, `program_wpo_status=missing`,
diff --git a/docs/safety-guide.md b/docs/safety-guide.md
index 06e94af..5572de0 100644
--- a/docs/safety-guide.md
+++ b/docs/safety-guide.md
@@ -775,12 +775,16 @@ produce a "multiple definitions" warning. This is cosmetic but means symbol
 resolution order could theoretically surprise you in edge cases. In practice,
 the safe version always wins because it is bound last.
 
-### Reproducibility Evidence Is Local
+### Reproducibility Evidence Needs Independent Comparison
 
 `make reproducibility-report` performs a local two-build comparison of the
 multicall binary, object cache, bundle, links, and source manifest, and exits
-nonzero on mismatch. Cross-machine reproducibility still requires comparing
-those reports from independent builders.
+nonzero on mismatch. Cross-machine reproducibility is checked by comparing
+reports from independent builders:
+
+```bash
+make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility
+```
 
 ---
 
diff --git a/mcp/build-jmcp.sh b/mcp/build-jmcp.sh
index 900c394..f372593 100755
--- a/mcp/build-jmcp.sh
+++ b/mcp/build-jmcp.sh
@@ -323,6 +323,12 @@ cat > "$builder" <<'SCHEME'
       [(and static? (string=? target-os "linux"))
        (format "~a -O2~a -static -Wl,--export-dynamic -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -Wl,--defsym=_dl_find_object=0 -lm -ldl -lpthread"
                ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)]
+      [(and static? (string=? target-os "freebsd"))
+       (format "~a -O2~a -static -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil"
+               ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)]
+      [(and static? (string=? target-os "macos"))
+       (error 'build-jmcp-binary
+              "JMCP_STATIC=1 is not supported for native macOS; use portable/cross Linux musl release artifacts")]
       [(string=? target-os "freebsd")
        (format "~a -O2~a -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil -lncurses"
                ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)]
diff --git a/support/build-binary.sh b/support/build-binary.sh
index 368f55e..e77b726 100755
--- a/support/build-binary.sh
+++ b/support/build-binary.sh
@@ -24,6 +24,7 @@ SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}"
 JERBOA_CHEZ_PREFIX="${JERBOA_CHEZ_PREFIX:-$JERBOA_HOME/.chez}"
 BINARY_LIBDIRS="${BINARY_LIBDIRS:-${LIBDIRS:-$JERBOA_HOME/lib}}"
 BINARY_STATIC_ENV="${BINARY_STATIC_ENV:-}"
+BINARY_STATIC="${BINARY_STATIC:-${STATIC:-}}"
 
 # ── Cross-compilation parameters (all optional; unset = native build) ────────
 # TARGET_MACHINE       Chez machine type to emit (e.g. ta6osx, ta6le).
@@ -33,6 +34,13 @@ TARGET_MACHINE="${TARGET_MACHINE:-}"
 JERBOA_CROSS_PREFIX="${JERBOA_CROSS_PREFIX:-}"
 JERBOA_XPATCH="${JERBOA_XPATCH:-}"
 
+flag_enabled() {
+    case "${1:-}" in
+        ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;;
+        *) return 0 ;;
+    esac
+}
+
 if [ -n "$TARGET_MACHINE" ]; then
     CROSS_BUILD=yes
     [ -n "$JERBOA_CROSS_PREFIX" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not" >&2; exit 1; }
@@ -102,16 +110,30 @@ else
 fi
 CC="${CC:-$CC_DEFAULT}"
 
+if flag_enabled "$BINARY_STATIC"; then
+    case "$TARGET_OS" in
+        Linux)
+            OS_LIBS="-lm -ldl -lpthread -static"
+            BINARY_STATIC_ENV=1
+            ;;
+        FreeBSD|OpenBSD|NetBSD)
+            OS_LIBS="-lm -lpthread -static"
+            BINARY_STATIC_ENV=1
+            ;;
+        Darwin)
+            echo "ERROR: BINARY_STATIC=1 is not supported for native macOS; use jerboa-portable/cross Linux musl for static release artifacts." >&2
+            exit 1
+            ;;
+        *)
+            echo "ERROR: BINARY_STATIC=1 is not supported for target OS '$TARGET_OS'." >&2
+            exit 1
+            ;;
+    esac
+fi
+
 # Allow caller to override link libs entirely (musl-static, etc.).
 OS_LIBS="${OS_LIBS_OVERRIDE:-$OS_LIBS}"
 
-flag_enabled() {
-    case "${1:-}" in
-        ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;;
-        *) return 0 ;;
-    esac
-}
-
 target_hardening_cflags() {
     target_os="$1"
     machine="$2"
diff --git a/support/build-jerbuild.sh b/support/build-jerbuild.sh
index 38e88d8..3084721 100755
--- a/support/build-jerbuild.sh
+++ b/support/build-jerbuild.sh
@@ -24,6 +24,7 @@ SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}"
 JERBOA_CHEZ_PREFIX="${JERBOA_CHEZ_PREFIX:-$JERBOA_HOME/.chez}"
 CC="${CC:-cc}"
 OUTPUT="${OUTPUT:-jerbuild}"
+JERBUILD_STATIC="${JERBUILD_STATIC:-${STATIC:-}}"
 
 # ── Cross-compilation parameters (all optional; unset = native build) ────────
 # TARGET_MACHINE       Chez machine type to emit (e.g. ta6osx, ta6le).
@@ -33,6 +34,13 @@ TARGET_MACHINE="${TARGET_MACHINE:-}"
 JERBOA_CROSS_PREFIX="${JERBOA_CROSS_PREFIX:-}"
 JERBOA_XPATCH="${JERBOA_XPATCH:-}"
 
+flag_enabled() {
+    case "${1:-}" in
+        ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;;
+        *) return 0 ;;
+    esac
+}
+
 if [ -n "$TARGET_MACHINE" ]; then
     CROSS_BUILD=yes
     [ -n "$JERBOA_CROSS_PREFIX" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not" >&2; exit 1; }
@@ -86,12 +94,24 @@ else
     esac
 fi
 
-flag_enabled() {
-    case "${1:-}" in
-        ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;;
-        *) return 0 ;;
+if flag_enabled "$JERBUILD_STATIC"; then
+    case "$TARGET_OS" in
+        Linux)
+            OS_LIBS="-lm -ldl -lpthread -static"
+            ;;
+        FreeBSD|OpenBSD|NetBSD)
+            OS_LIBS="-lm -lpthread -static"
+            ;;
+        Darwin)
+            echo "ERROR: JERBUILD_STATIC=1 is not supported for native macOS; use jerboa-portable/cross Linux musl for static release artifacts." >&2
+            exit 1
+            ;;
+        *)
+            echo "ERROR: JERBUILD_STATIC=1 is not supported for target OS '$TARGET_OS'." >&2
+            exit 1
+            ;;
     esac
-}
+fi
 
 target_hardening_cflags() {
     target_os="$1"
diff --git a/support/reproducibility-compare.sh b/support/reproducibility-compare.sh
new file mode 100755
index 0000000..07b0632
--- /dev/null
+++ b/support/reproducibility-compare.sh
@@ -0,0 +1,168 @@
+#!/bin/sh
+set -eu
+
+if [ "$#" -ne 2 ]; then
+    echo "Usage: $0 <repro-report-a> <repro-report-b>" >&2
+    exit 1
+fi
+
+A=$1
+B=$2
+fail=0
+
+note() {
+    printf '%s\n' "$*"
+}
+
+mark_fail() {
+    note "ERROR: $*"
+    fail=1
+}
+
+require_file() {
+    if [ ! -f "$1" ]; then
+        mark_fail "missing required report file: $1"
+        return 1
+    fi
+}
+
+field_value() {
+    file=$1
+    key=$2
+    awk -F= -v key="$key" '$1 == key {print substr($0, length(key) + 2); found=1; exit} END {if (!found) exit 1}' "$file"
+}
+
+compare_kv_field() {
+    rel=$1
+    key=$2
+    fa="$A/$rel"
+    fb="$B/$rel"
+    if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then
+        mark_fail "missing key/value file for $key: $rel"
+        return
+    fi
+    va=$(field_value "$fa" "$key" 2>/dev/null || true)
+    vb=$(field_value "$fb" "$key" 2>/dev/null || true)
+    if [ -z "$va" ] || [ -z "$vb" ]; then
+        mark_fail "missing field $key in $rel"
+    elif [ "$va" != "$vb" ]; then
+        mark_fail "$rel field mismatch for $key: '$va' != '$vb'"
+    else
+        note "$rel:$key=$va"
+    fi
+}
+
+compare_field() {
+    key=$1
+    va=$(field_value "$A/result.txt" "$key" 2>/dev/null || true)
+    vb=$(field_value "$B/result.txt" "$key" 2>/dev/null || true)
+    if [ -z "$va" ] || [ -z "$vb" ]; then
+        mark_fail "missing result field: $key"
+    elif [ "$va" != "$vb" ]; then
+        mark_fail "result field mismatch for $key: '$va' != '$vb'"
+    else
+        note "$key=$va"
+    fi
+}
+
+first_hash() {
+    awk 'NF >= 1 {print $1; exit}' "$1"
+}
+
+compare_hash_file() {
+    rel=$1
+    fa="$A/$rel"
+    fb="$B/$rel"
+    if [ ! -f "$fa" ] && [ ! -f "$fb" ]; then
+        note "$rel=missing-in-both"
+        return
+    fi
+    if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then
+        mark_fail "hash file presence mismatch: $rel"
+        return
+    fi
+    ha=$(first_hash "$fa")
+    hb=$(first_hash "$fb")
+    if [ "$ha" != "$hb" ]; then
+        mark_fail "hash mismatch for $rel: $ha != $hb"
+    else
+        note "$rel=$ha"
+    fi
+}
+
+compare_manifest() {
+    rel=$1
+    fa="$A/$rel"
+    fb="$B/$rel"
+    if [ ! -f "$fa" ] && [ ! -f "$fb" ]; then
+        note "$rel=missing-in-both"
+        return
+    fi
+    if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then
+        mark_fail "manifest presence mismatch: $rel"
+        return
+    fi
+    if cmp -s "$fa" "$fb"; then
+        note "$rel=match"
+    else
+        mark_fail "manifest mismatch: $rel"
+    fi
+}
+
+require_file "$A/result.txt" || exit 1
+require_file "$B/result.txt" || exit 1
+
+status_a=$(field_value "$A/result.txt" status 2>/dev/null || true)
+status_b=$(field_value "$B/result.txt" status 2>/dev/null || true)
+if [ "$status_a" != match ] || [ "$status_b" != match ]; then
+    mark_fail "both reports must have status=match before cross-report comparison"
+fi
+
+compare_kv_field build-env.txt git_commit
+compare_kv_field build-env.txt source_date_epoch
+
+for key in \
+    program_image_kind \
+    binary_status \
+    program_image_status \
+    bundle_status \
+    link_status \
+    source_manifest_status \
+    object_cache_equivalence_status \
+    measurement_mode
+do
+    compare_field "$key"
+done
+
+for rel in \
+    first-jerboa.sha256 \
+    second-jerboa.sha256 \
+    first-program.so.sha256 \
+    second-program.so.sha256 \
+    first-program.wp.so.sha256 \
+    second-program.wp.so.sha256 \
+    first-bundle.sha256 \
+    second-bundle.sha256
+do
+    compare_hash_file "$rel"
+done
+
+for rel in \
+    source-before.sha256 \
+    source-after.sha256 \
+    first-links.txt \
+    second-links.txt \
+    warm-object-cache.sha256 \
+    warm-multicall-object-cache.sha256 \
+    first-fresh-object-cache.sha256 \
+    second-fresh-object-cache.sha256
+do
+    compare_manifest "$rel"
+done
+
+if [ "$fail" -ne 0 ]; then
+    note "status=mismatch"
+    exit 1
+fi
+
+note "status=match"