Send client logs to the host — the log-escape hatch for locked-down platforms #247

Merged
enricobuehler merged 1 commits from worktree-client-log-upload into main 2026-08-15 10:57:19 +00:00
1 Commits
Author SHA1 Message Date
enricobuehler 5ae08ffaa5 feat: send client logs to the host — the log-escape hatch for locked-down platforms
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m15s
android / android (pull_request) Successful in 5m38s
ci / rust (pull_request) Failing after 5m11s
ci / bun-nix (pull_request) Successful in 43s
ci / docs-site (pull_request) Successful in 2m8s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m39s
ci / rust-arm64 (pull_request) Successful in 12m57s
ci / web (pull_request) Successful in 10m37s
On a Steam Deck in Gaming Mode, a tvOS box or a webOS TV the user has no
realistic way to get the client's log off the device, so field reports
arrive host-log-only and the client half of every stutter story is
invisible (today's Skynet analysis was the third such report in two
days). This inverts the collection: an explicit action on the client
posts its recent log to the PAIRED host, and the host's web console
lists the bundle next to the host's own log export — both halves of a
report in one place, from the one surface reporters already use.

Host: crate::client_logs is a bounded file store under
<config-dir>/client-logs (traversal-proof ids per the stats_recorder
pattern, newest 5 bundles per device, 1 MiB cap), NOT the log ring — a
multi-thousand-line bundle would evict the host's own log there. POST
/api/v1/client-logs is the cert lane's first and only WRITE: paired
mTLS devices may upload (write-only — no read of anything, not even
their own bundle), while list/fetch/delete stay on the loopback bearer
lane; the lane matrix rows pin all four. Per-client access (#245)
integration: the upload gate uses effective() — an expired guest's
upload is refused (403) while any live-authorized device, including
view-only guests, may send; no input-grant bit is required since
uploading one's own diagnostics is not an input capability.

Client: pf_client_core::logring keeps the newest 4096 lines / 768 KiB
(dependency-free ring; each shell installs a thin tracing layer — the
session binary's ring_layer captures DEBUG+ regardless of RUST_LOG,
wall-clock stamped so bundles correlate with the host log). send_to_host
reuses the library fetch's mTLS agent + host pin and the same error
classification. The gamepad console gains "Send logs to host" in the
per-host menu (paired + online rows only), a ConsoleCmd worker thread,
and a shared-model notice channel so the result lands as a toast.

Web console: a Client logs card on the Logs page (list/download/delete,
hidden while empty), en+de messages, api/openapi.json regenerated.

Apple (tvOS/iOS/macOS), Android and webOS legs are follow-ups: each
already holds a paired identity and the mgmt port from the Welcome, so
they only need a ring + one authenticated POST.

Gates: mgmt+client_logs 57/57 and full host suite green on Ubuntu 26.04
(the one failure, hooks::prep_runs_do_in_order_and_undo_in_reverse,
fails identically on pristine origin/main — pre-existing); clippy clean
across punktfunk-host, pf-client-core, pf-console-ui; session binary
checks; web build + tsc + i18n clean; pf-client-core logring test run
in the amd64 container.
2026-08-15 12:32:20 +02:00