Move release distribution to SourceHut

ober

bb6340c5d5fe841b8c5609759e6e750c93b2a762

diff --git a/.github/workflows/build-macos-libs.yml b/.github/workflows/build-macos-libs.yml
deleted file mode 100644
index e563c07..0000000
--- a/.github/workflows/build-macos-libs.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: Build macOS Rust Libraries
-
-on:
-  workflow_dispatch:  # manual trigger from GitHub UI or gh CLI
-  push:
-    paths:
-      - 'jerboa-native-rs/**'
-      - 'support/container-dependencies.lock'
-      - 'support/container-inputs.sh'
-      - '.github/workflows/build-macos-libs.yml'
-
-env:
-  RELEASE_TAG: macos-libs-v1
-
-jobs:
-  build:
-    strategy:
-      matrix:
-        include:
-          - runner: macos-14        # Apple Silicon (M1+)
-            arch: arm64
-          - runner: macos-13        # Intel
-            arch: x86_64
-    runs-on: ${{ matrix.runner }}
-    steps:
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-
-      - name: Install Rust toolchain
-        uses: dtolnay/rust-toolchain@8fae6aadcd3e1a6f3fcb446ce7f9438c9d1349f1 # 1.94.1
-        with:
-          toolchain: 1.94.1
-
-      - name: Build libjerboa_native.a
-        run: |
-          cd jerboa-native-rs
-          cargo build --release
-          ls -lh target/release/libjerboa_native.a
-
-      - name: Materialize pinned jerboa-shell (for rust-coreutils)
-        run: support/container-inputs.sh fetch-git jerboa-shell
-
-      - name: Build libjsh_coreutils.a
-        run: |
-          cd /tmp/jerboa-shell/rust-coreutils
-          cargo build --release
-          ls -lh target/release/libjsh_coreutils.a
-
-      - name: Stage artifacts with arch suffix
-        run: |
-          cp jerboa-native-rs/target/release/libjerboa_native.a \
-            libjerboa_native-macos-${{ matrix.arch }}.a
-          cp /tmp/jerboa-shell/rust-coreutils/target/release/libjsh_coreutils.a \
-            libjsh_coreutils-macos-${{ matrix.arch }}.a
-          ls -lh *.a
-
-      - name: Upload to GitHub release
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        run: |
-          # Create the release if it doesn't exist yet
-          gh release view "$RELEASE_TAG" --repo ${{ github.repository }} >/dev/null 2>&1 || \
-            gh release create "$RELEASE_TAG" \
-              --repo ${{ github.repository }} \
-              --title "macOS Pre-built Rust Libraries" \
-              --notes "Pre-built static Rust libraries for macOS jsh builds (no Rust toolchain required).
-
-          Assets:
-          - libjerboa_native — crypto, regex, compression
-          - libjsh_coreutils — Rust coreutils
-
-          Built automatically by CI for arm64 (Apple Silicon) and x86_64 (Intel)."
-          gh release upload "$RELEASE_TAG" \
-            libjerboa_native-macos-${{ matrix.arch }}.a \
-            libjsh_coreutils-macos-${{ matrix.arch }}.a \
-            --repo ${{ github.repository }} --clobber
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 7e1bb56..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,134 +0,0 @@
-name: CI
-
-on:
-  push:
-    branches: [master]
-  pull_request:
-  schedule:
-    # Daily security maintenance: `make audit` refreshes advisories and the
-    # scheduled-only deep fuzz step exercises the checked-in corpora.
-    - cron: '23 9 * * *'
-
-env:
-  IMAGE: docker.io/jerboa21/jerboa
-
-jobs:
-  build-and-test:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-
-      - name: Verify immutable container inputs
-        id: container-lock
-        run: |
-          support/container-inputs.sh check
-          echo "base_image=$(support/container-inputs.sh field ubuntu-24.04 3)" >> "$GITHUB_OUTPUT"
-
-      - name: Build base image (loaded locally)
-        run: |
-          podman build --platform linux/amd64 -f Containerfile \
-            --build-arg JERBOA_BASE_IMAGE="${{ steps.container-lock.outputs.base_image }}" \
-            -t "${{ env.IMAGE }}:ci" .
-
-      - name: Run core tests
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make test SCHEME=scheme
-
-      - name: Build libraries
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make build SCHEME=scheme
-
-      - name: Run production security audit
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make audit SCHEME=scheme
-
-      - name: Run fuzz smoke
-        timeout-minutes: 15
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make fuzz-smoke SCHEME=scheme
-
-      - name: Run scheduled deep fuzz
-        if: github.event_name == 'schedule'
-        timeout-minutes: 60
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make fuzz-deep SCHEME=scheme
-
-      - name: Run feature tests (informational)
-        continue-on-error: true
-        timeout-minutes: 10
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make test-features SCHEME=scheme
-
-      - name: Check doc examples (parse-only)
-        # NOTE: ~20 fences across docs/ currently have real parse errors
-        # (mismatched parens etc). Informational until they are fixed.
-        continue-on-error: true
-        timeout-minutes: 10
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make check-docs SCHEME=scheme
-
-      - name: Check doc examples (strict, imports resolved)
-        continue-on-error: true
-        timeout-minutes: 15
-        run: |
-          podman run --rm \
-            -v "${{ github.workspace }}:/workspace" -w /workspace \
-            -e JERBOA=/workspace/lib -e JERBOA_HOME=/workspace \
-            ${{ env.IMAGE }}:ci \
-            make check-docs-strict SCHEME=scheme
-
-  push-image:
-    needs: build-and-test
-    if: github.ref == 'refs/heads/master' && github.event_name == 'push'
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-
-      - name: Verify immutable container inputs
-        id: container-lock
-        run: |
-          support/container-inputs.sh check
-          echo "base_image=$(support/container-inputs.sh field ubuntu-24.04 3)" >> "$GITHUB_OUTPUT"
-
-      - name: Login to container registry
-        run: |
-          podman login docker.io \
-            --username "${{ secrets.REGISTRY_USERNAME }}" \
-            --password-stdin <<< "${{ secrets.REGISTRY_TOKEN }}"
-
-      - name: Build and push (latest + sha)
-        run: |
-          podman build --platform linux/amd64 -f Containerfile \
-            --build-arg JERBOA_BASE_IMAGE="${{ steps.container-lock.outputs.base_image }}" \
-            -t "${{ env.IMAGE }}:latest" \
-            -t "${{ env.IMAGE }}:${{ github.sha }}" .
-          podman push "${{ env.IMAGE }}:latest"
-          podman push "${{ env.IMAGE }}:${{ github.sha }}"
diff --git a/.github/workflows/platform-sandbox.yml b/.github/workflows/platform-sandbox.yml
deleted file mode 100644
index 505bb56..0000000
--- a/.github/workflows/platform-sandbox.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Platform Sandbox
-
-on:
-  workflow_dispatch:
-  pull_request:
-    paths:
-      - '.github/workflows/platform-sandbox.yml'
-      - 'lib/std/os/**'
-      - 'lib/std/security/**'
-      - 'tests/test-seatbelt.ss'
-      - 'tests/test-worker.ss'
-  push:
-    branches: [master]
-    paths:
-      - '.github/workflows/platform-sandbox.yml'
-      - 'lib/std/os/**'
-      - 'lib/std/security/**'
-      - 'tests/test-seatbelt.ss'
-      - 'tests/test-worker.ss'
-
-jobs:
-  macos-seatbelt:
-    runs-on: macos-14
-    steps:
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-
-      - name: Build Scheme libraries
-        run: make build
-
-      - name: Run Seatbelt platform tests
-        run: |
-          .chez/bin/scheme --libdirs lib:vendor/jsqlite/src --script tests/test-seatbelt.ss
-          .chez/bin/scheme --libdirs lib:vendor/jsqlite/src --script tests/test-worker.ss
diff --git a/.github/workflows/security-baseline.yml b/.github/workflows/security-baseline.yml
deleted file mode 100644
index 5381c31..0000000
--- a/.github/workflows/security-baseline.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: Security Baseline
-
-on:
-  push:
-    branches: [main, master]
-  pull_request:
-  workflow_dispatch:
-
-permissions:
-  contents: read
-
-jobs:
-  baseline:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-
-      - name: Required release files
-        run: |
-          set -eu
-          test -f LICENSE
-          test -f SECURITY.md
-          test -f .gitignore
-          find . -maxdepth 1 -iname "README*" -type f | grep -q .
-
-      - name: High-confidence secret scan
-        run: |
-          set -eu
-          pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})"
-          matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)"
-          if [ -n "$matches" ]; then
-            echo "$matches"
-            echo "High-confidence secret pattern found."
-            exit 1
-          fi
diff --git a/AGENTS.md b/AGENTS.md
index e8d4d0d..0dec250 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -370,6 +370,19 @@ improved versions of the above.
 
 When working in a Jerboa project, **ONLY modify files in the current repo** unless the user explicitly names another path.
 
+### Release Distribution: SourceHut Only
+
+**Never use GitHub Releases, GitHub release URLs, or GitHub as a fallback for
+Jerboa release artifacts.** Jerboa's sole official release channel is attached
+files on a versioned SourceHut git tag. Publish them with
+`hut git -r ~lisp/jerboa artifact upload <file> --rev <tag>` (normally through
+`make release-upload`) and download them from
+`https://git.sr.ht/~lisp/jerboa/refs/download/<tag>/<artifact>`.
+
+Do not add a GitHub mirror or fallback when SourceHut delivery needs work;
+repair or configure the SourceHut release path instead. See the SourceHut
+manual: <https://man.sr.ht/git.sr.ht/#attaching-files-to-releases>.
+
 Common sibling repos that exist but must NOT be touched without explicit instruction:
 - `~/mine/jerboa-mcp` — Legacy node MCP, superseded. The active MCP server now lives in THIS repo at `mcp/` + `data/`. Don't modify the legacy repo unless told.
 - `~/mine/jerboa-shell` — Only modify when user explicitly says to work there.
diff --git a/docs/release-artifacts.md b/docs/release-artifacts.md
index feeb972..f36298b 100644
--- a/docs/release-artifacts.md
+++ b/docs/release-artifacts.md
@@ -3,6 +3,21 @@
 Jerboa publishes project-local toolchain tarballs for package repos that should
 build without cloning and compiling this repository first.
 
+## Distribution Channel
+
+SourceHut git-tag artifacts are the only official release channel. Never
+publish or download Jerboa release artifacts through GitHub Releases, including
+as a mirror or fallback. Attach each signed release file to its versioned
+SourceHut tag with `hut git -r ~lisp/jerboa artifact upload <file> --rev <tag>`;
+`make release-upload` performs this for the complete release set. Consumers
+download assets at:
+
+```text
+https://git.sr.ht/~lisp/jerboa/refs/download/<tag>/<artifact>
+```
+
+See [SourceHut's release-artifact manual](https://man.sr.ht/git.sr.ht/#attaching-files-to-releases).
+
 Each release target produces:
 
 ```text
diff --git a/support/install.sh b/support/install.sh
index 9f54371..567dc7d 100755
--- a/support/install.sh
+++ b/support/install.sh
@@ -192,19 +192,11 @@ esac
 
 file="jerboa-${version}-${target}.tar.gz"
 
-# Release base URLs, tried in order. SourceHut is canonical, but the go-away
-# anti-LLM proxy in front of git.sr.ht 404s anonymous scripted downloads of
-# /refs/download/<tag>/<file>. GitHub Releases is an anonymous-CDN mirror of
-# the exact same signed artifacts and serves as a fallback. Override with
-# JERBOA_RELEASE_BASE (single URL) or JERBOA_RELEASE_BASES (space separated).
+# Release base URL. SourceHut git-tag artifacts are the only release download
+# channel. Override it with JERBOA_RELEASE_BASE when using an independently
+# operated mirror.
 sourcehut_base="${repo_url}/refs/download/${version}"
-github_base="https://github.com/ober/jerboa/releases/download/${version}"
-default_bases="$sourcehut_base $github_base"
-release_bases=${JERBOA_RELEASE_BASES:-${JERBOA_RELEASE_BASE:-$default_bases}}
-case "$release_bases" in
-    *" "*) ;;  # multi-base fallback path
-    *) release_bases="$release_bases $github_base" ;;  # always keep mirror as last resort
-esac
+release_bases=${JERBOA_RELEASE_BASE:-$sourcehut_base}
 
 tmp_parent=$(choose_tmp_parent)
 tmp=$(mktemp -d "$tmp_parent/jerboa-install.XXXXXX")
diff --git a/tests/test-supply-chain-policy.sh b/tests/test-supply-chain-policy.sh
index d5eaa7e..b427ba9 100755
--- a/tests/test-supply-chain-policy.sh
+++ b/tests/test-supply-chain-policy.sh
@@ -50,15 +50,7 @@ awk -F '\t' '
 ' support/container-dependencies.lock || fail 'container lock still has blocked release inputs'
 must_contain support/container-dependencies.lock 'https://static.rust-lang.org/rustup/archive/1.28.2/'
 
-must_contain .github/workflows/build-macos-libs.yml 'container-inputs.sh fetch-git jerboa-shell'
-must_contain .github/workflows/ci.yml 'support/container-inputs.sh check'
-if [ "$(grep -F -- '--build-arg JERBOA_BASE_IMAGE="${{ steps.container-lock.outputs.base_image }}"' .github/workflows/ci.yml | wc -l | tr -d '[:space:]')" -ne 2 ]; then
-    fail 'container CI does not pass the authenticated base digest to every Podman build'
-fi
 must_contain Makefile 'JERBOA_REQUIRE_RELEASE_SIGNATURE_VERIFICATION=1'
-if grep -Eq 'git[[:space:]]+clone' .github/workflows/build-macos-libs.yml; then
-    fail 'macOS native build still clones a mutable jerboa-shell head'
-fi
 if grep -Eq 'git[[:space:]]+(clone|pull)' lib/jerboa/registry.ss; then
     fail 'legacy package registry still resolves a mutable Git head'
 fi
@@ -137,6 +129,23 @@ if grep -Eq 'sum_url|tar[[:space:]]+-xzf|curl' support/ensure-jerboa.sh; then
     fail 'project-local bootstrap bypasses the authenticated installer'
 fi
 
+# Versioned Jerboa toolchains are published only as SourceHut git-tag
+# artifacts.  Keep the delivery path for macOS, Linux amd64, and FreeBSD amd64
+# free of GitHub release URLs and publication commands.
+must_contain support/sourcehut-release-upload.sh 'hut git -r "$repo" artifact upload "$file" --rev "$version"'
+for release_build in \
+    .builds/release-linux-amd64.yml \
+    .builds/release-freebsd-amd64.yml; do
+    must_contain "$release_build" 'make release-upload'
+    if grep -Eiq 'github.*release|releases/download|gh[[:space:]]+release' "$release_build"; then
+        fail "$release_build retains a GitHub release path"
+    fi
+done
+if grep -Eiq 'github.*release|releases/download|gh[[:space:]]+release' \
+    support/install.sh support/sourcehut-release-upload.sh; then
+    fail 'Jerboa release delivery retains a GitHub release path'
+fi
+
 # Exercise the complete signed-manifest path with an ephemeral consumer key.
 command -v ssh-keygen >/dev/null 2>&1 || fail 'ssh-keygen is required for installer regression tests'
 command -v curl >/dev/null 2>&1 || fail 'curl is required for installer regression tests'