---
id: IR-12
title: "synapse: emit MethodRole (mdRole) in generated IR"
status: Complete
type: implementation
blocked_by: [IR-9]
unlocks: []
severity: High
target_repo: synapse
---

## Problem

IR-9 landed typed-handle codegen in hub-codegen: when an input IR contains `MethodDef` with `md_role: DynamicChild { .. }`, hub-codegen emits `DynamicChild<T> & Listable & Searchable` intersection types in the generated TypeScript client. However, **synapse (Haskell) — the IR builder that feeds hub-codegen — does not yet emit `mdRole`**. Its `MethodDef` record in `src/Synapse/IR/Types.hs` around line 229 has no role field.

Effect: hub-codegen's new typed-handle codegen never activates against real substrate schemas, because synapse's IR output always omits the role and hub-codegen defaults to `Rpc`. The IR epic's end-to-end promise (author writes `#[plexus_macros::child]`, synapse-cc generates typed handles) is broken at this link.

## Context

**Synapse's IR builder:** `~/dev/controlflow/hypermemetic/synapse/src/Synapse/IR/Builder.hs` walks a `PluginSchema` received from a Plexus RPC server and constructs `MethodDef` records. Today's walk extracts name, description, params, return type, streaming-ness, etc.

**Wire schema side:** Post-IR-3, `plexus-core::MethodSchema.role: MethodRole` carries the role tag. Synapse's protocol deserializer (`plexus-protocol` sibling repo, `src/Plexus/Schema/Recursive.hs`) needs to parse this field too — may already be additive-compatible but verify.

**Downstream consumer:** hub-codegen's `MethodDef` (Rust side, in `hub-codegen/src/ir.rs`) expects JSON field `mdRole` with variants `rpc`, `static_child`, and `dynamic_child { list_method, search_method }`.

## Required behavior

1. **plexus-protocol Haskell-side:** add `methodRole :: MethodRole` (or equivalent) field on `MethodSchema`. Define the `MethodRole` Haskell data type mirroring plexus-core's enum. Deserialize from JSON with `role` as the wire key, defaulting to `MethodRole.Rpc` when absent (back-compat with pre-IR servers).

2. **Synapse IR-builder side:** extend `MethodDef` in `src/Synapse/IR/Types.hs` with `mdRole :: MethodRole`. Populate it in `Synapse/IR/Builder.hs`'s walk from the `MethodSchema.methodRole` field.

3. **JSON emission:** synapse already emits IR as JSON consumed by hub-codegen. Verify the emitted JSON's shape matches hub-codegen's expected tag format (`{"kind": "rpc" | "static_child" | "dynamic_child", ...}` per IR-9's commit `b4fea08`). Align if different.

## Risks

| Risk | Mitigation |
|---|---|
| JSON tag shape mismatch between plexus-core's serde encoding and hub-codegen's Rust deserialization. | IR-9's `MethodRole` enum in hub-codegen uses `#[serde(tag = "kind", rename_all = "snake_case")]` or equivalent (verify). Synapse emits in the exact same shape. Acceptance 2 pins this via end-to-end test. |
| Pre-IR substrate servers (running older plexus-core) won't emit `role` on the wire. | Synapse defaults to `MethodRole.Rpc` when absent; hub-codegen also defaults to `Rpc`; typed-handle codegen remains inactive until the substrate upgrades. No breaking change. |
| Haskell's `Maybe MethodRole` vs default `Rpc` — choose one. | Pinned: default `Rpc` when absent (consistent with Rust side's `#[serde(default)]` behavior). |

## What must NOT change

- synapse's existing RPC dispatch, tree rendering, and help output for methods are unaffected — this ticket is about IR emission, not CLI behavior.
- pre-IR backends continue to work: synapse receives a `PluginSchema` without `role` and continues to function, emitting `mdRole: Rpc` in its IR output.
- Existing hub-codegen fixtures that use synthetic `MethodDef`s (e.g., for unit tests) remain valid.

## Acceptance criteria

1. `cabal build` and `cabal test` in synapse succeed.
2. End-to-end integration test: connect synapse to a substrate post-IR-3 (Solar migrated, `#[plexus_macros::child]` in use). Generate IR via `synapse -i`. The emitted JSON contains at least one `mdRole` with the `dynamic_child` tag and matches hub-codegen's expected format. Feed the IR into hub-codegen. hub-codegen generates a TypeScript client containing at least one `DynamicChild<ChildClient> & Listable` property (verified against a committed snapshot).
3. Running the end-to-end test against a pre-IR substrate produces JSON where every `mdRole` is `Rpc` (or absent) — no regressions.
4. A unit test in synapse round-trips a JSON `MethodSchema` with `role: {"kind": "dynamic_child", "list_method": "planet_names", "search_method": null}` and asserts the Haskell `MethodRole` matches the expected `DynamicChild` variant.

## Completion

PR(s) against `plexus-protocol` (if the Haskell-side `MethodRole` type lands there) and `synapse` (for the IR builder and emitter). CI green. End-to-end snapshot attached to PR demonstrating the typed-handle code in hub-codegen's output. Status flipped from `Ready` to `Complete` in the same commit as the synapse code.
