Skip to content

feat(bazel): full AADL → WIT → wit-bindgen → .wasm chain builds end-to-end#2

Open
avrabe wants to merge 6 commits into
mainfrom
feat/bazel-wasm-component
Open

feat(bazel): full AADL → WIT → wit-bindgen → .wasm chain builds end-to-end#2
avrabe wants to merge 6 commits into
mainfrom
feat/bazel-wasm-component

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 24, 2026

What

Adds the full Bazel + Rust scaffolding so the AADL → WIT → wit-bindgen
→ .wasm component chain actually builds and tests for this repo.
Previously the repo had arch/kvs.wit and src/lib.rs sitting next
to each other documentarily; now they're wired through
rules_wasm_component and the produced .wasm component is link-time
gated against the WIT contract.

Why

Answers the 'but then you need the full stack with bazel as well or?'
question with yes. Without the build wiring, the example only
demonstrates the modelling layer (artifacts + AADL + WIT); with it,
the operational difference from eclipse-score's interface-as-doc
approach is concretely real — change a method signature in
src/lib.rs and the Rust compile rejects it.

How

  • MODULE.bazel pins rules_wasm_component to a main-branch SHA
    (the v1.0.0 tag predates Bazel-9's CcInfo migration fix).
  • BUILD.bazel uses wit_library + rust_wasm_component_bindgen +
    rust_wasm_component_test.
  • Cargo.toml + Cargo.lock wire bitflags + wit-bindgen 0.46 via
    crate_universe — the bindgen rule hardcodes @crates//:bitflags
    and @crates//:wit-bindgen; the rule's docstring explicitly
    tells external consumers to wire this themselves.
  • src/lib.rs is a ~80-LoC in-memory KVS implementing the WIT
    Guest trait.
  • CI adds a bazel-wasm-component job that installs bazelisk and
    runs bazel build //... && bazel test //....

Test plan

  • bazel build //... locally → Build completed successfully
  • bazel test //:kvs_component_test locally → PASSED
  • rivet validate still passes (1 warning, intentional)
  • make verify still passes (4/4 artifacts have tests)
  • CI builds the .wasm on this PR — verify before merge

avrabe and others added 6 commits May 24, 2026 20:59
…ilds

Real end-to-end Bazel build using rules_wasm_component pinned to a
specific main-branch SHA (the v1.0.0 tag predates the Bazel-9
CcInfo migration). The chain:

  arch/kvs.wit
    │
    ▼  wit_library + rust_wasm_component_bindgen
    │   (in BUILD.bazel)
    │
  src/lib.rs implements the wit-bindgen-generated `Guest` trait
    │
    ▼  rust + WASI sysroot toolchain (pulled by rules_wasm_component)
    │
  bazel-bin/.../kvs_component_*_wasm_base.wasm   ← real .wasm

Local + CI:
  bazel build //...                   ✓ Build completed successfully
  bazel test  //:kvs_component_test   ✓ PASSED

If src/lib.rs's `impl Guest for Component` doesn't match the WIT
signatures (renamed method, wrong arity, drifted error variant),
the Rust compile fails. The binary contract is enforced at build
time — the operational difference from eclipse-score's
interface-as-documentation model is now demonstrably real, not
narrative.

## Files

- MODULE.bazel — rules_wasm_component via git_override (SHA-pinned
  to main; v1.0.0 tag has a Bazel-9 incompat issue); rules_rust,
  bazel_skylib, platforms; crate_universe (`@crates`) wired to
  Cargo.toml for the bitflags + wit-bindgen runtime deps the
  bindgen rule needs (known wart in rules_wasm_component — its
  docstring explicitly tells external consumers to wire this).
- BUILD.bazel — wit_library + rust_wasm_component_bindgen +
  rust_wasm_component_test.
- Cargo.toml + Cargo.lock — bitflags + wit-bindgen 0.46 (matched
  to the CLI version rules_wasm_component embeds).
- src/lib.rs — in-memory KVS impl. ~80 LOC; implements the WIT
  `Guest` trait with store / load / delete / snapshot_create /
  snapshot_restore. Key validation gates per
  COMP-REQ-KVS-KEY-NAMING.
- .github/workflows/validate.yml — bazel-wasm-component job
  installs bazelisk and runs `bazel build //...` + `bazel test`
  on every push/PR.
- .gitignore — bazel-* outputs, MODULE.bazel.lock, target/

README updated to reflect that the chain actually builds (status
table at the top now uses ✅ / 📄 instead of "real infrastructure
/ skeleton" prose).

This closes the user's "but then you need the full stack with
bazel as well or?" question: yes, and it does now.
…l tests

Replaces the toy 80-line in-memory KVS with the actual eclipse-score
rust_kvs sources (Apache-2.0, vendored verbatim at vendor/rust_kvs/)
plus a tiny no-op shim for score_log/baselibs_rust at
vendor/score_log_shim/. The vendored crate compiles natively under
bazel rust_library and runs all 244 of its upstream unit tests via
bazel rust_test.

tools/verify.py is no longer a stub. It reads each comp-req artifact's
new `verified-by:` list (`<bazel-target>:<test-fn-name>` entries),
discovers tests via the bazel target binary's --list, then invokes
`bazel test --test_arg=--exact --test_arg=<name>` per entry. PASSED /
FAILED / MISSING reflects real bazel-test outcomes.

Adds tests/surface/surface_tests.rs — one #[test] per comp-req that
asserts the upstream's verbatim RST text against the vendored impl.
Honest finding (clean-room-verified across both Rust and C++ sources):
comp_req__kvs__key_naming (alphabet [A-Za-z0-9_-]) and
comp_req__kvs__key_length (32-byte cap) are documented but unenforced
and untested. The gate goes red on those two comp-reqs; that's the
LS-N demonstration vs. coverage-wedge reporting.

CI collapses into a single job that installs bazelisk, runs rivet
validate, bazel build, and make verify.

Final gate output: 4 PASSED, 2 FAILED, 0 MISSING.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verify.py no longer takes the --tests directory argument — test
discovery now comes from each comp-req's verified-by: list of bazel
targets, so there is no "tests directory" to walk. The Makefile still
passed the obsolete flag, causing CI to fail with "unrecognized
arguments: --tests verification" before the gate could even run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ustc profile

Reviewer fixes
--------------
- INLINE-STORAGE de-greenwashed: surface test removed, verified-by emptied;
  verify gate now reports MISSING (not falsely PASSED) for the no-runtime-
  allocation requirement that needs witness instrumentation we don't wire.
- DR-KVS-BACKEND-CHOICE rewritten: matches the actual vendored impl
  (json_backend.rs with Adler32 + fs::rename snapshot rotation), not the
  fabricated RocksDB ADR that contradicted the code.
- DR-KVS-INLINE-STORAGE-GAP added: records the three honest response
  options (witness wiring / heapless / spec downgrade) so the gap has
  an audit trail.
- 9 upstream miri_test targets restored via tools/miri.sh + `make miri`
  (cargo-miri wrapper — public rules_rust 0.70 has no miri_test rule;
  eclipse-score uses a private 0.68.2-score fork).
- verify.py: mutable-default-arg fixed (_LIST_CACHE module-level dict);
  bazel build errors no longer swallowed by capture_output=True.
- README: explicit that C++ has the same gap (kvs.cpp:24 carries an
  open `// TODO String Handling in set_value TBD`); :status: valid in
  sphinx-needs framing clarified as "approved-for-design" not
  "must-be-implemented"; 6-of-35 comp-req scope rationale recorded.

Variants model
--------------
- variants/feature-model.yaml + bindings.yaml declare two deployment
  contexts: `dev` (engineering iteration) and `prod` (release-mode
  safety builds).
- Each variant binds three axes by name: (a) KvsBuilder runtime dials
  (KvsDefaults/KvsLoad/snapshot_max_count), (b) bazel --config= profile
  in /.bazelrc, (c) audit scope (out-of-scope-for: list).
- /.bazelrc adds --config=dev (fastbuild) and --config=prod (compilation_
  mode=opt + lto=fat + codegen-units=1 + overflow-checks=on + strip=
  symbols + embed-bitcode=yes). panic=abort lives in a separate
  --config=prod_ship for the shipping binary (Rust's #[test] harness
  needs unwinding outside nightly's -Zpanic_abort_tests).
- Makefile threads VARIANT= through `make verify` and `make bazel`.
- README + variants/README explain the design and why a variant model
  is needed: upstream eclipse-score rust_kvs has NO cargo features in
  the core library, so variants ride on builder dials + bazel rustc-
  flag profiles + audit scope, not --features.

Gate output (variant=dev, default):  3 PASSED, 2 FAILED, 0 MISSING
Gate output (variant=prod):          3 PASSED, 2 FAILED, 1 MISSING

The 2 FAILED are the confirmed-real upstream falsifications of
comp_req__kvs__key_naming and comp_req__kvs__key_length; the MISSING
under prod is INLINE-STORAGE (out-of-scope for dev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tub)

src/lib.rs rewritten to construct a real rust_kvs::Kvs via KvsBuilder
(InMemoryBackend so we don't need WASI filesystem permissions) and
implement the WIT Guest trait by delegating store/load/delete to
KvsApi. The .wasm component therefore contains the actual eclipse-
score Rust code — the same code the 244 native tests exercise — not
a parallel toy implementation that could drift from upstream silently.

BUILD.bazel adds //vendor/rust_kvs:rust_kvs as a dep on the
rust_wasm_component_bindgen target. cross-compile to wasm32-wasip2
just works; no upstream source change needed.

Real numbers:
  fastbuild         2.9 MB
  --config=prod_ship 225 KB   (13x smaller from lto=fat + codegen-units=1
                               + panic=abort + strip=symbols + opt-level=3)

Smoke test (rust_wasm_component_test) passes on both — the produced
component is well-formed.

Adapter notes (in src/lib.rs head comment):
  - rust_kvs's KvsValue has no Bytes variant, so WIT's Vec<u8> is
    stored as KvsValue::Array(Vec<U32>) — lossless, not space-
    efficient (~4x overhead). A real deployment would extend
    KvsValue upstream or pick a different encoding.
  - JsonBackend uses std::fs::rename, which needs WASI filesystem
    permissions; the example ships an InMemoryBackend (KvsBackend
    trait impl) at the bottom of src/lib.rs to keep the component
    self-contained.
  - snapshot_create returns a static 0; snapshot_restore errors.
    Snapshot semantics require a backend that persists across calls,
    which is out of scope for the in-memory demo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant