GH-4181 + GH-4182: stop claiming the trigger label, and read a collection response as its element type - #4185
Merged
jeremydmiller merged 1 commit intoAug 29, 2026
Conversation
…tion response as its element type Two Event Model derivation defects found by the four-source test vehicle (JasperFx/bobcat#172), both in EventModelRoles.Describe. GH-4181. An HTTP slice claimed TriggerLabel with "{verb} {route}", and a gRPC trigger-only slice claimed it with "{service}/{method}". Both are Derived claims, so per-role precedence (#703) made them beat any label an overlay declared -- and minted a SourceDisagreement hotspot per labelled route for the privilege, so five labelled endpoints meant five noise hotspots drowning out any real finding. The claim carried no information: both facts already ride, structured, on TriggerOrigin, and the wireframe's trigger element resolves from TriggerType / TriggerLabel / TriggerOrigin together. A trigger label is a naming role -- "Customer at the ATM", which the code cannot express -- so the role is now left unclaimed and a declaration wins it by default. ApplyExternalSystems still claims TriggerLabel, deliberately: when a listener triggers a slice that already has an origin, the external label lives nowhere else, so it is not the same duplication. GH-4182. A query chain's response was taken as its read model verbatim, so GET => Task<IReadOnlyList<Order>> reported the closed generic's assembly-qualified CLR string as a canvas node sitting next to the "Order" node its single-document siblings name. A collection response now reads its element type, folding the slice onto the node that already exists. The unwrap walks interfaces rather than matching a whitelist so Marten's IPagedList<Order> unwraps too; maps, strings, types that enumerate as two things, and scalar elements (a byte[] download is not a view over byte) are left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
saithis
pushed a commit
to saithis/wolverine
that referenced
this pull request
Sep 1, 2026
…1.0-alpha.1, Polecat 4.6.0-alpha.1 Bumps JasperFx 2.13.3 → 2.14.2 (document-diagnostics surface + the three projection codegen fixes: JasperFx#4185 DI-activated projection evolver, JasperFx#297 multi-identity dispatcher, derived/custom-named projection evolver inheritance), Marten 9.8.0 → 9.11.0-alpha.1 (+ Marten.AspNetCore / Marten.Newtonsoft), Polecat 4.5.0 → 4.6.0-alpha.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4181, closes #4182.
Two Event Model derivation defects found while wiring the four-source test vehicle (JasperFx/bobcat#172, part of the SDD effort). Both land in
EventModelRoles.Describe, which is why they ship together.GH-4181 — HTTP and gRPC slices claimed
TriggerLabelDescribepassedseed.TriggerOrigin?.Labelinto theTriggerLabelslot, andHttpEventModelSource.ForChainalways populates that origin with"{verb} {route}". So every HTTP slice claimed the role on the Derived rung, per-role precedence (#703) made it beat the overlay's Declared claim, and the merge recorded aSourceDisagreementhotspot for the dropped claim. Five labelled endpoints meant five noise hotspots drowning out any real finding.The claim carried no information. The route and verb already ride on
TriggerOrigin, structured and lossless, and a viewer resolves the trigger element fromTriggerType/TriggerLabel/TriggerOrigintogether — asserted in the new test, so withholding the label costs the canvas nothing. A trigger label is a naming concern ("Customer at the ATM"), exactly what the SDD decisions reserve for declarations: the code cannot express it. With the role unclaimed, an overlay's label wins by default because nothing else claims it — which is the contract #703's own remarks state.grpcTriggerOnlySlicehad the same duplication (it sets the origin two lines below the claim) and gets the same treatment.Deliberately not changed:
ApplyExternalSystemsstill claimsTriggerLabel. When a listener triggers a slice that already has an origin, the slice keeps its own origin and the external label lives nowhere else — so unlike the two above, that claim is not a duplicate of something already carried, and nulling it would lose the label outright.GH-4182 — a collection response reported the raw generic CLR string
A
Viewslice tookseed.ResponseTypeverbatim, soreported
System.Collections.Generic.IReadOnlyList1[[WolverineWebApi.Marten.Order, WolverineWebApi, Version=…as its read model — an unreadable canvas node sitting right next to theOrder` node its single-document siblings name.A collection response now reads its element type, folding the slice onto the node that already exists. The unwrap walks interfaces rather than matching a whitelist of collection types, so Marten's
IPagedList<Order>(a page of Order is still a view over Order) unwraps too. Left alone: maps (aDictionary<K,V>enumerates asKeyValuePair<,>, which is not a read model anybody drew),string, a type that enumerates as two different things, and scalar elements — abyte[]download is not a view overbyte, and folding every list-of-string route onto oneStringnode would be a worse canvas than the one this fixes.Tests
event_model_trigger_label_4181(new) — the derived slice leaves the role unclaimed while the origin still names the route; the wireframe trigger element still rendersPOST /orders/ship3; and a Declared overlay label merged first wins the role with noSourceDisagreementhotspot, without taking any derived role with it.event_model_read_model_collection_4182(new) —IReadOnlyList<Order>andIPagedList<Order>both readOrder, asserted onFullNameagainst the single-document route so a match onNamealone cannot pass it vacuously.event_model_roles_3988(HTTP) andevent_model_per_rpc_4000(CoreTests) now assert the route/RPC offTriggerOrigin.Label— the fact moved, it did not disappear.WolverineWebApi(GET /api/orders/list) for the literal shape Event model: a query endpoint returning a collection reports the raw IReadOnlyList`1 CLR string as its read model, not the element type #4182 was reported against.Verification
dotnet build wolverine.slnx -c Release -f net9.0— clean.EventModeling7/7.Follow-ups outside this PR
Both filed against the bobcat vehicle, and both gated on
samples/BankAccountES/BankAccountES.csprojbumping itsWolverineFx.*pin off 6.30.1 to whatever release carries this:Program.cswithholdsTriggeredByfrom all five HTTP slices, andEventModel.featureasserts the broken behaviour as a tripwire with a pointer back to the issue. Both need restoring, and the restored scenario should assertreports no source disagreement— five labelled endpoints used to bury the planted disagreement that vehicle exists to surface.{word}, so they cannot address a View slice at all (a query endpoint with no request type is named"GET /api/clients/{clientId}/accounts").🤖 Generated with Claude Code