A ticket's log category names who LOGGED it, never who owns the code

An automated incident ticket has to land in some repository, and the only thing the pipeline reliably knows about the fault is the logger's Category. Routing on it works whenever the component that wrote the line and the component that broke are the same — which is most of the time, and is exactly what makes the remaining cases expensive.

What was measured

Three tickets described code that does not exist in the repository they were filed in. MeshWeaver.AI, MeshWeaver.SelfUpdate.Aks, MeshWeaver.LogWatcher and MeshWeaver.Observability have zero tracked files in the platform repository and live here; one of the three even ended with "Where to look: MeshWeaver.AI.AgentChatClient", which over there names a directory containing two build folders. They were moved by hand.

The instrument that should have caught it had a blind spot with a precise shape. The router already separated two answers a repository name alone conflates — "the configuration says this belongs to X" and "nothing here knows, put it in the default" — and a defaulted ticket said so on its own face:

| Routing | not determined — no configured route matches the category …. This repository is the configured fallback, not a finding about who owns the fault; the category names the LOGGER, which may not be the subject. |

But that row fires only when NOTHING matched. All three misfiled tickets had a matching prefix, so each reported itself DETERMINED and said nothing at all. The caveat was printed in the case where the reader is already suspicious, and withheld in the case that actually misroutes. The router's own test suite documented the boundary in as many words — "nothing in this class can see which repository owns an assembly" — which is how a known limitation stays a defect.

🚨 The obvious fix is not obviously right, and its stated evidence does not hold

The tempting repair is "route by the first stack frame in an in-house assembly instead of by Category". Two open tickets were cited as proof that a category-keyed router misroutes in the opposite direction. Re-measured, neither supports the claim:

The conclusions those two were cited for happen to be right. The evidence offered for them was not. That matters more than a footnote: the argument for making the frame win rested on those two, and with them gone there is no measurement showing the frame is the better key. A third point cuts the other way — one ticket's top frame is OpenAI.AsyncSseUpdateCollection1…`, a third-party assembly in neither repository — so the frame is not even always available, let alone always better.

What the two keys can establish between them

Neither key is authoritative:

key what it establishes how it fails
log Category which component wrote the line silent when the reporter is not the subject
first in-house stack frame where the fault ran absent entirely for a log line with no stack; the top frame is often third-party

So the router reads both, and the rule is chosen to match what is actually known:

The important property is that a contested route is still determined in the narrow sense that a route matched. Collapsing the two would have lost the undetermined state, which already works.

Why the SAME prefix table answers both questions

A route prefix (MeshWeaver.AI.) is a namespace prefix, and a stack frame is a namespace-qualified method. So the frame key is the existing table applied to a different string, and "in-house" means exactly "some configured route claims it" — there is no second list of in-house assembly roots to keep in step, and no root hard-coded anywhere. A deployment that adds a route extends both keys at once.

That is the answer to the standing objection that an assembly→repository map cannot be maintained: the split has moved before and will move again, so the map is derived from configuration that has to be right anyway for the primary key to work, and an undetermined route is the honest output when it is not.

🚨 Two traps in reading a frame out of an incident

Both were found by review, after the first version of this change had a green suite, and each would have shipped a fix that did nothing or broke a consumer.

A sample is a WHOLE BURST, not a line. BurstAggregator stores one sample per burst as string.Join('\n', burst.Raw.Lines), and the frame regex is anchored ^\s*at\s+…$ with no RegexOptions.Multiline — so ^ matches only at the start of the whole string. Handing samples to the frame walk unsplit therefore matches nothing unless the entire blob happens to be one at … line: the walk is inert on production data, and silent about it. It fails precisely on the case it exists for — a burst whose top frame is third-party and whose in-house frame is further down.

And it was a fact about the TEST first. The original harness took one line per argument and built one sample each, a shape the producer never produces; a harness more permissive than the real producer is how a fix reads green and does nothing. The harness now joins the lines exactly as BurstAggregator does, and removing the split makes the third-party-top-frame case fail — which is the only reason to believe the split is load-bearing.

Adding a positional parameter to a shipped record is a binary break. A record's primary constructor is one method, so a fifth positional parameter — even with a default — deletes the four-argument .ctor and the four-value Deconstruct from the assembly. Source callers recompile happily, which is what makes it invisible in-repo; a consumer already compiled against an earlier minor gets MissingMethodException. That breaks the fleet's same-major-serves rule for a change that is not a major bump, so ContestedReason is a non-positional init property set with with { … }.

What this does not fix

🚨 A fault in runtime-compiled in-mesh code is never published (Plugins#4444)

The sharpest case of "the logger is not the subject": a render fault thrown inside a NodeType's in-mesh source — code compiled at runtime from a node in somebody's partition — is logged by the platform's LayoutAreaHost. The category routed it like a platform defect, and two such faults from one user's dev module were filed as public issues on the core repository, with that user's partition paths in the bodies. The platform had behaved correctly both times.

The discriminator. A console stack trace names methods, not assemblies, so the framework's own marker for a NodeType build — its DynamicNode_* assembly in a collectible load context — is not on the line. What is on the line is the frame's ROOT, the text before its first dot: code compiled into the image starts with a namespace root some loaded assembly carries (MeshWeaver, Memex, OpenAI, …); in-mesh source declares no namespace, so its frame starts with a type name no assembly is named after. InMeshCodeClassifier reads the FIRST application frame that carries evidence (a dynamic method or a compiler-generated <…> root carries none) against InMeshCodeRoots.Loaded — the roots of every assembly the ingesting process loaded, minus DynamicNode_*, plus every route prefix — read on each call, so nothing goes stale and nothing is a list to maintain. A platform frame on top of in-mesh frames is a platform fault and is not classified.

What happens to it. The portal classifies the report InMeshCode at ingest. That classification is recorded, not triaged, and — stronger than the other recorded classifications — withheld from public (LogIncidentClassification.IsWithheldFromPublic):

It fails towards privacy: a third-party frame whose assembly the ingesting process never loaded is read as in-mesh and recorded rather than filed — a lost ticket, never a leak. The trade-off is deliberate and also covers the platform's OWN in-mesh packages (their sources are namespace-less too): those faults were routed by the logger's category to the public core repository, which was the wrong owner for them as well.

What is published carries no mesh path. Independently of the classification, every log line the filer copies into an issue or a comment goes through LogIncidentPublicText.MaskMeshPaths: a relative multi-segment path (a hub address, a node) becomes {path} and a URL keeps only its origin. The incident node keeps the line verbatim.

Not done yet. Delivering an in-mesh fault to the module's author — in-app, in their own partition — needs the NodeType path, which the log line does not carry; until the portal stamps it, the private incident node is the sink. The triage draft (the agent's own prose) is not masked; for an in-mesh fault it is never written, because the incident is never triaged.