v0.5.22
What's new in v0.5.22
v0.5.22 ships a first-class CLI helper for the daemon ack API introduced in v0.5.20. Operators on call can now acknowledge findings from the terminal without typing curl by hand: perf-sentinel ack create --signature <SIG> --reason <TEXT> [--expires 7d], perf-sentinel ack revoke --signature <SIG>, perf-sentinel ack list [--output text|json]. The release is purely a UX layer on top of the existing v0.5.20 endpoints (POST / DELETE /api/findings/{signature}/ack and GET /api/acks) and the v0.5.21 Prometheus counters that track operator-driven activity. The daemon HTTP surface is byte-for-byte unchanged.
The CLI resolves the daemon API key in priority order: PERF_SENTINEL_DAEMON_API_KEY env var, then --api-key-file <path> (with O_NOFOLLOW and an opt-in stderr warning when the file is group/world readable), then a no-echo rpassword prompt if a 401 is received and stdin is a TTY. There is no --api-key <SECRET> flag, by design: passing secrets on the command line leaks them via the process list and shell history. The --expires flag accepts both ISO8601 datetimes (2026-05-11T00:00:00Z) and relative durations parsed by humantime (7d, 24h, 30m). Exit codes follow Unix conventions: 0 on success, 1 on generic errors (network, parse, file IO), 2 on HTTP 4xx (already acknowledged, unauthorized, invalid signature, not found on revoke), 3 on HTTP 5xx (ack store disabled, write failure, store full).
The --daemon <URL> validator hardens against four classes of typo-or-paste mistakes that would otherwise bubble as opaque transport errors: empty authority (http://), port without host (http://:8080), userinfo embedded in the URL (http://alice@host, redirected to --api-key-file / env), path component (https://api.example.com/v1/, the CLI builds /api/... itself), and query string (http://host?debug=1). Each rejection produces a one-line stderr error with an actionable hint. Inputs containing ASCII control characters (\n, \r mid-string) inside the API key file are rejected at read time, before the value reaches hyper's HeaderValue::from_str, so operators get a message naming the file rather than a generic build error.
A new fetch_with_body helper in crates/sentinel-core/src/http_client.rs serves the POST/DELETE traffic with a configurable timeout, the existing 8 MiB response-body cap, the X-API-Key header flagged sensitive (so hyper redacts it from Debug and HPACK), and a (StatusCode, Bytes) return shape so callers can discriminate non-2xx without ? short-circuit. The corresponding HttpClientWithBody type alias and build_client_with_body constructor are factored through a single private build_client_inner<B>() generic so the rustls TLS configuration cannot drift between the GET and POST clients.
Helm chart 0.2.25 ships in lockstep, bumping the default daemon image tag to ghcr.io/robintra/perf-sentinel:0.5.22. No chart-level config change.
Added
- New
perf-sentinel ackCLI subcommand with three subactions:ack create --signature <SIG> --reason <TEXT> [--expires <ISO8601_OR_DURATION>] [--by <NAME>] [--api-key-file <PATH>]posts to/api/findings/{sig}/ackand prints a colored summary (signature, by, reason, expires with a relative hint like "in 7 days").ack revoke --signature <SIG> [--api-key-file <PATH>]deletes via/api/findings/{sig}/ackand prints a confirmation line.ack list [--output text|json] [--api-key-file <PATH>]enumerates active daemon acks via/api/acks. Text format renders an aligned table with a footer that quotes the daemon's 1000-entry cap and points users at.perf-sentinel-acknowledgments.tomlfor the CI TOML side. JSON format is a pretty-printed array of the rawAckEntrypayload.
- Daemon URL resolution via
--daemon <URL>flag,PERF_SENTINEL_DAEMON_URLenv var, or defaulthttp://localhost:4318(matchesperf-sentinel watch). - Auth resolution via
PERF_SENTINEL_DAEMON_API_KEYenv var,--api-key-file <path>(trailing newline stripped, embedded ASCII control characters rejected), or interactiverpasswordprompt on 401 when stdin is a TTY. No--api-key <SECRET>flag. - Duration parsing on
--expiresaccepts ISO8601 datetimes (2026-05-11T00:00:00Z) or relative durations parsed byhumantime(7d,24h,30m). Omit for a permanent ack. - Conventional Unix exit codes: 0 success, 1 generic error (network, parse, file IO), 2 client error (HTTP 4xx), 3 server error (HTTP 5xx). Errors land on stderr with a one-line cause and an actionable hint when applicable (e.g., on 409 the hint points at
ack revoke, on 401 it lists the env var and--api-key-file). fetch_with_bodyhelper incrates/sentinel-core/src/http_client.rsfor POST/DELETE requests withX-API-Key(flaggedsensitive), 10s timeout, 8 MiB body cap. Returns(hyper::StatusCode, bytes::Bytes)so callers discriminate non-2xx without short-circuit.HttpClientWithBodytype alias andbuild_client_with_bodyconstructor in the same module, factored through a privatebuild_client_inner<B>()generic so the rustls TLS configuration is shared with the existingHttpClientGET path.call_with_tty_retryhelper in the new ack module: builds the client once per CLI invocation and reuses it across the 401-prompt-retry path, so the second TLS init the previous draft would have paid is gone.- Hardened
--daemonURL validator that rejects empty authority, port without host, userinfo, path components, and query strings before the first request goes out. Each rejection has its own actionable error message. O_NOFOLLOWon--api-key-file(Unix only): the CLI refuses to follow symlinks at the path target, mirroring the daemon'scrates/sentinel-core/src/daemon/ack.rsposture for the JSONL store. When the file mode is group/world readable (mode & 0o077 != 0), the CLI emits a one-line stderr warning suggestingchmod 600. The warning is gated behindstderr.is_terminal()so CI pipelines don't see it on every invocation.MAX_ACKS_RESPONSEexposedpubincrates/sentinel-core/src/daemon/query_api.rsand re-imported in the CLI under the same name, so theack listfooter ("showing up to 1000") cannot drift from the daemon-side cap.docs/CLI.md(English) anddocs/FR/CLI-FR.md(French): new reference page covering theacksubcommand exhaustively (synopsis, three subactions, auth resolution, daemon URL resolution, duration parsing, exit codes). Other subcommands are stubbed with a pointer to--help, to be filled in incrementally.docs/ACK-WORKFLOW.md(English) anddocs/FR/ACK-WORKFLOW-FR.md(French): new workflow page that covers both ack mechanisms (CI TOML in.perf-sentinel-acknowledgments.tomlfrom v0.5.17, daemon JSONL via the API from v0.5.20) plus a "Choosing between TOML and daemon" decision table and a note on the TOML-wins-on-conflict precedence.- 18 integration tests in
crates/sentinel-cli/tests/cli_ack.rscovering create/revoke/list across success, conflict, unauthorized (with and without env API key), service unavailable, bad request, insufficient storage, signature from stdin, ISO8601 vs relative--expires, network error, invalid URL, missing key file. The tests spawn a hand-rolled HTTP/1.1 mock server on127.0.0.1:0to match thecrates/sentinel-core/src/test_helpers.rsconvention of avoidingwiremock/httpmock. - 30+ unit tests in
crates/sentinel-cli/src/ack.rsfor the URL validator, API-key file reader (including symlink refusal, group/world-readable warning, embedded-control-char rejection), expires parser, table renderer (empty list, multi-row alignment), exit-code mapping, and theresolve_by$USERfallback. - Five unit tests in
crates/sentinel-core/src/http_client.rsforfetch_with_body: 201 success path, 409 surfaced without erroring,X-API-Keyheader attached,Content-Type: application/jsonattached, request body propagated.
Changed
build_clientandbuild_client_with_bodyshare a private generic constructor incrates/sentinel-core/src/http_client.rs. Same TLS configuration on both, no risk of rustls feature drift between the GET and POST/DELETE paths.- Helm chart
0.2.24to0.2.25,appVersion0.5.21to0.5.22, default daemon image tag points atghcr.io/robintra/perf-sentinel:0.5.22. Theartifacthub.io/imagesannotation is updated in lockstep.
Behavior
- No HTTP-shape change. The three ack endpoints (
POST/DELETE /api/findings/{signature}/ackandGET /api/acks) plus the rest of the daemon API surface keep their v0.5.21 status codes and JSON shapes byte-for-byte. - No new daemon dependency. The CLI is the only crate gaining
humantime,rpassword, and the explicithyper/bytesdev-deps. - Operator activity stays observable. Every
ack createandack revokeinvocation increments the v0.5.21perf_sentinel_ack_operations_total{action}counter on the daemon's/metrics. The smoke run for this release verifies that 3 success-pathaction=ackcalls plus 1 success-pathaction=unackcall plus 1reason=already_ackedand 1reason=not_ackedfailure match the expected/metricsdeltas. X-API-Keyvalue is flaggedsensitiveon the wire. hyper redacts sensitive header values fromDebugoutput and HPACK tables, mirroring theAuthHeader::set_sensitive(true)pattern used byfetch_getforAuthorizationheaders.--api-key-filedoes not follow symlinks on Unix. A symlinked path returns an open error before the file is read, so a path-flip race after the user types the flag cannot redirect the read to a different target.- No
--api-key <SECRET>flag by design. Passing secrets on the command line leaks them via the process list (ps,/proc/<pid>/cmdline) and shell history (~/.bash_history,~/.zsh_history). The three resolution paths (env var, file, interactive prompt) all avoid those leak vectors.
Documentation
- New
docs/CLI.mdwith a reference section for theacksubcommand and a stub for the other subcommands. The TTY-only nature of the--api-key-filepermission warning is documented explicitly so operators in CI / Docker / systemd contexts know the runtime check is suppressed and they should set the file mode declaratively (Kubernetes Secret withdefaultMode: 0o400, etc.). - New
docs/ACK-WORKFLOW.mdcovering both ack mechanisms (TOML + JSONL), the TOML-wins-on-conflict precedence, the decision table for picking the right mechanism, and a pointer to the v0.5.21 Prometheus counters for observability. - French mirrors at
docs/FR/CLI-FR.mdanddocs/FR/ACK-WORKFLOW-FR.md, same content with French accents and prose conventions.
Install
Prebuilt binaries (Linux amd64 / arm64, macOS arm64, Windows amd64):
curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.22/perf-sentinel-linux-amd64
chmod +x perf-sentinel-linux-amd64
sudo mv perf-sentinel-linux-amd64 /usr/local/bin/perf-sentinelLinux binaries are statically linked against musl and run on any distribution (Alpine, Debian, RHEL, Ubuntu any version) regardless of glibc version, and inside FROM scratch images.
From crates.io:
cargo install perf-sentinel --version 0.5.22Docker:
docker run --rm -p 4317:4317 -p 4318:4318 \
ghcr.io/robintra/perf-sentinel:0.5.22 watch --listen-address 0.0.0.0Helm chart 0.2.25 ships alongside, see the matching chart-v0.2.25 release for the chart-side details.
Full Changelog: v0.5.21...v0.5.22