Migrate OSIS services off OServer onto hero_rpc2 trait/dispatch — close the META hybrid loop #90
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_rpc#90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The parent META (hero_skills#262) locked the hybrid hero_rpc + hero_rpc2 decision back in May:
The codegen rails landed:
#[method(name=…)]trait methods + Python dataclass methods.recipe_sdk_rpc2round-trips a Pythonsystem.pingover UDS end-to-end.But every OSIS service in the ecosystem still runs on the older
OServerdispatch path, not on hero_rpc2. The hero_logic#44 agent explicitly scoped this out: "DO NOT migrate to hero_rpc2 in this PR — that's a separate later step." This is the separate later step.Why this matters now
Downstream symptoms surface as separate issues that all share this root cause:
system.pingsmoke test fails on OServer because OServer doesn't registersystem.*builtins. hero_rpc2 does, automatically. Supplanted by this issue.rust_rpc.rslegacy → OServer,rust_rpc2.rs→ hero_rpc2). Until we cut over, both have to be kept correct in lockstep.recipe_sdk_rpc2example exists in parallel torecipe_serverfor the same reason — proving hero_rpc2 works while OServer remains canonical.Affected services (OSIS-backed, currently OServer)
hero_servicehero_logichero_computehero_dbhero_rpc_osisrpcfeaturehero_rpc_osis.*features.*rpcacross the org.hero_procruns its own JSON-RPC stack (not OServer), so it's outside this scope — separate decision whether it also adopts hero_rpc2.Migration shape
Proposed phasing (open for discussion in this thread before implementation):
Phase 1 — framework path (hero_rpc)
hero_rpc2::ServerBuilderdoesn't (context routing, OSIS-CRUD auto-dispatch, claims layer, hero_router integration). For each gap, either land it in hero_rpc2 or document the gap as a constraint of the new path.rust_rpc2.rs); legacyrust_rpc.rsemitter stays callable behind a flag until services migrate, then gets deleted.Phase 2 — first migration (
hero_servicetemplate)hero_serviceto hero_rpc2 dispatch. This validates the framework changes and updates the canonical reference everyone clones.hero_service_scaffold.mdskill so new scaffolds get hero_rpc2 by default.Phase 3 — sweep remaining OSIS services
HeroLifecycle + ServerBuilderpattern from hero_service). Likely possible to mostly automate.Phase 4 — drop legacy
hero_rpc/crates/server/.rust_rpc.rsemitter.recipe_sdk_rpc2parallel example —recipe_serverbecomes the canonical demo on the new path.What this closes / supersedes
system.pingbuiltin missing. Resolved automatically once services move to hero_rpc2 (which registerssystem.*natively).rust_rpc.rs/rust_rpc2.rsemitters; therecipe_server/recipe_sdk_rpc2split.Acceptance
lhumina_code/that's nothero_procruns on hero_rpc2 dispatch.hero_rpc/crates/server/.rust_rpc.rsemitter is deleted; the scaffolder only emits the hero_rpc2 trait.recipe_sdk_rpc2parallel example is consolidated intorecipe_server(single demo on the new path).system.ping.hero_service_scaffold.mddescribes only the hero_rpc2 path.Out of scope
hero_procmigration (different RPC stack; separate decision).hero_rpc2itself growing new framework features beyond what migration requires — those are their own follow-ups.Related
system.pingbuiltin (skill smoke test expects it) #80Phase 1 design proposal — DESIGN gate (no code yet)
Read parent META hero_skills#262 + decision threads #55 / #60. Worktree set up at
/tmp/hero_rpc_90on branchissue-90-phase1-frameworkofforigin/development. No code pushed yet — three answers below need sign-off first.(1) Feature-parity audit — OServer vs
hero_rpc2::ServerBuilderSurveyed
crates/server/src/{lib.rs,server/*.rs,acl/}(3,362 LoC) againstcrates/hero_rpc2/src/{lib.rs,server.rs,context.rs,transport/*}. Grouped by capability:A. Wire / transport / context — hero_rpc2 already covers
rpc.sockper service, JSON-RPC 2.0 over HTTP-over-UDSserve_httpX-Hero-Contextheader → typed routingwith_lifted_headers+HeroRequestContext+task_local!(landed in #55 §2 part 2)X-Hero-Claimsheader → claims vecHeroRequestContextrpc.discover/rpc.healthbuiltinsrpc.discoverviawith_discover; gap: norpc.healthbuiltin (this is exactly the symptom in #80)RpcErrorenum (codegen wiring of OSchema typed errors is #83 follow-up territory — not Phase 1)B. Codegen — needs one targeted extension in Phase 1
Type.set/get/find/delete/existsrouted fromOsisAppRpcHandler::type_names()without the service declaring those methodsemit/rust_rpc2.rsonly walksservice Foo { … }blocks. To match OServer's behaviour for rootobjects, the emitter must additionally emit#[method(name = "<type>.get/set/delete/find/exists")]entries into the same trait for everyrootobjectdeclared in the schema. This keeps "CRUD-for-free" working under hero_rpc2 dispatch with no per-service hand-coding.A::openrpc_spec())crates/generator/src/build/openrpc/…); not a hero_rpc2 concernC. Multi-domain orchestration — lives elsewhere, not a hero_rpc2 concern
These are OSIS-runtime concerns, not generic JSON-RPC framework. META locks hero_rpc2 to "wire protocol, UDS transports, and the auto-generated Rust client" only.
ContextRegistry,registry.tomlat~/hero/var/osisdb/.core/)hero_rpc_osis(or a small new module). NOT moving into hero_rpc2.context.list / context.add / context.remove / context.import / context.export / domain.listmanagement RPC methodsjsonrpsee::RpcModuleto merge with the user's trait moduleseed_from_dir(TOML-tree seeding)git_sync::context_import / context_export(subprocess rsync + git)~/hero/var/osisdb/{context}/{domain}/)create(db_path, user_id)callers (already application-level)register::<A>(ctx, domain)orchestration (storage init +OsisDomainInit::create+ dispatch wiring)RpcModuleper registered domain andRpcModule::merges themmethodsmerged + management methods appended)ServerBuilder::with_discover(…)Phase 1 does not introduce that helper. Phase 2 (
hero_servicetemplate migration) introduces it as a concrete helper, validates the pattern, then Phase 3 sweeps it across the four other repos.D. Mandatory HTTP endpoints — mostly in
hero_lifecycle; one composition gap/healthHTTP endpointhero_lifecycle::HeroRpcServer::mandatory_router()/openrpc.jsonHTTP endpoint/.well-known/heroservice.jsonHTTP endpoint (hero_router discovery)rpc.sock(OServer'srun_with_extension(Router))serve_httpis a private hyper service that only acceptsPOST /. Phase 2 services need to compose the rpc handler with axum's well-known endpoints + optional SSE on one UDS. Cleanest fix: expose the jsonrpseeRpcModuleas an axum-mountable handler (hero_rpc2::transport::http::axum_handler(module, max_body, lifted_headers) -> axum::Router). ThenHeroRpcServer(which already serves an axumRouterover UDS) just merges that handler atPOST /rpcand we have the same shape OServer offers.E. CLI / lifecycle — lives in
hero_lifecycle, no gap--infoJSON descriptorherolib_core::base::handle_info_flag(SERVICE_TOML)covers it (every service main.rs already calls it beforerun_cli)--start/--stop/ bare-foreground dispatchhero_lifecycle::HeroLifecycle::{start,stop}covers it--contexts foo,barflag (multi-context start)--seed-dir / --seed-domainsflagsF. Genuine dead code — delete in Phase 4
crates/server/src/acl/(824 LoC:Acl/Group/Ace/Right)use hero_rpc_server::acl::…outside the crate's own docs. Delete with OServer in Phase 4 (not in Phase 1 — keep the deprecation surface intact for downstream services).inspector_uifeaturehero_<name>_admin. Delete with OServer in Phase 4.Phase 1 gap summary (what actually lands inside
hero_rpc2/this PR)emit/rust_rpc2.rs: extend trait emission to auto-emit<type>.{get,set,delete,find,exists}#[method]entries for everyrootobjectin the schema (closes B auto-CRUD).hero_rpc2::transport::http: exposeaxum_handler(module, max_body, lifted_headers) -> axum::Routerso services can compose rpc + well-known + SSE on one UDS viaHeroRpcServer(closes D extension merging).hero_rpc2::ServerBuilder: register a defaultrpc.healthbuiltin (matches OServer's contract; closes the failure surfaced by #80).Items C and E are explicitly not Phase 1 work — they land in Phase 2's
hero_servicetemplate migration as a freshOsisServer*helper that wrapsServerBuilder.(2) OServer fate — recommend deprecate-then-remove
Recommendation: deprecate now, schedule removal for Phase 4.
Reasoning:
hero_service,hero_logic,hero_compute,hero_db, plusexample/recipe_server). Hard-cut means flag-day migration across 5 repos in one PR landed window.Concrete deprecation steps in this PR (Phase 1):
#[deprecated(since = "<this-version>", note = "Use hero_rpc2::ServerBuilder + the OsisServer helper landing in Phase 2 (#90). OServer is removed in Phase 4.")]on theOServerstruct,OServerConfig,UnifiedServerBuilder, andServerCli.crates/server/src/lib.rsandcrates/server/README.mdlinking to #90 with the removal timeline.Cargo.tomldoes NOT drop thehero_rpc_servermember yet (existing services still depend on it).#[deny(deprecated)]— let the warnings surface naturally in downstream service builds so each consuming repo notices on their nextcargo buildand can schedule their Phase 3 PR.Phase 4 then deletes
crates/server/, thehero_rpc_serverworkspace member, and the legacy emitter path.(3) Scaffolder cutover — default to hero_rpc2, keep legacy behind an opt-in
The current scaffolder path (
crates/generator/src/build/scaffold.rs::generate_server_main_rs) emits amain.rsthat usesOServer::run_cli(...). There is no separaterust_rpc.rsemitter file — the legacy/new split lives in the scaffolded main.rs template, with the trait/SDK split living in twoemit/modules (rust_rpc2.rsfor the new hero_rpc2 trait,rust_sdk.rsfor the legacy OServer-targeted client). Restating the user's framing in these accurate terms:Current state (committed on
development):emit/rust_rpc2.rs→ emitssdk/rust/src/<domain>.rscontaining the hero_rpc2 trait. Opt-in viaOschemaBuildConfig::with_hero_rpc2_sdk(). Default is off.emit/rust_sdk.rs→ emits the legacy typed-client SDK targeting the OServer wire shape. Default is on.scaffold.rs::generate_server_main_rs→ emits amain.rscallingOServer::run_cli. Hard-coded, no flag.Phase 1 cutover plan:
Flip the scaffolder's emitted
main.rsto hero_rpc2 by default. The new template uses:hero_rpc2::ServerBuilderfor transport,hero_lifecycle::HeroRpcServerfor the well-known endpoints + lifecycle CLI,sdk/rust/src/<domain>.rs(which the scaffoldedbuild.rsnow opts into viawith_hero_rpc2_sdk()).The new template ships an inline TODO comment pointing each service author at the (Phase 2)
OsisServerhelper for context-registry + seed wiring.Flip the
OschemaBuildConfigdefaults so the scaffoldedbuild.rsproduces the rpc2 trait by default and also produces the legacy SDK while existing services still need it (so the build pipeline stays compatible during the transition window):generate_rpc2_sdk: true(wasfalse)generate_rust_sdk: trueunchanged (legacy still emitted for existing services)with_legacy_disabled()for new scaffolds that want a clean hero_rpc2-only crateLegacy main.rs template stays callable behind an explicit flag for the brief window while existing services migrate:
WorkspaceScaffolder::with_legacy_oserver_main(). Default = off. Phase 4 deletes this flag along with OServer.example/recipe_serverkeeps building unchanged through Phase 1 — its existingmain.rspredates the flag flip and uses OServer; the deprecation warnings will surface but compile stays clean. Phase 2 migrateshero_servicefirst (validating the new template), Phase 3 sweepsrecipe_server+ the four real services, Phase 4 deletes the legacy emitter.example/recipe_sdk_rpc2stays as the hero_rpc2 reference until Phase 4 consolidates it intorecipe_server. No change in Phase 1.Acceptance gate (re-stated from #90 body)
For Phase 1 only, this PR will land with:
axum_handlerintegration,rpc.healthbuiltin.#[deprecated]attrs + README note linking to #90 with the Phase 4 removal timeline.with_legacy_oserver_main().cargo build --workspace+cargo test --workspaceclean onhero_rpc.example/recipe_server+example/recipe_sdk_rpc2+ every existing OSIS-backed service in the org continues to build (deprecation warnings only; no errors).Phases 2 / 3 / 4 stay future PRs — they need this framework path in place first.
Waiting on sign-off
Before any code, three explicit yes/no asks:
hero_rpc_osis, NOT inhero_rpc2" the right split, given META's "hero_rpc2 owns wire/transport/Rust-client" line?with_legacy_oserver_main()flag good, or do you want a different opt-in name / different default forgenerate_rust_sdk?Once those three are signed off I push the implementation in one squash-mergeable PR.
Phase 1 design — revised after sign-off feedback
Investigated. Two outcomes:
hero_rpc_osisturn out to be either ORPHAN (no external callers) or already SUPERSEDED by service-side codegen. Drop them. Detail below.Re (1) — UDS is already there; the real gap is
/rpc+ the four well-known endpointsConfirmed against the
hero_socketsskill (the canonical spec —~/.claude/skills/hero_sockets/SKILL.md):serve_httpis already UDS —tokio::net::UnixListener, no TCP. Same forserve_line. No UDS gap.POST /rpcas the dispatch path. hero_rpc2's HTTP transport today accepts onlyPOST /(crates/hero_rpc2/src/transport/http.rs:105). Gap — fix in Phase 1.GET /openrpc.json,GET /health,GET /.well-known/heroservice.jsonon everyrpc.sock. hero_rpc2 has none. Gap — fix in Phase 1. These plug straight in to hero_rpc2's existing hyper service inserve_http; doesn't need axum at all. Much smaller than my earlier "expose as axum handler" proposal.Re (2)/B — hybrid was right; emitter does NOT need to grow auto-CRUD
You're correct, this was the locked decision and I drifted. Refactor of the proposal:
OsisAppRpcHandlerimpl (handler.rs + the generated dispatch in<service>_server/src/<domain>/server/*) — same as today. CRUD-by-typename + service methods both live in there. Nothing changes on the codegen side for this.OsisAppRpcHandler::handle_rpc_call_with_context/handle_service_call_with_contextdispatch into ajsonrpsee::RpcModule. That adapter is the only new code: register one method per declared type-method pair, route through the existing handler. CRUD-for-free stays automatic because the handler already does it.This is meaningfully smaller than what I'd outlined. The rust_rpc2 emitter can stay narrow (typed-client + server-trait declaration for hand-written service methods only — already what it does). The bridge to existing OSIS dispatch lives in a new
hero_rpc2module (call itosis_adapteror similar) — or alternatively inhero_rpc_osisitself as the consumer-facing seam.Re (2) — hard-cut accepted; orphan-detail follows
Inventory of every OServer feature with current callers (workspace-wide grep done — concrete file:line evidence):
crates/server/src/acl/(824 LoC)use hero_rpc_server::acl::*outside the crate.Acl::/Group::new(/Ace::references found elsewhere belong to unrelated types (hero_osis::AclRight,hero_books::Group,hero_ledger::Group). Drop.ContextRegistry+~/hero/var/osisdb/.core/registry.tomlpersistencecontext.addRPC methodcontext.removeRPC methodcontext.importRPC methodunified_server.rs:630-665dispatch wires it togit_sync::context_import. Drop.context.exportRPC methodunified_server.rs:667-709. Drop.git_sync::context_import/context_exportcontext.listRPC methodhero_osis_server/src/base/server/osis_server_generated.rs:1832(services' own generated dispatch), plus the consumer sidehero_osis_sdk/.../osis_client_generated.rs:458+hero_code_admin/static/js/contexts.js. The OServer built-in is dead weight — services already serve their own version. Drop.domain.listRPC methodhero_proxy_server/src/lib.rs:275implements it on the service side;hero_proxy_server/openrpc.client.generated.rs:1031is the client. Drop.OServer::seed_from_dir+--seed-dir/--seed-domainsCLI flagshero_osis/tests/e2e_seed.rs(test that exercises OServer behaviour — goes when hero_osis migrates in Phase 3),hero_rpc/crates/server/tests/cli_lifecycle.rs(test of OServer — goes with OServer),hero_rpc/crates/server/src/server/spawn.rs:94-111(inside OServer crate — goes),hero_rpc/crates/generator/src/build/emit/bin.rs:136-149(legacy bin emitter — gets rewritten in this PR when we flip the scaffolder template). No external Rust API surface. Drop with OServer; Phase 2+ services that want TOML seeding implement it themselves on the OSIS dispatch directly (small helper if commonality emerges).--contexts foo,barmulti-context start flagcrates/generator/src/build/emit/bin.rs(which we're rewriting) +crates/server/tests/cli_lifecycle.rs(OServer test). Drop with OServer; if multi-context-per-process becomes a need later, it lives on the consumer's CLI parser, not the framework.inspector_uifeaturerpc.sock. No external opt-ins found. Drop with OServer.Net of Phase 1: delete
hero_rpc/crates/server/entirely. Workspace member gone. No#[deprecated]machinery, no "stays-callable-behind-a-flag" — straight removal.Re (3) — scaffolder: only new-way; legacy SDK emitter also goes
Following the "don't keep legacy, don't emit 2" rule:
main.rsemits only the hero_rpc2 path (ServerBuilder + UDS HTTP + the new well-known endpoints + the bridge adapter to the codegen-generated OSIS dispatch).OschemaBuildConfig.generate_rpc2_sdkdefault flipped totrue.generate_rust_sdkdefault flipped tofalse. Nowith_legacy_oserver_main(), nowith_legacy_disabled()— just one path.emit/rust_sdk.rs(legacy SDK emitter producing the OServer-targeted typed client) gets deleted in Phase 1, not Phase 4.One downstream snag worth flagging:
hero_launcheris the one workspace service that ships an auto-generatedsdk/rust/from the legacy emitter, andhero_wasmos/Cargo.toml:187depends onhero_launcher_sdk. If we delete the legacy emitter in Phase 1, hero_launcher stops regenerating that SDK on its next build, and hero_wasmos either keeps building against the last committed version (likely — the SDK is checked-in code) or breaks. Two options:#[deprecated], drop in Phase 4 when hero_launcher migrates.I lean toward option 1 — it keeps "one path only" honest and avoids carrying a deprecated emitter through Phase 2–3. Asking explicitly before doing either.
One genuine acceptance-criterion conflict to surface
Your original prompt said: "keep recipe_server building unchanged (it can stay on OServer through Phase 1)... every existing OSIS service still builds clean." That assumed deprecate-then-remove.
Hard-cut OServer in Phase 1 breaks that —
example/recipe_serveris on OServer, it stops compiling the momentcrates/server/is deleted. Options:example/recipe_serveronto the new hero_rpc2 + OSIS-adapter path in this same PR. This effectively folds Phase 2's "validate the new template" work into Phase 1, using recipe_server as the validator instead of hero_service. recipe_sdk_rpc2 collapses into recipe_server (no more two parallel examples). hero_service template migration in Phase 2 then becomes "copy what recipe_server did."example/recipe_serveroutright in Phase 1;example/recipe_sdk_rpc2becomes the only example until Phase 4 consolidation.crates/server/in Phase 1; just delete the scaffolder's emission of OServer-using main.rs + the legacy SDK emitter + acl/registry/git_sync orphan code, and have Phase 4 delete thecrates/server/shell. This is the only way to keep recipe_server building. It's not really a hard-cut — it's "hard-cut the user-facing scaffolder, gentle-cut the OServer crate itself."I lean toward (a) — folds the recipe_server work into Phase 1 cleanly, retires
recipe_sdk_rpc2, and validates the new path end-to-end in the same PR. (b) loses the realistic end-to-end example. (c) reintroduces the "deprecated but compiling" pattern you said you don't want.Concrete Phase 1 plan (waiting on three confirmations)
OsisAppRpcHandler → jsonrpsee::RpcModuleexposed from hero_rpc2 (orhero_rpc_osis). The rpc2 trait emitter does NOT grow auto-CRUD. Confirm?crates/server/(incl. acl, registry, git_sync, all six management methods, seed_from_dir, inspector_ui), drop the legacy SDK emitteremit/rust_sdk.rs. Confirm?Once (1), (2), (3) are answered I push the implementation.
What hero_rpc2 grows in this PR
serve_http: routePOST /rpcinstead ofPOST /(matching skill convention).GET /openrpc.json,GET /health,GET /.well-known/heroservice.jsonto the hyper service.ServerBuilderlearnswith_service_info(name, title, version, description)for the discovery manifest + a way to register a built-inrpc.health.osis_adaptermodule: takes anArc<A: OsisAppRpcHandler>+ the static OpenRPC spec it ships, returns ajsonrpsee::RpcModulethat hero_rpc2 dispatches through. This is the single replacement for the OServer dispatch loop.generated/subfolders + gitignore #96Phase 1 landed — branch ready for PR
Branch
issue-90-phase1-frameworkpushed; ready to open the PR againstdevelopment. Three commits, hard-cut per sign-off:0648239— hero_rpc2 transport rewrite.POST /rpc(wasPOST /) plus the three mandatory GET endpoints perhero_socketsskill:/health,/openrpc.json,/.well-known/heroservice.json. NewServiceInfo+ServerBuilder::with_service_info()to populate those.rpc.healthJSON-RPC builtin auto-registered byserve_http(closes #80 root cause). Newtests/http_well_known.rspins the four-endpoint shape so hero_router discovery can't regress silently.0a53247— OSIS → hero_rpc2 bridge. Newhero_rpc_osis::rpc::rpc2_adapter::module_for(Arc<A: OsisAppRpcHandler>)returns ajsonrpsee::RpcModule<()>that registers<type>.{get,set,delete,find,exists,list}for every type inA::type_names()(routed tohandle_rpc_call_with_context, same param-shape extraction OServer used), plus every entry inA::openrpc_spec().methods[]not colliding with CRUD or hero_rpc2 builtins (routed tohandle_service_call_with_context). Lowercased method names, typed-error mapping per #83,HeroRequestContext → RequestContextprojection per call. This is the seam hybrid-decision spec called for.f828383— Hard-cut OServer + flip scaffolder.crates/server/entire crate (3,362 LoC — OServer, ACL module,ContextRegistry/git_sync/context.*/domain.*management methods,seed_from_dir,inspector_ui,unified_server.rs, every workspace member dropped). Workspace-wide audit (Acl::,Group::new(,Ace::, six management-method literals,seed_from_dir(, everyhero_rpc_server::*import) confirmed zero external callers before deletion.emit/rust_sdk.rs(legacy OServer-targeted typed client) +emit/bin.rs(per-domain orchestrator using OServer). All their config (OschemaBuildConfig::generate_rust_sdkfield,with_rust_sdk(),bin_*/single_bin/SingleBinConfig) dropped.example/recipe_sdk_rpc2/— folded intoexample/recipe_server, which is now the single end-to-end demo on the new path.OschemaBuildConfig::generate_rpc2_sdkdefaults totrue. Newwithout_hero_rpc2_sdk()opt-out for the rare build script with its own client path. Scaffolder's emittedmain.rsnow drivesServerBuilder::serve_http+rpc2_adapter::register_methodsdirectly — no OServer, norun_clicallback, no per-context registry assumption.example/recipe_serverend-to-end.crates/hero_recipes_server/src/main.rsrewritten (ServerBuilder+rpc2_adapter+tokio::signal::ctrl_c).crates/hero_recipes/build.rsopts into the rpc2 SDK.service.tomlsocket path moved tohero_recipes/rpc.sock(canonicalhero_socketsshape — no_serversuffix in the directory).sdk/rust/Cargo.tomldepends onhero_rpc2 + jsonrpseeinstead of the deletedhero_rpc_client.flag_defaults_off→flag_defaults_on_after_phase1_cutover, threecrates/server/examples/recipe_server/…cases removed fromreal_schemas(theexample/recipe_server/schemas/…case remains).hero_launcher / hero_wasmos — false positive
Investigation reversed the earlier "lone consumer" finding:
hero_launcher_sdkis a fully hand-written Rust client (no build.rs, no codegen, zero references tohero_rpc_server/hero_rpc_client/ the legacy emitter). hero_wasmos consumes it as a plain library crate. Neither repo is touched by this PR — no companion branch needed.Verification
cargo check --workspace: clean.cargo test --workspace --lib --bins: all 132 + 79 + 66 tests pass.cargo test -p hero_rpc2 --features uds-http --tests: 27 tests pass, including the newhttp_well_known.rs(health/discovery/openrpc.json/rpc.health) + the updatedhttp_hero_rpc_compat.rs+http_context_lift.rsnow POSTing to/rpc.example/recipe_server:cargo test --workspace --lib --binsclean against the local in-tree patches.Two pre-existing failures on
development— NOT addressed herecargo test --workspace(without--lib --bins) surfaces two failures that also occur onorigin/developmentwithout any of these changes — bit-rot from earlier work, not caused by #90:hero_rpc_derivetestsse_proxy—openrpc_proxy!macro expansion referencesherolib_corebut the test crate doesn't depend on it.hero_rpc_osisexamples1_generate_codeand2_use_models— call APIs (hero_rpc_osis::oschema::Generator,DBTyped::new_with_index) that no longer exist on the crate.Flag these for separate one-off fixes; they're orthogonal to the framework migration.
What did NOT change (deferred to Phase 2–4)
Phase 1 is only the framework cut. No service binaries are migrated:
hero_servicetemplate repo — first real OSIS service moved onto rpc2 transport. Validates the path; updates thehero_service_scaffold.mdskill.hero_logic,hero_compute,hero_db, … — each their own PR. Each repo'sCargo.tomlflips its branch pin fromissue-90-phase1-frameworkback todevelopmentafter this PR squash-merges. Pattern: copy whatexample/recipe_server/crates/hero_recipes_server/src/main.rsdoes in this PR._sdk_rpc2shadow examples (there is one in this repo's tree already retired) and any service repos still on a temporary pin.Ready to merge once reviewed.
_admin+_webthat drive the generated SDK end-to-end #98generated/subfolders + gitignore #96Phase 2 —
hero_servicemigrated, framework gap closedhero_servicePR #5 squash-merged. The first OSIS service is onhero_rpc2end-to-end. Smoke transcript:GET /health{status: ok, service: hero_service, version: 0.1.0}GET /.well-known/heroservice.jsonprotocol=openrpc,socket=rpcGET /openrpc.jsondocs/openrpc.jsonPOST /rpc rpc.health{status: ok, trusted: true}(builtin, auto-registered)POST /rpc servicecatalog.list[](empty catalog, storage clean)lab infocheckFramework gap surfaced + closed
The migration ran into a small
hero_rpc2API gap: services build their OpenRPC doc offline via codegen (<workspace>/docs/openrpc.jsonper #73) andinclude_str!it, butServerBuilder::with_discoveronly accepted documents constructed in-process viaOpenRpcBuilder. Filed + landed in this same session:OpenRpcDocument::from_value(Value)constructor — squash-merged.That unblocks every Phase 3 service. The
from_valuetest inhttp_well_known.rspins the round-trip so it can't regress silently.Out-of-scope (intentional)
hero_servicestill has the legacy sibling-crate SDK layout (crates/hero_service_sdkinstead of the #70sdk/rust/shape). The migration keptclient_crate_dirpointing at the legacy spot — everything compiles, nothing breaks. A future cleanup PR can lift the layout if desired; it's not blocking #90.Next
Phase 3a —
hero_logic(base PR againstmain, notdevelopment).generated/subfolders + gitignore #96Phase 3 — all OSIS services migrated
3a —
hero_logichero_logicPR #47 squash-merged againstmain(per the prompt — hero_logic usesmainas its release branch, notdevelopment).The complex one. 19 hand-written
logicservice.*service methods, 4 hand-written sibling modules (engine,seed,services,tracing_layer), Python flow execution, built-in flow seeding, file-logger bridge — all preserved. Sibling modules now carry the//! @server-feature:marker (#93) so the regeneratedlib.rskeeps them automatically; the build.rs's old post-write override hack is gone.Smoke transcript:
GET /health{status: ok, service: hero_logic, version: 0.1.0}GET /.well-known/heroservice.jsonprotocol=openrpc,socket=rpcGET /openrpc.jsonLogicServicePOST /rpc rpc.health{status: ok, trusted: true}POST /rpc workflow.listPOST /rpc logicservice.flow_library_searchservice_agentcreated — no -32601 warningslab infocheckSecond framework gap surfaced + closed
The first attempt failed with
-32601 Method not foundon every seed call. Root cause: therpc2_adapterwas registering service-method wire names with their PascalCase service prefix verbatim from the openrpc spec (LogicService.example_upsert), but every existing client sends lowercase, and the codegen's dispatchmatcharms compare against lowercase. OServer used to lowercase before dispatch. Fix:rpc2_adapterlowercases service-method wire names at registration time. New test pins the contract. Merged before this PR.3b —
hero_computeNo migration needed. Inventoried the workspace:
crates/hero_compute_serveris a skeleton stub (std::thread::park()loop) — no RPC code yet, no OServer dep.crates/my_compute_{explorer,mos,zos,playground}_serverall usehero_rpc_osis::rpc::AxumRpcServerdirectly (a separate RPC stack), not OServer. AxumRpcServer is still alive in hero_rpc post-Phase-1, so nothing breaks.Verified by
cargo check --workspaceonorigin/development— all 14 crates build clean. Closing as no-op.3c —
hero_dbNo migration needed.
hero_db_serveris a RESP/Redis-protocol server with an OpenRPC management surface built directly onhero_rpc_openrpc+hero_rpc_derive— no OServer, nohero_rpc_osis. Verified bycargo check --workspace: all 7 crates build clean. Closing as no-op.Phase 3 verdict
OpenRpcDocument::from_value(#99),rpc2_adapterservice-method case-folding (#100).lhumina_code(other thanhero_proc, which has its own RPC stack) now runs onhero_rpc2dispatch.Next
Phase 4 — hero_rpc cleanup (audit any remaining OServer references; the legacy
rust_rpc.rsemitter was already deleted in Phase 1 along withbin.rs+rust_sdk.rs; verify recipe_server still regenerates cleanly).#90 closed — every OSIS service now runs on hero_rpc2
All four phases merged. OServer is gone, the rpc2_adapter bridges every generated OSIS handler into hero_rpc2's UDS HTTP transport, and the legacy Rust client emitter is deleted. Roll-up:
Per-PR breakdown
51c306eOpenRpcDocument::from_valueframework gapc906794da30b07cargo check --workspacecleancargo check --workspacecleanNet deletion
crates/server/— 3,362 LoC (OServer + ACL + ContextRegistry + git_sync + context./domain. RPC + seed_from_dir + inspector_ui). Phase 1.crates/generator/src/build/emit/{rust_sdk,bin}.rs— legacy SDK emitter + per-domain bin orchestrator. Phase 1.crates/generator/src/generate/rust_rpc.rs— legacyosis_client_generated.rsper-domain emitter. Phase 4.crates/generator/src/rust/rust_client.rs— the actual client codegen body. Phase 4.OschemaBuildConfigconfig-flag pairs + builder methods (generate_rust_sdk,with_rust_sdk(),bin_prefix/bin_git_url/bin_skip/bin_ui_builder,single_bin/bin_companions,generate_rpc2_sdk/with_hero_rpc2_sdk()/without_hero_rpc2_sdk()).Generatorbuilder methods + four config fields (client,client_only,client_types_crate,generate_client/client_flat/client_external_types).Net additions
crates/hero_rpc2/src/transport/http.rs: routesPOST /rpc+ serves the three mandatoryGETendpoints perhero_socketsskill.ServiceInfo+with_service_info+ auto-registeredrpc.healthbuiltin.crates/hero_rpc2/src/discover.rs:OpenRpcDocument::from_value(Value) -> Selffor codegen-built specs.crates/osis/src/rpc/rpc2_adapter.rs: bridgesArc<A: OsisAppRpcHandler>→jsonrpsee::RpcModule(CRUD-by-typename + service methods + lowercased wire names +HeroRequestContext → RequestContextprojection + typed-error mapping).main.rsrewritten to driveServerBuilder::serve_httpdirectly, no OServer.Smoke transcripts (canonical, from each migrated service)
hero_service (PR #5):
GET /health→{status:ok, service:hero_service, version:0.1.0}GET /.well-known/heroservice.json→ canonical shape,protocol=openrpc,socket=rpcGET /openrpc.json→ 7 methods, byte-identical todocs/openrpc.jsonPOST /rpc rpc.health→{status:ok, trusted:true}POST /rpc servicecatalog.list→[](empty catalog, storage clean)lab infocheck→ 2 crates clean, 0 findingshero_logic (PR #47):
GET /openrpc.json→ 19 methods, titleLogicServicePOST /rpc rpc.health→{status:ok, trusted:true}POST /rpc workflow.list→ 15+ seeded SIDsPOST /rpc logicservice.flow_library_search {query:"agent"}→ 3 hits-32601 Method not foundwarnings (the failure mode that surfaced PR #100)lab infocheck→ 3 crates clean, 0 findingsPhase 4 verification (hero_rpc workspace):
cargo build --workspaceclean.cargo test --workspace --lib --bins: 279 tests passing across hero_rpc2 / oschema / osis / generator.cargo test -p hero_rpc2 --features uds-http,discover --tests: 27 passing (incl.http_well_known+from_valueround-trip).cargo test -p hero_rpc_osis --features rpc rpc2_adapter: 8 passing (incl. the lowercase-wire-name pin from PR #100).example/recipe_server: builds clean; regen produces no diff vs prior output other than a socket-path catch-up to the canonicalhero_socketsshape.Pre-existing dev-tip bit-rot left for separate cleanup
Not caused by #90 — flagged so they don't get lost:
crates/osis/examples/basic/{1_generate_code,2_use_models,4_custom_rpc_methods,5_start_server,6_rpc_test}.rsreference removed APIs (Generatorimport,DBTyped::new_with_index,reqwest).crates/derive/tests/sse_proxy.rsmissingherolib_corecrate dep.Acceptance gate (from the issue body)
lhumina_code/(excepthero_proc) on hero_rpc2 dispatchhero_rpc/crates/server/rust_rpc.rsemitter deleted; scaffolder only emits the hero_rpc2 traitemit/rust_sdk.rs+emit/bin.rs; Phase 4 deletedgenerate/rust_rpc.rs+rust/rust_client.rs+ the toggle)recipe_sdk_rpc2parallel example consolidated intorecipe_serverhero_service_scaffold.mddescribes only the hero_rpc2 pathsystem.ping(nowrpc.health)Closing.
generated/subfolders + gitignore #96