Files
optimclaw/src/channels/wasm/error.rs
T
04c5c3fe9f feat: WASM extension versioning with WIT compat checks (#592)
* feat: add WASM extension versioning with WIT compat checks and CI enforcement

Phase 1 — WIT Versioning & Compatibility Checks:
- Version WIT packages as `package near:[email protected];`
- Add `semver` crate for version parsing and comparison
- Add `WIT_TOOL_VERSION` / `WIT_CHANNEL_VERSION` host constants
- Add `version` and `wit_version` fields to capabilities schemas
- Add `wit_version` column to `wasm_tools` DB table (both backends)
- Add load-time `check_wit_version_compat()` with semver rules
- Add `IncompatibleWitVersion` error variants for tools and channels
- Enhance instantiation errors with WIT version mismatch hints
- Update all 14 capabilities JSON and 14 registry JSON files

Phase 2 — Upgrade-in-Place & Channel DB Storage:
- Change tool store to DELETE-before-INSERT (one version per extension)
- Create `wasm_channels` table (PostgreSQL migration + libSQL schema)
- Add `WasmChannelStore` trait with PostgreSQL and libSQL backends
- Add `extension_info` tool showing version, WIT version, and status
- Wire `ExtensionInfoTool` into tool registry (7 extension tools)

Phase 3 — CI Version-Bump Enforcement:
- Add `scripts/check-version-bumps.sh` checking WIT/tool/channel versions
- Add `version-check` CI job (PR-only) to `.github/workflows/test.yml`
- Support `[skip-version-check]` label/commit message bypass

Includes 7 regression tests for WIT version compatibility checking
and 2 integration tests for WIT version annotation verification.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review feedback for WASM extension versioning

- Wrap PostgreSQL DELETE+INSERT in transactions for both tool and channel
  store() methods to prevent data loss on partial failure (Gemini, Copilot)
- Rename StoredWasmChannelWithBinary.tool → .channel (copy-paste fix)
- Remove unused WasmError::IncompatibleWitVersion variant (dead code)
- Map channel loader WIT mismatch to IncompatibleWitVersion instead of
  generic Config error, simplify variant to single String message
- Fix extension_info description to match actual returned fields
- Add schema test for ExtensionInfoTool matching existing test pattern
- Fix CI script to fail fast on git errors instead of silent bypass

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-03-06 04:38:07 +00:00

93 lines
2.6 KiB
Rust

//! Error types for WASM channels.
use std::path::PathBuf;
/// Error during WASM channel operations.
#[derive(Debug, thiserror::Error)]
pub enum WasmChannelError {
#[error("Channel {name} failed to start: {reason}")]
StartupFailed { name: String, reason: String },
#[error("Channel {name} callback failed: {reason}")]
CallbackFailed { name: String, reason: String },
#[error("Channel {name} WASM execution trapped: {reason}")]
Trapped { name: String, reason: String },
#[error("Channel {name} callback '{callback}' timed out")]
Timeout { name: String, callback: String },
#[error("Channel {name} execution panicked: {reason}")]
ExecutionPanicked { name: String, reason: String },
#[error("Channel {name} emit rate limited")]
EmitRateLimited { name: String },
#[error("Channel {name} HTTP path not allowed: {path}")]
PathNotAllowed { name: String, path: String },
#[error("Channel {name} polling interval too short: {interval_ms}ms (minimum: {min_ms}ms)")]
PollIntervalTooShort {
name: String,
interval_ms: u32,
min_ms: u32,
},
#[error("Channel {name} workspace path escape attempt: {path}")]
WorkspaceEscape { name: String, path: String },
#[error("Channel {name} exhausted fuel limit ({limit})")]
FuelExhausted { name: String, limit: u64 },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("WASM file not found: {0}")]
WasmNotFound(PathBuf),
#[error("Capabilities file not found: {0}")]
CapabilitiesNotFound(PathBuf),
#[error("Invalid capabilities JSON: {0}")]
InvalidCapabilities(String),
#[error("WASM compilation error: {0}")]
Compilation(String),
#[error("WASM instantiation error: {0}")]
Instantiation(String),
#[error("Invalid channel name: {0}")]
InvalidName(String),
#[error("Channel {name} not found")]
NotFound { name: String },
#[error("Channel module missing export: {0}")]
MissingExport(String),
#[error("Invalid response from WASM: {0}")]
InvalidResponse(String),
#[error("Runtime not initialized")]
RuntimeNotInitialized,
#[error("Configuration error: {0}")]
Config(String),
#[error("Webhook registration failed for channel {name}: {reason}")]
WebhookRegistration { name: String, reason: String },
#[error("HTTP request error: {0}")]
HttpRequest(String),
#[error("WIT version mismatch: {0}")]
IncompatibleWitVersion(String),
}
impl From<crate::tools::wasm::WasmError> for WasmChannelError {
fn from(err: crate::tools::wasm::WasmError) -> Self {
WasmChannelError::Compilation(err.to_string())
}
}