Files
pbakaus_impeccable/docs/ENGINE.md
T
Paul BakausandClaude Fable 5.1 4369ad538d Open the detector: the rule crates join the workspace, the C-ABI goes away
The detector is open source. The rules it ships were already public in this
repo's git history and in every npm tarball of the JS engine, so a closed
binary bought nothing it could keep; the moat is the service (the catalog,
the labs, the review pipeline), not the check functions. Keeping them behind
a prebuilt archive cost a C-ABI, an exact toolchain pin, a build-time
download, a second release to order ahead of every engine release, and a
serde layer that had to serve two encodings.

Deleted
- crates/core/src/ffi.rs, crates/core/build.rs, crates/core/tests/boundary.rs
  and the shim modules under src/checks and src/browser.
- crates/foundation/src/boundary.rs and the postcard dependency.
- DETECTOR_VERSION, scripts/check-detector-release.mjs and its test, the
  check:detector-release script, the detector gate and
  IMPECCABLE_SKIP_DETECTOR_CHECK in scripts/release.mjs.
- scripts/lib/detector-bundle.mjs and tests/detector-bundle.test.mjs (the
  vendoring path for the closed browser bundle).
- scripts/build-browser-detector.js and the build:browser script (a stub
  since the JS engine left the tree).
- xtask's detector-archive subcommand and its public-repo lookup.

Came back
- crates/core is now the rule logic itself: every check_* / scan_*, the
  browser adapters, the visual-contrast decisions. It re-exports foundation
  as before, so no consumer changed. Its vectors dispatcher is the union of
  both id tables again, and tests/vectors.rs replays the frozen vectors
  straight through it.
- crates/wasm and crates/xtask join the workspace. cargo xtask bundle builds
  the in-page bundle from browser-bundle/ plus the wasm core, writes
  dist/, refreshes the tracked crates/live/assets/detect-antipatterns-
  browser.js, and writes extension/detector/. bun run build:extension runs
  it instead of downloading.
- crates/live/assets/detect-antipatterns-browser.js is tracked again; live
  mode embeds it and serves it as /detect.js.
- Serde is back to plain derives: no is_human_readable branch in
  js::json_number, derived Serialize for Rgba and BrowserFinding with their
  skip_serializing_if attributes.
- profile.release has lto = "fat" again; rust-toolchain.toml is plain
  stable plus the wasm32 target. The rust, rust-windows and oracle CI jobs
  lose continue-on-error and can be required.

Verified
- cargo build --workspace --all-targets: clean, no warnings.
- cargo test --workspace: 346 pass, 0 fail (the 8 boundary tests are gone
  with the boundary).
- cargo build -p impeccable-wasm --target wasm32-unknown-unknown --release: ok.
- cargo xtask bundle && cargo xtask bundle --check: reproducible; the
  regenerated bundle is committed (it differs from the archived one, which
  was built with a pinned rustc and lto = false).
- cargo build --release -p impeccable: no linker warnings, 12.5 MB (the
  same source at lto = false is 13.1 MB).
- oracle: 795 pass, 0 fail, 0 accepted deltas, 0 missing goldens.
- bun run build, bun run build:extension, web-ext lint (0 errors,
  8 warnings), bun run test: 363 + 80 + 1 + 1 + 133 + 180 + 4 pass, 0 fail.
- impeccable detect --no-config --json tests/fixtures/antipatterns: 128.7 ms
  median of 5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
2026-09-03 09:11:33 -07:00

6.3 KiB

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/.

Everything is in this repo, Apache-2.0, and builds offline from source. No part of the engine is fetched at build time.

Layout

Cargo.toml              the workspace (crates/*), release profile
rust-toolchain.toml     the channel plus the wasm32 target
ENGINE_VERSION          which engine release the launcher / npm shim download
.cargo/config.toml      the `cargo xtask` alias
browser-bundle/         the page JS the in-page bundle is built from
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   JS-semantics helpers, color, findings, the rule registry, inline
               ignores, the Dom trait, SnapshotDom, and the plain-data types
               every check takes in and hands back
  core         the rule logic: every `check_*` / `scan_*` and its heuristics,
               the browser rule adapters, the visual-contrast decisions
  wasm         wasm-bindgen exports over `core` (the in-page bundle and the
               extension's offscreen core)
  xtask        `cargo xtask bundle`: builds the in-page bundle and the
               extension pieces

crates/core re-exports the foundation modules under its own paths, so every consumer names one crate: impeccable_core::js, impeccable_core::color, impeccable_core::checks::rules::check_colors, impeccable_core::browser::driver::collect_browser_findings. The split between the two crates is about what a check is written against, not about who may see it.

Build and test:

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 frozen function-level vectors in tests/oracle/vectors/calls/ replay through impeccable_core::vectors::call (cargo test -p impeccable-core), which is the union of foundation's dispatch arms and the core's.

The browser bundle

The same rules that run natively run in a page, compiled to WebAssembly. cargo xtask bundle is the one command that produces every browser artifact:

  1. wasm-pack build crates/wasm --target no-modules --release into target/wasm-bundle/ (opt-level z, then wasm-opt).
  2. Concatenate the page JS in browser-bundle/*.js in a fixed order with the wasm-bindgen glue and the .wasm embedded as base64. The page JS only implements the Dom probe, marshals JSON, and draws the overlay; no rule logic lives there.
  3. Write dist/detect-antipatterns-browser.js and dist/antipatterns.json, and copy the bundle to crates/live/assets/detect-antipatterns-browser.js. That copy is a tracked generated file: crates/live/src/browser_assets.rs embeds it with include_str! and the live server hands it to the browser as /detect.js, so the binary has to carry it.
  4. Write the five extension pieces into extension/detector/ (snapshot.js, overlay.js, core.js, core_bg.wasm, antipatterns.json). That directory is gitignored; bun run build:extension runs this task and then packages the zips.

cargo xtask bundle --check rebuilds and fails when the tracked live asset is stale, which is the CI staleness gate. The build is deterministic: same sources, same bytes.

wasm-pack is the one extra tool this needs (cargo install wasm-pack --locked) plus the wasm32-unknown-unknown target, which rust-toolchain.toml requests. IMPECCABLE_XTASK_SKIP_WASM_PACK=1 reuses whatever is already in target/wasm-bundle/, for iterating on the page JS alone. IMPECCABLE_EXTENSION_SKIP_BUNDLE=1 lets bun run build:extension skip the bundle step when extension/detector/ is already complete, for CI matrices that pre-built it.

Run cargo xtask bundle after touching crates/core, crates/wasm, or browser-bundle/, and commit the refreshed live asset.

Releases

Two release kinds touch the runtime, in this order:

  1. Engine (engine-v<ENGINE_VERSION>): bun run release:engine verifies the version, the npm platform-package pins and a clean tree, then 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>/.
  2. npm platform packages, then the skill and CLI releases, which scripts/check-engine-release.mjs gates on the engine release.

The extension ships its own vendored WASM core and never execs the engine binary, so bun run release:ext is exempt from that gate. It does need bun run build:extension (and therefore a Rust toolchain and wasm-pack) before the zip is attached.

CI runs the workspace build and tests (rust, rust-windows) and replays the oracle against a release build from the checkout under test.