Skip to content

Commit 04d3c3e

Browse files
build(bootstrap): iser-style SessionStart prover bootstrap (Coq/Idris2/Zig) (#323)
## Summary Make a fresh Claude-Code-on-the-web (or devcontainer) session come up **prover-ready**, using the `hyperpolymath/iseriser` bootstrap doctrine. No more manual `apt`/build dance, and no more "403 mystery." ## Why this works (the 403, explained) The agent/CI proxy has **two independent gates**: 1. **Git-protocol repo allowlist** — scoped to `hyperpolymath/ephapax`. This is what 403s `cargo`'s `typed-wasm` git dep and `git clone idris-lang/Idris2`. 2. **HTTPS egress domain allowlist** — `github.com` / `codeload.github.com` / `raw.githubusercontent.com` / `ziglang.org` all return **200**. So the fix (the iser doctrine, documented in `ephapaxiser/scripts/install-zig.sh`) is: **fetch every toolchain by `curl`+`tar` over HTTPS, never `git clone`.** Idris2 was never unreachable — it just needs its *source tarball* from codeload, not a git clone. ## What's here Scripts — idempotent + fail-soft (a blocked egress domain degrades that one prover, never bricks startup): - `scripts/install-coq.sh` — apt (noble ships exactly 8.18.0, the pinned version) - `scripts/install-zig.sh` — ziglang.org vendor tarball (ported near-verbatim from `iseriser`) - `scripts/install-idris2.sh` — codeload source tarball + `chezscheme`/`libgmp-dev`, bootstrap-built. Idempotent via the **install location** (`$PREFIX/bin/idris2`), not just PATH — so it never re-triggers the ~15-min rebuild. - `scripts/bootstrap-provers.sh` — one entry point; persists idris2 on PATH via `$CLAUDE_ENV_FILE`. Triggers (both, as requested): - `.claude/hooks/session-start.sh` + `.claude/settings.json` — **async** SessionStart hook for Claude Code on the web. Non-blocking: session is usable immediately; Coq + Zig land in ~1–2 min, the Idris2 build finishes in the background within `asyncTimeout`. - `.devcontainer/devcontainer.json` `postCreateCommand` — for devcontainers / Codespaces (synchronous). - `just bootstrap` — manual entry point. - `.tool-versions` — pins `coq 8.18.0` / `idris2 0.7.0` / `zig 0.14.0`. ## Validated in-session - Fresh **Zig 0.14.0** install from ziglang.org → success (proves the vendor-binary path through the proxy). - **Coq 8.18.0** + **Idris2 0.7.0** idempotent-skip (no rebuild) — including under a clean PATH (the bug the install-location check fixes). - SessionStart hook emits valid async JSON and execs the bootstrap. - With the bootstrapped toolchain: `src/formal` + `src/abi` (idris2), `formal/Modality.v` (coqc), and the zig coproc test **all build**. ## Hook execution mode **Asynchronous** (you asked for non-blocking, given the ~15-min Idris2 build). - *Pro:* session starts immediately; provers provision in the background. - *Con:* a step that needs a prover in the first ~15 min may briefly find it absent (fail-soft, so it just retries). Happy to switch the hook to synchronous if you'd rather guarantee provers-before-prompt. Once merged to the default branch, all future web sessions use it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AGFqWzByua4dKbA7W7oVsL --- _Generated by [Claude Code](https://claude.ai/code/session_01AGFqWzByua4dKbA7W7oVsL)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 47c8092 commit 04d3c3e

9 files changed

Lines changed: 295 additions & 0 deletions

File tree

.claude/hooks/session-start.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# SessionStart hook for Claude Code on the web. Bootstraps the formal-
6+
# methods provers (Coq / Idris2 / Zig) via scripts/bootstrap-provers.sh so
7+
# a fresh remote session can run the proofs without a manual install dance.
8+
#
9+
# ASYNC (non-blocking): the first stdout line opts into async mode, so the
10+
# session becomes usable immediately while the provers install in the
11+
# background. Coq (apt) and Zig (vendor binary) land in ~1–2 min; the
12+
# Idris2 source build takes ~10–15 min and finishes within asyncTimeout.
13+
# Everything is idempotent + fail-soft, so re-runs and blocked egress are
14+
# both safe. PATH for idris2 is persisted via $CLAUDE_ENV_FILE.
15+
16+
set -uo pipefail
17+
18+
# Opt into async so session startup is not blocked by the ~15-min Idris2 build.
19+
echo '{"async": true, "asyncTimeout": 1200000}'
20+
21+
# Only meaningful in the remote (web) environment; devcontainers use the
22+
# postCreateCommand instead. No-op locally.
23+
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
24+
exit 0
25+
fi
26+
27+
exec "${CLAUDE_PROJECT_DIR:-.}/scripts/bootstrap-provers.sh" \
28+
> "${TMPDIR:-/tmp}/ephapax-bootstrap.log" 2>&1

.claude/settings.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
9+
}
10+
]
11+
}
12+
]
13+
}
14+
}

.devcontainer/devcontainer.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "ephapax",
3+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
4+
"features": {
5+
"ghcr.io/devcontainers/features/rust:1": {},
6+
"ghcr.io/devcontainers/features/common-utils:2": {}
7+
},
8+
"postCreateCommand": "bash scripts/bootstrap-provers.sh",
9+
"remoteEnv": {
10+
"PATH": "${containerEnv:HOME}/.idris2/bin:${containerEnv:PATH}"
11+
}
12+
}

.tool-versions

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,11 @@
22
# asdf tool versions for Ephapax development
33
rust stable
44
ocaml 5.4.1
5+
# Prover toolchain pins — the single source of truth the install scripts
6+
# default to (COQ_VERSION / IDRIS2_VERSION / ZIG_VERSION in
7+
# scripts/install-*.sh) and that CI mirrors. asdf/mise consume zig + idris2
8+
# directly; coq is provisioned via apt (noble ships exactly 8.18.0), kept
9+
# here only as the documented pin.
10+
coq 8.18.0
11+
idris2 0.7.0
12+
zig 0.14.0

Justfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ conformance:
7272
status-gate:
7373
./scripts/status-gate.sh
7474

75+
# Provision all provers (Coq / Idris2 / Zig) the -iser way: curl+tar from
76+
# vendor/codeload over HTTPS, never git; idempotent + fail-soft. Same entry
77+
# point the SessionStart hook and the devcontainer postCreate use.
78+
bootstrap:
79+
bash scripts/bootstrap-provers.sh
80+
7581
# Build Idris2 formal proofs
7682
idris-build:
7783
cd src/formal && idris2 --build ephapax-formal.ipkg

scripts/bootstrap-provers.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# bootstrap-provers.sh — one entry point that provisions every prover the
6+
# Ephapax proofs need, using the -iser doctrine (curl+tar from vendor /
7+
# codeload over HTTPS, never `git clone`; idempotent; fail-soft).
8+
#
9+
# Coq 8.18 → formal/*.v (scripts/install-coq.sh, apt)
10+
# Idris2 0.7 → src/abi, src/formal (scripts/install-idris2.sh, codeload)
11+
# Zig 0.14 → tools/coproc/*.zig (scripts/install-zig.sh, ziglang.org)
12+
#
13+
# Why this exists: a fresh Claude-Code-on-the-web container is re-cloned
14+
# from scratch, so the provers must be (re)installed per session. This is
15+
# invoked by the SessionStart hook (.claude/hooks/session-start.sh) and by
16+
# the devcontainer postCreateCommand, and can be run by hand (`just
17+
# bootstrap`).
18+
#
19+
# Fail-soft by construction: each installer exits 0 even on failure, so a
20+
# blocked egress domain degrades gracefully (that prover is simply absent)
21+
# rather than bricking startup. Idris2 is built from source (~10–15 min);
22+
# Coq (apt) and Zig (vendor binary) are quick.
23+
#
24+
# If CLAUDE_ENV_FILE is set (Claude Code on the web), the Idris2 bin dir is
25+
# persisted onto PATH for the rest of the session.
26+
27+
set -uo pipefail
28+
29+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30+
log() { printf '[bootstrap-provers] %s\n' "$*"; }
31+
32+
# Put the Idris2 install dir on PATH up front so the installers' idempotency
33+
# checks see an already-installed idris2 and skip the ~15-min rebuild.
34+
export PATH="${PREFIX:-$HOME/.idris2}/bin:$PATH"
35+
36+
log "begin — $(date -u 2>/dev/null || echo '?')"
37+
38+
bash "$HERE/install-coq.sh" || true
39+
bash "$HERE/install-zig.sh" || true
40+
bash "$HERE/install-idris2.sh" || true
41+
42+
# Persist Idris2 on PATH for the session (Claude Code on the web).
43+
IDRIS2_BIN="${PREFIX:-$HOME/.idris2}/bin"
44+
if [ -x "$IDRIS2_BIN/idris2" ] && [ -n "${CLAUDE_ENV_FILE:-}" ]; then
45+
echo "export PATH=\"$IDRIS2_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
46+
log "persisted $IDRIS2_BIN to \$CLAUDE_ENV_FILE"
47+
fi
48+
export PATH="$IDRIS2_BIN:$PATH"
49+
50+
log "summary:"
51+
log " coq: $(command -v coqc >/dev/null 2>&1 && coqc --version 2>/dev/null | head -1 || echo 'absent')"
52+
log " idris2: $(command -v idris2 >/dev/null 2>&1 && idris2 --version 2>/dev/null | head -1 || echo 'absent')"
53+
log " zig: $(command -v zig >/dev/null 2>&1 && echo "zig $(zig version 2>/dev/null)" || echo 'absent')"
54+
log "done."

scripts/install-coq.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# install-coq.sh — provision Coq for the Ephapax formal proofs (formal/).
6+
#
7+
# Coq is the one prover that needs no curl+tar dance: Ubuntu 24.04 (noble)
8+
# ships exactly 8.18.0 (`coq 8.18.0+dfsg`), the version pinned by
9+
# formal/Justfile, pixi.toml's [feature.proofs], and .github/workflows/
10+
# coq-build.yml. apt mirrors are not GitHub, so the git-protocol repo scope
11+
# does not apply.
12+
#
13+
# Doctrine (shared with install-zig.sh / install-idris2.sh): idempotent
14+
# (early-exit if the pinned version is present) and fail-soft (never block
15+
# setup or session start — exit 0 on failure with a diagnostic).
16+
17+
set -uo pipefail
18+
19+
COQ_VERSION="${COQ_VERSION:-8.18.0}"
20+
note() { printf '[install-coq] %s\n' "$*" >&2; }
21+
22+
if command -v coqc >/dev/null 2>&1; then
23+
have="$(coqc --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
24+
if [ "$have" = "$COQ_VERSION" ]; then
25+
note "coqc $COQ_VERSION already installed — skipping."
26+
exit 0
27+
fi
28+
note "coqc present but version '$have' != '$COQ_VERSION' — (re)installing via apt."
29+
fi
30+
31+
if ! command -v apt-get >/dev/null 2>&1; then
32+
note "apt-get unavailable — install Coq $COQ_VERSION another way (opam/nix). Skipping (non-blocking)."
33+
exit 0
34+
fi
35+
36+
sudo apt-get update -qq || note "apt update failed (continuing)"
37+
if sudo apt-get install -y --no-install-recommends coq; then
38+
note "installed: $(coqc --version 2>/dev/null | head -1)"
39+
else
40+
note "apt install of coq failed — skipping (non-blocking)."
41+
exit 0
42+
fi

scripts/install-idris2.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# install-idris2.sh — provision Idris2 for the Ephapax ABI + formal proofs
6+
# (src/abi/ephapax-abi.ipkg, src/formal/ephapax-formal.ipkg).
7+
#
8+
# Follows the `iseriser/scripts/install-zig.sh` doctrine:
9+
# * curl + tar over HTTPS — NEVER `git clone`. The agent/CI proxy
10+
# scopes the GIT protocol to one repo (so `git clone idris-lang/Idris2`
11+
# → 403), but allowlists HTTPS to github.com / codeload.github.com.
12+
# A source *tarball* from codeload is a normal HTTPS GET → 200.
13+
# * pinned version (IDRIS2_VERSION), idempotent (early-exit if present),
14+
# * fail-soft: a missing Idris2 must NOT block setup or session start
15+
# (exit 0 on any fetch/build failure, with a diagnostic).
16+
#
17+
# Egress note: codeload.github.com is allowlisted by default. If your
18+
# environment's proxy is stricter, add `codeload.github.com` to the
19+
# HTTPS egress allowlist (same way ziglang.org is added for zig).
20+
21+
set -uo pipefail
22+
23+
IDRIS2_VERSION="${IDRIS2_VERSION:-0.7.0}"
24+
PREFIX="${PREFIX:-$HOME/.idris2}"
25+
SCHEME="${SCHEME:-chezscheme}"
26+
27+
note() { printf '[install-idris2] %s\n' "$*" >&2; }
28+
29+
# Idempotent: already the pinned version? Check both PATH *and* the install
30+
# location ($PREFIX/bin/idris2). The latter matters because callers may run
31+
# this before adding $PREFIX/bin to PATH, and we must NOT trigger a ~15-min
32+
# rebuild when idris2 is already installed.
33+
existing="$(command -v idris2 2>/dev/null || true)"
34+
[ -z "$existing" ] && [ -x "$PREFIX/bin/idris2" ] && existing="$PREFIX/bin/idris2"
35+
if [ -n "$existing" ]; then
36+
have="$("$existing" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
37+
if [ "$have" = "$IDRIS2_VERSION" ]; then
38+
note "idris2 $IDRIS2_VERSION already installed ($existing) — skipping."
39+
exit 0
40+
fi
41+
fi
42+
43+
# Backend + C deps from apt (no cross-repo network). These are the two
44+
# that bit us the first time: chezscheme (the codegen backend) and
45+
# libgmp-dev (gmp.h, needed by the refc support lib at `make install`).
46+
if command -v apt-get >/dev/null 2>&1; then
47+
sudo apt-get update -qq || note "apt update failed (continuing)"
48+
sudo apt-get install -y --no-install-recommends \
49+
"$SCHEME" libgmp-dev make gcc curl || note "apt install of backend deps failed"
50+
fi
51+
if ! command -v "$SCHEME" >/dev/null 2>&1; then
52+
note "Chez Scheme ($SCHEME) unavailable — cannot build Idris2. Skipping (non-blocking)."
53+
exit 0
54+
fi
55+
56+
work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT
57+
url="https://codeload.github.com/idris-lang/Idris2/tar.gz/refs/tags/v${IDRIS2_VERSION}"
58+
note "fetching Idris2 ${IDRIS2_VERSION} source via HTTPS (curl, not git): $url"
59+
if ! curl -fsSL -o "$work/idris2.tar.gz" "$url"; then
60+
note "download failed (proxy/egress?). Skipping — session start not blocked."
61+
exit 0
62+
fi
63+
tar -xzf "$work/idris2.tar.gz" -C "$work" || { note "extract failed — skipping."; exit 0; }
64+
src="$work/Idris2-${IDRIS2_VERSION}"
65+
66+
note "bootstrapping (SCHEME=$SCHEME, PREFIX=$PREFIX) …"
67+
if make -C "$src" bootstrap SCHEME="$SCHEME" PREFIX="$PREFIX" \
68+
&& make -C "$src" install PREFIX="$PREFIX"; then
69+
note "installed → $PREFIX/bin/idris2"
70+
note "add to PATH: export PATH=\"$PREFIX/bin:\$PATH\""
71+
"$PREFIX/bin/idris2" --version >&2 || true
72+
else
73+
note "build/install failed — skipping (non-blocking). See logs above."
74+
exit 0
75+
fi

scripts/install-zig.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# install-zig.sh — provision Zig for ephapax's coprocessor seam
6+
# (`just build-coproc`, tools/coproc/*.zig; pixi.toml wants zig >= 0.13).
7+
#
8+
# Ported from hyperpolymath/iseriser/scripts/install-zig.sh. The doctrine:
9+
# * fetch a PINNED prebuilt binary by curl+tar straight from the vendor
10+
# (ziglang.org) — never a package manager, never git;
11+
# * idempotent (early-exit if the pinned version is already on PATH);
12+
# * fail-soft: a missing Zig must NOT block setup or session start.
13+
#
14+
# Egress note: github.com is allowlisted by the agent/CI proxy by default,
15+
# but ziglang.org must be added to the HTTPS egress allowlist explicitly.
16+
# If it is not reachable, this script logs and exits 0 (non-blocking).
17+
18+
set -uo pipefail
19+
20+
ZIG_VERSION="${ZIG_VERSION:-0.14.0}"
21+
PREFIX="${PREFIX:-/usr/local}"
22+
note() { printf '[install-zig] %s\n' "$*" >&2; }
23+
24+
if command -v zig >/dev/null 2>&1 && [ "$(zig version 2>/dev/null)" = "$ZIG_VERSION" ]; then
25+
note "zig $ZIG_VERSION already installed — skipping."
26+
exit 0
27+
fi
28+
29+
case "$(uname -m)" in
30+
x86_64|amd64) zarch=x86_64 ;;
31+
aarch64|arm64) zarch=aarch64 ;;
32+
*) note "unsupported arch $(uname -m) — install zig $ZIG_VERSION manually. Skipping."; exit 0 ;;
33+
esac
34+
case "$(uname -s)" in
35+
Linux) zos=linux ;;
36+
Darwin) zos=macos ;;
37+
*) note "unsupported OS $(uname -s) — install zig $ZIG_VERSION manually. Skipping."; exit 0 ;;
38+
esac
39+
40+
tarball="zig-${zos}-${zarch}-${ZIG_VERSION}.tar.xz"
41+
url="https://ziglang.org/download/${ZIG_VERSION}/${tarball}"
42+
work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT
43+
44+
note "fetching $url"
45+
if ! curl -fsSL -o "$work/$tarball" "$url"; then
46+
note "download failed (ziglang.org not allowlisted?). Skipping — session start not blocked."
47+
exit 0
48+
fi
49+
tar -xJf "$work/$tarball" -C "$work" || { note "extract failed — skipping."; exit 0; }
50+
51+
dest="${PREFIX}/lib/zig-${ZIG_VERSION}"
52+
sudo rm -rf "$dest"
53+
sudo mkdir -p "${PREFIX}/lib" "${PREFIX}/bin"
54+
sudo mv "$work/zig-${zos}-${zarch}-${ZIG_VERSION}" "$dest"
55+
sudo ln -sf "${dest}/zig" "${PREFIX}/bin/zig"
56+
note "installed → ${PREFIX}/bin/zig ($(zig version 2>/dev/null || echo '?'))"

0 commit comments

Comments
 (0)