Skip to content

GH-4152/4147/4146: adopt JasperFx 2.56 provenance and add event-model --url - #4153

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-4152/jasperfx-256-provenance-and-event-model-url
Aug 26, 2026
Merged

GH-4152/4147/4146: adopt JasperFx 2.56 provenance and add event-model --url#4153
jeremydmiller merged 1 commit into
mainfrom
gh-4152/jasperfx-256-provenance-and-event-model-url

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #4152. Closes #4147. Closes #4146.

Bumps the JasperFx pins to 2.56.0 and takes the three things that unlocks.

#4152 / #4147 — provenance instead of registration order

WolverineEventModelSource and HttpEventModelSource both read their roles off compiled chains, so both now declare:

public EventModelProvenance Provenance => EventModelProvenance.Derived;

That lets the services.Insert(0, ...) in UseWolverine() and MapWolverineEndpoints() become plain AddSingleton calls. The insert was load-bearing behaviour nothing in the registration explained — the class doc had to spell it out:

Registered by UseWolverine() ahead of every other source so that EventModelDiscovery.Assemble lets the derived roles win over an overlay's names.

Both the inserts and that sentence are gone.

Note the deliberate inversion this buys, exactly as #4147 asks for: a source that observes a running system now outranks a derived one. Production truth beating what the code says it should do is the point of the ladder, not a regression.

#4146event-model --url

PUTs the assembled descriptor to a monitor instead of, or as well as, writing the file:

dotnet watch run -- event-model --url http://localhost:5525

JsonFlag's default moves from "event-model.json" to null so --url on its own doesn't also litter the application directory; with neither flag the command still writes event-model.json exactly as before. Wolverine takes no reference on the monitor — it's an HTTP PUT to whatever URL is named.

Deliberately not a --watch flag, per the issue: this process already has the assembly loaded, so an internal loop would re-serialize the same chains forever. Only a fresh process picks up recompiled handlers, which makes dotnet watch the mechanism rather than a workaround.

The part worth reading — how the provenance change is tested

The existing GH-3988 fixture registers an overlay before UseWolverine() and asserts the overlay may only fill gaps. That looks exactly like the regression guard for this change. It isn't — it passes with or without the Derived stamp.

The public overlay API (EventModelSliceBuilder) can only express TriggeredBy, InDomain, LinksToSpecification, Hotspot — annotations, none of which are factual roles. So an overlay cannot collide with a derived role, and that fixture never exercises the ladder at all. Had I stopped there I'd have shipped an "adoption" with no evidence it was load-bearing.

The new event_model_provenance_ladder_4147 tests use custom IEventModelDefinitionSource stubs that do claim a colliding role, and test #4147's acceptance criteria as written — an observed source overrides a derived role, a declared one cannot.

Verified red. With the stamp removed, the declared rival registered first unions into the derived role instead of losing to it:

should be  ["PaymentRecorded"]
but was    ["MerelyDeclared", "PaymentRecorded"]

One existing test asserted the Wolverine source was registered first. That pinned the mechanism this PR removes, so it now asserts the rung — through the interface, since reading a default interface member off the concrete type only compiles when the override exists and would assert nothing.

Verification

dotnet build wolverine.slnx -c Release -f net9.0 clean, 0 warnings on 2.56.0
CoreTests 2658 passed, 2 skipped, 0 failed
Wolverine.Http.Tests 992 passed, 10 skipped, 0 failed

All four --url acceptance cases driven end to end against the Quickstart sample and a stub monitor, not just unit-tested:

  1. no flags → writes event-model.json as before
  2. --url alone → PUT /event-model application/json, no file written
  3. monitor down → Could not reach the monitor at http://127.0.0.1:5601/nope: Connection refused, exit code 1, no stack trace
  4. --json + --url → both, and the file and request body are byte-for-byte identical (8153 bytes each)

-u, --url also confirmed to register without colliding with any NetCoreInput flag.

Not included

The RetryBlock.ShouldRetry / OnTerminalFailure migration also unblocked by 2.56 (jasperfx#701, noted at the bottom of #4152). That's item 5 of #4012 and belongs with the three transports it touches — RabbitMqChannelCallback, AzureServiceBusSettlement, SqsSettlement — rather than in an event-model PR. #4152's own title scopes it to the event model.

Worth a follow-up look

#4152 mentions that dropped claims now surface as HotspotOrigin.SourceDisagreement (#704) rather than being silently resolved. Nothing in the suites surfaced a disagreement between the two derived sources, but that's on this repo's fixtures, not on a real application's model — worth a glance at a substantial app's event-model output after the bump.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HxbBpN6PmRB5CMSSTdGNr3

… --url

Bumps the JasperFx pins to 2.56.0 and takes the three things that unlocks.

GH-4152 / GH-4147 — provenance instead of registration order.
WolverineEventModelSource and HttpEventModelSource both read their roles off
compiled chains, so both now declare EventModelProvenance.Derived. That lets the
services.Insert(0, ...) in UseWolverine() and MapWolverineEndpoints() become plain
AddSingleton calls: the insert was load-bearing behaviour that nothing in the
registration explained, and the class doc had to spell it out. Both are gone.

Note the deliberate inversion this buys: a source that OBSERVES a running system
now outranks a derived one. Production truth beating what the code says it should
do is the point of the ladder, not a regression.

GH-4146 — event-model --url.
PUTs the assembled descriptor to a monitor instead of, or as well as, writing the
file, so the design-time loop is one command:

    dotnet watch run -- event-model --url http://localhost:5525

JsonFlag's default moves from "event-model.json" to null so --url on its own does
not also litter the application directory; with neither flag the command still
writes event-model.json exactly as before. Wolverine takes no reference on the
monitor -- it is an HTTP PUT to whatever URL is named. A monitor that is down
fails with a one-line message and a non-zero exit rather than a stack trace,
because under dotnet watch a console that has not been started yet is the ordinary
case and must not look like a crash.

Deliberately NOT a --watch flag: this process already has the assembly loaded, so
an internal loop would re-serialize the same chains forever. Only a fresh process
picks up recompiled handlers.

On testing the provenance change honestly.
The existing GH-3988 fixture registers an overlay BEFORE UseWolverine() and
asserts the overlay may only fill gaps, which looks like the regression guard for
this change. It is not: it passes with or without the Derived stamp. The public
overlay API (EventModelSliceBuilder) can only express TriggeredBy / InDomain /
LinksToSpecification / Hotspot -- annotations, none of which are factual roles --
so an overlay cannot collide with a derived role and that fixture never exercises
the ladder at all.

The new event_model_provenance_ladder_4147 tests use custom
IEventModelDefinitionSource stubs that do claim a colliding role, and they test
GH-4147's acceptance criteria as written: an observed source overrides a derived
role, a declared one cannot. Verified red -- with the stamp removed, the declared
rival registered first unions into the derived role
(["MerelyDeclared", "PaymentRecorded"]) instead of losing to it.

One existing test asserted the Wolverine source was registered FIRST. That pinned
the mechanism this change removes, so it now asserts the rung instead -- through
the interface, since reading a default interface member off the concrete type only
compiles when the override exists and would assert nothing.

Verification: full solution builds clean on 2.56.0 (-f net9.0, 0 warnings).
CoreTests 2658 passed / 2 skipped / 0 failed. Wolverine.Http.Tests 992 passed /
10 skipped / 0 failed. All four --url acceptance cases driven end to end against
the Quickstart sample and a stub monitor: default file unchanged; --url alone
publishes and writes nothing; a down monitor prints one line and exits 1; --json
and --url compose with byte-identical file and request body.

Not included: the RetryBlock.ShouldRetry / OnTerminalFailure migration also
unblocked by 2.56 (jasperfx#701). That is item 5 of #4012 and belongs with the
three transports it touches, not here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxbBpN6PmRB5CMSSTdGNr3
@jeremydmiller jeremydmiller reopened this Aug 26, 2026
@jeremydmiller
jeremydmiller merged commit 5d01870 into main Aug 26, 2026
78 checks passed
jeremydmiller added a commit that referenced this pull request Aug 26, 2026
Nine schema read-back fixes, three of which never converged: a named
foreign key rebuilt a SQLite table on every run, a partition-aligned
index was dropped and recreated on every run on SQL Server, and a
concurrent index on a manager-owned partitioned table stayed permanently
invalid. No DDL changes for a schema Weasel was already reading
correctly, and no configuration changes.

JasperFx is already on 2.56.0, the current release (#4153).


Claude-Session: https://claude.ai/code/session_01HxbBpN6PmRB5CMSSTdGNr3

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant