Skip to content

Commit 8490ae6

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 3a0ad64 + e35d2c9 commit 8490ae6

10 files changed

Lines changed: 257 additions & 75 deletions

File tree

.github/workflows/dogfood-gate.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
3939
- name: Validate A2ML manifests
4040
if: steps.detect.outputs.count > 0
41-
uses: hyperpolymath/a2ml-validate-action@59145c7d1039fa3059b3ecacdb50ee23d7505898 # main
41+
uses: hyperpolymath/a2ml-validate-action@6bff6ec134fc977e86d25166a5c522ddea5c1e78 # main
4242
with:
4343
path: '.'
4444
strict: 'false'

.github/workflows/e2e.yml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,22 @@ jobs:
4343
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
4444

4545
- name: Install Zig
46-
uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1
46+
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
4747
with:
48-
version: 0.15.0
48+
version: '0.15.2'
4949

50-
- name: Build FFI
51-
run: cd ffi/zig && zig build
50+
# Build the standalone pure-Zig FFI surface (src/main.zig only) -- no
51+
# Idris2 RefC / idris2_zig_ffi dependency. The full Idris->RefC->Zig
52+
# pipeline needs the private nextgen-languages/language-bridges repo,
53+
# which CI cannot fetch as a path dependency; see PR #149.
54+
- name: Build FFI (standalone Zig surface)
55+
run: cd ffi/zig && zig build --build-file build_standalone.zig
5256

53-
- name: Run FFI integration tests
54-
run: cd ffi/zig && zig build test
57+
- name: Run FFI tests (standalone)
58+
run: cd ffi/zig && zig build test --build-file build_standalone.zig
5559

5660
- name: Verify library output
57-
run: ls -la ffi/zig/zig-out/lib/libproven.* || echo "Library output check"
61+
run: ls -la ffi/zig/zig-out/lib/libproven_ffi.* || echo "Library output check"
5862

5963
safety-aspects:
6064
name: Aspect — Safety Invariants
@@ -154,12 +158,12 @@ jobs:
154158
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
155159

156160
- name: Install Zig
157-
uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1
161+
uses: mlugg/setup-zig@e7d1537c378b83b8049f65dda471d87a2f7b2df2 # v1
158162
with:
159-
version: 0.15.0
163+
version: '0.15.2'
160164

161165
- name: Build and bench FFI
162-
run: cd ffi/zig && zig build bench 2>&1 | tee /tmp/bench-results.txt || echo "No bench target yet"
166+
run: cd ffi/zig && zig build bench --build-file build_standalone.zig 2>&1 | tee /tmp/bench-results.txt || echo "No bench target yet"
163167

164168
- name: Upload benchmark results
165169
if: always()

.github/workflows/idris2-ci.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,93 @@ jobs:
9494
idris2 --check src/Proven/SafeDateTime.idr
9595
idris2 --check src/Proven/SafeNetwork.idr
9696
97+
# Fast, targeted gate over every Proofs.idr companion module.
98+
#
99+
# Rationale (issue #145): the swapped-arg `appendLengthInc` break (#127)
100+
# reached main because the only job that compiles the proof modules is the
101+
# slow full `build` above (~12-20 min on a warm runner), which merges
102+
# routinely outrace. The explicit "Type check all modules" list above
103+
# covers zero Proofs.idr files — exactly the modules the discharge campaign
104+
# keeps editing. This job type-checks ALL of them, independently and in
105+
# parallel with `build`, so a non-compiling proof is caught fast. Make THIS
106+
# job a required status check on main (branch protection) to close the gate.
107+
proof-check:
108+
runs-on: ubuntu-latest
109+
timeout-minutes: 30
110+
strategy:
111+
matrix:
112+
idris2-version: ['0.8.0']
113+
114+
steps:
115+
- name: Checkout
116+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
117+
118+
- name: Install Idris 2 dependencies
119+
run: |
120+
sudo apt-get update
121+
sudo apt-get install -y chezscheme libgmp-dev
122+
123+
# Cache the Idris2 compiler + bundled packages (base/contrib/network)
124+
# only. This job never runs `idris2 --install proven.ipkg` and never
125+
# caches `build/`, so every run type-checks the CURRENT source of each
126+
# Proofs.idr — a stale cache cannot mask a source-level break.
127+
- name: Cache Idris 2
128+
id: cache-idris2
129+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
130+
with:
131+
path: |
132+
~/.idris2
133+
~/idris2-${{ matrix.idris2-version }}
134+
key: idris2-${{ matrix.idris2-version }}-${{ runner.os }}
135+
136+
- name: Install Idris 2
137+
if: steps.cache-idris2.outputs.cache-hit != 'true'
138+
run: |
139+
git clone --depth 1 --branch v${{ matrix.idris2-version }} https://github.com/idris-lang/Idris2.git ~/idris2-${{ matrix.idris2-version }}
140+
cd ~/idris2-${{ matrix.idris2-version }}
141+
make bootstrap SCHEME=chezscheme
142+
make install
143+
144+
- name: Add Idris 2 to PATH
145+
run: echo "$HOME/.idris2/bin" >> $GITHUB_PATH
146+
147+
- name: Type check the proof modules in proven.ipkg
148+
# Scope: every `*.Proofs` module LISTED IN proven.ipkg — i.e. the proof
149+
# modules the package actually builds. (64 of the 93 Proofs.idr files on
150+
# disk are intentionally absent from the package: env-blocked on retired
151+
# stdlib like Data.Nat.Division, or work-in-progress. Gating on those
152+
# would be stricter than `build` itself.)
153+
#
154+
# Run from the package source root (proven.ipkg `sourcedir = src`) with
155+
# module-relative paths. Idris2 matches a module's dotted namespace
156+
# against trailing path segments, so `Proven/Foo/Proofs.idr` resolves
157+
# `module Proven.Foo.Proofs` whereas `src/Proven/Foo/Proofs.idr` raises a
158+
# spurious "module name does not match file name". The clean `build/`
159+
# removal guarantees a stale TTC cannot mask a real break.
160+
run: |
161+
set -uo pipefail
162+
grep -oE 'Proven\.[A-Za-z0-9]+(\.[A-Za-z0-9]+)*\.Proofs' proven.ipkg \
163+
| sort -u | sed 's#\.#/#g; s#$#.idr#' > /tmp/proof_modules.txt
164+
echo "Proof modules from proven.ipkg:"; cat /tmp/proof_modules.txt
165+
cd src
166+
rm -rf build
167+
fail=0
168+
checked=0
169+
while IFS= read -r f; do
170+
[ -f "$f" ] || { echo "::error file=proven.ipkg::listed proof module missing on disk: src/$f"; fail=1; continue; }
171+
checked=$((checked + 1))
172+
echo "::group::idris2 --check $f"
173+
if idris2 -p contrib -p network --check "$f"; then
174+
echo "PASS $f"
175+
else
176+
echo "::error file=src/$f::idris2 --check failed"
177+
fail=1
178+
fi
179+
echo "::endgroup::"
180+
done < /tmp/proof_modules.txt
181+
echo "Checked $checked proof module(s); fail=$fail"
182+
exit "$fail"
183+
97184
docs:
98185
runs-on: ubuntu-latest
99186
needs: build

.machine_readable/6a2/META.a2ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ project = "proven"
66
author = "Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"
77
license = "MPL-2.0"
88
standard = "RSR 2026"
9-
last-updated = "2026-05-20"
9+
last-updated = "2026-06-01"
1010

1111
[architecture-decisions]
1212
decisions = [
1313
{ id = "ADR-001", title = "OWED-with-justification convention for bodyless declarations in Proofs.idr files", status = "accepted", date = "2026-05-20", body = "Bodyless type signatures (implicit postulates) are made discoverable via triple-pipe doc-comment naming the claim + Idris2 0.8.0 blocker + discharge condition, plus a leading `0 ` erased-multiplicity marker, plus the original bare signature. The `postulate` keyword is NOT used (zero Proofs.idr files use it). Canonical example: src/Proven/SafeChecksum/Proofs.idr. Reference: hyperpolymath/standards#158 Fork A campaign, PROOF-NEEDS.md." },
14+
{ id = "ADR-002", title = "Fast proof-check CI gate over the proven.ipkg proof modules", status = "accepted", date = "2026-06-01", body = "A required proof-check job runs idris2 --check on each *.Proofs module listed in proven.ipkg (the 29 build-contract proof modules), from the src/ sourcedir with module-relative paths, on a clean idris2-0.8.0 + contrib toolchain (build/ removed so a stale TTC cannot mask a break). This is the fast cone-check recommended in #145 after #127/#145 showed DISCHARGED-but-unbuilt proofs reaching main while the slow full build sat queued. Scope is the ipkg list, not every on-disk Proofs.idr (64 of 93 are intentionally env-blocked or WIP). A corollary of ADR-001: a theorem that can only be closed by consuming an erased 0-multiplicity OWED premise, or an irreducible top-level-constant projection, is itself reverted to OWED rather than given a body that does not type-check. Origin: #147 (gate) and #148 (gate correctness + 5 proof repairs)." },
15+
{ id = "ADR-003", title = "CI builds the standalone Zig FFI surface; full Idris->RefC->Zig integration deferred", status = "accepted", date = "2026-06-01", body = "The e2e.yml E2E and Bench jobs build ffi/zig/build_standalone.zig (src/main.zig only, the pure-Zig C-ABI surface) instead of the default build.zig, because build.zig's idris2_zig_ffi is a Zig .path dependency on the private, sibling-checkout-only repo nextgen-languages/language-bridges, which CI cannot resolve. The Zig toolchain is provisioned via mlugg/setup-zig@v1 at the build.zig.zon minimum (0.15.2). Full Idris->RefC->Zig integration (idris2 --codegen refc + scripts/build-refc.sh + zig build -Didris-*) is documented in docs/ffi-ci-build.md and deferred until language-bridges is provisioned as a submodule plus a CI read credential. Origin: #149; tracked in #151." },
1416
]
1517

1618
[proof-conventions]

.machine_readable/6a2/STATE.a2ml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[metadata]
55
project = "proven"
66
version = "0.1.0"
7-
last-updated = "2026-05-30"
7+
last-updated = "2026-05-31"
88
status = "active"
99

1010
[project-context]
@@ -30,10 +30,18 @@ bodyless-decls-total = 256
3030
# SafeChecksum convention. See PROOF-NEEDS.md "OWED-with-justification
3131
# convention" and META.a2ml ADR-001.
3232

33-
discharged-decls = 34
33+
discharged-decls = 31
3434
# DISCHARGED entries (OWED → proven Refl or contrib-lemma) landed
35-
# across Phase 3 PRs #97-124 (2026-05-30 session). Each PR converts
36-
# one OWED to a machine-checked theorem.
35+
# across Phase 3 PRs #97-124 (2026-05-30) + #144 (SafeJson anyMatchesTAny,
36+
# singleKeyPath). 2026-05-31 honesty correction (#147 proof-check gate):
37+
# the new gate type-checked all 29 ipkg proof modules on a clean toolchain
38+
# and found 5 that never actually compiled — "DISCHARGED" theorems merged
39+
# without a verifying build. Repaired: SafeUrl appendAssociative (wrong
40+
# contrib name) + addParamIncreasesCount (lengthSnoc arg order) now
41+
# genuinely hold; SafeSQL builderQueriesSafe / cannotEscapeStringLiteral /
42+
# combinePreservesSafety / addParamPreservesSafety reverted to OWED (they
43+
# consumed erased OWED premises); SafeEmail validResultIsValid reverted to
44+
# OWED (top-level-constant projection opacity). Net: 36 → 31 honest.
3745

3846
[trust-posture]
3947
believe-me-uses = 0
@@ -65,6 +73,9 @@ scorecard-fuzzing-source-fixed = "PR #121 (real ClusterFuzzLite + cargo-fuzz)"
6573

6674
[session-history]
6775
# Most recent first.
76+
"2026-06-01" = "Greened main: #148 + #149 admin-merged. #148 — proof-check gate correctness (run from src/ with module-relative paths; ipkg-scoped to the 29 *.Proofs modules) + 5 never-compiled proof repairs: SafeUrl appendAssociative (→ sym (Data.List.appendAssociative)) and addParamIncreasesCount (lengthSnoc arg order) now genuinely discharged; SafeSQL ×4 + SafeEmail ×1 reverted DISCHARGED→OWED (erased premises / top-level-constant opacity); discharged-decls 36→31; ADR-002. #149 — fixed e2e.yml Zig toolchain (goto-bus-stop@v2.2.1/0.15.0 → mlugg@v1/0.15.2 per build.zig.zon) and pointed E2E/Bench at build_standalone.zig (pure-Zig surface; full Idris→Zig pipeline deferred — ADR-003, docs/ffi-ci-build.md, #151); set bindings/rust/fuzz/README.adoc SPDX CC-BY-4.0→MPL-2.0 (fixed Enforce Trustfile Policies). Both merged over the one remaining red, governance / Language / package anti-pattern policy — pre-existing, lives in hyperpolymath/standards (rsr-antipattern.yml), likely flags banned-language bindings (typescript/hy/javascript); tracked in #150."
77+
"2026-05-31b" = "CI proof-gate (#147 → #148): added a fast `proof-check` job type-checking every *.Proofs module listed in proven.ipkg (29) from src/ with module-relative paths; fixed two self-inflicted gate bugs (repo-root path → spurious 'module name does not match file name'; over-broad glob over all 93 on-disk vs the 29 ipkg-built). The gate found 5 ipkg proof modules that never compiled (DISCHARGED-but-unbuilt, same disease as #127): SafeUrl appendAssociative (referenced nonexistent Data.List.Equalities.appendAssociative; fixed to sym (Data.List.appendAssociative)) + addParamIncreasesCount (lengthSnoc arg order) now genuinely hold; SafeSQL ×4 and SafeEmail ×1 reverted DISCHARGED→OWED (depended on erased OWED premises / top-level-constant opacity). All 29 now green on a clean idris2-0.8.0+contrib build. ADR-002."
78+
"2026-05-31" = "SafeJson Phase 3: #144 DISCHARGES anyMatchesTAny (6-arm split) + singleKeyPath (with-pattern), completing #138. Root-caused a pre-existing breakage: appendLengthInc (from #127) used `lengthSnoc arr v` but contrib Data.List.Equalities.lengthSnoc is element-first — it failed to type-check and blocked the ENTIRE SafeJson.Proofs module (so main did not compile). Fixed to `lengthSnoc v arr`. Verified on a clean idris2-0.8.0+contrib install (full SafeJson cone, 0 errors). Filed #145: the Idris build is not a required status check, so #127's broken merge went green (created→merged in ~2.5min with build jobs still queued); recommends branch-protection gating + faster cone-check job."
6879
"2026-05-30" = "Phase 3 discharge: PRs #97-124 land (~28 OWED→DISCHARGED conversions). Security tab cleanup: source-fixes for #2 / #296 / #299 + branch-protection safe-subset + 100 Hypatia FP alerts dismissed at API. Estate-wide CSA self-echo bulk-dismiss sweep (~9050 alerts) running in background."
6980
"2026-05-20" = "Fork A campaign complete: 250 bodyless decls across 28 of 41 modules annotated via OWED-with-justification convention (PRs #37-64, Refs standards#158); cross-prover audit confirms 0 Lean sorry / 0 Coq Admitted in first-party code (only echidnabot dogfood fixtures)"
7081
"2026-05-19" = "Six new modules merged with explicit OWED markers (SafeChecksum, SafeBuffer, SafeBloom, SafeCryptoAccel, SafeHKDF, SafeFPGA) — convention-setting"

bindings/rust/fuzz/README.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: CC-BY-4.0
1+
// SPDX-License-Identifier: MPL-2.0
22
= Cargo-fuzz harness for proven Rust bindings
33

44
== What

docs/ffi-ci-build.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
# FFI CI build: standalone surface (now) vs full Idris→Zig pipeline (deferred)
5+
6+
`proven`'s FFI layer compiles Idris 2 to C via the RefC backend and wraps it in
7+
a stable C ABI with Zig (`Idris2 --codegen refc → .c → zig build → libproven`).
8+
There are **two** Zig build entry points:
9+
10+
| File | Builds | External deps | Used by |
11+
|------|--------|---------------|---------|
12+
| `ffi/zig/build.zig` | full Idris→RefC→Zig `libproven` | `idris2_zig_ffi` (path dep on `nextgen-languages/language-bridges`) + generated RefC C | local dev, full integration |
13+
| `ffi/zig/build_standalone.zig` | `libproven_ffi.a` from `src/main.zig` only (pure-Zig C-ABI surface) | none | **CI** (`e2e.yml`) |
14+
15+
## Why CI uses the standalone build (ADR-003)
16+
17+
`build.zig` declares its `idris2_zig_ffi` dependency as a Zig **`.path`**
18+
dependency:
19+
20+
.idris2_zig_ffi = .{ .path = "../../../nextgen-languages/language-bridges/bridges/idris2" }
21+
22+
That points at a **private, cross-owner sibling repo** which CI does not (and
23+
cannot, without a credential) check out, so `zig build` fails at dependency
24+
resolution. `build_standalone.zig` compiles `src/main.zig` alone — and that
25+
file imports only `std`/`builtin` — so it needs neither the bridge nor the
26+
generated RefC, giving CI a real (if narrower) Zig FFI build + unit-test signal.
27+
28+
`e2e.yml` therefore runs, with Zig provisioned by `mlugg/setup-zig@v1` at
29+
`0.15.2` (the `build.zig.zon` minimum):
30+
31+
zig build --build-file build_standalone.zig # E2E build
32+
zig build test --build-file build_standalone.zig # E2E tests
33+
zig build bench --build-file build_standalone.zig || … # Bench (soft)
34+
35+
## The full pipeline (recipe — Idris half verified locally)
36+
37+
The canonical recipe is the root `Justfile` (`build-refc` + `build-ffi`).
38+
Verified locally on idris2-0.8.0 + contrib:
39+
40+
# Step 1 — Idris → RefC C (scripts/build-refc.sh, IPKG=proven-ffi.ipkg)
41+
idris2 --codegen refc --build proven-ffi.ipkg # → build/exec/proven-ffi.c
42+
# staged to build/refc/ ; the script prints the four paths used below
43+
44+
# Step 2 — Zig links the RefC C + Idris runtime/support into libproven
45+
PFX=$(idris2 --prefix)
46+
zig build \
47+
-Didris-refc="$PWD/build/refc" \
48+
-Didris-refc-runtime="$(find "$PFX" -path '*/support/refc' -type d | head -1)" \
49+
-Didris-c-support="$(find "$PFX" -path '*/support/c' -type d | head -1)" \
50+
-Didris-support-lib="$(find "$PFX" -path '*/lib/libidris2_support.*' | head -1 | xargs dirname)"
51+
52+
Requires `libgmp` (the RefC runtime links `gmp`/`pthread`/`m`).
53+
54+
## Enabling full integration in CI (deferred — #151)
55+
56+
The default `build.zig` needs `nextgen-languages/language-bridges` present.
57+
Because it is **private** and **not** a submodule, CI cannot fetch it today.
58+
To enable:
59+
60+
1. Add `language-bridges` as a **git submodule** inside `proven`, and point
61+
`ffi/zig/build.zig.zon`'s `.path` at the in-repo submodule location (the
62+
current `../../../…` escapes the repo, so it must change).
63+
2. Add a **CI read credential** (deploy key / PAT) and use `actions/checkout`
64+
with `submodules: true`.
65+
3. Add/extend a workflow that installs Idris2 + `libgmp`, runs
66+
`scripts/build-refc.sh`, then the `zig build -Didris-*` invocation above.
67+
68+
Tracked in #151. The interim standalone build is ADR-003.

src/Proven/SafeEmail/Proofs.idr

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,21 @@ parseDeterministic s = Refl
6464
-- Validation Properties
6565
--------------------------------------------------------------------------------
6666

67-
||| DISCHARGED: `validResult.isValid = True`. By definition
68-
||| `validResult = MkValidationResult True []` (Validation.idr L62)
69-
||| and `validResult` is `public export`, so the elaborator unfolds
70-
||| `validResult.isValid` to `(MkValidationResult True []).isValid =
71-
||| True` by direct record-projection reduction.
67+
||| OWED: `validResult.isValid = True`. Operationally immediate —
68+
||| `validResult = MkValidationResult True []` (Validation.idr L62) — but
69+
||| Idris2 0.8.0 does not reduce the projection of a top-level constant
70+
||| (`validResult.isValid`) by `Refl`, even with `ValidationResult` and
71+
||| `validResult` both `public export`. Verified: the same
72+
||| `Can't solve constraint between: True and validResult.isValid` arises
73+
||| in a minimal cross-module repro. Same top-level-constant-opacity
74+
||| blocker as `SafePassword.chainedBuildersCompose` and
75+
||| `SafeCSV.defaultDelimiterIsComma`. Discharge once Idris2 reduces
76+
||| top-level constant projections, or `validResult` is inlined at the use
77+
||| site so the constructor is exposed. (The prior "DISCHARGED … by direct
78+
||| record-projection reduction" comment was incorrect — it did not
79+
||| type-check under Idris2 0.8.0.)
7280
public export
73-
validResultIsValid : validResult.isValid = True
74-
validResultIsValid = Refl
81+
0 validResultIsValid : validResult.isValid = True
7582

7683
||| OWED: adding an Error-severity issue makes the result invalid.
7784
||| `addIssue` (Validation.idr L71-74) computes the new validity as

0 commit comments

Comments
 (0)