3c36a3b57f
Two Copilot findings on PR #24, both correct, both applied. **`test.fail` was masking setup failures.** Applied before navigation, it made any earlier failure — the page not loading, the script not starting, `#status` never updating — an *expected* failure, so CI stayed green through it. That is precisely the check-the-checker defect these tests exist to catch, wearing the name of its cure. Both tests now assert their setup first and apply `test.fail` immediately before the one assertion the known bug owns. Verified by breaking the setup (making `paintStatus` a no-op): before, 2 passed; after, **2 failed**. **Firefox now runs the whole battery, and it passes.** improvise-xf1's acceptance criterion names Chrome, Firefox and Safari, and the reason is real rather than ceremonial: `createSyncAccessHandle` is the one API this design rests on (gotcha 28) and each engine implemented it separately, so Chromium alone was one engine's opinion. `storage_contract` against `OpfsFs` now passes on **two** engines. **Safari is not covered, and the criterion is met for two of three.** Playwright's WebKit build provides no OPFS at all — `navigator.storage.getDirectory()` itself rejects with `UnknownError` — so there is nothing there for `OpfsFs` to be right or wrong about. Rather than delete the project and quietly narrow the claim, it is kept and the spec skips on a **runtime probe**: the day that build gains OPFS the checks start running with no CI change. The skip is conditional on the probe and not on the browser name, and an unexpected skip on any other engine **fails** with "that is a regression, not a known gap" — a silently skipped check being the failure this harness exists to prevent. The shell spec stays Chromium-only on purpose: it is about `web/app.js`'s own logic, which is engine-independent, and running `routeWebSocket` plus the worker handshake three times is slower and flakier to no benefit. 14 passing, 3 skipped, 36s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
237 lines
10 KiB
YAML
237 lines
10 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
|
|
jobs:
|
|
test:
|
|
name: Test and lint (host)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- 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
|
|
|
|
wasm:
|
|
name: wasm32 check (browser bundle crates)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- 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@v4
|
|
- uses: cachix/install-nix-action@v27
|
|
# 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@v4
|
|
- 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@v4
|
|
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@v4
|
|
- 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@v3
|
|
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@v5
|
|
with:
|
|
enablement: false
|
|
- id: deployment
|
|
uses: actions/deploy-pages@v4
|