Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/actions/build-native/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ runs:
if: steps.native-cache.outputs.cache-hit != 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
# keep in sync with the package engines floor (>=22.19.5)
node-version: '22'

- name: Install npm dependencies
if: steps.native-cache.outputs.cache-hit != 'true'
Expand Down
11 changes: 9 additions & 2 deletions .github/actions/build-wasm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ inputs:
node_version:
description: 'Node.js version to use'
required: false
default: '20'
# must satisfy the package engines floor (>=22.19.5) — the napi-wasm
# backend (worker_threads + WASI + shared wasm memory) is not supported
# on older node
default: '22'
proof_systems_commit:
description: 'proof_systems commit to use'
required: false
Expand Down Expand Up @@ -62,7 +65,11 @@ runs:
~/.npm
node_modules
dist
key: ${{ runner.OS }}-node-${{ inputs.node_version }}-v2-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js') }}
# v3: the key must also hash the build scripts — this cache includes
# `dist`, and on a hit the build is skipped entirely, so a build-script
# fix (e.g. removing the wasm-opt miscompilation) never took effect
# while ts/js were unchanged
key: ${{ runner.OS }}-node-${{ inputs.node_version }}-v3-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.js', 'scripts/**/*.sh', '.github/actions/**') }}
- name: Build examples
if: ${{ steps.cache.outputs.cache-hit != 'true' }}
shell: bash
Expand Down
7 changes: 4 additions & 3 deletions .github/actions/release-pkg-pr-version/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ runs:
uses: actions/cache@v4
with:
path: .
key: repo-${{ github.sha }}-node-20
- name: Setup Node.JS 20
# must match the key build-wasm/action.yml saves under
key: repo-${{ github.sha }}-node-22
- name: Setup Node.JS 22
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
- name: build
uses: ./.github/actions/build-wasm
- name: Build o1js and mina-signer
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ jobs:
lmdb \
pkgconf \
postgresql@15 \
wasm-pack \
wasm-tools \
wabt \
bash \
Expand Down Expand Up @@ -135,7 +134,7 @@ jobs:
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2025-12-11
targets: wasm32-unknown-unknown
targets: wasm32-unknown-unknown,wasm32-wasip1-threads
components: rust-src

- name: Setup go
Expand Down
90 changes: 84 additions & 6 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,20 @@ jobs:
TOTAL_TESTS: ${{ steps.count_tests.outputs.test_count }}
CHUNK: ${{ matrix.chunk }}
CHUNKS: 8
O1JS_CI_DIAG: 1
# SPECULATIVE FIX (evaluate, keep or revert): V8's wasm trap-handler
# reserves a ~10GB virtual-address guard cage; the loader needs >8GB VA
# to instantiate (measured locally). If the CI runner constrains VA,
# instantiation wedges. Inline bounds checks drop the requirement to
# ~4GB (the shared-memory reservation) at a small runtime cost.
NODE_OPTIONS: '--disable-wasm-trap-handler'
shell: bash
run: |
# TEMP CI diagnosis (remove): runner resource limits — the wasm loader
# needs >8GB of virtual address space (trap-handler cage + 4GiB
# shared-memory reservation); a constrained runner wedges at load
echo "=== runner limits:"; ulimit -a; free -h || true

echo "Total tests: $TOTAL_TESTS"
echo "Current chunk: $CHUNK"
echo "Total chunks: $CHUNKS"
Expand All @@ -327,17 +339,65 @@ jobs:

set -o pipefail

# TEMP CI diagnosis (remove): the wasm loader wedges inside module
# instantiation only on the CI runner — not reproducible locally on
# any arch / node version / cache state. Print exact runtime versions
# so we can see what the runner actually installed vs the lockfile.
echo "=== o1js-diag runtime versions:"
node -e 'console.log("node", process.version, process.arch, "cpus", require("os").cpus().length);
for (const p of ["@emnapi/core","@emnapi/runtime","@napi-rs/wasm-runtime","@tybys/wasm-util"]) {
try { console.log(p, require(p + "/package.json").version); } catch (e) { console.log(p, "MISSING"); }
}' || true

# TEMP CI diagnosis (remove): if a test is still alive after 120s
# (normal tests finish well under that), dump every thread's kernel
# wait-state from /proc — no gdb/symbols needed. futex_wait = lock
# deadlock, mmap/mprotect = memory reservation, epoll/poll = I/O.
# This shows exactly what the wasm instantiation wedges on.
dump_hang() {
local label="$1"
local np=""
# the pattern also matches the `timeout` wrapper (label is in its
# argv), so pick the process whose comm is actually `node`
for p in $(pgrep -f "enable-source-maps ${label}"); do
if [ "$(cat /proc/"$p"/comm 2>/dev/null)" = "node" ]; then np="$p"; break; fi
done
[ -z "$np" ] && return 0
{
echo "===== HANG DIAG: ${label} (pid ${np}) ====="
for t in /proc/"$np"/task/*; do
local tid; tid=$(basename "$t")
printf "tid=%s comm=%s state=%s wchan=%s syscall=%s\n" \
"$tid" "$(cat "$t"/comm 2>/dev/null)" \
"$(awk '{print $3}' "$t"/stat 2>/dev/null)" \
"$(cat "$t"/wchan 2>/dev/null)" \
"$(cut -d' ' -f1 "$t"/syscall 2>/dev/null)"
done
echo "--- kernel stacks (sudo):"
sudo bash -c 'for t in /proc/'"$np"'/task/*; do echo "== $t"; cat "$t"/stack 2>/dev/null; done' 2>/dev/null || echo "(no sudo/stack)"
} >> hang-diag.txt 2>&1
}

for ((i=start_index; i<end_index && i<${#test_files[@]}; i++)); do
echo "Running test: ${test_files[$i]}"
node --enable-source-maps "${test_files[$i]}" | tee -a profiling.md
( sleep 120; dump_hang "${test_files[$i]}" ) &
diag_pid=$!
# per-test timeout: a wedged test becomes a named failure in
# 30 min instead of silently eating the job's 210-minute budget.
# SIGTERM can't interrupt a main thread blocked in wasm/Atomics,
# so escalate to SIGKILL after 30s.
timeout --kill-after=30 1800 node --enable-source-maps "${test_files[$i]}" | tee -a profiling.md
kill "$diag_pid" 2>/dev/null || true
done
continue-on-error: false
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.chunk }}
path: profiling.md
path: |
profiling.md
hang-diag.txt
- name: Add to job summary
if: always()
shell: bash
Expand All @@ -361,9 +421,11 @@ jobs:
uses: actions/cache@v4
with:
path: .
key: repo-${{ github.sha }}-node-20
# must match the key build-wasm/action.yml saves under
# (repo-<sha>-node-<node_version>, currently 22)
key: repo-${{ github.sha }}-node-22

- name: Setup Node.JS 20
- name: Setup Node.JS 22
uses: actions/setup-node@v4
with:
node-version: 22
Expand All @@ -372,7 +434,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.OS }}-node-20-${{ hashFiles('**/package-lock.json') }}
key: ${{ runner.OS }}-node-22-${{ hashFiles('**/package-lock.json') }}

- name: Cache Playwright browsers
uses: actions/cache@v4
Expand Down Expand Up @@ -492,8 +554,20 @@ jobs:
CHUNK: ${{ matrix.chunk }}
CHUNKS: 4
O1JS_BACKEND: native
O1JS_CI_DIAG: 1
# SPECULATIVE FIX (evaluate, keep or revert): V8's wasm trap-handler
# reserves a ~10GB virtual-address guard cage; the loader needs >8GB VA
# to instantiate (measured locally). If the CI runner constrains VA,
# instantiation wedges. Inline bounds checks drop the requirement to
# ~4GB (the shared-memory reservation) at a small runtime cost.
NODE_OPTIONS: '--disable-wasm-trap-handler'
shell: bash
run: |
# TEMP CI diagnosis (remove): runner resource limits — the wasm loader
# needs >8GB of virtual address space (trap-handler cage + 4GiB
# shared-memory reservation); a constrained runner wedges at load
echo "=== runner limits:"; ulimit -a; free -h || true

echo "Total tests: $TOTAL_TESTS"
echo "Current chunk: $CHUNK"
echo "Total chunks: $CHUNKS"
Expand All @@ -515,7 +589,11 @@ jobs:

for ((i=start_index; i<end_index && i<${#test_files[@]}; i++)); do
echo "Running test (native): ${test_files[$i]}"
node --enable-source-maps "${test_files[$i]}" | tee -a profiling.md
# per-test timeout: a wedged test becomes a named failure in
# 30 min instead of silently eating the job's 210-minute budget.
# SIGTERM can't interrupt a main thread blocked in wasm/Atomics,
# so escalate to SIGKILL after 30s.
timeout --kill-after=30 1800 node --enable-source-maps "${test_files[$i]}" | tee -a profiling.md
done
continue-on-error: false
- name: Upload test results
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_requests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
- name: Setup Node.JS
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22

- name: Install Dependencies
run: npm ci
Expand Down
16 changes: 16 additions & 0 deletions .omc/state/checkpoints/checkpoint-2026-07-03T15-41-47-636Z.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"created_at": "2026-07-03T15:41:47.635Z",
"trigger": "manual",
"active_modes": {},
"todo_summary": {
"pending": 0,
"in_progress": 0,
"completed": 0
},
"wisdom_exported": false,
"background_jobs": {
"active": [],
"recent": [],
"stats": null
}
}
12 changes: 7 additions & 5 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ and are proven/verified using the underlying proof system.
│ Pasta curves (Pallas / Vesta cycle) │
├─────────────────────────────────────────────────┤
│ Runtime Target │
WASM (browser/Node via wasm-bindgen)
native .node or WASM via napi-rs (kimchi-napi)
└─────────────────────────────────────────────────┘
```

Expand Down Expand Up @@ -75,10 +75,12 @@ critical to understand before making changes:

### WASM boundary

The Rust backend is compiled to WASM via `wasm-bindgen` for use in both browser
and Node.js environments. This boundary is a major source of subtle bugs — see
`AGENT_LOG.md` for historical context on panics, threading issues, and memory
problems.
The Rust backend is the `kimchi-napi` crate (napi-rs), built two ways from the
same source: native `.node` prebuilds per platform, and a
`wasm32-wasip1-threads` WASM build (loaded via `@napi-rs/wasm-runtime`) used as
the portable fallback in Node and in browsers. The old `wasm-bindgen` layer was
removed — see `PLAN.md` for the migration and `AGENT_LOG.md` for historical
context on panics, threading issues, and memory problems.

## Essential Commands

Expand Down
Loading
Loading