---
id: IR-21
title: "plexus-macros: HandleEnum derive must support generic activation types (unblocks IR-17/18/19)"
status: Complete
type: implementation
blocked_by: []
unlocks: [IR-17, IR-18, IR-19]
severity: High
target_repo: plexus-macros
---

## Problem

`#[derive(HandleEnum)]` on enums like `ConeHandle` and `ClaudeCodeStreamHandle` references the owning activation type's associated constants (`Cone::PLUGIN_ID`, `ClaudeCode::PLUGIN_ID`). When the activation is **generic** (e.g., `Cone<P: HubContext>`, `ClaudeCode<P: HubContext>`), rustc cannot infer `P` at the `HandleEnum` derive-expansion site:

```
error[E0283]: type annotations needed
  --> src/activations/cone/types.rs:20:24
20 | #[derive(Debug, Clone, HandleEnum)]
   |                        ^^^^^^^^^^ cannot infer type
   = note: cannot satisfy `_: plexus_core::plexus::HubContext`
note: required by a bound in `Cone`
```

Four E0283 errors currently block `cargo build -p plexus-substrate`. They are pre-existing (not caused by IR-10 or IR-20 — verified by stashing those changes) but block:

- **IR-17** (Orcha migration) — its acceptance criterion `cargo build -p plexus-substrate` cannot pass.
- **IR-18** / **IR-19** (claudecode + cone child gates) — same.
- Anyone else trying to build substrate.

## Context

The HandleEnum derive macro lives at `/Users/shmendez/dev/controlflow/hypermemetic/plexus-macros/src/handle_enum.rs`. It generates code that references the activation type (e.g., `Cone::PLUGIN_ID`) via the attribute:

```rust
#[derive(HandleEnum)]
#[handle(plugin_id = "Cone::PLUGIN_ID", version = "1.0.0")]
pub enum ConeHandle { /* ... */ }
```

The string `"Cone::PLUGIN_ID"` is parsed as a `syn::Path` and emitted into generated code that then asks the compiler to evaluate it. For a non-generic `Cone`, this works. For `Cone<P>` the compiler sees an ambiguous type and emits E0283.

## Required behavior

The derive macro emits code that either:

**Option A (preferred):** explicitly turbofishes the default generic parameter when the referenced type is generic. For `Cone<P: HubContext = NoParent>`, emit `<Cone<::plexus_core::plexus::NoParent>>::PLUGIN_ID` rather than `Cone::PLUGIN_ID`.

**Option B (fallback):** accept a `plugin_id_type` attribute arg that lets the author pin the concrete instantiation:

```rust
#[handle(plugin_id_type = "Cone<NoParent>", plugin_id = "PLUGIN_ID", version = "1.0.0")]
```

And emit `<Cone<NoParent>>::PLUGIN_ID`.

**Pin Option A.** The macro inspects the activation type's default generic parameter (usually `NoParent`) and substitutes it into the generated path automatically. No author-facing change required — existing `plugin_id = "Cone::PLUGIN_ID"` attributes keep working.

Implementation sketch: the derive macro already parses `plugin_id` as a path. When that path references a generic type, detect the generic params and fill in with defaults. `syn` exposes `ItemStruct::generics` which carries default params; the macro can read those from the same crate by spanning, though cross-crate inspection may require the fallback Option B.

If Option A's cross-crate generics inspection is too complex, fall back to Option B and require authors to write `plugin_id_type = "Cone<NoParent>"` explicitly at each call site. The acceptance criterion only requires ONE path to work — pin whichever is feasible in one-diff scope.

## Risks

| Risk | Mitigation |
|---|---|
| Cross-crate generics inspection at macro expansion time is hard. | Fall back to Option B. Explicit `plugin_id_type` attr arg. 4 substrate call sites to update (Cone, ClaudeCode, probably Arbor — grep for `#[derive(HandleEnum)]`). |
| Option A generates incorrect defaults when the type has a non-obvious default. | Substrate's generic activations all default to `NoParent`. If a future activation uses a different default, author adds `plugin_id_type` explicitly. |
| Changing the derive codegen breaks existing non-generic call sites. | Regression gate: all substrate activations that use `#[derive(HandleEnum)]` today (Cone, ClaudeCode, possibly Arbor) continue to build after this ticket. |

## What must NOT change

- The `HandleEnum` attribute surface at the author's call site (author still writes `#[handle(plugin_id = "Foo::PLUGIN_ID", version = "X.Y.Z")]`) — unless Option B is needed as fallback.
- Non-generic activation HandleEnum derives continue to work unchanged.

## Acceptance criteria

1. `cargo build -p plexus-macros` and `cargo test -p plexus-macros` succeed.
2. `cargo build -p plexus-substrate` succeeds — zero E0283 errors on `Cone<P>`, `ClaudeCode<P>`, or any other generic activation's HandleEnum derive.
3. A fixture in plexus-macros with a generic activation `GenericActivation<P: HubContext>` and a `#[derive(HandleEnum)]` enum referencing `GenericActivation::PLUGIN_ID` compiles cleanly.
4. Existing HandleEnum derives across substrate (grep for `#[derive(HandleEnum)]`) continue to produce valid code — each still routes to the correct `plugin_id` at runtime.
5. If Option B is needed: substrate's 3 (approx) call sites updated to pass `plugin_id_type`.

## Completion

PR against plexus-macros (and substrate if Option B requires call-site updates). `cargo build -p plexus-substrate` green. Status flipped to Complete in the same commit as the macro change.
