Repository navigation
Echo Conformance
A connector is a small program inside someone else's site, so a bug in one can lose a message or let it out. Echo's conformance kit checks a connector against the protocol it speaks: rhythm echo conformance runs a strict stand-in Echo that checks every capture against the wire schema. The repository holds shared test vectors that the WordPress, Laravel and Node connectors all run. This page covers the stand-in, the scenarios it can expect, its report and the vectors; the protocol itself is in API.
rhythm echo conformance --expect email,run,http-before # then point the connector's end-to-end test at itThe stand-in answers like Echo (the same decisions, fakes and errors). It checks each POST /v1/capture against the wire schema first. A bad capture gets 400 with every problem and never reaches its inbox, so a connector's own end-to-end test fails on a protocol bug instead of passing silently.
rhythm echo conformance --listen 127.0.0.1:28025 --expect email,run,http-beforerhythm echo conformance: stand-in Echo on http://127.0.0.1:28025 (seal, wire 2)
A good capture is answered as Echo would:
curl -s -H "Content-Type: application/json" -d @good.json http://127.0.0.1:28025/v1/capture{"id":"52b3ee51758a","mode":"seal","action":"fake"}A capture with a field the protocol doesn't have is refused:
curl -s -H "Content-Type: application/json" -d @bad.json http://127.0.0.1:28025/v1/captureconformance: capture.http.header: not part of the wire protocol
Stop it with Ctrl+C or SIGTERM and it prints its verdict (exit 1 when any check failed, 0 when all passed):
CHECK RESULT DETAIL
schema FAIL 1 of 2 captures broke the wire schema: capture.http.header: not part of the wire protocol
email PASS 1: an email reported before it is sent
http-before FAIL expected an HTTP request reported before it is sent, saw none
run PASS 1: a capture tied to a run
wire-2 INFO 1: a capture in wire 2
refused capture 1:
capture.http.header: not part of the wire protocol
FAIL
A run where no capture arrived at all fails with captures FAIL no captures arrived; one where every capture was valid and nothing was expected ends PASS (exit 0).
GET /v1/conformance returns the report so far as JSON, for a test that checks it before stopping the stand-in:
{"ok":false,"captures":2,"rows":[{"check":"schema","result":"fail","detail":"1 of 2 captures broke the wire schema: capture.http.header: not part of the wire protocol"},{"check":"email","result":"pass","detail":"1: an email reported before it is sent"},{"check":"http-before","result":"fail","detail":"expected an HTTP request reported before it is sent, saw none"},{"check":"run","result":"pass","detail":"1: a capture tied to a run"},{"check":"wire-2","result":"info","detail":"1: a capture in wire 2"}],"problems":[…]}| Flag | Default | Does |
|---|---|---|
--listen <addr> |
127.0.0.1:8025 |
The API address. |
--smtp <addr> |
off | Also run Echo's SMTP sink there. |
--mode <m> |
seal |
seal or observe: what the stand-in decides. |
--token <t> |
Require this X-Echo-Token. |
|
--allow <hosts> |
Hosts reachable when sealed, comma separated. | |
--wire <n> |
2 |
Speak this wire version, to check a connector falls back to an older Echo. |
--expect <list> |
Scenarios the run must include, comma separated (below). | |
--json |
Print the report as JSON instead of a table. |
Exit codes: 0 every check passed, 1 a check failed, 2 usage error, 3 couldn't listen (rhythm echo conformance: listen tcp 127.0.0.1:1: bind: permission denied).
--expect |
The run must include |
|---|---|
email |
an email reported before it's sent |
email-after |
an email's outcome reported after a real send (observe mode) |
http-before |
an HTTP request reported before it's sent |
http-after |
an HTTP request's outcome reported after it was sent |
run |
a capture tied to a run (X-Echo-Run or RHYTHM_ECHO_RUN) |
site |
a capture naming its hub site (source.site) |
attachments |
an email listing its attachments |
wire-1, wire-2
|
a capture in that wire version |
The schema row is always there. With --wire 1, the stand-in's health says "wire": 1 and a wire 2 capture gets 400 with this connector is newer than Echo; run: rhythm update, the words a connector falls back on; the fallback row then says whether it did (refused 1 newer capture, then got wire 1).
The protocol is wire version 2 (Echo still accepts 1). Wire 2 adds source.site (the site's ID on an agency hub) and email.attachments, each file's name, type, size and SHA-256 for connectors that send no raw MIME. The schema is JSON Schema (draft 2020-12), strict: an unknown field is refused, so a typo shows. It's in the Echo repository at wire/schema.json. The stand-in's problems read like missing (required), not part of the wire protocol, "sms" is not one of email, http, 3 is above 2, the newest this Echo speaks and new in wire 2 while this capture says v 1: leave it out for an older Echo.
An Echo that receives a newer wire than it speaks answers 400 with this connector is newer than Echo; run: rhythm update (it speaks wire 3, this Echo wire 2 at most). The connectors then ask /v1/health, send the older version and log it once ([rhythm-echo] Echo at <url> speaks wire 1: sending that (rhythm update gets a newer Echo)). They try the newer one again after an hour.
conformance/testdata holds the cases every connector's suite reads:
| Files | Cover |
|---|---|
capture-*.json |
valid captures and the decision Echo gives each: a sealed email, an observed one, a faked HTTP call, the site's own host, wire 1, wire 2 with a site and attachments |
broken-*.json |
captures the schema must refuse, with the problems expected |
fail-closed-*.json, fail-open-observe.json
|
what a connector must do when Echo is unreachable, answers 5xx or times out: refuse to send while sealed, send in observe mode |
run-signature.json |
signed X-Echo-Run values: which ones verify, which a connector must ignore, the 512-byte limit |
A connector for another stack can run the same cases. The repository's conformance/README.md describes the format.
← MCP server · Home · CI →
rhythm · Useful ideas. Working systems. · Check only sites you own or have permission to test. Keep staging sealed (responsible use) · This wiki is published from wiki/ in the rhythm repository; suggest changes there with a pull request.
Get started
rhythm in depth
Tuner
- Overview
- Your first scan
- Reading results
- Choosing checks
- Requests and politeness
- Output and reports
- Batch scans
- Local scans
- Integrity and malware
- Changes and notifications
Tuner in depth
- Known vulnerabilities
- PHP advisories
- CI and code scanning
- GitHub Action
- Config file
- Watch and serve
- Writing checks
- Check packs
- Finding reference
- CLI reference
- Coming from Wordfence CLI or WPScan
- FAQ and troubleshooting
Soundcheck
- Overview
- Your first check
- Reading results
- Review and accept
- Discovery
- Page health
- Visual changes
- Interactions
- Forms and flows
- Cache
- Speed
- Accessibility and SEO
Soundcheck in depth
- CI
- CI baselines and PR comments
- Configuration
- Error rules and exec
- WordPress updates
- Fleet and client reports
- History
- Import from BackstopJS and Lost Pixel
- Firefox and WebKit
- CLI reference
- FAQ and troubleshooting
- Benchmark
Echo
- Overview
- Your first run
- Connectors
- Sealing staging
- Echo Inbox
- Messages and checks
- Checks and rules
- Attachments
- Actions and compare
- With Soundcheck
- Replay
- Deliverability
Echo in depth
- Docker and DDEV
- Agency hub and reports
- Schedule
- MCP server
- Conformance kit
- CI
- Configuration
- API
- CLI reference
- FAQ and troubleshooting
- Benchmark
Project