Step-runner over Part D work items: select ready -> compile context at run time -> invoke the allowlisted worker -> typed done-probe decides -> record actuals -> advance status. The model gets navigated; it never navigates.
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/work-runner |
| Edition | 2021 |
| Targets | tools-work-runner, tools_work_runner, watch_integration |
| Public items | 36 across 4 modules |
| Tests | 44 |
What it is for
Step-runner core (sprint 4.5): the pure halves of the loop — probe parsing/evaluation and the retry state machine — kept free of DB and model I/O so every decision branch is testable and mutation-provable. The loop's doctrine (harness-design-notes §5): the probe owns "done"; the model's claim owns nothing.
Capabilities
crate root
Step-runner core (sprint 4.5): the pure halves of the loop — probe
| Item |
|---|
pub const DEV_SERVER_EXIT_BOOT_FAILED: u8 |
pub const DEV_SERVER_EXIT_INFRA: u8 |
fn http_status_line_valid(line : & str) -> bool |
fn boot_verdict(child_alive : bool, http_answered : bool) -> Result <(), String> |
FailedProbeRoute
Step-runner core (sprint 4.5): the pure halves of the loop — probe
| Item |
|---|
pub enum FailedProbeRoute |
fn route_after_failed_probe(probe_only : bool) -> FailedProbeRoute |
watch (other)
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub const INTERVAL_RANGE: (u64, u64) |
pub const MAX_TICKS_CAP: u64 |
pub const STALL_TICKS_RANGE: (u64, u64) |
pub const ERROR_TICKS_RANGE: (u64, u64) |
pub const OBSERVE_TIMEOUT: Duration |
async fn run_watch(cfg : & WatchConfig, dsn : & str) -> LoopEnd |
ExitOn
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub enum ExitOn |
Format
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub enum Format |
LoopEnd
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub enum LoopEnd |
fn exit_code(end : LoopEnd) -> i32 |
MetricOp
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub enum MetricOp |
MetricOp :: fn holds(self, actual : f64, wanted : f64) -> bool |
ProcessProbe
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub enum ProcessProbe |
WatchConfig
work-runner watch (sprint 4.14): poll a run_measurement series, a
| Item |
|---|
pub struct WatchConfig |
fn parse_watch_args(args : & String) -> Result <WatchConfig, String> |
watch::emit (other)
Tick rendering (sprint 4.14 AC1/AC2). Every DB- or git-sourced field
| Item |
|---|
pub const FIELD_CAP_BYTES: usize |
fn sanitize(field : & str) -> String |
TickRender
Tick rendering (sprint 4.14 AC1/AC2). Every DB- or git-sourced field
| Item |
|---|
pub struct TickRender<'a> |
fn render_text(t : & TickRender) -> String |
fn render_json(t : & TickRender) -> String |
watch::observe (other)
The three observers (sprint 4.14 AC1/AC6/AC7). Subprocesses are strict
| Item |
|---|
async fn observe_telemetry(pool : & sqlx::PgPool, run_id : & str,) -> Result <Option <(String, String)>, String> |
async fn observe_processes(probe : & ProcessProbe) -> Result <u64, String> |
async fn observe_head(worktree : & Path) -> Result <String, String> |
async fn run_argv_with_timeout(bin : & str, args : & & str, dur : std::time::Duration,) -> Result <std::process::Output, String> |
tokio::process::Child
The three observers (sprint 4.14 AC1/AC6/AC7). Subprocesses are strict
| Item |
|---|
tokio::process::Child :: async fn wait_with_output_shim(& mut self) -> std::io::Result <std::process::Output> |
StallMachine
Pure stall state machine (sprint 4.14 AC4 + Gate 1.5 amendment (a)):
| Item |
|---|
pub struct StallMachine |
StallMachine :: fn new(stall_ticks : u64) -> Self |
StallMachine :: fn advance(& mut self, obs : & TickObs) -> Verdict |
TickObs
Pure stall state machine (sprint 4.14 AC4 + Gate 1.5 amendment (a)):
| Item |
|---|
pub struct TickObs |
Verdict
Pure stall state machine (sprint 4.14 AC4 + Gate 1.5 amendment (a)):
| Item |
|---|
pub enum Verdict |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
tools_work_runner
watchwatch::emitwatch::observewatch::stall
flowchart TD n_tools_work_runner["tools_work_runner"] n_tools_work_runner --> n_watch["watch"] n_watch --> n_watch__emit["emit"] n_watch --> n_watch__observe["observe"] n_watch --> n_watch__stall["stall"]
Public surface
`crate root`
| Item | What it is |
|---|---|
pub enum FailedProbeRoute | Where a failed rung-0 probe goes (sprint 4.48) |
fn route_after_failed_probe(probe_only : bool) -> FailedProbeRoute | — |
pub const DEV_SERVER_EXIT_BOOT_FAILED: u8 | Dev-server exit codes (sprint 4.9 AC-retry-split, Gate 1.5 adopted redline 5): contractual so the step machine can map them onto its corrective/infra split without parsing prose |
pub const DEV_SERVER_EXIT_INFRA: u8 | — |
fn http_status_line_valid(line : & str) -> bool | The AC5 readiness predicate, pure: a valid HTTP status line proves the server ANSWERED, whatever the status (a 500 is a booted server; a non-HTTP banner or empty read is not). |
fn boot_verdict(child_alive : bool, http_answered : bool) -> Result <(), String> | Boot verdict, pure (Gate 1.5 redline 2: a child that exited is a boot failure even if SOMETHING answers the port — false-green class). |
`watch`
| Item | What it is |
|---|---|
pub const INTERVAL_RANGE: (u64, u64) | — |
pub const MAX_TICKS_CAP: u64 | — |
pub const STALL_TICKS_RANGE: (u64, u64) | — |
pub const ERROR_TICKS_RANGE: (u64, u64) | — |
pub const OBSERVE_TIMEOUT: Duration | — |
pub enum ExitOn | — |
pub enum MetricOp | — |
MetricOp :: fn holds(self, actual : f64, wanted : f64) -> bool | — |
pub enum Format | — |
pub enum ProcessProbe | — |
pub struct WatchConfig | — |
pub enum LoopEnd | Why the loop ended, mapped to the exit contract (AC5). |
fn exit_code(end : LoopEnd) -> i32 | — |
fn parse_watch_args(args : & String) -> Result <WatchConfig, String> | Parse and validate every input BEFORE any I/O (AC3) |
async fn run_watch(cfg : & WatchConfig, dsn : & str) -> LoopEnd | The tick loop |
`watch::emit`
| Item | What it is |
|---|---|
pub const FIELD_CAP_BYTES: usize | — |
fn sanitize(field : & str) -> String | Newline/CR become single spaces; result truncated to FIELD_CAP_BYTES without splitting a UTF-8 char. |
pub struct TickRender<'a> | One tick, ready to render |
fn render_text(t : & TickRender) -> String | Text mode: STALL ts run_id metric=value processes=N head=<short> with an error=<msg> field appended when an observer failed. |
fn render_json(t : & TickRender) -> String | JSON mode: one object per line, all eight keys always present (null where absent) |
`watch::observe`
| Item | What it is |
|---|---|
async fn observe_telemetry(pool : & sqlx::PgPool, run_id : & str,) -> Result <Option <(String, String)>, String> | Latest telemetry row for the series: metric and value as SEPARATE columns (AC1); value is numeric in the schema — cast to text at the query so rendering never reformats it. |
async fn observe_processes(probe : & ProcessProbe) -> Result <u64, String> | Process liveness |
async fn observe_head(worktree : & Path) -> Result <String, String> | Worktree HEAD short line |
async fn run_argv_with_timeout(bin : & str, args : & & str, dur : std::time::Duration,) -> Result <std::process::Output, String> | Strict-argv subprocess under a deadline |
tokio::process::Child :: async fn wait_with_output_shim(& mut self) -> std::io::Result <std::process::Output> | — |
`watch::stall`
| Item | What it is |
|---|---|
pub struct TickObs | One tick's observations, reduced to what the machine compares. |
pub enum Verdict | — |
pub struct StallMachine | — |
StallMachine :: fn new(stall_ticks : u64) -> Self | — |
StallMachine :: fn advance(& mut self, obs : & TickObs) -> Verdict | — |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
{decide,eval_path_exists,parse_probe,Attempts,NextAction,Outcome,PathKind,Probe,ProbeResult,} | operations_control_plane::probe::{decide,eval_path_exists,parse_probe,Attempts,NextAction,Outcome,PathKind,Probe,ProbeResult,} |
Boundary
Reaches into operations.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/work-runner |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_tools["tools"] --> n_operations["operations"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `operations-control-plane` | operations | no | always |
| `tools-packet-compile` | tools | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
anyhow | ^1 | — | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json, runtime-tokio, … (10 total) | no | always |
tokio | ^1 | full, rt-multi-thread, macros, time | no | always |
uuid | ^1 | v4, v7, serde, js, v4, serde | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tempfile | ^3 | — | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["tools-work-runner"] SELF -->|runtime| n_operations_control_plane["operations-control-plane"] SELF -->|runtime| n_tools_packet_compile["tools-packet-compile"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | tools-work-runner | `src/main.rs` |
| lib | tools_work_runner | `src/lib.rs` |
| test | watch_integration | `tests/watch_integration.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | yes |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
CARGO_PKG_VERSION | src/main.rs |
CORPUS_SOURCECODE_APP_DB | src/main.rs |
WORK_RUNNER_LOCAL_BIN | src/main.rs |
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 32 |
| Integration tests | 12 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 6 | 0 | 0 |
watch | 14 | 0 | 0 |
watch::emit | 5 | 0 | 0 |
watch::observe | 4 | 0 | 0 |
watch::stall | 3 | 0 | 0 |
What the tests establish, by name:
bad_dsn_is_error_observation—tests/watch_integration.rsclosed_stdout_exits_1—tests/watch_integration.rsdb_statement_timeout_is_bounded—tests/watch_integration.rserror_cap_exits_4—tests/watch_integration.rsgit_index_lock_does_not_break_head—tests/watch_integration.rshung_child_is_killed_and_reaped—tests/watch_integration.rspgrep_no_match_is_zero_not_error—tests/watch_integration.rspid_mode_liveness—tests/watch_integration.rsseeded_tick_and_series_empty_warning—tests/watch_integration.rssigterm_interrupts_sleep_and_exits_0_within_2s—tests/watch_integration.rsunset_dsn_exits_2_no_fallback—tests/watch_integration.rsvalidation_failures_exit_2_before_io—tests/watch_integration.rshttp_status_line_and_boot_verdict_are_exact—src/lib.rsprobe_only_routes_a_failed_probe_to_block_not_to_a_model—src/lib.rsthe_lifted_probe_vocabulary_is_still_reachable_from_here—src/lib.rsforged_value_cannot_start_a_line—src/watch/emit.rsjson_has_all_eight_keys_and_parses—src/watch/emit.rsjson_stall_is_key_not_prefix—src/watch/emit.rssanitize_strips_newlines_and_caps_bytes—src/watch/emit.rstext_empty_series_renders_none—src/watch/emit.rstext_line_shape—src/watch/emit.rstext_stall_prefix_and_error_field—src/watch/emit.rsdefaults_parse—src/watch/mod.rserror_ticks_boundaries—src/watch/mod.rsexit_code_contract—src/watch/mod.rsexit_on_specs—src/watch/mod.rsinterval_boundaries—src/watch/mod.rsmax_ticks_boundaries—src/watch/mod.rsmetric_op_semantics—src/watch/mod.rspattern_charset_allows_plus_rejects_shell—src/watch/mod.rs- _… 14 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 16 | 36 |
Public modules with a //! block | 4 | 4 |
pie showData
title Public items with rustdoc
"Documented" : 16
"No rustdoc detected" : 20
Metrics
| Metric | Value |
|---|---|
| Rust source files | 6 |
| Source lines | 3193 |
| Code lines | 2725 |
| Public API items | 36 |
| Public modules | 4 |
| Tests | 44 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 8 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 8
"enum" : 7
"function" : 13
"method" : 4
"struct" : 4
pie showData
title Rust source composition
"Code" : 2725
"Blank or comment" : 468
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.