Files
wayscriber/docs/daemon-protocol-v2.md
devmobasa e190a47d67 docs: state when session reports are read and what a remembered file loses
The protocol doc said reports are read only at retirement and removed
either way. It now says the visible-target guard also reads the current
report, and that nothing in a report directory that is not private is read
or removed. CONFIG.md says that a moved or deleted remembered file opens
home even when a backup or recovery copy is left beside it, unlike a
startup --session-file, and its session command list renders as one tight
list again.
2026-10-05 02:02:49 +02:00

8.1 KiB

Daemon Protocol v2 and Process Ownership

Wayscriber publishes daemon protocol v2 as one closed production mode. The CLI, config, environment, runtime files, and incoming requests cannot select a protocol implementation. The legacy implementation remains only for mixed-version parsing, the independently compiled v1 test fixture, the empty-request SIGUSR1 visibility fallback, and a source-level rollback compatibility selector.

Route map

Route Publication and wake Durable owner Terminal proof
Typed CLI request Atomic rename into commands/v2/queue and inotify ClientCommand plus caller lease Typed response digest and acknowledgement
Empty legacy visibility request SIGUSR1 only Daemon visibility intent Current-generation child state
Tray/global shortcut PID-free bounded intent plus eventfd wake Daemon controller Controller transition result
Hide ready overlay Committed command decision Daemon child owner Broker wait/reap result
Show stopped overlay Committed command decision Generation and pidfd child owner Readiness record matching PID start identity
Ready-overlay action Eligible ordered action journal entry plus SIGUSR2 Overlay event-loop action applier Applied/abandoned journal state and typed response
Start-and-deliver action Prepared action made eligible by command commit Command control plus action journal Child application or explicit indeterminate result
Anonymous tray action Eligible ordered action journal entry Action journal Applied/abandoned tombstone
Runtime helper Bounded SOCK_SEQPACKET broker request Pre-lock process broker Exit status, timeout, explicit output-overflow failure, or exec acceptance
Restart recovery Capped queue/control/journal scans New daemon generation Prior open work rejected; committed work preserved as indeterminate
Terminal collection Admission, decision, and caller-lease locks Command GC owner Response, disposition, reconciliation, report, and lease predicates

The queue-reference rename is the command-admission point. Failures before it are definite publication failures. Failures while finalizing control state or releasing locks after it are reported as admitted-indeterminate, so callers do not retry a command that the daemon may execute. Expected claim or collection lock contention defers work to the protocol's durable retry deadline instead of terminating the daemon.

The runtime record carries protocol versions, boot identity, time/PID namespace identities, process start ticks, and a CSPRNG instance token. Clients validate that identity against procfs and a pidfd before publishing work. Canonical JSON records reject unknown fields, non-canonical encoding, symlinks, oversized inputs, and cross-generation identities. Records containing v2 discriminator fields never fall back to the permissive legacy parser when strict v2 parsing fails.

Process ownership

Daemon, active-overlay, About, and explicit update-check runs create an authenticated process broker before acquiring locks or starting threads. Runtime code uses closed helper kinds with bounded arguments, environment changes, input, output, and deadlines. The broker alone creates and reaps runtime children, including the update check's allowlisted curl/wget client. The daemon retains an opaque broker handle, generation, display-only PID, and pidfd for each overlay child; tray and shortcut producers never carry a PID.

Each broker also owns an out-of-band shutdown socket. Closing or signaling that channel preempts an active bounded helper, kills its process group, and reaps owned children without waiting for the request/response exchange lock. The broker runs in its own process group so terminating an overlay group leaves the broker alive long enough to perform that cleanup. Complete helper output that exceeds its declared cap fails the operation; the explicit wl-paste prefix mode instead returns a bounded sample and stops its helper as soon as the requested prefix is complete. Screenshot helpers use a larger but still bounded 256 MiB cap so large valid compositor captures are not returned as truncated PNG data. Retained wl-copy publication uses that same cap. Failed, stalled, or shutdown-interrupted publication groups are cancelled, while the latest successful provider is tracked for replacement during the runtime. Normal broker shutdown reaps its already-terminal leader without signaling the background provider group, allowing the Wayland selection to outlive an auto-exiting overlay. Only receipt of the exact graceful-shutdown packet authorizes that handoff; channel hangup, channel error, and abnormal broker teardown still kill a retained provider group.

Overlay startup uses a CSPRNG generation passed only to the candidate. After the candidate wins the overlay singleton lock it publishes a private readiness record containing its generation, PID, and process-start identity. The daemon does not mark that child ready until all three match. Overlay exit is watched through the pidfd, while signals, tray intents, shortcut intents, and typed queue renames have owned wake descriptors; the daemon lifecycle has no periodic discovery tick.

The enforced process-site inventory is tests/repository_guards/process_sites.rs. Direct process creation is limited to the broker, pre-runtime systemd setup, the separate configurator process, standalone About clipboard integration, and named test fixtures. The same check audits the raw-clone child stub: before execve it may reach only the fixed fcntl, dup3, setpgid, close_range, execve, and exit_group syscall set over prebuilt buffers.

Overlay session reports

A daemon launch passes three optional environment variables, so an older overlay ignores them: WAYSCRIBER_OVERLAY_SESSION_REPORTS=1 says the daemon reads session reports, WAYSCRIBER_OVERLAY_HOME_SESSION names its startup session file, and WAYSCRIBER_OVERLAY_PREFERRED_SESSION names the session it remembers, omitted when the request carried its own --session-file. The command line is unchanged. The broker strips these variables from helpers that do not relaunch wayscriber.

An overlay with a published child identity reports its session in daemon-commands/overlay-targets/<generation>.target, a private sibling of v2/, never inside it. The canonical JSON record carries a schema version, the generation, PID and process-start identity, and a target: an absolute session file, or null for home. It is replaced on each change. When the child is retired, on exit, stop or forced reap, the daemon reads the report before it releases the child's identity and removes it, along with any temporary its writer left. While the child runs, the visible-target guard reads the current report without removing it. Either read accepts a report only from a private regular file in a real private directory, with exactly the identity captured at readiness; anything else is ignored. If the directory itself is not private, nothing in it is read or removed. The remembered session lives only in daemon memory; startup removes reports an earlier daemon left without restoring them.

Compatibility and rollback

  • A v2 client against a v1 daemon uses the strict legacy parser and v1 request path.
  • A frozen v1 client against a v2 daemon rejects typed requests because no v1 token exists. Its explicitly empty visibility signal remains supported.
  • V1 cleanup removes only exact v1 request/response artifacts and never recursively removes the v2 root.
  • Session reports sit outside the strict v2 tree, and the legacy request scan reads only plain files in daemon-commands/, so reports left behind never reach an older daemon's parsers.
  • Restart recovery rejects prior-generation open commands with a durable no-effect response and records authorized commands without terminal proof as indeterminate. Foreign-generation journal entries are abandoned rather than replayed.
  • Rollback is a source-level release decision using the v2-aware compatibility selector. It is not user-selectable and must preserve the complete versioned v2 tree; an arbitrary pre-v2 binary is not a supported rollback target.