[META] Migrate legacy openrpc_client! services to oschema-driven codegen — final closeout of hero_skills#262 #132

Open
opened 2026-05-22 13:25:20 +00:00 by timur · 1 comment
Owner

Goal

Final closeout of hero_skills#262. The meta decision is locked: oschema is the single source of truth for SDK generation across the Hero ecosystem. Several core services predate that decision and still hand-write openrpc.json + use the openrpc_client! proc-macro for client derivation. This issue tracks migrating each of them to oschema-driven codegen.

Audit — current state

Across lhumina_code core services, as of 2026-05-22:

hero_indexer       oschema=no   openrpc_client!=YES   ← migrate
hero_code          oschema=no   openrpc_client!=YES   ← migrate
hero_db            oschema=no   openrpc_client!=YES   ← migrate
hero_browser       oschema=no   openrpc_client!=YES   ← migrate
hero_aibroker      oschema=no   openrpc_client!=YES   ← migrate
hero_books         oschema=YES  openrpc_client!=YES   ← finish migration (hybrid)
hero_drive         oschema=YES  openrpc_client!=no    ← canonical (already done)
hero_biz/foundry   neither (non-RPC)

Six services need migration, in suggested order: hero_indexerhero_books (finish) → hero_dbhero_codehero_browserhero_aibroker. hero_indexer is first because (a) it is the smallest API surface, (b) the recent hero_rpc#127 / hero_rpc#130 work has us deep in its consumer-side surface, and (c) doing it first proves the methods-only path (#131 here) ahead of the larger surfaces.

Why this matters — concrete example

The OsisIndexer bug introduced by hero_rpc#127 (sync facade over an async openrpc_client!-generated client, panicked inside #[tokio::test]) is exactly the class of mistake that would have been impossible if hero_indexer were on oschema codegen. The consumer (hero_rpc) would have been written against a generated async client from the same generator that emits the consumer's async handlers, and the architectural footgun would not have existed.

What "migrated" means per service

For each target service (using hero_indexer as the template):

  1. Add schemas/<domain>/<domain>.oschema files declaring the existing API surface as service blocks. For hero_indexer: one schema per RPC domain (db, doc, index, search, server).
  2. Replace crates/<svc>_sdk/src/lib.rs's openrpc_client!(...) invocation with the oschema build script (fn main() { hero_rpc_osis_generator::build()?; }). Result: <svc>_sdk now contains generated trait + client + types from the same source the server consumes.
  3. Replace the server's hand-written handlers with the generated server trait + handler stubs filled in with the existing business logic. Types are now shared.
  4. Delete the hand-written openrpc.json — it becomes a generator output, not input.
  5. Re-validate: every existing consumer of the service's SDK still compiles (this is the heaviest step for hero_indexer because hero_rpc itself consumes it via OsisIndexer).
  6. Generated tests — auto-emitted <domain>_e2e.rs files exercise the SDK end-to-end. Hand-written integration tests stay alongside.

Prerequisites

  • hero_rpc#131 — verify the methods-only oschema path end-to-end. Without rootobjects, hero_indexer (and most of the legacy services) have nothing to anchor the generator on. #131 lands a canary example and shakes out any latent codegen bugs in the rootobject-free path.

Deliverables

  • One PR per service (six total), each: schemas + generator wiring + server adoption + consumer revalidation + tests.
  • Linter / lab infocheck extension that warns when a first-party service uses openrpc_client! directly (escape hatch retained for talking to foreign OpenRPC services we do not control).
  • Documentation update: hero_skills/skills/oschema/oschema.md and hero_skills/skills/hero/service/* should make clear oschema is the only path for first-party services.

Out of scope

  • Refactoring the openrpc_client! proc-macro itself — it stays as the foreign-server escape hatch.
  • Non-RPC services (hero_biz, hero_foundry) — different category, not part of this migration.
  • The OsisIndexer async fix (hero_rpc#130) — orthogonal; the migration retires the underlying problem class.
  • Meta: hero_skills#262
  • Prerequisite: hero_rpc#131
  • Motivating bug: hero_rpc#127 (merged) → hero_rpc#130 (in flight)
## Goal **Final closeout of [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262).** The meta decision is locked: oschema is the single source of truth for SDK generation across the Hero ecosystem. Several core services predate that decision and still hand-write `openrpc.json` + use the `openrpc_client!` proc-macro for client derivation. This issue tracks migrating each of them to oschema-driven codegen. ## Audit — current state Across `lhumina_code` core services, as of 2026-05-22: ``` hero_indexer oschema=no openrpc_client!=YES ← migrate hero_code oschema=no openrpc_client!=YES ← migrate hero_db oschema=no openrpc_client!=YES ← migrate hero_browser oschema=no openrpc_client!=YES ← migrate hero_aibroker oschema=no openrpc_client!=YES ← migrate hero_books oschema=YES openrpc_client!=YES ← finish migration (hybrid) hero_drive oschema=YES openrpc_client!=no ← canonical (already done) hero_biz/foundry neither (non-RPC) ``` **Six services need migration**, in suggested order: `hero_indexer` → `hero_books` (finish) → `hero_db` → `hero_code` → `hero_browser` → `hero_aibroker`. `hero_indexer` is first because (a) it is the smallest API surface, (b) the recent hero_rpc#127 / hero_rpc#130 work has us deep in its consumer-side surface, and (c) doing it first proves the methods-only path (#131 here) ahead of the larger surfaces. ## Why this matters — concrete example The `OsisIndexer` bug introduced by hero_rpc#127 (sync facade over an async `openrpc_client!`-generated client, panicked inside `#[tokio::test]`) is exactly the class of mistake that would have been impossible if hero_indexer were on oschema codegen. The consumer (hero_rpc) would have been written against a *generated* async client from the same generator that emits the *consumer*'s async handlers, and the architectural footgun would not have existed. ## What "migrated" means per service For each target service (using `hero_indexer` as the template): 1. **Add `schemas/<domain>/<domain>.oschema`** files declaring the existing API surface as `service` blocks. For `hero_indexer`: one schema per RPC domain (`db`, `doc`, `index`, `search`, `server`). 2. **Replace** `crates/<svc>_sdk/src/lib.rs`'s `openrpc_client!(...)` invocation with the oschema build script (`fn main() { hero_rpc_osis_generator::build()?; }`). Result: `<svc>_sdk` now contains generated trait + client + types from the same source the server consumes. 3. **Replace** the server's hand-written handlers with the generated server trait + handler stubs filled in with the existing business logic. Types are now shared. 4. **Delete** the hand-written `openrpc.json` — it becomes a generator output, not input. 5. **Re-validate**: every existing consumer of the service's SDK still compiles (this is the heaviest step for `hero_indexer` because hero_rpc itself consumes it via `OsisIndexer`). 6. **Generated tests** — auto-emitted `<domain>_e2e.rs` files exercise the SDK end-to-end. Hand-written integration tests stay alongside. ## Prerequisites - **[hero_rpc#131](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/131)** — verify the methods-only oschema path end-to-end. Without rootobjects, `hero_indexer` (and most of the legacy services) have nothing to anchor the generator on. #131 lands a canary example and shakes out any latent codegen bugs in the rootobject-free path. ## Deliverables - One PR per service (six total), each: schemas + generator wiring + server adoption + consumer revalidation + tests. - Linter / `lab infocheck` extension that warns when a first-party service uses `openrpc_client!` directly (escape hatch retained for talking *to* foreign OpenRPC services we do not control). - Documentation update: `hero_skills/skills/oschema/oschema.md` and `hero_skills/skills/hero/service/*` should make clear oschema is the only path for first-party services. ## Out of scope - Refactoring the `openrpc_client!` proc-macro itself — it stays as the foreign-server escape hatch. - Non-RPC services (`hero_biz`, `hero_foundry`) — different category, not part of this migration. - The `OsisIndexer` async fix ([hero_rpc#130](https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/130)) — orthogonal; the migration retires the underlying problem class. ## Links - Meta: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262) - Prerequisite: hero_rpc#131 - Motivating bug: hero_rpc#127 (merged) → hero_rpc#130 (in flight)
Author
Owner

First sub-issue filed: hero_indexer characterization tests

lhumina_code/hero_indexer#29 is now the gating
issue for hero_indexer's leg of this META. It locks the current
wire-path behavior (17 methods across Server / Db / Schema / Doc /
Index / Search) down as cargo + nu + persistence goldens, against
both origin/development (baseline) AND the in-flight Phase B
branch tip 2179a51. Phase C (server-side oschema adoption) is
blocked on this issue's suite passing — that's the migration
contract.

The migration agent has been told to push 2179a51 as a draft PR
with a BLOCKED tag and stand down. A fresh sub-agent will pick up
the characterization issue. Once green on both branches, Phase C
unblocks and the remaining five services (each their own
characterization-then-migrate sub-issue, same shape) follow.

## First sub-issue filed: hero_indexer characterization tests [lhumina_code/hero_indexer#29](https://forge.ourworld.tf/lhumina_code/hero_indexer/issues/29) is now the gating issue for hero_indexer's leg of this META. It locks the current wire-path behavior (17 methods across Server / Db / Schema / Doc / Index / Search) down as cargo + nu + persistence goldens, against both `origin/development` (baseline) AND the in-flight Phase B branch tip `2179a51`. Phase C (server-side oschema adoption) is blocked on this issue's suite passing — that's the migration contract. The migration agent has been told to push `2179a51` as a draft PR with a BLOCKED tag and stand down. A fresh sub-agent will pick up the characterization issue. Once green on both branches, Phase C unblocks and the remaining five services (each their own characterization-then-migrate sub-issue, same shape) follow.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_rpc#132
No description provided.