Skip to content

Commit 40b127f

Browse files
feat(chapel-binding): stand up detachable CI + tests — binding-tier-1 pilot (closes #129) (#135)
## Summary Stands up `bindings/chapel/` as the **binding-tier-1 pilot** for proven with a **detachable** CI harness extractable to a standalone `proven-chapel` repo. - 5-symbol WIRED subset (verified by symbol-audit, exercised end-to-end) - ~45 GATED extern decls preserved as the proven.h ABI contract (link-failure on call; tracked at proven#88) - 6-job strict CI (detachability-guard / build / symbol-audit / smoke / tests / detachable-build) - ADR-0001 documents the detachable-harness template; ADR-0002 documents the Chapel pilot - Sub-issues #130-#134 file the replication breadcrumb for Crystal, Nim, OCaml, Haskell, Lua Wave 1 path determination: **path (b)** — trim Chapel decls to symbols that link, defer the rest to proven#88. Path (a) would require landing proven#88's multi-week ABI redesign as a prereq, which is out of scope for the Chapel pilot. ## Path (a) vs (b) decision Static audit shows `ffi/zig/src/main.zig` exports only 13 functions, of which **5** match `bindings/c/include/proven.h` declarations AND have a consistent ABI: | Symbol | proven.h | Zig export | ABI match | Chapel wrapper | |------------------------------|----------|------------|-----------|----------------------| | `proven_path_has_traversal` | ✓ | ✓ | ✓ | `SafePath.hasTraversal` | | `proven_header_has_crlf` | ✓ | ✓ | ✓ | `SafeHeader.hasCrlf` (new) | | `proven_free_string` | ✓ | ✓ | ✓ | internal (`extractString`) | | `proven_version` | (stub) | ✓ | n/a | `libraryVersion()` | | `proven_build_info` | (stub) | ✓ | n/a | `libraryBuildInfo()` | Path (b) selected. Full WIRED vs GATED table at `audits/chapel-symbol-audit-2026-05-30.md`. ## Symbol-audit table (from CI) The `chapel-symbol-audit` job runs: ``` nm -D libproven.so vs bindings/chapel/symbol-manifest.txt ``` Local self-check (`comm` against the actual main.zig exports): ``` === required NOT in exports (would fail) === (empty) ``` All 5 manifest entries resolve. ## Per-module test coverage | Test | Procs exercised | Inputs | |-------------------------------|-------------------------------|--------| | `TestSafePath.chpl` | `SafePath.hasTraversal` | 8 (safe / traversal / boundary) | | `TestSafeHeader.chpl` | `SafeHeader.hasCrlf` | 9 (safe / CRLF / boundary) | | `TestLibraryInfo.chpl` | `libraryVersion`, `libraryBuildInfo` | shape + plausibility | | `TestFfiContract.chpl` | differential (wrapper vs raw extern) | 12 inputs across both modules | | `smoke/hello_smoke.chpl` | all 4 wrappers + free-string | end-to-end exit-0 | ## Detachability proof `chapel-detachable-build` does a sparse checkout of ONLY: - `bindings/chapel/` - `bindings/c/include/proven.h` - `.github/workflows/chapel-ci.yml` …and runs `just test` in the detached tree. Detachability cannot regress without a red CI job. ## Test plan - [ ] CI: `detachability-guard` self-test green (workflow path-set audit) - [ ] CI: `chapel-build` SHA-pinned Chapel install (verify SHA: `b7c1a4…`) - [ ] CI: `chapel-symbol-audit` PASS for all 5 WIRED symbols - [ ] CI: `chapel-smoke` exit-0 - [ ] CI: `chapel-tests` all assertions green - [ ] CI: `chapel-detachable-build` builds + tests from sparse checkout - [ ] `just check` (root) continues to be green - [ ] `just chapel-{check,build,test}` (root forwarders) work end-to-end - [ ] `cd bindings/chapel && PROVEN_LIB_PATH=... just test` works - [ ] Linked sub-issues (#130-#134) referenced from parent #129 - [ ] Auto-merge armed ## Tooling versions - Chapel 2.3.0 (SHA-pinned .deb: `b7c1a48cbf26cfc7ad9af22a84ea4becb1a5e2fd485ec4a1f5f8a69e8a7cd58c`) - Zig 0.13.0 (matches `.github/workflows/zig-ffi.yml`) ## References - Parent tracking issue: #129 - Sub-issues: #130 (Crystal), #131 (Nim), #132 (OCaml), #133 (Haskell), #134 (Lua) - Template ADR: `docs/adr/0001-binding-ci-template.adoc` - Pilot ADR: `docs/adr/0002-chapel-binding-standup.adoc` - Prerequisite ramp: proven#88 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 01c7c07 commit 40b127f

18 files changed

Lines changed: 1687 additions & 129 deletions

.github/workflows/chapel-ci.yml

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# Chapel CI — proven binding-tier-1 pilot.
5+
#
6+
# This workflow is the DETACHABLE-HARNESS TEMPLATE for proven binding CI.
7+
# It is intentionally narrow: it references ONLY paths under
8+
# bindings/chapel/, ffi/zig/, and bindings/c/include/proven.h. No
9+
# root-level test fixtures, no sibling-binding tooling, no surprise
10+
# dependencies. See docs/adr/0001-binding-ci-template.md for the full
11+
# detachability contract.
12+
#
13+
# Replication template (search/replace for the next binding):
14+
# chapel -> <lang> (e.g. crystal, nim, ocaml)
15+
# Chapel -> <Lang> (display name)
16+
# bindings/chapel/ -> bindings/<lang>/
17+
# chapel-ci.yml -> <lang>-ci.yml
18+
# Chapel 2.3.0 -> <toolchain pin>
19+
# ubuntu22.amd64.deb -> <toolchain installer>
20+
# chpl --version -> <toolchain version probe>
21+
#
22+
# Quality posture: every job is strict (no continue-on-error). PRs that
23+
# break Chapel are blocked from merge.
24+
25+
name: Chapel CI
26+
27+
permissions:
28+
contents: read
29+
30+
on:
31+
push:
32+
branches: [main]
33+
paths:
34+
- 'bindings/chapel/**'
35+
- 'ffi/zig/**'
36+
- 'bindings/c/include/proven.h'
37+
- '.github/workflows/chapel-ci.yml'
38+
pull_request:
39+
branches: [main]
40+
paths:
41+
- 'bindings/chapel/**'
42+
- 'ffi/zig/**'
43+
- 'bindings/c/include/proven.h'
44+
- '.github/workflows/chapel-ci.yml'
45+
workflow_dispatch:
46+
47+
# Cancel superseded runs on the same ref. Chapel install (cold) is ~30s
48+
# but the full smoke + tests cycle is ~3 min; without cancel-in-progress
49+
# a rapid push sequence will burn into the Pro-plan concurrency ceiling.
50+
concurrency:
51+
group: chapel-ci-${{ github.ref }}
52+
cancel-in-progress: true
53+
54+
env:
55+
# Pinned Chapel deb (Ubuntu 22.04 amd64). SHA-256 verified out-of-band
56+
# against the GitHub Releases asset; mismatch fails the install step.
57+
CHAPEL_VERSION: '2.3.0'
58+
CHAPEL_DEB_URL: 'https://github.com/chapel-lang/chapel/releases/download/2.3.0/chapel-2.3.0-1.ubuntu22.amd64.deb'
59+
CHAPEL_DEB_SHA256: 'b7c1a48cbf26cfc7ad9af22a84ea4becb1a5e2fd485ec4a1f5f8a69e8a7cd58c'
60+
# Pinned Zig version (kept in sync with .github/workflows/zig-ffi.yml).
61+
ZIG_VERSION: '0.13.0'
62+
63+
jobs:
64+
# ==========================================================================
65+
# Job 0 — Detachability guard.
66+
# Fails the PR if chapel-ci.yml references any path outside the allowed
67+
# set (bindings/chapel/, ffi/zig/, bindings/c/include/proven.h, self).
68+
# ==========================================================================
69+
detachability-guard:
70+
runs-on: ubuntu-22.04
71+
timeout-minutes: 2
72+
steps:
73+
- name: Checkout (workflow file only)
74+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
75+
with:
76+
sparse-checkout: |
77+
.github/workflows/chapel-ci.yml
78+
sparse-checkout-cone-mode: false
79+
80+
- name: Detachability grep guard
81+
run: |
82+
set -euo pipefail
83+
# The workflow may reference exactly these path tokens:
84+
# bindings/chapel/...
85+
# ffi/zig/...
86+
# bindings/c/include or bindings/c/include/proven.h
87+
# .github/workflows/chapel-ci.yml
88+
# Anything else under bindings/, or any root-level tests/ token,
89+
# is a detachability violation.
90+
forbidden=0
91+
while IFS= read -r raw; do
92+
# Strip a trailing punctuation char that a path-regex might pick up
93+
# from prose ("see bindings/c/include/proven.h.").
94+
p="${raw%[.,;:)]}"
95+
case "$p" in
96+
bindings/chapel/*|ffi/zig/*|bindings/c/include|bindings/c/include/proven.h|.github/workflows/chapel-ci.yml)
97+
;;
98+
bindings/*/*)
99+
echo "::error file=.github/workflows/chapel-ci.yml::detachability violation: $p"
100+
forbidden=1
101+
;;
102+
tests/*)
103+
echo "::error file=.github/workflows/chapel-ci.yml::detachability violation (root-level tests/): $p"
104+
forbidden=1
105+
;;
106+
esac
107+
done < <(grep -oE '(bindings/[a-zA-Z0-9_-]+/[A-Za-z0-9_./-]*|tests/[A-Za-z0-9_./-]+)' \
108+
.github/workflows/chapel-ci.yml | sort -u)
109+
[ "$forbidden" -eq 0 ]
110+
echo "detachability-guard: OK"
111+
112+
# ==========================================================================
113+
# Job 1 — Build libproven.so + Chapel typecheck.
114+
# Produces the .so artefact every subsequent job consumes via download.
115+
# ==========================================================================
116+
chapel-build:
117+
runs-on: ubuntu-22.04
118+
needs: detachability-guard
119+
timeout-minutes: 20
120+
121+
steps:
122+
- name: Checkout proven
123+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
124+
with:
125+
path: proven
126+
127+
- name: Checkout idris2-zig-ffi dependency
128+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
129+
with:
130+
repository: hyperpolymath/idris2-zig-ffi
131+
path: idris2-zig-ffi
132+
133+
- name: Setup Zig ${{ env.ZIG_VERSION }}
134+
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
135+
with:
136+
version: ${{ env.ZIG_VERSION }}
137+
138+
- name: Build libproven.so via Zig
139+
working-directory: proven/ffi/zig
140+
run: zig build -Dtarget=x86_64-linux-gnu
141+
142+
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
143+
run: |
144+
set -euo pipefail
145+
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
146+
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
147+
sudo apt-get update
148+
sudo apt-get install -y /tmp/chapel.deb
149+
chpl --version
150+
151+
- name: Chapel module typecheck (--library on every module)
152+
working-directory: proven/bindings/chapel
153+
run: |
154+
set -euo pipefail
155+
for m in src/*.chpl; do
156+
echo "[chapel-build] typecheck $m"
157+
chpl --library --dynamic --print-callstack-on-error \
158+
--no-codegen \
159+
-M src/ \
160+
-I "$GITHUB_WORKSPACE/proven/bindings/c/include" \
161+
"$m"
162+
done
163+
164+
- name: Upload libproven.so artefact
165+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
166+
with:
167+
name: libproven-x86_64-linux-gnu
168+
path: proven/ffi/zig/zig-out/lib/libproven.so
169+
if-no-files-found: error
170+
retention-days: 1
171+
172+
# ==========================================================================
173+
# Job 2 — Symbol audit (FFI parity gate).
174+
# The load-bearing check: nm -D libproven.so vs. symbol-manifest.txt.
175+
# ==========================================================================
176+
chapel-symbol-audit:
177+
runs-on: ubuntu-22.04
178+
needs: chapel-build
179+
timeout-minutes: 5
180+
steps:
181+
- name: Checkout (binding only)
182+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
183+
with:
184+
sparse-checkout: |
185+
bindings/chapel
186+
sparse-checkout-cone-mode: false
187+
188+
- name: Download libproven.so
189+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
190+
with:
191+
name: libproven-x86_64-linux-gnu
192+
path: lib/
193+
194+
- name: Run chapel-symbol-audit
195+
working-directory: bindings/chapel
196+
env:
197+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
198+
run: ./scripts/symbol-audit.sh
199+
200+
# ==========================================================================
201+
# Job 3 — Smoke (the "binding actually links + runs" gate).
202+
# ==========================================================================
203+
chapel-smoke:
204+
runs-on: ubuntu-22.04
205+
needs: chapel-build
206+
timeout-minutes: 10
207+
steps:
208+
- name: Checkout (binding + C header only)
209+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
210+
with:
211+
sparse-checkout: |
212+
bindings/chapel
213+
bindings/c/include
214+
.github/workflows/chapel-ci.yml
215+
sparse-checkout-cone-mode: false
216+
217+
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
218+
run: |
219+
set -euo pipefail
220+
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
221+
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
222+
sudo apt-get update
223+
sudo apt-get install -y /tmp/chapel.deb
224+
225+
- name: Install just
226+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
227+
with:
228+
just-version: '1.36.0'
229+
230+
- name: Download libproven.so
231+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
232+
with:
233+
name: libproven-x86_64-linux-gnu
234+
path: lib/
235+
236+
- name: Build hello_smoke (cd bindings/chapel && just build)
237+
working-directory: bindings/chapel
238+
env:
239+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
240+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
241+
run: just build
242+
243+
- name: Run hello_smoke
244+
working-directory: bindings/chapel
245+
env:
246+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
247+
run: ./smoke/hello_smoke
248+
249+
# ==========================================================================
250+
# Job 4 — Per-module tests.
251+
# ==========================================================================
252+
chapel-tests:
253+
runs-on: ubuntu-22.04
254+
needs: chapel-build
255+
timeout-minutes: 15
256+
steps:
257+
- name: Checkout (binding + C header only)
258+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
259+
with:
260+
sparse-checkout: |
261+
bindings/chapel
262+
bindings/c/include
263+
.github/workflows/chapel-ci.yml
264+
sparse-checkout-cone-mode: false
265+
266+
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
267+
run: |
268+
set -euo pipefail
269+
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
270+
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
271+
sudo apt-get update
272+
sudo apt-get install -y /tmp/chapel.deb
273+
274+
- name: Install just
275+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
276+
with:
277+
just-version: '1.36.0'
278+
279+
- name: Download libproven.so
280+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
281+
with:
282+
name: libproven-x86_64-linux-gnu
283+
path: lib/
284+
285+
- name: Run all Chapel tests (cd bindings/chapel && just test)
286+
working-directory: bindings/chapel
287+
env:
288+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
289+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/bindings/c/include
290+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
291+
run: just test
292+
293+
# ==========================================================================
294+
# Job 5 — Detachable-checkout proof.
295+
# Validates the harness self-builds with ONLY:
296+
# bindings/chapel/, bindings/c/include/proven.h, the workflow file.
297+
# If this job goes red, detachability has regressed.
298+
# ==========================================================================
299+
chapel-detachable-build:
300+
runs-on: ubuntu-22.04
301+
needs: chapel-build
302+
timeout-minutes: 15
303+
steps:
304+
- name: Sparse checkout (detachable set only)
305+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
306+
with:
307+
sparse-checkout: |
308+
bindings/chapel
309+
bindings/c/include/proven.h
310+
.github/workflows/chapel-ci.yml
311+
sparse-checkout-cone-mode: false
312+
path: proven-chapel
313+
314+
- name: Verify detached layout (no other proven trees present)
315+
working-directory: proven-chapel
316+
run: |
317+
set -euo pipefail
318+
if [ -d "src" ] || [ -d "ffi" ] || [ -d "tests" ] || [ -d "audits" ]; then
319+
echo "::error::detachability violation: non-binding trees present in detached checkout"
320+
ls -la
321+
exit 1
322+
fi
323+
test -d bindings/chapel
324+
test -f bindings/c/include/proven.h
325+
echo "detached layout: OK"
326+
327+
- name: Install Chapel ${{ env.CHAPEL_VERSION }} (SHA-pinned .deb)
328+
run: |
329+
set -euo pipefail
330+
curl -fsSL -o /tmp/chapel.deb "$CHAPEL_DEB_URL"
331+
echo "$CHAPEL_DEB_SHA256 /tmp/chapel.deb" | sha256sum --check
332+
sudo apt-get update
333+
sudo apt-get install -y /tmp/chapel.deb
334+
335+
- name: Install just
336+
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2
337+
with:
338+
just-version: '1.36.0'
339+
340+
- name: Download libproven.so (the only external dep allowed)
341+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
342+
with:
343+
name: libproven-x86_64-linux-gnu
344+
path: lib/
345+
346+
- name: Build + test from detached checkout
347+
working-directory: proven-chapel/bindings/chapel
348+
env:
349+
PROVEN_LIB_PATH: ${{ github.workspace }}/lib
350+
PROVEN_INCLUDE_PATH: ${{ github.workspace }}/proven-chapel/bindings/c/include
351+
LD_LIBRARY_PATH: ${{ github.workspace }}/lib
352+
run: just test

Justfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,34 @@ test-ffi:
152152
-Didris-support-lib="$SUPPORT_LIB"
153153
echo "[proven] FFI tests passed."
154154
155+
# ---------------------------------------------------------------------------
156+
# Chapel binding (detachable harness — see docs/adr/0001-binding-ci-template.md)
157+
#
158+
# These recipes are THIN FORWARDERS into bindings/chapel/. Any Chapel-
159+
# specific logic lives in bindings/chapel/Justfile so that the harness
160+
# remains extractable to a standalone proven-chapel repo. The forwarders
161+
# set PROVEN_LIB_PATH to the in-tree libproven.so before delegating.
162+
# ---------------------------------------------------------------------------
163+
164+
# Forward to the Chapel binding's own check recipe (fast symbol audit).
165+
chapel-check: build-ffi
166+
PROVEN_LIB_PATH="$(pwd)/ffi/zig/zig-out/lib" \
167+
just -d bindings/chapel --justfile bindings/chapel/Justfile check
168+
169+
# Forward to the Chapel binding's own build recipe.
170+
chapel-build: build-ffi
171+
PROVEN_LIB_PATH="$(pwd)/ffi/zig/zig-out/lib" \
172+
just -d bindings/chapel --justfile bindings/chapel/Justfile build
173+
174+
# Forward to the Chapel binding's own test recipe.
175+
chapel-test: build-ffi
176+
PROVEN_LIB_PATH="$(pwd)/ffi/zig/zig-out/lib" \
177+
just -d bindings/chapel --justfile bindings/chapel/Justfile test
178+
179+
# Forward to the Chapel binding's own clean recipe.
180+
chapel-clean:
181+
just -d bindings/chapel --justfile bindings/chapel/Justfile clean
182+
155183
# ---------------------------------------------------------------------------
156184
# Cleaning
157185
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)