Guard Wire Compatibility
Commit a Job Manifest check so a removed or renamed Wire Name fails in PR review instead of quarantining live jobs in production.
A Wire Name is a job type's identity in storage and on the wire. Jobs already sitting in a Queue carry it, so the moment a Wire Name a running system depends on disappears, those jobs stop routing and land in Quarantined. The failure is silent at build time and loud in production, which is the worst possible place to learn about it. The Job Manifest turns that silent break into a failed test in code review.
The Manifest is a committed snapshot of your registry, one line per registration, sorted by Wire Name. You check it into the repository next to your tests, and a single call to JobManifest.Verify in your suite compares the live registry against that recorded file. An additive change rewrites it, so the new line shows up in the pull request diff. A destructive change (a Wire Name that used to be recorded and no longer is) throws. The build goes red where a reviewer can see it, not after a deploy.
The one-line test#
You need a JobRegistry and a path to the committed file. The registry is the generated BackWaveJobs.CreateRegistry(), and the path points at a file you keep under source control alongside the test.
[Fact]
public void Job_manifest_is_unchanged()
{
var registry = BackWaveJobs.CreateRegistry();
var manifestPath = Path.Combine(AppContext.BaseDirectory, "JobManifest.txt");
JobManifest.Verify(registry, manifestPath);
}That is the whole guard. Verify is the shipped BackWave.Testing helper; it takes the registry and the manifest path and does the rest. There is nothing to configure and nothing to assert by hand. The check either passes or throws with the offending lines named.
What the committed file looks like#
Each line pairs a Wire Name with the payload type registered behind it, sorted by Wire Name so the file is stable and diffs cleanly:
order-charged => Acme.Jobs.OrderCharged
send-welcome => Acme.Jobs.SendWelcomeYou do not write this file by hand. Verify renders it from the registry the first time it runs, and every additive change afterward. Your job is to commit it and to read the diff when it changes.
The three outcomes#
Verify behaves differently depending on how the live registry compares to the recorded file, and each behavior maps to a distinct kind of change.
| Situation | What Verify does | What it means for you |
|---|---|---|
| No file exists yet | Writes the manifest from the current registry and returns | First run. Commit the generated file. |
| Registry adds a Wire Name | Rewrites the file to include the new line and returns | Additive change. The new entry appears in your PR diff; commit it. |
| A recorded Wire Name is gone or its payload type changed | Throws, naming every broken line | A wire-format break. The build fails in review before it can ship. |
The additive case passing is deliberate. Adding a job is safe for anything already in the queue, so the test should not block it, but it should surface it. Rewriting the file means the reviewer sees exactly which Wire Names entered the wire format, in the same diff that added the handler.
The throwing case is the point of the whole exercise. A recorded Wire Name that no longer appears in the registry means one of three things happened: the Wire Name was removed, it was renamed, or the payload type recorded against it changed. Every one of those would quarantine in-flight jobs that still carry the old identity. Verify treats all three as the same break and refuses to let the manifest forget the old line silently.
Why renaming code is safe#
The reason this guard is cheap to live with is that Wire Names are never derived from CLR type names. You set a Wire Name explicitly in the [Job] attribute, and it is the only thing stored with a job. Rename the class, rename the method, move it to another namespace, reshape the folder layout. The Wire Name stored with jobs already in the queue does not move, and the Manifest test stays green. Refactoring your code is decoupled from the wire contract by design.
That is also why the test is meaningful rather than noisy. If the Manifest were keyed off type names, every ordinary rename would trip it and you would train yourself to blindly re-baseline the file. Because the key is the explicit Wire Name, a red build genuinely means the wire contract changed, and re-baselining it is a decision you make on purpose.
When the test throws on purpose#
Sometimes you do want to change the wire format because a payload needs a breaking shape change. The Manifest does not stop you; it makes the change a reviewed act. The safe procedure is to give the new shape a new Wire Name and keep the old registration in place until in-flight jobs under the old name have drained. Both lines then live in the Manifest, both are visible in the diff, and nothing quarantines. Once the old Wire Name has drained from every environment, you retire its registration, and the resulting throw is the moment you consciously accept that the old contract is gone. The end-to-end procedure lives in Evolve Job Payloads.
Where to go next#
- Jobs & Handlers: the
[Job]attribute, the Wire Name, and why it is refactor-safe. - Evolve Job Payloads: the drain-and-retire procedure for a breaking payload change.
- Write Your First Test: the
BackWaveHarnessthat runs your handlers deterministically in the same test suite this check lives in.