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
Owner

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 report was the third such case 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 on the one page reporters already use.

Hostcrate::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), deliberately 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 exhaustive lane matrix pins all four routes.

Per-client access (#245) integration — the upload gate uses effective(), not the (deliberately expiry-blind) is_paired: an expired guest's upload is refused with 403, while any live-authorized device — including a view-only guest mid-session, exactly who a debug bundle is wanted from — may send. No input-grant bit is required: uploading one's own diagnostics is not an input capability.

Clientpf_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 learns the mgmt port from the Welcome, so each reduces to a ring + one authenticated POST.

Gates — mgmt + client_logs 57/57 and the full host suite green on Ubuntu 26.04; clippy clean across punktfunk-host, pf-client-core, pf-console-ui; session binary checks; web build + tsc + i18n clean; the logring test run in the amd64 container. ⚠ One pre-existing failure surfaced while gating: current main fails hooks::tests::prep_runs_do_in_order_and_undo_in_reverse (verified on a pristine git archive origin/main on Ubuntu 26.04) — unrelated to this branch, but worth a look after the #244/#245 merges.

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 report was the third such case 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 on the one page 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), deliberately **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 exhaustive lane matrix pins all four routes. **Per-client access (#245) integration** — the upload gate uses `effective()`, not the (deliberately expiry-blind) `is_paired`: an expired guest's upload is refused with 403, while any live-authorized device — including a view-only guest mid-session, exactly who a debug bundle is wanted from — may send. No input-grant bit is required: 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 learns the mgmt port from the `Welcome`, so each reduces to a ring + one authenticated POST. **Gates** — mgmt + client_logs 57/57 and the full host suite green on Ubuntu 26.04; clippy clean across `punktfunk-host`, `pf-client-core`, `pf-console-ui`; session binary checks; web build + tsc + i18n clean; the logring test run in the amd64 container. ⚠ One pre-existing failure surfaced while gating: **current main fails `hooks::tests::prep_runs_do_in_order_and_undo_in_reverse`** (verified on a pristine `git archive origin/main` on Ubuntu 26.04) — unrelated to this branch, but worth a look after the #244/#245 merges.
enricobuehler added 1 commit 2026-08-15 10:50:58 +00:00
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
5ae08ffaa5
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.
enricobuehler merged commit 42a257e013 into main 2026-08-15 10:57:19 +00:00
enricobuehler deleted branch worktree-client-log-upload 2026-08-15 10:57:21 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#247