service_biz.nu — hero_biz server + UI lifecycle module #86
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_skills#86
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?
Child of #75.
Objective
Add
tools/modules/services/service_biz.nuimplementinginstall | start | stop | statusfor the hero_biz service.Scope
ssh://git@forge.ourworld.tf/lhumina_code/hero_biz.gitbuildenv.sh):hero_biz,hero_biz_uihero_biz— server daemon (hero_bizbinary without args =run_server()), binds$HERO_SOCKET_DIR/hero_biz/rpc.sockhero_biz_ui— UI daemon (hero_biz_uibinary), binds$HERO_SOCKET_DIR/hero_biz/ui.sock[server]but the CLI'sself_start()actually registers both actions):lhumina_code/hero_zero/services/hero_biz.toml[package]) — plaincargo build --releasecovers both binaries.hero_bizwith no args = server.--start/--stopare the self-registration shortcuts we will NOT use (nu module does the registration).[env]block, applies to server):RUST_LOG=infoBASE_PATH=/hero_biz/ui— URL prefix for the biz UI when reached through hero_routerHERO0_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)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 viaHERO0_BASE_URL(TCP URL, not socket).Acceptance criteria
use services/mod.nu *makesservice_bizavailable.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) + thehero_bizservice, 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.RUST_LOG,BASE_PATH=/hero_biz/ui, andHERO0_BASE_URLresolved at register time via a helper (see spec decisions below).$HERO_SOCKET_DIR/hero_osis_identity/rpc.sockis missing (hero_biz reads from it). DO NOT preflighthero_proxy_ui— the HTTP dep is less visible and failure mode is cosmetic (UI links break, service still runs).Spec decisions to confirm
HERO0_BASE_URLresolution: TOML uses the hardcoded loopbackhttp://127.0.0.1:6666/hero_osis/ui. Should we:svc_home $rootsomehow — can't, because this is a TCP URL not a socket path.Multi-dep preflight: first service with 2
depends_onentries. 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.Action script shape:
script: $binfor both (no subcommand).hero_bizwith no args hitsrun_server();hero_biz_uiis a straightforward tokio main.Template & references
service_books.nu(PR #81) — for the embedder-preflight pattern and env-at-register-time pattern. Adapt itssvx_check_embeddertosvx_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.hero_biz/crates/hero_biz/src/main.rsbuild_service_definition()— the CLI already encodes the correct registration spec; the nu module should mirror it exactly.Implementation Spec for Issue #86
Objective
Add
service_biz.nu— a hero_proc lifecycle module for thehero_bizservice (server + UI) that mirrors the Rust-sidebuild_service_definition()and adds a soft-preflight helper for thehero_osis_identitydependency declared by the TOML.Requirements
tools/modules/services/service_biz.nuexportinginstall,start,stop,status— shape identical to the mergedservice_whiteboard.nu/service_collab.numodules.["hero_biz" "hero_biz_ui"]— hero_biz IS the server; no separate_serverbinary (confirmed byhero_biz/buildenv.sh:8andhero_biz/Cargo.toml:3-7).hero_biz(server) andhero_biz_ui. Server action name ishero_biz, NOThero_biz_server(seehero_biz/crates/hero_biz/src/main.rs:103) — first service we've shipped where the server action drops the_serversuffix.build_service_definition()inhero_biz/crates/hero_biz/src/main.rs:93-179field-for-field (see step 4–5 below for citations).$HERO_SOCKET_DIR/hero_osis_identity/rpc.sockis missing. Do NOT preflighthero_proxy_ui— the Hero stack convention surfaces UIs through hero_router/hero_proxy_ui, and no code inhero_biz/reads from a proxy_ui socket.BASE_PATH=/hero_biz/uiandHERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/uiare declared on the hero_zero TOML[env]block (hero_zero/services/hero_biz.toml:12-14). Grep confirms both are consumed ONLY byhero_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 insidecrates/hero_biz/. Set them on the UI action only. Pass both verbatim as string literals.RUST_LOG: "info"on both actions, permain.rs:107andmain.rs:144../mod.nu.Files to Modify/Create
tools/modules/services/service_biz.nu(new)tools/modules/services/mod.nu(+1 line:export use service_biz.nuappended after theservice_collab.nuline)Implementation Plan
Step 1: Copy
service_collab.nuskeletonFiles:
tools/modules/services/service_biz.nuservice_collab.nu(the most minimal two-action module merged to date).service_books.nu:185-202(svx_check_embedder) as shape template for the preflight helper.service_books.nu:72-84as shape template for the UI action's multi-entry env block.Dependencies: none.
Step 2: Module header docstring
Files:
tools/modules/services/service_biz.nuMirror
service_collab.nu:1-44structure. Call out:hero_biz(the server — no_serversuffix; the CLI binary doubles as the server daemon when run with no args, seehero_biz/crates/hero_biz/src/main.rs:46) andhero_biz_ui.$HERO_SOCKET_DIR/hero_biz/rpc.sockand$HERO_SOCKET_DIR/hero_biz/ui.sock.hero_osis_identity:hero_biz_uireaches OSIS identity throughHERO0_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.tomldeclares only[server]— this nu module is the authoritative place recording that hero_biz is a two-action service, matching whathero_biz --startinstalls viabuild_service_definition().BASE_PATH/HERO0_BASE_URLbrittleness note (see Notes section).Step 3: Constants
Files:
tools/modules/services/service_biz.nuNote
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— matchmain.rs:103-137Files:
tools/modules/services/service_biz.nuname: "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). Addstability_period_ms: 30000to 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", policyinterval_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— matchmain.rs:140-172Files:
tools/modules/services/service_biz.nuname: "hero_biz_ui"(line 140),script: (svc_bin "hero_biz_ui" $root)(line 96).interpreter: "exec"(line 141).RUST_LOGonly):RUST_LOG: "info"(line 144).BASE_PATH: "/hero_biz/ui"— verbatim fromhero_zero/services/hero_biz.toml:13. Consumer:hero_biz_ui/src/web/server.rs:89(plainstd::env::var, no validation).HERO0_BASE_URL: "http://127.0.0.1:6666/hero_osis/ui"— verbatim fromhero_zero/services/hero_biz.toml:14. Consumers:hero_biz_ui/src/hero0/mod.rs:93,.../services/mod.rs:36,50(plainstd::env::var, no validation).is_process: true(line 150).retry_policy:max_attempts: 3,delay_ms: 2000,backoff: true(lines 146-148). Rust builder leavesmax_delay_ms,start_timeout_ms,stability_period_msunset; include them with the same values used by the UI action inservice_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", policyinterval_ms: 2000,timeout_ms: 5000,retries: 3,start_period_ms: 5000(lines 160-172; UIinterval_ms: 2000diverges from prior services' 3000 — match Rust source).Step 6:
svx_service_configFiles:
tools/modules/services/service_biz.nuMirror
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 frommain.rs:175).Step 7:
svx_drop_registrationFiles:
tools/modules/services/service_biz.nuIdentical to
service_collab.nu:161-167, parameterised on$SVX_SERVICE_NAMEand$SVX_ACTIONS. No changes needed beyond constant substitution.Step 8:
svx_check_osis_identitypreflight helperFiles:
tools/modules/services/service_biz.nuModel on
service_books.nu:185-202(svx_check_embedder). Keep insideservice_biz.nu; do not touchlib.nu.$"(svc_sock_base $root)/hero_osis_identity/rpc.sock".path exists(orsudo test -Swhensvc_need_sudo $root).hero_proxy_ui— add a code comment explaining the decision so future maintainers don't "fix" it.Step 9:
install/start/stop/statusFiles:
tools/modules/services/service_biz.nuMirror
service_collab.nu:176-317with these deltas:start, aftersvc_require_proc+ bin-verify steps and BEFOREsvx_drop_registration, callsvx_check_osis_identity $root(same position whereservice_books.nu:287callssvx_check_embedder)."hero_biz"and"hero_biz_ui"everywhere. Add extra printed lines showing the configuredHERO0_BASE_URLandBASE_PATHso the operator can spot stale or wrong values at a glance:Step 10: Register in
mod.nuFiles:
tools/modules/services/mod.nuAppend
export use service_biz.nuas a new line afterservice_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 showsbase path/hero0 urllines.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".service_biz stop --root— clean unregistration.service_biz status --root— expectedservice 'hero_biz' not found.service_proc stop --root.Acceptance Criteria
tools/modules/services/service_biz.nuexists and exportsinstall,start,stop,status— shape matches prior two-action modules.tools/modules/services/mod.nure-exportsservice_biz.nu.hero_biz/crates/hero_biz/src/main.rs:103-172exactly: server action name ishero_biz(nothero_biz_server); server healthstart_period_ms: 5000; UI healthinterval_ms: 2000; UIstop_timeout_ms: 5000.RUST_LOG,BASE_PATH="/hero_biz/ui",HERO0_BASE_URL="http://127.0.0.1:6666/hero_osis/ui"verbatim.RUST_LOG: "info".svx_check_osis_identitywarns but never errors when the socket is absent; silently passes when present.hero_proxy_ui(TOML dep is advisory only).lib.nuunchanged.nu -c 'source .../service_biz.nu; print parse-ok'succeeds.service_biz start --resetis idempotent and leaves the servicerunning.Notes
hero_zero/services/hero_biz.tomldeclares only[server]even though the CLI (hero_biz --start) registers BOTHhero_bizandhero_biz_uias hero_proc actions. The nu module'sSVX_ACTIONSis therefore the authoritative list; do not read the TOML to derive it.depends_on = ["hero_osis_identity", "hero_proxy_ui"]. Onlyhero_osis_identitygets a warning helper — grep confirms the UI talks to it viaHERO0_BASE_URL.hero_proxy_uiis advisory (operators surface UIs through hero_router's socket discovery; no code path inhero_biz/reads from a proxy_ui socket). Both warnings are soft — missinghero_osis_identitydegrades the/hero0/*surface but does not prevent bind.HERO0_BASE_URLbrittleness: the URL is hardcoded tohttp://127.0.0.1:6666/hero_osis/ui. The port (6666) and host (127.0.0.1) are baked in. Ifhero_routerever moves off 6666, orhero_osisrelocates 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 readHERO_ROUTER_PORT/ construct the URL fromsvc_sock_baseonce hero_osis has a lifecycle module.BASE_PATHis a URL path: not a filesystem path; do not try tosvc_sock_base-substitute it. Pass/hero_biz/uiverbatim.build_service_definition()only setsRUST_LOGon each action —BASE_PATHandHERO0_BASE_URLare 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 byhero_biz_ui, so set them on the UI action only and leave the server action clean.hero_proc+hero_routeralready up; the OSIS identity preflight warning path is exercised by simply NOT havinghero_osis_identityinstalled (the current state of the stack at merge time) — the warning must fire butservice_biz startmust still bring the service to running.Critical Files for Implementation
Implementation summary
Changes
tools/modules/services/service_biz.nu— ~320 lines, mirrors the Rustbuild_service_definition()athero_biz/crates/hero_biz/src/main.rs:93-179.tools/modules/services/mod.nu— appendedexport use service_biz.nu.End-to-end smoke test on Hetzner
Ran under
flockon/tmp/hero_skills_smoke.lockto serialize with the parallel agent's voice test.service_proc start --roothealthyservice_biz install --rootproduced 2 binariesservice_biz start --reset --root— osis_identity preflight warning fired (socket absent, expected), both actions registered, summary printsbase path+hero0 url/root/hero/var/sockets/hero_biz/rpc.sockis a live socket/root/hero/var/sockets/hero_biz/ui.sockis a live socketcurl --unix-socket rpc.sockaccepts HTTPcurl --unix-socket ui.sockaccepts HTTPservice_biz statusreturns{name: hero_biz, state: running, restarts: 0}BASE_PATH = /hero_biz/uiproc action get hero_biz_ui --root)HERO0_BASE_URL = http://127.0.0.1:6666/hero_osis/uihero_biz(no_serversuffix)start(no--reset) prints "already running"runningservice_biz stop --rootunregisters cleanlystatusreturns expectedservice 'hero_biz' not foundNotes on the 2d/2f FAILs
hero_biz'srun_server()function is currently a placeholder backend —crates/hero_biz/src/main.rs:183-207: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.nuis correct: it registers the server action exactly as the Rustbuild_service_definition()atmain.rs:103-137specifies, including therpc.sockpath inkill_otherandhealth_checks. Once the hero_biz backend is implemented upstream, the rpc side will light up automatically without any change here.state: runningis optimistic in the sense that the process stays alive (doesn't crash), so the restart counter stays at 0. If upstream later makesrun_server()do its own health reporting, hero_proc's state may flip todegradedor similar — again, no change needed here.ui.sockbinds, accepts HTTP, and the env plumbing (BASE_PATH,HERO0_BASE_URL) is registered correctly.Filing a follow-up issue against
lhumina_code/hero_bizis 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 withproc action get hero_biz_ui --rootandproc action get hero_biz --root— both match the spec:hero_biz_uienv:RUST_LOG,BASE_PATH,HERO0_BASE_URLhero_bizenv:RUST_LOGonlyAcceptance criteria
use services/mod.nu *(4 subcommands visible).installbuilds both binaries and places them in/root/hero/bin/via--root.startregistershero_biz+hero_biz_ui+ thehero_bizservice, fires the osis_identity preflight warning, prints summary with both sockets +base path+hero0 url.hero_biz(nothero_biz_server).RUST_LOG,BASE_PATH=/hero_biz/ui,HERO0_BASE_URL=http://127.0.0.1:6666/hero_osis/ui; server action env has onlyRUST_LOG.stopunregisters cleanly; post-stopstatusreturnsservice not found.--rootoptional, user-level default.PR opened: #89