Skip to content

ROCMAI-83: extract watchers.rs from apps/rocmd/src/lib.rs - #489

Closed
jussielo-amd wants to merge 7 commits into
ROCm:rocmai-83-servicefrom
jussielo-amd:rocmai-83-watchers
Closed

jussielo-amd wants to merge 7 commits into
ROCm:rocmai-83-servicefrom
jussielo-amd:rocmai-83-watchers

Conversation

@jussielo-amd

Copy link
Copy Markdown
Collaborator

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.rs module.

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, and rocm+rocmd together to confirm the external API surface is untouched)
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p rocmd — 130 tests, same count as before this change (129 passed + 1 pre-existing ignored)
  • cargo xtask manifest --check
  • cargo fmt / prek hooks clean
  • tests/e2e-cucumber (CI)

@jussielo-amd
jussielo-amd marked this pull request as ready for review October 1, 2026 13:55
@jussielo-amd
jussielo-amd requested a review from a team as a code owner October 1, 2026 13:55
@jussielo-amd
jussielo-amd requested a review from johnl-amd October 1, 2026 13:55

@r0x0r r0x0r left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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").
  2. Keep them independent but rebase serially — after each lands on main, rebase the next and repoint its crate:: 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.rs is 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.rs here, so nothing is re-exported from it; run_bin_cli and run_from_args are still defined directly in lib.rs at lines 250 and 255.
  • The doc lists all eight modules as extracted; apps/rocmd/src/ contains lib.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_MS 30_000 · SERVER_TRANSIENT_STALE_MS 5 * 60 * 1_000 · ENDPOINT_HEALTH_TIMEOUT Duration::from_millis(250) · THEROCK_UPDATE_INTERVAL_MS 6 * 60 * 60 * 1000 · GPU_METRICS_INTERVAL_MS 60 * 1000 · GPU_THERMAL_HOTSPOT_PRESSURE_C 95.0 · GPU_THERMAL_MEMORY_PRESSURE_C 95.0 · GPU_MEMORY_VRAM_PRESSURE_PERCENT 95.0

    The "nothing else in lib.rs used 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_action identical, 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_services correctly did not move (they remain at lib.rs:3640 and :3683 for #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 in lib.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>
@jussielo-amd
jussielo-amd changed the base branch from main to rocmai-83-service October 2, 2026 16:42
@jussielo-amd
jussielo-amd marked this pull request as draft October 2, 2026 16:42
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>
@jussielo-amd

Copy link
Copy Markdown
Collaborator Author

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:

crate:: reach-backs / merge-order problem. Per AGENTS.md §11, all eight Phase 5 PRs are now stacked in merge order (#477 → #479 → #480 → #481 → #483 → #484 → #487 → #489), rather than independently branched off the same pre-stack main. This PR's base is now rocmai-83-service (#487), and I rebased watchers.rs onto it: all ~18 crate:: reach-backs that the pre-stack diff left dangling (CommandCapture, SandboxToolArg/SandboxToolPolicy, run_sandbox_tool, record_event, load_managed_services, update_check_message, wait_for_port, payload_string, etc.) now point at their real homes — common::, cli::, sandbox::, persistence::, webhook:: — verified against each module's actual current content rather than assumed from the original diff. I also repointed the stale bare crate:: calls the review's table doesn't cover because they live in this PR's own direction: service.rs's evaluate_watchers/evaluate_watchers_for_events/reconcile_watcher_snapshots/load_service_record and webhook.rs's payload_string now call crate::watchers::, and sandbox.rs's restart_managed_service import now comes from crate::watchers. cargo check/clippy -D warnings/fmt --check/test are all green on the rebased branch (129 passed, 1 pre-existing ignore).

On the duplicate-ownership front: CommandCapture/update_check_message are resolved in common.rs's favor per the note on #484 — watchers.rs imports both from crate::common rather than redefining them, and I did the same systematic check for every symbol this PR moves (no other duplicates found). Also 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.

"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: lib.rs on this branch is 94 lines of module declarations, two re-exported entry points, two shared consts, and one surviving test; all seven sibling modules (persistence.rs, common.rs, webhook.rs, cli.rs, sandbox.rs, mcp.rs, service.rs) plus watchers.rs itself exist. docs/architecture.md's hunk now lists all eight as extracted with no "still pending" clause, which is genuinely accurate once the stack lands in this order.

PR is back in draft with base rocmai-83-service, matching the rest of the stack, and will come out of draft once #487 and its predecessors merge.

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>
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>
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>
@jussielo-amd
jussielo-amd requested a review from r0x0r October 5, 2026 08:17
@jussielo-amd
jussielo-amd marked this pull request as ready for review October 5, 2026 08:17
@jussielo-amd

Copy link
Copy Markdown
Collaborator Author

Closing — watchers.rs is now part of #487 (consolidated with service.rs to cut down the PR count for this phase). No further action needed on this PR; follow #487 for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-hub-reviewed agent-hub has reviewed this

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants