Every module named its events the way the old bus spelled a routing key, so on the new bus every cross-module subscription pointed at a namespace nobody publishes to. Nothing failed — the services started and none of them reacted. Converted, and the rule now has checks at both scales: at registration for one manifest, and as a test across the whole catalogue where a consumed event's emitter is present. It was larger than the report said, in two directions nobody had looked. Forty-three files of module code pass the event name at runtime, so the code mattered as much as the manifests. And both clients had to learn the mapping — without that, converting the modules would have broken the mesh that is actually running, which is the opposite of what fixing this was for. Design 29 gained three things it did not say: what a wildcard is (`*` for one name, `**` for the rest, spelled the mesh's way and derived to each bus's own), that an event about a role belongs on the seat and why that is not yet possible, and how the rule is checked — because "a subscription that matches nothing is silence" is exactly why nobody noticed thirty-seven manifests being wrong the same way. 4.2 and 4.3 are unblocked. The catch-up half of 4.5 is not: it is a decision, and it narrowed rather than closed. It cannot be a reply to a module's inbox, because that needs the blanket grant design 25 §4 refuses.
94 lines
5.8 KiB
Markdown
94 lines
5.8 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-27
|
|
located-in: [mesh-catalog modules, mesh-control internal/catalogue, mesh-tools src]
|
|
fixed-by: mesh-catalog 7b06a7a, mesh-tools fbeb373, mesh-control 05ff606
|
|
amended-design: 03-DESIGN/01-to-be/29-what-a-module-declares.md
|
|
---
|
|
|
|
# 127 — A module's event derives a subject nothing publishes
|
|
|
|
## What was observed
|
|
|
|
[Design 29](../../03-DESIGN/01-to-be/29-what-a-module-declares.md) §1 says a module names an event
|
|
locally and the mesh derives the subject: `emits: order.placed` becomes
|
|
`mesh.mod.<module>.event.order.placed`, and a consumer declaring `consumes: shop.order.placed`
|
|
subscribes the emitter's own subject. That derivation is built and tested.
|
|
|
|
**Every event name in the catalogue is still written the way a routing key on the bus the mesh has
|
|
is written** — `module.<module>.<verb>` — and the derivation reads it as `<emitter>.<event>`. Asked
|
|
of the composer directly, with the module names and declarations the catalogue holds today:
|
|
|
|
| declared | derived |
|
|
|---|---|
|
|
| `builder` emits `module.builder.built` | publish `mesh.mod.builder.event.module.builder.built` |
|
|
| the catalogue consumes `module.builder.built` | subscribe `mesh.mod.module.event.builder.built` |
|
|
| a media module emits `module.<itself>.download.completed` | publish `mesh.mod.<itself>.event.module.<itself>.download.completed` |
|
|
| a player consumes `module.*.download.completed` | subscribe `mesh.mod.module.event.*.download.completed` |
|
|
|
|
The consumer's subject names a module called `module`. **No cross-module subscription in the
|
|
catalogue matches what any emitter publishes.** Thirty-seven manifests declare events; every one of
|
|
their consume declarations derives this way.
|
|
|
|
Two further consequences of the same cause, found in the same check:
|
|
|
|
- One module declares `consumes: "#"` — the wildcard of the bus the mesh has, which is not a
|
|
subject at all. The composer **refuses it outright**, so that module's account cannot be composed
|
|
and the module cannot be assigned.
|
|
- One module emits under a name that is not its own — it declares `module.<other>.image.pushed`
|
|
while being a differently named module — which the derivation puts inside *its* namespace. Whether
|
|
that is legitimate is a design question: design 29 §2 makes an event's source a fact the server
|
|
enforces, and this is a module claiming another's name in its own event.
|
|
|
|
None of it fails on the bus the mesh runs on today, where a routing key is matched literally and
|
|
nothing derives anything. It fails only once the subject is derived — which is to say it fails on
|
|
the first mesh raised on the new bus, and not before.
|
|
|
|
Evidence: run against the controller's own `PermissionsFor` on the current feature branch, with the
|
|
declarations read from the catalogue's manifests. Found while wiring the controller's consume side
|
|
(design 28 step 3.4), when the controller's own subscription had to be written and the subject it
|
|
would have to name turned out not to be the one design 29 specifies.
|
|
|
|
## Why it matters beyond this instance
|
|
|
|
**This is the failure [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md)
|
|
exists to catch, arriving by a route the conformance suite does not cover.** Two implementations
|
|
that disagree about an envelope do not fail to compile — they ignore each other while both keep
|
|
running. Here it is not two implementations disagreeing but a *declaration* and a *derivation*
|
|
disagreeing, and the symptom is identical: every service starts, every log is quiet, and nothing
|
|
reacts to anything.
|
|
|
|
The fixtures cannot catch it. They pin one emitter's envelope against one subject, and both halves
|
|
of that pair are correct. What is wrong is only visible when an emitter's derived subject is set
|
|
beside a consumer's derived subject — a check nothing performs, because until the subject was
|
|
derived there was nothing to compare.
|
|
|
|
It also means the rule 3.8 established is weaker than it reads. That task asserted **no manifest
|
|
contains a subject**, which holds: a manifest contains a local name. What nothing asserts is that a
|
|
local name derives to a subject some emitter actually publishes, and the rule as stated is satisfied
|
|
by thirty-seven manifests whose names derive to nothing.
|
|
|
|
And it blocks work already scheduled. Design 28's step 4.2 (a build source's change reaching the
|
|
builder over the bus) and 4.3 (an installation completing over the bus) are both event flows through
|
|
exactly these pairs, and the catch-up flow the controller answers is a third — the controller
|
|
currently replays a build announcement under its *own* name rather than the builder's, which a
|
|
consumer filtering the builder's subject will not hear either.
|
|
|
|
## Open questions
|
|
|
|
- Is a local name converted per manifest (`emits: built`), or does the derivation keep accepting the
|
|
old form and strip a redundant prefix? The first is thirty-seven manifests and a rule that can be
|
|
checked; the second is a rule that cannot, because `module.foo.bar` is also a legitimate three-part
|
|
local name.
|
|
- What checks the pair? An emitter's derived subject against every consumer's derived subject is a
|
|
whole-catalogue check, not a per-manifest one — and a module lives in its own repository and may
|
|
be registered long after the catalogue was checked.
|
|
- What are `#` and `*` in a consumed name? The bus the mesh has and the bus being built spell
|
|
wildcards differently, and a `consumes` pattern is the one place a module writes one.
|
|
- May a module emit an event named after another module, and if not, what does the module that does
|
|
it today declare instead?
|
|
- Who replays? A catch-up answer published by the controller under a builder's subject is the
|
|
controller signing an event as another module, which is the thing the derived namespace prevents.
|
|
If it must not, then a replay is a different message from an announcement, and the consumer needs
|
|
to be told so.
|