feat: upgrade to hero_service template codegen pattern (#44) #46

Merged
timur merged 3 commits from issue-44-template-pattern-upgrade into main 2026-05-20 09:35:45 +00:00
Owner

Closes #44. Migrates hero_logic from the older OSchema codegen path onto the canonical hero_service template (hero_skills#261 / hero_rpc#55).

What changed

New shared crate crates/hero_logic_types/

  • Owns build.rs with the new OschemaBuildConfig shape: schemas_dir, sdk_dir, client_crate_dir, server_crate_dir, server_types_crate, sdk_types_crate, with_wasm, with_python_sdk, with_rhai. Mirrors the hero_service template exactly.
  • Generates OSchema types in-place; routes server handler files to ../hero_logic_server/src/logic/; routes typed client wrappers + WASM-compat types to ../hero_logic_sdk/src/logic/.
  • Post-process: re-emits hero_logic_server/src/lib.rs to restore the hand-written engine, seed, services, tracing_layer sibling modules the codegen would otherwise strip; and patches the emitted openrpc.json (info.title colon + missing servers block) so the openrpc_client! macro accepts it.

Server bootstrap (crates/hero_logic_server/src/main.rs)

  • Replaces OServerConfig::new() + server.run().await with HeroLifecycle::new(name, repo, package) + OServer::run_cli(lifecycle, |server, contexts, ...| async move { ... }). Domain registration, banner, socket prep, seed-flow spawn, and shutdown all run inside the closure.
  • Removes the old per-crate build.rs (moved into the shared types crate).

SDK (crates/hero_logic_sdk/)

  • Deps swapped: hero_rpc_derive + hero_rpc_openrpchero_rpc_client + herolib_otoml + herolib_sid + (still) hero_rpc_derive for the local macro path.
  • src/logic/{types,osis_client_generated,mod}.rs now auto-emitted by codegen. Contains LogicClient (HTTP/WASM client built on OsisClient) — the canonical SDK consumer surface for browser/Dioxus/mobile.
  • src/local_client.rsopenrpc_client! macro wrapping the codegen-emitted openrpc.json to produce LogicServiceClient for in-tree Unix-socket consumers (CLI + admin). Co-exists with LogicClient so consumers pick their transport.
  • src/python_source.rs — hand-written decode_python_source b64-prefix helper preserved across regens via the SDK builder's auto-discovery.

Admin (crates/hero_logic_admin/)

  • Deleted src/rpc_client.rs entirely.
  • AppState::client: LogicRpcClient field removed. New AppState::rpc_call(method, params) helper routes raw RPC calls through the typed SDK's underlying OpenRpcTransport (via LogicServiceClient::transport()). Same per-call reconnect semantics, no separate transport.
  • All 17 raw-call sites in routes.rs migrated from state.client.rpc_call(...)state.rpc_call(...). Typed SDK calls (sdk.logic_service_*()) unchanged.

Workspace

  • Schemas moved from crates/hero_logic_server/schemas/ to top-level schemas/.
  • New workspace deps: hero_lifecycle, hero_rpc_client, herolib_otoml.
  • Path deps: hero_logic_types, hero_logic_sdk.
  • hero_logic_sdk depends on hero_logic_types to force build-order serialisation (the macro reads docs/logic/openrpc.json which hero_logic_types' build.rs writes + patches).

JS / Python SDK targets

  • Auto-emitted into top-level sdk/js/ and sdk/python/ by the new build config (with_python_sdk() + js() is implicit when target is Server).
  • Rhai bindings enabled via with_rhai().

Out of scope

Per the issue body: no hero_rpc2 trait-macro migration (separate follow-up). No domain-code refactor (engine, seed, services, tracing_layer, admin templates, CLI clap tree, 19 hand-written logicservice.* methods).

Acceptance

  • build.rs uses the new OschemaBuildConfig shape
  • Server uses HeroLifecycle + OServer::run_cli
  • Types live in shared hero_logic_types crate consumed by _server + _sdk
  • admin/src/rpc_client.rs deleted; admin uses typed SDK wrappers throughout
  • JS / Rhai / Python SDK targets emit alongside Rust
  • python_source b64-prefix helper preserved
  • cargo build --workspace finishes clean (1 unused-imports warning in codegen-emitted types_generated.rs — upstream concern)
  • lab infocheck: 3 crate(s) clean, 0 with issues, 0 finding(s) total
  • Smoke verified: server boots and binds rpc.sock, CLI search + workflow versions succeed through typed SDK, admin renders /workflows + /plays + /health through both typed-SDK and raw-rpc paths

Three small hero_rpc generator fixes would eliminate the build.rs post-process hack in this PR:

  1. Emit info.title without a colon (e.g. LogicService instead of LogicService:logic) so openrpc_client!'s identifier derivation accepts it.
  2. Emit a servers block in openrpc.json for service projects so the macro's connect() resolves to the right Unix socket out of the box.
  3. Have generate_server_lib_rs auto-discover hand-written sibling modules the way generate_sdk_lib_rs already does (via //! @sdk-feature: doc-comment markers), so consumers with extra modules like engine/seed don't need a build.rs post-process step to restore them after regen.

🤖 Generated with Claude Code

Closes #44. Migrates hero_logic from the older OSchema codegen path onto the canonical hero_service template (hero_skills#261 / hero_rpc#55). ## What changed **New shared crate `crates/hero_logic_types/`** - Owns `build.rs` with the new `OschemaBuildConfig` shape: `schemas_dir`, `sdk_dir`, `client_crate_dir`, `server_crate_dir`, `server_types_crate`, `sdk_types_crate`, `with_wasm`, `with_python_sdk`, `with_rhai`. Mirrors the hero_service template exactly. - Generates OSchema types in-place; routes server handler files to `../hero_logic_server/src/logic/`; routes typed client wrappers + WASM-compat types to `../hero_logic_sdk/src/logic/`. - Post-process: re-emits `hero_logic_server/src/lib.rs` to restore the hand-written `engine`, `seed`, `services`, `tracing_layer` sibling modules the codegen would otherwise strip; and patches the emitted `openrpc.json` (`info.title` colon + missing `servers` block) so the `openrpc_client!` macro accepts it. **Server bootstrap (`crates/hero_logic_server/src/main.rs`)** - Replaces `OServerConfig::new()` + `server.run().await` with `HeroLifecycle::new(name, repo, package)` + `OServer::run_cli(lifecycle, |server, contexts, ...| async move { ... })`. Domain registration, banner, socket prep, seed-flow spawn, and shutdown all run inside the closure. - Removes the old per-crate `build.rs` (moved into the shared types crate). **SDK (`crates/hero_logic_sdk/`)** - Deps swapped: `hero_rpc_derive` + `hero_rpc_openrpc` → `hero_rpc_client` + `herolib_otoml` + `herolib_sid` + (still) `hero_rpc_derive` for the local macro path. - `src/logic/{types,osis_client_generated,mod}.rs` now auto-emitted by codegen. Contains `LogicClient` (HTTP/WASM client built on `OsisClient`) — the canonical SDK consumer surface for browser/Dioxus/mobile. - `src/local_client.rs` — `openrpc_client!` macro wrapping the codegen-emitted `openrpc.json` to produce `LogicServiceClient` for in-tree Unix-socket consumers (CLI + admin). Co-exists with `LogicClient` so consumers pick their transport. - `src/python_source.rs` — hand-written `decode_python_source` b64-prefix helper preserved across regens via the SDK builder's auto-discovery. **Admin (`crates/hero_logic_admin/`)** - Deleted `src/rpc_client.rs` entirely. - `AppState::client: LogicRpcClient` field removed. New `AppState::rpc_call(method, params)` helper routes raw RPC calls through the typed SDK's underlying `OpenRpcTransport` (via `LogicServiceClient::transport()`). Same per-call reconnect semantics, no separate transport. - All 17 raw-call sites in `routes.rs` migrated from `state.client.rpc_call(...)` → `state.rpc_call(...)`. Typed SDK calls (`sdk.logic_service_*()`) unchanged. **Workspace** - Schemas moved from `crates/hero_logic_server/schemas/` to top-level `schemas/`. - New workspace deps: `hero_lifecycle`, `hero_rpc_client`, `herolib_otoml`. - Path deps: `hero_logic_types`, `hero_logic_sdk`. - `hero_logic_sdk` depends on `hero_logic_types` to force build-order serialisation (the macro reads docs/logic/openrpc.json which hero_logic_types' build.rs writes + patches). **JS / Python SDK targets** - Auto-emitted into top-level `sdk/js/` and `sdk/python/` by the new build config (`with_python_sdk()` + `js()` is implicit when target is Server). - Rhai bindings enabled via `with_rhai()`. ## Out of scope Per the issue body: no hero_rpc2 trait-macro migration (separate follow-up). No domain-code refactor (`engine`, `seed`, `services`, `tracing_layer`, admin templates, CLI clap tree, 19 hand-written `logicservice.*` methods). ## Acceptance - ✅ `build.rs` uses the new `OschemaBuildConfig` shape - ✅ Server uses `HeroLifecycle + OServer::run_cli` - ✅ Types live in shared `hero_logic_types` crate consumed by `_server` + `_sdk` - ✅ `admin/src/rpc_client.rs` deleted; admin uses typed SDK wrappers throughout - ✅ JS / Rhai / Python SDK targets emit alongside Rust - ✅ `python_source` b64-prefix helper preserved - ✅ `cargo build --workspace` finishes clean (1 unused-imports warning in codegen-emitted `types_generated.rs` — upstream concern) - ✅ `lab infocheck`: `3 crate(s) clean, 0 with issues, 0 finding(s) total` - ✅ Smoke verified: server boots and binds rpc.sock, CLI search + workflow versions succeed through typed SDK, admin renders /workflows + /plays + /health through both typed-SDK and raw-rpc paths ## Recommended follow-ups (upstream) Three small hero_rpc generator fixes would eliminate the build.rs post-process hack in this PR: 1. Emit `info.title` without a colon (e.g. `LogicService` instead of `LogicService:logic`) so `openrpc_client!`'s identifier derivation accepts it. 2. Emit a `servers` block in `openrpc.json` for service projects so the macro's `connect()` resolves to the right Unix socket out of the box. 3. Have `generate_server_lib_rs` auto-discover hand-written sibling modules the way `generate_sdk_lib_rs` already does (via `//! @sdk-feature:` doc-comment markers), so consumers with extra modules like `engine`/`seed` don't need a build.rs post-process step to restore them after regen. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Phase 1–3 of the template-pattern migration. See PR description for details.

- New crates/hero_logic_types/ shared crate owns build.rs with the new
  OschemaBuildConfig shape (sdk_dir, client_crate_dir, server_crate_dir,
  server_types_crate, sdk_types_crate, with_wasm, with_python_sdk,
  with_rhai). Generated types live here, consumed by both _server and
  _sdk. Build.rs post-processes hero_logic_server's lib.rs to restore
  the hand-written engine/seed/services/tracing_layer modules the codegen
  would otherwise strip.
- Schemas moved to top-level schemas/logic/; docs to top-level docs/.
- hero_logic_server: main.rs migrated to HeroLifecycle + OServer::run_cli
  closure pattern. lib.rs becomes thin (re-exports the generated logic
  domain via hero_logic_types). src/logic/{core,server}/ flattened to
  src/logic/ to match the codegen's flat per-domain layout when
  server_crate_dir is set.
- hero_logic_sdk: deps swapped for hero_rpc_client + herolib_otoml +
  herolib_sid + base64. Old hand-written generated/ deleted in favor of
  codegen-emitted src/logic/{types,osis_client_generated,mod}.rs.
  decode_python_source preserved as src/python_source.rs (auto-discovered
  by the SDK builder and re-emitted into the auto-generated lib.rs).
- JS / Python SDK targets emit alongside Rust into top-level sdk/.

REMAINING:
- admin/src/rpc_client.rs deletion + routes.rs migration to typed
  wrappers; CLI migration too. Blocked on a hand-written LocalLogicClient
  that wraps OpenRpcTransport::unix_socket because OsisClient is HTTP-only
  (hero_rpc upstream gap — would need a new_unix() constructor for the
  one-line typed-client path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the template-pattern migration started in 8e98641. With the
codegen-emitted `LogicClient` (HTTP-only via OsisClient) unable to talk
to the local Unix socket, this PR keeps the OpenRPC macro path alive
under `hero_logic_sdk::local_client` so in-tree consumers (CLI + admin)
get a typed Unix-socket client, while the `LogicClient` typed wrapper
ships for browser/WASM consumers.

- New `crates/hero_logic_sdk/src/local_client.rs` — `openrpc_client!`
  macro wrapping the codegen-emitted `docs/logic/openrpc.json`. Exposes
  `LogicServiceClient` + the 19 `LogicService*Input` types. Auto-
  discovered by the SDK builder and preserved across regens.
- `hero_logic_types` build.rs post-process patches the generated
  openrpc.json's `info.title` from `LogicService:logic` → `LogicService`
  so the proc-macro's identifier derivation accepts it. Upstream fix
  belongs in hero_rpc_generator's openrpc emitter.
- Deleted `crates/hero_logic_admin/src/rpc_client.rs`. The `LogicRpcClient`
  raw-transport struct is replaced by a `AppState::rpc_call(method,
  params)` helper that routes raw calls through the typed SDK's
  underlying `OpenRpcTransport` (via `LogicServiceClient::transport()`).
  Same per-call reconnect semantics, no separate transport.
- `routes.rs`: all 17 `state.client.rpc_call(...)` callsites replaced
  with `state.rpc_call(...)`. `state.client` field removed from AppState.
- CLI (`hero_logic/src/main.rs`) imports `LogicServiceClient` from
  `hero_logic_sdk::local_client` instead of crate root.
- Admin `routes.rs` updates `hero_logic_sdk::decode_python_source` →
  `hero_logic_sdk::python_source::decode_python_source` to match the
  SDK builder's auto-discovery namespace.
- Added `rhai = []` feature to `hero_logic_types` to silence the
  check-cfg lint on codegen-emitted `#[cfg(feature = "rhai")]` gate.

Acceptance criteria met:
- `cargo build --workspace` clean (one unused-imports warning in
  codegen-emitted `types_generated.rs` — upstream concern, no errors).
- `lab infocheck`: 3 crate(s) clean, 0 with issues, 0 findings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smoke-testing the migration surfaced two gaps that prevented the
macro-derived `LogicServiceClient::connect()` from resolving the
Unix socket path:

1. The OSchema-generated `openrpc.json` omits a `servers` block, so
   the macro's default `connect()` falls through with an empty URL and
   fails at builder time. Patch the spec in hero_logic_types' build.rs
   to inject:

       "servers": [{"name": "hero_logic",
                    "url": "unix://${HERO_SOCKET_DIR}/hero_logic/rpc.sock"}]

   so the macro generates a `connect()` that resolves through
   `herolib_core::base::resolve_socket_path_with_override`. Upstream
   fix belongs in hero_rpc_generator's openrpc emitter (alongside the
   `info.title` colon fix from the prior commit).

2. Add `hero_logic_types` as a workspace dep on `hero_logic_sdk` so
   cargo serialises the SDK build after build.rs writes/patches the
   spec. Without this edge a clean parallel build races and the
   `openrpc_client!` macro reads a stale or pre-patch spec.

3. Add `herolib_core` to the SDK's deps — the macro now emits a call
   to `resolve_socket_path_with_override` so the crate has to resolve.

Smoke verified:
- `hero_logic_server` boots, registers logic domain, binds rpc.sock.
- `hero_logic search`, `hero_logic workflow versions <sid>` succeed
  end-to-end through the typed `LogicServiceClient`.
- `hero_logic play start` reaches the server (rejects at app-level
  with "Missing required input 'model'" — application logic, not a
  transport failure).
- `hero_logic_admin` boots, binds admin.sock, renders /health,
  /workflows, /plays.
- `AppState::rpc_call` raw-call helper (replacement for the deleted
  `LogicRpcClient`) routes successfully through the typed SDK's
  underlying `OpenRpcTransport`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
timur merged commit 29c10e653b into main 2026-05-20 09:35:45 +00:00
Sign in to join this conversation.
No reviewers
No labels
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_logic!46
No description provided.