Cargo.toml
Rama main · réplica de lectura del repositorio original.
# Virtual workspace manifest for `runner-manager`.
#
# `crates/` sits at the repository root; there is no nested product directory
# (`.taskflow/2026-08-21-local-runner-manager/02-target-architecture.md`,
# "Workspace shape").
#
# ----------------------------------------------------------------------------
# ADDING A NEW EXTERNAL CRATE IS AN A-GROUP CHANGE.
# ----------------------------------------------------------------------------
# Task `a1` owns every `Cargo.toml` in this workspace and the whole
# `[workspace.dependencies]` table below. Every member manifest consumes an
# entry from it with `workspace = true` and never states a version of its own.
#
# The reason is mechanical, not stylistic: 22 later tasks run in parallel
# against this tree. A task that edits a manifest also rewrites `Cargo.lock`,
# and `Cargo.lock` is a single file that every one of them would then collide
# on. So the member manifests below already list dependencies their crate does
# not use yet, precisely so that no later task has to add one.
#
# If a task genuinely needs an external crate that is not listed here, that is
# an A-group change: it is made in this file, by an A-group task, and not in a
# member manifest.
#
# Versions are exact `major.minor.patch` selections; `Cargo.lock` is committed
# and is what actually pins the resolved graph
# (`02-target-architecture.md`: "Exact dependency versions are selected in
# implementation, pinned with `Cargo.lock` and `rust-toolchain.toml`").
[workspace]
resolver = "3"
members = [
"crates/agent",
"crates/app",
"crates/coordinator",
"crates/domain",
"crates/github",
"crates/platform",
"crates/testkit",
"tests",
]
[workspace.package]
version = "0.4.60"
edition = "2024"
rust-version = "1.94.0"
license = "MIT"
authors = ["Ivan Murzak"]
repository = "https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI"
homepage = "https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI"
[workspace.dependencies]
# --- Workspace members ------------------------------------------------------
# Each carries a `version` as well as a `path` so the published `runner-manager`
# crate (D11, `cargo install`) can resolve them from the registry.
runner-manager-agent = { path = "crates/agent", version = "0.4.60" }
runner-manager-domain = { path = "crates/domain", version = "0.4.60" }
runner-manager-github = { path = "crates/github", version = "0.4.60" }
runner-manager-platform = { path = "crates/platform", version = "0.4.60" }
# The coordinator server is deployed as a container image, never published; it
# is a dev-dependency of the app's link tests and so carries no `version`.
runner-manager-coordinator = { path = "crates/coordinator" }
# `testkit` is never published; it is a dev-dependency everywhere and therefore
# carries no `version`, which is what lets `cargo publish` strip it.
runner-manager-testkit = { path = "crates/testkit" }
# --- Async runtime and HTTP -------------------------------------------------
tokio = { version = "1.53.1", features = [
"fs",
"io-util",
"macros",
"net",
"process",
"rt-multi-thread",
"signal",
"sync",
"time",
] }
futures = "0.3.34"
async-trait = "0.1.92"
# `rustls` rather than the platform TLS stack: a released Linux binary that
# links the system OpenSSL fails on hosts carrying a different major version,
# and every documented install path here is "download the binary and run it"
# (`09-release-distribution.md`).
#
# `query` gates `RequestBuilder::query()`, which every paginated GitHub call
# needs: `c3` pages the runner inventory and filters in-progress runs, `c4`
# pages queued runs and their jobs. Both are group C, so without this feature
# both tasks would have to edit `crates/github/Cargo.toml`.
#
# `stream` gates `Response::bytes_stream()`. `e2` downloads the GitHub runner
# package — 150-300 MB — and must verify its SHA-256 *before* extraction and
# delete the partial file on mismatch. Without a stream the only option is
# buffering the whole package in RAM on a home host, and there is no partial
# file left on disk to remove.
reqwest = { version = "0.13.5", default-features = false, features = [
"charset",
"gzip",
"http2",
"json",
"query",
"rustls",
"stream",
"system-proxy",
] }
url = "2.5.8"
# --- Serialization and configuration ----------------------------------------
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
# Non-secret local configuration is TOML (`05-infrastructure.md`).
toml = "1.1.6"
base64 = "0.23.1"
hex = "0.4.3"
# --- Persistence ------------------------------------------------------------
# `bundled` compiles SQLite from source, so no CI leg and no user machine needs
# a system SQLite of a particular version.
rusqlite = { version = "0.40.2", features = [
"bundled",
"chrono",
"serde_json",
"uuid",
] }
# --- Domain value types -----------------------------------------------------
uuid = { version = "1.26.1", features = ["serde", "v4"] }
chrono = { version = "0.4.45", default-features = false, features = [
"clock",
"serde",
"std",
] }
# --- Errors and diagnostics -------------------------------------------------
thiserror = "2.0.20"
anyhow = "1.0.104"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = [
"env-filter",
"json",
"registry",
] }
# Rotating local agent diagnostics (`05-infrastructure.md`, `logs/`).
tracing-appender = "0.2.5"
# Keeps the user access token and the encoded JIT configuration out of `Debug`
# output by construction rather than by review (`07-security.md`).
secrecy = "0.10.3"
# --- Terminal UI ------------------------------------------------------------
# Ratatui 0.30 keeps Crossterm as its default backend and re-exports it
# (`02-target-architecture.md`). Crossterm is *also* declared directly, at the
# version Ratatui uses, so the `bracketed-paste` and `event-stream` features
# that `g1` requires are unified into Ratatui's own copy. `ratatui::init()`
# alone enables neither (`03-control-flows.md`, flow 6.1).
ratatui = "0.30.2"
crossterm = { version = "0.29.0", features = ["bracketed-paste", "event-stream"] }
# The TUI grid measures every cell in terminal COLUMNS, not bytes or chars: a
# repository or runner name may be wide or combining, and a column that is one
# cell too wide is exactly the ragged table this dependency exists to prevent.
# `unicode-truncate` does the cutting and padding at that same measure, and is
# grapheme-aware where hand-written character arithmetic is not. Ratatui already
# builds both, so neither is a new crate in the tree.
unicode-width = "0.2.2"
unicode-truncate = "3.0.0"
# --- Command line -----------------------------------------------------------
clap = { version = "4.6.6", features = ["derive", "env", "wrap_help"] }
# --- Filesystem, packaging, and hashing -------------------------------------
# The runner package is a `.zip` on Windows and a `.tar.gz` elsewhere, and its
# published SHA-256 is verified before extraction (`05-infrastructure.md`).
sha2 = "0.11.0"
flate2 = "1.1.10"
tar = "0.4.46"
zip = "8.6.0"
tempfile = "3.27.0"
directories = "6.0.0"
rand = "0.10.2"
remove_dir_all = "1.0.0"
# --- Windows-only platform adapters -----------------------------------------
# DPAPI machine scope for the secret store (`d2`) and the service control
# handler the Windows service host needs (`d3`).
#
# The `windows` crate gates every binding behind a feature module, so a missing
# module is a compile error in the task that needs the call, not a runtime
# surprise. Each one below is here because a named task's Definition of Done
# cannot be met without it:
#
# Win32_Foundation `LocalFree`, `HANDLE`, `CloseHandle` — the
# types every other module below returns.
# Win32_Security parent of the two security modules; also
# `SECURITY_ATTRIBUTES` for the restrictive
# handles `d1` and `e3` create.
# Win32_Security_Cryptography `CryptProtectData`/`CryptUnprotectData`, the
# DPAPI machine scope behind `d2`.
# Win32_Security_Authorization `SetNamedSecurityInfoW`/`GetNamedSecurityInfoW`
# — `d2` must ACL the stored token to the service
# account so an ordinary local user cannot read
# it, and its DoD asserts that per OS, which
# means reading the ACL back.
# Win32_System_Threading `OpenProcess` + `GetProcessTimes`. `d1` needs a
# process identity that survives a restart and
# does *not* match a recycled PID; process start
# time is the discriminator. `e3`'s
# restart-recovery DoD rests on the same
# primitive.
# Win32_Storage_FileSystem `CreateFileW` taking `SECURITY_ATTRIBUTES`, for
# `d1`'s restrictive handoff and `e3`'s
# restrictive temporary file. Also
# `GetVolumePathNameW` + `GetDriveTypeW`, which
# `b1-runner-path-platform` uses to prove a
# configured runner root is on a local volume
# rather than a network share, and the
# directory-handle open that answers "may this
# account create entries here?" without writing
# anything.
# Win32_System_SystemInformation
# `GetSystemDirectoryW`.
# `b1-runner-path-platform`'s Definition of Done
# is that the Windows default runner root is
# `<system-drive>\rman` derived "without reading
# a mutable `%SystemDrive%` value as authority",
# and this is the platform API that answers it.
# Win32_System_WindowsProgramming
# the `DRIVE_*` return values of `GetDriveTypeW`.
# They live in a different feature module from
# the function itself; spelling the constants by
# hand instead would be three magic numbers in
# the one place that decides whether a runner
# root is remote.
# Win32_System_Registry `RegGetValueW`, `RegCreateKeyExW`,
# `RegSetValueExW`, `RegDeleteKeyValueW` and
# `RegEnumValueW`. `host doctor` reads
# `LongPathsEnabled`, Developer Mode, the
# PowerShell execution policy and Defender's
# exclusion list without starting a process;
# `host prepare` writes the first three back.
# Defender's own keys are tamper-protected and go
# through `Add-MpPreference` instead. Also a
# prerequisite of `SHELLEXECUTEINFOW` below.
# Win32_System_Diagnostics_ToolHelp
# `CreateToolhelp32Snapshot` and
# `Process32FirstW`/`Process32NextW`: the
# parent links of every process, from which the
# coordinator's job statistics find a native
# runner's process tree (it runs in no Job
# Object, so there is no job accounting).
# Win32_System_ProcessStatus `GetProcessMemoryInfo`, the working set of
# each process of that tree.
# Win32_UI_Shell `ShellExecuteExW` with the `runas` verb, the
# documented way to ask the user for elevation.
# `host prepare` relaunches only its own fix step
# that way, so a batch of fixes costs one UAC
# prompt.
#
# None of these modules adds a package to `Cargo.lock`: the `windows` crate's
# features are cfg gates over its own generated bindings and pull in no
# optional dependency.
windows = { version = "0.62.2", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_Security_Cryptography",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_ProcessStatus",
"Win32_System_Registry",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Shell",
] }
windows-service = "0.8.1"
# --- macOS-only platform adapters -------------------------------------------
# System Keychain for the machine-scoped secret store (`d2`).
security-framework = "3.7.0"
# `security-framework` wraps every Security.framework object in a Core
# Foundation type and exposes the raw reference only through
# `core_foundation::base::TCFType`. `d2` needs that reference for the one call
# the wrapper does not make -- setting a stored item's access so that it is
# granted to every application rather than to the single binary that wrote it,
# without which a macOS daemon loses its own credential on every upgrade. This
# adds no package to `Cargo.lock`: `security-framework` already resolves the
# same 0.10 line.
core-foundation = "0.10.1"
# --- Unix-only platform adapters --------------------------------------------
# The Unix half of the same process-identity primitive the Windows block above
# describes: `d1` needs a process identity that a recycled PID cannot forge, so
# it pairs the PID with the process start time. Windows reads that through
# `GetProcessTimes`; macOS reads it through `sysctl(KERN_PROC)` and Linux
# through field 22 of `/proc/<pid>/stat`, neither of which `std` exposes.
#
# `libc` rather than `sysinfo`, on three grounds. (1) `sysinfo` would only be
# doing the Unix half anyway — Windows already goes through the `windows` crate
# for `GetProcessTimes` — so it buys one platform path and leaves two sources
# of truth for the same fact. (2) `sysinfo` reports process start time as whole
# seconds since the epoch, which is coarser than a PID-recycling discriminator
# wants, and it refreshes a whole process table to answer a question about one
# PID. (3) `libc` is already in the resolved graph transitively, so declaring
# it adds no package to `Cargo.lock` and nothing new to audit. The cost is the
# two `unsafe` platform paths in `d1`, both short and both testable against the
# calling process itself.
libc = "0.2.189"
# --- Optional coordinator ---------------------------------------------------
# The coordinator server (`crates/coordinator`) and the daemon's outbound link
# to it (`.taskflow/2026-11-07-coordinator-server`). `axum` serves the webhook,
# enrollment and WebSocket endpoints behind a TLS-terminating proxy;
# `tokio-tungstenite` is the host's client, over the same `rustls` the GitHub
# client already uses, with the platform verifier `reqwest` already builds.
# Hosts authenticate with Ed25519 challenge signatures; webhooks with
# HMAC-SHA256 over the workspace's `sha2` 0.11.
# RS256 for the coordinator's GitHub App JWT. Already in the resolved graph as
# `rustls`'s crypto provider, so declaring it adds no package to `Cargo.lock`.
aws-lc-rs = { version = "1.18.0", default-features = false, features = ["aws-lc-sys"] }
axum = { version = "0.8.9", default-features = false, features = [
"http1",
"json",
"query",
"tokio",
"ws",
] }
tokio-tungstenite = { version = "0.29.0", default-features = false, features = [
"connect",
"handshake",
"__rustls-tls",
] }
rustls = { version = "0.23.44", default-features = false, features = ["aws_lc_rs", "std", "tls12"] }
rustls-platform-verifier = "0.7.0"
ed25519-dalek = { version = "3.0.0", features = ["zeroize"] }
hmac = "0.13.0"
# --- Test-only --------------------------------------------------------------
insta = "1.48.0"
wiremock = "0.6.5"
assert_cmd = "2.2.2"
predicates = "3.1.4"
serial_test = "4.0.1"