ROCMAI-83: extract service.rs and watchers.rs from apps/rocmd/src/lib.rs - #487
jussielo-amd wants to merge 6 commits into
Conversation
r0x0r
left a comment
There was a problem hiding this comment.
Review (draft): extraction is clean; held by the phase-wide merge problem
The code motion verified as faithful, including the endpoint-key guard that the regression tests in this PR exist to protect. The reasons to hold are not about this diff in isolation — they are about how the eight PRs in this phase combine.
service.rs has 13 crate:: references that break when siblings merge
This module reaches back into the crate root for items that other open PRs move elsewhere:
crate:: reference |
relocated by |
|---|---|
record_event, load_managed_services |
#477 → persistence.rs |
optional_arg, parse_gpu_indices_arg, apply_endpoint_key_env, engine_healthcheck_ready, ensure_public_service_has_endpoint_key |
#479 → common.rs |
start_local_webhook_source, receive_local_webhook_event |
#480 → webhook.rs |
evaluate_watchers, evaluate_watchers_for_events, reconcile_watcher_snapshots, load_service_record |
#489 → watchers.rs |
Each becomes a dangling path once the owning PR lands. The part that makes this worth blocking on rather than noting: these references live in service.rs, a file none of those PRs touches. There is no overlapping hunk, so git merges them cleanly and the breakage shows up as a compile error on main with no conflict to warn anyone.
I checked this across all eight PRs rather than just this one. 52 such references break, spread over six of them — #489 watchers.rs (18), this PR (13), #481 cli.rs (11), #484 mcp.rs (5), #480 webhook.rs (1), #479 common.rs (1). The payload_string case between #480 and #489 is the cleanest illustration and I've written it up there.
The crate::-reach-back approach is fine for one PR against main; it only fails because eight are in flight against the same base. Two ways out:
- Stack them — each branches off its predecessor, so every
crate::path is correct by construction. AGENTS.md §11 already describes this ("keep stacked PRs in draft until dependencies merge upstream, then rebase and move out of draft"). - Keep them independent, rebase serially — after each lands, rebase the next and repoint its paths before merging.
The architecture-doc hunk
The doc text lists seven modules as extracted; this branch has lib.rs, main.rs, service.rs.
Worth being precise, because it would be reasonable to assume this is inherited drift rather than new: it isn't. main's current text is `lib.rs` is **not yet modularized** — see EAI-7768 (docs/architecture.md:34). Every one of the eight PRs rewrites that same single line with its own cumulative list, so each is independently introducing the inaccuracy, and the result is only true at merge order 477 → 479 → 480 → 481 → 483 → 484 → 487 → 489. Scoping each hunk to its own module makes it correct at any order, and turns eight whole-line rewrites into eight trivial appends.
Verified clean
- The endpoint-key guard survived intact.
ensure_public_service_has_endpoint_keyis still called twice insupervise_service(service.rs:574,:619), in the same order, with the same arguments and the samepreviously_requiredregistry-propagation path viaload_managed_services. This is the one I most wanted to be sure of: the PR moves the key-guard regression tests in the same commit as the code they guard, so a mistake in the guard could have travelled with its own tests and still gone green. - PID lifecycle unchanged —
terminate_processsends-TERM,force_terminate_remaining_processessends-KILLafterthread::sleep(Duration::from_millis(750)), and descendant-PID discovery is identical. Signal choice, ordering and escalation delay all preserved; nothing here can orphan or over-kill a process differently than before. run_daemon's loop unchanged — all threetokio::select!arms (ticker, webhook event, shutdown) identical,MissedTickBehavior::Delaypreserved.- Startup-phase log polling unchanged — offset arithmetic
*pos += read as u64and the rotation reset*pos = 0whenlen < *posboth preserved. - GPU-required behavior intact (AGENTS.md §6) —
device_policystill threaded through toengine_serve_http_args; no CPU fallback introduced. - The non-contiguous move is correct — both halves landed despite being separated by the entire test block, with nothing swept in from between them.
- Call sites —
stop_managed_service,run_daemon,supervise_serviceandprint_statusall repointed toservice::(lib.rs:281,:294,:309,:615,:2270); no definition left behind inlib.rs. - Imports —
HashSetandtokio::time::{self, MissedTickBehavior}correctly moved;BufReadcorrectly kept inlib.rs, wherestdin.lock().read_line()at line 1556 still needs it, and the#[cfg(test)] use tokio::time;is correctly narrowed to the one test that usestime::timeout. Nothing fails under-D warnings. - Tests — 130 attributes conserved (117 + 13), matching the claimed 13 exactly.
- No dead code, stubs,
todo!orunimplemented!; everylet _ =suppression matches its original. - No module-name collision — no sibling PR creates a
service.rsor definesrun_daemon/supervise_service/stop_managed_service/print_status. - Blast radius — nothing outside
apps/rocmd/src/references any moved symbol.
🤖 by agent-hub on AMD AgentHub
fbb96b9 to
13d2a9a
Compare
Pull request was converted to draft
|
Thanks for the thorough review. Both blocking points are resolved by stacking, per AGENTS.md §11:
While rebasing I also found and fixed the other direction of the same hazard: All 130 test attributes (117 + 13) still present and green, |
Second PR of Phase 5 (rocmd modularization, ROCMAI-27): pull the helpers shared across >=2 of the still-inline sandbox/mcp/service/ watchers clusters into their own module, ahead of extracting those clusters themselves. Covers GPU/amd-smi snapshotting, the bridge-snapshot diagnostic, CommandCapture/command-timeout plumbing, and small arg/healthcheck/endpoint-key utilities. No behavior change; call sites repointed to common::. The 8 tests that exercise these helpers directly (not mixed with a not-yet-extracted cluster's own logic) moved into common.rs's own #[cfg(test)] mod in this same PR. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Third PR of Phase 5 (rocmd modularization, ROCMAI-27): pull the local webhook source (axum routes, request validation, watcher-kind allow-list) into its own module. Fully self-contained aside from one call into payload_string, which stays at the crate root until watchers.rs is extracted in a later PR (PR8 will need to re-point that one call site once it moves). No behavior change; call sites repointed to webhook::. The 17 tests that exercise this module's own logic directly (not mixed with run_daemon/Cli-parsing/watcher-domain logic that happens to share the local_webhook_* name prefix) moved into webhook.rs's own Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Fourth PR of Phase 5 (rocmd modularization, ROCMAI-27): pull the Cli/Command clap definitions, SandboxToolArg/SandboxToolPolicy, and the top-level dispatch (run_cli/run_bin_cli/run_from_args) into their own module, following the mechanical-relocation pattern (thin dispatch; call sites into not-yet-extracted clusters stay crate::- qualified until those land). lib.rs re-exports run_bin_cli/ run_from_args via pub use so the crate's only two external call sites are untouched. No behavior change. SandboxToolArg/SandboxToolPolicy are consumed pervasively by the still-inline sandbox.rs cluster (and its test suite), so every one of those ~89 call sites is repointed to cli::SandboxToolArg/cli::SandboxToolPolicy. The 4 tests that exercise Cli/Command parsing directly (not mixed with sandbox/mcp/webhook/service domain logic that happens to share a name prefix or construct these types as a parameter) moved into cli.rs's own #[cfg(test)] mod in this same PR. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
1a01846 to
c03dd2f
Compare
13d2a9a to
a88ade9
Compare
c03dd2f to
535e747
Compare
a88ade9 to
a7d0524
Compare
Fifth PR of Phase 5 (rocmd modularization, ROCMAI-27): pull bubblewrap/ native sandbox execution, the atomic-write helper family, artifact prefetch policy gating, and the check_updates/driver_plan sandbox-tool result shaping into their own module. No behavior change. Rebased onto the common.rs/cli.rs/persistence.rs extractions earlier in this stack, so this module now reaches those items through their real owners (common::CommandCapture, cli::SandboxToolArg/SandboxToolPolicy, persistence::load_managed_services) instead of a crate-root placeholder; restart_managed_service, run_rocm_capture_for_paths, and stop_managed_service stay at the crate root pending service.rs. The rebase also found update_check_message independently duplicated in common.rs (landed earlier in this stack) and sandbox.rs (this PR); sandbox.rs now calls common's copy instead of keeping its own, and cli.rs's two sandbox-dispatch call sites (run_sandbox_runner, run_sandbox_tool) were updated to the sandbox:: path. 37 tests that exercise this module's own logic (atomic-write semantics, sandbox-tool dispatch and output shaping, prefetch policy gating, huggingface-URL classification) moved into sandbox.rs's own are really about the managed-service-registry stop/restart lifecycle (service.rs), or that use this module's sandbox_check_updates_value/ sandbox_driver_plan_value purely as mock watcher-event data, stayed in lib.rs to move with service.rs/watchers.rs in their own later PRs. sandbox.rs's test module now reuses crate::test_support::temp_app_paths (the shared fixture introduced earlier in this stack) instead of its own copy of temp_app_paths/unique_test_root/workspace_test_artifact_dir. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Sixth PR of Phase 5 (rocmd modularization, ROCMAI-27): pull the MCP stdio server, tool schema table, tool dispatch, and the rocm-subprocess capture/argv-building helpers behind the MCP tools into their own module. No behavior change. `CommandCapture` and `run_command_with_timeout` were independently claimed by this PR and by the already-landed common.rs extraction (both authored against the pre-stack monolith, unaware of each other); common.rs keeps ownership since it landed first in the merge order, so mcp.rs now imports both from `crate::common` instead of redefining them. The other crate-root reach-backs this module needed (`build_bridge_snapshot`, `gather_gpu_snapshot_for_config`, `bridge_engine_inventory`, `load_managed_services`) are now reached via `crate::common`/ `crate::persistence`, since those modules landed earlier in this stack; `stop_managed_service` stays reached via bare `crate::` since it has not moved out of lib.rs yet. `cli.rs`'s MCP dispatch arms and sandbox.rs's `run_rocm_capture_for_paths` import (both landed before this PR, so both still reached into lib.rs directly) are repointed to `crate::mcp::`. 15 tests that exercise this module's own logic (MCP tool-schema/ dispatch shape, read-only-verb classification, install_sdk/ install_engine/launch_server/watcher_enable argv building, and read_tail_lines) moved into mcp.rs's own #[cfg(test)] mod in this same PR, reusing `crate::test_support::workspace_test_artifact_dir` instead of a second local copy. Tests that share a name prefix or construct these types but actually exercise Cli parsing, run_daemon, or watcher-domain logic stayed in lib.rs for their own later PRs. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
535e747 to
46b4ed4
Compare
Seventh PR of Phase 5 (rocmd modularization, ROCMAI-27): pull the managed-service PID lifecycle (stop/terminate/descendant-pid discovery), run_daemon's foreground loop, supervise_service's spawn-and-recover path, and serve-log startup-phase polling into their own module. Non-contiguous in the source (the startup-phase/ shutdown-signal helpers sit far from the rest, separated by the whole test block) -- both halves moved in this PR. No behavior change. Reaches back into still-crate-root items (load_managed_services, record_event, start_local_webhook_source, receive_local_webhook_event, evaluate_watchers/evaluate_watchers_for_ events/reconcile_watcher_snapshots, WATCHER_TICK_INTERVAL, parse_gpu_indices_arg/optional_arg/ensure_public_service_has_endpoint_ key/apply_endpoint_key_env/engine_healthcheck_ready, load_service_record) via crate::, since the modules that will eventually own those (persistence.rs/webhook.rs/watchers.rs/ common.rs) are separate, independent PRs not present on this branch. 13 tests that exercise this module's own logic (the supervise key-guard regression tests, run_daemon's automation-loop gate, stop_managed_service, descendant-pid discovery, startup-phase log parsing, engine_serve_http_args) moved into service.rs's own sandbox/mcp-domain tests that construct ManagedServiceRecord or call stop_managed_service merely as setup stayed in lib.rs for watchers.rs/other PRs. Stacked on rocmai-83-mcp (ROCMAI-83 Phase 5 batch, AGENTS.md §11): rebased service.rs's crate:: reaches onto the sibling modules already landed by the predecessor PRs (persistence.rs, common.rs, webhook.rs), leaving only the references still owned by lib.rs (evaluate_watchers*, reconcile_watcher_snapshots, WATCHER_TICK_INTERVAL, load_service_record) until watchers.rs lands. Deduped the test-only temp_app_paths/ unique_test_root helpers in favor of the shared crate::test_support module, and repointed cli.rs/mcp.rs/sandbox.rs's stale crate::-root calls to service::stop_managed_service/run_daemon/supervise_service/ print_status now that this module owns them. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
a7d0524 to
f9bd7cf
Compare
r0x0r
left a comment
There was a problem hiding this comment.
Follow-up review (draft): the code motion holds, but the pushed head is not what the description says
Since my review at fbb96b9, the branch was stacked and rebased (f9bd7cf), #489 was closed, and the description and a comment now say this PR is service.rs + watchers.rs against main. The branch on GitHub does not match that. I'm commenting only, since it is a draft with a conflict.
Status of my earlier findings
- (b)
crate::breakage across sibling PRs: resolved in mechanism, unverifiable in practice. The serial order #479 -> #483 -> this PR turns a silent break into an explicit dependency. I overlaidservice.rsandwatchers.rs(from #489's1c066ea) onmainand on #483's head and resolved everycrate::path. On #483's head there are zero unresolved paths, so the order works. Onmainthere are 14. See Blocking 2 for why #483 is not yet a sound base. - (c) architecture.md: not resolved. The hunk is still a whole-line cumulative rewrite. Head's text still says "Still pending: the watcher cluster", which contradicts "completes Phase 5". It also drops the "earns a place in
common.rs" admission-rule sentence that #479 put onmain. Resolve the conflict by keeping that sentence.
Blocking before approval
- Head
f9bd7cfhas nowatchers.rsand is still the old stack. Againstmain, the diff is 8 files, +9325/-9161. It carries thecommon/webhook/cli/sandbox/mcpcommits on the57b78c6base, andservice.rsis only the last of those.watchers.rsexists only on closed #489's head.git merge-treeshows add/add oncommon.rsand conflicts inlib.rsandarchitecture.md. The commit message is also stale ("Stacked on rocmai-83-mcp"). The "47 tests / 13 + 34" and "scope note" comments describe an unpushed state. Please push the rebased branch. - The dependency claims do not match
mainor #483.- #479 landed
common.rsonly.webhook.rsandcli.rsare not onmain. - #479's review fixup moved
parse_gpu_indices_arg,optional_argandengine_healthcheck_readyback intolib.rs. Socrate::common::{parse_gpu_indices_arg, optional_arg, engine_healthcheck_ready}does not resolve onmain. - #483's head (
36513c1) re-adds those helpers, plusprint_bridge_snapshot, tocommon.rs. That quietly reverses the landed fixup and the rule indocs/architecture.md. By that ruleparse_gpu_indices_argandengine_healthcheck_readyhave exactly one caller (service.rs:525,:839) and belong inservice.rs.optional_argis shared byservice.rsandwatchers.rs, socommon.rsis right for it. - The body says the code needs
mcp::paths from #483, but neither module referencesmcp::, and #483's head has nomcp.rs(its body says sandbox + mcp). The paths actually needed arewebhook::,cli::andsandbox::. - #483's inline MCP code still calls bare
stop_managed_service(lib.rs:775). This PR has to repoint it once rebased.
Please make the descriptions and branches agree, and decide where those three helpers live.
- #479 landed
Non-blocking
lib.rskeepsWATCHER_TICK_INTERVAL(used only byrun_daemon) andARTIFACT_PREFETCH_TIMEOUT(sandbox). It also keeps one sandbox-subject test,sandbox_tool_stop_server_updates_manifest_and_skips_current_pid. The crate-level#![allow(clippy::items_after_test_module)]is now vacuous. Moving these next to their users would make "lib.rs is glue" literal.- The "130 tests" claim is stale.
mainnow has 131 because ofinstall_sdk_rejects_system_prefix_reached_by_escaping_home. It will conflict inlib.rson rebase and likely belongs inmcp.rs. - Only a docs-build status is reported on the head.
cargo xtask coverage(AGENTS.mdcoverage floors, rocmd 75.36) is not in the test plan. Motion should be neutral, but run it.
Verified clean (service.rs from f9bd7cf, watchers.rs from #489's 1c066ea; no cargo here, so nothing was built or run)
- A token-level diff of the removed
lib.rscode againstservice.rsandwatchers.rsshows only module-path prefixes,pub(crate)and rustfmt reflow. That covers the endpoint-key guard, the TERM/KILL 750ms escalation, therun_daemonselect!arms, log offset arithmetic, watcher dispatch, recovery classification, proposal queuing anddetached_rocmd_command(both cfg variants). Every watcher threshold constant is unchanged and moved with it. - No function was lost from
lib.rs.pub(crate)coverage and path resolution check out in both heads. - Tests: the 130 test names are identical to pre-stack (13 service + 34 watchers). There is still 1
#[ignore], and notodo!/stubs. - Nothing outside
apps/rocmd/srcanddocs/is touched.
🤖 by agent-hub on AMD AgentHub
Summary
Seventh and eighth (final) PRs of Phase 5 (ROCMAI-83, part of the modularization epic ROCMAI-27), consolidated into one PR. Pulls
service.rsandwatchers.rsout ofapps/rocmd/src/lib.rs. Originally opened separately as #487/#489; consolidated here to cut down the PR count for this phase. The two are coupled — watchers.rs's server-recover recovery logic calls directly into service.rs's managed-service lifecycle.service.rs — the managed-service PID lifecycle (stop/terminate/descendant-pid discovery),
run_daemon's foreground loop,supervise_service's spawn-and-recover path, and serve-log startup-phase polling. Non-contiguous in the source (the startup-phase/shutdown-signal helpers sit far from the rest of the cluster, separated by the entire test block) — both halves moved.watchers.rs — event collection/dispatch for all built-in watchers (TheRock update, GPU metrics/thermal-pressure, cache-warm, driver-upgrade, server-recover), managed-service recovery classification, and automation-proposal queuing. Also non-contiguous:
queue_proposal/queue_proposal_with_arguments/proposal_tool_for_action/proposal_arguments_for_actionsit between persistence.rs-territory code, anddetached_rocmd_commandsits at the tail of the file before the test module. Watcher-only consts (SERVER_RECOVER_BACKOFF_MS,SERVER_TRANSIENT_STALE_MS,ENDPOINT_HEALTH_TIMEOUT,THEROCK_UPDATE_INTERVAL_MS,GPU_METRICS_INTERVAL_MS, GPU thermal/VRAM pressure thresholds) moved with it.common.rs/persistence.rs(already merged/landed via ROCMAI-83: extract persistence.rs from apps/rocmd/src/lib.rs #477 and ROCMAI-83: extract common.rs, webhook.rs, and cli.rs from apps/rocmd/src/lib.rs #479) via the appropriate qualified paths.run_daemon's automation-loop gate, descendant-pid discovery, startup-phase log parsing, event-collector/gpu/cache-warm/driver-upgrade/server-recover dispatch, therock-update handling, watcher-policy mode mapping, recovery-reason classification) moved into each module's own#[cfg(test)] modin this same PR.docs/architecture.mdupdated in this PR — this completes Phase 5:lib.rsis now top-level glue (module declarations and the two externally-consumed entry points re-exported fromcli.rs).Targets
maindirectly (not stacked on a sibling PR branch) so this PR can't be silently auto-closed if an intermediate branch gets deleted. Depends on #479 and #483 merging first — this code calls intosandbox::/mcp::paths that only exist once #483 lands, so CI will be red until then; that's expected, not a regression. Merge order: #479 → #483 → this PR.Supersedes #489, which is closed in favor of this PR.
Test plan
cargo build(rocmd, androcm+rocmdtogether to confirm the external API surface is untouched)cargo clippy --workspace --all-targets -- -D warningscargo test -p rocmd— 130 tests, same count as before this change (129 passed + 1 pre-existing ignored)cargo xtask manifest --checkcargo fmt/ prek hooks cleantests/e2e-cucumber(CI) — expected red until ROCMAI-83: extract common.rs, webhook.rs, and cli.rs from apps/rocmd/src/lib.rs #479 and ROCMAI-83: common.rs follow-up, webhook.rs, cli.rs and sandbox.rs extraction from apps/rocmd/src/lib.rs #483 merge