service_biz.nu — hero_biz server + UI lifecycle module #86

Closed
opened 2026-04-19 19:44:13 +00:00 by mahmoud · 3 comments
Owner

Child of #75.

Objective

Add tools/modules/services/service_biz.nu implementing install | start | stop | status for the hero_biz service.

Scope

  • Repo: ssh://git@forge.ourworld.tf/lhumina_code/hero_biz.git
  • Binaries (per buildenv.sh): hero_biz, hero_biz_ui
  • Runtime actions (both registered with hero_proc):
    • hero_biz — server daemon (hero_biz binary without args = run_server()), binds $HERO_SOCKET_DIR/hero_biz/rpc.sock
    • hero_biz_ui — UI daemon (hero_biz_ui binary), binds $HERO_SOCKET_DIR/hero_biz/ui.sock
  • TOML (misleading — only declares [server] but the CLI's self_start() actually registers both actions): lhumina_code/hero_zero/services/hero_biz.toml
  • Workspace layout: virtual workspace (no root [package]) — plain cargo build --release covers both binaries.
  • Subcommand: none. hero_biz with no args = server. --start / --stop are the self-registration shortcuts we will NOT use (nu module does the registration).
  • Env on the server action (from the TOML's [env] block, applies to server):
    • RUST_LOG=info
    • BASE_PATH=/hero_biz/ui — URL prefix for the biz UI when reached through hero_router
    • HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui — HTTP-over-TCP URL for hero_osis UI through hero_router (NOT a unix socket — this is the first Tier 1 service to use an HTTP dep)
  • Dependencies: depends_on = ["hero_osis_identity", "hero_proxy_ui"] — two deps, new pattern (books had only embedder).
    • hero_osis_identity — an OSIS domain server (one of the 18 OSIS variants). Socket at $HERO_SOCKET_DIR/hero_osis_identity/rpc.sock.
    • hero_proxy_ui — hero_proxy's UI action. Soft dep via HERO0_BASE_URL (TCP URL, not socket).

Acceptance criteria

  • use services/mod.nu * makes service_biz available.
  • service_biz install [--root] [--update] clones + builds both binaries, installs to ~/hero/bin/ (or /root/hero/bin/ with --root).
  • service_biz start [--reset] [--root] [--update] registers both actions (hero_biz, hero_biz_ui) + the hero_biz service, starts, prints summary with both sockets + http+unix://…/ui.sock/ URL. Idempotent without --reset.
  • service_biz status [--root] reports state.
  • service_biz stop [--root] cleanly unregisters.
  • Server action env contains RUST_LOG, BASE_PATH=/hero_biz/ui, and HERO0_BASE_URL resolved at register time via a helper (see spec decisions below).
  • Multi-dep preflight: warn (don't fail) if $HERO_SOCKET_DIR/hero_osis_identity/rpc.sock is missing (hero_biz reads from it). DO NOT preflight hero_proxy_ui — the HTTP dep is less visible and failure mode is cosmetic (UI links break, service still runs).
  • Smoke-tested on Hetzner: install → start --reset → status running → stop.

Spec decisions to confirm

  1. HERO0_BASE_URL resolution: TOML uses the hardcoded loopback http://127.0.0.1:6666/hero_osis/ui. Should we:

    • (a) Pass the TOML value verbatim — simplest, works on single-host setups.
    • (b) Compute from svc_home $root somehow — can't, because this is a TCP URL not a socket path.
    • Spec should probe hero_biz source to confirm whether the URL is used verbatim or parsed, then pick (a).
  2. Multi-dep preflight: first service with 2 depends_on entries. Warn only on osis_identity socket absence, not on proxy_ui. Rationale: hero_biz reads osis_identity over RPC (hard dependency for data); proxy_ui is only referenced in a UI link generation (soft dep). Spec should confirm this by grepping for how the deps are consumed in hero_biz source.

  3. Action script shape: script: $bin for both (no subcommand). hero_biz with no args hits run_server(); hero_biz_ui is a straightforward tokio main.

Template & references

  • service_books.nu (PR #81) — for the embedder-preflight pattern and env-at-register-time pattern. Adapt its svx_check_embedder to svx_check_osis_identity.
  • service_whiteboard.nu (PR #83) — base shape (virtual workspace, two actions, no subcommand).
  • service_collab.nu (PR #85) — recent simplest copy-rename.
  • Authoritative reference: hero_biz/crates/hero_biz/src/main.rs build_service_definition() — the CLI already encodes the correct registration spec; the nu module should mirror it exactly.
Child of #75. ## Objective Add `tools/modules/services/service_biz.nu` implementing `install | start | stop | status` for the **hero_biz** service. ## Scope - **Repo**: `ssh://git@forge.ourworld.tf/lhumina_code/hero_biz.git` - **Binaries** (per `buildenv.sh`): `hero_biz`, `hero_biz_ui` - **Runtime actions** (both registered with hero_proc): - `hero_biz` — server daemon (`hero_biz` binary without args = `run_server()`), binds `$HERO_SOCKET_DIR/hero_biz/rpc.sock` - `hero_biz_ui` — UI daemon (`hero_biz_ui` binary), binds `$HERO_SOCKET_DIR/hero_biz/ui.sock` - **TOML** (misleading — only declares `[server]` but the CLI's `self_start()` actually registers both actions): `lhumina_code/hero_zero/services/hero_biz.toml` - **Workspace layout**: virtual workspace (no root `[package]`) — plain `cargo build --release` covers both binaries. - **Subcommand**: none. `hero_biz` with no args = server. `--start` / `--stop` are the self-registration shortcuts we will NOT use (nu module does the registration). - **Env on the server action** (from the TOML's `[env]` block, applies to server): - `RUST_LOG=info` - `BASE_PATH=/hero_biz/ui` — URL prefix for the biz UI when reached through hero_router - `HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui` — HTTP-over-TCP URL for hero_osis UI through hero_router (NOT a unix socket — this is the first Tier 1 service to use an HTTP dep) - **Dependencies**: `depends_on = ["hero_osis_identity", "hero_proxy_ui"]` — two deps, new pattern (books had only embedder). - `hero_osis_identity` — an OSIS domain server (one of the 18 OSIS variants). Socket at `$HERO_SOCKET_DIR/hero_osis_identity/rpc.sock`. - `hero_proxy_ui` — hero_proxy's UI action. Soft dep via `HERO0_BASE_URL` (TCP URL, not socket). ## Acceptance criteria - [ ] `use services/mod.nu *` makes `service_biz` available. - [ ] `service_biz install [--root] [--update]` clones + builds both binaries, installs to `~/hero/bin/` (or `/root/hero/bin/` with `--root`). - [ ] `service_biz start [--reset] [--root] [--update]` registers both actions (`hero_biz`, `hero_biz_ui`) + the `hero_biz` service, starts, prints summary with both sockets + `http+unix://…/ui.sock/` URL. Idempotent without `--reset`. - [ ] `service_biz status [--root]` reports state. - [ ] `service_biz stop [--root]` cleanly unregisters. - [ ] Server action env contains `RUST_LOG`, `BASE_PATH=/hero_biz/ui`, and `HERO0_BASE_URL` resolved at register time via a helper (see spec decisions below). - [ ] Multi-dep preflight: warn (don't fail) if `$HERO_SOCKET_DIR/hero_osis_identity/rpc.sock` is missing (hero_biz reads from it). DO NOT preflight `hero_proxy_ui` — the HTTP dep is less visible and failure mode is cosmetic (UI links break, service still runs). - [ ] Smoke-tested on Hetzner: install → start --reset → status running → stop. ## Spec decisions to confirm 1. **`HERO0_BASE_URL` resolution**: TOML uses the hardcoded loopback `http://127.0.0.1:6666/hero_osis/ui`. Should we: - (a) Pass the TOML value verbatim — simplest, works on single-host setups. - (b) Compute from `svc_home $root` somehow — can't, because this is a TCP URL not a socket path. - Spec should probe hero_biz source to confirm whether the URL is used verbatim or parsed, then pick (a). 2. **Multi-dep preflight**: first service with 2 `depends_on` entries. Warn only on osis_identity socket absence, not on proxy_ui. Rationale: hero_biz reads osis_identity over RPC (hard dependency for data); proxy_ui is only referenced in a UI link generation (soft dep). Spec should confirm this by grepping for how the deps are consumed in hero_biz source. 3. **Action script shape**: `script: $bin` for both (no subcommand). `hero_biz` with no args hits `run_server()`; `hero_biz_ui` is a straightforward tokio main. ## Template & references - `service_books.nu` (PR #81) — for the embedder-preflight pattern and env-at-register-time pattern. Adapt its `svx_check_embedder` to `svx_check_osis_identity`. - `service_whiteboard.nu` (PR #83) — base shape (virtual workspace, two actions, no subcommand). - `service_collab.nu` (PR #85) — recent simplest copy-rename. - Authoritative reference: `hero_biz/crates/hero_biz/src/main.rs` `build_service_definition()` — the CLI already encodes the correct registration spec; the nu module should mirror it exactly.
mahmoud self-assigned this 2026-04-19 19:44:59 +00:00
mahmoud added this to the ACTIVE project 2026-04-19 19:45:02 +00:00
mahmoud added this to the now milestone 2026-04-19 19:45:04 +00:00
Author
Owner

Implementation Spec for Issue #86

Objective

Add service_biz.nu — a hero_proc lifecycle module for the hero_biz service (server + UI) that mirrors the Rust-side build_service_definition() and adds a soft-preflight helper for the hero_osis_identity dependency declared by the TOML.

Requirements

  • Add tools/modules/services/service_biz.nu exporting install, start, stop, status — shape identical to the merged service_whiteboard.nu / service_collab.nu modules.
  • Binary set ["hero_biz" "hero_biz_ui"] — hero_biz IS the server; no separate _server binary (confirmed by hero_biz/buildenv.sh:8 and hero_biz/Cargo.toml:3-7).
  • Two hero_proc actions with names taken verbatim from the Rust source: hero_biz (server) and hero_biz_ui. Server action name is hero_biz, NOT hero_biz_server (see hero_biz/crates/hero_biz/src/main.rs:103) — first service we've shipped where the server action drops the _server suffix.
  • Action specs must match build_service_definition() in hero_biz/crates/hero_biz/src/main.rs:93-179 field-for-field (see step 4–5 below for citations).
  • Multi-dep preflight: warn (do NOT fail) when $HERO_SOCKET_DIR/hero_osis_identity/rpc.sock is missing. Do NOT preflight hero_proxy_ui — the Hero stack convention surfaces UIs through hero_router/hero_proxy_ui, and no code in hero_biz/ reads from a proxy_ui socket.
  • BASE_PATH=/hero_biz/ui and HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui are declared on the hero_zero TOML [env] block (hero_zero/services/hero_biz.toml:12-14). Grep confirms both are consumed ONLY by hero_biz_ui (crates/hero_biz_ui/src/web/server.rs:89, crates/hero_biz_ui/src/hero0/mod.rs:93, crates/hero_biz_ui/src/services/mod.rs:36,50) — zero matches inside crates/hero_biz/. Set them on the UI action only. Pass both verbatim as string literals.
  • RUST_LOG: "info" on both actions, per main.rs:107 and main.rs:144.
  • Register the new module from ./mod.nu.

Files to Modify/Create

  • tools/modules/services/service_biz.nu (new)
  • tools/modules/services/mod.nu (+1 line: export use service_biz.nu appended after the service_collab.nu line)

Implementation Plan

Step 1: Copy service_collab.nu skeleton

Files: tools/modules/services/service_biz.nu

  • Start from service_collab.nu (the most minimal two-action module merged to date).
  • Use service_books.nu:185-202 (svx_check_embedder) as shape template for the preflight helper.
  • Use service_books.nu:72-84 as shape template for the UI action's multi-entry env block.
    Dependencies: none.

Step 2: Module header docstring

Files: tools/modules/services/service_biz.nu
Mirror service_collab.nu:1-44 structure. Call out:

  • Two actions: hero_biz (the server — no _server suffix; the CLI binary doubles as the server daemon when run with no args, see hero_biz/crates/hero_biz/src/main.rs:46) and hero_biz_ui.
  • Sockets at $HERO_SOCKET_DIR/hero_biz/rpc.sock and $HERO_SOCKET_DIR/hero_biz/ui.sock.
  • Soft dependency on hero_osis_identity: hero_biz_ui reaches OSIS identity through HERO0_BASE_URL, so without that service the /hero0/* surface returns errors — but the server + UI themselves still bind, bootstrap, and serve. Do not hard-fail.
  • hero_zero/services/hero_biz.toml declares only [server] — this nu module is the authoritative place recording that hero_biz is a two-action service, matching what hero_biz --start installs via build_service_definition().
  • BASE_PATH / HERO0_BASE_URL brittleness note (see Notes section).

Step 3: Constants

Files: tools/modules/services/service_biz.nu

const SVX_SERVICE_NAME = "hero_biz"
const SVX_FORGE_LOC    = "lhumina_code/hero_biz"
const SVX_BINARIES     = ["hero_biz" "hero_biz_ui"]
const SVX_ACTIONS      = ["hero_biz" "hero_biz_ui"]

Note SVX_BINARIES == SVX_ACTIONS — both installed binaries are hero_proc actions; there is no CLI-only shim (unlike hero_os / hero_books / hero_collab / hero_whiteboard which all ship a separate CLI binary).

Step 4: svx_server_action — match main.rs:103-137

Files: tools/modules/services/service_biz.nu

  • name: "hero_biz" (line 103)
  • script: (svc_bin "hero_biz" $root) (line 95)
  • interpreter: "exec" (line 104)
  • env: {RUST_LOG: "info"} (line 107)
  • is_process: true (line 115)
  • retry_policy: max_attempts: 5, delay_ms: 2000, backoff: true, max_delay_ms: 60000, start_timeout_ms: 30000 (lines 109-113). Add stability_period_ms: 30000 to match nu convention (Rust builder leaves it unset — prior nu modules all include it explicitly).
  • stop_signal: "SIGTERM" (line 105), stop_timeout_ms: 10000 (line 106), timeout_ms: 0, tty: false.
  • kill_other: action: "", process_filters: [], port: [], socket: [$"($sock_base)/hero_biz/rpc.sock"] (lines 118-123).
  • health_checks: action: "hero_biz", openrpc_socket: $"($sock_base)/hero_biz/rpc.sock", policy interval_ms: 2000, timeout_ms: 5000, retries: 3, start_period_ms: 5000 (lines 125-137; diverges from prior services' 3000 — match the Rust source).

Step 5: svx_ui_action — match main.rs:140-172

Files: tools/modules/services/service_biz.nu

  • name: "hero_biz_ui" (line 140), script: (svc_bin "hero_biz_ui" $root) (line 96).
  • interpreter: "exec" (line 141).
  • env — three entries (this is the UI-specific env; server action stays on RUST_LOG only):
    • RUST_LOG: "info" (line 144).
    • BASE_PATH: "/hero_biz/ui" — verbatim from hero_zero/services/hero_biz.toml:13. Consumer: hero_biz_ui/src/web/server.rs:89 (plain std::env::var, no validation).
    • HERO0_BASE_URL: "http://127.0.0.1:6666/hero_osis/ui" — verbatim from hero_zero/services/hero_biz.toml:14. Consumers: hero_biz_ui/src/hero0/mod.rs:93, .../services/mod.rs:36,50 (plain std::env::var, no validation).
  • is_process: true (line 150).
  • retry_policy: max_attempts: 3, delay_ms: 2000, backoff: true (lines 146-148). Rust builder leaves max_delay_ms, start_timeout_ms, stability_period_ms unset; include them with the same values used by the UI action in service_collab.nu:116-121 (max_delay_ms: 60000, start_timeout_ms: 30000, stability_period_ms: 30000) for consistency. Add a code comment noting the Rust builder relies on its own defaults here.
  • stop_signal: "SIGTERM" (line 142), stop_timeout_ms: 5000 (line 143), timeout_ms: 0, tty: false.
  • kill_other: socket: [$"($sock_base)/hero_biz/ui.sock"] (lines 153-158).
  • health_checks: action: "hero_biz_ui", openrpc_socket: $"($sock_base)/hero_biz/ui.sock", policy interval_ms: 2000, timeout_ms: 5000, retries: 3, start_period_ms: 5000 (lines 160-172; UI interval_ms: 2000 diverges from prior services' 3000 — match Rust source).

Step 6: svx_service_config

Files: tools/modules/services/service_biz.nu
Mirror service_collab.nu:146-158:

  • context_name: "core", name: $SVX_SERVICE_NAME, actions: $SVX_ACTIONS, class: "system", critical: false, status: "start".
  • description: "Hero Biz — business data management backend" (verbatim from main.rs:175).

Step 7: svx_drop_registration

Files: tools/modules/services/service_biz.nu
Identical to service_collab.nu:161-167, parameterised on $SVX_SERVICE_NAME and $SVX_ACTIONS. No changes needed beyond constant substitution.

Step 8: svx_check_osis_identity preflight helper

Files: tools/modules/services/service_biz.nu
Model on service_books.nu:185-202 (svx_check_embedder). Keep inside service_biz.nu; do not touch lib.nu.

  • Compute $"(svc_sock_base $root)/hero_osis_identity/rpc.sock".
  • Check existence via path exists (or sudo test -S when svc_need_sudo $root).
  • If missing, print warning + remediation:
    ⚠ hero_osis_identity socket not found at <path>
      hero_biz_ui reaches OSIS identity through HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui
      via hero_router; until hero_osis_identity is up, the UI's /hero0/* surface will
      return errors (the server and UI themselves still bind and serve).
      Bring up OSIS identity when ready. The canonical service_osis.nu lifecycle module
      is still in progress (see #75 tracker); for now use the repo's own CLI:
        hero_osis --start  (identity domain)
      Once service_osis.nu lands this will become:
        service_osis start --domain identity<flag>
    
  • Do NOT preflight hero_proxy_ui — add a code comment explaining the decision so future maintainers don't "fix" it.

Step 9: install / start / stop / status

Files: tools/modules/services/service_biz.nu
Mirror service_collab.nu:176-317 with these deltas:

  • Header comments for each function mention the OSIS identity preflight.
  • In start, after svc_require_proc + bin-verify steps and BEFORE svx_drop_registration, call svx_check_osis_identity $root (same position where service_books.nu:287 calls svx_check_embedder).
  • Summary block uses "hero_biz" and "hero_biz_ui" everywhere. Add extra printed lines showing the configured HERO0_BASE_URL and BASE_PATH so the operator can spot stale or wrong values at a glance:
    print $"  base path: /hero_biz/ui"
    print $"  hero0 url: http://127.0.0.1:6666/hero_osis/ui"
    

Step 10: Register in mod.nu

Files: tools/modules/services/mod.nu
Append export use service_biz.nu as a new line after service_collab.nu.

Step 11: Syntax check

  • /root/hero/bin/nu -c "source tools/modules/services/service_biz.nu; print parse-ok" must succeed.
  • /root/hero/bin/nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_biz '" must show four subcommands.

Step 12: Smoke test on Hetzner

  • service_proc start --root (prereq).
  • service_biz install --root — expect cargo build + 2 binaries in /root/hero/bin/.
  • service_biz start --reset --root — expect osis_identity warning (socket absent; first deployment), both actions registered, both sockets live, state: running, summary shows base path / hero0 url lines.
  • service_biz status --root — record shows running, 0 restarts.
  • curl --unix-socket /root/hero/var/sockets/hero_biz/rpc.sock http://localhost/ — HTTP response.
  • curl --unix-socket /root/hero/var/sockets/hero_biz/ui.sock http://localhost/ — HTTP response.
  • service_biz start --root — idempotent "already running".
  • Observe 15s — no new restarts.
  • service_biz stop --root — clean unregistration.
  • Post-stop service_biz status --root — expected service 'hero_biz' not found.
  • Cleanup: service_proc stop --root.

Acceptance Criteria

  • tools/modules/services/service_biz.nu exists and exports install, start, stop, status — shape matches prior two-action modules.
  • tools/modules/services/mod.nu re-exports service_biz.nu.
  • Action specs match hero_biz/crates/hero_biz/src/main.rs:103-172 exactly: server action name is hero_biz (not hero_biz_server); server health start_period_ms: 5000; UI health interval_ms: 2000; UI stop_timeout_ms: 5000.
  • UI action env sets RUST_LOG, BASE_PATH="/hero_biz/ui", HERO0_BASE_URL="http://127.0.0.1:6666/hero_osis/ui" verbatim.
  • Server action env sets only RUST_LOG: "info".
  • svx_check_osis_identity warns but never errors when the socket is absent; silently passes when present.
  • No preflight is done for hero_proxy_ui (TOML dep is advisory only).
  • lib.nu unchanged.
  • nu -c 'source .../service_biz.nu; print parse-ok' succeeds.
  • Smoke test passes the 10 scenarios above on the Hetzner box.
  • service_biz start --reset is idempotent and leaves the service running.

Notes

  • hero_zero TOML misleading: hero_zero/services/hero_biz.toml declares only [server] even though the CLI (hero_biz --start) registers BOTH hero_biz and hero_biz_ui as hero_proc actions. The nu module's SVX_ACTIONS is therefore the authoritative list; do not read the TOML to derive it.
  • Multi-dep preflight decisions: TOML depends_on = ["hero_osis_identity", "hero_proxy_ui"]. Only hero_osis_identity gets a warning helper — grep confirms the UI talks to it via HERO0_BASE_URL. hero_proxy_ui is advisory (operators surface UIs through hero_router's socket discovery; no code path in hero_biz/ reads from a proxy_ui socket). Both warnings are soft — missing hero_osis_identity degrades the /hero0/* surface but does not prevent bind.
  • HERO0_BASE_URL brittleness: the URL is hardcoded to http://127.0.0.1:6666/hero_osis/ui. The port (6666) and host (127.0.0.1) are baked in. If hero_router ever moves off 6666, or hero_osis relocates the identity mount path, this URL needs to be flipped. The spec passes the value verbatim from the TOML — option (a) per the issue brief. Document this in the module header comment; a future iteration can read HERO_ROUTER_PORT / construct the URL from svc_sock_base once hero_osis has a lifecycle module.
  • BASE_PATH is a URL path: not a filesystem path; do not try to svc_sock_base-substitute it. Pass /hero_biz/ui verbatim.
  • Rust vs nu env divergence: the Rust build_service_definition() only sets RUST_LOG on each action — BASE_PATH and HERO0_BASE_URL are supplied by the TOML [env] block when hero_zero is the registrant. When this nu module is the registrant, we must supply them ourselves. Grep shows both are consumed only by hero_biz_ui, so set them on the UI action only and leave the server action clean.
  • Smoke-test plan: run on Hetzner with hero_proc + hero_router already up; the OSIS identity preflight warning path is exercised by simply NOT having hero_osis_identity installed (the current state of the stack at merge time) — the warning must fire but service_biz start must still bring the service to running.

Critical Files for Implementation

  • /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_collab.nu
  • /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_books.nu
  • /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/mod.nu
  • /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/lib.nu
  • /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_biz/crates/hero_biz/src/main.rs
## Implementation Spec for Issue #86 ### Objective Add `service_biz.nu` — a hero_proc lifecycle module for the `hero_biz` service (server + UI) that mirrors the Rust-side `build_service_definition()` and adds a soft-preflight helper for the `hero_osis_identity` dependency declared by the TOML. ### Requirements - Add `tools/modules/services/service_biz.nu` exporting `install`, `start`, `stop`, `status` — shape identical to the merged `service_whiteboard.nu` / `service_collab.nu` modules. - Binary set `["hero_biz" "hero_biz_ui"]` — hero_biz IS the server; no separate `_server` binary (confirmed by `hero_biz/buildenv.sh:8` and `hero_biz/Cargo.toml:3-7`). - Two hero_proc actions with names taken verbatim from the Rust source: `hero_biz` (server) and `hero_biz_ui`. Server action name is `hero_biz`, **NOT** `hero_biz_server` (see `hero_biz/crates/hero_biz/src/main.rs:103`) — first service we've shipped where the server action drops the `_server` suffix. - Action specs must match `build_service_definition()` in `hero_biz/crates/hero_biz/src/main.rs:93-179` field-for-field (see step 4–5 below for citations). - Multi-dep preflight: warn (do NOT fail) when `$HERO_SOCKET_DIR/hero_osis_identity/rpc.sock` is missing. Do NOT preflight `hero_proxy_ui` — the Hero stack convention surfaces UIs through hero_router/hero_proxy_ui, and no code in `hero_biz/` reads from a proxy_ui socket. - `BASE_PATH=/hero_biz/ui` and `HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui` are declared on the hero_zero TOML `[env]` block (`hero_zero/services/hero_biz.toml:12-14`). Grep confirms both are consumed ONLY by `hero_biz_ui` (`crates/hero_biz_ui/src/web/server.rs:89`, `crates/hero_biz_ui/src/hero0/mod.rs:93`, `crates/hero_biz_ui/src/services/mod.rs:36,50`) — zero matches inside `crates/hero_biz/`. Set them on the **UI action only**. Pass both verbatim as string literals. - `RUST_LOG: "info"` on both actions, per `main.rs:107` and `main.rs:144`. - Register the new module from `./mod.nu`. ### Files to Modify/Create - `tools/modules/services/service_biz.nu` (new) - `tools/modules/services/mod.nu` (+1 line: `export use service_biz.nu` appended after the `service_collab.nu` line) ### Implementation Plan #### Step 1: Copy `service_collab.nu` skeleton Files: `tools/modules/services/service_biz.nu` - Start from `service_collab.nu` (the most minimal two-action module merged to date). - Use `service_books.nu:185-202` (`svx_check_embedder`) as shape template for the preflight helper. - Use `service_books.nu:72-84` as shape template for the UI action's multi-entry env block. Dependencies: none. #### Step 2: Module header docstring Files: `tools/modules/services/service_biz.nu` Mirror `service_collab.nu:1-44` structure. Call out: - Two actions: `hero_biz` (the server — no `_server` suffix; the CLI binary doubles as the server daemon when run with no args, see `hero_biz/crates/hero_biz/src/main.rs:46`) and `hero_biz_ui`. - Sockets at `$HERO_SOCKET_DIR/hero_biz/rpc.sock` and `$HERO_SOCKET_DIR/hero_biz/ui.sock`. - Soft dependency on `hero_osis_identity`: `hero_biz_ui` reaches OSIS identity through `HERO0_BASE_URL`, so without that service the `/hero0/*` surface returns errors — but the server + UI themselves still bind, bootstrap, and serve. Do not hard-fail. - `hero_zero/services/hero_biz.toml` declares only `[server]` — this nu module is the authoritative place recording that hero_biz is a two-action service, matching what `hero_biz --start` installs via `build_service_definition()`. - `BASE_PATH` / `HERO0_BASE_URL` brittleness note (see Notes section). #### Step 3: Constants Files: `tools/modules/services/service_biz.nu` ``` const SVX_SERVICE_NAME = "hero_biz" const SVX_FORGE_LOC = "lhumina_code/hero_biz" const SVX_BINARIES = ["hero_biz" "hero_biz_ui"] const SVX_ACTIONS = ["hero_biz" "hero_biz_ui"] ``` Note `SVX_BINARIES == SVX_ACTIONS` — both installed binaries are hero_proc actions; there is no CLI-only shim (unlike hero_os / hero_books / hero_collab / hero_whiteboard which all ship a separate CLI binary). #### Step 4: `svx_server_action` — match `main.rs:103-137` Files: `tools/modules/services/service_biz.nu` - `name: "hero_biz"` (line 103) - `script: (svc_bin "hero_biz" $root)` (line 95) - `interpreter: "exec"` (line 104) - `env: {RUST_LOG: "info"}` (line 107) - `is_process: true` (line 115) - `retry_policy`: `max_attempts: 5`, `delay_ms: 2000`, `backoff: true`, `max_delay_ms: 60000`, `start_timeout_ms: 30000` (lines 109-113). Add `stability_period_ms: 30000` to match nu convention (Rust builder leaves it unset — prior nu modules all include it explicitly). - `stop_signal: "SIGTERM"` (line 105), `stop_timeout_ms: 10000` (line 106), `timeout_ms: 0`, `tty: false`. - `kill_other`: `action: ""`, `process_filters: []`, `port: []`, `socket: [$"($sock_base)/hero_biz/rpc.sock"]` (lines 118-123). - `health_checks`: `action: "hero_biz"`, `openrpc_socket: $"($sock_base)/hero_biz/rpc.sock"`, policy `interval_ms: 2000`, `timeout_ms: 5000`, `retries: 3`, **`start_period_ms: 5000`** (lines 125-137; diverges from prior services' 3000 — match the Rust source). #### Step 5: `svx_ui_action` — match `main.rs:140-172` Files: `tools/modules/services/service_biz.nu` - `name: "hero_biz_ui"` (line 140), `script: (svc_bin "hero_biz_ui" $root)` (line 96). - `interpreter: "exec"` (line 141). - **env — three entries** (this is the UI-specific env; server action stays on `RUST_LOG` only): - `RUST_LOG: "info"` (line 144). - `BASE_PATH: "/hero_biz/ui"` — verbatim from `hero_zero/services/hero_biz.toml:13`. Consumer: `hero_biz_ui/src/web/server.rs:89` (plain `std::env::var`, no validation). - `HERO0_BASE_URL: "http://127.0.0.1:6666/hero_osis/ui"` — verbatim from `hero_zero/services/hero_biz.toml:14`. Consumers: `hero_biz_ui/src/hero0/mod.rs:93`, `.../services/mod.rs:36,50` (plain `std::env::var`, no validation). - `is_process: true` (line 150). - `retry_policy`: `max_attempts: 3`, `delay_ms: 2000`, `backoff: true` (lines 146-148). Rust builder leaves `max_delay_ms`, `start_timeout_ms`, `stability_period_ms` unset; include them with the same values used by the UI action in `service_collab.nu:116-121` (`max_delay_ms: 60000`, `start_timeout_ms: 30000`, `stability_period_ms: 30000`) for consistency. Add a code comment noting the Rust builder relies on its own defaults here. - `stop_signal: "SIGTERM"` (line 142), `stop_timeout_ms: 5000` (line 143), `timeout_ms: 0`, `tty: false`. - `kill_other`: `socket: [$"($sock_base)/hero_biz/ui.sock"]` (lines 153-158). - `health_checks`: `action: "hero_biz_ui"`, `openrpc_socket: $"($sock_base)/hero_biz/ui.sock"`, policy **`interval_ms: 2000`**, `timeout_ms: 5000`, `retries: 3`, `start_period_ms: 5000` (lines 160-172; UI `interval_ms: 2000` diverges from prior services' 3000 — match Rust source). #### Step 6: `svx_service_config` Files: `tools/modules/services/service_biz.nu` Mirror `service_collab.nu:146-158`: - `context_name: "core"`, `name: $SVX_SERVICE_NAME`, `actions: $SVX_ACTIONS`, `class: "system"`, `critical: false`, `status: "start"`. - `description: "Hero Biz — business data management backend"` (verbatim from `main.rs:175`). #### Step 7: `svx_drop_registration` Files: `tools/modules/services/service_biz.nu` Identical to `service_collab.nu:161-167`, parameterised on `$SVX_SERVICE_NAME` and `$SVX_ACTIONS`. No changes needed beyond constant substitution. #### Step 8: `svx_check_osis_identity` preflight helper Files: `tools/modules/services/service_biz.nu` Model on `service_books.nu:185-202` (`svx_check_embedder`). Keep inside `service_biz.nu`; do not touch `lib.nu`. - Compute `$"(svc_sock_base $root)/hero_osis_identity/rpc.sock"`. - Check existence via `path exists` (or `sudo test -S` when `svc_need_sudo $root`). - If missing, print warning + remediation: ``` ⚠ hero_osis_identity socket not found at <path> hero_biz_ui reaches OSIS identity through HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui via hero_router; until hero_osis_identity is up, the UI's /hero0/* surface will return errors (the server and UI themselves still bind and serve). Bring up OSIS identity when ready. The canonical service_osis.nu lifecycle module is still in progress (see #75 tracker); for now use the repo's own CLI: hero_osis --start (identity domain) Once service_osis.nu lands this will become: service_osis start --domain identity<flag> ``` - Do NOT preflight `hero_proxy_ui` — add a code comment explaining the decision so future maintainers don't "fix" it. #### Step 9: `install` / `start` / `stop` / `status` Files: `tools/modules/services/service_biz.nu` Mirror `service_collab.nu:176-317` with these deltas: - Header comments for each function mention the OSIS identity preflight. - In `start`, after `svc_require_proc` + bin-verify steps and BEFORE `svx_drop_registration`, call `svx_check_osis_identity $root` (same position where `service_books.nu:287` calls `svx_check_embedder`). - Summary block uses `"hero_biz"` and `"hero_biz_ui"` everywhere. Add extra printed lines showing the configured `HERO0_BASE_URL` and `BASE_PATH` so the operator can spot stale or wrong values at a glance: ``` print $" base path: /hero_biz/ui" print $" hero0 url: http://127.0.0.1:6666/hero_osis/ui" ``` #### Step 10: Register in `mod.nu` Files: `tools/modules/services/mod.nu` Append `export use service_biz.nu` as a new line after `service_collab.nu`. #### Step 11: Syntax check - `/root/hero/bin/nu -c "source tools/modules/services/service_biz.nu; print parse-ok"` must succeed. - `/root/hero/bin/nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_biz '"` must show four subcommands. #### Step 12: Smoke test on Hetzner - `service_proc start --root` (prereq). - `service_biz install --root` — expect cargo build + 2 binaries in `/root/hero/bin/`. - `service_biz start --reset --root` — expect osis_identity warning (socket absent; first deployment), both actions registered, both sockets live, state: running, summary shows `base path` / `hero0 url` lines. - `service_biz status --root` — record shows running, 0 restarts. - `curl --unix-socket /root/hero/var/sockets/hero_biz/rpc.sock http://localhost/` — HTTP response. - `curl --unix-socket /root/hero/var/sockets/hero_biz/ui.sock http://localhost/` — HTTP response. - `service_biz start --root` — idempotent "already running". - Observe 15s — no new restarts. - `service_biz stop --root` — clean unregistration. - Post-stop `service_biz status --root` — expected `service 'hero_biz' not found`. - Cleanup: `service_proc stop --root`. ### Acceptance Criteria - [ ] `tools/modules/services/service_biz.nu` exists and exports `install`, `start`, `stop`, `status` — shape matches prior two-action modules. - [ ] `tools/modules/services/mod.nu` re-exports `service_biz.nu`. - [ ] Action specs match `hero_biz/crates/hero_biz/src/main.rs:103-172` exactly: server action name is `hero_biz` (not `hero_biz_server`); server health `start_period_ms: 5000`; UI health `interval_ms: 2000`; UI `stop_timeout_ms: 5000`. - [ ] UI action env sets `RUST_LOG`, `BASE_PATH="/hero_biz/ui"`, `HERO0_BASE_URL="http://127.0.0.1:6666/hero_osis/ui"` verbatim. - [ ] Server action env sets only `RUST_LOG: "info"`. - [ ] `svx_check_osis_identity` warns but never errors when the socket is absent; silently passes when present. - [ ] No preflight is done for `hero_proxy_ui` (TOML dep is advisory only). - [ ] `lib.nu` unchanged. - [ ] `nu -c 'source .../service_biz.nu; print parse-ok'` succeeds. - [ ] Smoke test passes the 10 scenarios above on the Hetzner box. - [ ] `service_biz start --reset` is idempotent and leaves the service `running`. ### Notes - **hero_zero TOML misleading**: `hero_zero/services/hero_biz.toml` declares only `[server]` even though the CLI (`hero_biz --start`) registers BOTH `hero_biz` and `hero_biz_ui` as hero_proc actions. The nu module's `SVX_ACTIONS` is therefore the authoritative list; do not read the TOML to derive it. - **Multi-dep preflight decisions**: TOML `depends_on = ["hero_osis_identity", "hero_proxy_ui"]`. Only `hero_osis_identity` gets a warning helper — grep confirms the UI talks to it via `HERO0_BASE_URL`. `hero_proxy_ui` is advisory (operators surface UIs through hero_router's socket discovery; no code path in `hero_biz/` reads from a proxy_ui socket). Both warnings are soft — missing `hero_osis_identity` degrades the `/hero0/*` surface but does not prevent bind. - **`HERO0_BASE_URL` brittleness**: the URL is hardcoded to `http://127.0.0.1:6666/hero_osis/ui`. The port (6666) and host (127.0.0.1) are baked in. If `hero_router` ever moves off 6666, or `hero_osis` relocates the identity mount path, this URL needs to be flipped. The spec passes the value verbatim from the TOML — option (a) per the issue brief. Document this in the module header comment; a future iteration can read `HERO_ROUTER_PORT` / construct the URL from `svc_sock_base` once hero_osis has a lifecycle module. - **`BASE_PATH` is a URL path**: not a filesystem path; do not try to `svc_sock_base`-substitute it. Pass `/hero_biz/ui` verbatim. - **Rust vs nu env divergence**: the Rust `build_service_definition()` only sets `RUST_LOG` on each action — `BASE_PATH` and `HERO0_BASE_URL` are supplied by the TOML `[env]` block when hero_zero is the registrant. When this nu module is the registrant, we must supply them ourselves. Grep shows both are consumed only by `hero_biz_ui`, so set them on the UI action only and leave the server action clean. - **Smoke-test plan**: run on Hetzner with `hero_proc` + `hero_router` already up; the OSIS identity preflight warning path is exercised by simply NOT having `hero_osis_identity` installed (the current state of the stack at merge time) — the warning must fire but `service_biz start` must still bring the service to running. ### Critical Files for Implementation - /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_collab.nu - /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_books.nu - /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/mod.nu - /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/lib.nu - /Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_biz/crates/hero_biz/src/main.rs
Author
Owner

Implementation summary

Changes

  • Added tools/modules/services/service_biz.nu — ~320 lines, mirrors the Rust build_service_definition() at hero_biz/crates/hero_biz/src/main.rs:93-179.
  • Updated tools/modules/services/mod.nu — appended export use service_biz.nu.

End-to-end smoke test on Hetzner

Ran under flock on /tmp/hero_skills_smoke.lock to serialize with the parallel agent's voice test.

# Assertion Result
1a–1c hero_proc-down error paths (status / stop / start) PASS
2a service_proc start --root healthy PASS
2b service_biz install --root produced 2 binaries PASS
2c service_biz start --reset --root — osis_identity preflight warning fired (socket absent, expected), both actions registered, summary prints base path + hero0 url PASS
2d /root/hero/var/sockets/hero_biz/rpc.sock is a live socket FAIL — see notes
2e /root/hero/var/sockets/hero_biz/ui.sock is a live socket PASS
2f curl --unix-socket rpc.sock accepts HTTP FAIL — see notes
2g curl --unix-socket ui.sock accepts HTTP PASS
2h service_biz status returns {name: hero_biz, state: running, restarts: 0} PASS
2i-A UI action env BASE_PATH = /hero_biz/ui PASS (verified directly via proc action get hero_biz_ui --root)
2i-B UI action env HERO0_BASE_URL = http://127.0.0.1:6666/hero_osis/ui PASS
2j Server action registered as name hero_biz (no _server suffix) PASS — the key divergence from prior services
2k Idempotent start (no --reset) prints "already running" PASS
2l 15 s observation — no new restarts, state held running PASS
2m service_biz stop --root unregisters cleanly PASS
2n Post-stop status returns expected service 'hero_biz' not found PASS

Notes on the 2d/2f FAILs

hero_biz's run_server() function is currently a placeholder backendcrates/hero_biz/src/main.rs:183-207:

tracing::info!("hero_biz backend — placeholder (not yet implemented)");
tokio::signal::ctrl_c().await?;

The server process runs and waits for ctrl_c but never binds rpc.sock (it only creates the parent directory and removes any stale socket file). That is why 2d and 2f fail — the socket doesn't exist on disk and nothing listens on it.

Implications for this PR:

  • service_biz.nu is correct: it registers the server action exactly as the Rust build_service_definition() at main.rs:103-137 specifies, including the rpc.sock path in kill_other and health_checks. Once the hero_biz backend is implemented upstream, the rpc side will light up automatically without any change here.
  • hero_proc's state: running is optimistic in the sense that the process stays alive (doesn't crash), so the restart counter stays at 0. If upstream later makes run_server() do its own health reporting, hero_proc's state may flip to degraded or similar — again, no change needed here.
  • The UI side works end-to-end today: ui.sock binds, accepts HTTP, and the env plumbing (BASE_PATH, HERO0_BASE_URL) is registered correctly.

Filing a follow-up issue against lhumina_code/hero_biz is not in scope for this PR; the placeholder comment is already in the codebase and whoever implements the backend will see it.

Notes on the 2i error (test-script only)

The test script had an interpolated-string with literal parens ("(got: ($x))") that nu parses as a subexpression. That's a bug in my test, not in the module. I verified the env registration directly with proc action get hero_biz_ui --root and proc action get hero_biz --root — both match the spec:

  • hero_biz_ui env: RUST_LOG, BASE_PATH, HERO0_BASE_URL
  • hero_biz env: RUST_LOG only

Acceptance criteria

  • Module loadable via use services/mod.nu * (4 subcommands visible).
  • install builds both binaries and places them in /root/hero/bin/ via --root.
  • start registers hero_biz + hero_biz_ui + the hero_biz service, fires the osis_identity preflight warning, prints summary with both sockets + base path + hero0 url.
  • Server action name registered as hero_biz (not hero_biz_server).
  • UI action env contains RUST_LOG, BASE_PATH=/hero_biz/ui, HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui; server action env has only RUST_LOG.
  • stop unregisters cleanly; post-stop status returns service not found.
  • --root optional, user-level default.
  • Smoke-tested end-to-end on Hetzner (10 scenarios green; the 2 placeholder-backend FAILs are upstream, not module issues).
## Implementation summary ### Changes - Added `tools/modules/services/service_biz.nu` — ~320 lines, mirrors the Rust `build_service_definition()` at `hero_biz/crates/hero_biz/src/main.rs:93-179`. - Updated `tools/modules/services/mod.nu` — appended `export use service_biz.nu`. ### End-to-end smoke test on Hetzner Ran under `flock` on `/tmp/hero_skills_smoke.lock` to serialize with the parallel agent's voice test. | # | Assertion | Result | |---|---|---| | 1a–1c | hero_proc-down error paths (status / stop / start) | PASS | | 2a | `service_proc start --root` healthy | PASS | | 2b | `service_biz install --root` produced 2 binaries | PASS | | 2c | `service_biz start --reset --root` — osis_identity preflight warning fired (socket absent, expected), both actions registered, summary prints `base path` + `hero0 url` | PASS | | 2d | `/root/hero/var/sockets/hero_biz/rpc.sock` is a live socket | **FAIL** — see notes | | 2e | `/root/hero/var/sockets/hero_biz/ui.sock` is a live socket | PASS | | 2f | `curl --unix-socket rpc.sock` accepts HTTP | **FAIL** — see notes | | 2g | `curl --unix-socket ui.sock` accepts HTTP | PASS | | 2h | `service_biz status` returns `{name: hero_biz, state: running, restarts: 0}` | PASS | | 2i-A | UI action env `BASE_PATH = /hero_biz/ui` | PASS (verified directly via `proc action get hero_biz_ui --root`) | | 2i-B | UI action env `HERO0_BASE_URL = http://127.0.0.1:6666/hero_osis/ui` | PASS | | 2j | Server action registered as name `hero_biz` (no `_server` suffix) | PASS — the key divergence from prior services | | 2k | Idempotent `start` (no `--reset`) prints "already running" | PASS | | 2l | 15 s observation — no new restarts, state held `running` | PASS | | 2m | `service_biz stop --root` unregisters cleanly | PASS | | 2n | Post-stop `status` returns expected `service 'hero_biz' not found` | PASS | ### Notes on the 2d/2f FAILs `hero_biz`'s `run_server()` function is currently a **placeholder backend** — `crates/hero_biz/src/main.rs:183-207`: ```rust tracing::info!("hero_biz backend — placeholder (not yet implemented)"); tokio::signal::ctrl_c().await?; ``` The server process runs and waits for ctrl_c but **never binds `rpc.sock`** (it only creates the parent directory and removes any stale socket file). That is why 2d and 2f fail — the socket doesn't exist on disk and nothing listens on it. Implications for this PR: - `service_biz.nu` is correct: it registers the server action exactly as the Rust `build_service_definition()` at `main.rs:103-137` specifies, including the `rpc.sock` path in `kill_other` and `health_checks`. Once the hero_biz backend is implemented upstream, the rpc side will light up automatically without any change here. - hero_proc's `state: running` is optimistic in the sense that the process stays alive (doesn't crash), so the restart counter stays at 0. If upstream later makes `run_server()` do its own health reporting, hero_proc's state may flip to `degraded` or similar — again, no change needed here. - The UI side works end-to-end today: `ui.sock` binds, accepts HTTP, and the env plumbing (`BASE_PATH`, `HERO0_BASE_URL`) is registered correctly. Filing a follow-up issue against `lhumina_code/hero_biz` is not in scope for this PR; the placeholder comment is already in the codebase and whoever implements the backend will see it. ### Notes on the 2i error (test-script only) The test script had an interpolated-string with literal parens (`"(got: ($x))"`) that nu parses as a subexpression. That's a bug in my test, not in the module. I verified the env registration directly with `proc action get hero_biz_ui --root` and `proc action get hero_biz --root` — both match the spec: - `hero_biz_ui` env: `RUST_LOG`, `BASE_PATH`, `HERO0_BASE_URL` - `hero_biz` env: `RUST_LOG` only ### Acceptance criteria - [x] Module loadable via `use services/mod.nu *` (4 subcommands visible). - [x] `install` builds both binaries and places them in `/root/hero/bin/` via `--root`. - [x] `start` registers `hero_biz` + `hero_biz_ui` + the `hero_biz` service, fires the osis_identity preflight warning, prints summary with both sockets + `base path` + `hero0 url`. - [x] Server action name registered as `hero_biz` (not `hero_biz_server`). - [x] UI action env contains `RUST_LOG`, `BASE_PATH=/hero_biz/ui`, `HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui`; server action env has only `RUST_LOG`. - [x] `stop` unregisters cleanly; post-stop `status` returns `service not found`. - [x] `--root` optional, user-level default. - [x] Smoke-tested end-to-end on Hetzner (10 scenarios green; the 2 placeholder-backend FAILs are upstream, not module issues).
Author
Owner

PR opened: #89

PR opened: https://forge.ourworld.tf/lhumina_code/hero_skills/pulls/89
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_skills#86
No description provided.