WBS 3.4 is done both halves; issue 127 holds 4.2, 4.3 and catch-up
The controller's inbound is through a seam with both transports behind it, and the store window is now the server's rather than the controller's memory. Seven claims about that were asked of a running server rather than reasoned. Wiring the controller's own subscription is what found issue 127: every event name in the catalogue is still written the way a routing key is, so design 29's derivation turns a consumer's declaration into a subject no emitter publishes. Thirty-seven manifests, one that cannot be composed at all. It fails on the first mesh raised on the new bus and not before, which is why nothing had caught it — the conformance fixtures pin one emitter against one subject, and both halves of that pair are correct. The node-facing flows are unaffected: those subjects are the mesh's own and derive from nothing a module declares.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
---
|
||||
status: open
|
||||
opened: 2026-09-27
|
||||
located-in: []
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
|
||||
# 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.
|
||||
Reference in New Issue
Block a user