Repository navigation
Description
Found while checking whether a deliberately-degraded fixture in #3619 would red the shard. It would not — and the reason is that the gate cannot fire at all in core's test lane.
The gate
dotnet-test.yml:1504 runs check-untyped-content.sh collected-logs, which greps the collected shard logs for
stayed an untyped JsonElement
and reds the shard on a hit. It was made load-bearing on 2026-09-02 (#3056 removed the accidental type-registration that MessageService.Post's Debug serialize was performing, and the degradations it had been masking started surfacing as this warning). The script is careful in every way that matters given its input: it separates "found nothing" from "the scan failed" on grep's exit code, refuses a missing directory, and explains the fix.
The phrase cannot reach collected-logs
Both producers log it as a Warning with NO exception:
src/MeshWeaver.Hosting/MeshNodeStreamCache.cs:2283—logger.LogWarning("MeshNodeStreamCache.GetStream: Content for {Path} stayed an untyped JsonElement after …", node.Path, TruncateRaw(je))src/MeshWeaver.Hosting/MeshNodeStreamCache.cs:2717— the same, fromGetQuery
The sink that feeds the collected file takes a record only when there IS an exception:
test/MeshWeaver.Fixture/LoggingBuilderExtensions.cs:193—var isFaultCandidate = exception is not null && logLevel >= LogLevel.Warning;test/MeshWeaver.Fixture/XUnitFileOutputHelper.cs:507— the same condition guardsTestTraceLog.AppendFault
so neither warning ever reaches collected-logs/_meshweaver-test-trace.log.
The other route into the directory is off in this lane. The collect step globs */bin/*/test-logs/*.log; those per-method files are opt-in via MESHWEAVER_TEST_FILE_LOGS, which is set only in node-repo-module-pack.yml (lines 2163, 2553) — never in dotnet-test.yml.
Nothing else in collected-logs can carry it: symbols-*/ (signal deaths only), _meshweaver-memory-delta.log (numbers), _meshweaver-msg-trace.log (off unless MESHWEAVER_MSG_TRACE=1), *.dmp, and teardown-stragglers.log — which records only ObjectDisposedException-shaped stragglers and unhandled exceptions, never an arbitrary log line.
Measured locally, with a positive control
Ran a test that deliberately seeds unreadable node content (#3619's UnreadablePolicyRecordIsNotClobberedTest). The phrase was emitted — it is in the .trx StdOut, twice per arm:
[Warning] [MeshWeaver.Hosting.MeshNodeStreamCache] MeshNodeStreamCache.GetStream: Content for
Admin/UpdatePolicy stayed an untyped JsonElement after deserialization …
and in the very file CI copies to collected-logs/_meshweaver-test-trace.log:
$ grep -c 'stayed an untyped JsonElement' "$TMPDIR/meshweaver-test-trace.log" → 0
$ grep -c 'UnreadablePolicyRecordIsNotClobbered' "$TMPDIR/meshweaver-test-trace.log" → 817
817 records naming that test class, zero occurrences of the phrase it emitted. The control is the second number: the file is not empty, and it is not missing this test — it is missing this kind of record.
Why the existing control test does not catch it
UntypedContentDegradationGate is cited in the script as the control arm, and it checks two things: the phrase still exists in the source that emits it, and the script still greps the identical string. Both are true. Neither asks whether the phrase can reach the directory the script scans — so the subject moved (the fault sink became exception-gated) and the roots did not. That is the shape AGENTS.md names: a guard whose subject moved and whose roots did not passes having checked nothing.
This is also the third recorded instance of the same underlying trap — a diagnostic carrying no exception is dropped by the trace sink (#890 and #3413 each lost their own explanation to it). The new part is that this time it silently disarms a gate.
Options
- Give the two
LogWarningcalls an exception so they reach the fault sink. Honest — a content degradation IS a fault — and it needs no workflow change. TheJsonExceptionis not always in hand at that point (theGetStreamsite reaches the warning precisely when nothing threw), so it may need a purpose-made exception type carrying the path and the raw JSON. - Add a second, unconditional sink for this one phrase — the way
TestTraceLogalready takes phase records regardless of exception (AppendPhase). Narrow, and it keeps the production log-level contract untouched. - Turn on
MESHWEAVER_TEST_FILE_LOGSfor core's shards. Rejected upstream already, and for a stated reason: the artifact upload then ships thousands of files.
Whatever the fix, the control test must be extended to assert reachability, not just the string: emit the phrase in a test, then assert it appears in TestTraceLog's file. A gate whose input cannot arrive is indistinguishable from a gate that passes, which is exactly what this one has been.
What I did NOT measure
I did not download a real shard artifact and list its collected-logs/. The claim rests on the source of both log calls, the source of the sink's condition, the collect step, the MESHWEAVER_TEST_FILE_LOGS grep across .github/, and the local trace-file measurement above. One gh run download <run-id> -n testResults-shard1 plus ls collected-logs/ would settle it directly.
Activity
rbuergi commentedon Sep 7, 2026
The template for the reachability control already exists in this repo, and it was written for the same trap one diagnostic over:
test/MeshWeaver.Compiler.Pipeline.Test/EmitDeadAttributionReachesTheTraceSinkTest.cs.It restates the sink's own condition —
— and asserts the captured record against that, not against "we passed an exception", which its own doc calls out as tautological. Reverting the fix it guards turns it red naming the sink.
Its file header also states the second half of why this matters here, measured: the job log is not an equivalent sink, because it carries only the output of tests that FAILED — so a degradation logged under a passing test is absent from both places. That is precisely the shape of a deliberately-degraded fixture, and of the production case this gate exists for.
So the missing piece for
stayed an untyped JsonElementis one file modelled on that one: emit the phrase, evaluate the sink predicate against the record, assert it would be written.fix(#3625): the gate's first real catch — every instance's module inv…
docs(#3625): classify what the repaired gate found, and the assertion…
fix(#3625): a test whose SUBJECT is a degradation ASSERTS the record …
Merge pull request #3628 from Systemorph/fix/3625-the-untyped-content…
rbuergi commentedon Sep 8, 2026
Closing — #3628 merged and the merge queue did not fire the link
#3628merged 2026-09-07T22:35:03Z carryingCloses #3625. Core'smainmerges through the mergequeue, which does not fire
Closeslinks, so this stayed open with nothing recording why. Closingby hand, and naming the evidence rather than the merge tick.
Verified on
origin/main, not inferred from the PR:MeshNodeStreamCache.csconstructsnew MeshNodeContentDegradedExceptionat 3 seamssrc/MeshWeaver.Data.Contract/MeshNodeContentDegradedException.cscheck-untyped-content.shnow carriesMARKER='MeshNodeContentDegradedException'as the primary key, withPHRASE='stayed an untyped JsonElement'kept only as a second nettest/MeshWeaver.Hosting.Test/UntypedContentDegradationReachesTheTraceSinkTest.csSo the defect this issue names — "the gate cannot fire" — is closed: the record now satisfies the
sink's own predicate (
exception is not null && logLevel >= Warning), and the gate keys on theevent's identity rather than on its description, which is what stops a rewording from silently
retiring it again.
🚨 This is not a claim that the gate is now perfectly calibrated. With it able to fire it
immediately reported three tests in
LateContentTypeRegistrationTestwhose SUBJECT is genuinelyunresolvable content, and it cannot presently tell a normal TRANSIENT degradation — a NodeType whose
runtime compile has not registered yet, which recovers within the same test — from a permanent one.
That is a real remaining defect and it is tracked separately as #3645, with the mechanism and
three candidate directions. It is deliberately not folded back into this issue: what this one asked
for was a gate that can fire, and that is done and verified.
Reopen if a degradation record is found that reaches a shard's logs without the marker.