Expand description
Producer helper for the yah process-control channel — the two lines a workload writes so its supervisor stops having to guess at it.
§What the channel is
A supervisor watching a process from outside can ask exactly two questions: is the pid alive, and is the port open. Both are proxies. A process that has finished booting, one still replaying a WAL, and one wedged on a lock answer them identically — so the real answer has always been somewhere in stdout, and reading it means grepping a log tail for a sentence nobody agreed on.
W315’s rule: any process built to run under a yah camp SHOULD expose a
control channel. One verb, status, answering with a status document:
{"state":"running","pid":71455,"uptime_secs":41,"detail":"3 windows open"}state is the only required field, and its vocabulary is exactly
[kamaji_proto::WorkloadState] — pending | starting | running | draining | exited | failed. That is the whole compatibility story: the supervisor
already answers a Probe verb in these words, so a workload reporting in
the same words is believed verbatim instead of run through a translation
table that rots the first time either side gains a state. Build with the
kamaji feature and the From impls between the two enums are exhaustive
matches — adding a state to either side stops compiling until both agree.
§Using it
use procctl::{ProcState, ProcStatus};
// Hold the guard for as long as the process should answer. Dropping it
// stops the listener and unlinks the socket.
let _control = procctl::serve_env(|| {
if booted() {
ProcStatus::new(ProcState::Running)
.with_detail(format!("{} windows open", windows_open()))
} else {
ProcStatus::new(ProcState::Starting)
}
})?;serve_env returns Ok(None) when YAH_CONTROL_SOCK is unset, which is
the contract: the same binary runs unchanged outside a camp, and a process
MUST NOT fail for the variable’s absence.
The closure runs on the listener thread, on demand, once per request. It must not block for long and must not panic — a panic there takes the listener down with it, which reads to the supervisor as a process that stopped answering.
§What this crate deliberately is not
It is not required. The protocol is one newline-delimited JSON verb
precisely so the producer side is implementable in twenty lines, with no
dependency, in any language, by someone whose actual job that day is their
own app. A Bun script or a Python daemon emitting the same line is a
first-class conforming producer. This crate is ergonomics for the Rust
case, not a gate — and that is also why it does not reach for
kamaji-proto’s postcard wire, which would make conforming mean linking a
Rust crate (W315 §“Why newline-JSON”).
The client feature adds the consumer half (async, tokio) for supervisors.
Producers should leave it off; the default build is std-only.
@arch:see(.yah/docs/working/W315-process-control-channel.md)
Structs§
- Control
Server - A live control channel. Answers
statusuntil dropped. - Proc
Status - A workload’s self-description. Only
Self::stateis required.
Enums§
- Proc
State - Lifecycle vocabulary of a supervised process.
Constants§
- CONTROL_
SOCK_ ENV - Environment variable naming the control socket a supervised process should bind. Absent → the process is not running under a supervisor that wants a control channel, and MUST NOT fail for its absence.
- DEFAULT_
HTTP_ PATH - Conventional HTTP path for the status document on a process that already
serves HTTP. Not enforced —
[process.control] http_pathoverrides it — but a service with no reason to differ should use this one. - STATUS_
CMD - The one verb the channel requires.
Functions§
- control_
sock_ path - The socket path this process was told to bind, or
Noneoutside a camp. - serve_
at - Bind an explicit path and answer
statusfromstatus_fn. - serve_
env - Bind
$YAH_CONTROL_SOCKand answerstatusfromstatus_fn.