Channel

Channels are proxies through which a simulation sends inputs and receives outputs from the system-under-test. For example:

channels:
  db:
    format:
      type: sql
    target:
      type: stream
      command: psql -q $DATABASE_URL
  api:
    format:
      type: template
      template: curl -sS -X {{method}} $API_BASE_URL{{path}}
    target:
      type: exec
  log:
    target:
      type: stream
      command: tail -F logs/app.log

Output

A channel may produce outputs, which could be either:

  • interactive, or explicitly caused by one input, e.g. a response to an HTTP request
  • ambient, or having ambiguous causality, e.g. an application log line

In either case, every output is added to the log and may be included in a signal.

Interactive outputs always include an input_id foreign key to the input that caused it. Currently, these will only come from channels with exec targets.

Ambient outputs come from channels with stream targets. They may come from channels that take inputs, or from read-only channels.

Target

A channel MUST specify a target, which defines how the channel sends data to and receives data from the system. There are two supported types: exec and stream.

Exec

Runs each input as a command in a sub-shell. It expects a string, and will record an error if it receives anything else. It will also record an error if the command has a non-zero exit.

Since the commands run in the shell, they can make use of features like quoting, expansions and pipes. Additionally the CLI will load the local .env, which means these commands can reference those environment variables.

The stdout of each command's sub-shell will be recorded as interactive outputs, i.e. they will include the ID of the input that triggered them.

So, for example, if API_URL and API_TOKEN were defined in .env, you could send this command to exec:

curl -s -H "Authorization: Bearer $API_TOKEN" \
    "${API_URL:-http://localhost:3000}/users" \
        | jq -r '.[].email'

Each line jq prints to stdout is recorded as an interactive output.

Stream

Runs the specified command in a sub-shell, prior to the start of a simulation. It then pipes each input to the sub-shell's stdin.

Similar to exec, the command can take advantage of shell features and reference environment variables in the local .env.

Any data received from stdout or stderr will be recorded as ambient outputs, i.e. ones that do not reference any input.

For example, you could send SQL inputs like this:

format:
  type: sql
target:
  type: stream
  command: psql -q $DATABASE_URL

Relatedly, a stream target may be used to implement a read-only channel - i.e. one that no input is routed to - since the channel will record stdout and stderr regardless of effect input.

A log tail is a good example of this:

target:
  type: stream
  command: tail -F logs/app.log

Format

A channel MAY specify a format, which defines how it will transform an input prior to sending it to the target. If no format is specified, the input's JSON-encoded data will be sent.

Template

Builds a string by running the input through the specified Handlebars template. For example, here's simple channel that sends inputs over HTTP:

format:
  type: template
  template: >-
    curl -sS --fail-with-body -X {{data.method}} "${API_BASE_URL:-http://localhost:5173}{{{data.path}}}"
    {{#if data.body}}-H 'Content-Type: application/json' --data '{{json data.body}}'{{/if}}
target:
  type: exec

The template can reference the following fields

  • effect - information about the effect that emitted the input - currently only includes key, the key of the effect
  • timestamp - the logical timestamp of the input
  • data - the input data generated by the effect's schema

SQL

Transforms an input into a SQL insert statement. For example, if an input had the following data:

{
  "id": 1,
  "name": "Alice",
  "admin": true
}

it would become:

INSERT INTO `users` (id, name, admin) VALUES (1, 'Alice', true);

The table name is taken from the table field on the associated effect's metadata. If that's not specifed, it will fallback to the effect's key.

Examples

cURL

You can use cURL as an exec target for an API channel. This example uses a template that expects the effect data to include method and path fields.

format:
  type: template
  template: curl -sS -X {{data.method}} ${API_BASE_URL:-http://localhost:5173}{{data.path}}
target:
  type: exec

PostgreSQL

Use a stream target and sql format to pipe SQL statements into psql's stdin.

format:
  type: sql
target:
  type: stream
  command: psql -q $DATABASE_URL

Log File

Use a stream target to receive signals from a log file. This is an example of a read-only channel, meaning no effects will be sent to it.

target:
  type: stream
  command: tail -F logs/app.log