Skip to content

plugin-cstream

Placement runtime (out-of-process over gRPC)
Source github.com/opencharly/plugin-cstream/candy/plugin-cstream
Version 2026.242.1500
Candy plugin-cstream

This plugin is not listed in charly/charly.yml’s compiled_plugins:. It is not part of the shipped binary: charly builds and loads it out-of-process over gRPC when a plan references one of its words (the coexist path).

The reserved words this plugin serves:

  • cstream — verb class

OUT-OF-TREE charly plugin serving the cstream check verb — the probes that can only be answered from the STREAM side of a cstream deployment, served OUT-OF-PROCESS over go-plugin gRPC via the charly plugin SDK.

Why a verb of its own rather than methods on wl:. screenshot already exists on four verbs (cdp, wl, vnc, spice), each asserting its own protocol’s view of the pixels, and that duplication is deliberate: a probe is worth having only if it can fail on the defect it exists to catch. wl: screenshot reads the COMPOSITOR, so it passes cleanly while the encoder is misconfigured or the transport is dead. cstream: frame pulls a frame back out THROUGH the negotiated WebRTC track, so encoding, negotiation and transport are all in its failure path. Folding it into wl: would produce a probe that cannot fail on the bug it was written for.

SCOPE IS DELIBERATELY NARROW — three methods, not the eleven the architecture eventually wants. status, frame and login are served because the deployment can actually answer them today. session-list, stats, volume, clipboard, idle, input and logout are NOT: the streamer has no control surface behind them yet, and a method that cannot be exercised reads as coverage while providing none. They arrive with the ctl socket.

The plugin owns no podman/venue machinery. It uses BOTH reverse legs because the two halves of the deployment are reachable differently: ResolveEndpoint for the gateway’s HTTP surface, and ExecutorFromInvoke for anything that must run inside the venue (the WebRTC consumer pull, and authenticating through the session leader).

The CUE schema below is the authoritative grammar for this plugin’s input. It is the same single source that generates the plugin’s Go parameter types and answers the runtime Describe RPC, so this page cannot disagree with either.

// The `cstream` verb's OWN CUE schema — the typed plugin_input for a `cstream:`
// check step. Single source: it generates ../params/cue_types_gen.go AND is
// served over Describe so the host validates every authored step against it.
//
// SELF-CONTAINED: every field is a bare primitive referencing NO base def, so it
// compiles standalone and splices onto the base.
//
// FIELD SPLIT: only cstream-EXCLUSIVE fields live here. The matchers
// exit_status/stdout/stderr and the general modifiers timeout/eventually stay on
// #Op and are read off the step by the runner — they are NOT reproduced here.
#CstreamInput: {
// method — what to ask the stream.
//
// The set is deliberately small. Each method is here because it can FAIL on a
// defect nothing else catches:
//
// status the gateway's own readiness view -- it reports ready only when a
// DRM render node exists AND the compositor has published a socket,
// which is strictly more than "the port answers"
// login one PAM authentication through the real session leader
//
// frame a frame pulled back THROUGH the negotiated WebRTC track. This is
// the method the verb ultimately exists for: `wl: screenshot` reads
// the COMPOSITOR and `stream-probe` reads the producer's own tap, so
// neither can fail on an encoding or transport defect. This one
// decodes what a real consumer receives, so it fails on a broken
// encoder, a broken negotiation, or a broken transport.
//
// `frame` was deliberately withheld until it could be gated. The defect that
// kept it unshippable is on record and fixed: gst-plugins-bad was missing, so
// webrtcbin did not exist and webrtcsink failed to build its session pipeline
// with a WARN -- sessions negotiated, no media followed, and no ICE or DTLS
// error surfaced. Producer-side checks all passed throughout.
//
// Methods the architecture also lists (session-list, stats, volume, clipboard,
// idle, input, logout) are NOT served yet: the control surface they read does
// not exist in the streamer, and a method that cannot be exercised reads as
// coverage while providing none.
method: "status" | "login" | "frame"
// user / password — `login` only.
//
// ⚠️ THIS PASSWORD IS NOT PRIVATE. The venue executor exposes only
// VenueCapture(ctx, cmd string) — there is no stdin channel to an
// out-of-process verb — so the value reaches the venue inside a command
// string and lands in `sh -c` argv, which is world-readable through
// /proc/<pid>/cmdline for the life of that exec.
//
// So `cstream: login` is for FIXTURE credentials: an account a disposable bed
// created in order to be authenticated. Do not point it at a real one.
//
// The production path does not have this property and must not grow it: the
// broker (root) execs the leader over a socketpair and the leader reads the
// credential from STDIN, never argv. That discipline is deliberate and is
// tested; this verb simply cannot reach it through the executor it is given.
user?: string
password?: string
// expect — `login` only: whether this credential SHOULD be accepted.
// A login probe that only ever checks the correct password passes just as
// happily against a stack that accepts anything, so the rejecting case has to
// be expressible.
expect?: "accept" | "reject"
// artifact — `frame` only: the HOST path the decoded frame is written to.
//
// The frame is captured in the venue, pulled back over the executor's reverse
// channel (GetFile) and written here BEFORE the provider's artifact validators
// run, so artifact_min_bytes / artifact_not_uniform gate the bytes a consumer
// actually received rather than anything the producer merely claims.
//
// Without a gate on the CONTENT this method would be theatre: a stream that
// negotiates and sends nothing, or sends a uniform placeholder, is exactly the
// failure mode it exists to catch.
artifact?: string
// The artifact VALIDATORS. These are not general #Op modifiers -- they left
// core #Op in the schema-compaction cutover, so every verb that writes an
// artifact declares its own (plugin-wl carries the same four). Without them
// declared here an authored step fails host validation with
// `#CstreamInput.artifact_min_bytes: field not allowed`, which is how this
// omission was found.
//
// artifact_not_uniform is the one that matters: a stream that negotiates and
// then sends a black or placeholder frame is precisely the failure this method
// exists to catch, and a byte-count alone would pass it.
artifact_min_bytes?: int & >=0 @go(ArtifactMinBytes,type=int)
artifact_min_dimensions?: string & =~"^[0-9]+x[0-9]+$" @go(ArtifactMinDimensions)
artifact_not_uniform?: bool @go(ArtifactNotUniform)
}

See also the candy reference for this candy’s install surface.