Skip to content

Commit 130c6a1

Browse files
Merge branch 'main' into dependabot/github_actions/actions-3c815a503c
2 parents 9dd354d + cd3f401 commit 130c6a1

5 files changed

Lines changed: 225 additions & 4 deletions

File tree

.claude/hooks/session-start.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# SessionStart hook — make Lean available in Claude Code on the web so the
4+
# proofs in proofs/Tangle.lean can be built and verified (the repo's working
5+
# rule: "every edit ends with a Lean compile").
6+
#
7+
# Thin wrapper over proofs/bootstrap-lean.sh (the single source of truth for
8+
# toolchain setup). Synchronous: guarantees Lean is ready before the agent
9+
# loop starts, so it never races a build/verify against a half-installed
10+
# toolchain. Web/remote sessions only; local developers use their own Lean.
11+
set -euo pipefail
12+
13+
[ "${CLAUDE_CODE_REMOTE:-}" = "true" ] || exit 0
14+
15+
REPO="${CLAUDE_PROJECT_DIR:-$(pwd)}"
16+
BOOT="$REPO/proofs/bootstrap-lean.sh"
17+
if [ ! -x "$BOOT" ]; then
18+
echo "session-start: $BOOT not found or not executable; skipping Lean bootstrap."
19+
exit 0
20+
fi
21+
22+
# Install the pinned toolchain (idempotent; ~16s cold, instant when cached).
23+
# A failure must not block the whole session — Lean just won't be ready, and
24+
# the developer can run proofs/bootstrap-lean.sh by hand.
25+
if "$BOOT"; then
26+
# Persist `lean` on PATH for the rest of the session.
27+
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
28+
"$BOOT" --print-path >> "$CLAUDE_ENV_FILE"
29+
fi
30+
else
31+
echo "session-start: Lean bootstrap failed; continuing without Lean." \
32+
"Run proofs/bootstrap-lean.sh manually once network is available." >&2
33+
fi
34+
exit 0

.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+
}

.github/workflows/lean-proofs.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,16 @@ jobs:
4040
with:
4141
persist-credentials: false
4242

43-
- name: Install elan
43+
- name: Bootstrap Lean toolchain (pinned; GitHub-asset fallback)
4444
run: |
45-
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \
46-
| sh -s -- -y --default-toolchain none
45+
# Single source of truth for toolchain setup (proofs/bootstrap-lean.sh).
46+
# Uses the normal elan dist server on open networks (GitHub runners);
47+
# falls back to the GitHub release asset if release.lean-lang.org is
48+
# unreachable, so CI stays green through dist-server outages.
49+
bash proofs/bootstrap-lean.sh
4750
echo "$HOME/.elan/bin" >> "$GITHUB_PATH"
4851
49-
- name: Pin Lean toolchain from proofs/lean-toolchain
52+
- name: Show pinned Lean version
5053
working-directory: proofs
5154
run: |
5255
test -f lean-toolchain || { echo "::error::proofs/lean-toolchain missing"; exit 1; }

proofs/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!--
2+
SPDX-License-Identifier: MPL-2.0
3+
Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
-->
5+
# Tangle proofs
6+
7+
Mechanised metatheory for the Tangle core type system, in Lean 4.
8+
9+
- [`Tangle.lean`](Tangle.lean) — the proofs (the repo's **build oracle**).
10+
- [`lean-toolchain`](lean-toolchain) — the pinned Lean version
11+
(`leanprover/lean4:v4.14.0`). Single source of truth for the toolchain.
12+
- [`bootstrap-lean.sh`](bootstrap-lean.sh) — installs that toolchain.
13+
14+
## What is proven
15+
16+
`Tangle.lean` mechanises type safety for the core language, all under Lean's
17+
kernel with **no `sorry`/`axiom`/`admit`** (enforced by CI):
18+
19+
- **Progress, Preservation, Determinism, Type Safety** — for the let-free
20+
fragment *and* the echo-types fragment.
21+
- **Echo types** (structured loss): `Ty.echo`, `echoClose`/`lower`/`residue`,
22+
with the residue-recovery / non-injectivity capstones. See the
23+
`§ECHO-TYPES` section of `Tangle.lean` and
24+
[`../PROOF-NARRATIVE.md`](../PROOF-NARRATIVE.md) §2.5.
25+
- **Decidability** (TG-2): `infer ≡ HasType`, type uniqueness, and a
26+
`Decidable (HasType [] e τ)` instance.
27+
28+
## Building / verifying
29+
30+
```sh
31+
# 1. Install the pinned toolchain (idempotent).
32+
./proofs/bootstrap-lean.sh
33+
34+
# 2. Put lean on PATH for this shell.
35+
eval "$(./proofs/bootstrap-lean.sh --print-path)"
36+
37+
# 3. Verify — 0 errors means the proofs check.
38+
cd proofs && lean Tangle.lean
39+
```
40+
41+
### Why `bootstrap-lean.sh` exists
42+
43+
`elan` (the Lean toolchain manager) resolves toolchains from
44+
`release.lean-lang.org`, which is **not on the network allowlist** in
45+
sandboxed environments such as Claude Code on the web. GitHub release assets
46+
*are* reachable, so when the normal install path is blocked the script fetches
47+
the pinned toolchain directly from `github.com`. On an open network (e.g.
48+
GitHub Actions runners) it uses the normal `elan` path. Either way it reads the
49+
version from `lean-toolchain`, so it stays correct when the pin is bumped.
50+
51+
CI runs the same oracle in [`.github/workflows/lean-proofs.yml`](../.github/workflows/lean-proofs.yml):
52+
`lean Tangle.lean` must report 0 errors and the file must contain no
53+
`sorry`/`axiom`/`admit`/`Admitted` outside comments.

proofs/bootstrap-lean.sh

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# bootstrap-lean.sh — install the pinned Lean 4 toolchain so the proofs in
4+
# this directory can be built and verified.
5+
#
6+
# WHY THIS EXISTS
7+
# proofs/Tangle.lean is the repo's build oracle (.github/workflows/
8+
# lean-proofs.yml) and the working rule is "every edit ends with a Lean
9+
# compile". GitHub Actions runners have open network and install Lean fine,
10+
# but sandboxed/allowlisted environments (e.g. Claude Code on the web)
11+
# cannot reach elan's default dist server release.lean-lang.org. GitHub
12+
# release assets ARE reachable, so when the normal install path is blocked
13+
# this script fetches the pinned toolchain directly from github.com.
14+
#
15+
# The version is read from proofs/lean-toolchain, so this stays correct
16+
# when the pin is bumped. Idempotent and non-interactive.
17+
#
18+
# USAGE
19+
# ./proofs/bootstrap-lean.sh # install the toolchain
20+
# eval "$(./proofs/bootstrap-lean.sh --print-path)" # and put lean on PATH
21+
# # then:
22+
# cd proofs && lean Tangle.lean # 0 errors == proofs verified
23+
set -euo pipefail
24+
25+
PRINT_PATH=0
26+
[ "${1:-}" = "--print-path" ] && PRINT_PATH=1
27+
28+
# Resolve repo paths relative to this script.
29+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
30+
PIN_FILE="$SCRIPT_DIR/lean-toolchain"
31+
32+
log() { [ "$PRINT_PATH" -eq 1 ] || echo "bootstrap-lean: $*"; }
33+
34+
if [ ! -f "$PIN_FILE" ]; then
35+
echo "bootstrap-lean: no $PIN_FILE found" >&2
36+
exit 1
37+
fi
38+
39+
PIN="$(tr -d '[:space:]' < "$PIN_FILE")" # e.g. leanprover/lean4:v4.14.0
40+
VER="${PIN##*:}" # v4.14.0
41+
VNUM="${VER#v}" # 4.14.0
42+
ELAN_DIR="${ELAN_HOME:-$HOME/.elan}"
43+
TC_NAME="$(printf '%s' "$PIN" | sed 's|/|--|; s|:|---|')" # leanprover--lean4---v4.14.0
44+
TC_PATH="$ELAN_DIR/toolchains/$TC_NAME"
45+
46+
# --print-path mode: just emit the PATH export (for eval), do no work.
47+
if [ "$PRINT_PATH" -eq 1 ]; then
48+
# $PATH is intentionally literal — it must expand when this line is later
49+
# eval'd / sourced, not now. Only %s (= $ELAN_DIR) expands here.
50+
# shellcheck disable=SC2016
51+
printf 'export PATH="%s/bin:$PATH"\n' "$ELAN_DIR"
52+
exit 0
53+
fi
54+
55+
export PATH="$ELAN_DIR/bin:$PATH"
56+
57+
# Already bootstrapped — fast idempotent exit.
58+
if [ -x "$TC_PATH/bin/lean" ]; then
59+
log "Lean toolchain $PIN already present at $TC_PATH"
60+
log "Run: eval \"\$($0 --print-path)\" then (cd $SCRIPT_DIR && lean Tangle.lean)"
61+
exit 0
62+
fi
63+
64+
# 1. elan (toolchain manager). raw.githubusercontent.com is reachable.
65+
if [ ! -x "$ELAN_DIR/bin/elan" ]; then
66+
log "installing elan…"
67+
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \
68+
| sh -s -- -y --default-toolchain none >/dev/null
69+
fi
70+
71+
# 2. Try the normal toolchain install; fall back to the GitHub release asset
72+
# if the elan dist server is unreachable (the allowlist case).
73+
if "$ELAN_DIR/bin/elan" toolchain install "$PIN" >/dev/null 2>&1 \
74+
&& [ -x "$TC_PATH/bin/lean" ]; then
75+
log "installed $PIN via elan."
76+
else
77+
log "elan dist server unreachable — using GitHub release asset."
78+
arch="$(uname -m)"
79+
case "$arch" in
80+
x86_64) asset="lean-${VNUM}-linux.tar.zst" ;;
81+
aarch64) asset="lean-${VNUM}-linux_aarch64.tar.zst" ;;
82+
*) echo "bootstrap-lean: unsupported arch '$arch'" >&2; exit 1 ;;
83+
esac
84+
url="https://github.com/leanprover/lean4/releases/download/${VER}/${asset}"
85+
tmp="$(mktemp -d)"
86+
trap 'rm -rf "$tmp"' EXIT
87+
log "downloading $url"
88+
curl -sSfL -o "$tmp/lean.tar.zst" "$url"
89+
90+
# zstd is needed to unpack .tar.zst.
91+
if ! command -v unzstd >/dev/null 2>&1 && ! command -v zstd >/dev/null 2>&1; then
92+
if command -v apt-get >/dev/null 2>&1; then
93+
sudo apt-get install -y zstd >/dev/null 2>&1 \
94+
|| apt-get install -y zstd >/dev/null 2>&1 || true
95+
fi
96+
fi
97+
98+
mkdir -p "$ELAN_DIR/toolchains"
99+
tar --use-compress-program=unzstd -xf "$tmp/lean.tar.zst" -C "$tmp"
100+
extracted="$(find "$tmp" -maxdepth 1 -type d -name 'lean-*' | head -1)"
101+
if [ -z "$extracted" ]; then
102+
echo "bootstrap-lean: extraction produced no lean-* directory" >&2
103+
exit 1
104+
fi
105+
rm -rf "$TC_PATH"
106+
mv "$extracted" "$TC_PATH"
107+
log "installed $PIN from GitHub release."
108+
fi
109+
110+
# 3. Sanity check.
111+
if [ -x "$TC_PATH/bin/lean" ]; then
112+
log "ready — $("$TC_PATH/bin/lean" --version 2>/dev/null || echo '(version unavailable)')"
113+
log "next: eval \"\$($0 --print-path)\" then (cd $SCRIPT_DIR && lean Tangle.lean)"
114+
else
115+
echo "bootstrap-lean: toolchain bootstrap FAILED" >&2
116+
exit 1
117+
fi

0 commit comments

Comments
 (0)