1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Async task scheduler and process executor for `haz`.
//!
//! The crate currently exposes the following public modules:
//!
//! - [`process`]: the [`process::ProcessSpawner`] /
//! [`process::Process`] trait pair, the
//! [`std_impl::StdProcessSpawner`] production backend over
//! [`tokio::process::Command`], and the value types every
//! implementation produces. A scriptable mock implementation lives
//! alongside but is gated to test builds only and is not part of
//! the crate's public surface.
//! - [`cache_key`]: the [`cache_key::build_cache_key`] derivation,
//! gluing the workspace state, the validated dependency graph,
//! the host-env snapshot, and the per-predecessor stream hashes
//! into a [`haz_cache::CacheKey`].
//! - [`run_task`]: the single-task lifecycle ([`run_task::run_task`],
//! [`run_task::cache_lookup_phase`], [`run_task::restore_from_hit`],
//! [`run_task::run_fresh`]) and the [`run_task::RunObserver`]
//! port. The [`run_task::RunOutcome`] sum type carries
//! [`run_task::CompletedRecord`] (a task that reached an
//! `EXEC-009` classification through the lookup-then-spawn
//! pipeline), [`run_task::SkipRecord`] (a task the cascade
//! marked do-not-schedule per `EXEC-010` / `EXEC-011`), and
//! [`run_task::CancelledRecord`] (a task the cancellation
//! flow caught per `EXEC-012..015`, in three structural
//! shapes: signalled in flight, drained from the ready set,
//! or cascade-cancelled from an upstream cancellation).
//! - [`run_graph`]: the workspace-wide [`run_graph::run_graph`]
//! scheduler. It walks a validated [`haz_dag::graph::TaskGraph`]
//! in canonical order, admits ready tasks subject to the
//! global and per-tag concurrency caps (`EXEC-004` /
//! `EXEC-005`), evaluates mutex compatibility post-lookup
//! against a live hold set (`EXEC-006` condition 3 /
//! `EXEC-007` / `MUTEX-001..007`), threads predecessor stream
//! hashes into every downstream [`run_task::run_task`] call,
//! and cascade-skips hard descendants of a failed task while
//! letting unrelated subgraphs continue (`EXEC-010`). Per
//! `EXEC-011` the scheduler emits a
//! [`run_task::RunOutcome::Skipped`] for every cascade-skipped
//! descendant and fires
//! [`run_task::RunObserver::on_task_skipped`] at cascade time.
//!
//! Cancellation (`EXEC-009` cancelled state,
//! `EXEC-012..015`) is wired end-to-end: the spawn-step future
//! observes [`run_task::RunContext::cancel`] and runs the
//! per-future SIGTERM / grace / SIGKILL dance against the child's
//! process group on Unix; the scheduler polls the same token,
//! stops admitting new tasks, drains [`run_task::RunOutcome`]s
//! into the `RunCancelled` shape for every task still in the
//! ready set, and reclassifies late-arriving lookup-step results
//! as `RunCancelled`. Cancellation cascades along hard edges via
//! the same `complete_failed` machinery as the failure cascade,
//! emitting [`run_task::CancelledRecord::UpstreamCancelled`]
//! per descendant.
//!
//! Output presentation (`EXEC-016` / `EXEC-017`) is delivered as
//! two distinct [`run_task::RunObserver`] implementations under
//! the [`output`] module:
//! [`output::LiveOutputObserver`] tag-prefixes each emitted line
//! with `[project:task] ` and writes lines atomically under a
//! single per-observer mutex, so multiple in-flight tasks
//! interleave at line granularity only;
//! [`output::BufferedOutputObserver`] accumulates each task's
//! bytes and writes them as two contiguous blocks (`stdout` then
//! `stderr`) on task completion. Cache-hit emission travels the
//! same observer methods as a fresh run, satisfying `EXEC-017`
//! structurally.
//!
//! Runtime DAG validation (`EXEC-019`, `EXEC-020`) is wired:
//! [`run_task::CompletedRecord`] carries each succeeded task's
//! [`run_task::CompletedRecord::materialised_outputs`] (sourced
//! from the output-resolution pass for fresh runs and
//! [`haz_cache::Manifest::outputs`] for cache hits); the
//! [`run_graph::run_graph`] scheduler maintains a per-run claim
//! map for `EXEC-020` and a kind-erased augmented edge set for
//! `EXEC-019`. Detected violations are appended to
//! [`run_graph::RunGraphOutcome::invariant_violations`] as
//! [`run_graph::RuntimeInvariantViolation`] entries
//! ([`run_graph::RuntimeInvariantViolation::OutputOverlap`] and
//! [`run_graph::RuntimeInvariantViolation::RuntimeCycle`]
//! variants). `EXEC-019` additionally trips an
//! internal child token (a child of [`run_task::RunContext::cancel`])
//! so in-flight non-cycle tasks receive SIGTERM via the
//! `EXEC-014` grace-and-escalate flow; the user-supplied parent
//! token stays
//! uncancelled. Pending cycle members in the ready set surface
//! as [`run_task::RunOutcome::Skipped`] with
//! [`run_task::SkipCause::RuntimeCycle`]. `EXEC-020` only stops
//! admission and lets in-flight tasks complete naturally per the
//! spec's silence on cancellation for output overlaps.
//!
//! Exit-code mapping (`EXEC-021`) is delivered by the
//! [`exit_code`] module: [`exit_code::exit_code_for`] takes a
//! finished [`run_graph::RunGraphOutcome`] plus an optional
//! [`exit_code::CancellationSignal`] (recorded by the binary's
//! OS signal handler) and returns the numeric exit status the
//! `haz run` process MUST report. The helper computes the
//! number; the binary that consumes haz-exec performs the
//! actual `process::exit` call (per the workspace-wide "pure
//! library" decision: signal handlers and stdio writes live at
//! the binary boundary, not inside any haz-exec module).
pub
pub