# Experimental Remote keyboard IPC prerequisite The protocol 1.1 host recognizes `remote.key`, with public Go types and matching Python validation. A bounded incoming-key mailbox is attached in production for sessions installed with `keyboard-inject`, and the external Remote command forwards controller keys when keyboard control is approved for the session. See the production attachment section below; live acceptance is pending. ## Wire contract One child-to-host request carries one logical transition: ```json {"lease":"0123456789abcdef0123456789abcdef","epoch":1,"sequence":2, "key":{"name":"Control_R","pressed":false}} ``` All fields are required, including boolean `pressed` on release. Field names are exact; unknown fields, duplicate keys, nulls, missing values, nonboolean press state and invalid numeric identities are rejected. Epoch and sequence are positive uint64 values. Lease identifiers have the existing 32-byte bound; only the actual token issued to this process can authorize work. `name` identifies one canonical unshifted logical key. It is neither text to type nor an X keycode, Windows VK/scan code, script path or command. The bounded allowlist matches the external mapper's canonical subset: - Lowercase Latin letter names, top-row digits, F1–F24 and KP_0–KP_9. - Left/right Shift, Control, Alt and Super; Menu, lock keys, Pause and Print. - Backspace, Tab, Return, Escape and Space; main navigation, Insert and Delete. - Keypad Enter, navigation, Begin, Insert, Delete and arithmetic/separator keys. - Eighteen browser, volume, media and launch keys (`XF86Back`, `XF86Forward`, `XF86Reload`, `XF86Stop`, `XF86Search`, `XF86Favorites`, `XF86HomePage`, `XF86AudioMute`, `XF86AudioLowerVolume`, `XF86AudioRaiseVolume`, `XF86AudioNext`, `XF86AudioPrev`, `XF86AudioStop`, `XF86AudioPlay`, `XF86Mail`, `XF86AudioMedia`, `XF86MyComputer`, `XF86Calculator`), matching Windows VK 0xA6–0xB7. Other XF86 keys, such as power and eject, stay unsupported. - The eleven explicit punctuation identities supported by the external US profile. The owner must resolve the destination layout; this does not grant permission to guess a matching physical key or change the global keymap. Aliases such as `Prior`, uppercase text, shifted character names, AltGr, dead/IME keys, arbitrary Unicode and unrecognized names remain unsupported. For exact case/spelling, see `input.ValidRemoteKeyName` in `internal/input/remote_key_names.go`, used by `ValidRemoteKey` in the wire/SDK layer. No implicit modifier transitions, text insertion or fallback commands are synthesized. Go exposes `RemoteKey`, `RemoteKeyRequest`, `ValidRemoteKey` and `ValidRemoteKeyRequest` through `sdk/go/tentacle`. Python exposes `valid_remote_key` and `valid_remote_key_request` from `cthulhu_tentacle.session`. They validate representation, not consent or native availability. Generic `SessionClient.Call`/`call` still permits method-specific host errors; callers may use the validators before submitting work. Older hosts return unsupported-operation for this addition. ## Authorization and admission The host requires the current process session's live controlled-role lease and the separately approved `keyboard-inject` grant. Installation grants, observer consent, controller role, relay membership and a child's claimed origin cannot authorize injection. Parameters contain no local-origin or consent override. `remote.key` shares the strictly increasing operation sequence with heartbeat, close and other lease operations. An accepted key advances it without renewing the heartbeat deadline. Replayed, foreign, expired or cancelled operations are denied. Invalid parameters and missing-owner errors do not consume the lease sequence or enqueue work. `Session.EnableRemoteKeys` is a reader API, not a child RPC. It attaches one 256-item mailbox. Retained queued plus in-flight keys are bounded by 256 items and 256 KiB of canonical encoded request bytes, including lease/sequence metadata. Receiving a delivery does not return capacity. Its idempotent `Release` returns capacity after delivery or discard, only to the original lease. A release from a retired worker cannot alter replacement accounting. The RPC result `{}` acknowledges mailbox admission only. It does not acknowledge XTEST, application delivery or key recovery. Saturation revokes the lease and drains pending work rather than dropping a key-up and continuing. Heartbeat, revocation and shutdown do not wait for a native worker. ## Ownership and retirement `RemoteKeyDelivery` retains the original lease, operation sequence, typed key and cancellation context. A future input owner must check that context and `Session.RemoteContext(lease, "keyboard-inject")` immediately before applying each effect, alongside current focus, lock/handoff and native policy. Context alone does not replace the deadline check. No native callback is invoked while the host session mutex is held. Revoke, explicit close, replacement, expiry, policy cancellation, disable and session failure cancel accepted work and clear pending keys. Local policy cancellation invalidates the delivery context synchronously, before asynchronous notification/drain callbacks acquire the session lock. Restoring availability never revives the original lease. A new explicit approval creates a new epoch. The native owner must keep its own destination key ledger, resolve repeats and lock state, reject physical/injected collisions and guarantee cleanup even if the Tentacle, reader or network disappears. Cleanup must not depend on this mailbox admitting a last up. The external outgoing encoder's transport write acknowledgements and this API's mailbox admission are separate contracts; neither establishes actual destination input completion. ## Verification and remaining gates Strict representation tests compare Go and Python validators, including both transitions, all letter/digit/function ranges, uint64 boundaries, missing/null values, exact field names and rejected metadata. Real Go/Python SDK child processes exercise owner-unavailable, admission, permission/role/process checks, shared heartbeat ordering, replay, malformed requests, capacity retention, concurrent idempotent release, overflow and retirement/replacement. The external bridge verifies that every canonical mapper result satisfies the public SDK contract. None of these tests injects a desktop key. An unconnected [destination-key prerequisite](testing/remote-key-target.md) now retains native codes through layout changes and uncertain effects, with a quarantined Xorg resolver/backend. It is not attached to this mailbox and supplies no physical collision guarantee. A subsequent [source-built helper](testing/remote-input-helper.md) now supplies private process-lifetime/EOF/watchdog cleanup. A quarantined reader-side Supervisor now launches it with serialized heartbeats and an original-epoch cleanup/process-exit barrier; unconfirmed retirement blocks replacement. The quarantined `app.run_remote_keyboard` worker now joins this supervisor to the authenticated mailbox with a required policy callback; see [its lifecycle and native evidence](testing/remote-input-helper.md#quarantined-leasemailbox-worker). `start_remote_keyboard` is now its production caller; see below. No capture event API, production Xorg input owner or supervised helper attachment, core escape/bypass integration or installed Remote keyboard command is provided. Physical-source approval, physical collision/recovery, audible speech, Braille and real NVDA/Orca interoperability remain unverified. Private IPC and relay checks close no live release gate. This increment passed the full wire/host/both-SDK race suites (host: about 128 seconds), focused application Remote connection/speech race tests, the existing speech regression harness, affected-package vet, a separate reader build and Tentacles `make check`. The lifecycle regression now checks malformed typed keys and keeps its unsupported-operation check on Braille. No full reader suite or native/hardware acceptance was performed; no active reader was replaced. ## Production attachment (2026-11-06) Local launch and managed-session startup now call `start_remote_keyboard` for every session installed with `keyboard-inject`, next to the speech owner. It attaches the mailbox before the child becomes active and runs the existing lease/mailbox worker with one `remoteinput.Supervisor` retained for the reader lifetime, so the uncertainty latch survives session replacement. The helper is `cthulhu-input-helper` beside the reader executable; `build-cthulhu.sh` and both Arch packages now build and install it. A missing helper or a session without the grant leaves `remote.key` owner-unavailable and does not affect startup. Delivery-time policy adds reader lock, XTerm-handoff suspension and active internal reader UI to the worker's lease, grant and deadline checks; a denied key revokes the original lease. A clean helper failure retires only that lease and is announced; unconfirmed cleanup is announced as requiring a reader restart. Real SDK-process tests cover delivery and lock-policy retirement, handoff denial before helper launch, and the no-grant and missing-helper cases. This makes injection reachable from an approved controlled session. It does not add physical-collision safety, injection provenance, lock/AltGr policy or live acceptance, which remain required before keyboard control is declared ready.