Files
pbakaus_impeccable/docs/ENGINE.md
T
Paul BakausandClaude Fable 5.1 f2c9aeab5b build:extension: ship the wasm-core extension shell and vendor its detector from the detector release
`bun run build:extension` was broken on this branch: it still imported the
deleted JS engine (cli/engine/registry/antipatterns.mjs,
scripts/lib/browser-detector-bundle.js).

The shipped shell now matches the new design. The content script only
snapshots the DOM; an extension-owned offscreen document runs the
WebAssembly rule core over that snapshot, so the scanned page's CSP no
longer matters. That replaces the old approach of injecting a JS rules
bundle into the page. New files: extension/offscreen/offscreen.html, plus
the "offscreen" permission and a 'wasm-unsafe-eval' extension_pages CSP in
the manifest.

The manifest version stays at 1.3.3. The shell's own manifest carried
2.0.0; feature branches never bump versions, so the bump is a release step.

The five generated detector pieces (core.js, core_bg.wasm, snapshot.js,
overlay.js, antipatterns.json) are vendored at build time into the
gitignored extension/detector/ by the new scripts/lib/detector-bundle.mjs,
which resolves them the same three ways crates/core/build.rs resolves the
native archive: IMPECCABLE_DETECTOR_LIB/extension-detector/, the
~/.impeccable/detector/<DETECTOR_VERSION>/ cache, then a checksum-verified
download of detector-browser-bundle.zip from the detector release.
antipatterns.json is no longer regenerated here.

The zip packaging is unchanged. The Firefox variant still builds so
`web-ext lint` keeps covering the shared shell, but it cannot scan: Gecko
has no chrome.offscreen API. The build prints a one-line warning saying so.

Also here: a referenced-path check that fails the build when the manifest
or the service worker points at a file that is not in extension/, a
resolver unit test wired into the core suite, and the detector rule count
in the READMEs synced to the 61 the vendored registry carries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-01 16:20:54 -07:00

146 lines
7.3 KiB
Markdown

# The engine: the Rust runtime behind every skill verb
Every command the skill text runs is `{{scripts_path}}/impeccable <verb>`. The
launcher next to the skill (`skill/scripts/impeccable`, `impeccable.cmd`)
finds or downloads one static binary per platform and execs it. That binary
is built from this repo's Cargo workspace. There is no Node at runtime.
This page is the map for anyone building or changing the runtime. The
observable behavior of every verb is specified in `CLI-CONTRACT.md` and
pinned byte-for-byte by `tests/oracle/`.
## Layout
```
Cargo.toml the workspace (crates/*), release profile
rust-toolchain.toml EXACT rustc pin (see "The closed detector")
DETECTOR_VERSION which prebuilt detector release crates/core links
ENGINE_VERSION which engine release the launcher / npm shim download
crates/
cli the `impeccable` binary: verb router, exit codes
common Io handle (stdout/stderr/stdin/env/cwd), path + process helpers
context context, doctor, staleness, signals, concept-seed, pin, ...
hook the design hook (hook, hook-before-edit, hook-admin)
live live mode: server, wrap, accept, manual edits, Svelte/Vue
skills install / update / check / link (the old npm CLI verbs)
comp comp-fidelity pure libs (raster, png, metrics, fonts)
comp-verbs build-phase, comp-diff, comp-spec, font-match
detect `impeccable detect`: file walk, config, ignores, output, regex engine
html the static HTML engine: parser, cascade, static DOM, rule adapters
browser the URL engine: Chrome discovery, CDP, snapshot, visual pass
foundation OPEN helpers + boundary types: JS-semantics helpers, color,
findings, registry, inline ignores, the Dom trait, SnapshotDom
core the shim: re-exports foundation under the paths every crate
uses, and forwards the rule checks to the closed detector
```
Build and test:
```bash
cargo build --release -p impeccable # target/release/impeccable
cargo test --workspace
IMPECCABLE_BIN=target/release/impeccable node tests/oracle/run.mjs # the behavior gate
```
`bun run test` and the oracle find the binary through `IMPECCABLE_BIN`, then
`skill/scripts/bin/<os>-<arch>/` (`bun run fetch:engine` downloads the pinned
release there; `IMPECCABLE_BIN=target/release/impeccable bun run fetch:engine`
copies a local build), then `target/release/impeccable`, so a plain
`cargo build --release -p impeccable` is enough.
## The closed detector
The rule engine itself (the checks, the browser rule adapters, the visual
contrast decisions) is proprietary and lives in a private repo. It ships as a
prebuilt native archive per target, `libimpeccable_detector-<os>-<arch>.a`
(`impeccable_detector-windows-x64.lib`), published as the GitHub Release
`detector-v<DETECTOR_VERSION>` on this repo, next to
`detector-browser-bundle.zip` (the same rules compiled to wasm for the
extension, the live overlay and the site). That release also carries
`detect-antipatterns-browser.js` and its `.sha256`: the in-page bundle
`crates/core/build.rs` resolves the same three ways and hands to
`impeccable_core::browser::IN_PAGE_BUNDLE_JS`, which live mode serves as
`/detect.js`. It is generated, so it is not tracked here.
`crates/core/build.rs` resolves the archive in this order and links it:
1. `IMPECCABLE_DETECTOR_LIB=<dir>`: a directory holding the archive for the
current target (a local build of the detector repo).
2. `~/.impeccable/detector/<DETECTOR_VERSION>/<os>-<arch>/` (`IMPECCABLE_HOME`
moves the root).
3. A download from `detector-v<DETECTOR_VERSION>` into that cache, verified
against the `.sha256` sidecar. `IMPECCABLE_DETECTOR_BASE` overrides the
release root; `IMPECCABLE_DETECTOR_OFFLINE=1` refuses to download.
The in-page bundle follows the same order (beside the archive when
`IMPECCABLE_DETECTOR_LIB` supplies one, else the version cache, else a
verified download).
Three things follow from how that archive is made, and they are the reason
for three otherwise odd-looking settings:
- **The toolchain is pinned to an exact version.** The archive is the closed
crates' rlib objects repacked with `llvm-ar`, with no std inside. Its
objects reference std by mangled symbol name, which only resolves against
the same rustc build. `rust-toolchain.toml` pins it; rustup installs it on
first `cargo` invocation. A toolchain bump needs a new detector release.
- **The release profile has `lto = false`.** Fat and thin LTO internalize std
symbols the opaque archive still needs and the link fails with "symbol(s)
not found". Cargo's default thin-local LTO stays.
- **`crates/core` keeps the old paths.** Nothing outside `crates/core` and
`crates/foundation` knows about the boundary: `impeccable_core::checks::
rules::check_colors` is a one-line shim that encodes its argument, calls
the archive, decodes the result. The C-ABI is three symbols
(`det_abi_version`, `det_call`, `det_free`) and a host vtable the closed
side uses to call back into the open `Dom` / `StyleMap` implementations.
Every id and every type that crosses is declared in
`crates/foundation/src/boundary.rs`; the shim checks the ABI number once
and panics with a clear message when the archive was built for another.
The frozen function-level vectors in `tests/oracle/vectors/calls/` replay
through the shipped archive (`impeccable_core::vectors::call` forwards
unknown names to it), so the black box is verified the same way the open
code is.
## Releases
Three release kinds touch the runtime, in this order:
1. **Detector** (`detector-v<X>`, published by the private repo's CI to this
repo's Releases). `DETECTOR_VERSION` here pins it.
2. **Engine** (`engine-v<ENGINE_VERSION>`): `bun run release:engine` verifies
the detector release exists (`scripts/check-detector-release.mjs`), tags,
and pushes; `.github/workflows/release-engine.yml` builds the five targets
and publishes the binaries with `.sha256` sidecars. The launcher, the npm
shim and `impeccable install` download from
`github.com/pbakaus/impeccable/releases/download/engine-v<X>/`.
3. **npm platform packages**, then the **skill** and **CLI** releases, which
`scripts/check-engine-release.mjs` gates on the engine release.
The browser extension vendors from the same detector release: `bun run
build:extension` pulls `detector-browser-bundle.zip` for the pinned
`DETECTOR_VERSION` through `scripts/lib/detector-bundle.mjs`, which resolves
it the same three ways `crates/core/build.rs` resolves the archive, and
unpacks the five generated pieces into the gitignored `extension/detector/`.
CI runs the workspace build and tests (`rust`, `rust-windows`) and the oracle
against a source build; both are warn-only until the first detector release
exists, then their `continue-on-error` flips to false.
## Working on the detector
Changes to rule logic happen in the private detector repo. Point the shim at
a local build while iterating:
```bash
# in the detector repo
cargo xtask detector-archive --out /tmp/det
# here
IMPECCABLE_DETECTOR_LIB=/tmp/det cargo test --workspace
```
Adding a function that the open crates call: add its id to
`foundation/src/boundary.rs` (never renumber), the shim in `crates/core`, the
dispatcher arm in the detector repo, and bump `boundary::ABI` if any existing
signature or type changed. The shim's test diffs the two id tables.