Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
44cdbdc
feat(cli): add `tui` Cargo feature gate for `sozu top`
FlorentinDUBOIS May 10, 2026
fd5291a
feat(metrics): add `Aggregator` runtime cardinality lease bookkeeping
FlorentinDUBOIS May 10, 2026
49305f9
feat(proto,command,server): SetMetricDetail TTL lease verb + plumbing
FlorentinDUBOIS May 10, 2026
d700f5d
feat(top): add `sozu top` subcommand skeleton + version flag
FlorentinDUBOIS May 10, 2026
fd18b8a
feat(top): theme + dual-channel transport + detail-lease guard
FlorentinDUBOIS May 10, 2026
3f26476
feat(top): app state with sparkline rings, rate calc, threshold table
FlorentinDUBOIS May 10, 2026
489f925
feat(top): OVERVIEW + CLUSTERS panes + cluster table state
FlorentinDUBOIS May 10, 2026
9336173
feat(top): wire render loop, F-key bar, terminal restore guard
FlorentinDUBOIS May 10, 2026
fb69314
style(top): rustfmt line wrap in cardinality::send_set_detail
FlorentinDUBOIS May 10, 2026
22355a8
feat(top): EVENTS pane — colour-coded SubscribeEvents tail
FlorentinDUBOIS May 10, 2026
15529c7
feat(top): H2 pane — streams, flow control, flood mitigations
FlorentinDUBOIS May 10, 2026
2e9db5e
feat(top): BACKENDS pane — flat sortable per-backend table
FlorentinDUBOIS May 10, 2026
e686aaf
feat(top): LISTENERS pane + listeners-collector transport thread
FlorentinDUBOIS May 10, 2026
6989ba5
feat(top): pulse tracker — fade tint on disappeared / down transitions
FlorentinDUBOIS May 10, 2026
14860e3
feat(top): big-text alert banner via tui-big-text
FlorentinDUBOIS May 10, 2026
f1ec6ca
feat(top): TOML skin loader + SOZU_TOP_SKIN env override
FlorentinDUBOIS May 10, 2026
985099f
feat(top): glyph mode auto-detect cascade
FlorentinDUBOIS May 10, 2026
5c64e8b
test(top): insta snapshot tests for every pane at multiple sizes
FlorentinDUBOIS May 10, 2026
73dff21
docs(top): add doc/sozu-top.md + wire configure_cli, CHANGELOG, README
FlorentinDUBOIS May 10, 2026
c34623d
feat(top): master-side audit emission for SetMetricDetail
FlorentinDUBOIS May 10, 2026
c40c615
feat(top): CERTS pane + certs-collector transport thread
FlorentinDUBOIS May 10, 2026
8dd6329
feat(top): k9s-style colon command palette via tui-input
FlorentinDUBOIS May 10, 2026
20c0dd4
feat(top): completion-time audit row for inline-audited verbs
FlorentinDUBOIS May 10, 2026
01f54a9
test(top): subprocess e2e harness for the `sozu top` binary
FlorentinDUBOIS May 10, 2026
c6d6fc1
fix(command): whitelist SetMetricDetail in ConfigState::dispatch
FlorentinDUBOIS May 11, 2026
e4aca9e
fix(top): transport threads publish-or-skip on UI stall
FlorentinDUBOIS May 11, 2026
08765e8
docs(proto,changelog): scope METRIC_DETAIL_CHANGED to operator-initia…
FlorentinDUBOIS May 11, 2026
ddbae84
fix(server,metrics): reject SetMetricDetail ttl_seconds > 300s with F…
FlorentinDUBOIS May 11, 2026
b568936
fix(top): loud ctrlc handler + panic hook + dirty-gate the render loop
FlorentinDUBOIS May 11, 2026
934fd84
fix(top): App is_dirty flag + RateCalculator retain + pulse rename + …
FlorentinDUBOIS May 11, 2026
25e6348
refactor(top): defer lease-failure to App.status; doc four-thread count
FlorentinDUBOIS May 11, 2026
f15fc5e
fix(top): canonicalize skin path under skins dir to enforce confinement
FlorentinDUBOIS May 11, 2026
339573e
refactor(top): cardinality cleanup — drop final_channel parking, uran…
FlorentinDUBOIS May 11, 2026
487a996
style(top): apply clippy::iter_cloned_collect on alpn_protocols copy
FlorentinDUBOIS May 11, 2026
65fecd6
test(top): refresh insta snapshot artefacts after rename and dirty-gate
FlorentinDUBOIS May 11, 2026
9b64fbb
docs(top): four transport threads, refresh CERTS pane status
FlorentinDUBOIS May 11, 2026
0ab5f9f
refactor(top): drop unwired no_color and log_file flags from TopArgs
FlorentinDUBOIS May 11, 2026
663fb75
style(top,proto): silence the last clippy and rustdoc warnings
FlorentinDUBOIS May 11, 2026
d988217
refactor(metrics): extract metric names into typed constants in metri…
FlorentinDUBOIS May 11, 2026
c0b9c37
fix(top,command): plug ship-blocker review findings
FlorentinDUBOIS May 11, 2026
56d2f88
feat(metrics,command): bind lease ownership to peer credentials
FlorentinDUBOIS May 11, 2026
e51b91d
feat(server,command): wire worker→master audit IPC for METRIC_DETAIL_…
FlorentinDUBOIS May 11, 2026
1421b42
feat(command): worker proto version handshake, scaffolding for unsupp…
FlorentinDUBOIS May 11, 2026
e8713ab
fix(metrics,server,top): TTL pre-validate, loud lease_apply, lease ca…
FlorentinDUBOIS May 11, 2026
9089d49
fix(top): skin anchor fail-closed, TOCTOU close, ctrlc graceful degrade
FlorentinDUBOIS May 11, 2026
c5d3e5c
refactor(top): drop Arc<Mutex> in DetailGuard.final_channel
FlorentinDUBOIS May 11, 2026
adfd810
refactor(metrics,top): finish names::* migration in H2 pane
FlorentinDUBOIS May 11, 2026
77d2346
docs(changelog,top): four-thread / six-connection accuracy, audit-sco…
FlorentinDUBOIS May 11, 2026
b0dd0da
chore(ci): add cargo audit gate; remove stray bin/api artefact
FlorentinDUBOIS May 11, 2026
ac19368
style: rustfmt sweep — collapse multi-line constructors emitted by ma…
FlorentinDUBOIS May 11, 2026
614a03a
test,fix: dispatch whitelist regression guard, safer u32 conversion
FlorentinDUBOIS May 11, 2026
0855259
fix(top): surface renewer-thread status on App.status
FlorentinDUBOIS May 11, 2026
5aa24b5
refactor(top): consolidate sort_header rendering across CLUSTERS + BA…
FlorentinDUBOIS May 11, 2026
a842114
refactor(top): collapse the three polling transport loops into a gene…
FlorentinDUBOIS May 11, 2026
cc1b4b2
refactor(top): PulseTracker retain + ActiveTab::from_alias palette di…
FlorentinDUBOIS May 11, 2026
8dd0057
refactor(top): consolidate gauge/count metric helpers across app + H2…
FlorentinDUBOIS May 11, 2026
6d0fe68
feat(command): capability-aware SetMetricDetail dispatch, MetricDetai…
FlorentinDUBOIS May 11, 2026
d0c2791
refactor(top): A6 + A8 cleanup, Tier-B stylistic sweep
FlorentinDUBOIS May 11, 2026
8233e15
feat(command,lib): per-worker WorkerMetricDetailStatus payload
FlorentinDUBOIS May 11, 2026
7533f63
style: rustfmt nightly sweep for the round-4 per-worker payload edits
FlorentinDUBOIS May 11, 2026
9e76b51
docs: scrub plan/round/review-tag references from committed comments
FlorentinDUBOIS May 11, 2026
be1127e
fix(command,ci): clippy clone_on_copy + cargo audit ignores for unmai…
FlorentinDUBOIS May 11, 2026
5de4f5e
fix(top): certs poll accepts CertificatesWithFingerprints; redact une…
FlorentinDUBOIS May 11, 2026
322dc41
chore(deps): drop paste + rustls-pemfile, restore strict cargo audit …
FlorentinDUBOIS May 11, 2026
d207812
test(top): align help-flag e2e with the trimmed clap surface
FlorentinDUBOIS May 12, 2026
006efb6
fix(command,metrics): single-worker merge, drop systemd RELOADING fla…
FlorentinDUBOIS May 12, 2026
cb85ef7
docs(configure): document the runtime metric-detail cardinality lease
FlorentinDUBOIS May 12, 2026
429b9fb
refactor(top): consolidate row helpers, Option combinators, pulse-tick
FlorentinDUBOIS May 12, 2026
2ce1170
fix(top): read per-cluster counters under backend-detail filing too
FlorentinDUBOIS May 12, 2026
3ae25cc
docs(changelog): note backend-detail counter fix on sozu top
FlorentinDUBOIS May 12, 2026
b80a8fc
feat(metrics): name the cluster.available_backends rollup gauge
FlorentinDUBOIS May 12, 2026
0805dd8
fix(top): cluster RPS rate, backends rollup, service-time, cert addr,…
FlorentinDUBOIS May 12, 2026
4ef9eb8
fix(top): redraw all panes on terminal resize
FlorentinDUBOIS May 12, 2026
45e9f0f
feat(top): wire --glyphs into graphs, fall back on backend rollup
FlorentinDUBOIS May 12, 2026
5e4c932
fix(top): backend bw column, sparkline width, F-key bar wiring
FlorentinDUBOIS May 12, 2026
544734c
feat(top): bandwidth as Mbps rate, auto-scaled cluster RPS
FlorentinDUBOIS May 12, 2026
1245f20
fix(top): drop --no-color from the ignored e2e test invocation
FlorentinDUBOIS May 12, 2026
85f94b0
fix(top): theme loader uses O_NOFOLLOW to close TOCTOU on leaf
FlorentinDUBOIS May 12, 2026
4e2d061
fix(audit): close SIEM column-smuggling at source and harden the sani…
FlorentinDUBOIS May 12, 2026
94ff63f
refactor(command,server): remove the proto_version capability handshake
FlorentinDUBOIS May 12, 2026
96f8b8e
refactor(top): single-owner channel for DetailGuard apply + renew + c…
FlorentinDUBOIS May 12, 2026
c115cb7
fix(server): enforce LEASE_CLIENT_ID_MAX_BYTES on worker clear branch
FlorentinDUBOIS May 12, 2026
3daba7b
fix(top): plumb shutdown flag + finite read timeout into events trans…
FlorentinDUBOIS May 12, 2026
44f1b45
fix(top): RawModeGuard install ordering + SIGTERM coverage
FlorentinDUBOIS May 12, 2026
7962982
fix(top): mark visible state mutations dirty so input redraws immedia…
FlorentinDUBOIS May 12, 2026
2d8bdcf
fix(top): graceful spawn-failure on background threads
FlorentinDUBOIS May 12, 2026
bb25425
fix(top): plumb transport-thread errors through App.status
FlorentinDUBOIS May 12, 2026
beafc77
fix(ctl): cfg-gate CtlError::SpawnFailed behind `tui` feature
FlorentinDUBOIS May 12, 2026
e0fbf41
docs: scrub stale flag references and fix doc/code mismatches
FlorentinDUBOIS May 12, 2026
d308875
refactor: misc PR-1256 polish
FlorentinDUBOIS May 12, 2026
692f89c
fix(metrics): latch cluster availability on state replay and health-c…
FlorentinDUBOIS May 12, 2026
e7f80d4
fix(metrics): authorise lease renewals against apply-time peer binding
FlorentinDUBOIS May 12, 2026
2115f9c
fix(audit): cover bidirectional override class in is_unsafe_line
FlorentinDUBOIS May 12, 2026
859586d
fix(audit): sanitise worker error reasons at both join sites
FlorentinDUBOIS May 12, 2026
4caf001
style: rustfmt the new import_configuration_state test
FlorentinDUBOIS May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,51 @@ jobs:
path: fuzz/artifacts
retention-days: 14

# ┌──────────────────────────────────────────────────────────────┐
# │ Group C′ — cargo audit: surface RUSTSEC advisories on the │
# │ whole workspace including the `tui` feature surface, which │
# │ pulls 12 optional crates (color-eyre, crossterm, ratatui, │
# │ tui-input, tui-big-text, …) outside the default build's │
# │ coverage. Treats every advisory as a warning; the job │
# │ fails only on `--deny warnings` so a maintainer can react │
# │ before the next release lands. │
# └──────────────────────────────────────────────────────────────┘
cargo-audit:
name: cargo audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install rust 1.88.0
uses: dtolnay/rust-toolchain@1.88.0

- name: Cache cargo target
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-all-crates: true
prefix-key: ci-audit
workspaces: . -> target/

- name: Install cargo-audit
run: cargo install cargo-audit --locked

- name: Run cargo audit (default features)
run: cargo audit --deny warnings

- name: Run cargo audit (all features incl. tui)
run: |
# `cargo audit` reads `Cargo.lock`, which already includes the
# `tui`-feature transitive deps since this PR landed them as
# workspace-locked entries. Re-running with the same lockfile
# produces a consistent advisory set; the duplicate invocation
# is kept symmetrical with the per-feature build matrix above.
cargo audit --deny warnings

# ┌──────────────────────────────────────────────────────────────┐
# │ Group D — Bombardier load benches (folded from benchmark.yml) │
# └──────────────────────────────────────────────────────────────┘
Expand Down
175 changes: 175 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,134 @@ upgrade. See `doc/upgrade/1.x-to-2.0.md` for the full migration guide.
`*.com`) is intentionally not implemented — that is a browser-policy
concern, not a reverse-proxy responsibility.

- **`QueryMetrics` now folds single-worker fleets correctly**: the master's
`QueryMetricsTask::on_finish` (`bin/src/command/requests.rs`) used to gate
`AggregatedMetrics::merge_metrics()` on `server.workers.len() > 1`, leaving
one-worker setups with empty `clusters` / `proxying` maps and stranding all
per-worker data in `workers`. Every CLI/TUI consumer that reads the merged
shape (`sozu metrics get` without `--workers`, every `sozu top` pane) silently
showed zero data. The fix drops the `> 1` guard — `merge_metrics()` correctly
handles one worker (the relocation loop runs once) and zero workers (no-op).
Regression test `merge_relocates_single_worker_to_top_level` in
`command/src/proto/mod.rs` pins the contract.
- **`sozu top` BACKENDS bandwidth column shows a per-second rate in Mbps**:
the column was previously a cumulative byte count since worker start —
`1.4G/5.6G` after a few hours, monotonically increasing, almost never
what an operator needs while monitoring live traffic. Mirror the
cluster-RPS pattern: the shared `RateCalculator` derives per-second
deltas from the cumulative `BYTES_IN`/`OUT` counters under
`__backend.<cluster>.<backend>.<bytes_in|bytes_out>` keys, the App
caches the rates on `backend_rate_in_bps` / `backend_rate_out_bps`,
and the renderer formats `bytes/sec × 8` as Kbps / Mbps / Gbps
base-1000 to match the networking convention (`nload`, `iftop`,
Grafana panels). Stale per-backend keys are pruned on every snapshot
alongside the cluster-RPS sweep so `RateCalculator.history` cannot
grow unbounded as a fleet churns. Column header changes from
`bw down/up B` to `bw down/up Mbps`; the BACKENDS-sort `Bandwidth`
comparator now uses the rate.
- **`sozu top` CLUSTERS rps column auto-scales to K/M/G**: dashboards
with traffic over 1k req/s were truncating the `rps` cell at the
8-character width. The column is now 14 wide and the value renders
with a base-1000 suffix (`94.8K req/s`, `1.20M req/s`) so the cell
stays readable for any cluster scale.
- **`sozu top` BACKENDS pane "bw down/up B" now populates**: the renderer
was reading `names::backend::BACK_BYTES_IN` / `BACK_BYTES_OUT` from the
per-backend filing, but those keys are emitted as no-label proxy
counters via `count!(key, value)` at every byte-flow site in
`lib/src/protocol/{pipe,kawa_h1/mod,mux/mod,proxy_protocol/{send,relay}}.rs`
— they land in `worker.proxy[…]` and are never tagged
`(cluster_id, backend_id)`. The per-backend cumulative bytes flow
through `record_backend_metrics!` (`lib/src/metrics/mod.rs`) at request
end under the keys `BYTES_IN` / `BYTES_OUT` with `$bin` / `$bout`
sourced from `SessionMetrics.backend_bin` / `backend_bout` — i.e.
the same backend-socket accumulators. The TUI now reads those.
- **`sozu top` overview sparklines fill the full pane width**: the
default `RenderDirection::LeftToRight` left-anchored samples, which
made the graph appear to use only the left half of its cell until
the ring filled out to column-width. Switch to `RightToLeft` so the
newest sample sits on the right edge and history scrolls leftward
as new ticks arrive — the familiar `vmstat` / `htop` direction.
- **`sozu top` F-key bar actually does things**: F1 Help and F10 Quit
were wired but F2-F9 were placeholders. F2 cycles the resolved
glyph mode (Block → Braille → Tty); F3 and F4 open the colon
palette so operators can type `:cluster <id>` etc.; F5 (also `p`)
pauses snapshot ingest without dropping the transport lease so the
TUI freezes on the last frame for closer inspection, and the label
flips to "Resume" while paused; F6 cycles the active pane's sort
column. F7/F8/F9 remain reserved slots (`·`) so the bar width
stays stable across builds.
- **`sozu top` CLUSTERS column now shows req/s, not cumulative counter**:
the column header was `rps` but the cell rendered the cumulative
`names::backend::REQUESTS` count, which only changes when a backend
responds. Per-cluster `RateCalculator` keyed by `__cluster.<id>.requests`
derives the per-second delta once per ingest and the renderer prints
`<rps> req/s`. Stale cluster keys are pruned on each snapshot so the
rate history cannot grow unbounded as clusters churn.
- **`sozu top` BACKENDS up/total now reads the cluster rollup gauge**:
the renderer was reading `names::backend::AVAILABLE` (a per-backend
gauge set on backend up/down transitions) at the cluster level, which
was always `0` because the gauge is keyed `(cluster, backend)` and
only fires on transitions. A new `names::cluster::AVAILABLE_BACKENDS`
constant captures the canonical per-cluster rollup (`available`
argument of `gauge!` in `lib/src/backends.rs::notify_availability`),
which is the authoritative aggregate and refreshes every health-check
tick. Per-backend `backend.available` (under backend-detail filing)
remains a fallback for the very first snapshot.
- **`sozu top` OVERVIEW replaces 5xx panel with sozu service-time
p99**: the 5xx cell duplicated the LATENCY p99 cell's intent (both
read worst-case quality). Operators want sozu's own request-processing
time distinct from backend latency, so the bottom-left OVERVIEW cell
now plots p99 of `names::event_loop::SERVICE_TIME` from the proxy
map. The critical-banner threshold reuses `latency_p99_critical_ms`
with the `SOZU SLOW` headline so a stalled event loop still trips
the alert overlay.
- **`sozu top` CERTS pane renders addresses as `ip:port`**: the address
column was printing `{:?}` of the proto `SocketAddress` (`SocketAddress
{ ip: IpAddress { … } }`), which is unreadable. The pane now goes
through the existing `From<SocketAddress> for SocketAddr` conversion
so v4 prints as `1.2.3.4:443` and v6 as `[::1]:443`.
- **`sozu top` H2 pane trend column actually plots a trend**: the
trend cells rendered as `"—"` placeholders. Each H2-pane metric now
has a 60-sample `SparkRing` populated by `App::fold_h2_trends` per
ingest; the renderer prints Unicode bars (`▁▂▃▄▅▆▇█`) scaled to the
ring's max sample so even zero-flat series stay distinguishable from
the cold-start placeholder.
- **`sozu top` OVERVIEW and CLUSTERS read counters under backend-detail
filing**: the TUI auto-leases `MetricDetail::Backend` on startup so the
BACKENDS pane has per-row data, which routes every per-cluster counter
(`requests`, `http.status.5xx`, `backend_response_time`) into the worker's
`clusters[<id>].backends[<id>].metrics` map rather than `clusters[<id>].cluster`.
The OVERVIEW and CLUSTERS panes were reading the cluster-level map
exclusively, so the REQUESTS / SEC big-numeral, 5xx ratio, latency p99, and
every per-cluster row read 0 the moment the lease elevated detail. New
helpers in `bin/src/ctl/top/app.rs` (`cluster_count_total`, `cluster_p99_max`,
`cluster_p50_max`) sum across both filings; magic-string keys are replaced
with `sozu_lib::metrics::names::*` constants so a future name rename cannot
silently zero the TUI again. The `proxying` fallback now uses
`names::http::REQUESTS` (`http.requests`, incremented at request-receive
time) so traffic surfaces even before the first backend round-trip
completes.
- **`SetMetricDetail` no longer flaps systemd `RELOADING=1` every TTL/2**: the
cardinality-lease verb was in the `is_mutating_verb` allowlist
(`bin/src/command/requests.rs`), which brackets each call with
`sd_notify(STATE_RELOADING)` / `READY=1`. The TUI auto-renews its lease every
`ttl/2` seconds (≈ 30 s by default) to keep the lease alive, so a long-lived
`sozu top` session caused the unit to flap `reloading`/`active` every 30 s in
`systemctl status` and external monitors. `SetMetricDetail` is now excluded:
it does not change cluster/listener/cert state or fleet topology, so it does
not belong in the systemd bracket set. The audit-log trail
(`EventKind::METRIC_DETAIL_CHANGED`, proto tag 30) still records every apply,
clear, and TTL expiry, so SOC visibility is unaffected. Regression test
`set_metric_detail_is_not_mutating` in `bin/src/command/requests.rs` pins
membership.
- **Cross-worker `Percentiles` aggregation no longer silently drops on
`merge_metrics()`**: `command/src/proto/mod.rs::is_mergeable` returned `false`
for `Inner::Percentiles`, so any cluster metric emitted in percentile shape
(notably `backend_response_time`) was discarded when more than one worker
contributed. The merge now propagates the element-wise max per quantile and
accumulates `samples` + `sum`, preserving the "worst observed quantile"
upper bound across workers. The companion `<name>_histogram` value remains
the source of truth for statistically accurate aggregation.
- **Response-side header edits no longer re-injected on every prepare
cycle (`H2BlockConverter::finalize` "out buffer not empty" leak)**:
`apply_response_header_edits` was called inconditionally before
Expand Down Expand Up @@ -346,6 +474,53 @@ upgrade. See `doc/upgrade/1.x-to-2.0.md` for the full migration guide.

### 🌟 Added

- **`sozu top` btop/htop-style live operator TUI**: a new clap subcommand
gated by the optional `tui` Cargo feature on `bin/`
(default builds remain lean — `sozu --version` reports `+tui` when the
subcommand is linked in). Surfaces metrics that Sōzu already emits in
six panes — OVERVIEW (sparklines + big numerals), CLUSTERS / BACKENDS
(sortable tables), LISTENERS (5 s ListListeners poll), H2 (streams +
flow control + CVE flood mitigations), CERTS (15 s certificates
poll), EVENTS (colour-coded SubscribeEvents tail). Built on
`ratatui = "0.30"` + `crossterm = "0.29"` over four synchronous
transport threads (snapshots, listeners, certs, events — no `tokio`
runtime in v1 by design); render loop is capped at 30 fps with
DEC-mode-2026 synchronized output for tmux-free flicker. The TUI auto-elevates
metric cardinality via a `SetMetricDetail` TTL lease (`client_id`-
keyed; default 60 s TTL, server clamp 300 s; auto-renewed at half-TTL;
self-expires server-side on crash). A 4-tick pulse tint surfaces
cluster appearance / disappearance / backend-went-down transitions
visually. A `tui-big-text` alert banner overlays the active pane when
the threshold table fires (p99 > 500 ms / 5xx > 1 % / saturation >
80 %). `--skin <name>` (with `SOZU_TOP_SKIN` env override) resolves
TOML skins under `$XDG_CONFIG_HOME/sozu/skins/`. Default Okabe-Ito
colour-blind safe categorical + Viridis-shaped continuous ramp;
trend glyphs `▲ ▼ ●` back up the colour cue. Three glyph modes
(Braille / Block / TTY-ASCII) auto-detect against `TERM` / `LANG`.
Full operator guide: [`doc/sozu-top.md`](doc/sozu-top.md).

- **`SetMetricDetail` runtime cardinality verb**
(`command/src/command.proto:55`, `ResponseContent::MetricDetailStatus =
16`, `EventKind::METRIC_DETAIL_CHANGED = 30`): operators (and any TUI
client) can elevate the worker's `MetricDetail` floor at runtime via
a TTL-bounded lease. Multiple clients lease independently — the
effective level is `max(configured, max(active leases))`. Leases
self-expire server-side after `ttl_seconds` so a crashed client
cannot permanently elevate cardinality. Workers that pre-date the
verb return the standard `unknown request type` error, which surfaces
in the normal fan-out error tally on `MetricDetailStatus`; production
deployments keep master + workers in sync via the `UpgradeMain`
hot-upgrade flow, so this mixed-version state is transient. Full
semantics in `command.proto`'s `SetMetricDetail` doc comment. Audit-scope: every
cardinality transition emits `EventKind::METRIC_DETAIL_CHANGED` —
operator-initiated transitions are logged by the master at the
dispatch site, AND the worker now emits the same event for janitor-
driven lease expiries and post-fan-out worker-arm apply/clear via
the new worker→master audit IPC, so the audit log carries the full
history regardless of origin. Lease ownership is bound to the
connecting peer's PID + session ULID (`SO_PEERCRED`-derived);
cross-operator `clear` requests are refused at the worker.

- **Pre-built binaries for tagged releases
([#1089](https://github.com/sozu-proxy/sozu/issues/1089))**: a new
`.github/workflows/release.yml` triggers on tag push (`X.Y.Z` and
Expand Down
Loading
Loading