diff --git a/apps/rocm/src/examine_report_text.rs b/apps/rocm/src/examine_report_text.rs index 2674b2a7d..980572433 100644 --- a/apps/rocm/src/examine_report_text.rs +++ b/apps/rocm/src/examine_report_text.rs @@ -9,7 +9,7 @@ //! Relocated from `main.rs` with no behavior change (see //! `docs/architecture.md`). The `rocm model` catalog renderer used to live //! here too; it moved to `model_catalog_text.rs`, which this module does not -//! call into outside its own tests. +//! call into. //! //! This module owns: the launch-summary/examine text surfaces //! (`render_launch_summary`, `render_examine_text`, @@ -49,11 +49,9 @@ //! The regression tests for these surfaces live in this module's own //! `mod tests`, reaching the shared `test_paths` fixture through //! `crate::tests::` and `write_test_pip_runtime` through -//! `crate::test_support::`. One of them, -//! `linux_only_engine_runtime_status_is_explicit_on_native_windows`, also -//! calls `model_catalog_text.rs`'s `model_registry_adapter_availability_note`, -//! which is `pub(crate)` for that reason; its primary subject is -//! `engine_runtime_status_label`, so it stays here. +//! `crate::test_support::`. The note the `rocm model --verbose` listing prints +//! for a rule-out engine is tested in `model_catalog_text.rs`, next to the +//! renderer that prints it. use std::fmt::Write as _; use std::path::PathBuf; @@ -578,7 +576,6 @@ mod tests { use std::fs; use super::*; - use crate::model_catalog_text::model_registry_adapter_availability_note; use crate::runtimes_cmd::runtime_manifest_path; use crate::test_support::write_test_pip_runtime; use crate::tests::test_paths; @@ -891,13 +888,8 @@ mod tests { engine_runtime_status_label("vllm", &detect), "unsupported_native_windows" ); - assert!( - model_registry_adapter_availability_note("vllm", &|| true) - .is_some_and(|note| note.contains("unsupported_native_windows")) - ); } else { assert_eq!(engine_runtime_status_label("vllm", &detect), "not found"); - assert!(model_registry_adapter_availability_note("vllm", &|| true).is_none()); } } diff --git a/apps/rocm/src/model_catalog_text.rs b/apps/rocm/src/model_catalog_text.rs index c9cafd54e..eca491bff 100644 --- a/apps/rocm/src/model_catalog_text.rs +++ b/apps/rocm/src/model_catalog_text.rs @@ -21,9 +21,7 @@ //! //! The two renderers and `format_bytes` are `pub(crate)`; `main.rs` imports them //! and `chat.rs`, `storage.rs` and `automations.rs` reach them as -//! `crate::`. `model_registry_adapter_availability_note` is `pub(crate)` -//! only so the engine-status test that stays in `examine_report_text.rs` can -//! call it. Every other item is private. +//! `crate::`. Every other item is private. use std::collections::BTreeMap; use std::fmt::Write as _; @@ -819,7 +817,7 @@ fn append_model_engine_support_lines( } #[allow(dead_code)] -pub(crate) fn model_registry_adapter_availability_note( +fn model_registry_adapter_availability_note( engine: &str, docker_reachable: &dyn Fn() -> bool, ) -> Option<&'static str> { @@ -1407,4 +1405,21 @@ mod tests { assert!(!rendered.contains("CPU fallback")); assert!(rendered.contains("engine_action: use /engine install ")); } + + /// The adapter-availability note the verbose listing appends for vLLM: a + /// native Windows host gets the explicit `unsupported_native_windows` + /// status, every other host gets no note. Moved here from the + /// `engine_runtime_status_label` test in `examine_report_text.rs`, which + /// keeps the `runtime:` label half of the same check. + #[test] + fn vllm_adapter_availability_note_is_explicit_on_native_windows() { + if cfg!(windows) { + assert!( + model_registry_adapter_availability_note("vllm", &|| true) + .is_some_and(|note| note.contains("unsupported_native_windows")) + ); + } else { + assert!(model_registry_adapter_availability_note("vllm", &|| true).is_none()); + } + } } diff --git a/docs/architecture.md b/docs/architecture.md index bbd9d4b6f..1fde39ff9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -52,7 +52,7 @@ Each per-crate module inventory lists one file per line. Flat inventories (every - [`engine_runtime_select.rs`](../apps/rocm/src/engine_runtime_select.rs) — mechanical relocation, owns types (Phase 6f, ROCMAI-215): deciding which runtime/env a `serve`/`engines install` invocation resolves to (`EngineSelection`, `resolve_engine_selection`, `validate_engine_selection_runtime`, `single_ready_runtime_key`, `resolve_runtime_selector_to_exact_key`), small argv-building helpers used when relaunching a managed engine (`optional_arg`, `engine_recipe_json_arg`, `parse_engine_recipe_json_arg`, `daemon_run_argv`, `managed_service_launcher_path`), the `ROCM_CLI_*` environment variables every managed child inherits (`app_path_env_vars`/`app_path_env_var_values`/`app_path_env_var_refs`/`apply_app_path_env`), and polling a freshly launched service until it can answer inference requests (`wait_for_service_http_ready`, `wait_for_service_http_ready_with_progress`, `service_http_readiness_paths`, `http_get_local_service`, `http_post_local_service_json`, `service_http_readiness_response_ready`, `lemonade_health_ready_for_model`, `model_list_ready_for_model`, `service_model_gpu_ready`, `service_model_reports_rocm_backend`, `service_model_names_match`). `const fn status_for_readiness` sits physically interleaved in this span in `main.rs` but stayed there instead of moving — it has callers outside this cluster ([`service_launch.rs`](../apps/rocm/src/service_launch.rs) and [`service_control.rs`](../apps/rocm/src/service_control.rs)'s `record_restart_readiness`, which `restart_internal_managed_service_with` calls), so it stays a shared crate-root function, reached only from this module's own tests, via `use crate::status_for_readiness;`. Which items are `pub(crate)` and who calls them is documented in the module's own doc comment rather than repeated here. The cluster's regression tests live in the module's own `mod tests`. What remains in `main.rs` immediately after this cluster is `treat_as_natural_language`, dispatch-adjacent natural-language-invocation routing left over from an earlier phase (ROCMAI-87), explicitly out of scope and untouched by this split. - [`engines_cmd.rs`](../apps/rocm/src/engines_cmd.rs) — mechanical relocation, owns private types (Phase 6a): `engines()` (the `rocm engines` command), `ensure_self_managed_engine_ready` (the readiness check [`serve_cmd.rs`](../apps/rocm/src/serve_cmd.rs) runs for engines that bring their own runtime) and `env_root_for_service` (read from [`service_launch.rs`](../apps/rocm/src/service_launch.rs) and [`service_control.rs`](../apps/rocm/src/service_control.rs)). Which runtime an engine installs into and where its environment lives is answered in [`engine_install_env.rs`](../apps/rocm/src/engine_install_env.rs), which this module imports from. This module also calls into [`runtime_install_finalize.rs`](../apps/rocm/src/runtime_install_finalize.rs) for `settle_engine_install` and the host system-package installs; that module does not import this one. - [`examine_cmd.rs`](../apps/rocm/src/examine_cmd.rs) — mechanical relocation, dispatch-adjacent, owns private types: `rocm examine`'s command handler (`examine()`), which prints the human report through [`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs)'s `examine_human_report`, or with `--json` the document built from the private `ExamineJson`/`ExamineJsonSummary` types (the `Examination` verbatim plus a summary of what the CLI knows but the probe cannot). `examine()` is `pub(crate)` and re-imported in [`main.rs`](../apps/rocm/src/main.rs) for `dispatch()`. No test in the crate calls it or builds the document directly. -- [`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs) — mechanical relocation: the launch-summary/examine text surfaces (`render_launch_summary`, `render_examine_text`, `render_examine_text_with_paths`, `examine_human_report`, `render_examine_plain_header`, `plain_status_label`) and the engine-inventory listing shared by `rocm engines` and `rocm examine` (`write_default_engine_legend`, `render_engine_inventory_text_with_paths`, `append_examine_runtime_state`, `append_examine_engine_inventory`, `append_engine_detect_summary`, `friendly_engine_runtime_kind`, `friendly_engine_detect_notes`, `friendly_engine_detect_note_fallback`, `engine_runtime_status_label`). The `DEFAULT_ENGINE_MARKER` constant is shared by both engine-inventory renderers so the two copies cannot drift apart. The `rocm model` catalog renderer is not here; it lives in [`model_catalog_text.rs`](../apps/rocm/src/model_catalog_text.rs), and nothing in this file calls into it outside its own tests. [`examine_cmd.rs`](../apps/rocm/src/examine_cmd.rs)'s `examine()`, which the `Command::Examine` arm calls, imports and calls `examine_human_report`; `main.rs`'s own `launch_default` calls `render_launch_summary`, re-imported there. `render_examine_text`, `render_examine_text_with_paths` and `render_engine_inventory_text_with_paths` are `pub(crate)`, read from [`chat.rs`](../apps/rocm/src/chat.rs)'s deterministic tool-output summaries; `render_engine_inventory_text_with_paths` is also read from [`engines_cmd.rs`](../apps/rocm/src/engines_cmd.rs)'s `render_engine_inventory_text`; `append_examine_runtime_state` is also `pub(crate)`, read only from [`runtimes_cmd.rs`](../apps/rocm/src/runtimes_cmd.rs)'s own test module, so `main.rs`'s re-import of it is itself `#[cfg(test)]` rather than carrying an otherwise-unused import into a non-test build. Every other item in this list has no caller outside this module and stays private. This module also reaches items that live at the crate root because they have other callers: `engine_inventory()` (also read from [`update_cmd.rs`](../apps/rocm/src/update_cmd.rs) and [`bridge_snapshot.rs`](../apps/rocm/src/bridge_snapshot.rs)), `PlatformRuleOut` and `engine_ruled_out_by_platform` (defined in [`model_fit.rs`](../apps/rocm/src/model_fit.rs) and re-imported in `main.rs`) — all reached via `use crate::{...};`. The regression tests live in this module's own `mod tests`, reaching the shared `test_paths` fixture through `crate::tests::` and `write_test_pip_runtime`/`runtime_manifest_path` through `crate::test_support::`/`crate::runtimes_cmd::`. One test here, `linux_only_engine_runtime_status_is_explicit_on_native_windows`, also calls `model_registry_adapter_availability_note` from `model_catalog_text.rs`, which is `pub(crate)` for that reason; its primary subject is `engine_runtime_status_label`, so it stays. +- [`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs) — mechanical relocation: the launch-summary/examine text surfaces (`render_launch_summary`, `render_examine_text`, `render_examine_text_with_paths`, `examine_human_report`, `render_examine_plain_header`, `plain_status_label`) and the engine-inventory listing shared by `rocm engines` and `rocm examine` (`write_default_engine_legend`, `render_engine_inventory_text_with_paths`, `append_examine_runtime_state`, `append_examine_engine_inventory`, `append_engine_detect_summary`, `friendly_engine_runtime_kind`, `friendly_engine_detect_notes`, `friendly_engine_detect_note_fallback`, `engine_runtime_status_label`). The `DEFAULT_ENGINE_MARKER` constant is shared by both engine-inventory renderers so the two copies cannot drift apart. The `rocm model` catalog renderer is not here; it lives in [`model_catalog_text.rs`](../apps/rocm/src/model_catalog_text.rs), and nothing in this file calls into it. [`examine_cmd.rs`](../apps/rocm/src/examine_cmd.rs)'s `examine()`, which the `Command::Examine` arm calls, imports and calls `examine_human_report`; `main.rs`'s own `launch_default` calls `render_launch_summary`, re-imported there. `render_examine_text`, `render_examine_text_with_paths` and `render_engine_inventory_text_with_paths` are `pub(crate)`, read from [`chat.rs`](../apps/rocm/src/chat.rs)'s deterministic tool-output summaries; `render_engine_inventory_text_with_paths` is also read from [`engines_cmd.rs`](../apps/rocm/src/engines_cmd.rs)'s `render_engine_inventory_text`; `append_examine_runtime_state` is also `pub(crate)`, read only from [`runtimes_cmd.rs`](../apps/rocm/src/runtimes_cmd.rs)'s own test module, so `main.rs`'s re-import of it is itself `#[cfg(test)]` rather than carrying an otherwise-unused import into a non-test build. Every other item in this list has no caller outside this module and stays private. This module also reaches items that live at the crate root because they have other callers: `engine_inventory()` (also read from [`update_cmd.rs`](../apps/rocm/src/update_cmd.rs) and [`bridge_snapshot.rs`](../apps/rocm/src/bridge_snapshot.rs)), `PlatformRuleOut` and `engine_ruled_out_by_platform` (defined in [`model_fit.rs`](../apps/rocm/src/model_fit.rs) and re-imported in `main.rs`) — all reached via `use crate::{...};`. The regression tests live in this module's own `mod tests`, reaching the shared `test_paths` fixture through `crate::tests::` and `write_test_pip_runtime`/`runtime_manifest_path` through `crate::test_support::`/`crate::runtimes_cmd::`. Its tests do not call into `model_catalog_text.rs`: the test of the note the verbose `rocm model` listing prints for a rule-out engine lives beside that renderer. - [`freeform_answer.rs`](../apps/rocm/src/freeform_answer.rs) — mechanical relocation, no owned types: the answers a freeform request can get without running a command — `render_freeform_read_only_answer`, which answers a single approval-free `examine`, `comfyui status` or `comfyui logs` action, and `render_freeform_examine_answer`, the plain-language ROCm status it prints for `examine`. `run_freeform` in [`main.rs`](../apps/rocm/src/main.rs) is the only caller of the first, which is `pub(crate)` and re-imported there; the second is private. Besides `chat.rs` (`any_substring`, re-exported there from [`prompt_text.rs`](../apps/rocm/src/prompt_text.rs)), it depends on `comfyui.rs`, `runtimes_cmd.rs` and `therock.rs`, none of which calls back into it. No unit test calls either function directly. - [`freeform_execution.rs`](../apps/rocm/src/freeform_execution.rs) — mechanical relocation, dispatch-adjacent, owns a `pub(crate)` type (as of Phase 6e's follow-up, ROCMAI-726; see the Phase 6a variant note above for the owned-types pattern). Holds the freeform natural-language planner's plan-*execution*/consent half that [`freeform_planner.rs`](../apps/rocm/src/freeform_planner.rs) below deliberately left out: `validate_freeform_execution_action()`, `prepare_freeform_execution()`, `apply_freeform_execution_consent()`, `render_freeform_execution_header()`, and the `pub(crate)` `FreeformExecution` struct that `prepare_freeform_execution` returns and `render_freeform_execution_header` consumes. `main.rs`'s `execute_freeform_next_action()` and `run_freeform()` stay in `main.rs` — both are dispatch-level, not part of deciding whether a plan's next action is safe to execute — and `execute_freeform_next_action()` calls in via the unqualified re-import `use crate::freeform_execution::{prepare_freeform_execution, render_freeform_execution_header};` (`run_freeform()` only calls `execute_freeform_next_action()`, never this module directly), matching [`driver_install.rs`](../apps/rocm/src/driver_install.rs)'s dispatch-adjacent form rather than `freeform_planner.rs`'s own qualified-path form. `prepare_freeform_execution` takes the already-built `plan` by value rather than rebuilding it from `request`/`paths`/`config` (ROCMAI-725's single-build guarantee, inherited unchanged from `main.rs`); `prepare_freeform_execution`/`render_freeform_execution_header` are `pub(crate)` for that cross-module call; `FreeformExecution` was already `pub(crate)` (read directly by `main.rs` and this module's own tests); `validate_freeform_execution_action`/`apply_freeform_execution_consent` have no caller outside this module and stay module-private, unchanged from before the move. The cluster's six regression tests moved out of `main.rs`'s shared `mod tests` into this module's own `mod tests`, inheriting the qualified `freeform_planner::` path they already used to reach `freeform_planner.rs`'s items as `main.rs`'s former tests — this module reaches in from outside using the same qualified path `main.rs` and `providers.rs`'s tests use (unlike `chat.rs`/`comfyui.rs`, which import it unqualified). - [`freeform_planner.rs`](../apps/rocm/src/freeform_planner.rs) — full domain extraction (Phase 6e, ROCMAI-87). Owns the freeform natural-language planner cluster's types (`StructuredRequestPlan`, `PlannerIntent`, `PlannedToolCall`, `FreeformPlanAction`, `ProviderPlannerResponse`, `ProviderPlannerToolCall`, all module-private except `FreeformPlanAction`, `StructuredRequestPlan` and `PlannedToolCall`, which are `pub(crate)`, and `ProviderPlannerToolCall`, `pub(crate)` together with `validate_provider_planner_tool_call` and `planner_failure_note` so [`chat.rs`](../apps/rocm/src/chat.rs)'s command-preview property tests can drive the planner's guards), the deterministic plan builder (`build_freeform_plan*`), the provider-assisted path (`resolve_freeform_plan_with_provider`, `build_provider_planner_prompt`, `provider_planner_response_to_plan`, `validate_provider_planner_tool_call`), and plan rendering (`render_structured_request_plan`). [`main.rs`](../apps/rocm/src/main.rs) calls `build_freeform_plan_with_context`/`render_structured_request_plan` directly for the dispatch/review flow ([`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs)'s `render_launch_summary` → `host_default_engine` is another call into this module, unrelated to planning: bare `rocm`'s non-interactive launch summary), while [`freeform_execution.rs`](../apps/rocm/src/freeform_execution.rs) above calls `plan_next_action`/`format_structured_tool_call` for the execution flow and reads `FreeformPlanAction`/`StructuredRequestPlan` by type for its execution-validation functions — those live there, not here, because they decide whether a plan's next action is safe to *execute*, a dispatch-adjacent concern distinct from *building* the plan; `main.rs`'s `run_freeform` builds a plan exactly once via `build_freeform_plan_with_context` and threads that same value through `execute_freeform_next_action` to `prepare_freeform_execution` rather than rebuilding it (ROCMAI-725: building a plan can call a configured cloud `planner_provider`, which is not guaranteed deterministic, so a second independent build could silently disagree with the first). [`chat.rs`](../apps/rocm/src/chat.rs)'s `natural_language_plan` MCP tool gets the same single-build guarantee from `plan_freeform_request_with_context`, which builds once and returns both the rendered text and the next action; `chat.rs` also calls `format_structured_tool_call` directly, like [`comfyui.rs`](../apps/rocm/src/comfyui.rs) does, while [`tool_policy.rs`](../apps/rocm/src/tool_policy.rs)'s `validate_chat_rocm_command_safety` reads `serve_args_request_cpu_device` from [`rocm_argv.rs`](../apps/rocm/src/rocm_argv.rs) directly as a no-CPU-fallback safety check on provider-proposed `serve` args; `validate_provider_planner_tool_call` calls that same function in its own `serve` branch, and hands an `install sdk` call to `tool_policy::validate_chat_rocm_command_safety`. `host_default_engine`, `build_freeform_plan_with_context`, `render_structured_request_plan`, `plan_next_action`, `plan_freeform_request_with_context` and `provider_planner_response_to_plan` are `pub(crate)` for these reasons, as is `plan_driver_request` (read from [`driver_plan.rs`](../apps/rocm/src/driver_plan.rs)'s test module); `format_structured_tool_call` lives in [`rocm_argv.rs`](../apps/rocm/src/rocm_argv.rs) and is re-exported here with `pub(crate) use`, so the callers above keep their `freeform_planner::` path, while the planner's own argv lookups call `rocm_argv`, its phrase and engine readers call [`prompt_text.rs`](../apps/rocm/src/prompt_text.rs) and its safety check calls `tool_policy.rs`, directly: the planner no longer imports `chat`, and only `chat` imports the planner (`plan_freeform_request_with_context`); `freeform_plan_next_action` and `render_freeform_plan` are test-only (`#[cfg(test)]`) and `FreeformPlanAction` was already `pub(crate)` before the split. `parse_freeform_invocation`/`should_treat_as_freeform` (deciding whether an invocation is freeform at all) stay in `main.rs`, a dispatch-level concern outside both this module and `freeform_execution.rs`. The cluster's regression tests live in `freeform_planner.rs`'s own `mod tests`; the handful of `main.rs` tests that exercise the invocation classifier/clap-suggestion-avoidance stay in `main.rs`'s shared `mod tests`, since neither is specific to this module or to `freeform_execution.rs` (and neither touches `freeform_planner::` at all), and the ROCMAI-725 regression tests proving the single-build guarantee live in `providers.rs`'s `mod tests` (reusing its mock local-provider server), reaching this module's items through the qualified `freeform_planner::` path like any other external caller. @@ -60,7 +60,7 @@ Each per-crate module inventory lists one file per line. Flat inventories (every - [`install_cmd.rs`](../apps/rocm/src/install_cmd.rs) — mechanical relocation, dispatch-adjacent, no owned types: `rocm install`'s command handler (`install()`, for the `sdk` and `driver` targets), the `--version`/`--build-date` selector parsing and display it uses (`therock_install_version_selector`, `therock_install_version_selector_display`) and `sdk_install_request`, which maps the parsed `install sdk` arguments onto a `therock::SdkInstallRequest` and moved here from [`chat.rs`](../apps/rocm/src/chat.rs) with its two tests. `InstallTarget` and `InstallFormat` stay in [`main.rs`](../apps/rocm/src/main.rs). `install()` is `pub(crate)` and re-imported in `main.rs` for `dispatch()`; `therock_install_version_selector` is `pub(crate)` too, and `chat.rs`'s `render_install_sdk_dry_run_for_args` reaches it as `crate::therock_install_version_selector` through the same kind of re-import, which is why `sdk_install_request` lives here and not in `chat.rs`: this module does not depend on `chat.rs`. `sdk_install_request` keeps the `pub(crate)` it had, although only `install()` and the tests call it now. - [`logging.rs`](../apps/rocm/src/logging.rs) — mechanical relocation, no owned types, reached by qualified path. - [`managed_service_status.rs`](../apps/rocm/src/managed_service_status.rs) — mechanical relocation, owns types (Phase 6f, ROCMAI-211): managed-service liveness classification (`managed_service_is_live`, `refresh_managed_service_runtime_liveness`, `existing_live_managed_service`'s idempotency guard) and the dashboard-sidebar status/counts helpers derived from it (`ManagedServiceSidebarCounts`/`managed_service_sidebar_counts`/`local_server_sidebar_status`), plus the engine display-name helper `friendly_engine_label`, read only by [`chat.rs`](../apps/rocm/src/chat.rs). `ManagedServiceSidebarCounts` is `pub(crate)` to match the visibility of the `pub(crate)` functions that return/take it; `friendly_engine_label`, `existing_live_managed_service`, `recorded_service_pids`, `refresh_managed_service_runtime_liveness`, `local_server_sidebar_status`, `managed_service_sidebar_counts`, and `load_managed_services` are `pub(crate)` for the same reason — each has a confirmed caller outside this module. [`service_records.rs`](../apps/rocm/src/service_records.rs) uses its liveness helpers (`managed_service_is_live`, `recorded_service_pids`) for the in-use check behind service-record removal and pruning, and `load_managed_services` for prune planning, while [`bridge_snapshot.rs`](../apps/rocm/src/bridge_snapshot.rs) uses `load_managed_services` for the codex-bridge snapshot; [`service_launch.rs`](../apps/rocm/src/service_launch.rs) is the only caller of the `existing_live_managed_service` duplicate-launch guard; [`services_text.rs`](../apps/rocm/src/services_text.rs) reads the sidebar helpers; other modules, including [`chat.rs`](../apps/rocm/src/chat.rs) and [`runtime_services.rs`](../apps/rocm/src/runtime_services.rs), read the registry and liveness helpers. -- [`model_catalog_text.rs`](../apps/rocm/src/model_catalog_text.rs) — mechanical relocation out of `examine_report_text.rs`: the `rocm model` (alias `models`) catalog renderer. `render_model_registry_text_with_context_and_host` prints the curated list grouped by hardware target (`model_catalog_platform_heading`, `model_catalog_header`, `model_recipe_memory_label`, `model_recipe_gpu_fit_label`), and `render_model_registry_verbose_text_with_context_and_host` prints the per-recipe diagnostics behind `rocm model --verbose` (`append_model_recipe_registry_source`, `append_model_recipe_metadata_lines`, `append_model_engine_recipe_settings_lines`, `append_model_recipe_staleness_lines`, `append_model_artifact_lines`, `append_artifact_source_policy_lines`, `artifact_source_policy_label`, `append_model_host_ram_fit_lines`, `append_model_fit_lines`, `append_manual_alternative_lines`, `manual_alternative_recommendations`, `recipe_is_manual_fit`, `append_model_engine_support_lines`, `model_registry_adapter_availability_note`, `recipe_display_ref`, `preferred_engine_action_target`, `model_registry_reason`, plus the small formatters `format_list_or_none`, `format_string_map_or_none`, `format_gib` and `once_per_call`). `format_bytes`, the human-readable size formatter, lives here too. The two renderers and `format_bytes` are `pub(crate)`: [`main.rs`](../apps/rocm/src/main.rs) imports them for the `Command::Model` arm, and [`chat.rs`](../apps/rocm/src/chat.rs) (the verbose renderer), [`storage.rs`](../apps/rocm/src/storage.rs) and [`automations.rs`](../apps/rocm/src/automations.rs) (`format_bytes`) reach them as `crate::`. `model_registry_adapter_availability_note` is `pub(crate)` only because a test in `examine_report_text.rs` calls it. The module has no edge to `examine_report_text.rs`; it reaches the engine-availability helpers in [`engine_ipc.rs`](../apps/rocm/src/engine_ipc.rs), `PlatformRuleOut` and `engine_ruled_out_by_platform_with` at the crate root, and [`serve_cmd.rs`](../apps/rocm/src/serve_cmd.rs)'s `select_serve_engine`. The catalog's regression tests live in this module's own `mod tests`; they keep the names they had in `examine_report_text.rs`. The `format_bytes` property test's saved failure case lives in [`model_catalog_text.txt`](../apps/rocm/proptest-regressions/model_catalog_text.txt), which proptest finds by the test's source file, so that file has to be renamed whenever the test moves. +- [`model_catalog_text.rs`](../apps/rocm/src/model_catalog_text.rs) — mechanical relocation out of `examine_report_text.rs`: the `rocm model` (alias `models`) catalog renderer. `render_model_registry_text_with_context_and_host` prints the curated list grouped by hardware target (`model_catalog_platform_heading`, `model_catalog_header`, `model_recipe_memory_label`, `model_recipe_gpu_fit_label`), and `render_model_registry_verbose_text_with_context_and_host` prints the per-recipe diagnostics behind `rocm model --verbose` (`append_model_recipe_registry_source`, `append_model_recipe_metadata_lines`, `append_model_engine_recipe_settings_lines`, `append_model_recipe_staleness_lines`, `append_model_artifact_lines`, `append_artifact_source_policy_lines`, `artifact_source_policy_label`, `append_model_host_ram_fit_lines`, `append_model_fit_lines`, `append_manual_alternative_lines`, `manual_alternative_recommendations`, `recipe_is_manual_fit`, `append_model_engine_support_lines`, `model_registry_adapter_availability_note`, `recipe_display_ref`, `preferred_engine_action_target`, `model_registry_reason`, plus the small formatters `format_list_or_none`, `format_string_map_or_none`, `format_gib` and `once_per_call`). `format_bytes`, the human-readable size formatter, lives here too. The two renderers and `format_bytes` are `pub(crate)`: [`main.rs`](../apps/rocm/src/main.rs) imports them for the `Command::Model` arm, and [`chat.rs`](../apps/rocm/src/chat.rs) (the verbose renderer), [`storage.rs`](../apps/rocm/src/storage.rs) and [`automations.rs`](../apps/rocm/src/automations.rs) (`format_bytes`) reach them as `crate::`. Every other item, including `model_registry_adapter_availability_note`, is private. The module has no edge to `examine_report_text.rs`; it reaches the engine-availability helpers in [`engine_ipc.rs`](../apps/rocm/src/engine_ipc.rs), `PlatformRuleOut` and `engine_ruled_out_by_platform_with` at the crate root, and [`serve_cmd.rs`](../apps/rocm/src/serve_cmd.rs)'s `select_serve_engine`. The catalog's regression tests live in this module's own `mod tests`; they keep the names they had in `examine_report_text.rs`. The `format_bytes` property test's saved failure case lives in [`model_catalog_text.txt`](../apps/rocm/proptest-regressions/model_catalog_text.txt), which proptest finds by the test's source file, so that file has to be renamed whenever the test moves. - [`model_fit.rs`](../apps/rocm/src/model_fit.rs) — mechanical relocation, owns a `pub(crate)` type: answering whether a host can run a model for `rocm diagnose --model` (`assess_model_on_this_host`, `assess_model_with_host_paths`, `assess_model_for_host`), the accelerator-memory fact it rests on (`host_accelerator_memory`, `classify_accelerator_memory`, `examination_reports_amd_gpu`) and the platform gate that rules an engine out (`PlatformRuleOut`, `engine_ruled_out_by_platform`, `unsupported_here_for`). It is a module of its own, rather than part of the `diagnose` handler, so [`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs) and [`model_catalog_text.rs`](../apps/rocm/src/model_catalog_text.rs), which read `PlatformRuleOut` and the platform gate (`engine_ruled_out_by_platform` and `engine_ruled_out_by_platform_with` respectively), do not depend on a command module. It depends on [`serve_cmd.rs`](../apps/rocm/src/serve_cmd.rs) (`select_serve_engine`) and [`gpu_policy.rs`](../apps/rocm/src/gpu_policy.rs), neither of which depends on it. `assess_model_on_this_host` (called from [`diagnose_cmd.rs`](../apps/rocm/src/diagnose_cmd.rs)), `PlatformRuleOut` and `engine_ruled_out_by_platform` are `pub(crate)`, and [`main.rs`](../apps/rocm/src/main.rs) re-imports the last two so `examine_report_text.rs` reaches them as `crate::` (`model_catalog_text.rs` reaches `PlatformRuleOut` and `engine_ruled_out_by_platform_with` the same way); every other item is private. The cluster's regression tests live in this module's own `mod tests`; the one that re-runs the test binary on a helper test by its full path (`engine_ruled_out_by_platform_gates_aim_on_docker_reachability`) names the helper's new path, `model_fit::tests::docker_unreachable_subprocess_helper`. - [`prompt_text.rs`](../apps/rocm/src/prompt_text.rs) — text extraction from a natural-language request, shared by `chat.rs`'s prompt-intent heuristics (`fallback_rocm_tool_call_for_prompt`, `fallback_config_tool_call_for_prompt`, `install_sdk_chat_approval_for_prompt` and the `prompt_*` predicates) and the freeform planner, in a leaf module that depends only on `therock::RuntimeVersionSelector` (`tool_policy.rs` does not use it): `any_substring`, the install-folder, TheRock build-date and TheRock version readers (`requested_install_prefix_from_prompt`, `requested_therock_build_date_from_prompt`, `requested_therock_version_from_prompt`, with `requested_bare_install_prefix_from_prompt`, `clean_requested_install_prefix`, `looks_like_user_path` and `looks_like_therock_runtime_version` private beside them) from `chat.rs`, and `infer_engine_from_request` from `freeform_planner.rs`. They moved verbatim, together with `infer_engine_from_request`'s regression test, so that `freeform_planner.rs` no longer imports `chat` for them; `chat.rs` re-exports `any_substring`, which [`freeform_answer.rs`](../apps/rocm/src/freeform_answer.rs) still reaches as `chat::any_substring`. - [`provider_keys.rs`](../apps/rocm/src/provider_keys.rs) — full domain extraction.