Codegen alignment with current standards (service.toml, SDK, _admin, lab lifecycle) #55

Closed
opened 2026-05-18 12:37:09 +00:00 by timur · 14 comments
Owner

Context

hero_rpc's codegen predates several recent ecosystem changes. The generated output does not match what current Hero services need to compile and run cleanly. This issue tracks bringing the generator output up to current standards.

Reference services (pulled latest, development branch):

  • hero_proc — canonical wiring reference. hero_proc_server/src/main.rs uses service_base!(), validate_service_toml, handle_info_flag exactly as the skills mandate.
  • hero_compute — reference for workspace layout + hero_rpc OSchema deps. (Note: its _server/src/main.rs uses an older run/start/stop/status subcommand pattern with no service_base!() — do not mimic that.)

Generator output should let a service look like hero_proc without manual fix-ups.

Source-of-truth direction (flipped from initial draft)

service.toml is the single source of truth, written at scaffold time (#54). Codegen READS from it; it does NOT generate it.

scaffold time  →  writes service.toml (per crate)
build time     →  OschemaBuildConfig::from_service_toml() reads it
                  → drives codegen with service name, binaries, sockets, version

This way the user has a single place to specify what the service is. The codegen never overwrites service.toml.

Misalignments to fix

1. OschemaBuildConfig::from_service_toml() constructor

New entry point: read the embedded or sibling service.toml, deserialize as herolib_core::base::ServiceToml, derive service name / domain / output paths from it. Service-side build.rs collapses to:

fn main() {
    hero_rpc_osis::build::OschemaBuilder::from_service_toml().generate().unwrap();
}

(Depends on #56 for the modularization that enables ≤5-line build.rs.)

Codegen MUST NOT write service.toml — that file is owned by scaffold + the contributor.

2. Generate the SDK crate — decision pending: own codegen vs hero_rpc2

SDKs are hand-written in every Hero service today. Two paths:

Path A — Generate from OpenRPC ourselves. Continue using hero_rpc's existing JSON Schema / OpenRPC output to emit a hero_<name>_sdk crate with typed client methods (UDS + HTTP via herolib_core openrpc-transport feature). Pros: full control, reuses existing pipeline, same generator produces JS/TS + Rhai bindings. Cons: more code we maintain.

Path B — Adopt delandtj/hero_rpc2. Lean JSON-RPC framework on top of jsonrpsee 0.26 with Unix-socket transports and OpenRPC discovery built in. The #[rpc(server, client)] trait macro auto-generates the client alongside the server impl — no separate SDK codegen step. README explicitly positions it as a replacement for the heavier hero_rpc ("axum + tower-http + ACL + tenancy + OSchema codegen") for MOS daemons.

Aspect Path A (own codegen) Path B (hero_rpc2)
SDK generation Custom from OpenRPC Auto via #[rpc(client)]
Dep weight axum + tower-http + … jsonrpsee 0.26 only
Schema source .oschema files Rust traits + schemars
OSchema storage layer (OsisObject, DBTyped<T>, SmartID) Kept Lost (would need to keep on the side)
Multi-tenant X-Hero-Context routing Kept Not built-in
JS/TS + Rhai bindings codegen Kept Lost (no schema source for them)
Ecosystem cohesion One framework Two frameworks coexisting

Action items for this issue:

  • Read delandtj/hero_rpc2's docs/PRD.md and code, write a 1-page evaluation in this thread.
  • Decide: A, B, or a hybrid (e.g., use hero_rpc2 for the transport+client, keep OSchema codegen for everything else).
  • Whichever wins: produce the hero_<name>_sdk crate scaffold (from #54) using the chosen mechanism.

Do not unblock the SDK generation work until this evaluation is done.

3. Generate the _admin crate scaffold

Minimal admin binary using hero_website_lib (drop the hero_admin_lib alias — see hero_website_framework#5). Default screens come from the framework (hero_website_framework#4); this generator just wires the binary, sockets, and reads its service.toml.

Templating: Askama by default. Hero ecosystem default is compile-time Askama (matches hero_proc_admin). Tera is used only where runtime template loading is genuinely needed (user-editable content). Codegen emits Askama for the scaffolded _admin's own templates. hero_website_framework's existing Tera-based default screens stay Tera until/unless migrated separately (hero_website_framework#4).

4. Reconcile dual lifecycle paths

Today two abstractions coexist:

  • hero_rpc/crates/service/HeroLifecycle, HeroRpcServer, HeroUiServer (used by out-of-date hero_inspector).
  • herolib_core::base + service_base!() + lab lifecycle (used by current hero_proc, documented in hero_skills/skills/hero/service/).

Decision: delete hero_rpc/crates/service/. Generated code, the scaffolded main.rs, and docs reference herolib_core::base + lab only.

Action items:

  • Delete crates/service/ from this repo.
  • Remove hero_service = { ... } from workspace deps in hero_rpc/Cargo.toml and any downstream consumers — verify nothing in lhumina_code references the deleted crate (grep across all repos).
  • Update generated main.rs boilerplate accordingly (no HeroLifecycle::new(...)).

5. Update generated main.rs boilerplate

Must include, in this order, matching hero_proc_server/src/main.rs:

  1. service_base!() at module scope
  2. validate_service_toml(SERVICE_TOML) first in main
  3. handle_info_flag(SERVICE_TOML) second — unless the --info deprecation question (below) lands
  4. print_startup_banner(...) from herolib_core::base — no hand-rolled print_startup_info
  5. prepare_sockets(...) from herolib_core::base — no hand-rolled resolve_socket_dir

This is what lab infocheck validates (§0a of hero_service_check_fix).

5a. --info / --info --json — status check

@timur flagged that --info may have been deprecated. Current evidence says it is NOT deprecated:

  • All three skills in hero_skills/skills/hero/service/ mandate --info and --info --json (hero_service.md line 6, hero_service_toml_info.md headers "--info flag — mandatory for every Hero binary, no exceptions", hero_service_check_fix.md §0a).
  • 6 current hero_proc binaries call herolib_core::base::handle_info_flag.
  • lab infocheck literally executes <bin> --info --json and re-deserialises the output to validate it.

Action items before changing anything:

  • Confirm with Kristof whether deprecation is planned (and if so, what replaces it — lab service <name> --info?).
  • If deprecated, file a separate issue in hero_skills to remove --info requirements from all three service skills, file an issue in herolib_core to remove handle_info_flag, and propagate to current consumers (hero_proc, etc.) before this codegen drops --info.
  • Until confirmed: scaffolded main.rs keeps handle_info_flag.

6. Naming suffixes

Generator currently has no opinion. Enforce _server, _admin, _sdk, _web, _app per hero_service_check_fix §2 — never _ui, _cli, _cmd.

Acceptance

  • A service generated end-to-end (scaffold + codegen) passes lab infocheck clean with zero manual edits.
  • The generated _server binary --info --json deserialises into ServiceToml (unless --info is removed per §5a).
  • hero_rpc/crates/service/ is deleted; no lhumina_code repo references it.
  • The SDK generation path (A or B) is decided in-thread and produced output is integrated.
  • hero_inspector removed from any "reference" mentions in hero_rpc docs.
## Context hero_rpc's codegen predates several recent ecosystem changes. The generated output does not match what current Hero services need to compile and run cleanly. This issue tracks bringing the generator output up to current standards. Reference services (pulled latest, `development` branch): - [`hero_proc`](https://forge.ourworld.tf/lhumina_code/hero_proc) — canonical **wiring** reference. `hero_proc_server/src/main.rs` uses `service_base!()`, `validate_service_toml`, `handle_info_flag` exactly as the skills mandate. - [`hero_compute`](https://forge.ourworld.tf/lhumina_code/hero_compute) — reference for **workspace layout + hero_rpc OSchema deps**. (Note: its `_server/src/main.rs` uses an older `run/start/stop/status` subcommand pattern with no `service_base!()` — do not mimic that.) Generator output should let a service look like `hero_proc` without manual fix-ups. ## Source-of-truth direction (flipped from initial draft) **`service.toml` is the single source of truth, written at scaffold time** ([#54](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/54)). Codegen READS from it; it does NOT generate it. ``` scaffold time → writes service.toml (per crate) build time → OschemaBuildConfig::from_service_toml() reads it → drives codegen with service name, binaries, sockets, version ``` This way the user has a single place to specify what the service is. The codegen never overwrites `service.toml`. ## Misalignments to fix ### 1. `OschemaBuildConfig::from_service_toml()` constructor New entry point: read the embedded or sibling `service.toml`, deserialize as `herolib_core::base::ServiceToml`, derive service name / domain / output paths from it. Service-side `build.rs` collapses to: ```rust fn main() { hero_rpc_osis::build::OschemaBuilder::from_service_toml().generate().unwrap(); } ``` (Depends on [#56](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/56) for the modularization that enables ≤5-line `build.rs`.) Codegen MUST NOT write `service.toml` — that file is owned by scaffold + the contributor. ### 2. Generate the SDK crate — **decision pending: own codegen vs `hero_rpc2`** SDKs are hand-written in every Hero service today. Two paths: **Path A — Generate from OpenRPC ourselves.** Continue using hero_rpc's existing JSON Schema / OpenRPC output to emit a `hero_<name>_sdk` crate with typed client methods (UDS + HTTP via `herolib_core` `openrpc-transport` feature). Pros: full control, reuses existing pipeline, same generator produces JS/TS + Rhai bindings. Cons: more code we maintain. **Path B — Adopt [`delandtj/hero_rpc2`](https://forge.ourworld.tf/delandtj/hero_rpc2).** Lean JSON-RPC framework on top of `jsonrpsee` 0.26 with Unix-socket transports and OpenRPC discovery built in. The `#[rpc(server, client)]` trait macro auto-generates the client alongside the server impl — no separate SDK codegen step. README explicitly positions it as a replacement for the heavier hero_rpc ("axum + tower-http + ACL + tenancy + OSchema codegen") for MOS daemons. | Aspect | Path A (own codegen) | Path B (hero_rpc2) | |---|---|---| | SDK generation | Custom from OpenRPC | Auto via `#[rpc(client)]` | | Dep weight | axum + tower-http + … | jsonrpsee 0.26 only | | Schema source | `.oschema` files | Rust traits + schemars | | OSchema storage layer (`OsisObject`, `DBTyped<T>`, SmartID) | Kept | Lost (would need to keep on the side) | | Multi-tenant `X-Hero-Context` routing | Kept | Not built-in | | JS/TS + Rhai bindings codegen | Kept | Lost (no schema source for them) | | Ecosystem cohesion | One framework | Two frameworks coexisting | **Action items for this issue:** - [ ] Read `delandtj/hero_rpc2`'s `docs/PRD.md` and code, write a 1-page evaluation in this thread. - [ ] Decide: A, B, or a hybrid (e.g., use hero_rpc2 for the transport+client, keep OSchema codegen for everything else). - [ ] Whichever wins: produce the `hero_<name>_sdk` crate scaffold (from #54) using the chosen mechanism. Do not unblock the SDK generation work until this evaluation is done. ### 3. Generate the `_admin` crate scaffold Minimal admin binary using `hero_website_lib` (drop the `hero_admin_lib` alias — see [hero_website_framework#5](https://forge.ourworld.tf/lhumina_code/hero_website_framework/issues/5)). Default screens come from the framework ([hero_website_framework#4](https://forge.ourworld.tf/lhumina_code/hero_website_framework/issues/4)); this generator just wires the binary, sockets, and reads its `service.toml`. **Templating: Askama by default.** Hero ecosystem default is compile-time Askama (matches `hero_proc_admin`). Tera is used only where runtime template loading is genuinely needed (user-editable content). Codegen emits Askama for the scaffolded `_admin`'s own templates. `hero_website_framework`'s existing Tera-based default screens stay Tera until/unless migrated separately ([hero_website_framework#4](https://forge.ourworld.tf/lhumina_code/hero_website_framework/issues/4)). ### 4. Reconcile dual lifecycle paths Today two abstractions coexist: - `hero_rpc/crates/service/` — `HeroLifecycle`, `HeroRpcServer`, `HeroUiServer` (used by out-of-date `hero_inspector`). - `herolib_core::base` + `service_base!()` + `lab` lifecycle (used by current `hero_proc`, documented in `hero_skills/skills/hero/service/`). **Decision: delete `hero_rpc/crates/service/`.** Generated code, the scaffolded `main.rs`, and docs reference `herolib_core::base` + `lab` only. Action items: - [ ] Delete `crates/service/` from this repo. - [ ] Remove `hero_service = { ... }` from workspace deps in `hero_rpc/Cargo.toml` and any downstream consumers — verify nothing in lhumina_code references the deleted crate (grep across all repos). - [ ] Update generated `main.rs` boilerplate accordingly (no `HeroLifecycle::new(...)`). ### 5. Update generated `main.rs` boilerplate Must include, in this order, matching [`hero_proc_server/src/main.rs`](https://forge.ourworld.tf/lhumina_code/hero_proc/src/branch/development/crates/hero_proc_server/src/main.rs): 1. `service_base!()` at module scope 2. `validate_service_toml(SERVICE_TOML)` first in `main` 3. `handle_info_flag(SERVICE_TOML)` second — **unless** the `--info` deprecation question (below) lands 4. `print_startup_banner(...)` from `herolib_core::base` — no hand-rolled `print_startup_info` 5. `prepare_sockets(...)` from `herolib_core::base` — no hand-rolled `resolve_socket_dir` This is what `lab infocheck` validates (§0a of `hero_service_check_fix`). ### 5a. `--info` / `--info --json` — status check @timur flagged that `--info` may have been deprecated. **Current evidence says it is NOT deprecated:** - All three skills in [`hero_skills/skills/hero/service/`](https://forge.ourworld.tf/lhumina_code/hero_skills/src/branch/development/skills/hero/service) mandate `--info` and `--info --json` (`hero_service.md` line 6, `hero_service_toml_info.md` headers "`--info` flag — **mandatory for every Hero binary, no exceptions**", `hero_service_check_fix.md` §0a). - 6 current `hero_proc` binaries call `herolib_core::base::handle_info_flag`. - `lab infocheck` literally executes `<bin> --info --json` and re-deserialises the output to validate it. Action items before changing anything: - [ ] Confirm with Kristof whether deprecation is planned (and if so, what replaces it — `lab service <name> --info`?). - [ ] If deprecated, file a separate issue in `hero_skills` to remove `--info` requirements from all three service skills, file an issue in `herolib_core` to remove `handle_info_flag`, and propagate to current consumers (hero_proc, etc.) before this codegen drops `--info`. - [ ] Until confirmed: scaffolded `main.rs` keeps `handle_info_flag`. ### 6. Naming suffixes Generator currently has no opinion. Enforce `_server`, `_admin`, `_sdk`, `_web`, `_app` per `hero_service_check_fix` §2 — never `_ui`, `_cli`, `_cmd`. ## Acceptance - A service generated end-to-end (scaffold + codegen) passes `lab infocheck` clean with **zero** manual edits. - The generated `_server` binary `--info --json` deserialises into `ServiceToml` (unless `--info` is removed per §5a). - `hero_rpc/crates/service/` is deleted; no lhumina_code repo references it. - The SDK generation path (A or B) is decided in-thread and produced output is integrated. - `hero_inspector` removed from any "reference" mentions in hero_rpc docs. ## Related - [#54](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/54) — Scaffold (owns `service.toml` creation). - [#56](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/56) — Modularize `generator/src/build/build.rs` (enables the ≤5-line `build.rs` shape). - [#57](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/57) — Upgrade `example/recipe_server` (consumes this). - [hero_website_framework#4](https://forge.ourworld.tf/lhumina_code/hero_website_framework/issues/4) — Reusable admin components. - [hero_website_framework#5](https://forge.ourworld.tf/lhumina_code/hero_website_framework/issues/5) — Drop `hero_admin_lib` alias. - [hero_skills#260](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/260) — Scaffold/check/refactor context skills. - [`delandtj/hero_rpc2`](https://forge.ourworld.tf/delandtj/hero_rpc2) — SDK-generation alternative being evaluated. - Reference repos: [`hero_proc`](https://forge.ourworld.tf/lhumina_code/hero_proc) (wiring), [`hero_compute`](https://forge.ourworld.tf/lhumina_code/hero_compute) (layout).
Author
Owner

well, service.toml is more of a part of the scaffold then generation. in fact, oschema build config can use service name etc from there instead of other way around so we have a single place to specify our service. For the sdk crate lets explore the possibility of using https://forge.ourworld.tf/delandtj/hero_rpc2 as well and how that might effect our services and pros and cons. for 3. again lets link the mentioned issue. 4) yes delete hero_rpc/crates/service/. --info and --json was deprecated to my knowledge. lets check on that and if that is the case lets also remove it from whatever gave the impression if was necessary in hero_skills. only service.toml is for service info. edit description to link related issues and repos with actual links

well, service.toml is more of a part of the scaffold then generation. in fact, oschema build config can use service name etc from there instead of other way around so we have a single place to specify our service. For the sdk crate lets explore the possibility of using https://forge.ourworld.tf/delandtj/hero_rpc2 as well and how that might effect our services and pros and cons. for 3. again lets link the mentioned issue. 4) yes delete hero_rpc/crates/service/. --info and --json was deprecated to my knowledge. lets check on that and if that is the case lets also remove it from whatever gave the impression if was necessary in hero_skills. only service.toml is for service info. edit description to link related issues and repos with actual links
Author
Owner

lets use hybrid approach for now, hero_rpc2 stuff for client and transport etc. lets move hero_rpc2 into hero_rpc as a crate. We still want schema source to be oschema files, so perhaps those are used to create code that has the traits that hero_rpc2 uses to create the server client etc. we also still want OSchema storage layer. we still want js/ts + rhai bindings, in fact even python generated in sdk, but rust client can come from hero_rpc2 via macros and such. Multi-tenant X-Hero-Context routing requires more explaining, but this is how i would look at it: hero_rpc2 is less opinionated, we can have it extended to carry certain headers from http calls into the context of a call and have it pass that to the handler as an optional field such that it can work without context as well. So then x-hero-context id can be carried in a context struct into the handled rpc call, and stuff that uses the OSIS storage for instance then can store / operate in the db of the hero-context id provided in the transport context. Checking on 5.

lets use hybrid approach for now, hero_rpc2 stuff for client and transport etc. lets move hero_rpc2 into hero_rpc as a crate. We still want schema source to be oschema files, so perhaps those are used to create code that has the traits that hero_rpc2 uses to create the server client etc. we also still want OSchema storage layer. we still want js/ts + rhai bindings, in fact even python generated in sdk, but rust client can come from hero_rpc2 via macros and such. Multi-tenant X-Hero-Context routing requires more explaining, but this is how i would look at it: hero_rpc2 is less opinionated, we can have it extended to carry certain headers from http calls into the context of a call and have it pass that to the handler as an optional field such that it can work without context as well. So then x-hero-context id can be carried in a context struct into the handled rpc call, and stuff that uses the OSIS storage for instance then can store / operate in the db of the hero-context id provided in the transport context. Checking on 5.
Author
Owner

Confirmed — hybrid (Path C). Working sketch of how the pieces fit:

Architecture

.oschema file
     │
     ▼
┌────────────────────────────────────────────────────────────┐
│ hero_rpc codegen (OSchema-driven, our own)                 │
├────────────────────────────────────────────────────────────┤
│ • Rust types (#[derive(Serialize, Deserialize)])           │
│ • Rust trait annotated with hero_rpc2's #[rpc(server,     │
│   client)] — server impl stub + client auto-generated      │
│ • OSIS storage layer (OsisObject, DBTyped<T>, SmartID)     │
│ • OpenRPC spec (already produced)                          │
│ • JS/TS SDK                                                │
│ • Rhai bindings                                            │
│ • Python SDK (NEW — please confirm in scope)               │
└────────────────────────────────────────────────────────────┘
     │
     ▼
┌────────────────────────────────────────────────────────────┐
│ hero_rpc2 (vendored as crates/hero_rpc2/ in this repo)     │
├────────────────────────────────────────────────────────────┤
│ • ServerBuilder (jsonrpsee + UDS line/http transports)     │
│ • Auto-generated client from #[rpc(client)]                │
│ • OpenRPC discover endpoint                                │
│ • NEW: header-to-context lift (see below)                  │
└────────────────────────────────────────────────────────────┘

OSchema stays the schema source of truth. hero_rpc2 owns the wire protocol + transport + Rust client. OSIS storage, JS/TS, Rhai, and Python all keep coming from our OSchema codegen.

Action items

  • Vendor hero_rpc2 into hero_rpc/crates/hero_rpc2/. Coordinate with @delandtj — is the upstream delandtj/hero_rpc2 repo still active, or do we take ownership? Either way, we'll need to fork or copy. Once vendored, update workspace deps + this issue's references.
  • Codegen produces hero_rpc2-compatible traits. OSchema methods → Rust trait with #[rpc(server, client)] from hero_rpc2::prelude (matches the hero_rpc2 README example). Trait impl stub for the contributor lives in a preserved file (src/<domain>/server/rpc.rs) like today.
  • Add Python SDK target to the OSchema generator (new — confirm in scope; previously only JS/TS + Rhai).
  • Extend hero_rpc2 with header-lift context support (details below).
  • Delete hero_rpc/crates/service/ (already agreed; still ours to do).
  • OSchema storage layer (OsisObject, DBTyped<T>, SmartID) stays as-is — codegen still emits it.

X-Hero-Context proposal — extending hero_rpc2

Following your sketch: hero_rpc2 is unopinionated about per-request context, so we add it as an opt-in extension. Strawman API:

// At server-builder time, declare which headers to lift.
let server = ServerBuilder::new(module)
    .with_request_context::<HeroRequestContext>()  // maps configured headers
    .with_lifted_headers(["X-Hero-Context", "X-Hero-Claims"])
    .serve_http(sock).await?;

// Generated trait carries the optional context as the first arg.
#[rpc(server, client)]
pub trait Recipes {
    #[method(name = "recipe.get")]
    async fn get(&self, ctx: Option<HeroRequestContext>, id: SmartId) 
        -> RpcResult<Recipe>;
}

// Handler uses ctx to scope OSIS storage to the tenant.
#[async_trait]
impl RecipesServer for RecipesImpl {
    async fn get(&self, ctx: Option<HeroRequestContext>, id: SmartId) 
        -> RpcResult<Recipe> 
    {
        let tenant = ctx.as_ref().and_then(|c| c.hero_context()).unwrap_or("default");
        let db = self.db.scope(tenant);
        Ok(db.get(&id)?)
    }
}

Design notes:

  • HeroRequestContext is a typed struct generated from a config (which headers, types, defaults).
  • Option<...> lets the same trait work over the line transport (no headers) and HTTP transport (with headers).
  • For UDS line transport, hero_rpc2 emits a synthesised default context (or None), so the handler signature stays the same.
  • OSIS storage gets a .scope(context) method to namespace by X-Hero-Context — replacing today's implicit per-socket multiplexing.
  • Claims (X-Hero-Claims) ride the same mechanism for the openrpc_authorize layer.

Open questions on this:

  1. Should the context arg be Option<HeroRequestContext> (works without context) or always-present-but-defaulted (HeroRequestContext with is_anonymous())? I lean toward always-present-defaulted — simpler trait, no unwrap_or boilerplate everywhere.
  2. Does hero_rpc2's upstream want this extension as a contribution, or is it a fork-and-extend in our crate?
  3. Does lab need to know about this (e.g., to route specific contexts to specific socket dirs)?

Still pending

  • #55 §5a --info deprecation check — your investigation underway.
  • Will draft the hero_rpc2 evaluation as a separate doc once vendored + once the context-extension shape is confirmed.

Will update the issue body with this hybrid decision once the open questions above settle — keeping the body frozen until then so the comment thread tracks the iteration.

Confirmed — hybrid (Path C). Working sketch of how the pieces fit: ## Architecture ``` .oschema file │ ▼ ┌────────────────────────────────────────────────────────────┐ │ hero_rpc codegen (OSchema-driven, our own) │ ├────────────────────────────────────────────────────────────┤ │ • Rust types (#[derive(Serialize, Deserialize)]) │ │ • Rust trait annotated with hero_rpc2's #[rpc(server, │ │ client)] — server impl stub + client auto-generated │ │ • OSIS storage layer (OsisObject, DBTyped<T>, SmartID) │ │ • OpenRPC spec (already produced) │ │ • JS/TS SDK │ │ • Rhai bindings │ │ • Python SDK (NEW — please confirm in scope) │ └────────────────────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────────────────────┐ │ hero_rpc2 (vendored as crates/hero_rpc2/ in this repo) │ ├────────────────────────────────────────────────────────────┤ │ • ServerBuilder (jsonrpsee + UDS line/http transports) │ │ • Auto-generated client from #[rpc(client)] │ │ • OpenRPC discover endpoint │ │ • NEW: header-to-context lift (see below) │ └────────────────────────────────────────────────────────────┘ ``` OSchema stays the schema source of truth. hero_rpc2 owns the wire protocol + transport + Rust client. OSIS storage, JS/TS, Rhai, and Python all keep coming from our OSchema codegen. ## Action items - [ ] **Vendor hero_rpc2 into `hero_rpc/crates/hero_rpc2/`.** Coordinate with @delandtj — is the upstream `delandtj/hero_rpc2` repo still active, or do we take ownership? Either way, we'll need to fork or copy. Once vendored, update workspace deps + this issue's references. - [ ] **Codegen produces hero_rpc2-compatible traits.** OSchema methods → Rust trait with `#[rpc(server, client)]` from `hero_rpc2::prelude` (matches the hero_rpc2 README example). Trait impl stub for the contributor lives in a preserved file (`src/<domain>/server/rpc.rs`) like today. - [ ] **Add Python SDK target** to the OSchema generator (new — confirm in scope; previously only JS/TS + Rhai). - [ ] **Extend hero_rpc2 with header-lift context support** (details below). - [ ] **Delete `hero_rpc/crates/service/`** (already agreed; still ours to do). - [ ] OSchema storage layer (`OsisObject`, `DBTyped<T>`, SmartID) stays as-is — codegen still emits it. ## X-Hero-Context proposal — extending hero_rpc2 Following your sketch: hero_rpc2 is unopinionated about per-request context, so we add it as an opt-in extension. Strawman API: ```rust // At server-builder time, declare which headers to lift. let server = ServerBuilder::new(module) .with_request_context::<HeroRequestContext>() // maps configured headers .with_lifted_headers(["X-Hero-Context", "X-Hero-Claims"]) .serve_http(sock).await?; // Generated trait carries the optional context as the first arg. #[rpc(server, client)] pub trait Recipes { #[method(name = "recipe.get")] async fn get(&self, ctx: Option<HeroRequestContext>, id: SmartId) -> RpcResult<Recipe>; } // Handler uses ctx to scope OSIS storage to the tenant. #[async_trait] impl RecipesServer for RecipesImpl { async fn get(&self, ctx: Option<HeroRequestContext>, id: SmartId) -> RpcResult<Recipe> { let tenant = ctx.as_ref().and_then(|c| c.hero_context()).unwrap_or("default"); let db = self.db.scope(tenant); Ok(db.get(&id)?) } } ``` Design notes: - `HeroRequestContext` is a typed struct generated from a config (which headers, types, defaults). - `Option<...>` lets the same trait work over the line transport (no headers) and HTTP transport (with headers). - For UDS line transport, hero_rpc2 emits a synthesised default context (or `None`), so the handler signature stays the same. - OSIS storage gets a `.scope(context)` method to namespace by `X-Hero-Context` — replacing today's implicit per-socket multiplexing. - Claims (`X-Hero-Claims`) ride the same mechanism for the openrpc_authorize layer. **Open questions on this:** 1. Should the context arg be `Option<HeroRequestContext>` (works without context) or always-present-but-defaulted (`HeroRequestContext` with `is_anonymous()`)? I lean toward always-present-defaulted — simpler trait, no `unwrap_or` boilerplate everywhere. 2. Does hero_rpc2's upstream want this extension as a contribution, or is it a fork-and-extend in our crate? 3. Does `lab` need to know about this (e.g., to route specific contexts to specific socket dirs)? ## Still pending - [#55 §5a](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55) `--info` deprecation check — your investigation underway. - Will draft the hero_rpc2 evaluation as a separate doc once vendored + once the context-extension shape is confirmed. Will update the issue body with this hybrid decision once the open questions above settle — keeping the body frozen until then so the comment thread tracks the iteration.
Author
Owner

—info just gives the toml, so not deprecated. Lets keep HeroReqCtx option, for now its a fork and extend (but just copy over dont actually fork). there is no longer ever a case where contexts are routed to specific context socket dirs, and any mention should be removed.

—info just gives the toml, so not deprecated. Lets keep HeroReqCtx option, for now its a fork and extend (but just copy over dont actually fork). there is no longer ever a case where contexts are routed to specific context socket dirs, and any mention should be removed.
Author
Owner

Locked in:

  • --info stays — confirmed it just prints the embedded service.toml, not deprecated. §5a will be removed in the consolidation pass.
  • Option<HeroRequestContext> stays as the trait-arg shape (over the always-defaulted alternative).
  • hero_rpc2 vendor strategy: copy the code into hero_rpc/crates/hero_rpc2/, no fork relationship, no upstream PR. We treat it as ours from that point.
  • Python SDK confirmed new + in scope. Added as a fourth target alongside JS/TS and Rhai in the OSchema codegen.
  • No per-context socket dir routing anywhere. Single socket per service, context via X-Hero-Context header — period.

Cleanup needed in this repo

Grep turned up legacy socket-per-context surface that should go:

Path What's there
crates/osis/src/rpc/unix_server.rs:32 Doc comment: Binds one socket per context.
crates/server/src/server/unified_server.rs:58 Per-context dispatch functions (for service method fallback).
crates/server/README.md Lines 10 + 18 describe socket-per-domain as a "legacy architecture" alongside the unified mode.

Action: delete the legacy unix_server code path (and its docs) entirely once the hero_rpc2-backed server lands. README references go too. No "legacy mode" / "socket-per-domain" framing — just the one single-socket model.

I'll roll this into the §5a removal during the consolidation pass on the body once the remaining open items settle. Sweep across other lhumina_code repos for the same wording is a follow-up — agent can do it standalone.

Still open (carried forward)

  • hero_rpc2 vendor PR — needs the actual copy + workspace member add.
  • _admin crate scaffold output shape (mostly settled — Askama, framework consumption pattern).
  • SDK output crate layout per target (Rust client from hero_rpc2 macros; JS/TS/Rhai/Python crates emit where?).
Locked in: - **`--info`** stays — confirmed it just prints the embedded `service.toml`, not deprecated. §5a will be removed in the consolidation pass. - **`Option<HeroRequestContext>`** stays as the trait-arg shape (over the always-defaulted alternative). - **hero_rpc2 vendor strategy:** copy the code into `hero_rpc/crates/hero_rpc2/`, no fork relationship, no upstream PR. We treat it as ours from that point. - **Python SDK** confirmed new + in scope. Added as a fourth target alongside JS/TS and Rhai in the OSchema codegen. - **No per-context socket dir routing anywhere.** Single socket per service, context via `X-Hero-Context` header — period. ## Cleanup needed in this repo Grep turned up legacy socket-per-context surface that should go: | Path | What's there | |---|---| | [`crates/osis/src/rpc/unix_server.rs:32`](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/osis/src/rpc/unix_server.rs#L32) | Doc comment: `Binds one socket per context.` | | [`crates/server/src/server/unified_server.rs:58`](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/unified_server.rs#L58) | `Per-context dispatch functions (for service method fallback).` | | [`crates/server/README.md`](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/README.md) | Lines 10 + 18 describe socket-per-domain as a "legacy architecture" alongside the unified mode. | Action: delete the legacy `unix_server` code path (and its docs) entirely once the hero_rpc2-backed server lands. README references go too. No "legacy mode" / "socket-per-domain" framing — just the one single-socket model. I'll roll this into the §5a removal during the consolidation pass on the body once the remaining open items settle. Sweep across other lhumina_code repos for the same wording is a follow-up — agent can do it standalone. ## Still open (carried forward) - hero_rpc2 vendor PR — needs the actual copy + workspace member add. - `_admin` crate scaffold output shape (mostly settled — Askama, framework consumption pattern). - SDK output crate layout per target (Rust client from hero_rpc2 macros; JS/TS/Rhai/Python crates emit where?).
Author
Owner

Parent / context tracker: hero_skills#262 — read it before starting work on this issue. Locked decisions, reference materials, and execution order live there. Iterate via comments here; consolidation passes on the body only after feedback settles.

Parent / context tracker: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262) — read it before starting work on this issue. Locked decisions, reference materials, and execution order live there. Iterate via comments here; consolidation passes on the body only after feedback settles.
Author
Owner

State refresh + action correction

Pulled latest development. Several relevant deltas since I drafted this:

  1. crates/osis/src/rpc/server.rs was reduced by 755 lines in recent commits — legacy socket-per-context surface is partly already gone. unix_server.rs still exists in crates/osis/src/rpc/; verify what role it plays now before deleting.
  2. crates/service/ is being actively modified (recent commits touched hero_server.rs, lifecycle.rs, service.rs, lib.rs, cli.rs, plus a new bin/ and test.rs). So the earlier decision to delete the crate (comment 33659) is wrong — code is still landing there.
  3. #261 Decision B is locked (per latest user message): name the template repo hero_service, rename this crate to free the name. So the action becomes a rename, not a delete:

Corrected action for §4 ("Reconcile dual lifecycle paths")

  • Delete hero_rpc/crates/service/
  • Rename hero_rpc/crates/service/hero_rpc/crates/hero_lifecycle/ and update the package name = "hero_service"name = "hero_lifecycle". Update all [workspace.dependencies] references in this repo + every downstream consumer (grep hero_service = across lhumina_code repos).
  • The functional reconciliation question (HeroLifecycle vs herolib_core::base + lab) is still open — recent activity in crates/service/ suggests this crate is the path forward, not a dead end. Generated main.rs should still use service_base!() + validate_service_toml + handle_info_flag from herolib_core::base (per hero_proc_server reference), and call into the renamed hero_lifecycle only for the hero_proc registration glue.

Other §4–§5a status

  • §5a (--info): resolved — not deprecated. Comment 33681 confirmed it just prints the embedded TOML. Will drop §5a in the body consolidation pass.
  • §1 (service.toml source-of-truth flip): still pending implementation, not yet in dev.
  • §2 (hero_rpc2 vendor): not yet in dev.
  • §3 (_admin crate scaffold): not yet — scaffolder still emits _ui. Tracked in #54.

Body consolidation pass to happen once these corrections settle.

## State refresh + action correction Pulled latest `development`. Several relevant deltas since I drafted this: 1. **`crates/osis/src/rpc/server.rs` was reduced by 755 lines** in recent commits — legacy socket-per-context surface is partly already gone. `unix_server.rs` still exists in `crates/osis/src/rpc/`; verify what role it plays now before deleting. 2. **`crates/service/` is being actively modified** (recent commits touched `hero_server.rs`, `lifecycle.rs`, `service.rs`, `lib.rs`, `cli.rs`, plus a new `bin/` and `test.rs`). So the earlier decision to **delete** the crate ([comment 33659](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55#issuecomment-33659)) is wrong — code is still landing there. 3. **#261 Decision B is locked** (per latest user message): name the template repo `hero_service`, rename this crate to free the name. So the action becomes a **rename**, not a delete: ### Corrected action for §4 ("Reconcile dual lifecycle paths") - ~~Delete `hero_rpc/crates/service/`~~ - **Rename `hero_rpc/crates/service/` → `hero_rpc/crates/hero_lifecycle/`** and update the package `name = "hero_service"` → `name = "hero_lifecycle"`. Update all `[workspace.dependencies]` references in this repo + every downstream consumer (grep `hero_service = ` across `lhumina_code` repos). - The functional reconciliation question (HeroLifecycle vs herolib_core::base + lab) is still open — recent activity in `crates/service/` suggests this crate is the path forward, not a dead end. Generated `main.rs` should still use `service_base!()` + `validate_service_toml` + `handle_info_flag` from `herolib_core::base` (per `hero_proc_server` reference), and call into the renamed `hero_lifecycle` only for the hero_proc registration glue. ### Other §4–§5a status - §5a (`--info`): **resolved — not deprecated.** [Comment 33681](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55#issuecomment-33681) confirmed it just prints the embedded TOML. Will drop §5a in the body consolidation pass. - §1 (`service.toml` source-of-truth flip): still pending implementation, not yet in dev. - §2 (hero_rpc2 vendor): not yet in dev. - §3 (`_admin` crate scaffold): not yet — scaffolder still emits `_ui`. Tracked in [#54](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/54). Body consolidation pass to happen once these corrections settle.
Author
Owner

§4 rename — landed on issue-55-codegen-alignment

aad9f81crates/service/crates/hero_lifecycle/, package hero_servicehero_lifecycle. Workspace cargo check clean.

In-repo touches: workspace Cargo.toml, the renamed crate's own sources (Cargo.toml + lib/hero_server/service/test doc-comments, src/bin/main.rs binary name + clap config, examples/test_server.rs), crates/server/ (Cargo.toml + lib.rs re-export + cli/server/spawn use-sites + README), and the generator emitters that hand hero_service down to scaffolded/generated code (emit/bin.rs, scaffold.rs).

No behavioural change. The Cargo.toml.hero_builder_backup files (legacy build_lib leftovers) were left untouched.

Downstream consumer bumps next (per q3a in the thread): walking through hero_osis, hero_inspector, hero_proxy, hero_fossil, hero_archipelagos (and grep for any I missed) — one commit per repo on a matching branch.

Then §1 (OschemaBuildConfig::from_service_toml()), §5+§6 (generated main.rs boilerplate matching hero_proc_server + naming-suffix enforcement), §3 (_admin scaffold), and §2 (hero_rpc2 vendor + hybrid SDK — likely splits into its own PR given size).

## §4 rename — landed on `issue-55-codegen-alignment` [`aad9f81`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/aad9f81) — `crates/service/` → `crates/hero_lifecycle/`, package `hero_service` → `hero_lifecycle`. Workspace `cargo check` clean. **In-repo touches:** workspace `Cargo.toml`, the renamed crate's own sources (Cargo.toml + lib/hero_server/service/test doc-comments, `src/bin/main.rs` binary name + clap config, `examples/test_server.rs`), `crates/server/` (Cargo.toml + lib.rs re-export + cli/server/spawn use-sites + README), and the generator emitters that hand `hero_service` down to scaffolded/generated code (`emit/bin.rs`, `scaffold.rs`). No behavioural change. The `Cargo.toml.hero_builder_backup` files (legacy build_lib leftovers) were left untouched. **Downstream consumer bumps next** (per q3a in the thread): walking through `hero_osis`, `hero_inspector`, `hero_proxy`, `hero_fossil`, `hero_archipelagos` (and grep for any I missed) — one commit per repo on a matching branch. Then §1 (`OschemaBuildConfig::from_service_toml()`), §5+§6 (generated `main.rs` boilerplate matching `hero_proc_server` + naming-suffix enforcement), §3 (`_admin` scaffold), and §2 (`hero_rpc2` vendor + hybrid SDK — likely splits into its own PR given size).
Author
Owner

§4 — downstream sweep done (7 repos)

Branch issue-55-hero-lifecycle-rename pushed on each affected repo; workspace-root hero_lifecycle dep is temporarily pinned to branch = "issue-55-codegen-alignment" of hero_rpc. After the hero_rpc PR squash-merges, a one-line follow-up per repo flips the pin back to branch = "development".

Repo Commit Files
hero_proxy 66e55675 workspace Cargo.toml
hero_osis 13add9c2 workspace + 2 sub-crate Cargo.toml + hero_osis_admin/src/main.rs
hero_index_ui_old bca62d30 Cargo.toml + src/main.rs
hero_compute be0c2b37 workspace Cargo.tomlcargo check --workspace clean against issue branch
hero_os f914378d + 0467ab63 workspace + sub-crate Cargo.toml + hero_os_server/src/main.rs; second commit reverts an over-eager string-literal rewrite in island_content.rs where "hero_service" is a Dioxus island ID matched against the hero_archipelagos_hero_service crate, not our renamed dep
hero_router 842eec55 workspace Cargo.toml
hero_voice 7d4fed85 workspace + sub-crate Cargo.toml + hero_voice/src/lifecycle.rs

Skipped (upstream redirects revealed stale local clones)

Local Upstream redirect Reason
hero_inspector hero_router merged into hero_router
hero_fossil hero_foundry renamed
hero_auth hero_auth_archive archived
hero_indexer_ui hero_index_ui_old duplicate of the _old clone we processed
hero_foundry (active) crates/hero_foundry_server/Cargo.toml no longer has a hero_service dep on latest dev — already migrated in commit e6d17e3 (D-10 sweep). No-op.

Side branches archived

Three repos had uncommitted local changes; archived to development_wip on the respective remotes before sweeping: hero_os, hero_voice, hero_foundry.

Follow-up after merge

One-liner per repo: perl -pi -e 's|branch = "issue-55-codegen-alignment"|branch = "development"|' Cargo.toml, commit, push. I'll bundle into a small "flip pins" PR once §1/§5/§6/§3 ship and the hero_rpc PR is approved.

Next

→ §1 OschemaBuildConfig::from_service_toml() constructor (additive; reduces recipe_server/build.rs from 41 lines to ≤5).

## §4 — downstream sweep done (7 repos) Branch `issue-55-hero-lifecycle-rename` pushed on each affected repo; workspace-root `hero_lifecycle` dep is temporarily pinned to `branch = "issue-55-codegen-alignment"` of hero_rpc. After the hero_rpc PR squash-merges, a one-line follow-up per repo flips the pin back to `branch = "development"`. | Repo | Commit | Files | |---|---|---| | `hero_proxy` | [`66e55675`](https://forge.ourworld.tf/lhumina_code/hero_proxy/commit/66e55675) | workspace `Cargo.toml` | | `hero_osis` | [`13add9c2`](https://forge.ourworld.tf/lhumina_code/hero_osis/commit/13add9c2) | workspace + 2 sub-crate Cargo.toml + `hero_osis_admin/src/main.rs` | | `hero_index_ui_old` | [`bca62d30`](https://forge.ourworld.tf/lhumina_code/hero_index_ui_old/commit/bca62d30) | Cargo.toml + `src/main.rs` | | `hero_compute` | [`be0c2b37`](https://forge.ourworld.tf/lhumina_code/hero_compute/commit/be0c2b37) | workspace `Cargo.toml` — `cargo check --workspace` clean against issue branch | | `hero_os` | [`f914378d`](https://forge.ourworld.tf/lhumina_code/hero_os/commit/f914378d) + [`0467ab63`](https://forge.ourworld.tf/lhumina_code/hero_os/commit/0467ab63) | workspace + sub-crate Cargo.toml + `hero_os_server/src/main.rs`; second commit reverts an over-eager string-literal rewrite in `island_content.rs` where `"hero_service"` is a Dioxus island ID matched against the `hero_archipelagos_hero_service` crate, not our renamed dep | | `hero_router` | [`842eec55`](https://forge.ourworld.tf/lhumina_code/hero_router/commit/842eec55) | workspace `Cargo.toml` | | `hero_voice` | [`7d4fed85`](https://forge.ourworld.tf/lhumina_code/hero_voice/commit/7d4fed85) | workspace + sub-crate Cargo.toml + `hero_voice/src/lifecycle.rs` | ### Skipped (upstream redirects revealed stale local clones) | Local | Upstream redirect | Reason | |---|---|---| | `hero_inspector` | → `hero_router` | merged into hero_router | | `hero_fossil` | → `hero_foundry` | renamed | | `hero_auth` | → `hero_auth_archive` | archived | | `hero_indexer_ui` | → `hero_index_ui_old` | duplicate of the `_old` clone we processed | | `hero_foundry` | (active) | `crates/hero_foundry_server/Cargo.toml` no longer has a `hero_service` dep on latest dev — already migrated in commit `e6d17e3` (D-10 sweep). No-op. | ### Side branches archived Three repos had uncommitted local changes; archived to `development_wip` on the respective remotes before sweeping: `hero_os`, `hero_voice`, `hero_foundry`. ### Follow-up after merge One-liner per repo: `perl -pi -e 's|branch = "issue-55-codegen-alignment"|branch = "development"|' Cargo.toml`, commit, push. I'll bundle into a small "flip pins" PR once §1/§5/§6/§3 ship and the hero_rpc PR is approved. ### Next → §1 `OschemaBuildConfig::from_service_toml()` constructor (additive; reduces `recipe_server/build.rs` from 41 lines to ≤5).
Author
Owner

§1 landed — 6174a00

service.toml is now codegen's single source of truth. Two new entry points:

OschemaBuildConfig::from_service_toml()           // reads $CARGO_MANIFEST_DIR/service.toml
OschemaBuildConfig::from_service_toml_at(path)    // explicit-path variant
OschemaBuilder::from_service_toml()               // convenience: wraps the config

Both call herolib_core::base::validate_service_toml (so a malformed file fails at build time with a clear panic) and emit cargo:rerun-if-changed=service.toml. Parsed ServiceToml is stored on OschemaBuildConfig::service_toml so future emitters (§3 _admin scaffold, §5 generated main.rs) can read service name / version / binaries without re-parsing.

Modern defaults applied automatically when constructed via from_service_toml:

  • schemas_dir = "schemas"
  • nested_layout = true
  • target = GenerationTarget::Server

Caller chains further builder methods to override.

Generator now owns rustfmt

Dropped the hand-rolled format_generated_sources() helper that every service had to copy into its build.rs. OschemaBuilder::generate() now runs rustfmt --edition 2024 over src/, sdk/, and any configured client/server crate dirs after emission. Best-effort: missing rustfmt is silently skipped. New fs::rustfmt_dir(dir, debug) + fs::collect_rs_files helpers handle the walk.

recipe_server/build.rs — 41 lines → 5

Before:

use hero_rpc_osis::build::{OschemaBuildConfig, OschemaBuilder};

fn main() {
    let config = OschemaBuildConfig::new()
        .schemas_dir("schemas")
        .docs_dir("docs/schemas")
        .domain("recipes", "Recipe management domain")
        .generate_server()
        .nested_layout()
        .debug(true);

    OschemaBuilder::new(config)
        .generate()
        .expect("Failed to generate code from schemas");

    format_generated_sources();  // + 18 lines of rustfmt boilerplate
}

fn format_generated_sources() { /* ... */ }

After:

fn main() {
    hero_rpc_osis::build::OschemaBuilder::from_service_toml()
        .generate()
        .expect("OSchema generation failed");
}

Paired with a new example/recipe_server/service.toml (contributor-owned, codegen never writes it).

cargo check --workspace cargo fmt --check

Next

→ §5 + §6 — update the generated main.rs template in emit/bin.rs to match hero_proc_server/src/main.rs (service_base!(), validate_service_toml, handle_info_flag, print_startup_banner, prepare_sockets — in that order), drop HeroLifecycle::new(...), and enforce the _server/_admin/_sdk/_web/_app naming suffixes in the scaffolder.

## §1 landed — [`6174a00`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/6174a00) `service.toml` is now codegen's single source of truth. Two new entry points: ```rust OschemaBuildConfig::from_service_toml() // reads $CARGO_MANIFEST_DIR/service.toml OschemaBuildConfig::from_service_toml_at(path) // explicit-path variant OschemaBuilder::from_service_toml() // convenience: wraps the config ``` Both call `herolib_core::base::validate_service_toml` (so a malformed file fails at build time with a clear panic) and emit `cargo:rerun-if-changed=service.toml`. Parsed `ServiceToml` is stored on `OschemaBuildConfig::service_toml` so future emitters (§3 `_admin` scaffold, §5 generated `main.rs`) can read service name / version / binaries without re-parsing. **Modern defaults applied automatically** when constructed via `from_service_toml`: - `schemas_dir = "schemas"` - `nested_layout = true` - `target = GenerationTarget::Server` Caller chains further builder methods to override. ### Generator now owns rustfmt Dropped the hand-rolled `format_generated_sources()` helper that every service had to copy into its `build.rs`. `OschemaBuilder::generate()` now runs `rustfmt --edition 2024` over `src/`, `sdk/`, and any configured client/server crate dirs after emission. Best-effort: missing rustfmt is silently skipped. New `fs::rustfmt_dir(dir, debug)` + `fs::collect_rs_files` helpers handle the walk. ### `recipe_server/build.rs` — 41 lines → 5 Before: ```rust use hero_rpc_osis::build::{OschemaBuildConfig, OschemaBuilder}; fn main() { let config = OschemaBuildConfig::new() .schemas_dir("schemas") .docs_dir("docs/schemas") .domain("recipes", "Recipe management domain") .generate_server() .nested_layout() .debug(true); OschemaBuilder::new(config) .generate() .expect("Failed to generate code from schemas"); format_generated_sources(); // + 18 lines of rustfmt boilerplate } fn format_generated_sources() { /* ... */ } ``` After: ```rust fn main() { hero_rpc_osis::build::OschemaBuilder::from_service_toml() .generate() .expect("OSchema generation failed"); } ``` Paired with a new `example/recipe_server/service.toml` (contributor-owned, codegen never writes it). `cargo check --workspace` ✅ `cargo fmt --check` ✅ ### Next → §5 + §6 — update the generated `main.rs` template in `emit/bin.rs` to match `hero_proc_server/src/main.rs` (`service_base!()`, `validate_service_toml`, `handle_info_flag`, `print_startup_banner`, `prepare_sockets` — in that order), drop `HeroLifecycle::new(...)`, and enforce the `_server`/`_admin`/`_sdk`/`_web`/`_app` naming suffixes in the scaffolder.
Author
Owner

§5 + §6 landed — 36eaf2f

§6 — naming

Scaffolded crates now follow the canonical _server / _admin / _sdk suffixes per hero_service_check_fix.md. _ui and _client are gone in the scaffolder; the API renamed accordingly:

  • crates/<name>_uicrates/<name>_admin (workspace member, dir layout, package name, buildenv.sh, helper fns: generate_admin_crate, generate_admin_main_rs)
  • crates/<name>_clientcrates/<name>_sdk (workspace member, dir layout, package name, OschemaBuildConfig::client_crate_dir target, helper generate_sdk_crate)
  • WorkspaceScaffolder::with_ui() / without_ui()with_admin() / without_admin(); deprecated with_http() / without_http() shims dropped
  • Scaffolder CLI: --no-admin is the new flag; --no-ui kept as a back-compat alias

Grep across lhumina_code confirms no external call sites of the renamed builder methods.

§5 — main.rs boilerplate

Scaffolded _server and _admin main.rs now follow the spec exactly:

use herolib_core::service_base;
use hero_lifecycle::HeroLifecycle;
use hero_rpc_server::OServer;

service_base!();   // 1) module scope → SERVICE_TOML const

#[tokio::main]
async fn main() -> ... {
    herolib_core::base::validate_service_toml(SERVICE_TOML);  // 2) first
    herolib_core::base::handle_info_flag(SERVICE_TOML);        // 3) second
    ...
    OServer::run_cli(lifecycle, |server, contexts, _, _| async move {
        herolib_core::base::print_startup_banner(...);         // 4)
        herolib_core::base::prepare_sockets(...);              // 5)
        ...
        server.run().await
    }).await
}

Banner + sockets are inside the OServer::run_cli callback so they fire only in foreground mode (skipped on --start / --stop dispatch). HeroLifecycle import flipped from hero_rpc_server::{OServer, HeroLifecycle} to the canonical hero_lifecycle::HeroLifecycle (§4 rename).

Per-crate service.toml writes

service_base!() expands to include_str!("../service.toml"), so every binary crate needs its own service.toml at the crate root. New write_binary_service_toml scaffolder helper emits one per binary crate (server + admin), write-only-if-missing so it never clobbers contributor edits (per §1: scaffolder + contributor own service.toml, codegen never writes it).

Tests

6 new assertions in test_scaffold_server_main_* pin the §5 boilerplate; 2 new in test_scaffold_admin_main_uses_hero_ui_server. All 8 scaffold tests + the full 105-test generator suite pass.

Deliberate scope gap (filing as follow-up)

The per-domain bin emitter (emit/bin.rs::generate_per_domain_bins) and the single-bin orchestrator (generate_single_bin) still emit HeroLifecycle::new(...) without service_base!(). Those bins live at src/bin/<name>.rs inside an existing crate, so include_str!("../service.toml") resolves to src/service.toml — wrong. Two ways to fix:

  1. A custom service_base_at_path!() macro that accepts an explicit path.
  2. Restructure those bins as separate crates (matches the parent tracker's "one binary per crate" guidance for _server/_admin/_sdk).

Neither feels in-scope for this PR — flagging now so it can be tracked separately.

Next

→ §3 — generate the _admin crate scaffold using hero_website_lib / hero_admin_lib default screens (replaces the placeholder HeroUiServer + axum router in generate_admin_main_rs with the framework-driven version).

## §5 + §6 landed — [`36eaf2f`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/36eaf2f) ### §6 — naming Scaffolded crates now follow the canonical `_server` / `_admin` / `_sdk` suffixes per `hero_service_check_fix.md`. `_ui` and `_client` are gone in the scaffolder; the API renamed accordingly: - `crates/<name>_ui` → `crates/<name>_admin` (workspace member, dir layout, package name, buildenv.sh, helper fns: `generate_admin_crate`, `generate_admin_main_rs`) - `crates/<name>_client` → `crates/<name>_sdk` (workspace member, dir layout, package name, `OschemaBuildConfig::client_crate_dir` target, helper `generate_sdk_crate`) - `WorkspaceScaffolder::with_ui()` / `without_ui()` → `with_admin()` / `without_admin()`; deprecated `with_http()` / `without_http()` shims dropped - Scaffolder CLI: `--no-admin` is the new flag; `--no-ui` kept as a back-compat alias Grep across `lhumina_code` confirms no external call sites of the renamed builder methods. ### §5 — main.rs boilerplate Scaffolded `_server` and `_admin` `main.rs` now follow the spec exactly: ```rust use herolib_core::service_base; use hero_lifecycle::HeroLifecycle; use hero_rpc_server::OServer; service_base!(); // 1) module scope → SERVICE_TOML const #[tokio::main] async fn main() -> ... { herolib_core::base::validate_service_toml(SERVICE_TOML); // 2) first herolib_core::base::handle_info_flag(SERVICE_TOML); // 3) second ... OServer::run_cli(lifecycle, |server, contexts, _, _| async move { herolib_core::base::print_startup_banner(...); // 4) herolib_core::base::prepare_sockets(...); // 5) ... server.run().await }).await } ``` Banner + sockets are inside the `OServer::run_cli` callback so they fire only in foreground mode (skipped on `--start` / `--stop` dispatch). `HeroLifecycle` import flipped from `hero_rpc_server::{OServer, HeroLifecycle}` to the canonical `hero_lifecycle::HeroLifecycle` (§4 rename). ### Per-crate `service.toml` writes `service_base!()` expands to `include_str!("../service.toml")`, so every binary crate needs its own `service.toml` at the crate root. New `write_binary_service_toml` scaffolder helper emits one per binary crate (server + admin), write-only-if-missing so it never clobbers contributor edits (per §1: scaffolder + contributor own `service.toml`, codegen never writes it). ### Tests 6 new assertions in `test_scaffold_server_main_*` pin the §5 boilerplate; 2 new in `test_scaffold_admin_main_uses_hero_ui_server`. All 8 scaffold tests + the full 105-test generator suite pass. ### Deliberate scope gap (filing as follow-up) The per-domain bin emitter (`emit/bin.rs::generate_per_domain_bins`) and the single-bin orchestrator (`generate_single_bin`) still emit `HeroLifecycle::new(...)` without `service_base!()`. Those bins live at `src/bin/<name>.rs` inside an existing crate, so `include_str!("../service.toml")` resolves to `src/service.toml` — wrong. Two ways to fix: 1. A custom `service_base_at_path!()` macro that accepts an explicit path. 2. Restructure those bins as separate crates (matches the parent tracker's "one binary per crate" guidance for `_server`/`_admin`/`_sdk`). Neither feels in-scope for this PR — flagging now so it can be tracked separately. ### Next → §3 — generate the `_admin` crate scaffold using `hero_website_lib` / `hero_admin_lib` default screens (replaces the placeholder `HeroUiServer` + axum router in `generate_admin_main_rs` with the framework-driven version).
Author
Owner

§3 landed — f3bce13

Scaffolded _admin crate now consumes hero_admin_lib (from hero_website_framework) for the standard admin surface — per the parent tracker's correction of §3 ("Build new admin features on hero_admin_lib, not from scratch").

What the generated _admin binary looks like now

use axum::{Router, middleware, response::IntoResponse, routing::get};

use herolib_core::service_base;
use hero_admin_lib::middleware::base_path_middleware;
use hero_admin_lib::routes::{health_response, heroservice_manifest};
use hero_admin_lib::socket::{admin_socket_path, bind_socket};

service_base!();

const VERSION: &str = env!("CARGO_PKG_VERSION");

async fn health() -> impl IntoResponse {
    health_response("hero_test_admin", VERSION)
}

async fn heroservice() -> axum::response::Response {
    heroservice_manifest("hero_test_admin", "Hero_test admin panel", "...", VERSION)
}

async fn index() -> axum::response::Html<&'static str> {
    axum::response::Html("<h1>Hero_test Admin Panel</h1><p>Service is running. Add screens here.</p>")
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    herolib_core::base::validate_service_toml(SERVICE_TOML);   // §5
    herolib_core::base::handle_info_flag(SERVICE_TOML);         // §5
    herolib_core::base::print_startup_banner(...);              // §5
    herolib_core::base::prepare_sockets(...);                   // §5

    let app = Router::new()
        .route("/", get(index))
        .route("/health", get(health))
        .route("/.well-known/heroservice.json", get(heroservice))
        .layer(middleware::from_fn(base_path_middleware));     // X-Forwarded-Prefix / X-Hero-*

    let sock_path = admin_socket_path("hero_test_admin");
    let listener = bind_socket(&sock_path).await?;
    axum::serve(listener, app).await?;
    Ok(())
}

Index stays a placeholder for the contributor to extend. The framework provides everything underneath: standard routes, hero_router header lifting, socket lifecycle (create dir + remove stale + 0o770 perms + bind), and embedded Bootstrap / Unpoly / highlight.js / marked / Chart.js / the hero web components (<hero-connection-status>, <hero-api-docs>, <hero-logs-viewer>, …) via hero_admin_lib's rust-embed asset bundle.

Cargo.toml diff

  • hero_lifecycle dep (HeroUiServer no longer used here)
  • tower-http (CORS isn't needed for hero_router-fronted admins)
  • hero_admin_lib = { git = "…hero_website_framework.git", branch = "development" }
  • axum 0.70.8 (matches hero_admin_lib)
  • tokio narrowed to {macros, rt-multi-thread, signal} features

Tests

test_scaffold_admin_main_uses_hero_admin_lib (renamed from …_uses_hero_ui_server) asserts the new imports, the socket path, the standard route helpers, the base_path_middleware wiring, and explicitly that HeroUiServer is gone. 8/8 scaffold tests + the full 105-test generator suite pass.

cargo check --workspace cargo fmt --check

What's left

→ §2 — hero_rpc2 vendor + hybrid SDK + Python target + HeroRequestContext header-lift. By far the largest piece, with an external dep copy (delandtj/hero_rpc2) + new codegen target + extension work in the vendored crate. Per my opener (q1), this will be its own PR; I'll start by vendoring the upstream as-is and post a separate plan-of-attack comment before the SDK-codegen work proper.

## §3 landed — [`f3bce13`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/f3bce13) Scaffolded `_admin` crate now consumes `hero_admin_lib` (from `hero_website_framework`) for the standard admin surface — per the parent tracker's correction of §3 ("Build new admin features on hero_admin_lib, not from scratch"). ### What the generated `_admin` binary looks like now ```rust use axum::{Router, middleware, response::IntoResponse, routing::get}; use herolib_core::service_base; use hero_admin_lib::middleware::base_path_middleware; use hero_admin_lib::routes::{health_response, heroservice_manifest}; use hero_admin_lib::socket::{admin_socket_path, bind_socket}; service_base!(); const VERSION: &str = env!("CARGO_PKG_VERSION"); async fn health() -> impl IntoResponse { health_response("hero_test_admin", VERSION) } async fn heroservice() -> axum::response::Response { heroservice_manifest("hero_test_admin", "Hero_test admin panel", "...", VERSION) } async fn index() -> axum::response::Html<&'static str> { axum::response::Html("<h1>Hero_test Admin Panel</h1><p>Service is running. Add screens here.</p>") } #[tokio::main] async fn main() -> anyhow::Result<()> { herolib_core::base::validate_service_toml(SERVICE_TOML); // §5 herolib_core::base::handle_info_flag(SERVICE_TOML); // §5 herolib_core::base::print_startup_banner(...); // §5 herolib_core::base::prepare_sockets(...); // §5 let app = Router::new() .route("/", get(index)) .route("/health", get(health)) .route("/.well-known/heroservice.json", get(heroservice)) .layer(middleware::from_fn(base_path_middleware)); // X-Forwarded-Prefix / X-Hero-* let sock_path = admin_socket_path("hero_test_admin"); let listener = bind_socket(&sock_path).await?; axum::serve(listener, app).await?; Ok(()) } ``` Index stays a placeholder for the contributor to extend. The framework provides everything underneath: standard routes, hero_router header lifting, socket lifecycle (create dir + remove stale + `0o770` perms + bind), and embedded Bootstrap / Unpoly / highlight.js / marked / Chart.js / the hero web components (`<hero-connection-status>`, `<hero-api-docs>`, `<hero-logs-viewer>`, …) via `hero_admin_lib`'s rust-embed asset bundle. ### Cargo.toml diff - ➖ `hero_lifecycle` dep (HeroUiServer no longer used here) - ➖ `tower-http` (CORS isn't needed for hero_router-fronted admins) - ➕ `hero_admin_lib = { git = "…hero_website_framework.git", branch = "development" }` - axum `0.7` → `0.8` (matches hero_admin_lib) - tokio narrowed to `{macros, rt-multi-thread, signal}` features ### Tests `test_scaffold_admin_main_uses_hero_admin_lib` (renamed from `…_uses_hero_ui_server`) asserts the new imports, the socket path, the standard route helpers, the `base_path_middleware` wiring, and explicitly that `HeroUiServer` is gone. 8/8 scaffold tests + the full 105-test generator suite pass. `cargo check --workspace` ✅ `cargo fmt --check` ✅ ### What's left → §2 — `hero_rpc2` vendor + hybrid SDK + Python target + `HeroRequestContext` header-lift. By far the largest piece, with an external dep copy (`delandtj/hero_rpc2`) + new codegen target + extension work in the vendored crate. Per my opener (q1), this will be its own PR; I'll start by vendoring the upstream as-is and post a separate plan-of-attack comment before the SDK-codegen work proper.
Author
Owner

§2 progress — 3 of 5 parts landed on the same branch

Keeping §2 on issue-55-codegen-alignment after all (your continue opener). Three commits in:

Part Commit What
1. Vendor e2f4d27 crates/hero_rpc2/ — copy-over from delandtj/hero_rpc2, workspace member, shared deps via [workspace.dependencies], Cargo.lock dropped, VENDOR.md traces what was kept / what changes are planned in-place
2. Context types 479a558 crate::context::{HeroRequestContext, current, with_context}, tokio::task_local! REQUEST_CONTEXT, ServerBuilder::with_lifted_headers([...]) + lifted_headers() getter, prelude re-exports. 4 lib tests pin behaviour
3. HTTP wiring c16ddc0 serve_http + dispatch thread Arc<Vec<String>> lift list through; new build_context(&HeaderMap, &[String]) materialises X-Hero-Context + X-Hero-Claims into typed fields and any other configured header into extras; dispatch wraps dispatch_json in with_context(...) so handlers can read via current_context(). Line transport is unaffected

New test file tests/http_context_lift.rs (3 tests, all green) covers: (a) bare request → default ctx in scope; (b) typed headers always lifted even without config; (c) configured extras flow through extras map while typed wins on overlap.

All 22+ vendored + new tests pass. cargo check --workspace .

What's left in §2

  • Part 4 — OSchema codegen emits #[rpc(server, client)] traits. Replace the existing hand-typed client in emit/rust_sdk.rs (363 LOC) with a hero_rpc2-trait emitter. For each domain method on a rootobject, the emitted trait has ctx: Option<HeroRequestContext> as the first arg per the locked design. Generated traits live in the _sdk crate; the server impl stub stays in the server crate as a preserved file. This is the substantial piece.
  • Part 5 — Python SDK target. New emit module that takes the same OpenRPC spec the JS/TS target consumes and writes a Python client crate. Smaller than part 4 but needs a Python toolchain for validation (which isn't currently set up in this workspace).

I'll keep pushing on part 4 next. If the codegen design ends up needing your input on trait shape (e.g. when methods return SSE streams vs unary, or how Subscription methods map), I'll pause and post here before going further.

## §2 progress — 3 of 5 parts landed on the same branch Keeping §2 on `issue-55-codegen-alignment` after all (your `continue` opener). Three commits in: | Part | Commit | What | |---|---|---| | 1. Vendor | [`e2f4d27`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/e2f4d27) | `crates/hero_rpc2/` — copy-over from `delandtj/hero_rpc2`, workspace member, shared deps via `[workspace.dependencies]`, `Cargo.lock` dropped, `VENDOR.md` traces what was kept / what changes are planned in-place | | 2. Context types | [`479a558`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/479a558) | `crate::context::{HeroRequestContext, current, with_context}`, `tokio::task_local!` `REQUEST_CONTEXT`, `ServerBuilder::with_lifted_headers([...])` + `lifted_headers()` getter, prelude re-exports. 4 lib tests pin behaviour | | 3. HTTP wiring | [`c16ddc0`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/c16ddc0) | `serve_http` + `dispatch` thread `Arc<Vec<String>>` lift list through; new `build_context(&HeaderMap, &[String])` materialises `X-Hero-Context` + `X-Hero-Claims` into typed fields and any other configured header into `extras`; dispatch wraps `dispatch_json` in `with_context(...)` so handlers can read via `current_context()`. Line transport is unaffected | New test file `tests/http_context_lift.rs` (3 tests, all green) covers: (a) bare request → default ctx in scope; (b) typed headers always lifted even without config; (c) configured extras flow through `extras` map while typed wins on overlap. All 22+ vendored + new tests pass. `cargo check --workspace` ✅. ### What's left in §2 - **Part 4 — OSchema codegen emits `#[rpc(server, client)]` traits.** Replace the existing hand-typed client in `emit/rust_sdk.rs` (363 LOC) with a hero_rpc2-trait emitter. For each domain method on a rootobject, the emitted trait has `ctx: Option<HeroRequestContext>` as the first arg per the locked design. Generated traits live in the `_sdk` crate; the server impl stub stays in the server crate as a preserved file. This is the substantial piece. - **Part 5 — Python SDK target.** New emit module that takes the same OpenRPC spec the JS/TS target consumes and writes a Python client crate. Smaller than part 4 but needs a Python toolchain for validation (which isn't currently set up in this workspace). I'll keep pushing on part 4 next. If the codegen design ends up needing your input on trait shape (e.g. when methods return SSE streams vs unary, or how Subscription methods map), I'll pause and post here before going further.
Author
Owner

§2 — all five parts on the branch (parts 4 & 5 scaffolded)

Following up the earlier §2 progress comment. Two more commits in:

Part Commit What
4. hero_rpc2 trait SDK c55a74a New emit/rust_rpc2.rs + OschemaBuildConfig::with_hero_rpc2_sdk() + builder dispatch. Per-domain <sdk>/src/<domain>.rs containing #[rpc(server, client)] pub trait <Pascal> + compile_error! opt-in marker. Auto-indexed via emitted lib.rs (preserved when hand-written). 3 unit tests.
5. Python SDK df7f187 New emit/python_sdk.rs + OschemaBuildConfig::with_python_sdk() + builder dispatch. Per-domain <sdk>/python/hero_<svc>_sdk/<domain>.py containing a <Pascal>Client skeleton + # TODO marker. __init__.py re-export + minimal PEP 621 pyproject.toml (hatchling, Python ≥3.10, httpx). 3 unit tests.

Why scaffolds, not finished translators

Both part 4 and part 5 carry the codegen rails (file emission, config flag, builder dispatch, lib.rs/init.py indexing, test coverage of the wiring) but stop short of OSchema-method → output-language translation. That translation is the substantial piece in each case:

  • Rust: needs to reach into crate::generate::Generator + crate::rust::rust_struct to turn OSchema RpcMethod params and return types into RpcResult<T> signatures with ctx: Option<HeroRequestContext> as the first arg.
  • Python: needs an OSchema → @dataclass + Python type-hint translator (SmartId → str, optional → Optional[...], closed enums → StrEnum/Literal[...]), plus async client emission.

The compile_error! (Rust) and # TODO markers (Python) in the emitted scaffolds make those gaps explicit — consumers opting in get a clear signal to wait for the follow-up commits rather than silently shipping a half feature. Both follow-ups are bounded; each is roughly the size of an emit/rust_sdk.rs rewrite and would benefit from being its own focused commit.

Whole-branch summary now

7 commits on issue-55-codegen-alignment:

§ Commit Scope
§4 aad9f81 crates/service/crates/hero_lifecycle/ rename + 7 downstream PRs (see earlier comment)
§1 6174a00 OschemaBuildConfig::from_service_toml() + recipe_server/build.rs collapsed 41→5 lines + generator owns rustfmt
§5+§6 36eaf2f Scaffolded main.rs matches hero_proc_server (service_base + validate + handle_info + banner + sockets), _ui_admin, _client_sdk, per-crate service.toml writes
§3 f3bce13 Scaffolded _admin consumes hero_admin_lib instead of HeroUiServer
§2.1 e2f4d27 crates/hero_rpc2/ vendor from delandtj/hero_rpc2
§2.2 479a558 HeroRequestContext types + ServerBuilder::with_lifted_headers + prelude exports
§2.3 c16ddc0 HTTP transport lifts headers into the task-local for handlers
§2.4 c55a74a hero_rpc2-trait emitter scaffold (codegen rails)
§2.5 df7f187 Python SDK emitter scaffold (codegen rails)

111 generator tests + 22 hero_rpc2 tests + 8 scaffold tests = 141 unit/integration tests on the branch pass. cargo check --workspace and cargo fmt --check both clean.

Ready-for-PR

Branch is in a coherent shape for review now — each commit is independently meaningful. The two outstanding §2 method-translators are a clean follow-up: same codegen rails, just filled in. Happy to keep pushing on them next, or pause for review here — your call.

## §2 — all five parts on the branch (parts 4 & 5 scaffolded) Following up the earlier §2 progress comment. Two more commits in: | Part | Commit | What | |---|---|---| | 4. hero_rpc2 trait SDK | [`c55a74a`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/c55a74a) | New `emit/rust_rpc2.rs` + `OschemaBuildConfig::with_hero_rpc2_sdk()` + builder dispatch. Per-domain `<sdk>/src/<domain>.rs` containing `#[rpc(server, client)] pub trait <Pascal>` + `compile_error!` opt-in marker. Auto-indexed via emitted `lib.rs` (preserved when hand-written). 3 unit tests. | | 5. Python SDK | [`df7f187`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/df7f187) | New `emit/python_sdk.rs` + `OschemaBuildConfig::with_python_sdk()` + builder dispatch. Per-domain `<sdk>/python/hero_<svc>_sdk/<domain>.py` containing a `<Pascal>Client` skeleton + `# TODO` marker. `__init__.py` re-export + minimal PEP 621 `pyproject.toml` (hatchling, Python ≥3.10, httpx). 3 unit tests. | ### Why scaffolds, not finished translators Both part 4 and part 5 carry the *codegen rails* (file emission, config flag, builder dispatch, lib.rs/init.py indexing, test coverage of the wiring) but stop short of OSchema-method → output-language translation. That translation is the substantial piece in each case: - Rust: needs to reach into `crate::generate::Generator` + `crate::rust::rust_struct` to turn OSchema `RpcMethod` params and return types into `RpcResult<T>` signatures with `ctx: Option<HeroRequestContext>` as the first arg. - Python: needs an OSchema → `@dataclass` + Python type-hint translator (SmartId → str, optional → `Optional[...]`, closed enums → `StrEnum`/`Literal[...]`), plus async client emission. The `compile_error!` (Rust) and `# TODO` markers (Python) in the emitted scaffolds make those gaps explicit — consumers opting in get a clear signal to wait for the follow-up commits rather than silently shipping a half feature. Both follow-ups are bounded; each is roughly the size of an `emit/rust_sdk.rs` rewrite and would benefit from being its own focused commit. ### Whole-branch summary now 7 commits on `issue-55-codegen-alignment`: | § | Commit | Scope | |---|---|---| | §4 | [`aad9f81`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/aad9f81) | `crates/service/` → `crates/hero_lifecycle/` rename + 7 downstream PRs (see earlier comment) | | §1 | [`6174a00`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/6174a00) | `OschemaBuildConfig::from_service_toml()` + `recipe_server/build.rs` collapsed 41→5 lines + generator owns rustfmt | | §5+§6 | [`36eaf2f`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/36eaf2f) | Scaffolded `main.rs` matches `hero_proc_server` (service_base + validate + handle_info + banner + sockets), `_ui` → `_admin`, `_client` → `_sdk`, per-crate `service.toml` writes | | §3 | [`f3bce13`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/f3bce13) | Scaffolded `_admin` consumes `hero_admin_lib` instead of `HeroUiServer` | | §2.1 | [`e2f4d27`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/e2f4d27) | `crates/hero_rpc2/` vendor from `delandtj/hero_rpc2` | | §2.2 | [`479a558`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/479a558) | `HeroRequestContext` types + `ServerBuilder::with_lifted_headers` + prelude exports | | §2.3 | [`c16ddc0`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/c16ddc0) | HTTP transport lifts headers into the task-local for handlers | | §2.4 | [`c55a74a`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/c55a74a) | hero_rpc2-trait emitter scaffold (codegen rails) | | §2.5 | [`df7f187`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/df7f187) | Python SDK emitter scaffold (codegen rails) | 111 generator tests + 22 hero_rpc2 tests + 8 scaffold tests = 141 unit/integration tests on the branch pass. `cargo check --workspace` and `cargo fmt --check` both clean. ### Ready-for-PR Branch is in a coherent shape for review now — each commit is independently meaningful. The two outstanding §2 method-translators are a clean follow-up: same codegen rails, just filled in. Happy to keep pushing on them next, or pause for review here — your call.
timur closed this issue 2026-05-19 02:31:11 +00:00
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#55
No description provided.