The palette HW-DR-0045
rules on, and the pure functions every renderer applies it through.
Why this is a leaf crate and not a module of headwater-check
These primitives lived in headwater-check until #479, beside
headwater_check::fill, because the four crates that needed them then —
headwater-check itself, headwater-query, headwater-sweep and
headwater-cli — all sit above that crate and reach it without a cycle.
That placement held for as long as every renderer that wanted color sat
above headwater-check, and it stopped holding the moment one did not.
headwater-check depends on headwater-census, headwater-resolve and
headwater-lock. A renderer inside any of those three cannot name
headwater_check::paint at all: cargo refuses the cycle before a line
compiles. Six of the eleven command lines whose interface contract still
promises terminal sensing render inside exactly those three crates —
headwater derived in headwater-census, and taxonomy validate,
resolve, publish, vendor and migrate in headwater-resolve and
headwater-lock. Wiring any of them was not expensive, it was impossible,
and nothing recorded that until the dependency graph was read against
HW-OBL-0180.
So the palette moved to the bottom of the graph, where every renderer of
this engine reaches it. This crate depends on nothing, for the reason
headwater-hash and headwater-mark depend on nothing: a rule that two
components on opposite sides of the engine both read must have one
implementation, and a second copy of a palette is two palettes.
What stayed in headwater-check, and why
Severity is a type of the check layer, so glyph, severity_role and
severity_word stayed with it rather than dragging Severity down here
behind them. headwater_check::paint re-exports everything below
unchanged, so every caller that wrote headwater_check::paint::Role before
the move still compiles — the same re-export chain
engine/crates/cli/src/paint.rs already ran one level up.
Deciding whether a stream renders color stays in headwater-cli:
stdout_color and its stderr twin read the terminal a process is
attached to, which is a fact about the binary rather than about a corpus.
ColorMode is a parameter, never a read
Every function here is pure: given the same [Role] and the same
[ColorMode], it returns the same bytes. Nothing here opens a stream or
reads an environment variable, which is what makes every renderer that
takes a mode unit-testable with a case table and no real terminal.
That purity is also the defect HW-OBL-0180
records: a renderer threaded with a mode and a call site that hands it
[ColorMode::Plain] forever passes every headless test there is. What
catches that is tools/engine/color-fixtures.sh, which attaches a
pseudo-terminal and counts escape bytes, and nothing else in this
repository can.
Plain writes no escape sequence, ever
HW-DR-0045
is explicit that the fallback is "no escape sequence at all", and
NO_COLOR_TEXT (engine/crates/cli/src/lib.rs) promises the same thing to
every caller of --no-color. So [paint] and [dim] write text back
unchanged under [ColorMode::Plain].