---
id: IR-13
title: "hub-codegen: complete Rust backend typed-handle wiring (replace skeleton Child = serde_json::Value)"
status: Superseded
superseded_by: RUSTGEN-1
type: implementation
blocked_by: [IR-9]
unlocks: []
severity: Medium
target_repo: hub-codegen
---

## Problem

IR-9 landed TypeScript typed-handle codegen fully (with `tsc --noEmit` verifying compile-time capability rejection) and Rust typed-handle codegen as a skeleton. The Rust skeleton emits the `DynamicChild`, `Listable`, `Searchable` trait declarations plus per-gate structs that implement them — but the `Child` associated type is `serde_json::Value` rather than a real Rust child client.

Root cause: hub-codegen's Rust backend doesn't yet emit per-namespace typed client modules the way the TypeScript backend does. Generating a real `ChildClient` requires that infrastructure, which is a larger scope than IR-9 could cover.

Without this ticket, a Rust consumer using hub-codegen-generated clients gets untyped `serde_json::Value` responses when calling `.get(name)` on a dynamic-child gate — losing the whole point of the typed-handle pattern.

## Context

IR-9 committed skeleton: `hub-codegen/src/generator/rust/client.rs` emits per-gate structs `XxxGate` implementing `DynamicChild<Child = serde_json::Value>`. The TS backend's equivalent (`hub-codegen/src/generator/typescript/namespaces.rs`) generates a full `ChildClient` class per child namespace that the gate's `.get()` then returns.

To fix Rust: hub-codegen's Rust backend needs to grow per-namespace client module generation first, then rewire the dynamic-child gate's `Child` type to point at those generated types.

**This ticket's scope:** the full wiring. Optional intermediate scope if the per-namespace generation is larger than expected: cut a spike or split the ticket.

## Required behavior

1. **Per-namespace Rust clients.** For each child activation referenced by a dynamic-child gate, hub-codegen emits a Rust struct (e.g., `CelestialBodyClient`) with methods matching the child's schema. This mirrors what the TypeScript backend does per plugin.

2. **Gate wiring.** The `DynamicChild` impl on each per-gate struct specifies `type Child = CelestialBodyClient` (or the appropriate client type), with `.get(name)` constructing and returning that typed client.

3. **Capability methods.** `.list()` and `.search(query)` remain streams of `String` — same shape as IR-9's skeleton; no change beyond the child type.

4. **Runtime plumbing.** The generated Rust client code needs a tiny runtime library (constructor helpers, path composition) that mirrors what TS gets inline. Either vendor it into generated output (like TS does) or ship as a per-target runtime crate.

## Risks

| Risk | Mitigation |
|---|---|
| Per-namespace Rust codegen is a larger scope than expected. | If the implementer determines it's larger than a typical ticket's one-diff budget, split this ticket into (a) per-namespace Rust client scaffold and (b) dynamic-child gate rewire against scaffold. Flag in the PR and update this ticket's body to point at the split. |
| Existing Rust smoke tests (hub-codegen `tests/rust_codegen_smoke_test.rs`) assume `serde_json::Value` on the `Child` type. | Update those tests when the rewire lands; assert the new typed path. |
| Downstream Rust consumers of hub-codegen that have hand-wired around the skeleton. | None known at time of writing. If any exist in substrate (unlikely since substrate doesn't consume hub-codegen's Rust output), flag during PR. |

## What must NOT change

- TypeScript backend behavior — unchanged from IR-9.
- hub-codegen's Rust backend for `Rpc`-role and `StaticChild`-role methods — unchanged (those already emit non-skeleton typed clients; only DynamicChild was skeletal).
- Public CLI surface of hub-codegen — no new required flags.
- Pre-IR regression: regenerating against a no-role IR produces byte-identical output to pre-ticket.

## Acceptance criteria

1. `cargo build` and `cargo test --features rust` in hub-codegen succeed.
2. A fixture IR with one `DynamicChild` gate references a child schema. The generated Rust client emits a `ChildClient` struct with methods matching the child's schema (not `serde_json::Value`).
3. The generated dynamic-child gate's `DynamicChild` impl has `type Child = ChildClient`.
4. A consumer fixture `.rs` file uses the generated gate's `.get(name)` and calls a typed method on the result (e.g., `client.body.get("mercury").await?.info().await`). `cargo check` on this consumer fixture passes — the typed path is complete.
5. Capability intersections still compile-time-enforce correctly: a consumer calling `.search()` on a gate without `Searchable` still fails to compile in Rust.
6. Existing Rust smoke tests updated and passing against the non-skeleton output.

## Completion

PR against hub-codegen. CI green. PR description includes a before/after sample of the generated Rust gate implementation showing the typed `Child` association. Status flipped from `Ready` to `Complete` in the same commit.
