[nu-demo] hero_biz never refactored for OSIS per-domain split — Hero0Config legacy facade returns empty data even with correct URL #180

Closed
opened 2026-04-24 23:51:42 +00:00 by mik-tf · 1 comment
Owner

Symptom

The standalone HeroBiz iframe (/hero_biz/ui/c/<context>) renders the dashboard chrome correctly (sidebar, top bar, layout) but every counter shows 0 and every list (Persons, Companies, Contacts, Opportunities, Deals, Instruments, Contracts, Transactions) is empty — across all contexts (threefold, geomind, incubaid).

Meanwhile the native Business archipelago island (Dioxus WASM, hero_archipelagos/archipelagos/business) shows real data on the same VM: 6 Persons / 6 Companies / 4 Contacts / 4 Opportunities / 6 Deals / 5 Instruments / 7 Contracts / 15 Transactions for the same contexts — fetched from the same per-domain OSIS servers via the same hero_osis_sdk clients.

Root cause — architectural gap from the OSIS per-domain split

The split moved OSIS from a single monolithic hero_osis_server (one /hero_osis/rpc endpoint serving all domains) to 17 per-domain servers (hero_osis_business, hero_osis_identity, …) each on its own Unix socket. The hero_osis_sdk clients were redesigned for this:

  • hero_rpc/crates/openrpc_http_client_lib/src/lib.rs:151-155OsisClient::new(base_url, context, domain) constructs {base_url}/hero_osis_{domain}/rpc and sends X-Hero-Context: {context} on every call.
  • hero_router/src/server/routes.rs:1941-1948 — generic /:service_name/:webname proxy maps that path to the per-domain Unix socket. No /hero_osis/... aggregator route exists (and the architecture deliberately does not have one).
  • Native Business island reads IslandContext::osis_url() (router origin or relative "") and threads it to per-domain SDK clients. Works on herodemo right now.

HeroBiz iframe (hero_biz_ui Axum server) was never refactored for this split. Three concrete bugs:

  1. hero_biz_app/src/rpc.rs:42 (the WASM island variant): hardcodes a POST to /hero_osis/rpc/rpc — the legacy monolithic endpoint that no longer exists.

  2. hero_biz_ui/src/hero0/mod.rs:71-99Hero0Config holds 8 per-domain clients sharing one base_url. This was correct for the monolithic era. With per-domain split, even the right base_url can't make all 8 clients route correctly through one URL because each client needs _<domain> in its path (the SDK appends this if you pass the router-root base — but Hero0Config never passes IslandContext.osis_url() through; it reads HERO0_BASE_URL from env).

  3. hero_skills/tools/modules/services/service_biz.nu:131-132 + hero_demo/services/hero_biz.toml:14 — both hardcode HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui which is doubly wrong post-split: port 6666 is not where hero_router binds (router runs on 10.1.2.2:9988), and /hero_osis/ui is not a valid prefix in the per-domain world.

Demo experiment 2026-04-24

I tried fixing this with config alone:

  • Set HERO0_BASE_URL=http://10.1.2.2:9988 on hero_biz_ui's action env (the actual hero_router root).
  • Verified the running process picks it up: cat /proc/$pid/environ shows the new value.
  • Verified the SDK URL is reachable: curl POST http://10.1.2.2:9988/hero_osis_business/rpc -H 'X-Hero-Context: threefold' returns 6 person SIDs.
  • Verified the Hero0 facade in hero_biz_ui/src/services/mod.rs:208 actually calls BusinessClient::person_list() (not some other code path).

Result: dashboard still shows empty. Even with the right URL and the SDK doing its job upstream of the wrapper, Store::load_all_persons_for_space("threefold") returns Ok(vec![]). There is a deeper bug in hero_biz_ui::Hero0 (or its caching layer at services/mod.rs:33-90) beyond just the env-var. Tracing further would require enabling debug logs and instrumenting the request path — out of scope for a hotfix.

Conclusion

This is not a setup bug — it's a real codebase regression that the OSIS per-domain split introduced and never fixed in hero_biz_ui / hero_biz_app. The hero_biz package was effectively orphaned during the split, even though its installer (service_biz.nu) and service config (hero_demo/services/hero_biz.toml) still ship it.

Devops vision for the fix

Per the SDK's own design and the working native Business island, the canonical pattern is per-domain URL construction by the SDK, with a single router-root base_url shared across all per-domain clients. The fix in hero_biz:

  1. Drop Hero0Config legacy facade — replace with direct per-domain SDK clients (BusinessClient, ProjectsClient, IdentityClient, CalendarClient, FilesClient, CommunicationClient, FinanceClient, AiClient).

  2. Server-side (hero_biz_ui/src/services/mod.rs): Store constructs each per-domain client with base_url = HERO0_BASE_URL (router root, e.g. http://10.1.2.2:9988 or unix-socket-via-some-shim) and context = the route's context. SDK handles the rest.

  3. WASM-side (hero_biz_app/src/app.rs): accept IslandContext (already does at line 72) and pass IslandContext.osis_url() to per-domain SDK clients. Drop hero_biz_app/src/rpc.rs:42's hardcoded /hero_osis/rpc/rpc POST entirely.

  4. Installer: service_biz.nu:131-132 + hero_demo/services/hero_biz.toml:14 should set HERO0_BASE_URL to the hero_router root URL, not a service-prefixed path.

  5. Tests: smoke test that hits /hero_biz/ui/c/threefold/persons and asserts at least one row when OSIS has data.

  • home#175 — same class of bug for hero_osis_ui admin UI (no aggregator, per-domain discovery never completes)
  • home#179 — Biz iframe was loading the wrong URL /hero_biz/ (fixed); but even with the right URL, the data layer is still broken (this issue)
  • home#160 — consolidated demo state

Demo workaround on herodemo (2026-04-24)

HERO0_BASE_URL patched to http://10.1.2.2:9988 via hero_proc action.set (better than the broken default but doesn't unblock data display until the code refactor lands). Native Business island remains the working answer for now.

Signed-off-by: mik-tf

## Symptom The standalone HeroBiz iframe (`/hero_biz/ui/c/<context>`) renders the dashboard chrome correctly (sidebar, top bar, layout) but every counter shows **0** and every list (Persons, Companies, Contacts, Opportunities, Deals, Instruments, Contracts, Transactions) is empty — across all contexts (threefold, geomind, incubaid). Meanwhile the **native Business archipelago island** (Dioxus WASM, `hero_archipelagos/archipelagos/business`) shows real data on the same VM: 6 Persons / 6 Companies / 4 Contacts / 4 Opportunities / 6 Deals / 5 Instruments / 7 Contracts / 15 Transactions for the same contexts — fetched from the same per-domain OSIS servers via the same `hero_osis_sdk` clients. ## Root cause — architectural gap from the OSIS per-domain split The split moved OSIS from a single monolithic `hero_osis_server` (one `/hero_osis/rpc` endpoint serving all domains) to **17 per-domain servers** (`hero_osis_business`, `hero_osis_identity`, …) each on its own Unix socket. The `hero_osis_sdk` clients were redesigned for this: - `hero_rpc/crates/openrpc_http_client_lib/src/lib.rs:151-155` — `OsisClient::new(base_url, context, domain)` constructs `{base_url}/hero_osis_{domain}/rpc` and sends `X-Hero-Context: {context}` on every call. - `hero_router/src/server/routes.rs:1941-1948` — generic `/:service_name/:webname` proxy maps that path to the per-domain Unix socket. **No `/hero_osis/...` aggregator route exists** (and the architecture deliberately does not have one). - Native Business island reads `IslandContext::osis_url()` (router origin or relative `""`) and threads it to per-domain SDK clients. Works on herodemo right now. **HeroBiz iframe (`hero_biz_ui` Axum server) was never refactored for this split.** Three concrete bugs: 1. **`hero_biz_app/src/rpc.rs:42`** (the WASM island variant): hardcodes a POST to `/hero_osis/rpc/rpc` — the legacy monolithic endpoint that no longer exists. 2. **`hero_biz_ui/src/hero0/mod.rs:71-99`** — `Hero0Config` holds **8 per-domain clients sharing one `base_url`**. This was correct for the monolithic era. With per-domain split, even the right `base_url` can't make all 8 clients route correctly through one URL because each client needs `_<domain>` in its path (the SDK appends this if you pass the router-root base — but Hero0Config never passes `IslandContext.osis_url()` through; it reads `HERO0_BASE_URL` from env). 3. **`hero_skills/tools/modules/services/service_biz.nu:131-132`** + **`hero_demo/services/hero_biz.toml:14`** — both hardcode `HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui` which is doubly wrong post-split: port 6666 is not where hero_router binds (router runs on `10.1.2.2:9988`), and `/hero_osis/ui` is not a valid prefix in the per-domain world. ## Demo experiment 2026-04-24 I tried fixing this with config alone: - Set `HERO0_BASE_URL=http://10.1.2.2:9988` on hero_biz_ui's action env (the actual hero_router root). - Verified the running process picks it up: `cat /proc/$pid/environ` shows the new value. - Verified the SDK URL is reachable: `curl POST http://10.1.2.2:9988/hero_osis_business/rpc -H 'X-Hero-Context: threefold'` returns 6 person SIDs. - Verified the Hero0 facade in `hero_biz_ui/src/services/mod.rs:208` actually calls `BusinessClient::person_list()` (not some other code path). **Result: dashboard still shows empty.** Even with the right URL and the SDK doing its job upstream of the wrapper, `Store::load_all_persons_for_space("threefold")` returns `Ok(vec![])`. There is a deeper bug in `hero_biz_ui::Hero0` (or its caching layer at `services/mod.rs:33-90`) beyond just the env-var. Tracing further would require enabling debug logs and instrumenting the request path — out of scope for a hotfix. ## Conclusion This is **not a setup bug** — it's a **real codebase regression** that the OSIS per-domain split introduced and never fixed in `hero_biz_ui` / `hero_biz_app`. The hero_biz package was effectively orphaned during the split, even though its installer (`service_biz.nu`) and service config (`hero_demo/services/hero_biz.toml`) still ship it. ## Devops vision for the fix Per the SDK's own design and the working native Business island, the canonical pattern is **per-domain URL construction by the SDK**, with a single router-root `base_url` shared across all per-domain clients. The fix in `hero_biz`: 1. **Drop `Hero0Config` legacy facade** — replace with direct per-domain SDK clients (`BusinessClient`, `ProjectsClient`, `IdentityClient`, `CalendarClient`, `FilesClient`, `CommunicationClient`, `FinanceClient`, `AiClient`). 2. **Server-side (`hero_biz_ui/src/services/mod.rs`)**: `Store` constructs each per-domain client with `base_url` = `HERO0_BASE_URL` (router root, e.g. `http://10.1.2.2:9988` or unix-socket-via-some-shim) and `context` = the route's context. SDK handles the rest. 3. **WASM-side (`hero_biz_app/src/app.rs`)**: accept `IslandContext` (already does at line 72) and pass `IslandContext.osis_url()` to per-domain SDK clients. Drop `hero_biz_app/src/rpc.rs:42`'s hardcoded `/hero_osis/rpc/rpc` POST entirely. 4. **Installer**: `service_biz.nu:131-132` + `hero_demo/services/hero_biz.toml:14` should set `HERO0_BASE_URL` to the hero_router root URL, not a service-prefixed path. 5. **Tests**: smoke test that hits `/hero_biz/ui/c/threefold/persons` and asserts at least one row when OSIS has data. ## Related - [home#175](https://forge.ourworld.tf/lhumina_code/home/issues/175) — same class of bug for `hero_osis_ui` admin UI (no aggregator, per-domain discovery never completes) - [home#179](https://forge.ourworld.tf/lhumina_code/home/issues/179) — Biz iframe was loading the wrong URL `/hero_biz/` (fixed); but even with the right URL, the data layer is still broken (this issue) - [home#160](https://forge.ourworld.tf/lhumina_code/home/issues/160) — consolidated demo state ## Demo workaround on herodemo (2026-04-24) `HERO0_BASE_URL` patched to `http://10.1.2.2:9988` via `hero_proc action.set` (better than the broken default but doesn't unblock data display until the code refactor lands). Native Business island remains the working answer for now. Signed-off-by: mik-tf
Author
Owner

Resolved

Landed across three repos on development (squash commits):

  • hero_biz c3d338f — per-domain SDK migration: drop Hero0Config facade, rewrite Store with BusinessClient/ProjectsClient lazy-cached per context, drop the WASM osis_rpc raw POST, rewrite all 9 tabs onto a typed BizServices layer, add hero_biz_app to workspace.
  • hero_skills 7d47605service_biz.nu derives HERO0_BASE_URL from HERO_ROUTER_ADDRESS/HERO_ROUTER_UI_PORT (router root only; no /hero_osis/ui suffix).
  • hero_demo 8f4159b — drop the stale HERO0_BASE_URL = http://127.0.0.1:6666/hero_osis/ui pin from services/hero_biz.toml; runbook §4.4 / §8 / §13 updated.

Root cause

The architecture work (per-domain SDK, drop Hero0Config, fix WASM rpc, drop URL suffix) was all needed, but the specific blocker for live data was a Cargo.lock git-pin: hero_rpc_client was pinned to commit bbd118ca, which still built endpoints with the legacy /rpc/{context} URL format. hero_router post-split has no route for that shape and 404s; the hero_biz_ui handler then unwrap_or_default()'d the error and rendered an empty table — exactly the symptom this issue described as the "deeper bug." cargo update -p hero_rpc_client advanced the pin to 38a09290, which contains ed6e7eb's switch to /hero_osis_{domain}/rpc + X-Hero-Context header. This is exactly the trap the feedback_cargo_lock_git_deps skill warns about.

Verification on herodemo (2026-04-26)

Path Result
/hero_biz/ui/c/threefold/persons (via hero_router) 6 rows
/hero_biz/ui/c/threefold/{companies,contacts,opportunities,deals,instruments,contracts} All non-empty
/hero_biz/ui/c/geomind/persons and /incubaid/persons Different counts — context switching works
https://herodemo.gent01.grid.tf/hero_osis_business/rpc (auth-bypassed RPC path) Returns SIDs end-to-end through nginx + hero_router + per-domain socket
tracing logs person_list(threefold) -> 6 SIDs, load_all_persons -> 6 persons

User confirmed in browser that the Biz iframe is seeded and renders.

Workflow note for future deployers

The runbook §3 already says to export HERO_ROUTER_ADDRESS=10.1.2.2 and HERO_ROUTER_UI_PORT=9990 in ~/hero/cfg/env/env.sh on TF Grid VMs. herodemo's env.sh was missing both — added during this deploy. Without them, service_biz.nu's default falls back to 127.0.0.1:9988 which is unbound on TF Grid VMs (router is on 10.1.2.2:9990, nginx on 10.1.2.2:9988). Worth a one-line check in install_core later.

Signed-off-by: mik-tf

## Resolved Landed across three repos on `development` (squash commits): - `hero_biz` [c3d338f](https://forge.ourworld.tf/lhumina_code/hero_biz/commit/c3d338f) — per-domain SDK migration: drop `Hero0Config` facade, rewrite `Store` with `BusinessClient`/`ProjectsClient` lazy-cached per context, drop the WASM `osis_rpc` raw POST, rewrite all 9 tabs onto a typed `BizServices` layer, add `hero_biz_app` to workspace. - `hero_skills` [7d47605](https://forge.ourworld.tf/lhumina_code/hero_skills/commit/7d47605) — `service_biz.nu` derives `HERO0_BASE_URL` from `HERO_ROUTER_ADDRESS`/`HERO_ROUTER_UI_PORT` (router root only; no `/hero_osis/ui` suffix). - `hero_demo` [8f4159b](https://forge.ourworld.tf/lhumina_code/hero_demo/commit/8f4159b) — drop the stale `HERO0_BASE_URL = http://127.0.0.1:6666/hero_osis/ui` pin from `services/hero_biz.toml`; runbook §4.4 / §8 / §13 updated. ## Root cause The architecture work (per-domain SDK, drop Hero0Config, fix WASM rpc, drop URL suffix) was all needed, but the **specific blocker** for live data was a `Cargo.lock` git-pin: `hero_rpc_client` was pinned to commit `bbd118ca`, which still built endpoints with the legacy `/rpc/{context}` URL format. hero_router post-split has no route for that shape and 404s; the hero_biz_ui handler then `unwrap_or_default()`'d the error and rendered an empty table — exactly the symptom this issue described as the "deeper bug." `cargo update -p hero_rpc_client` advanced the pin to `38a09290`, which contains [`ed6e7eb`](https://forge.ourworld.tf/lhumina_code/hero_rpc/commit/ed6e7eb)'s switch to `/hero_osis_{domain}/rpc` + `X-Hero-Context` header. This is exactly the trap the `feedback_cargo_lock_git_deps` skill warns about. ## Verification on herodemo (2026-04-26) | Path | Result | |------|--------| | `/hero_biz/ui/c/threefold/persons` (via hero_router) | 6 rows | | `/hero_biz/ui/c/threefold/{companies,contacts,opportunities,deals,instruments,contracts}` | All non-empty | | `/hero_biz/ui/c/geomind/persons` and `/incubaid/persons` | Different counts — context switching works | | `https://herodemo.gent01.grid.tf/hero_osis_business/rpc` (auth-bypassed RPC path) | Returns SIDs end-to-end through nginx + hero_router + per-domain socket | | `tracing` logs | `person_list(threefold) -> 6 SIDs`, `load_all_persons -> 6 persons` | User confirmed in browser that the Biz iframe is seeded and renders. ## Workflow note for future deployers The runbook §3 already says to export `HERO_ROUTER_ADDRESS=10.1.2.2` and `HERO_ROUTER_UI_PORT=9990` in `~/hero/cfg/env/env.sh` on TF Grid VMs. herodemo's env.sh was missing both — added during this deploy. Without them, `service_biz.nu`'s default falls back to `127.0.0.1:9988` which is unbound on TF Grid VMs (router is on `10.1.2.2:9990`, nginx on `10.1.2.2:9988`). Worth a one-line check in `install_core` later. Signed-off-by: mik-tf
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/home#180
No description provided.