The old bus's wire is pinned unchanged, because this has to merge to a running mesh
Every module's event names were converted from the old bus's routing keys to local names, and this client maps them back. If that mapping is wrong anywhere a live mesh's events stop being delivered — silently, because a binding that matches nothing is not an error. So the mapping is pinned against the literal routing keys the mesh published before, taken from the manifests as they were: what each module now emits, what each now binds, and that a handler still matches what the bus delivers. Including the audit logger's "everything", which must stay `#` on this bus. And a key already in the old form is left alone, so a module built from an older manifest keeps working beside one built from a current manifest — which is the state the mesh will actually be in between deployments.
This commit is contained in:
+4
-4
@@ -373,7 +373,7 @@ function toEnvelope<T>(msg: amqp.ConsumeMessage): Envelope<T> {
|
||||
|
||||
/** AMQP topic matching: `*` matches one word, `#` zero or more. Used to fan a shared queue's
|
||||
* deliveries out to the handlers whose pattern actually matches the routing key. */
|
||||
function topicMatches(pattern: string, key: string): boolean {
|
||||
export function topicMatches(pattern: string, key: string): boolean {
|
||||
return matchFrom(pattern.split("."), 0, key.split("."), 0);
|
||||
}
|
||||
|
||||
@@ -404,19 +404,19 @@ function matchFrom(p: string[], pi: number, k: string[], ki: number): boolean {
|
||||
* **Why it exists at all.** Until 04-ISSUES/127 every module passed the routing key itself, which
|
||||
* worked on this bus and derived into a namespace nobody owns on the one being built. Converting the
|
||||
* modules to local names without this would have broken the mesh that is actually running. */
|
||||
function routingKeyFor(key: string, self: string): string {
|
||||
export function routingKeyFor(key: string, self: string): string {
|
||||
return key.startsWith("module.") ? key : `module.${self}.${key}`;
|
||||
}
|
||||
|
||||
/** A local pattern as this bus's binding. `**` is the mesh's wildcard for the rest of a name; here
|
||||
* that is `#`, and on the bus being built it is `>`. Neither spelling appears in a manifest. */
|
||||
function bindingFor(pattern: string): string {
|
||||
export function bindingFor(pattern: string): string {
|
||||
const here = pattern.split(".").map((part) => (part === "**" ? "#" : part)).join(".");
|
||||
if (here === "#") return "#";
|
||||
return here.startsWith("module.") ? here : `module.${here}`;
|
||||
}
|
||||
|
||||
/** A routing key as the local name a handler and a manifest both use: the emitter and the event. */
|
||||
function localKeyFor(routingKey: string): string {
|
||||
export function localKeyFor(routingKey: string): string {
|
||||
return routingKey.startsWith("module.") ? routingKey.slice("module.".length) : routingKey;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* **The old bus's wire is byte-identical after the rename, and this is the test that lets the change
|
||||
* be merged to a running mesh.**
|
||||
*
|
||||
* Every module's event names were converted from the old bus's routing keys to local names
|
||||
* (novox/hq 04-ISSUES/127), and the old bus's client maps them back. If that mapping is wrong
|
||||
* anywhere, a live mesh's events stop being delivered — silently, because a binding that matches
|
||||
* nothing is not an error.
|
||||
*
|
||||
* So this pins the mapping against the literal routing keys the mesh used before, taken from the
|
||||
* manifests as they were. It needs no bus: it is about a string.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { routingKeyFor, bindingFor, localKeyFor, topicMatches } from "../dist/broker-amqp.js";
|
||||
|
||||
test("a converted emit produces the routing key the mesh published before", () => {
|
||||
// left: what the module's code says now. right: what went on the wire before, unchanged.
|
||||
const same: [string, string, string][] = [
|
||||
["plex", "playback.started", "module.plex.playback.started"],
|
||||
["sonarr", "download.completed", "module.sonarr.download.completed"],
|
||||
["builder", "built", "module.builder.built"],
|
||||
["mesh-catalog", "upgraded", "module.mesh-catalog.upgraded"],
|
||||
["keycloak", "user.created", "module.keycloak.user.created"],
|
||||
["mesh-vault", "secret.rotated", "module.mesh-vault.secret.rotated"],
|
||||
];
|
||||
for (const [self, local, before] of same) {
|
||||
assert.equal(routingKeyFor(local, self), before, `${self} emitting ${local}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("a converted subscription binds what it bound before", () => {
|
||||
const same: [string, string][] = [
|
||||
["builder.built", "module.builder.built"],
|
||||
["*.download.completed", "module.*.download.completed"],
|
||||
["*.usage.*", "module.*.usage.*"],
|
||||
// The audit logger's "everything": `#` on this bus, and it must stay `#`.
|
||||
["**", "#"],
|
||||
];
|
||||
for (const [declared, before] of same) {
|
||||
assert.equal(bindingFor(declared), before, `consuming ${declared}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("a handler still matches what the bus delivers", () => {
|
||||
// The key a handler is given is the local one now, and the pattern it compares against is local
|
||||
// too — so the pair must still meet for every case the mesh actually has.
|
||||
const pairs: [string, string][] = [
|
||||
["builder.built", "module.builder.built"],
|
||||
["*.download.completed", "module.sonarr.download.completed"],
|
||||
["*.usage.*", "module.anthropic-consumer.usage.session"],
|
||||
["**", "module.anything.at.all"],
|
||||
];
|
||||
for (const [pattern, delivered] of pairs) {
|
||||
assert.ok(
|
||||
topicMatches(pattern, localKeyFor(delivered)),
|
||||
`${pattern} no longer matches ${delivered}, so a running module would stop reacting`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("a routing key already in the old form is left alone", () => {
|
||||
// Belt for the transition: anything not yet converted still goes out as it did, so a module built
|
||||
// from an older manifest keeps working beside one built from a current manifest.
|
||||
assert.equal(routingKeyFor("module.plex.playback.started", "plex"), "module.plex.playback.started");
|
||||
assert.equal(bindingFor("module.builder.built"), "module.builder.built");
|
||||
});
|
||||
Reference in New Issue
Block a user