# Product card adapter: host implementation proof Recorded 2026-09-23T07:09Z–07:12Z. Branch `impl/card-shell-adapter`, worktree `k230-card-shell-adapter`, implementation base `f655a2804208` (probe merge `aaff330`, accepted contract `af5f2e8`, policy `237be7b` + `c2ece6a` cherry-picked). The root coordinator owns the board; this work used no board or UART. Evidence class: **actual cross-built Sway 1.12 / wlroots 0.20.2, headless Pixman under QEMU user emulation, real native XDG/SHM clients, explicitly injected input and virtual keyboard protocol events**. This is not panel, real finger, OSK, normal service restoration, or board performance acceptance. ## Build and closure ``` nix build .#card-shell --max-jobs 1 --cores 8 --no-link --print-out-paths ``` PASS, final output `/nix/store/vv68a0gg1wv63dnvx58q1a0x9pihh402-k230-card-shell`. The wrapper supplies D-Bus/coreutils/bash PATH entries for transient sessions. Unwrapped compositor: `/nix/store/cr22m8r2rq2h89mjxk1pv4sya1p1ha3s-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway`. `closure.json` records `nix path-info --recursive --json` for this package and the unchanged normal system `/nix/store/nz82q373yj1hp2k4qm85xa3c328jli8j-nixos-system-nixos-26.11.20260919.20b1ddd`. Only five store paths are additional; all shared libraries already belong to the normal system. These are NAR closure bytes, not runtime RAM or compressed transfer size. No system-image dependency or default shell changed. ## Actual runtime and named gates ``` export CARD_SHELL_SWAY=/nix/store/cr22m8r2rq2h89mjxk1pv4sya1p1ha3s-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway export CARD_SHELL_CLIENT=/nix/store/6nskh3ldk3ya7mwxbb258qszrxhw2i61-card-composition-probe-client-0.1/bin/card-composition-probe-client python3 tests/test_card_shell_composition.py python3 tests/test_card_shell_gestures.py python3 tests/test_card_shell_recovery.py python3 tests/test_card_shell_state.py python3 tests/test_touch_menu.py ``` All PASS: three named actual-compositor suites, exact-disabled opt-in check, 21 compiled ASan/UBSan policy cases, and seven existing touch-menu tests. `TMPDIR=/mnt/MediaVolume/card-tests` was used after the host `/tmp` user quota filled; this only changes temporary test artifact placement. The longer producer/parser run used: ``` python3 tests/card_shell_runtime.py --sway "$CARD_SHELL_SWAY" \ --client "$CARD_SHELL_CLIENT" --benchmark \ --output /mnt/MediaVolume/card-tests/final ``` `result.json` lists sixteen verified behaviors. Assertions include both live parent and independently committing child frame counts advancing while both cards are actually visible, pixel changes during direct drag, tap expansion and virtual-key delivery, upward throw generating an actual XDG close request, refusal retaining the client until separately labeled timeout, accepted close exiting another client, private/unavailable uniform placeholder pixels, privacy changes during active composition, three mapped stable view IDs, top-bar restoration, no-up stream cancellation, two-contact draining, edge/button entry, output-loss restoration, and a clean compositor exit. Fully offscreen mirrors correctly receive no presentation/frame feedback. A teardown regression found during development was fixed before this record: output disable now restores routing synchronously before Sway evacuates its workspaces. Render/timer checks cannot substitute for this hook because a zero-output state stops rendering. The tests assert restoration before output re-enable and a zero exit status after clients, keyboard, and compositor close. `two-live.png`, `during-drag.png`, `private.png`, and `unavailable.png` are actual headless captures of synthetic fixtures. No physical panel is shown. Exported compositor lines contain only the patch's fixed-schema IDs, counters and states; client logs contain synthetic fixture data and key counts, never key contents. The normal Sway debug journal is not committed. ## Benchmark integration result: acceptance remains open ``` python3 tools/card-shell-benchmark.py \ --input docs/evidence/card-shell/headless/benchmark.txt \ --output docs/evidence/card-shell/headless/benchmark.json ``` Parser implementation `d3ffbd7` consumed the actual telemetry without schema or correlation errors: 107 inputs map to 104 successful commits, including three coalesced inputs; no unresolved input/commit/presentation IDs. Same two mapped apps span baseline, active deck, and restored resource phases. Entry header follows earlier baseline samples. Submission is timestamped before commit dispatch and logged only on success; actual presentation uses backend feedback. Nested CPU scopes include input, render, prepare and timer reconciliation once. **Exit 1 / diagnostic FAIL is preserved, not waived:** headless tracking frame interval p95 was 52.16033 ms against the provisional 33.334 ms limit. Isolated session memory is INCOMPLETE because this host run did not claim a dedicated cgroup. The report explicitly says `NOT_BOARD_EVIDENCE`; passing individual host timing metrics does not establish K230 performance. Root must run the separately declared board benchmark, including total isolated-session memory, actual RGB565 output and physical/injected provenance, before cost acceptance. ## Remaining work Product image integration/QEMU boot, every physical control/keyboard route, board injected acceptance, optical/real-finger trial, and measured Pixman cost remain UNVERIFIED. The architecture probe has its own independent board gate. This product package is opt-in and has no product board-session harness yet; operator session/rollback integration must be reviewed before deployment. After an isolated session is installed, arm `card_shell benchmark physical` or `injected`, retain the same apps for >=3-second baseline/active/restored phases, then collect normalized telemetry and run the committed benchmark command with `--board` and its required provenance manifest. Do not reuse the architecture probe's package identity as product deployment evidence. ## Popup and native-input review correction A follow-up to the initial implementation adds an explicit unsupported-popup boundary. Pinned Sway creates XDG popups under `root->layers.popup`, outside `view->content_tree`; they cannot safely be treated as mirrored descendants. Visible popups now reject card entry and restore an active deck before render. The actual XDG popup fixture maps a private parent, opens its popup during card mode, observes normal restoration, verifies entry rejection, then closes the popup and successfully enters again. `boundary-result.json` records seventeen passing assertions and clean compositor teardown; `boundary-compositor.txt` contains its normalized patch events. The fixture is compiled from committed C. The same correction gates new input while the launcher/hidden card UI or an existing app touch sequence owns the surface. A second contact while cards own the first is consumed before top-bar/keyboard bypass, and both contacts drain; this prevents forwarding a down whose up would later be consumed by cards. The separate `card_touch_receiver` review fixture validates actual protocol pairing through a registered wlroots touch device. Its evidence is a separate reviewed deliverable; direct card IPC alone does not prove client event pairing. Correction build PASS: `/nix/store/c41z2czaiana4dh8a7a2w6pwrqkawdgj-k230-card-shell`, unwrapped Sway `/nix/store/x95yrya49qds0qzrxfyqp000b01pggsp-sway-unwrapped-riscv64-unknown-linux-gnu-1.12/bin/sway`. `boundary-closure.json` records its delta from the same unchanged normal system. The headless-only test device requires exact `SWAY_K230_CARD_TEST_INPUT=1` and is never enabled by the package wrapper or on a DRM backend. The original benchmark report remains an honest failed host diagnostic; this correction makes no board-cost or product deployment claim. The complete seventeen-assertion runtime also PASSes with `--native-touch` on the correction executable. `native-input-result.json` records `wlroots-touch-device-through-cursor-and-seat`; every down/motion/up/cancel travels through the real registered device, rather than direct card handlers. The input device is removed by normal compositor shutdown and exit remains zero. Client-level launcher/bar touch pairing is still independently checked by the companion routing fixture; these are complementary evidence classes.