ROCMAI-83: extract watchers.rs from apps/rocmd/src/lib.rs - #489
jussielo-amd wants to merge 7 commits into
Conversation
r0x0r
left a comment
There was a problem hiding this comment.
Review (draft): code motion is clean; this PR exposes a phase-wide merge problem
The extraction itself is good — I audited every moved constant's value and the watcher decision logic, and found no behavior change. But reviewing this one alongside its seven siblings surfaced something none of them shows on its own, and it is the reason I'd hold the whole phase rather than this PR specifically.
payload_string silently breaks #480
This PR moves payload_string out of the crate root into watchers.rs:1101 and correctly repoints its own two call sites in lib.rs to watchers::payload_string.
#480 adds webhook.rs, which calls crate::payload_string at webhook.rs:164 and :168, and keeps the function at the crate root (lib.rs:4516, promoted to pub(crate) for exactly that purpose).
After both merge, crate::payload_string does not exist and rocmd does not compile.
What makes this one dangerous rather than merely annoying is where the breakage lives. The lib.rs side will conflict — #480 edits the payload_string line while this PR deletes it, which git reports as an edit/delete. But the two broken call sites are inside webhook.rs, a file this PR never touches. Resolving the lib.rs conflict by taking this PR's deletion produces a tree with no conflict markers, no merge warning, and a compile error two files away.
This is systemic, not a one-off
I checked every crate::-qualified reach-back in all eight new module files against the symbols each sibling moves out of lib.rs. 52 of them break on merge, across six of the eight PRs:
| PR | module | crate:: refs broken by a sibling |
|---|---|---|
| #489 | watchers.rs |
18 (incl. CommandCapture, SandboxToolArg/Policy, run_sandbox_tool, record_event) |
| #487 | service.rs |
13 (incl. evaluate_watchers, start_local_webhook_source, load_service_record) |
| #481 | cli.rs |
11 (incl. run_daemon, supervise_service, run_mcp_server, handle_mcp_tool_call) |
| #484 | mcp.rs |
5 (incl. build_bridge_snapshot, stop_managed_service) |
| #480 | webhook.rs |
1 (payload_string) |
| #479 | common.rs |
1 (load_managed_services) |
Every one has the same shape as the payload_string case: the dangling reference sits in a new file that the PR relocating the symbol never opens, so there is nothing for git to conflict on.
The crate::-reach-back design is sound for one PR against main — the problem is only that eight of them are in flight against the same base. Two ways out:
- Stack them. Each PR branches off its predecessor, so each sees the real location of everything already moved and
crate::paths are correct by construction. AGENTS.md §11 already prescribes this ("keep stacked PRs in draft until dependencies merge upstream, then rebase and move out of draft"). - Keep them independent but rebase serially — after each lands on
main, rebase the next and repoint itscrate::paths before merging.
Either works; what does not is merging them as-is in any order.
Three symbols are claimed by two PRs each
Separately from the dangling references, these are defined in two different modules by two open PRs:
| symbol | ||
|---|---|---|
CommandCapture |
#479 common.rs:204 |
#484 mcp.rs:846 |
run_command_with_timeout |
#479 common.rs |
#484 mcp.rs |
update_check_message |
#479 common.rs:64 |
#483 sandbox.rs:848 |
If two of these land, the symbol exists in both modules — the copies compile, but one becomes dead code, which the workspace's -D warnings gate treats as a failure, and the call sites each PR repointed differently disagree about which one is canonical. CommandCapture has a correct answer by the phase's own rule (two consumer clusters → common.rs); I've written that up on #484.
This branch does not complete Phase 5
The PR body and the doc hunk both say "this completes Phase 5: lib.rs is now top-level glue (module declarations and the two externally-consumed entry points re-exported from cli.rs)." On this branch:
lib.rsis 6,903 lines (down from 9,980 — so ~31% of the file moved, not all of it). It still contains the CLI definition, sandbox tooling, the MCP server, service lifecycle and GPU snapshot collection.- There is no
cli.rshere, so nothing is re-exported from it;run_bin_cliandrun_from_argsare still defined directly inlib.rsat lines 250 and 255. - The doc lists all eight modules as extracted;
apps/rocmd/src/containslib.rs,main.rs,watchers.rs.
That claim is true of the phase, not of this PR. Being the last one, it carries the largest such gap — and for the record, main's current text is plainly `lib.rs` is **not yet modularized** — see EAI-7768 (line 34), so none of this is inherited from an already-drifted doc.
Verified clean
-
Every moved constant's value is unchanged — I checked these individually because a changed numeric literal here silently alters when the daemon fires recovery or declares thermal pressure, and no test-count check would catch it:
SERVER_RECOVER_BACKOFF_MS30_000·SERVER_TRANSIENT_STALE_MS5 * 60 * 1_000·ENDPOINT_HEALTH_TIMEOUTDuration::from_millis(250)·THEROCK_UPDATE_INTERVAL_MS6 * 60 * 60 * 1000·GPU_METRICS_INTERVAL_MS60 * 1000·GPU_THERMAL_HOTSPOT_PRESSURE_C95.0·GPU_THERMAL_MEMORY_PRESSURE_C95.0·GPU_MEMORY_VRAM_PRESSURE_PERCENT95.0The "nothing else in
lib.rsused them" claim also holds — zero remaining references on this branch. -
GPU pressure evaluation — all three comparisons are
>=before and after; boundary behavior unchanged, and no CPU fallback introduced (AGENTS.md §6). -
Recovery classification —
service_recovery_event_kind's arms and prefixes (healthcheck_status_,endpoint_status_, fallback) unchanged in content and order. -
Proposal mappings — all five arms of
proposal_tool_for_action/proposal_arguments_for_actionidentical, same tools and same JSON shapes. -
The non-contiguous move is correct — all three sub-blocks landed, nothing between them was swept in, and
record_event/load_managed_servicescorrectly did not move (they remain atlib.rs:3640and:3683for #477). Given the middle sub-block sits sandwiched between those two functions, that is the easy mistake here and it was not made. -
Tests — 130 attributes conserved (96 + 34), matching the claimed 34 exactly.
-
Imports/visibility — all
crate::targets resolve on this branch; no unused imports left inlib.rs; the rocm_core items that became test-only are correctly#[cfg(test)]-scoped. -
Blast radius — nothing outside
apps/rocmd/src/references any moved symbol.
🤖 by agent-hub on AMD AgentHub
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>
b0ff7a6 to
3e59989
Compare
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>
|
Thanks for the thorough audit — replying to the phase-wide concerns from this review (#5380436537), both of which are resolved by how the stack has landed:
On the duplicate-ownership front: "This completes Phase 5" / doc overclaim. Correct that it wasn't true against this PR's pre-stack diff alone — fixed now that the base is #487: PR is back in draft with base |
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>
13d2a9a to
a88ade9
Compare
3e59989 to
28ba40b
Compare
a88ade9 to
a7d0524
Compare
28ba40b to
c9e0779
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>
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
Eighth and final PR of Phase 5 (rocmd modularization, ROCMAI-27): pull
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 into their own module.
Non-contiguous in the source: most of the cluster is one block, but
queue_proposal/queue_proposal_with_arguments/proposal_tool_for_action/
proposal_arguments_for_action sit sandwiched between record_event and
load_managed_services (persistence.rs territory, stays in lib.rs on
this branch), and detached_rocmd_command sits at the tail of the file
right before the test module. All three sub-blocks moved in this PR.
Watcher-only consts (SERVER_RECOVER_BACKOFF_MS,
SERVER_TRANSIENT_STALE_MS, ENDPOINT_HEALTH_TIMEOUT,
THEROCK_UPDATE_INTERVAL_MS, GPU_METRICS_INTERVAL_MS, and the GPU
thermal/VRAM pressure thresholds) moved with it, since nothing else in
lib.rs used them.
No behavior change. Reaches back into items owned by sibling modules
(record_event/load_managed_services via crate::persistence,
run_sandbox_tool/SandboxToolArg/SandboxToolPolicy via
crate::sandbox/crate::cli, update_check_message/
record_notification_audit via crate::sandbox, and the
engine-healthcheck/endpoint-key/wait_for_port/optional_arg/
gather_gpu_snapshot_for_config family via crate::common) and one
webhook-domain type (LocalWebhookEventRequest/
local_webhook_event_from_request via crate::webhook).
34 tests that exercise this module's own logic (event-collector/gpu/
cache-warm/driver-upgrade/server-recover dispatch, therock-update
handling, watcher-policy mode mapping, recovery-reason
classification) moved into watchers.rs's own #[cfg(test)] mod in this
same PR. Tests that are fundamentally about Cli parsing, run_daemon,
sandbox-tool dispatch shape, or the persistence-layer record_event
itself stayed in lib.rs for their own PRs.
This completes Phase 5 of the rocm-cli modularization effort
(ROCMAI-27): lib.rs is now top-level glue -- module declarations and
the two externally-consumed entry points re-exported from cli.rs.
Stacked on rocmai-83-service (ROCMAI-83 Phase 5 batch, AGENTS.md §11):
rebased watchers.rs's crate:: reaches onto all seven sibling modules
now landed by the predecessor PRs (persistence.rs, common.rs,
webhook.rs, cli.rs, sandbox.rs, mcp.rs, service.rs) -- this is the
last PR in the stack, so no reach-back is left unresolved. Repointed
service.rs's stale crate::-root calls (evaluate_watchers,
evaluate_watchers_for_events, reconcile_watcher_snapshots,
load_service_record) and webhook.rs's stale crate::payload_string call
to watchers::, now that this module owns them; fixed sandbox.rs's
`use crate::{ARTIFACT_PREFETCH_TIMEOUT, restart_managed_service}` to
import restart_managed_service from crate::watchers instead. Deduped
the test-only temp_app_paths/unique_test_root/workspace_test_artifact_dir
trio in watchers.rs's test module in favor of the shared
crate::test_support module. Updated docs/architecture.md: all eight
`apps/rocmd` modules are now listed as extracted, with no "still
pending" clause remaining.
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
c9e0779 to
1c066ea
Compare
f9bd7cf to
f5ebc2f
Compare
Summary
Eighth and final PR of Phase 5 (ROCMAI-83, part of the modularization epic ROCMAI-27). Pulls 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 into their own
watchers.rsmodule.queue_proposal/queue_proposal_with_arguments/proposal_tool_for_action/proposal_arguments_for_actionsit sandwiched betweenrecord_eventandload_managed_services(persistence.rs territory, stays inlib.rson this branch), anddetached_rocmd_commandsits at the tail of the file right before the test module. All three sub-blocks moved in this PR. Watcher-only consts (SERVER_RECOVER_BACKOFF_MS,SERVER_TRANSIENT_STALE_MS,ENDPOINT_HEALTH_TIMEOUT,THEROCK_UPDATE_INTERVAL_MS,GPU_METRICS_INTERVAL_MS, and the GPU thermal/VRAM pressure thresholds) moved with it, since nothing else inlib.rsused them.record_event/load_managed_services,run_sandbox_tool+SandboxToolArg/SandboxToolPolicy,update_check_message/record_notification_audit, the engine-healthcheck/endpoint-key/wait_for_port/optional_arg/gather_gpu_snapshot_for_configfamily) viacrate::, since the modules that will eventually own those (persistence.rs/sandbox.rs/cli.rs/common.rs) are separate, independent PRs not present on this branch (see ROCMAI-83: extract persistence.rs from apps/rocmd/src/lib.rs #477, ROCMAI-83: extract common.rs, webhook.rs, and cli.rs from apps/rocmd/src/lib.rs #479, ROCMAI-83: extract webhook.rs from apps/rocmd/src/lib.rs #480, ROCMAI-83: extract cli.rs from apps/rocmd/src/lib.rs #481, ROCMAI-83: common.rs follow-up, webhook.rs, cli.rs and sandbox.rs extraction from apps/rocmd/src/lib.rs #483, ROCMAI-83: extract mcp.rs from apps/rocmd/src/lib.rs #484, ROCMAI-83: extract service.rs and watchers.rs from apps/rocmd/src/lib.rs #487).watchers.rs's own#[cfg(test)] modin this same PR. Tests that are fundamentally aboutCliparsing,run_daemon, sandbox-tool dispatch shape, or the persistence-layerrecord_eventitself stayed inlib.rsfor their own PRs.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).Independent of #477, #479, #480, #481, #483, #484, #487 — branched separately from
main, not stacked, per the one-off PR approach for this phase.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)