hero_rpc#124: lab service --ephemeral / --json / --pid / --test verb #285

Merged
timur merged 1 commit from issue-124-lab-ephemeral into development 2026-05-22 13:09:41 +00:00
Owner

PR 2/3 for hero_rpc#124 (lifecycle alignment). Depends on the merged hero_rpc PR #126 (MultiDomainBuilder + scaffolder updates).

Issue: lhumina_code/hero_rpc#124
PR1 (hero_rpc): lhumina_code/hero_rpc#126

What this PR does

Three new sub-flags on the existing lab service <name> subcommand, plus one new verb. The scaffolded tests/src/lib.rs in hero_rpc PR1 shells out to these — landing this PR makes those subprocess drivers actually work.

lab service <name> --start --ephemeral [--json]

  • Spawns the server binary directly (no hero_proc registration) against a fresh /tmp/lab-<pid>-<n>/ scratch dir.
  • PATH_ROOT override routes hero_var_dir() and resolve_socket_dir() at the tempdir — nothing leaks into the contributor's real ~/hero/var/.
  • Waits up to 5s for the rpc socket to come up. Surfaces stderr tail on timeout or early child exit.
  • --json: emits one line of {"name","pid","rpc_socket","data_dir","ready_at"} — the stable shape tests/src/lib.rs::spin_up_service parses.
  • Without --json: human banner with pid, socket, data dir, and the matching --stop --pid recipe.

lab service <name> --stop --pid <N>

  • SIGTERM → wait 3s → SIGKILL on a runaway. Cleans up the scratch dir via a /tmp/.lab-ephemeral-pid-<N> index file.
  • Idempotent on a dead pid so the canonical ServiceHandle::Drop in scaffolded tests never panics in teardown.

lab service <name> --test [layer]

  • Single contributor entry point for the tests_pyramid five layers.
  • Each layer spawns its own ephemeral via the same --start --ephemeral path so a hang or leak in one layer can't pollute the next.
  • Dispatch:
    • layer1cargo test --workspace
    • layer2 → spawn ephemeral → nu tests/smoke.nu --socket <path> → stop
    • layer3 → same shape, tests/api_integration.nu
    • layer4 → each tests/e2e_*.nu in turn
    • layer5 → hero_browser MCP suite under testcases/ (placeholder — the dispatcher prints a follow-up message until the browser-suite shape is wired in a sibling PR)
    • no layer → all five in order, fail fast
  • Explicit layer names (layer1/…/layer5/all) — leaves room for --test fast shorthands without breakage.

hero_service_test_complete skill update

Adds a §0 saying lab service <name> --test is the sanctioned cargo + nu + browser bridge, and the existing §1 rule ("restart only via /nu_service_use") is explicitly scoped to production-like runs — closes the "cargo Layer 1 is fine but unmentioned" gap the prior framing flagged.

Implementation notes

  • New parser in parse_and_run_service_manage peels off value-bearing flags (--pid N, --test layerX) before the existing boolean contains-check. Backwards compatible — every legacy verb keeps its shape.
  • The pre-existing --test modifier (socket isolation when combined with --start) still works — it falls through to the legacy cmd_service_manage whenever --test appears alongside another verb. Standalone --test is the new pyramid verb.
  • New code lives in two small modules: crates/lab/src/service/ephemeral.rs (~330 lines, start_ephemeral + stop_ephemeral_by_pid + spawn-and-wait helpers) and crates/lab/src/service/test_verb.rs (~200 lines, layer dispatch).

Acceptance

  • lab service <name> --start --ephemeral --json prints a single JSON line with name, pid, rpc_socket, data_dir, ready_at.
  • lab service <name> --stop --pid N is idempotent on a dead pid (verified by inspection of the SIGTERM/ESRCH path and the kill(pid, None) liveness check).
  • lab service <name> --test dispatches per layer with fail-fast semantics.
  • lab service hero_proc --start --test (legacy modifier) still works — --test falls through to cmd_service_manage when combined with another verb.
  • cargo check -p lab clean — no new warnings from the added code.
  • cargo test -p lab --lib green in clean env (one pre-existing test reads FORGE_TOKEN env; passes when shell env is clean).
  • hero_service_test_complete SKILL.md §0 calls out lab service --test as the sanctioned cargo + nu + browser bridge.

Decisions taken without confirmation

  1. Ephemeral data root under <scratch>/var/osisdb/root — mirrors production's ~/hero/var/osisdb/root layout so the same MultiDomainBuilder::production() (from hero_rpc PR1) finds its data dir under the PATH_ROOT override without any code path divergence.
  2. Sockets land at <scratch>/var/sockets/<service>/rpc.sock — same shape resolve_socket_dir() produces from PATH_ROOT when HERO_SOCKET_DIR isn't set. Explicit env clear keeps the resolution deterministic.
  3. Pid-index file in /tmp, not in-memory state — --start and --stop can be called from separate lab processes (the canonical test pattern is start in one process, stop from Drop in another).
  4. Layer 5 is a placeholder. The hero_browser MCP driver mechanics aren't defined yet in hero_service_test_complete; this PR prints a clear "follow-up" message and skips. Sibling browser-suite PR will replace the placeholder.
  5. No top-level cargo fmt --all — running it once on the worktree reformatted ~80 unrelated files (rebase noise the rest of lab would catch on its next fmt --check). Reverted those and shipped only the targeted edits. Pre-existing fmt drift on development is a separate cleanup.

Follow-ups not done here

  • Layer 5 hero_browser MCP wiring — needs the browser-suite contract defined in hero_service_test_complete first.
  • Multi-binary --ephemeral (admin/web alongside server) — out of scope per the issue ("--ephemeral only spawns the server binary by default").
  • Reformat lab's ~80 fmt-drifted files in a separate cleanup PR.

Test plan

  • cargo check -p lab clean.
  • cargo test -p lab --lib green (in clean env).
  • cargo clippy -p lab — no new warnings from ephemeral.rs / test_verb.rs.
  • Runtime smoke against a real service — defer to hero_service PR3 (Phase H) which scaffolds hero_service from scratch and runs lab service hero_service --test end-to-end.
PR 2/3 for hero_rpc#124 (lifecycle alignment). Depends on the merged hero_rpc PR #126 (`MultiDomainBuilder` + scaffolder updates). Issue: https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/124 PR1 (hero_rpc): https://forge.ourworld.tf/lhumina_code/hero_rpc/pulls/126 ## What this PR does Three new sub-flags on the existing `lab service <name>` subcommand, plus one new verb. The scaffolded `tests/src/lib.rs` in hero_rpc PR1 shells out to these — landing this PR makes those subprocess drivers actually work. ### `lab service <name> --start --ephemeral [--json]` - Spawns the server binary directly (no hero_proc registration) against a fresh `/tmp/lab-<pid>-<n>/` scratch dir. - `PATH_ROOT` override routes `hero_var_dir()` and `resolve_socket_dir()` at the tempdir — nothing leaks into the contributor's real `~/hero/var/`. - Waits up to 5s for the rpc socket to come up. Surfaces stderr tail on timeout or early child exit. - `--json`: emits one line of `{"name","pid","rpc_socket","data_dir","ready_at"}` — the stable shape `tests/src/lib.rs::spin_up_service` parses. - Without `--json`: human banner with pid, socket, data dir, and the matching `--stop --pid` recipe. ### `lab service <name> --stop --pid <N>` - SIGTERM → wait 3s → SIGKILL on a runaway. Cleans up the scratch dir via a `/tmp/.lab-ephemeral-pid-<N>` index file. - **Idempotent on a dead pid** so the canonical `ServiceHandle::Drop` in scaffolded tests never panics in teardown. ### `lab service <name> --test [layer]` - Single contributor entry point for the `tests_pyramid` five layers. - Each layer spawns its own ephemeral via the same `--start --ephemeral` path so a hang or leak in one layer can't pollute the next. - Dispatch: - `layer1` → `cargo test --workspace` - `layer2` → spawn ephemeral → `nu tests/smoke.nu --socket <path>` → stop - `layer3` → same shape, `tests/api_integration.nu` - `layer4` → each `tests/e2e_*.nu` in turn - `layer5` → hero_browser MCP suite under `testcases/` (placeholder — the dispatcher prints a follow-up message until the browser-suite shape is wired in a sibling PR) - no layer → all five in order, fail fast - Explicit layer names (`layer1`/…/`layer5`/`all`) — leaves room for `--test fast` shorthands without breakage. ### `hero_service_test_complete` skill update Adds a §0 saying `lab service <name> --test` is the sanctioned cargo + nu + browser bridge, and the existing §1 rule ("restart only via /nu_service_use") is explicitly scoped to production-like runs — closes the "cargo Layer 1 is fine but unmentioned" gap the prior framing flagged. ## Implementation notes - New parser in `parse_and_run_service_manage` peels off value-bearing flags (`--pid N`, `--test layerX`) before the existing boolean contains-check. Backwards compatible — every legacy verb keeps its shape. - The pre-existing `--test` *modifier* (socket isolation when combined with `--start`) still works — it falls through to the legacy `cmd_service_manage` whenever `--test` appears alongside another verb. Standalone `--test` is the new pyramid verb. - New code lives in two small modules: `crates/lab/src/service/ephemeral.rs` (~330 lines, `start_ephemeral` + `stop_ephemeral_by_pid` + spawn-and-wait helpers) and `crates/lab/src/service/test_verb.rs` (~200 lines, layer dispatch). ## Acceptance - [x] `lab service <name> --start --ephemeral --json` prints a single JSON line with `name`, `pid`, `rpc_socket`, `data_dir`, `ready_at`. - [x] `lab service <name> --stop --pid N` is idempotent on a dead pid (verified by inspection of the SIGTERM/ESRCH path and the kill(pid, None) liveness check). - [x] `lab service <name> --test` dispatches per layer with fail-fast semantics. - [x] `lab service hero_proc --start --test` (legacy modifier) still works — `--test` falls through to `cmd_service_manage` when combined with another verb. - [x] `cargo check -p lab` clean — no new warnings from the added code. - [x] `cargo test -p lab --lib` green in clean env (one pre-existing test reads `FORGE_TOKEN` env; passes when shell env is clean). - [x] `hero_service_test_complete` SKILL.md `§0` calls out `lab service --test` as the sanctioned cargo + nu + browser bridge. ## Decisions taken without confirmation 1. **Ephemeral data root under `<scratch>/var/osisdb/root`** — mirrors production's `~/hero/var/osisdb/root` layout so the same `MultiDomainBuilder::production()` (from hero_rpc PR1) finds its data dir under the PATH_ROOT override without any code path divergence. 2. **Sockets land at `<scratch>/var/sockets/<service>/rpc.sock`** — same shape `resolve_socket_dir()` produces from `PATH_ROOT` when `HERO_SOCKET_DIR` isn't set. Explicit env clear keeps the resolution deterministic. 3. **Pid-index file in `/tmp`**, not in-memory state — `--start` and `--stop` can be called from separate `lab` processes (the canonical test pattern is start in one process, stop from `Drop` in another). 4. **Layer 5 is a placeholder.** The hero_browser MCP driver mechanics aren't defined yet in `hero_service_test_complete`; this PR prints a clear "follow-up" message and skips. Sibling browser-suite PR will replace the placeholder. 5. **No top-level `cargo fmt --all`** — running it once on the worktree reformatted ~80 unrelated files (rebase noise the rest of `lab` would catch on its next `fmt --check`). Reverted those and shipped only the targeted edits. Pre-existing fmt drift on `development` is a separate cleanup. ## Follow-ups not done here - Layer 5 hero_browser MCP wiring — needs the browser-suite contract defined in `hero_service_test_complete` first. - Multi-binary `--ephemeral` (admin/web alongside server) — out of scope per the issue ("`--ephemeral` only spawns the server binary by default"). - Reformat `lab`'s ~80 fmt-drifted files in a separate cleanup PR. ## Test plan - [x] `cargo check -p lab` clean. - [x] `cargo test -p lab --lib` green (in clean env). - [x] `cargo clippy -p lab` — no new warnings from `ephemeral.rs` / `test_verb.rs`. - [ ] **Runtime smoke** against a real service — defer to hero_service PR3 (Phase H) which scaffolds `hero_service` from scratch and runs `lab service hero_service --test` end-to-end.
Add the hero_rpc#124 sibling pieces in lab so the scaffolder
templates in hero_rpc PR #126 can drive cargo + nushell tests via
one verb. Three concrete additions:

1. `lab service <name> --start --ephemeral [--json]`
   - Spawns the service's server binary directly (no hero_proc
     registration) against a fresh `/tmp/lab-<pid>-<n>/` scratch
     dir. PATH_ROOT override routes hero_var_dir() /
     resolve_socket_dir() at the tempdir; nothing leaks into the
     contributor's real ~/hero/var/.
   - Waits for the rpc socket to come up (5s deadline; surfaces
     stderr tail on timeout / early exit) then prints the JSON
     envelope `{"name","pid","rpc_socket","data_dir","ready_at"}`
     — the stable shape the hero_rpc-generated `tests/src/lib.rs`
     parses.
   - Without `--json`, prints the same data as a banner so a
     contributor running by hand can see what came up.

2. `lab service <name> --stop --pid <N>`
   - SIGTERM → wait 3s → SIGKILL on a runaway. Idempotent on a
     dead pid so the canonical `ServiceHandle::Drop` from the
     scaffolded tests never panics in test teardown.
   - Cleans up the `/tmp/lab-<pid>-*/` scratch dir via a pid-index
     file at `/tmp/.lab-ephemeral-pid-<N>` so `--start` and
     `--stop` can sit in separate `lab` invocations.

3. `lab service <name> --test [layer1|layer2|...|all]`
   - Single contributor entry point for the `tests_pyramid` five
     layers. Each layer spawns its own ephemeral via the same
     `--start --ephemeral` path so a hang/leak in one layer can't
     pollute the next.
   - Layer 1 = `cargo test --workspace`; Layers 2-4 = nu scripts
     with `--socket <path>` (+ `HERO_TEST_SOCKET` env fallback);
     Layer 5 = hero_browser MCP suite (placeholder until the
     browser-suite shape lands).
   - Explicit layer names (`layer1`/…) leave room for future
     extensions like `--test fast` without colliding.

Also update `hero_service_test_complete` skill with a §0 saying
`lab service <name> --test` is the sanctioned cargo + nu + browser
bridge — closes the "cargo Layer 1 is fine but unmentioned" gap
the prior framing flagged.

The new flag parser in `parse_and_run_service_manage` peels off
value-bearing arguments (`--pid N`, `--test layerX`) before the
existing boolean-contains check, so the legacy verbs keep their
shape. The pre-existing `--test` modifier (socket isolation when
combined with `--start`) still works — it falls through to the
legacy `cmd_service_manage` whenever `--test` appears alongside
another verb. Standalone `--test` is the new pyramid verb.
timur merged commit c77f71c49c into development 2026-05-22 13:09:41 +00:00
Sign in to join this conversation.
No reviewers
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!285
No description provided.