7e991360df
improvise-vvl3, absorbing improvise-xf1. The condition the user set when
deferring browser testing — "WE HAVE TO HAVE SOMETHING TO TEST" — has been
met: after improvise-9x6, djm, hwpi and the demo model there is a complete
self-contained application whose entire untested surface is what a harness
covers.
`python3 -m http.server` plus one Playwright project, in `harness/`:
harness/opfs-contract/ a wasm crate that runs
`improvise_io::storage::contract::storage_contract`
against `OpfsFs` — the shared function, not a
transcription of it
harness/www/opfs/ the page and the dedicated Web Worker it runs in;
`createSyncAccessHandle` exists nowhere else
harness/tests/ two specs
harness/build.sh site + harness bundle -> harness/serve
It lives at the repo root rather than under `web/`, because `web/` is the
*source of what ships* and every file in it is copied to the site. The
harness serves the **assembled** tree (`web/assemble.sh`, extracted from
the CI workflow in this commit so there is one answer to "how is the site
arranged" rather than two) plus its own `/opfs/` — a harness that runs
against a different arrangement of files is testing something nobody
deploys. `harness/opfs-contract` is deliberately not a workspace member: it
only builds for wasm32, so a member would break `cargo test --workspace`.
What this runs that nothing has ever run:
- `storage_contract` against `OpfsFs`, at `""` **and** at `"docs"`. Two
bugs fell straight out — improvise-vhcz's `list` path shape, whose fix
the `"docs"` run guards (reverting it fails with `lists the one
document`), and the zero-byte entry fix in the previous commit, which
this found by failing on `starts empty`.
- The `keys()` async-iterator walk in `refresh`, hand-rolled through
`js_sys::Reflect` and named by xf1 as the least certain code in the file.
Three entries written out of order, so one passing by accident cannot
carry it.
- `FileSystemReadWriteOptions`' `at` on read and write: a long write then a
short one, which is where an append or a non-zero start shows up.
- `commit_deletes`, both halves — queued but still in OPFS before, gone
after — and **across a reload**, which is the only way to ask whether
OPFS persisted. That reload is also improvise-bck's persistence criterion
at the storage layer.
- improvise-hwpi's three one-assertion shell tests, which that ticket said
it could not write: status after a bad frame, status with no peer, one
repaint on commit. Plus the first-frame watchdog, which was found by
reading and only because someone went looking.
Check the checker, throughout, because a harness that passes against a stub
— or that silently skips because the worker never started — is exactly this
repo's characteristic defect wearing a green tick:
- `run_canary` deliberately fails, through the *same* reporting path every
real check uses, and the spec asserts it is reported as a failure with its
message intact.
- The spec asserts the report contains exactly the expected check names, in
order, so a battery that quietly shrinks is a failure.
- Every shell assertion was injected and watched to fail: removing the
bad-frame catch, the watchdog's `setTimeout`, the socket `error` listener
and the `transport.send(frame)` on commit each fails its own test and
only its own.
- The commit test types `20000` and asserts `20,000`, so a page that echoed
what was typed instead of drawing what the host returned would fail.
One live bug is pinned rather than fixed. `a peer that cannot be reached`
asserts the *history* of `#status`, because improvise-m18f is real and
reproduced here: the socket's `close` listener overwrites the precise
"cannot reach" with "disconnected". That is filed and deliberately not the
same fix as the watchdog's (gotcha 48), so the test asserts both halves and
will speak up whichever one moves.
CI gets a `browser` job that can fail the build, and `pages-deploy` now
needs it. Playwright's Chromium download is ~80 MB and well under a minute
on ubuntu-latest, next to the two wasm-pack builds the job already needs —
so "impractical in this CI" does not arise. A harness nobody runs is worse
than none, because it reads as coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
222 lines
9.4 KiB
YAML
222 lines
9.4 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
|
|
- 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
|
|
- name: Install the harness
|
|
working-directory: harness
|
|
run: npm ci
|
|
- name: Install Chromium
|
|
working-directory: harness
|
|
run: npx playwright install --with-deps chromium
|
|
# 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
|