Skip to content

Commit 596ebac

Browse files
authored
Merge pull request #19 from BuddhiLW/main
v1.15.3: signed build provenance, owner-relative Homebrew taps, demo links
2 parents 8621cd0 + 07598c5 commit 596ebac

6 files changed

Lines changed: 119 additions & 16 deletions

File tree

‎.dev/mutation_equivalent.jsonl‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
# can distinguish the mutant. run.sh reports an entry that matches nothing as STALE.
44
{"file": "src/runtime/collection/vector.zig", "op": "const_bump_in_arith", "before": "const new_tail_leaf = arrayFor(old, old.count - 2);", "reason": "Reached only when the tail holds exactly one element, i.e. count = 32m+1. arrayFor selects a 32-element leaf by i >> SHIFT_BITS, and (32m-1) >> 5 == (32m-2) >> 5, so `count - 2` and `count - 3` always name the same leaf. No input distinguishes them."}
55
{"file": "src/runtime/collection/vector.zig", "op": "const_bump_in_arith", "before": "const sub_index = ((new_count - 1) >> @as(u5, @intCast(level))) & MASK;", "reason": "popTail is entered only with new_count = 32m (a full-leaf boundary) and level >= SHIFT_BITS. new_count-1 and new_count-2 differ only in bit 0, which every level's >> level discards, so sub_index is identical at every level of the descent."}
6-
{"file": "src/runtime/print.zig", "op": "cmp_gt_to_ge", "before": "while (ks.tag() == .list and list_collection.countOf(ks) > 0) : (ks = list_collection.rest(ks)) {", "reason": "Ranges of every callee in the predicate, which is what decides this. map_collection.keys returns Value.nil_val for an empty map (the .array_map arm via keysArrayMap's `am.count == 0` guard, the .hash_map arm via `phm.root orelse`, and the .nil arm directly), and otherwise a chain built only by list_mod.consHeap seeded from Value.nil_val; sorted_collection.keys is the same shape, seeding keysInto with Value.nil_val. consHeap sets .count = countPrepending(tail), whose whole range is {1, n+1, COUNT_UNKNOWN} - every value >= 1. list_collection.rest of such a cell returns cell.rest, which in that chain is either another consHeap cell or the nil_val seed. So for every ks this loop can hold, ks.tag() == .list implies countOf(ks) >= 1: the conjunct is never the reason the loop stops, and >= 0 cannot change the iteration count. NOT the general claim that no empty .list exists - one does (rt.empty_list, list.zig, .count = 0, which is what '() evaluates to; verified: (list? '()) is true and (count '()) is 0). The equivalence is specifically that keys never returns it. If keys is ever changed to return the empty-list singleton for an empty map, re-derive this. One entry covers both loops (.array_map/.hash_map and .sorted_map) - the line text is identical and matching is on file+op+before."}

‎.github/workflows/release.yml‎

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,16 @@ jobs:
7575
tag: ${{ steps.resolve.outputs.tag }}
7676
sha: ${{ steps.resolve.outputs.sha }}
7777
proceed: ${{ steps.resolve.outputs.proceed }}
78+
tap-key: ${{ steps.tap-key.outputs.present }}
7879
steps:
80+
# A job-level `if` cannot read secrets, so the tap job gates on this.
81+
- name: Is there a tap deploy key
82+
id: tap-key
83+
env:
84+
PRESENT: ${{ secrets.TAP_DEPLOY_KEY != '' }}
85+
shell: bash
86+
run: echo "present=$PRESENT" >> "$GITHUB_OUTPUT"
87+
7988
- name: Checkout
8089
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
8190
with:
@@ -194,6 +203,12 @@ jobs:
194203
if: needs.preflight.outputs.proceed == 'true'
195204
runs-on: ${{ matrix.runs-on }}
196205
timeout-minutes: 60
206+
# id-token + attestations: the Sigstore provenance step below. A job-level
207+
# block replaces the workflow's, so contents: write is restated.
208+
permissions:
209+
contents: write
210+
id-token: write
211+
attestations: write
197212
strategy:
198213
fail-fast: true
199214
matrix:
@@ -238,6 +253,15 @@ jobs:
238253
tar -C zig-out/bin -czf "dist/${{ matrix.artifact }}.tar.gz" cljw
239254
( cd dist && shasum -a 256 "${{ matrix.artifact }}.tar.gz" > "${{ matrix.artifact }}.tar.gz.sha256" )
240255
256+
# Signed build provenance (Sigstore, keyless): binds the tarball's digest
257+
# to this repository, this workflow and the commit it was built from.
258+
# A .sha256 next to the asset only catches corruption; this catches a
259+
# substituted binary. Verify: gh attestation verify <tarball> -R <owner>/ClojureWasm
260+
- name: Attest build provenance
261+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
262+
with:
263+
subject-path: dist/${{ matrix.artifact }}.tar.gz
264+
241265
- name: Attach to GitHub Release
242266
env:
243267
GH_TOKEN: ${{ github.token }}
@@ -265,16 +289,47 @@ jobs:
265289
|| gh release view "$TAG_NAME" >/dev/null 2>&1
266290
gh release upload "$TAG_NAME" dist/* --clobber
267291
268-
# Point `brew install buddhilw/tap/cljw` at the release this run just built.
292+
# One SHA256SUMS per release, from the per-asset sidecars the build legs
293+
# uploaded, so `sha256sum -c SHA256SUMS --ignore-missing` checks any download.
294+
sums:
295+
name: SHA256SUMS
296+
needs: [preflight, build]
297+
runs-on: ubuntu-22.04
298+
timeout-minutes: 10
299+
steps:
300+
- name: Collect and upload SHA256SUMS
301+
env:
302+
GH_TOKEN: ${{ github.token }}
303+
GH_REPO: ${{ github.repository }}
304+
TAG_NAME: ${{ needs.preflight.outputs.tag }}
305+
shell: bash
306+
run: |
307+
set -euo pipefail
308+
mkdir -p sums && cd sums
309+
gh release download "$TAG_NAME" --pattern '*.tar.gz.sha256' --clobber
310+
cat ./*.tar.gz.sha256 | sort -k2 > SHA256SUMS
311+
test "$(wc -l < SHA256SUMS)" -eq 2
312+
cat SHA256SUMS
313+
gh release upload "$TAG_NAME" SHA256SUMS --clobber
314+
315+
# Point `brew install <owner>/tap/cljw` at the release this run just built.
269316
# Runs after BOTH matrix legs so the formula never names an asset that does
270317
# not exist yet; the checksums come from the .sha256 sidecars the build wrote,
271318
# not from a re-hash here, so the formula pins exactly what was uploaded.
272319
#
273-
# Auth is a deploy key on BuddhiLW/homebrew-tap (secret TAP_DEPLOY_KEY), not a
274-
# PAT: write access reaches that one repository and nothing else in the account.
320+
# Owner-relative: each repository bumps `<its owner>/homebrew-tap` and points
321+
# the formula at its OWN release assets. BuddhiLW/ClojureWasm and
322+
# clojurewasm/ClojureWasm build the same tag separately and the artifacts hash
323+
# differently, so a formula must never pair one repository's urls with the
324+
# other's checksums (the v1.15.1 mirror run tried exactly that, 8621cd0b).
325+
#
326+
# Auth is a deploy key on the tap (secret TAP_DEPLOY_KEY), not a PAT: write
327+
# access reaches that one repository and nothing else. A repository without
328+
# the secret (any other fork) skips this job instead of failing it.
275329
tap:
276330
name: bump homebrew tap
277331
needs: [preflight, build]
332+
if: needs.preflight.outputs.tap-key == 'true'
278333
runs-on: ubuntu-22.04
279334
timeout-minutes: 10
280335
steps:
@@ -300,7 +355,7 @@ jobs:
300355
- name: Check out the tap
301356
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
302357
with:
303-
repository: BuddhiLW/homebrew-tap
358+
repository: ${{ github.repository_owner }}/homebrew-tap
304359
ssh-key: ${{ secrets.TAP_DEPLOY_KEY }}
305360
path: tap
306361

@@ -318,8 +373,10 @@ jobs:
318373
tag = os.environ["TAG_NAME"]
319374
version = tag.lstrip("v")
320375
# The formula carries no `version` line: brew scans it from the URL,
321-
# and `brew audit --strict` rejects stating it twice.
322-
s = re.sub(r'/download/v[^/]+/', '/download/%s/' % tag, s)
376+
# and `brew audit --strict` rejects stating it twice. The url names
377+
# this repository, whose release the checksums below were read from.
378+
s = re.sub(r'https://github\.com/[^/"]+/[^/"]+/releases/download/v[^/]+/',
379+
'https://github.com/%s/releases/download/%s/' % (os.environ["GITHUB_REPOSITORY"], tag), s)
323380
# Each sha256 sits under its own url line, so replace positionally.
324381
for host, sha in (("macos-aarch64", os.environ["MAC_SHA256"]),
325382
("linux-x86_64", os.environ["LINUX_SHA256"])):

‎CHANGELOG.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@ first stable `1.0.0` tag; pre-1.0 `alpha` / `rc` tags may still change surfaces.
77

88
## [Unreleased]
99

10+
## [1.15.3] - 2026-10-22
11+
12+
### Added
13+
14+
- **Release binaries carry signed build provenance and a `SHA256SUMS` file.**
15+
Each tarball gets a Sigstore attestation from the release workflow, so
16+
`gh attestation verify cljw-linux-x86_64.tar.gz -R BuddhiLW/ClojureWasm`
17+
proves it was built by that workflow from a given commit. `SHA256SUMS`
18+
collects the per-asset sums for `sha256sum -c`.
19+
20+
### Changed
21+
22+
- **`brew install clojurewasm/tap/cljw` is the advertised install.** Each
23+
repository's release now bumps its own owner's tap from its own build:
24+
BuddhiLW/ClojureWasm keeps `buddhilw/tap`, clojurewasm/ClojureWasm drives
25+
`clojurewasm/tap`. A fork without a `TAP_DEPLOY_KEY` skips the tap job.
26+
27+
## [1.15.2] - 2026-10-21
28+
29+
### Fixed
30+
31+
- **The release workflow bumps the Homebrew tap only from
32+
BuddhiLW/ClojureWasm.** An upstream mirror carrying the workflow cut its own
33+
build of v1.15.1 and tried to write that build's checksums into
34+
`Formula/cljw.rb`, whose urls name this repository's release assets. The push
35+
failed with a 403; had it succeeded, `brew install` would have rejected the
36+
download. The `tap` job is now skipped in any other repository.
37+
1038
## [1.15.1] - 2026-10-21
1139

1240
### Added

‎README.md‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
<p align="center">
1414
<a href="https://github.com/BuddhiLW/ClojureWasm/releases/latest"><img src="https://img.shields.io/github/v/release/BuddhiLW/ClojureWasm?sort=semver&display_name=tag&label=release&color=success" alt="Latest release" /></a>
1515
<a href="https://github.com/BuddhiLW/ClojureWasm/actions/workflows/ci.yml"><img src="https://github.com/BuddhiLW/ClojureWasm/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
16-
<a href="https://github.com/BuddhiLW/homebrew-tap"><img src="https://img.shields.io/badge/brew-buddhilw%2Ftap%2Fcljw-F9A03C?logo=homebrew&logoColor=white" alt="Homebrew tap" /></a>
16+
<a href="https://github.com/clojurewasm/homebrew-tap"><img src="https://img.shields.io/badge/brew-clojurewasm%2Ftap%2Fcljw-F9A03C?logo=homebrew&logoColor=white" alt="Homebrew tap" /></a>
1717
<a href="https://ziglang.org/"><img src="https://img.shields.io/badge/Zig-0.16.0-f7a41d?logo=zig&logoColor=white" alt="Zig 0.16.0" /></a>
1818
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-EPL_2.0-blue.svg" alt="License: EPL 2.0" /></a>
1919
</p>
2020

2121
<p align="center">
2222
<a href="#install">Install</a> ·
23+
<a href="#try-it-online">Demos</a> ·
2324
<a href="#call-c-from-clojure">Call C from Clojure</a> ·
2425
<a href="#how-it-works">How it works</a> ·
2526
<a href="#numbers">Numbers</a> ·
@@ -36,12 +37,19 @@ you set.
3637
## Install
3738

3839
```sh
39-
brew install buddhilw/tap/cljw # macOS arm64, Linux x86_64
40+
brew install clojurewasm/tap/cljw # macOS arm64, Linux x86_64
4041
zig build -Dwasm -Doptimize=ReleaseSafe # or from source (Zig 0.16)
4142
```
4243

43-
Prebuilt binaries are on [Releases](https://github.com/BuddhiLW/ClojureWasm/releases).
44-
They are not code-signed; on macOS, `xattr -d com.apple.quarantine "$(which cljw)"`
44+
Prebuilt binaries are on [Releases](https://github.com/BuddhiLW/ClojureWasm/releases),
45+
each with a `SHA256SUMS` file and a signed build attestation. To check that a
46+
tarball was built by this repository's release workflow:
47+
48+
```sh
49+
gh attestation verify cljw-linux-x86_64.tar.gz -R BuddhiLW/ClojureWasm
50+
```
51+
52+
The binaries are not code-signed; on macOS, `xattr -d com.apple.quarantine "$(which cljw)"`
4553
once.
4654

4755
```sh
@@ -52,6 +60,16 @@ cljw nrepl --port 7888 # nREPL, CIDER-compatible
5260
cljw build script.clj -o app # self-contained native binary
5361
```
5462

63+
## Try it online
64+
65+
Both demos are a `cljw` server and a ClojureScript page, one container each,
66+
tracking the latest release.
67+
68+
| Demo | What it shows | Source |
69+
|---|---|---|
70+
| [Playground](https://cljw.buddhilw.com/playground/) | Evaluate Clojure under a step, time and heap budget; call Rust and Go Wasm modules | [cw-playground](https://github.com/clojurewasm/cw-playground) |
71+
| [Bookshelf](https://cljw.buddhilw.com/bookshelf/) | SQLite compiled to Wasm as the store, a Rust module for cover colours | [cw-serverless-demo](https://github.com/clojurewasm/cw-serverless-demo) |
72+
5573
## Call C from Clojure
5674

5775
A C function, compiled to Wasm, called with ordinary Clojure interop.
@@ -215,7 +233,8 @@ Created by [Shota Kudo (@chaploud)](https://github.com/chaploud), who built it
215233
from nothing to a working JVM-free Clojure with a Wasm FFI through v1.10.1. In
216234
2026-10 the project passed to [@BuddhiLW](https://github.com/BuddhiLW); the
217235
fork's work (v1.11 to v1.15.0) is merged back into
218-
[clojurewasm/ClojureWasm](https://github.com/clojurewasm/ClojureWasm), while
219-
releases and the Homebrew tap publish from
220-
[BuddhiLW/ClojureWasm](https://github.com/BuddhiLW/ClojureWasm). The engine is
236+
[clojurewasm/ClojureWasm](https://github.com/clojurewasm/ClojureWasm). Releases
237+
are cut on [BuddhiLW/ClojureWasm](https://github.com/BuddhiLW/ClojureWasm) and
238+
again when they reach clojurewasm/ClojureWasm; each repository bumps its own
239+
owner's Homebrew tap from its own build. The engine is
221240
[zwasm](https://github.com/zwasm/zwasm).

‎build.zig.zon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.{
22
.name = .cljw,
3-
.version = "1.15.1",
3+
.version = "1.15.3",
44
.fingerprint = 0x1869d207073beffa,
55
.minimum_zig_version = "0.16.0",
66
.dependencies = .{

‎docs/architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ the interpreter.
125125
Shipped through the tag the release badge points at; `CHANGELOG.md` is the
126126
record. Releases are cut by the release workflow on a push to `main` and ship
127127
as single binaries for macOS arm64 and Linux x86_64 plus the Homebrew tap
128-
(`brew install buddhilw/tap/cljw`).
128+
(`brew install clojurewasm/tap/cljw`).
129129

130130
Built and exercised end to end: the reader, analyzer, both backends, the
131131
error system, persistent collections and the GC; the numeric tower (`Long`

0 commit comments

Comments
 (0)