# Plugin-Core Extraction: WASM-Compatible Foundation

## Summary

Extract the transport-agnostic core of plexus into a new `plugin-core` crate that can compile to WebAssembly. This enables:
- Browser-side schema validation and type checking
- WASI-based edge deployment
- Shared types between Rust server and potential Rust/WASM clients

## Current State

The plexus module in `hub-core` has minimal jsonrpsee coupling:

| Location | Coupling | Notes |
|----------|----------|-------|
| `plexus.rs:16-17` | `Methods`, `RpcModule` | Only in `into_rpc_module()` |
| `plexus.rs:100` | `Activation::into_rpc_methods()` | Trait method returning jsonrpsee type |
| `errors.rs` | `ErrorObjectOwned` | Deprecated, behind `#[allow(dead_code)]` |

The core routing, schema generation, and streaming logic is already transport-agnostic.

## Target Architecture

```
plugin-core/                    # WASM-compatible
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── activation.rs          # Activation trait (transport-agnostic)
    ├── router.rs              # Plexus routing, ChildRouter trait
    ├── schema.rs              # PluginSchema, MethodSchema, ChildSummary
    ├── stream.rs              # PlexusStream, wrap_stream, StreamMetadata
    ├── handle.rs              # Handle type for cross-plugin references
    ├── error.rs               # PlexusError (no jsonrpsee)
    └── context.rs             # PlexusContext (if needed)

hub-core/                       # Server-side, depends on plugin-core
├── Cargo.toml                  # plugin-core + jsonrpsee + tokio
└── src/
    ├── lib.rs                  # Re-exports plugin-core::*
    ├── rpc.rs                  # into_rpc_module(), RpcActivation extension
    ├── mcp_bridge.rs           # MCP transport (rmcp)
    └── activations/            # Concrete activations
```

## Dependencies

### plugin-core (WASM target)

```toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
futures = "0.3"
async-trait = "0.1"
async-stream = "0.3"
thiserror = "1.0"
schemars = { version = "1.1", features = ["derive", "uuid1"] }
uuid = { version = "1.6", features = ["v4", "v5", "serde"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.0", features = ["sync"] }  # For RwLock

[target.'cfg(target_arch = "wasm32")'.dependencies]
# Use std::sync::RwLock or parking_lot for WASM
```

### hub-core

```toml
[dependencies]
plugin-core = { path = "../plugin-core" }
tokio = { version = "1.0", features = ["full"] }
jsonrpsee = { version = "0.26", features = ["server", "macros"] }
rmcp = { version = "0.12", features = ["server", "transport-io", "transport-streamable-http-server"] }
# ... rest unchanged
```

## API Changes

### Activation Trait Split

**Before (hub-core):**
```rust
#[async_trait]
pub trait Activation: Send + Sync + 'static {
    type Methods: MethodEnumSchema;
    fn namespace(&self) -> &str;
    fn version(&self) -> &str;
    // ...
    async fn call(&self, method: &str, params: Value) -> Result<PlexusStream, PlexusError>;
    fn into_rpc_methods(self) -> Methods where Self: Sized;  // jsonrpsee coupling
}
```

**After (plugin-core):**
```rust
#[async_trait]
pub trait Activation: Send + Sync + 'static {
    type Methods: MethodEnumSchema;
    fn namespace(&self) -> &str;
    fn version(&self) -> &str;
    // ...
    async fn call(&self, method: &str, params: Value) -> Result<PlexusStream, PlexusError>;
    // No into_rpc_methods - that's transport-specific
}
```

**After (hub-core extension):**
```rust
use plugin_core::Activation;
use jsonrpsee::core::server::Methods;

/// Extension trait for activations that can be exposed via JSON-RPC
pub trait RpcActivation: Activation {
    fn into_rpc_methods(self) -> Methods where Self: Sized;
}
```

### Plexus Changes

**plugin-core:** Core `Plexus` with `route()`, `register()`, schema methods.

**hub-core:** Extension methods:
```rust
impl Plexus {
    /// Convert to JSON-RPC module (hub-core only)
    pub fn into_rpc_module(self) -> Result<RpcModule<()>, ...> { ... }
}
```

## Migration Path

1. **Create plugin-core crate** with copied types (no modifications yet)
2. **Verify WASM compilation:** `cargo build --target wasm32-unknown-unknown`
3. **Update hub-core** to depend on plugin-core, re-export types
4. **Move jsonrpsee code** to hub-core's `rpc.rs` module
5. **Update hub-macro** if needed for the trait split
6. **Update substrate** to use new structure

## WASM Build Verification

```bash
# Add WASM target
rustup target add wasm32-unknown-unknown

# Build plugin-core for WASM
cd plugin-core
cargo build --target wasm32-unknown-unknown

# For browser usage with wasm-bindgen
cargo build --target wasm32-unknown-unknown --features wasm-bindgen
```

## Open Questions

1. **Async runtime in WASM:** The `async fn call()` needs an executor. Options:
   - `wasm-bindgen-futures` for browser
   - Leave as `impl Future` and let caller provide executor
   - Feature-gate async vs sync variants

2. **RwLock in WASM:** `std::sync::RwLock` works in WASM but panics on contention. Options:
   - Use `parking_lot` (has WASM support)
   - Make registry immutable after construction
   - Feature-gate with `tokio::sync::RwLock` for native

3. **hub-macro integration:** The macro generates `into_rpc_methods()`. Options:
   - Feature-gate the generated code
   - Split macro into `plugin-macro` (core) and `hub-macro` (rpc)
   - Keep macro in hub-core, only usable for server builds

## Success Criteria

- [ ] `plugin-core` compiles to `wasm32-unknown-unknown`
- [ ] All existing tests pass in hub-core
- [ ] No breaking changes to substrate activation implementations
- [ ] Schema types usable from JavaScript via wasm-bindgen (stretch)
