# `control-channel` _(no submodules)_ ## Purpose Bidirectional control plane: the main process tells a worker-isolated plugin to stop or resume emitting log records. The host calls `disable` during plugin shutdown so a worker that is still draining its work queue does not keep producing records after the main-side sink has been torn down (design doc §5.5 — components dispose in reverse dependency order). Without this channel the main-side sink would either log to a zombie sink (silent loss with no diagnostic) or panic on a closed port. Frames are simple: `{ kind: "control", action: "disable" | "enable" }`. There is no acknowledgement frame; the channel is fire-and-forget. The worker maintains a shared `ControlState` object the proxy client checks synchronously on every log call, so a `disable` received between two calls correctly suppresses the second. ## Features - `createControlChannelSender(port)` — returns `{ disable, enable }` thunks usable from the main process. - `mountControlChannelReceiver(port, options?)` — installs a listener on the worker-side port; returns the shared `ControlState` plus a `dispose` thunk. Tolerates malformed frames (silent drop) so a hostile or buggy host cannot crash the worker. - `onChange` callback fires only on real transitions, not on repeated same-value frames. ## Usage: Basic ```ts // Main side import { createControlChannelSender } from "@suaveplan/api-logger-worker-proxy/control-channel"; const ctl = createControlChannelSender(mainPort); ctl.disable(); // ... main-side sink torn down ... worker.terminate(); ``` ```ts // Worker side import { mountControlChannelReceiver } from "@suaveplan/api-logger-worker-proxy/control-channel"; const ctl = mountControlChannelReceiver(workerPort); // log.info() etc. read ctl.state.enabled synchronously ``` ## Usage: Advanced Observe transitions to flush in-flight batches and surface a counter: ```ts import { mountControlChannelReceiver } from "@suaveplan/api-logger-worker-proxy/control-channel"; const ctl = mountControlChannelReceiver(port, { onChange: (enabled) => { if (!enabled) batcher.flush(); meter.add(`logger_proxy.control_state.${enabled ? "on" : "off"}`, 1); }, }); ``` ## API Reference - `createControlChannelSender(port: PortLike): ControlChannelSender` — emits `{ v, kind: "control", action }` frames. - `mountControlChannelReceiver(port, options?)` — returns `{ state: Readonly, dispose }`. - `ControlState` — `{ enabled: boolean }`. Defaults to `true` so workers emit normally until told otherwise. ## Implementation Notes The receiver shares the port with `goodbye` and (incidentally) with the worker-side log emit path — frames whose `kind` is not `"control"` are ignored so the receiver composes cleanly with other listeners on the same port. The state object is mutated in place (not replaced) so the proxy client can capture a stable reference at construction time and not need to re-resolve a getter on the per-call hot path.