E3 · EchoMQ 2.0 · Movement II · orientation
Conformance, not compatibility
EchoMQ 2.0 broke from BullMQ on purpose. So it does not check that it stayed compatible — it checks that it conforms to its own protocol.
The v1 line was measured against BullMQ: did the wire stay the same? EchoMQ 2.0 retires that
question. emq.1 ships two new probes that measure the break instead — a v2 conformance
probe and a fleet-interop probe. The rung is drafted: the v2 script set and
lib/echomq/ext/ do not exist on disk yet, so this dive teaches from
specs/emq/emq.1.md ("emq.1 ships …") and is re-grounded in the real harnesses
when it ships. The v1 flaw the break resolves is real and present. This is the orientation dive; the chapter's
modules sit on the chapter landing.
Movement II · tracks emq.1 · drafted (the 2.0 break) ← redis-patterns R0 ← redis-patterns R2
The v2 conformance probe · queue payments
Probe one — the v2 conformance probe
The v1 line asked "did the wire stay compatible with BullMQ?" EchoMQ 2.0 broke from BullMQ on purpose, so the
question changes. emq.1 ships the v2 conformance probe (emq.1-D5): an
add → process round-trip on a v2 queue asserts the
deployment conforms to the 2.0 protocol — it does not measure against the wire it left.
The probe checks five things against the captured transition. The produced key set is
emq:{q}:*-only — no emq: prefix anywhere. Every key of
the transition shares one hashtag (the queue's {payments}), so it is
single-slot. There are zero emq:* reads or writes. The scripts ran
with every key declared — the suite traces every operand key to
KEYS[], no escape hatch. And meta.version
reads echomq:2.0.0.
The checklist runner in the figure above ticks each assertion over a fixed v2 capture — step through them one at a time, or assert all five at once.
- emq:{q}:*-only
- The produced key set is the owned keyspace; no
emq:prefix appears (INV1). - declared keys, no escape hatch
- Every operand key is in
KEYS[]; the suite runs on Valkey, the conformance truth, and asserts the declaration discipline directly (INV2). - one hashtag · version
- A single hashtag per queue (single-slot, atomic), and
meta.version=echomq:2.0.0.
Conformance is not a promise against an old wire. emq.1 ships a probe that asserts the deployment
conforms to the 2.0 protocol — emq:{q}:*-only, one hashtag, zero
emq:*, every key declared.
Probe two — the fleet-interop probe
Conformance on one runtime is half the proof. The other half is that a job crosses the wire between
first-party runtimes, both directions. emq.1 ships the fleet-interop probe (emq.1-D6): a job
enqueued by one v2 runtime is fetched and completed by another, and the reverse round-trips too. The shipped
EchoMQ.Queue.add/4 is the v1 enqueue this rides — its binary-queue clause holds
no process; Redis holds the state.
The honest seam (INV6): v2 interop is the EchoMQ fleet's own — stock BullMQ clients cannot speak
v2, by design, not by omission. Today only the Elixir reference speaks v2. The Go port
(apps/echomq-go) must implement v2 to join the fleet — not yet. The proposed
echomq-node, a first-party Node.js runtime, is proposed; it does not exist. The probe
reports the cross-runtime status on every surface that claims interop, never smoothed. Step the round-trip
below to see each direction and the honest fleet status.
The fleet round-trip · first-party runtimes only
# the shipped enqueue the fleet probe rides — EchoMQ.Queue.add/4 (v1 line) # @spec add(atom | pid | String.t, job_name, job_data, keyword) :: # {:ok, Job.t} | {:error, term} # the binary-queue clause: no process — Redis holds the state def add(queue, name, data, opts) when is_binary(queue) do conn = Keyword.fetch!(opts, :connection) ctx = Keys.new(queue, prefix: prefix) # v1: builds emq:queue:… verbatim job = Job.new(queue, name, data, opts) add_job(conn, ctx, job) # emq.1 ships the v2 form: emq:{payments}:wait end
An invariant must be checked, not promised. A multi-key change is correct because Lua makes it indivisible; "the deployment conforms" is correct only when a probe captures the transition and asserts it.
emq.1 ships the v2 conformance probe (emq:{q}:*-only, one hashtag, zero
emq:*, every key declared) and the fleet-interop probe — the interop the
fleet's own, the cross-runtime status honest (INV6).
Interop is the fleet's own. The conformance probe proves a deployment conforms to 2.0; the fleet-interop probe proves a job crosses between first-party runtimes — stock BullMQ clients cannot, by design.
The v1 flaw the break resolves
The conformance probe asserts "every key declared" because the v1 line, today, reaches keys it never
declares. In moveToActive-11.lua the worker fetch loop builds a job key with
local jobKey = keyPrefix .. jobId (:148), then a lock key
with local lockKey = jobKey .. ':lock' (:162) — both from
an ARGV keyPrefix, neither in the script's KEYS[]. The caller
sends only the declared list: EchoMQ.Scripts.execute_raw/4 issues
["EVALSHA", sha, num_keys | keys ++ encoded_args] (scripts.ex:256),
where num_keys = length(keys) — the concatenated keys are not in it. Roughly half
the v1 set, about 24–26 of the 50 scripts, share the pattern.
On a single-threaded engine this is invisible — but it sits outside the engine scripting contract
(Valkey's own guidance: every key a script accesses arrives as an input key argument), and a key the store
cannot see before execution is a key no placement or locking discipline can reason about. The inspector below
walks each anchor to its consequence. emq.1 ships the declared-keys v2 set as the root resolution (D2): every operand
key in KEYS[], the runtime-pick core redesigned so a popped id resolves inside the
queue's native {q} slot.
The v1 flaw · anchor → consequence → resolution
The 2.0 fork — EchoMQ leaves the BullMQ wire
The v1 scripts reach keys they never declare (moveToActive-11.lua:148,162; the
caller's declared list is scripts.ex:256) — roughly 24–26 of 50 —
outside the engine scripting contract. emq.1 ships the declared-keys v2 set as the root
resolution, and the v2 conformance probe asserts it: emq:{q}:*-only, one hashtag per
queue, zero emq:*, every key declared. Stock BullMQ clients cannot speak v2, by
design — the interop is the fleet's own.
Recap — conformance, not compatibility
EchoMQ 2.0 does not check compatibility with BullMQ; it checks conformance to its own protocol. emq.1 ships
two probes: the v2 conformance probe (add → process on a
v2 queue asserts emq:{q}:*-only · one hashtag per queue · zero
emq:* · every key declared ·
meta.version = echomq:2.0.0) and the fleet-interop probe (the
Elixir reference ⇄ the Go port once it ports v2 ⇄ the proposed echomq-node).
The WHY is the v1 undeclared-keys inheritance — moveToActive-11.lua:148,162, about
24–26 of the 50 scripts, the caller's declared list at scripts.ex:256 —
keys reached outside the engine scripting contract; 2.0 declares every key. The honest seam: stock BullMQ clients
cannot speak v2, by design. The next page in the chapter is the E3 landing — the full break emq.1
ships.
References
Where the v1 flaw the break resolves is documented, the engine contract the declared-keys set honors, and the chapter's own pages.
Sources
- BullMQ — Documentation — the reference implementation whose wire the v1 line speaks and the 2.0 break leaves.
- Valkey — Scripting with Lua — the key-declaration prescription: every key a script accesses arrives as an input key argument.
- Valkey — Cluster specification — the
{…}hashtag slot rule the declared, placed key set honors. - Redis — EVALSHA — the load-once, run-by-SHA dispatch the v2 set rides.
Related in this course
- E3 · EchoMQ 2.0 — the chapter landing: the owned keyspace and the triangle.
- E2 · The core — the v1 line the fork freezes.
- redis-patterns R2 · Atomicity — the pattern: prove the invariant, never assert it.
- redis-patterns R0 · Foundations — load-once scripts and the facade seam.