# HandleEnum: Declarative Handle Definition and Resolution

> Codegen approach for type-safe handle creation and automatic storage resolution

## Problem Statement

Currently, handles are created ad-hoc and resolution is implemented manually:

```rust
// Creation - scattered throughout code
let handle = Handle::new(self.plugin_id(), "1.0.0", "chat_event")
    .with_meta(vec![event_id.to_string()]);

// Resolution - manual implementation per plugin
async fn resolve_handle(&self, handle: &Handle) -> Result<PlexusStream, PlexusError> {
    match handle.method.as_str() {
        "chat_event" => {
            let event_id = handle.meta.get(0).ok_or(...)?;
            self.storage.get_event(event_id).await
        }
        _ => Err(PlexusError::HandleNotSupported(...))
    }
}
```

**Problems:**
1. Handle structure isn't documented - meta field meanings are implicit
2. Creation and resolution can get out of sync
3. Boilerplate resolution code in every plugin
4. No compile-time validation of handle structure

## Proposed Solution: HandleEnum Derive Macro

Define handles as an enum with storage mapping annotations:

```rust
#[derive(HandleEnum)]
#[handle(plugin_id = "CLAUDECODE_PLUGIN_ID", version = "1.0.0")]
pub enum ClaudeCodeHandle {
    #[handle(method = "chat_event", table = "chat_events", key = "id")]
    ChatEvent { event_id: String },

    #[handle(method = "message", table = "messages", key = "id")]
    Message { message_id: String, role: String },

    #[handle(method = "session", table = "claudecode_sessions", key = "id")]
    Session { session_id: String },
}
```

## Generated Code

### 1. Handle Creation (Enum → Handle)

```rust
impl ClaudeCodeHandle {
    /// Convert to a Handle for storage/transmission
    pub fn to_handle(&self) -> Handle {
        match self {
            Self::ChatEvent { event_id } =>
                Handle::new(CLAUDECODE_PLUGIN_ID, "1.0.0", "chat_event")
                    .with_meta(vec![event_id.clone()]),

            Self::Message { message_id, role } =>
                Handle::new(CLAUDECODE_PLUGIN_ID, "1.0.0", "message")
                    .with_meta(vec![message_id.clone(), role.clone()]),

            Self::Session { session_id } =>
                Handle::new(CLAUDECODE_PLUGIN_ID, "1.0.0", "session")
                    .with_meta(vec![session_id.clone()]),
        }
    }
}

// Convenience: impl From for Handle
impl From<ClaudeCodeHandle> for Handle {
    fn from(h: ClaudeCodeHandle) -> Handle {
        h.to_handle()
    }
}
```

### 2. Handle Parsing (Handle → Enum)

```rust
impl TryFrom<&Handle> for ClaudeCodeHandle {
    type Error = HandleParseError;

    fn try_from(handle: &Handle) -> Result<Self, Self::Error> {
        // Verify plugin ownership
        if handle.plugin_id != CLAUDECODE_PLUGIN_ID {
            return Err(HandleParseError::WrongPlugin {
                expected: CLAUDECODE_PLUGIN_ID,
                got: handle.plugin_id,
            });
        }

        match handle.method.as_str() {
            "chat_event" => {
                let event_id = handle.meta.get(0)
                    .ok_or(HandleParseError::MissingMeta { index: 0, field: "event_id" })?
                    .clone();
                Ok(Self::ChatEvent { event_id })
            }

            "message" => {
                let message_id = handle.meta.get(0)
                    .ok_or(HandleParseError::MissingMeta { index: 0, field: "message_id" })?
                    .clone();
                let role = handle.meta.get(1)
                    .ok_or(HandleParseError::MissingMeta { index: 1, field: "role" })?
                    .clone();
                Ok(Self::Message { message_id, role })
            }

            "session" => {
                let session_id = handle.meta.get(0)
                    .ok_or(HandleParseError::MissingMeta { index: 0, field: "session_id" })?
                    .clone();
                Ok(Self::Session { session_id })
            }

            _ => Err(HandleParseError::UnknownMethod(handle.method.clone()))
        }
    }
}
```

### 3. Storage Resolution (Handle → Data)

```rust
impl ClaudeCodeHandle {
    /// Resolve this handle from storage
    pub async fn resolve(&self, pool: &SqlitePool) -> Result<serde_json::Value, HandleResolveError> {
        match self {
            Self::ChatEvent { event_id } => {
                sqlx::query_scalar::<_, String>(
                    "SELECT event_data FROM chat_events WHERE id = ?"
                )
                .bind(event_id)
                .fetch_one(pool)
                .await
                .map(|s| serde_json::from_str(&s).unwrap_or(serde_json::Value::String(s)))
                .map_err(HandleResolveError::from)
            }

            Self::Message { message_id, .. } => {
                sqlx::query_scalar::<_, String>(
                    "SELECT content FROM messages WHERE id = ?"
                )
                .bind(message_id)
                .fetch_one(pool)
                .await
                .map(|s| serde_json::from_str(&s).unwrap_or(serde_json::Value::String(s)))
                .map_err(HandleResolveError::from)
            }

            Self::Session { session_id } => {
                sqlx::query_as::<_, (String, String, String)>(
                    "SELECT id, name, working_dir FROM claudecode_sessions WHERE id = ?"
                )
                .bind(session_id)
                .fetch_one(pool)
                .await
                .map(|(id, name, wd)| serde_json::json!({
                    "id": id, "name": name, "working_dir": wd
                }))
                .map_err(HandleResolveError::from)
            }
        }
    }
}
```

### 4. Activation Integration

The plugin's `resolve_handle` implementation becomes trivial:

```rust
impl Activation for ClaudeCode {
    async fn resolve_handle(&self, handle: &Handle) -> Result<PlexusStream, PlexusError> {
        let parsed = ClaudeCodeHandle::try_from(handle)
            .map_err(|e| PlexusError::InvalidParams(e.to_string()))?;

        let pool = self.storage.pool();
        let data = parsed.resolve(pool).await
            .map_err(|e| PlexusError::ExecutionError(e.to_string()))?;

        Ok(Box::pin(stream! {
            yield ResolveEvent::Data(data);
        }))
    }
}
```

Or even simpler with a generated helper:

```rust
// Generated by #[handle(activation = "ClaudeCode")]
impl ClaudeCode {
    async fn resolve_handle(&self, handle: &Handle) -> Result<PlexusStream, PlexusError> {
        ClaudeCodeHandle::resolve_for_activation(handle, &self.storage).await
    }
}
```

## Annotation Reference

### Enum-level attributes

```rust
#[derive(HandleEnum)]
#[handle(
    plugin_id = "PLUGIN_CONSTANT",  // Constant holding the plugin UUID
    version = "1.0.0",               // Semantic version for handles
)]
pub enum MyPluginHandle { ... }
```

### Variant-level attributes

```rust
#[handle(
    method = "event_type",     // The handle.method value
    table = "events",          // SQLite table name
    key = "id",                // Primary key column (matched against meta[0])
    columns = "data",          // Optional: column(s) to select (default: *)
)]
VariantName { field1: String, field2: String }
```

### Field ordering

Fields map to `handle.meta` by position:
- First field → `meta[0]`
- Second field → `meta[1]`
- etc.

The `key` attribute specifies which field is used for the WHERE clause.

## Advanced: Custom Resolution

For complex cases, allow custom resolution functions:

```rust
#[derive(HandleEnum)]
pub enum ClaudeCodeHandle {
    // Standard table lookup
    #[handle(method = "event", table = "events", key = "id")]
    Event { event_id: String },

    // Custom resolution function
    #[handle(method = "computed", resolve_fn = "resolve_computed")]
    Computed { base_id: String, variant: String },
}

impl ClaudeCodeHandle {
    async fn resolve_computed(
        pool: &SqlitePool,
        base_id: &str,
        variant: &str
    ) -> Result<Value, HandleResolveError> {
        // Custom logic here
    }
}
```

## Benefits

1. **Single source of truth**: Enum defines structure, creation, and resolution
2. **Type safety**: Compile-time validation of handle structure
3. **Self-documenting**: Enum variants document what handles a plugin produces
4. **Symmetric**: Creation and resolution are generated from same definition
5. **No boilerplate**: Resolution is fully generated from annotations

## Migration Path

1. Define `HandleEnum` derive macro in `hub-macro`
2. Add storage mapping attributes
3. Implement for one plugin (e.g., ClaudeCode) as proof of concept
4. Migrate other plugins incrementally
5. Remove manual `resolve_handle` implementations

## Related Documents

- [Arbor Stream Map](16677963644192091647_arbor-stream-map.md) - Where handles are stored
- [Handle Architecture](16679869168467720703_handle-architecture.md) - Algebraic model of handles
- [Plugin Development Guide](16678373036159325695_plugin-development-guide.md) - Creating plugins
