● Phase E — CI (both tiers) #30

Merged
navicore merged 2 commits from wasm-tier-2-phase-E into main 2026-06-21 02:53:00 +00:00
Owner

E2 — gate recipes (justfile)

  • Renamed reactor-smoke → wasm-reactor-smoke (matching its wasm-smoke sibling and the plan),
    updating the references in link.rs and the smoke driver.
  • wasm-lint — clippy --features wasm over the feature-gated compiler code (worker glue,
    reactor link, embedded archives). The default just lint doesn't enable the feature, so this
    is the only thing that lints those paths.
  • wasm-ci — aggregate gate: wasm-lint + wasm-smoke (Tier 1) + wasm-reactor-smoke (Tier 2).
    The single entry point the workflow calls. Updated the stale "LOCAL only — not in CI yet"
    comments on both smokes.

E1/E3 — separate CI workflow (.forgejo/workflows/wasm.yml, new)

  • A distinct job from ci-linux.yml, so a missing wasm toolchain fails this workflow without
    breaking the core just ci (exactly the E2 isolation requirement).
  • Installs the toolchain in-job — rustup target add wasm32-wasip1 wasm32-unknown-unknown,
    rustup component add llvm-tools-preview, and wasmtime via the official installer; verifies
    node (the Tier-2 V8 driver). Follows ci-linux.yml's conventions (navicore-rust runner, cargo
    caches, no target/ cache, only-just calls). These setup steps double as the E1
    image-provisioning spec — once the image carries them they're no-ops.
  • E3 assertions live in the smokes the workflow runs: byte-identical-to-native on both tiers,
    plus the 1,000,000-deep constant-stack proof on wasmtime (Tier 1) and V8 (Tier 2) — the
    manual gate findings, now automated.

Verification

Ran the exact thing the workflow runs — just wasm-ci — end-to-end after provisioning the same
toolchain locally (installed wasmtime):

  • wasm-lint
  • Tier 1: 3 queries byte-identical + 1M-deep on wasmtime
  • Tier 2: 3 queries byte-identical + 1M-deep on V8/Node
  • wasm gates passed (Tier 1 wasi + Tier 2 reactor)

Full workspace tests green; fmt + clippy clean (default and --features wasm); main just ci
confirmed unchanged (wasm gates stay separate).

Honest caveat: I can't execute the Forgejo runner from here, so the workflow file itself
isn't CI-validated — but just wasm-ci (everything it invokes) is verified locally against the
identical toolchain the workflow provisions, and the YAML parses clean. The one thing that
depends on the runner image is node being present (E1); the workflow verifies it explicitly
and fails loud if absent.

Only F (docs: Cloudflare deploy tutorial, the D3 contract + tuning, ROADMAP M11) remains.

E2 — gate recipes (justfile) - Renamed reactor-smoke → wasm-reactor-smoke (matching its wasm-smoke sibling and the plan), updating the references in link.rs and the smoke driver. - wasm-lint — clippy --features wasm over the feature-gated compiler code (worker glue, reactor link, embedded archives). The default just lint doesn't enable the feature, so this is the only thing that lints those paths. - wasm-ci — aggregate gate: wasm-lint + wasm-smoke (Tier 1) + wasm-reactor-smoke (Tier 2). The single entry point the workflow calls. Updated the stale "LOCAL only — not in CI yet" comments on both smokes. E1/E3 — separate CI workflow (.forgejo/workflows/wasm.yml, new) - A distinct job from ci-linux.yml, so a missing wasm toolchain fails this workflow without breaking the core just ci (exactly the E2 isolation requirement). - Installs the toolchain in-job — rustup target add wasm32-wasip1 wasm32-unknown-unknown, rustup component add llvm-tools-preview, and wasmtime via the official installer; verifies node (the Tier-2 V8 driver). Follows ci-linux.yml's conventions (navicore-rust runner, cargo caches, no target/ cache, only-just calls). These setup steps double as the E1 image-provisioning spec — once the image carries them they're no-ops. - E3 assertions live in the smokes the workflow runs: byte-identical-to-native on both tiers, plus the 1,000,000-deep constant-stack proof on wasmtime (Tier 1) and V8 (Tier 2) — the manual gate findings, now automated. Verification Ran the exact thing the workflow runs — just wasm-ci — end-to-end after provisioning the same toolchain locally (installed wasmtime): - wasm-lint ✅ - Tier 1: 3 queries byte-identical + 1M-deep on wasmtime ✅ - Tier 2: 3 queries byte-identical + 1M-deep on V8/Node ✅ - ✅ wasm gates passed (Tier 1 wasi + Tier 2 reactor) Full workspace tests green; fmt + clippy clean (default and --features wasm); main just ci confirmed unchanged (wasm gates stay separate). Honest caveat: I can't execute the Forgejo runner from here, so the workflow file itself isn't CI-validated — but just wasm-ci (everything it invokes) is verified locally against the identical toolchain the workflow provisions, and the YAML parses clean. The one thing that depends on the runner image is node being present (E1); the workflow verifies it explicitly and fails loud if absent. Only F (docs: Cloudflare deploy tutorial, the D3 contract + tuning, ROADMAP M11) remains.
● Phase E — CI (both tiers)
Some checks failed
CI - Linux / CI - Linux x86_64 (pull_request) Successful in 1m0s
WASM Gates / WASM gates (Tier 1 wasi + Tier 2 reactor) (pull_request) Failing after 18s
eec9619598
E2 — gate recipes (justfile)
  - Renamed reactor-smoke → wasm-reactor-smoke (matching its wasm-smoke sibling and the plan),
  updating the references in link.rs and the smoke driver.
  - wasm-lint — clippy --features wasm over the feature-gated compiler code (worker glue,
  reactor link, embedded archives). The default just lint doesn't enable the feature, so this
  is the only thing that lints those paths.
  - wasm-ci — aggregate gate: wasm-lint + wasm-smoke (Tier 1) + wasm-reactor-smoke (Tier 2).
  The single entry point the workflow calls. Updated the stale "LOCAL only — not in CI yet"
  comments on both smokes.

  E1/E3 — separate CI workflow (.forgejo/workflows/wasm.yml, new)
  - A distinct job from ci-linux.yml, so a missing wasm toolchain fails this workflow without
  breaking the core just ci (exactly the E2 isolation requirement).
  - Installs the toolchain in-job — rustup target add wasm32-wasip1 wasm32-unknown-unknown,
  rustup component add llvm-tools-preview, and wasmtime via the official installer; verifies
  node (the Tier-2 V8 driver). Follows ci-linux.yml's conventions (navicore-rust runner, cargo
  caches, no target/ cache, only-just calls). These setup steps double as the E1
  image-provisioning spec — once the image carries them they're no-ops.
  - E3 assertions live in the smokes the workflow runs: byte-identical-to-native on both tiers,
  plus the 1,000,000-deep constant-stack proof on wasmtime (Tier 1) and V8 (Tier 2) — the
  manual gate findings, now automated.

  Verification

  Ran the exact thing the workflow runs — just wasm-ci — end-to-end after provisioning the same
  toolchain locally (installed wasmtime):
  - wasm-lint 
  - Tier 1: 3 queries byte-identical + 1M-deep on wasmtime 
  - Tier 2: 3 queries byte-identical + 1M-deep on V8/Node 
  -  wasm gates passed (Tier 1 wasi + Tier 2 reactor)

  Full workspace tests green; fmt + clippy clean (default and --features wasm); main just ci
  confirmed unchanged (wasm gates stay separate).

  Honest caveat: I can't execute the Forgejo runner from here, so the workflow file itself
  isn't CI-validated — but just wasm-ci (everything it invokes) is verified locally against the
  identical toolchain the workflow provisions, and the YAML parses clean. The one thing that
  depends on the runner image is node being present (E1); the workflow verifies it explicitly
  and fails loud if absent.

  Only F (docs: Cloudflare deploy tutorial, the D3 contract + tuning, ROADMAP M11) remains.
Author
Owner

Review — #30 Phase E: CI (both tiers)

The workflow structure is right and matches ci-linux.yml's conventions exactly — same pull_request:[main] + workflow_dispatch triggers, navicore-rust runner, actions/checkout@v6, actions/cache@v5, registry+index caches but no target/ cache, and the "only call just" discipline. Keeping the wasm gates in a separate workflow so a missing wasm toolchain can't break the core just ci (E2) is the correct isolation, and folding the toolchain provisioning into in-job steps that double as the image-provisioning spec is a nice touch. The node --version check failing loud if the V8 driver is absent is the right call given Tier 2 depends on it. Honest caveat about not being able to exercise the Forgejo runner itself is fair.

But the gate as written will fail on its first real run on a clean runner.

Blocker

1. The --features wasm recipes don't depend on the archives they require, so wasm-ci panics before it lints anything. build.rs embeds both wasm archives unconditionally whenever CARGO_FEATURE_WASM is set:

if env::var_os("CARGO_FEATURE_WASM").is_some() {
    embed_wasm_archive(&join("wasm32-wasip1/release/libplg_runtime.a"), );          // tier 1
    embed_wasm_archive(&join("wasm32-unknown-unknown/release/libplg_runtime.a"), ); // tier 2
}

and embed_wasm_archive panics if the .a is missing. So any cargo … --features wasm invocation needs both archives on disk. Now look at the recipe prerequisites:

  • wasm-lint: — depends on neither archive, runs cargo clippy --features wasm. It's the first prereq in wasm-ci: wasm-lint wasm-smoke wasm-reactor-smoke, and just runs prerequisites left-to-right, so it executes before wasm-smoke/wasm-reactor-smoke build anything. On a fresh runner — target/ is deliberately not cached, and the image doesn't ship the archives — build.rs panics and the gate dies before a single lint runs.
  • wasm-smoke: build-runtime-wasm — builds only the tier-1 archive, but its second compile is cargo run --features wasm … --target wasm32-wasi, which (per above) also needs the tier-2 reactor archive. That archive isn't built until wasm-reactor-smoke's prereq, which runs after wasm-smoke. So even with wasm-lint fixed, wasm-smoke panics next.

Only wasm-reactor-smoke happens to be safe, and only because it runs last (by which point both build-runtime-* recipes have run). This passed locally because install-wasm had already left both archives in target/; CI starts clean, so the masking artifact isn't there.

Cleanest fix: make every --features wasm consumer depend on both builds. e.g. add a combined prereq

build-runtime-wasm-all: build-runtime-wasm build-runtime-wasm-reactor

wasm-lint: build-runtime-wasm-all
wasm-smoke: build-runtime-wasm-all
wasm-reactor-smoke: build-runtime-wasm-all

(just dedups the shared prereqs, so each archive still builds once per wasm-ci.) Or, minimally, list both builds first in wasm-ci and give wasm-lint the prereqs so just wasm-lint works standalone. Either way, this needs to land before the workflow can be trusted — right now CI is green locally and red on the runner, which is the worst failure mode for a gate whose whole job is to be trustworthy.

Small observation

2. Stale reactor-smoke reference survived the rename. The PR renamed the recipe to wasm-reactor-smoke and updated link.rs + scripts/reactor-smoke.mjs, but crates/compiler/src/worker_glue.rs:8 still says "the reactor-smoke test driver import it." Cosmetic, but it's exactly the kind of reference the rename was meant to sweep.

Net: #1 is a genuine blocker — verify by running just wasm-ci after cargo clean (or rm -rf target/wasm32-*) so no pre-built archives mask it; that reproduces what the runner sees.

## Review — #30 Phase E: CI (both tiers) The workflow structure is right and matches `ci-linux.yml`'s conventions exactly — same `pull_request:[main]` + `workflow_dispatch` triggers, `navicore-rust` runner, `actions/checkout@v6`, `actions/cache@v5`, registry+index caches but no `target/` cache, and the "only call `just`" discipline. Keeping the wasm gates in a *separate* workflow so a missing wasm toolchain can't break the core `just ci` (E2) is the correct isolation, and folding the toolchain provisioning into in-job steps that double as the image-provisioning spec is a nice touch. The `node --version` check failing loud if the V8 driver is absent is the right call given Tier 2 depends on it. Honest caveat about not being able to exercise the Forgejo runner itself is fair. But the gate as written will fail on its first real run on a clean runner. ### Blocker **1. The `--features wasm` recipes don't depend on the archives they require, so `wasm-ci` panics before it lints anything.** `build.rs` embeds *both* wasm archives unconditionally whenever `CARGO_FEATURE_WASM` is set: ```rust if env::var_os("CARGO_FEATURE_WASM").is_some() { embed_wasm_archive(&…join("wasm32-wasip1/release/libplg_runtime.a"), …); // tier 1 embed_wasm_archive(&…join("wasm32-unknown-unknown/release/libplg_runtime.a"), …); // tier 2 } ``` and `embed_wasm_archive` **panics** if the `.a` is missing. So *any* `cargo … --features wasm` invocation needs both archives on disk. Now look at the recipe prerequisites: - `wasm-lint:` — depends on **neither** archive, runs `cargo clippy --features wasm`. It's the **first** prereq in `wasm-ci: wasm-lint wasm-smoke wasm-reactor-smoke`, and `just` runs prerequisites left-to-right, so it executes before `wasm-smoke`/`wasm-reactor-smoke` build anything. On a fresh runner — `target/` is deliberately not cached, and the image doesn't ship the archives — `build.rs` panics and the gate dies before a single lint runs. - `wasm-smoke: build-runtime-wasm` — builds only the **tier-1** archive, but its second compile is `cargo run --features wasm … --target wasm32-wasi`, which (per above) also needs the **tier-2** reactor archive. That archive isn't built until `wasm-reactor-smoke`'s prereq, which runs *after* `wasm-smoke`. So even with `wasm-lint` fixed, `wasm-smoke` panics next. Only `wasm-reactor-smoke` happens to be safe, and only because it runs last (by which point both `build-runtime-*` recipes have run). This passed locally because `install-wasm` had already left both archives in `target/`; CI starts clean, so the masking artifact isn't there. Cleanest fix: make every `--features wasm` consumer depend on both builds. e.g. add a combined prereq ```make build-runtime-wasm-all: build-runtime-wasm build-runtime-wasm-reactor wasm-lint: build-runtime-wasm-all wasm-smoke: build-runtime-wasm-all wasm-reactor-smoke: build-runtime-wasm-all ``` (`just` dedups the shared prereqs, so each archive still builds once per `wasm-ci`.) Or, minimally, list both builds first in `wasm-ci` *and* give `wasm-lint` the prereqs so `just wasm-lint` works standalone. Either way, this needs to land before the workflow can be trusted — right now CI is green locally and red on the runner, which is the worst failure mode for a gate whose whole job is to be trustworthy. ### Small observation **2. Stale `reactor-smoke` reference survived the rename.** The PR renamed the recipe to `wasm-reactor-smoke` and updated `link.rs` + `scripts/reactor-smoke.mjs`, but `crates/compiler/src/worker_glue.rs:8` still says "the `reactor-smoke` test driver import it." Cosmetic, but it's exactly the kind of reference the rename was meant to sweep. Net: #1 is a genuine blocker — verify by running `just wasm-ci` after `cargo clean` (or `rm -rf target/wasm32-*`) so no pre-built archives mask it; that reproduces what the runner sees.
● Bash(cd /home/navicore/git/navicore/patch-prolog
All checks were successful
WASM Gates / WASM gates (Tier 1 wasi + Tier 2 reactor) (pull_request) Successful in 29s
CI - Linux / CI - Linux x86_64 (pull_request) Successful in 1m34s
192954ab49
cargo build -p patch-prolog-compiler -p patch-prolog-runtime 2>&1 | tail -2…)
  ⎿     Compiling patch-prolog-compiler v0.2.0 (/home/navicore/git/navicore/patch-prolog/c
     rates/compiler)
         Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.41s
     … +10 lines (ctrl+o to expand)

● Done. Yes — both belonged in done/, now that Tier 1 (M10) and Tier 2 (M11) have shipped.

  Moved (plain mv, left unstaged for you):
  - docs/design/WASM.md → docs/design/done/WASM.md
  - docs/design/WASM_TIER2_PLAN.md → docs/design/done/WASM_TIER2_PLAN.md

  docs/design/ now holds only the still-active tracks (INVOCATION.md, RUST_EMBEDDING.md),
  matching how every other shipped design doc (FACT_TABLE, SPANS, COMPILATION_MODEL…) already
  lives in done/.

  Reference hygiene:
  - Updated the full-path docs/design/WASM*.md references I'd introduced across the
  code/build/docs (reactor.rs, machine.rs, core.rs, lib.rs, link.rs, build.rs, worker_glue.rs,
  Cargo.toml, justfile, ROADMAP.md, reactor-smoke.mjs) → docs/design/done/WASM*.md. Verified:
  no stale full paths remain, no done/done double-replacement.
  - Bare-name citations (WASM.md finding #1, WASM_TIER2_PLAN.md A2) left as-is — that's the
  project's location-agnostic convention (same as the existing SPANS.md/FACT_TABLE.md
  citations), so they survive the move.

  Marked the archived docs as shipped — both headers now read Status: SHIPPED (M10/M11) and
  point to the user-facing guides, instead of "exploratory" / "working doc; delete when Tier 2
  ships."

  Verified clean: compiler + runtime build, worker_glue tests pass, fmt clean. The changes are
  all comments/doc-moves, so no functional impact.
navicore deleted branch wasm-tier-2-phase-E 2026-06-21 02:53:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
navicore/patch-prolog!30
No description provided.