name: CI on: # `push` unrestricted plus `pull_request` ran every job TWICE on any branch # with an open PR — visible as the (push) / (pull_request) pairs in the checks # list. Restricting push to main means a PR branch is covered once by # pull_request, and main once by push, which is also what pages-deploy needs. push: branches: [main] pull_request: env: CARGO_TERM_COLOR: always jobs: test: name: Test and lint (host) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo fmt --all -- --check - run: cargo clippy --workspace --all-targets -- -D warnings - run: cargo test --workspace # The comment cap and the test-evidence keys (improvise-9c8c); without # this step the rule is advisory. See context/design-principles.md §6. - name: The comment checker catches what it has been fixed for run: python3 scripts/check-comment-runs.py --self-test - name: Comment runs capped at two lines run: python3 scripts/check-comment-runs.py - name: Test-evidence headings name real tests run: python3 scripts/check-comment-runs.py --check-evidence # Too slow on GitHub Actions # # nix-test: # name: Nix Test # runs-on: ubuntu-latest # steps: # - name: Free up disk space # run: | # sudo rm -rf /usr/share/dotnet \ # /usr/local/lib/android # sudo df -h # - uses: actions/checkout@v7 # - name: Install Nix # uses: DeterminateSystems/nix-installer-action@v14 # - name: Magic Nix Cache # uses: DeterminateSystems/magic-nix-cache-action@v8 # - name: Check Nix health # run: nix doctor # - run: nix build wasm: name: wasm32 check (browser bundle crates) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 # Only the crates that go into the browser bundle. The root `improvise` # crate is the terminal binary and depends on crossterm, which does not # build for wasm — that is correct, not a gap (improvise-ywd). - name: improvise-core run: cargo check -p improvise-core --target wasm32-unknown-unknown - name: improvise-formula run: cargo check -p improvise-formula --target wasm32-unknown-unknown - name: improvise-protocol run: cargo check -p improvise-protocol --target wasm32-unknown-unknown - name: improvise-io run: cargo check -p improvise-io --target wasm32-unknown-unknown - name: improvise-command run: cargo check -p improvise-command --target wasm32-unknown-unknown # The browser client itself. This is the one crate for which a wasm # build is the *point* rather than a property worth keeping, so a # failure here is not a portability regression but a broken deliverable # (improvise-gsw). - name: improvise-wasm-client run: cargo check -p improvise-wasm-client --target wasm32-unknown-unknown # The worker half of the standalone deployment (improvise-9x6). Same # terms as the client: a wasm build is the point, so a failure here is a # broken deliverable rather than a portability regression. - name: improvise-worker-server run: cargo check -p improvise-worker-server --target wasm32-unknown-unknown # Not in the browser bundle yet — nothing depends on improvise-gp. Gated # anyway because it is a candidate (fitting a tree in the browser) and # staying wasm-clean is free now, expensive to retrofit (improvise-z5eg). - name: improvise-gp run: cargo check -p improvise-gp --target wasm32-unknown-unknown kb: name: Knowledge base consistency runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: cachix/install-nix-action@v31 # kb.pl is executable knowledge, so it gets checked like code: rules # must reject what they should, domains must close, and functional keys # must not carry conflicting values. See kb_report/0 in the file. - name: kb_consistent run: | nix shell nixpkgs#trealla --command tpl prolog-sessions/kb.pl \ -g "(kb_consistent -> halt(0) ; (kb_report, halt(1)))" # --------------------------------------------------------------------------- # The browser harness (improvise-vvl3, absorbing improvise-xf1). # # This is the only job that RUNS any of the browser half. `OpfsFs`, # `web/app.js` and `web/worker.js` were previously compile-checked, # syntax-checked and read — never executed — so every bug in them was found # by reading or by a person pressing keys, neither of which is repeatable. # # It can fail the build, deliberately: a harness nobody runs is worse than # none, because it reads as coverage. Playwright's browser download is ~80 MB # and takes well under a minute on ubuntu-latest, which is affordable next to # the two wasm-pack builds this job already needs. browser: name: Browser harness (OPFS contract + shell) runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown # Named rather than assumed: this job lints harness/opfs-contract, # which the `test` job's workspace-wide fmt and clippy cannot reach. components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v4 with: node-version: 22 - name: Install wasm-pack env: WASM_PACK_VERSION: v0.15.0 run: | set -eu url="https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl.tar.gz" curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \ "wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack" wasm-pack --version # Not a workspace member (it only builds for wasm32), so the `test` job's # workspace-wide fmt and clippy do not reach it. - name: Lint the harness crate working-directory: harness/opfs-contract run: | cargo fmt --all -- --check cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings - name: Install the harness working-directory: harness run: npm ci # All three, because improvise-xf1's acceptance criterion names Chrome, # Firefox and Safari and `createSyncAccessHandle` is implemented # separately by each engine. WebKit is installed even though its OPFS is # absent today: the spec skips on a runtime probe, so the day that build # gains OPFS the check starts running without a CI change. - name: Install browsers working-directory: harness run: npx playwright install --with-deps chromium firefox webkit # Builds both site bundles, the harness's own wasm bundle, and assembles # `harness/serve` — the same tree `pages-build` publishes, plus /opfs/. - name: Build the harness run: ./harness/build.sh - name: Run the harness working-directory: harness run: npx playwright test - if: failure() uses: actions/upload-artifact@v7 with: name: playwright-traces path: harness/test-results retention-days: 7 # --------------------------------------------------------------------------- # GitHub Pages delivery (improvise-d31). # # Two jobs on purpose. `pages-build` runs on every push and pull request, so # a change that breaks `web/build.sh` or the bundle fails on the PR that made # it — the build is a gate. `pages-deploy` runs only on a push to main and # `needs` the check jobs above, so nothing that failed `cargo test`, the # wasm32 gate or the browser harness can reach the published site. The deploy # depends on those checks rather than repeating them (improvise-d31 AC 2). # # Not path-filtered, deliberately. The bundles are `improvise-wasm-client` and # `improvise-worker-server` plus, between them, most of the workspace — the # worker bundle is the whole App: core, formula, io, protocol and command. A # filter # that meant "skip when the bundle cannot have changed" would have to name # most of the workspace, and the failure mode of getting it wrong is a stale # site with no signal that it is stale. A redundant build is the cheaper error. pages-build: name: Browser bundle + static site runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 # Pinned release binary rather than `cargo install` (a three-minute # source build) or a third-party action. wasm-pack runs its own bundled # `wasm-opt`, which is why improvise-wasm-client's Cargo.toml names # `--enable-bulk-memory`: without it the optimizer rejects the # `memory.copy` LLVM emits regardless. # v0.15.0 is what `nix develop` supplies, and the bundle was built and # measured against it locally (346 KB, 120 KB gzipped). Pinning the same # version is what makes that local run evidence about this one. - name: Install wasm-pack env: WASM_PACK_VERSION: v0.15.0 run: | set -eu url="https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl.tar.gz" curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \ "wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack" wasm-pack --version # The same script a developer runs locally, so there is one answer to # "how is the bundle built" rather than two that can drift. - name: Build the wasm bundle run: ./web/build.sh # `web/assemble.sh`, not an inline copy of it: the browser harness job # below assembles the same tree, and a harness that runs against a # different arrangement of files is testing something nobody deploys. - name: Assemble the site run: ./web/assemble.sh site "$GITHUB_SHA" - run: find site -type f | sort - run: du -sh site - uses: actions/upload-pages-artifact@v5 with: path: site pages-deploy: name: Deploy to GitHub Pages needs: [test, wasm, browser, pages-build] if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: read pages: write id-token: write # A superseded deploy is allowed to finish rather than being cancelled: # `deploy-pages` cancelled midway can leave the Pages deployment in a # half-applied state, and the next push republishes anyway. concurrency: group: pages cancel-in-progress: false environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: # `enablement: false` (the default) configures the environment URL # without attempting to create or modify the Pages site via the API. # GitHub Pages must be enabled once in the repository settings # (Settings → Pages → Source: GitHub Actions) before this job will # produce a live URL. Using `enablement: true` fails when the workflow # token lacks admin permission to create the site (improvise-3gy). - uses: actions/configure-pages@v6 with: enablement: false - id: deployment uses: actions/deploy-pages@v5