Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v7

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.97.1
with:
components: rustfmt, clippy

Expand Down Expand Up @@ -59,6 +59,15 @@ jobs:
- name: Run Clippy
run: cargo clippy --all --all-targets --all-features -- -D warnings

- name: Install Hawk
run: |
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/astral-sh/hawk/releases/download/0.1.10/cargo-hawk-installer.sh | sh
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
Comment thread
pesap marked this conversation as resolved.

- name: Run Hawk
run: cargo +1.97.1 hawk check -D warnings

- name: Check Rust-owned OpenAPI artifacts
env:
DATABASE_URL: sqlite:db/sqlite/dev.db
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Agent notes
PAPERCUTS.md

# Rust build artifacts
/target/
/torc-server/target/
Expand Down
23 changes: 16 additions & 7 deletions docs/src/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ cd torc

2. **Install Rust and dependencies:**

Make sure you have Rust 1.85 or later installed:

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Make sure you have Rust 1.95 or later installed. Hawk requires its analysis to run with the exact
compiler toolchain it was built against, currently Rust 1.97.1.

3. **Install cargo-nextest:**

Expand All @@ -37,7 +34,15 @@ cargo install sqlx-cli --no-default-features --features sqlite
cargo install cargo-release
```

6. **Set up the database:**
6. **Install Hawk:**

```bash
rustup toolchain install 1.97.1
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/astral-sh/hawk/releases/download/0.1.10/cargo-hawk-installer.sh | sh
```
Comment thread
pesap marked this conversation as resolved.

7. **Set up the database:**

```bash
# Create .env file
Expand All @@ -47,7 +52,7 @@ echo "DATABASE_URL=sqlite:torc.db" > .env
sqlx migrate run --source torc-server/migrations
```

7. **Build and test:**
8. **Build and test:**

```bash
cargo build
Expand All @@ -69,6 +74,9 @@ cargo clippy --all --all-targets --all-features -- -D warnings

# Run all checks
cargo fmt --check && cargo clippy --all --all-targets --all-features -- -D warnings

# Check public APIs with Hawk (run with the pinned Hawk toolchain)
cargo +1.97.1 hawk check -D warnings
```

### Adding Tests
Expand Down Expand Up @@ -121,6 +129,7 @@ git commit -m "Add feature: description"
cargo nextest run --all-features
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo +1.97.1 hawk check -D warnings
```

4. **Push to your fork:**
Expand Down
59 changes: 59 additions & 0 deletions hawk.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Torc ships these binaries as part of its release artifacts. Keep the production
# surface explicit so Hawk can distinguish supported entry points from internal code.
[[production]]
package = "torc"
bin = "torc"
reason = "shipped unified CLI"

[[production]]
package = "torc"
bin = "torc-openapi"
reason = "shipped OpenAPI utility"

[[production]]
package = "torc"
bin = "torc-server"
reason = "shipped server binary"

[[production]]
package = "torc"
bin = "torc-htpasswd"
reason = "shipped authentication utility"

[[production]]
package = "torc"
bin = "torc-mcp-server"
reason = "shipped MCP server"

[[production]]
package = "torc"
bin = "torc-dash"
reason = "shipped dashboard"

[[production]]
package = "torc"
bin = "torc-slurm-job-runner"
reason = "shipped Slurm job runner"

# These model aliases are part of the crate-root API retained for downstream users,
# even though Torc's in-workspace binaries do not import them across the crate boundary.
[[override]]
lint = "hawk::unnecessary_public"
crate = "torc"
item = "GetReadyJobRequirementsResponse"
level = "allow"
reason = "backward-compatible crate-root model re-export"

[[override]]
lint = "hawk::unnecessary_public"
crate = "torc"
item = "ListLocalSchedulersResponse"
level = "allow"
reason = "backward-compatible crate-root model re-export"

[[override]]
lint = "hawk::unnecessary_public"
crate = "torc"
item = "LocalSchedulerModel"
level = "allow"
reason = "backward-compatible crate-root model re-export"
16 changes: 6 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub mod async_cli_command;
pub mod commands;
pub mod errors;
pub mod resource_correction;
pub mod ro_crate_utils;
pub(crate) mod ro_crate_utils;

// Re-export config from the top-level module for backwards compatibility
#[cfg(feature = "config")]
Expand All @@ -29,7 +29,7 @@ pub mod remote;
pub mod report_models;
pub mod resource_monitor;
pub mod scheduler_plan;
pub mod slurm_utils;
pub(crate) mod slurm_utils;
pub mod sse_client;
pub mod utils;
pub mod version_check;
Expand All @@ -47,8 +47,8 @@ pub use apis::{
system_api, tasks_api, user_data_api, workflow_actions_api, workflows_api,
};
pub use hpc::{
HpcDetection, HpcInterface, HpcJobInfo, HpcJobStats, HpcJobStatus, HpcManager, HpcPartition,
HpcProfile, HpcProfileRegistry, HpcType, SlurmInterface, create_hpc_interface,
HpcDetection, HpcInterface, HpcJobInfo, HpcJobStats, HpcJobStatus, HpcPartition, HpcProfile,
HpcProfileRegistry, SlurmInterface,
};
pub use job_runner::JobRunner;
// JobModel is re-exported from models (which re-exports from crate::models)
Expand All @@ -59,11 +59,7 @@ pub use workflow_spec::{
};

// Report model types for inter-command data sharing
pub use report_models::{
JobResultRecord, ResourceUtilizationReport, ResourceViolation, ResultsReport,
};
pub use report_models::{ResourceUtilizationReport, ResourceViolation};

// Version checking utilities
pub use version_check::{
ServerInfo, VersionCheckResult, VersionMismatchSeverity, check_and_warn, check_version,
};
pub use version_check::{ServerInfo, VersionCheckResult, VersionMismatchSeverity, check_version};
15 changes: 9 additions & 6 deletions src/client/async_cli_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl AsyncCliCommand {

/// Returns the Slurm step name, if running inside an allocation.
/// Set after `start()` is called.
pub fn step_name(&self) -> Option<&str> {
fn step_name(&self) -> Option<&str> {
self.step_name.as_deref()
}

Expand Down Expand Up @@ -534,7 +534,7 @@ impl AsyncCliCommand {

/// Returns the Slurm accounting stats collected for this job step, if any.
/// Only populated when the job ran inside a Slurm allocation and sacct succeeded.
pub fn take_slurm_stats(&mut self) -> Option<SlurmStatsModel> {
pub(crate) fn take_slurm_stats(&mut self) -> Option<SlurmStatsModel> {
self.slurm_stats.take()
}

Expand Down Expand Up @@ -582,7 +582,7 @@ impl AsyncCliCommand {
/// // exit_code will be 143 (128 + 15) if killed by SIGTERM on Unix
/// ```
#[cfg(unix)]
pub fn send_sigterm(&mut self) -> Result<(), Box<dyn std::error::Error>> {
fn send_sigterm(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if let Some(ref child) = self.handle {
let pid = child.id();
debug!("Sending SIGTERM to job {} (PID {})", self.job_id, pid);
Expand Down Expand Up @@ -656,7 +656,10 @@ impl AsyncCliCommand {
/// **Note**: This method does not wait for the process to exit. Call
/// [`wait_for_completion()`] afterwards to wait for the process and capture its exit code.
#[cfg(unix)]
pub fn send_signal(&mut self, signal_name: &str) -> Result<(), Box<dyn std::error::Error>> {
pub(crate) fn send_signal(
&mut self,
signal_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(ref child) = self.handle {
let pid = child.id();
let signal = match signal_name {
Expand Down Expand Up @@ -714,7 +717,7 @@ impl AsyncCliCommand {
/// This is a forceful termination that cannot be caught or ignored by the process.
/// Use this as a last resort after graceful termination has failed.
#[cfg(unix)]
pub fn send_sigkill(&mut self) -> Result<(), Box<dyn std::error::Error>> {
pub(crate) fn send_sigkill(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if let Some(ref child) = self.handle {
let pid = child.id();
debug!("Sending SIGKILL to job {} (PID {})", self.job_id, pid);
Expand Down Expand Up @@ -842,7 +845,7 @@ impl AsyncCliCommand {
/// Process ID of the job, once `start()` has spawned it. In Slurm mode this
/// is the PID of the `srun` process, not of the job itself, which runs
/// under slurmstepd.
pub fn pid(&self) -> Option<u32> {
pub(crate) fn pid(&self) -> Option<u32> {
self.pid
}

Expand Down
14 changes: 7 additions & 7 deletions src/client/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub mod access_groups;
pub mod admin;
pub mod compute_nodes;
pub mod config;
pub mod diagnose;
pub(crate) mod diagnose;
pub mod events;
pub mod failure_handlers;
pub mod files;
Expand All @@ -11,7 +11,7 @@ pub mod job_dependencies;
pub mod jobs;
pub mod logs;
pub mod orphan_detection;
pub mod output;
pub(crate) mod output;
pub mod pagination;
pub mod reconcile;
pub mod recover;
Expand All @@ -23,7 +23,7 @@ pub mod ro_crate;
pub mod scheduled_compute_nodes;
pub mod self_update;
pub mod slurm;
pub mod table_format;
pub(crate) mod table_format;
pub mod tasks;
pub mod user_data;
pub mod watch;
Expand Down Expand Up @@ -114,19 +114,19 @@ pub fn select_workflow_interactively(
}

/// Helper function to get user name from parameter or environment variables
pub fn get_user_name(user: &Option<String>) -> String {
fn get_user_name(user: &Option<String>) -> String {
if user.is_some() {
return user.as_deref().unwrap().to_string();
}
get_env_user_name()
}

pub fn get_env_user_name() -> String {
pub(crate) fn get_env_user_name() -> String {
crate::get_username()
}

/// Truncate string to specified length
pub fn truncate_string(s: &str, max_len: usize) -> String {
fn truncate_string(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
Expand All @@ -135,7 +135,7 @@ pub fn truncate_string(s: &str, max_len: usize) -> String {
}

/// Print API errors in a user-friendly way
pub fn print_error<T>(operation: &str, error: &crate::client::apis::Error<T>) {
pub(crate) fn print_error<T>(operation: &str, error: &crate::client::apis::Error<T>) {
match error {
crate::client::apis::Error::Reqwest(e) => {
eprintln!("Network error while {}: {}", operation, e);
Expand Down
2 changes: 1 addition & 1 deletion src/client/commands/diagnose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct PackingDiagnosis {
}

/// Entry point for `torc workflows diagnose`.
pub fn diagnose_packing(config: &Configuration, workflow_id: Option<i64>, format: &str) {
pub(crate) fn diagnose_packing(config: &Configuration, workflow_id: Option<i64>, format: &str) {
let user = get_env_user_name();
let workflow_id = match workflow_id {
Some(id) => id,
Expand Down
4 changes: 3 additions & 1 deletion src/client/commands/hpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use super::table_format::{display_csv, display_table_with_count};
/// Create an HPC profile registry with built-in profiles and user-defined profiles from config
///
/// This is a public version for use by other modules (e.g., main.rs for submit command)
pub fn create_registry_with_config_public(hpc_config: &ClientHpcConfig) -> HpcProfileRegistry {
pub(crate) fn create_registry_with_config_public(
hpc_config: &ClientHpcConfig,
) -> HpcProfileRegistry {
create_registry_with_config(hpc_config)
}

Expand Down
4 changes: 2 additions & 2 deletions src/client/commands/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ pub fn create_jobs_from_file(
}

/// Get the current job count for a workflow
pub fn get_current_job_count(
fn get_current_job_count(
config: &Configuration,
workflow_id: i64,
) -> Result<i64, Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -2166,7 +2166,7 @@ fn handle_reset_job_status(
}

/// Get existing job names to avoid duplicates
pub fn get_existing_job_names(
fn get_existing_job_names(
config: &Configuration,
workflow_id: i64,
) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
Expand Down
14 changes: 7 additions & 7 deletions src/client/commands/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,30 +354,30 @@ impl std::fmt::Display for ErrorSeverity {
/// A detected error in a log file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectedError {
pub file: String,
pub line_number: usize,
pub(crate) file: String,
pub(crate) line_number: usize,
pub pattern_name: String,
pub severity: ErrorSeverity,
pub(crate) severity: ErrorSeverity,
pub line_content: String,
}

/// Result of analyzing workflow logs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogAnalysisResult {
/// Workflow ID that was analyzed
pub workflow_id: Option<i64>,
workflow_id: Option<i64>,
/// Number of log files parsed
pub files_parsed: usize,
pub(crate) files_parsed: usize,
/// Total number of errors detected
pub error_count: usize,
/// Total number of warnings detected
pub warning_count: usize,
/// All detected errors
pub errors: Vec<DetectedError>,
/// Errors grouped by file
pub errors_by_file: HashMap<String, Vec<DetectedError>>,
pub(crate) errors_by_file: HashMap<String, Vec<DetectedError>>,
/// Error counts by pattern type
pub errors_by_type: HashMap<String, usize>,
pub(crate) errors_by_type: HashMap<String, usize>,
}

impl LogAnalysisResult {
Expand Down
Loading