Skip to content

feat(wasm): app-agnostic WASM ABI v1 with KernelPort trait#296

Merged
flyingrobots merged 12 commits intomainfrom
chore/clean-repo
Mar 8, 2026
Merged

feat(wasm): app-agnostic WASM ABI v1 with KernelPort trait#296
flyingrobots merged 12 commits intomainfrom
chore/clean-repo

Conversation

@flyingrobots
Copy link
Owner

@flyingrobots flyingrobots commented Mar 8, 2026

Summary

  • KernelPort trait (echo-wasm-abi) — app-agnostic byte-level boundary contract for WASM host adapters, with ABI response DTOs, error codes, and CBOR wire envelope types
  • WarpKernel (warp-wasm, engine feature) — wraps warp-core::Engine implementing KernelPort, with deterministic tick execution and sys/ack_pending system rule
  • Real WASM exports replace all placeholders: init, dispatch_intent, step, drain_view_ops, get_head, snapshot_at, get_registry_info, and handshake metadata getters now return live CBOR-encoded data
  • CBOR success/error envelope protocol ({ ok: true/false, ... }) for all Uint8Array returns
  • SPEC-0009 documenting ABI v1 contract, wire encoding, error codes, versioning strategy, and migration notes
  • 14 conformance tests covering dispatch, step, drain, snapshot, determinism, error paths, and handshake metadata
  • Clippy cleanup — cfg-gate footprint enforcement internals to eliminate dead-code warnings under unsafe_graph, restructure TtdController WASM bindings

Test plan

  • cargo clippy --workspace --all-targets -- -D warnings — zero warnings
  • cargo test --workspace — all tests pass
  • cargo doc --no-deps -p warp-wasm — no broken intra-doc links
  • Pre-push hook (fmt, clippy, tests, rustdoc, patterns, determinism) — all passed

Summary by CodeRabbit

  • New Features

    • App-agnostic WASM kernel boundary with real exports, CBOR success/error envelopes, and kernel injection/init support.
  • Bug Fixes

    • Improved envelope validation, error mapping, and WASM export behaviors; resolved workspace lints and clippy warnings.
  • Documentation

    • Added WASM ABI v1 specification and determinism allowlist guidance.
  • Tests

    • Added extensive conformance and unit tests for the WASM kernel boundary and enforcement logic.

Replace all placeholder WASM exports in warp-wasm with real
implementations backed by the KernelPort trait and WarpKernel engine
wrapper. Adds ABI v1 contract (SPEC-0009), CBOR wire envelope protocol,
structured error codes, and 14 conformance tests.

- KernelPort trait in echo-wasm-abi (app-agnostic boundary contract)
- WarpKernel in warp-wasm (engine feature, wraps warp-core::Engine)
- init() export + install_kernel() injection API
- dispatch_intent, step, drain_view_ops, get_head, snapshot_at wired
- execute_query/render_snapshot return NOT_SUPPORTED honestly
… consistency, drain semantics

BLOCKING fixes:
- snapshot_at now saves/restores engine state around jump_to_tick to
  prevent corrupting the live state for subsequent operations
- execute_query, snapshot_at, render_snapshot now wrap success results
  in OkEnvelope via RawBytesResponse, matching the documented wire
  protocol (all Uint8Array returns use { ok: true/false } discriminator)

SHOULD FIX:
- drain_view_ops now tracks drained state; second call without
  intervening step returns empty (matches documented contract)
- Move serde-value from dependencies to dev-dependencies (test-only)

New tests:
- snapshot_at_does_not_corrupt_live_state (regression for state restore)
- drain_returns_empty_on_second_call (drain semantics)

Spec updated: SPEC-0009 documents RawBytesResponse wrapper for raw-byte
endpoints.
…add trait defaults

- Validate EINT envelope in dispatch_intent before engine ingestion;
  return INVALID_INTENT (code 2) for malformed envelopes.
- Add Display impl for EnvelopeError (no_std compatible).
- Make OkEnvelope/ErrEnvelope ok field private with ::new() constructors.
- Add default impls for execute_query and render_snapshot (NOT_SUPPORTED).
- Remove redundant overrides from WarpKernel.
- Add dispatch_invalid_intent_returns_invalid_intent_error test.
- Update SPEC-0009 error code 2 description and versioning notes.
…rrors

- Clarify OkEnvelope::new docs: T may be a reference for serialization.
- Include input byte length in EINT validation error messages for
  production debugging.
- cfg-gate footprint enforcement internals so they compile out under
  unsafe_graph without dead-code warnings.
- Add #[allow(unused_mut)] on cfg-conditional mutation in engine_impl.
- Restructure TtdController WASM bindings with per-method wasm_bindgen
  and JsValue/JsError wrappers.
- Add clippy allow attributes to echo-scene-codec test module.
- Move enforcement-only imports into cfg-gated mod in parallel_footprints.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Adds a WASM ABI v1 boundary (KernelPort, CBOR envelopes, error codes) and a WarpKernel implementation exposing CBOR-encoded wasm-bindgen exports. Gates footprint-enforcement code behind cfg flags, splits host vs wasm Ttd bindings, and adds a global-state allowlist entry for crates/warp-wasm/src/lib.rs.

Changes

Cohort / File(s) Summary
Kernel Boundary Definition
crates/echo-wasm-abi/src/kernel_port.rs, crates/echo-wasm-abi/src/lib.rs
Add KernelPort trait, ABI_VERSION, error_codes, AbiError, DTOs (DispatchResponse, HeadInfo, StepResponse, DrainResponse, ChannelData, RegistryInfo), CBOR envelope types (OkEnvelope, ErrEnvelope, RawBytesResponse) and Display for EnvelopeError.
WASM Engine Boundary
crates/warp-wasm/src/lib.rs, crates/warp-wasm/src/warp_kernel.rs, crates/warp-wasm/Cargo.toml
Implement install_kernel and thread-local kernel storage; add CBOR-encoded wasm exports (init, dispatch_intent, step, drain_view_ops, get_head, execute_query, snapshot_at, render_snapshot, registry accessors); add WarpKernel wrapping warp-core::Engine; add engine feature and rlib crate-type.
TTD WASM Bindings
crates/echo-wasm-bindings/src/ttd.rs
Separate host-only TtdController impl from wasm bindings; expose wasm-specific shims (new_wasm, open_session_wasm, close_session_wasm, set_privacy_mask_wasm, redact_value_wasm) with JsValue/JsError conversions and validation.
Footprint Enforcement Gating
crates/warp-core/src/...
crates/warp-core/src/attachment.rs, crates/warp-core/src/engine_impl.rs, crates/warp-core/src/footprint_guard.rs, crates/warp-core/src/parallel/exec.rs, crates/warp-core/src/tick_delta.rs, crates/warp-core/tests/parallel_footprints.rs
Gate enforcement types/functions (OpTargets, FootprintGuard, op_kind_str, op_write_targets, RewritesByWarp, PoisonedDelta::new, ops_ref, test helpers) behind cfg(any(debug_assertions, feature="footprint_enforce_release")) and cfg(not(feature="unsafe_graph")); localize enforcement imports; add lint allowances.
Global State Allowlist & Docs
.ban-globals-allowlist, docs/spec/SPEC-0009-wasm-abi-v1.md
Add allowlist entry exempting crates/warp-wasm/src/lib.rs from global-state ban; add SPEC-0009 documenting WASM ABI v1, CBOR envelope protocol, exports and error codes.
Build, Tests & Tooling
CHANGELOG.md, crates/echo-scene-codec/src/mock_adapter.rs, scripts/ban-globals.sh
Update changelog; add clippy allows to test modules; fix regex escaping for thread_local!/lazy_static! in ban-globals script; adjust dev-deps and crate-type for wasm crate.

Sequence Diagram(s)

sequenceDiagram
    participant WASM_Host as WASM Host
    participant WASM_Rt as WASM Runtime
    participant WarpK as WarpKernel
    participant Engine as Engine
    participant Envelope as Envelope (CBOR)

    WASM_Host->>WASM_Rt: install_kernel(kernel)
    WASM_Rt->>WASM_Rt: store kernel in thread_local KERNEL

    WASM_Host->>WASM_Rt: init()
    WASM_Rt->>WarpK: create or retrieve kernel
    WarpK->>Engine: new() or with_engine()
    Engine-->>WarpK: Engine instance
    WarpK-->>WASM_Rt: HeadInfo
    WASM_Rt->>Envelope: encode_ok(HeadInfo)
    Envelope-->>WASM_Host: Uint8Array(CBOR)

    WASM_Host->>WASM_Rt: dispatch_intent(intent_bytes)
    WASM_Rt->>WarpK: dispatch_intent(intent_bytes)
    WarpK->>Engine: ingest/validate intent
    Engine-->>WarpK: DispatchResponse
    WASM_Rt->>Envelope: encode_ok(DispatchResponse)
    Envelope-->>WASM_Host: Uint8Array(CBOR)

    WASM_Host->>WASM_Rt: step(budget)
    WASM_Rt->>WarpK: step(budget)
    WarpK->>Engine: begin_tick/process/commit
    Engine-->>WarpK: StepResponse (ticks_executed, head)
    WASM_Rt->>Envelope: encode_ok(StepResponse)
    Envelope-->>WASM_Host: Uint8Array(CBOR)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

tooling

Poem

🧪 A kernel docks at WASM's shore, CBOR seals the call,
Thread-local keys unlock the gate, exports answer all.
Footprints sleep behind a flag, tests march in line,
Determinism boxed and signed — the engine hums, refined. ⚙️

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: introducing an app-agnostic WASM ABI v1 with a KernelPort trait. It is specific, concise, and reflects the primary architectural addition across the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/clean-repo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9384eac54b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +174 to +177
encode_ok(&kernel_port::HeadInfo {
tick: 0,
state_root: Vec::new(),
commit_id: Vec::new(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return real head hashes from init()

init() returns a HeadInfo envelope but hard-codes state_root and commit_id to empty vectors here, which violates the HeadInfo contract (32-byte hashes) and gives callers invalid head metadata immediately after initialization. Any host that uses the init() response for initial sync/cache keys will observe incorrect values unless it makes an extra get_head() round-trip.

Useful? React with 👍 / 👎.

Comment on lines +77 to +81
pub fn with_engine(engine: Engine, registry: RegistryInfo) -> Self {
Self {
engine,
tick_count: 0,
drained: true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Register ack_pending when constructing with with_engine()

WarpKernel::new() registers sys/ack_pending because dispatch_next_intent always applies that rule when a pending intent exists, but with_engine() stores the provided engine as-is. For consumers using this advertised injection path, forgetting to pre-register that system rule causes step() to fail with ENGINE_ERROR after the first dispatched intent, breaking intent processing in custom kernels.

Useful? React with 👍 / 👎.

- Remove redundant explicit type annotation on `get_registry_info()`
  result and drop now-unused `RegistryInfo` import
- Remove unnecessary `#[allow(dead_code)]` on pub `with_engine()` method
- Add clarifying comment on abort-after-failed-commit safety
@flyingrobots flyingrobots changed the title feat(wasm): ship reusable app-kernel WASM boundary (ECO-001) feat(wasm): app-agnostic WASM ABI v1 with KernelPort trait Mar 8, 2026
- Return real HeadInfo (32-byte hashes) from init() instead of empty
  vecs, so hosts don't need an extra get_head() round-trip
- Auto-register sys/ack_pending in WarpKernel::with_engine(), silently
  ignoring duplicates if the caller already registered it
- Add .ban-globals-allowlist to exempt warp-wasm boundary (WASM exports
  require module-scoped thread_local kernel storage)
- Add tests for with_engine auto-registration and duplicate tolerance
@flyingrobots
Copy link
Owner Author

Fixed in 4c52d00:

init() returns real head hashesinit() now calls get_head() on the freshly constructed kernel before boxing it, returning actual 32-byte state_root and commit_id hashes instead of empty vecs.

with_engine() auto-registers ack_pendingWarpKernel::with_engine() now calls engine.register_rule(inbox::ack_pending_rule()) and silently ignores DuplicateRuleName if the caller already registered it. Two new tests verify both paths (auto-registration and duplicate tolerance).

Also added .ban-globals-allowlist to exempt warp-wasm/src/lib.rs from the install_* ban-globals CI check (WASM boundary legitimately needs module-scoped kernel storage).

… test

- Fix \bthread_local!\b and \blazy_static!\b patterns in ban-globals.sh:
  the unescaped ! was not matching in ripgrep regex, and the trailing \b
  after ! (a non-word char) could never match a word boundary. Use
  escaped \! without trailing \b.
- Add fresh_kernel_head_has_real_hashes regression test verifying that a
  fresh WarpKernel returns non-empty, non-zero 32-byte hashes (guards
  against init() returning placeholder empty vecs).
- Replace unwrap_or_else fallback with expect on fresh kernel get_head()
  — the operation is infallible and a silent fallback would mask bugs
- Add CHANGELOG entries for follow-up fixes: ban-globals regex,
  init() real hashes, with_engine auto-registration, rustdoc link
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/warp-core/src/footprint_guard.rs (1)

220-234: ⚠️ Potential issue | 🟠 Major

OpenPortal is under-modeled by the enforcement target extractor.

WarpOp::OpenPortal also upserts the child warp instance and child root node, but op_write_targets() only reports the parent attachment and parent warp. That leaves a blind spot for undeclared child-root writes and misstates the cross-warp semantics of portal creation. OpTargets needs to represent the child instance/root effects explicitly.

Also applies to: 290-297

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-core/src/footprint_guard.rs` around lines 220 - 234, The
extractor op_write_targets omits the child-warp and child-root writes for
WarpOp::OpenPortal, so update the OpenPortal arm in op_write_targets to include
the child instance and child root node in the returned OpTargets (in addition to
the existing parent attachment/parent warp entries); specifically, when matching
WarpOp::OpenPortal add write targets for the child warp instance and the child
root node (the concrete OpTargets fields your crate uses to represent instance
writes and node writes) so OpenPortal’s upsert semantics are fully
represented—apply the same change for the analogous case around the other
OpenPortal handling at the mentioned region (around lines 290–297).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/echo-scene-codec/src/mock_adapter.rs`:
- Line 176: Replace the module-level #[allow(clippy::unwrap_used,
clippy::expect_used, clippy::float_cmp)] with targeted fixes: remove the blanket
attribute and add per-line or per-item allows only where needed (e.g., the
specific test cases that call unwrap/expect), change the float_cmp usage on the
adapter.viewport check to an epsilon-based comparison (use f32::EPSILON or
approx crate) instead of silencing the lint, and replace .expect(...) usages in
tests (e.g., calls around apply_scene_delta(&delta)) with explicit assertions
like assert!(apply_scene_delta(...).is_ok()) so test helpers/mocks don’t mask
issues outside tests.

In `@crates/echo-wasm-abi/src/kernel_port.rs`:
- Around line 89-97: The HeadInfo.commit_id currently lacks precise semantics
and is being populated from Engine::snapshot().hash via WarpKernel; change this
by documenting the invariant and updating the population source: add a rustdoc
on struct HeadInfo and the commit_id field that clearly states it is the
canonical last-committed ledger commit hash (stable identifier for the last
committed snapshot), not the transient synthetic state hash, and update
WarpKernel/other callers to set HeadInfo.commit_id from the last committed
ledger snapshot commit ID (e.g., the engine/ledger API that returns the
committed snapshot hash) instead of Engine::snapshot().hash so public APIs carry
a single consistent meaning.

In `@crates/warp-core/src/engine_impl.rs`:
- Around line 1269-1272: Remove the local #[allow(unused_mut)] and instead
create two cfg-separated bindings for units so mutability is only present when
the footprint-enforcement cfg is active: use one #[cfg(...)] branch that
declares a mutable let mut units = build_work_units(items_by_warp) for the same
cfg used by the footprint enforcement code, and a separate #[cfg(not(...))]
branch that declares an immutable let units = build_work_units(items_by_warp);
this eliminates the unused_mut lint suppression while preserving behavior of
build_work_units and keeping the crate warning-clean.

In `@crates/warp-wasm/src/lib.rs`:
- Around line 169-180: init currently constructs a WarpKernel and fabricates a
default HeadInfo on get_head() failure, then installs the kernel and returns
encode_ok(&head); change this so that after creating the kernel with
warp_kernel::WarpKernel::new() you call kernel.get_head() and if it Errs you
return the encoded AbiError (do not call install_kernel) instead of building a
placeholder HeadInfo; only call install_kernel(Box::new(kernel)) and return
encode_ok(&head) when get_head() succeeds. Ensure you use the project’s existing
error-encoding helper (the counterpart to encode_ok) to return the AbiError.
- Around line 112-118: The fallback in encode_err_raw currently returns an empty
Uint8Array which violates the v1 CBOR envelope contract; change the Err match
branch to return a pre-encoded minimal CODEC_ERROR envelope instead. Create or
reuse a constant (e.g., PREENCODED_CODEC_ERROR_BYTES or CODEC_ERROR_ENVELOPE)
that holds the CBOR-encoded bytes for a minimal ErrEnvelope with a codec error
code, and in the Err(_) branch call
bytes_to_uint8array(&PREENCODED_CODEC_ERROR_BYTES) (or its Uint8Array
equivalent) rather than Uint8Array::new_with_length(0); keep the normal
Ok(bytes) => bytes_to_uint8array(&bytes) behavior and reference encode_err_raw,
ErrEnvelope, and echo_wasm_abi::encode_cbor when making the change.

In `@crates/warp-wasm/src/warp_kernel.rs`:
- Around line 213-246: snapshot_at currently calls
self.engine.jump_to_tick(tick) which only rewinds state but not the snapshot
context (last_snapshot), so engine.snapshot() produces a hash against the live
head; fix by obtaining the historical snapshot context instead of hashing the
live head: either read the stored ledger snapshot for that tick directly (e.g.
via the engine/ledger API that returns the saved snapshot/HeadInfo for a given
tick) and use that data for HeadInfo and encoding, or fully rewind the engine's
snapshot context (including last_snapshot/parent pointers) to the requested tick
before calling self.engine.snapshot(), then restore the full snapshot context
along with state after snapshot_at returns; update snapshot_at to use engine's
snapshot-at-tick API or to rewind/restore last_snapshot in addition to state so
the computed state_root/commit_id are derived from the historical context, not
the live head.

In `@docs/spec/SPEC-0009-wasm-abi-v1.md`:
- Around line 106-117: DrainResponse currently only exposes `channels` (or
absence thereof) which hides materialization boundary/finalization errors;
update the ABI docs for `DrainResponse` to add an explicit field (e.g.,
`boundary_errors` or `materialization_errors`) that surfaces any per-channel or
global finalization errors (type: array of error records or bytes plus an
error_code/message) so hosts can distinguish an empty successful drain from a
drain with silent output loss; update the `ChannelData` section if needed to
reference per-channel error linking, and apply the same addition to the other
affected block (lines around the duplicate section noted at 138-148) so both
places document the new error field consistently.

---

Outside diff comments:
In `@crates/warp-core/src/footprint_guard.rs`:
- Around line 220-234: The extractor op_write_targets omits the child-warp and
child-root writes for WarpOp::OpenPortal, so update the OpenPortal arm in
op_write_targets to include the child instance and child root node in the
returned OpTargets (in addition to the existing parent attachment/parent warp
entries); specifically, when matching WarpOp::OpenPortal add write targets for
the child warp instance and the child root node (the concrete OpTargets fields
your crate uses to represent instance writes and node writes) so OpenPortal’s
upsert semantics are fully represented—apply the same change for the analogous
case around the other OpenPortal handling at the mentioned region (around lines
290–297).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3cbdca53-167c-40cd-818d-6ba1f256bc17

📥 Commits

Reviewing files that changed from the base of the PR and between d0e8db6 and b632d77.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .ban-globals-allowlist
  • CHANGELOG.md
  • crates/echo-scene-codec/src/mock_adapter.rs
  • crates/echo-wasm-abi/src/kernel_port.rs
  • crates/echo-wasm-abi/src/lib.rs
  • crates/echo-wasm-bindings/src/ttd.rs
  • crates/warp-core/src/attachment.rs
  • crates/warp-core/src/engine_impl.rs
  • crates/warp-core/src/footprint_guard.rs
  • crates/warp-core/src/parallel/exec.rs
  • crates/warp-core/src/tick_delta.rs
  • crates/warp-core/tests/parallel_footprints.rs
  • crates/warp-wasm/Cargo.toml
  • crates/warp-wasm/src/lib.rs
  • crates/warp-wasm/src/warp_kernel.rs
  • docs/spec/SPEC-0009-wasm-abi-v1.md
  • scripts/ban-globals.sh

}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::float_cmp)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Blanket test allows are a lazy pattern.

The module-level #[allow(...)] silences lints across 360 lines of test code. While unwrap/expect are idiomatic in tests, blanket allows prevent detection if test helpers or mock utilities later leak into production paths.

Consider alternatives:

  1. Per-line allows: Apply #[allow(clippy::unwrap_used)] only to lines that need it (e.g., line 236).
  2. Better float assertions: Line 513 is the sole float_cmp trigger. Use epsilon-based comparison or the approx crate instead of silencing the lint:
    assert!((adapter.viewport.2 - 2.0).abs() < f32::EPSILON);
  3. Explicit test assertions: Replace .expect("...") with patterns like:
    assert!(adapter.apply_scene_delta(&delta).is_ok());

For test code, unwrap/expect pragmatism is acceptable, but the blanket allow is a maintainability smell.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/echo-scene-codec/src/mock_adapter.rs` at line 176, Replace the
module-level #[allow(clippy::unwrap_used, clippy::expect_used,
clippy::float_cmp)] with targeted fixes: remove the blanket attribute and add
per-line or per-item allows only where needed (e.g., the specific test cases
that call unwrap/expect), change the float_cmp usage on the adapter.viewport
check to an epsilon-based comparison (use f32::EPSILON or approx crate) instead
of silencing the lint, and replace .expect(...) usages in tests (e.g., calls
around apply_scene_delta(&delta)) with explicit assertions like
assert!(apply_scene_delta(...).is_ok()) so test helpers/mocks don’t mask issues
outside tests.

Comment on lines +89 to +97
/// Current head state of the kernel.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeadInfo {
/// Current tick count (number of committed ticks).
pub tick: u64,
/// Graph-only state hash (32 bytes).
pub state_root: Vec<u8>,
/// Canonical commit hash (32 bytes).
pub commit_id: Vec<u8>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Define the actual hash semantics of HeadInfo.commit_id.

The docs say this is the canonical commit hash, but WarpKernel currently populates it from Engine::snapshot().hash, which is a synthetic hash of the current state and can differ from the last committed ledger snapshot. Public callers need one stable meaning here, not both. Based on learnings: Every public API across crates (warp-core, warp-wasm, etc.) must carry rustdoc comments that explain intent, invariants, and usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/echo-wasm-abi/src/kernel_port.rs` around lines 89 - 97, The
HeadInfo.commit_id currently lacks precise semantics and is being populated from
Engine::snapshot().hash via WarpKernel; change this by documenting the invariant
and updating the population source: add a rustdoc on struct HeadInfo and the
commit_id field that clearly states it is the canonical last-committed ledger
commit hash (stable identifier for the last committed snapshot), not the
transient synthetic state hash, and update WarpKernel/other callers to set
HeadInfo.commit_id from the last committed ledger snapshot commit ID (e.g., the
engine/ledger API that returns the committed snapshot hash) instead of
Engine::snapshot().hash so public APIs carry a single consistent meaning.

Comment on lines 1269 to 1272
// Build (warp, shard) work units - canonical ordering preserved
// mut required when footprint enforcement is active (cfg-gated below).
#[allow(unused_mut)]
let mut units = build_work_units(items_by_warp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove the lint suppression and split the binding by cfg.

#[allow(unused_mut)] is only papering over cfg-dependent mutability here. In warp-core this is avoidable by defining the binding separately per cfg, which keeps the crate warning-clean without adding a local allow.

Proposed change
-        #[allow(unused_mut)]
-        let mut units = build_work_units(items_by_warp);
+        #[cfg(all(
+            any(debug_assertions, feature = "footprint_enforce_release"),
+            not(feature = "unsafe_graph")
+        ))]
+        let mut units = build_work_units(items_by_warp);
+        #[cfg(not(all(
+            any(debug_assertions, feature = "footprint_enforce_release"),
+            not(feature = "unsafe_graph")
+        )))]
+        let units = build_work_units(items_by_warp);

Based on learnings: All determinism-critical crates (warp-core, echo-wasm-abi, echo-scene-port) must be compiled with RUSTFLAGS="-Dwarnings" to forbid any compiler warnings, including unused imports, dead code, or silenced lints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-core/src/engine_impl.rs` around lines 1269 - 1272, Remove the
local #[allow(unused_mut)] and instead create two cfg-separated bindings for
units so mutability is only present when the footprint-enforcement cfg is
active: use one #[cfg(...)] branch that declares a mutable let mut units =
build_work_units(items_by_warp) for the same cfg used by the footprint
enforcement code, and a separate #[cfg(not(...))] branch that declares an
immutable let units = build_work_units(items_by_warp); this eliminates the
unused_mut lint suppression while preserving behavior of build_work_units and
keeping the crate warning-clean.

Comment on lines +112 to +118
/// Low-level error encoding that cannot itself fail (falls back to empty array).
fn encode_err_raw(code: u32, message: &str) -> Uint8Array {
let envelope = ErrEnvelope::new(code, message.into());
match echo_wasm_abi::encode_cbor(&envelope) {
Ok(bytes) => bytes_to_uint8array(&bytes),
Err(_) => Uint8Array::new_with_length(0),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Never return a zero-length "error envelope".

Falling back to Uint8Array(0) breaks the v1 contract that every byte-returning export is a CBOR { ok: ... } envelope. If error encoding fails, return a pre-encoded minimal CODEC_ERROR envelope instead of undecodable empty bytes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 112 - 118, The fallback in
encode_err_raw currently returns an empty Uint8Array which violates the v1 CBOR
envelope contract; change the Err match branch to return a pre-encoded minimal
CODEC_ERROR envelope instead. Create or reuse a constant (e.g.,
PREENCODED_CODEC_ERROR_BYTES or CODEC_ERROR_ENVELOPE) that holds the
CBOR-encoded bytes for a minimal ErrEnvelope with a codec error code, and in the
Err(_) branch call bytes_to_uint8array(&PREENCODED_CODEC_ERROR_BYTES) (or its
Uint8Array equivalent) rather than Uint8Array::new_with_length(0); keep the
normal Ok(bytes) => bytes_to_uint8array(&bytes) behavior and reference
encode_err_raw, ErrEnvelope, and echo_wasm_abi::encode_cbor when making the
change.

Comment on lines +169 to +180
pub fn init() -> Uint8Array {
#[cfg(feature = "engine")]
{
let kernel = warp_kernel::WarpKernel::new();
let head = kernel.get_head().unwrap_or_else(|_| kernel_port::HeadInfo {
tick: 0,
state_root: Vec::new(),
commit_id: Vec::new(),
});
install_kernel(Box::new(kernel));
encode_ok(&head)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not fabricate a successful head on initialization failure.

If get_head() fails, init() currently emits { tick: 0, state_root: [], commit_id: [] } and still installs the kernel. That recreates the placeholder-success behavior this PR is supposed to remove. Return the encoded AbiError and skip installation on failure.

Proposed change
     #[cfg(feature = "engine")]
     {
         let kernel = warp_kernel::WarpKernel::new();
-        let head = kernel.get_head().unwrap_or_else(|_| kernel_port::HeadInfo {
-            tick: 0,
-            state_root: Vec::new(),
-            commit_id: Vec::new(),
-        });
+        let head = match kernel.get_head() {
+            Ok(head) => head,
+            Err(err) => return encode_err(&err),
+        };
         install_kernel(Box::new(kernel));
         encode_ok(&head)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn init() -> Uint8Array {
#[cfg(feature = "engine")]
{
let kernel = warp_kernel::WarpKernel::new();
let head = kernel.get_head().unwrap_or_else(|_| kernel_port::HeadInfo {
tick: 0,
state_root: Vec::new(),
commit_id: Vec::new(),
});
install_kernel(Box::new(kernel));
encode_ok(&head)
}
pub fn init() -> Uint8Array {
#[cfg(feature = "engine")]
{
let kernel = warp_kernel::WarpKernel::new();
let head = match kernel.get_head() {
Ok(head) => head,
Err(err) => return encode_err(&err),
};
install_kernel(Box::new(kernel));
encode_ok(&head)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 169 - 180, init currently
constructs a WarpKernel and fabricates a default HeadInfo on get_head() failure,
then installs the kernel and returns encode_ok(&head); change this so that after
creating the kernel with warp_kernel::WarpKernel::new() you call
kernel.get_head() and if it Errs you return the encoded AbiError (do not call
install_kernel) instead of building a placeholder HeadInfo; only call
install_kernel(Box::new(kernel)) and return encode_ok(&head) when get_head()
succeeds. Ensure you use the project’s existing error-encoding helper (the
counterpart to encode_ok) to return the AbiError.

Comment on lines +78 to +88
pub fn with_engine(mut engine: Engine, registry: RegistryInfo) -> Self {
// Ensure the system inbox rule is present. If the caller already
// registered it, register_rule returns DuplicateRuleName — ignore.
let _ = engine.register_rule(inbox::ack_pending_rule());

Self {
engine,
tick_count: 0,
drained: true,
registry,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

with_engine() is assuming a fresh, already-correct engine.

It hard-codes tick_count = 0 and drained = true, and it also discards the result of register_rule(). Wrapping a pre-stepped engine can misreport the first head/drain, while a real registration failure is silently deferred to a later step() as a generic ENGINE_ERROR. Initialize the mirrored state from the injected Engine, and only swallow the specific duplicate-name case for sys/ack_pending.

Comment on lines +161 to +166
match self.engine.commit(tx) {
Ok(_snapshot) => {
self.tick_count += 1;
ticks_executed += 1;
self.drained = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Materialization failures are being dropped at the WASM boundary.

Engine::commit() can succeed with last_materialization_errors() populated. step() still returns success, and drain_view_ops() exposes only successful channels, so strict-channel conflicts turn into silent output loss over the ABI. These boundary errors need to be surfaced explicitly before this contract freezes.

Also applies to: 187-206

Comment on lines +213 to +246
fn snapshot_at(&mut self, tick: u64) -> Result<Vec<u8>, AbiError> {
let tick_index = usize::try_from(tick).map_err(|_| AbiError {
code: error_codes::INVALID_TICK,
message: format!("tick {tick} exceeds addressable range"),
})?;

// Save current state — jump_to_tick overwrites engine state with a
// replayed state. We must restore afterward to keep the live engine
// consistent with tick_count and subsequent operations.
let saved_state = self.engine.state().clone();

self.engine.jump_to_tick(tick_index).map_err(|e| {
// Restore even on error (jump_to_tick may have partially mutated).
*self.engine.state_mut() = saved_state.clone();
AbiError {
code: error_codes::INVALID_TICK,
message: e.to_string(),
}
})?;

let snap = self.engine.snapshot();
let head = HeadInfo {
tick,
state_root: snap.state_root.to_vec(),
commit_id: snap.hash.to_vec(),
};

// Restore live state.
*self.engine.state_mut() = saved_state;

echo_wasm_abi::encode_cbor(&head).map_err(|e| AbiError {
code: error_codes::CODEC_ERROR,
message: e.to_string(),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Historical snapshot hashes are computed against live context.

jump_to_tick() only rewinds state; it does not rewind last_snapshot. The self.engine.snapshot() call here therefore hashes the requested historical state with the live head’s parent context. This also collapses replay corruption into INVALID_TICK. Either read the stored ledger snapshot directly, or rewind the full snapshot context before encoding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/warp_kernel.rs` around lines 213 - 246, snapshot_at
currently calls self.engine.jump_to_tick(tick) which only rewinds state but not
the snapshot context (last_snapshot), so engine.snapshot() produces a hash
against the live head; fix by obtaining the historical snapshot context instead
of hashing the live head: either read the stored ledger snapshot for that tick
directly (e.g. via the engine/ledger API that returns the saved
snapshot/HeadInfo for a given tick) and use that data for HeadInfo and encoding,
or fully rewind the engine's snapshot context (including last_snapshot/parent
pointers) to the requested tick before calling self.engine.snapshot(), then
restore the full snapshot context along with state after snapshot_at returns;
update snapshot_at to use engine's snapshot-at-tick API or to rewind/restore
last_snapshot in addition to state so the computed state_root/commit_id are
derived from the historical context, not the live head.

Comment on lines +106 to +117
### DrainResponse

| Field | Type | Description |
| ---------- | ----- | --------------------- |
| `channels` | array | List of `ChannelData` |

### ChannelData

| Field | Type | Description |
| ------------ | --------- | ---------------------------------- |
| `channel_id` | bytes(32) | Materialization channel identifier |
| `data` | bytes | Raw finalized channel output |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Expose materialization boundary errors in ABI v1.

DrainResponse only lets the host observe "channels" or "no channels". The engine can also commit a tick and still produce materialization finalization errors, which is semantically different from an empty drain. Without an explicit field/export for those boundary errors, hosts will read silent output loss as a normal empty response. As per coding guidelines: docs/**/*.md: Echo is a deterministic simulation engine. Documentation accuracy matters — especially anything touching determinism guarantees, hash stability, or canonical ordering.

Also applies to: 138-148

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/spec/SPEC-0009-wasm-abi-v1.md` around lines 106 - 117, DrainResponse
currently only exposes `channels` (or absence thereof) which hides
materialization boundary/finalization errors; update the ABI docs for
`DrainResponse` to add an explicit field (e.g., `boundary_errors` or
`materialization_errors`) that surfaces any per-channel or global finalization
errors (type: array of error records or bytes plus an error_code/message) so
hosts can distinguish an empty successful drain from a drain with silent output
loss; update the `ChannelData` section if needed to reference per-channel error
linking, and apply the same addition to the other affected block (lines around
the duplicate section noted at 138-148) so both places document the new error
field consistently.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
crates/warp-wasm/src/lib.rs (1)

112-118: ⚠️ Potential issue | 🟠 Major

Never return an empty Uint8Array from the ABI boundary.

If error encoding fails here, callers get undecodable bytes instead of the promised { ok: false, code, message } envelope. Use a pre-encoded minimal CODEC_ERROR envelope as the last-resort fallback so every byte-returning export stays self-describing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 112 - 118, The encode_err_raw
function currently returns an empty Uint8Array on encode failure which produces
undecodable bytes for callers; instead keep a pre-encoded minimal fallback (e.g.
a const CODEC_ERROR: &[u8] representing { ok: false, code: <codec error code>,
message: "codec error" }) and convert that to a Uint8Array in the Err arm;
update encode_err_raw to call ErrEnvelope::new and echo_wasm_abi::encode_cbor as
now, but on Err(_) return bytes_to_uint8array(CODEC_ERROR) so every export
always returns a self-describing envelope (reference: function encode_err_raw,
type ErrEnvelope, echo_wasm_abi::encode_cbor, and bytes_to_uint8array).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/warp-wasm/src/lib.rs`:
- Around line 62-88: with_kernel and with_kernel_ref currently hold a RefCell
borrow while calling user-provided KernelPort code which allows reentrant calls
to panic; fix by extracting the boxed kernel out of the TLS cell before invoking
f (use KERNEL.with(|cell| { let mut borrow = cell.borrow_mut(); let mut
kernel_box = borrow.take().ok_or_else(|| AbiError{...})?; let result =
f(kernel_box.as_mut()); *borrow = Some(kernel_box); result }) ), and likewise
for with_kernel_ref take the Box out, call f(kernel_box.as_ref()), then put it
back; if borrow.take() returns None because the kernel is already taken, return
an AbiError indicating reentrancy (use or add a
kernel_port::error_codes::REENTRANT constant) instead of letting a RefCell
panic.
- Around line 267-303: The three getters (get_codec_id, get_registry_version,
get_schema_sha256_hex) currently collapse kernel-not-initialized errors and
genuinely absent metadata into JsValue::NULL; change them so with_kernel_ref
error paths surface initialization failure separately (e.g., return a distinct
JsValue like JsValue::from_str("NOT_INITIALIZED") or JsValue::UNDEFINED) while
keeping NULL only for missing fields, and add rustdoc to each function (and any
shared helper such as with_kernel_ref) documenting the contract: NULL means
field absent, the distinct value means kernel not initialized; update references
to NOT_INITIALIZED (or add a single constant) so the three functions
consistently return the same sentinel on Err.
- Around line 169-177: The init() entrypoint must not panic: replace the
expect() on kernel.get_head() in init() so failures are converted into a
structured ABI error and returned instead of trapping; only call
install_kernel(Box::new(kernel)) after successfully obtaining the head. Update
init() to propagate or serialize an AbiError/Result (instead of returning
Uint8Array directly with a hard panic), mapping WarpKernel::new()/get_head()
failures into that error path so no unwrap/expect is used.

---

Duplicate comments:
In `@crates/warp-wasm/src/lib.rs`:
- Around line 112-118: The encode_err_raw function currently returns an empty
Uint8Array on encode failure which produces undecodable bytes for callers;
instead keep a pre-encoded minimal fallback (e.g. a const CODEC_ERROR: &[u8]
representing { ok: false, code: <codec error code>, message: "codec error" })
and convert that to a Uint8Array in the Err arm; update encode_err_raw to call
ErrEnvelope::new and echo_wasm_abi::encode_cbor as now, but on Err(_) return
bytes_to_uint8array(CODEC_ERROR) so every export always returns a
self-describing envelope (reference: function encode_err_raw, type ErrEnvelope,
echo_wasm_abi::encode_cbor, and bytes_to_uint8array).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33997c21-cdca-43c9-bca2-e622527833e6

📥 Commits

Reviewing files that changed from the base of the PR and between b632d77 and d0b8f1c.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • crates/warp-wasm/src/lib.rs

Comment on lines +62 to +88
fn with_kernel<F, R>(f: F) -> Result<R, AbiError>
where
F: FnOnce(&mut dyn KernelPort) -> Result<R, AbiError>,
{
KERNEL.with(|cell| {
let mut borrow = cell.borrow_mut();
let kernel = borrow.as_mut().ok_or_else(|| AbiError {
code: kernel_port::error_codes::NOT_INITIALIZED,
message: "kernel not initialized; call init() first".into(),
})?;
f(kernel.as_mut())
})
}

/// Run a closure with an immutable reference to the installed kernel.
fn with_kernel_ref<F, R>(f: F) -> Result<R, AbiError>
where
F: FnOnce(&dyn KernelPort) -> Result<R, AbiError>,
{
KERNEL.with(|cell| {
let borrow = cell.borrow();
let kernel = borrow.as_ref().ok_or_else(|| AbiError {
code: kernel_port::error_codes::NOT_INITIALIZED,
message: "kernel not initialized; call init() first".into(),
})?;
f(kernel.as_ref())
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the TLS kernel access reentrancy-safe.

Both helpers keep the RefCell borrow alive while executing arbitrary KernelPort code. Because KernelPort is a public injection point, an implementation can reenter warp_wasm::{dispatch_intent, step, get_head, install_kernel, ...} and trigger a RefCell borrow panic. That breaks the crate-level "no panics" guarantee at the ABI boundary.

Take the boxed kernel out of TLS before invoking user code and put it back afterward, or detect reentrancy and turn it into an AbiError instead of panicking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 62 - 88, with_kernel and
with_kernel_ref currently hold a RefCell borrow while calling user-provided
KernelPort code which allows reentrant calls to panic; fix by extracting the
boxed kernel out of the TLS cell before invoking f (use KERNEL.with(|cell| { let
mut borrow = cell.borrow_mut(); let mut kernel_box = borrow.take().ok_or_else(||
AbiError{...})?; let result = f(kernel_box.as_mut()); *borrow =
Some(kernel_box); result }) ), and likewise for with_kernel_ref take the Box
out, call f(kernel_box.as_ref()), then put it back; if borrow.take() returns
None because the kernel is already taken, return an AbiError indicating
reentrancy (use or add a kernel_port::error_codes::REENTRANT constant) instead
of letting a RefCell panic.

Comment on lines +169 to +177
pub fn init() -> Uint8Array {
#[cfg(feature = "engine")]
{
let kernel = warp_kernel::WarpKernel::new();
#[allow(clippy::expect_used)] // Fresh kernel; infallible in practice.
let head = kernel
.get_head()
.expect("fresh kernel must have valid head");
install_kernel(Box::new(kernel));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove the panic path from init().

init() is a wasm entrypoint that advertises structured errors. Line 173 converts a kernel failure into a trap instead of an AbiError, so the ABI contract and the new changelog claim about "no panics" both become false on this path.

Suggested fix
     #[cfg(feature = "engine")]
     {
         let kernel = warp_kernel::WarpKernel::new();
-        #[allow(clippy::expect_used)] // Fresh kernel; infallible in practice.
-        let head = kernel
-            .get_head()
-            .expect("fresh kernel must have valid head");
+        let head = match kernel.get_head() {
+            Ok(head) => head,
+            Err(err) => return encode_err(&err),
+        };
         install_kernel(Box::new(kernel));
         encode_ok(&head)
     }

Based on learnings: Library code must return Result or Option instead of panicking; unwrap() and expect() are forbidden in non-test code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 169 - 177, The init() entrypoint
must not panic: replace the expect() on kernel.get_head() in init() so failures
are converted into a structured ABI error and returned instead of trapping; only
call install_kernel(Box::new(kernel)) after successfully obtaining the head.
Update init() to propagate or serialize an AbiError/Result (instead of returning
Uint8Array directly with a hard panic), mapping WarpKernel::new()/get_head()
failures into that error path so no unwrap/expect is used.

Comment on lines 267 to +303
/// Get the codec identifier from the installed registry.
#[wasm_bindgen]
pub fn get_codec_id() -> JsValue {
JsValue::NULL
let result = with_kernel_ref(|k| Ok(k.registry_info()));
match result {
Ok(info) => match info.codec_id {
Some(id) => JsValue::from_str(&id),
None => JsValue::NULL,
},
Err(_) => JsValue::NULL,
}
}

/// Get the registry version from the installed registry.
#[wasm_bindgen]
pub fn get_registry_version() -> JsValue {
JsValue::NULL
let result = with_kernel_ref(|k| Ok(k.registry_info()));
match result {
Ok(info) => match info.registry_version {
Some(v) => JsValue::from_str(&v),
None => JsValue::NULL,
},
Err(_) => JsValue::NULL,
}
}

/// Get the schema hash (hex) from the installed registry.
#[wasm_bindgen]
pub fn get_schema_sha256_hex() -> JsValue {
JsValue::NULL
let result = with_kernel_ref(|k| Ok(k.registry_info()));
match result {
Ok(info) => match info.schema_sha256_hex {
Some(h) => JsValue::from_str(&h),
None => JsValue::NULL,
},
Err(_) => JsValue::NULL,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not collapse NOT_INITIALIZED and "field absent" into the same null.

These getters use JsValue::NULL for two different states: the kernel is missing, or the metadata field is genuinely absent. That makes host-side handshake failures indistinguishable from valid "no metadata" cases and diverges from the documented init-before-use contract for exports.

Either surface initialization failure separately, or reserve null only for absent fields and document that behavior explicitly in the rustdoc.
Based on learnings: Every public API across crates (warp-core, warp-wasm, etc.) must carry rustdoc comments that explain intent, invariants, and usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/warp-wasm/src/lib.rs` around lines 267 - 303, The three getters
(get_codec_id, get_registry_version, get_schema_sha256_hex) currently collapse
kernel-not-initialized errors and genuinely absent metadata into JsValue::NULL;
change them so with_kernel_ref error paths surface initialization failure
separately (e.g., return a distinct JsValue like
JsValue::from_str("NOT_INITIALIZED") or JsValue::UNDEFINED) while keeping NULL
only for missing fields, and add rustdoc to each function (and any shared helper
such as with_kernel_ref) documenting the contract: NULL means field absent, the
distinct value means kernel not initialized; update references to
NOT_INITIALIZED (or add a single constant) so the three functions consistently
return the same sentinel on Err.

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