Repository navigation
Conversation
Maintainer, 2026-08-30: "also doc safety etc. same standard as with normal build … see that you tune it to same level. nullability etc … and doc references and comment references etc … diagnostic must go to activity." MEASURED FROM THE OUTSIDE FIRST. A deliberate CS0219 — an unused local — added to an in-mesh source compiled `ok` with "0 warning(s)". Not a clean build: the ABSENCE OF A REPORT. `emitResult.Diagnostics` is read only when Success is false, so every warning a green compile produced was discarded. That is why in-mesh C# was not held to the standard the compiled half is held to under -warnaserror. No unused-code warnings, and therefore no doc-comment and no cref ones either — even though CreateParseOptions has ALWAYS asked for DocumentationMode.Diagnose. The diagnostics were being produced and thrown away. They now travel to the compile ACTIVITY, which is where a diagnostic belongs: it is the record of that compile, what a reader opens, what the Overview/Tests areas render, and what a runner can stream. A warning that only reached a server log reached nobody. The route respects the log-once contract rather than working around it. EmitPipeline deliberately does not log — logging failures there once double-counted every compile failure in production (~150 ERROR lines/24h were ~72 real failures logged twice, the duplicate arriving first and context-free, which is what made "your C# does not compile" read like an emit/IO defect). So the warnings are RETURNED, on EmittedArtifact, and appended by the same single funnel that reports a failure — as Warning severity, which ActivityLog.Finish leaves alone, so surfacing them cannot turn a green compile red on its own. Both emit paths report the same thing. The in-memory path reads compilation.GetDiagnostics() explicitly: a warning that appeared only when the disk cache was on would be a warning that depends on a deployment's caching mode, which is exactly the kind of difference nobody finds. Deduped, ordered, and CAPPED at 50 with the remainder COUNTED in the last entry — one bad using-directive produces hundreds of identical diagnostics, and an activity that is 400 lines of the same warning is one nobody reads. A truncation a reader cannot see is a lie about how much was wrong. Four cases pin it, including the two that matter for a cross-repo refactor: an unresolvable cref (CS1574/CS1584) is reported, and a clean compile reports nothing — the empty case only means something now that a non-empty one is possible. 🚨 NOT CHANGED HERE, deliberately: the compilation OPTIONS. Adding a nullable context is the other half of "the same level", and it moves EmitPipeline.OptionsFingerprint — which is reflected over the options, so every cached assembly in the fleet is invalidated and recompiles. That is an operational event to schedule, not a rider on a diagnostics fix. This change is fingerprint-neutral: it reads diagnostics that were already being computed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011vRaDpW29KDS9NX1gvCjw3
… ours
Maintainer, 2026-08-30: "we are about to remove all nuget dependencies — for
tests. All repos. Check plugins how we do it."
Checked and measured rather than assumed. The Plugins idiom is stricter than
"no xUnit": across all 206 in-mesh test node sets, `using Xunit` occurs ZERO
times, and so does every other assertion library — including our own
Reactive.Assertions. A test is plain C# that throws, with a local four-line
Expect(bool, string) helper owned by the file; a TestsArea enumerates
(name, Action) cases and renders the pass/fail table, registered with a
LITERAL .WithView("Tests", …) because the gate reads that field as text; the
gate executes it on a probe instance and the Tests-area ratchet holds the
population.
So a test compiles against exactly what the image's framework provides plus
its own type's Source: nothing to restore, nothing to version, nothing that
can drift against the framework under test. What retires with the compiled
test projects is the whole test-dependency graph — xUnit v3, FluentAssertions,
the runner, the .trx machinery, and the restore step feeding them — not just
MeshWeaver.Fixture.
Deliberately-given-up list stated so it is a decision, not a discovery:
parametrised theories, runner-process isolation, IDE test explorer, .trx
artifacts. The gate log and the rendered table are the record; the trade has
been paid 206 times in production without a request for any of the four back.
This also RESOLVES the doc's own open question ("deciding what the in-mesh
unit-test surface is" for module tests): the surface exists, it is this, and
it has 206 production instances.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…as optional
`PackageInstaller` waited up to 30 s for a partition's cover grant while the
wait's own doc comment called that grant optional — "a partition whose node
type does not gate never writes it". On the shape the code itself called
*normal* the query can never emit, so every install burned the entire budget
and then reported success, leaving one Information line worded to cover both
outcomes. Measured in `MeshWeaver.PluginCatalog.Test`: 30.25 s / 30.29 s /
30.33 s / 30.24 s per install and 60.28 s for the test that installs twice —
181 s of a 336 s suite. `PackageInstaller.Install` is the production install
path, so a live mesh paid the same 30 s per non-gating package.
Core cannot ask a node type whether it gates (`Store/Plugin`, `PluginContent`
and `AddPluginGating` live in MeshWeaver.Plugins), which is why the grant is a
well-known PATH. It does not have to: `EnsureDeclaredAccess` is the ONE
access-establishment step of an install and already states, per manifest,
whether it published the partition or left it gated. `DeclaredAccessMarker` is
null exactly when it wrote nothing — the commercial branch — and that is the
only shape a gating pass owes a cover grant. `CoverGrantExpected` is that, and
the watch now runs only there.
- `GatingSettleTimeout` (30 s) → `GatingDetectorBudget` (5 s), renamed for what
it now is. Justified by measurement: when a grant IS owed it is observable in
44–55 ms (`GatingDetectorTest` measures the real write→observe latency on a
live mesh), and phase 1 has already paid the activation, so the only
outstanding work is one access-table write.
- The two outcomes stop looking identical: the expiry is a WARNING naming the
missing path and the consequence (the partition denies every viewer,
including on the Subscribe cover that would sell it); "nothing owed" is Debug.
Warning, not Error — the expiry proves the grant is not there yet, not that
it never will be, and `VerifyDeclaredAccess` owns the Error verdict for
access core itself promised to write.
- Still never fatal, and the query is unchanged: `path:{grant}` exact,
empty-on-absent, never a stream read (#2229) and never a children listing.
`GatingDetectorTest` pins all three outcomes and is mutation-proven: downgrading
the warning, disabling `CoverGrantExpected`, or restoring the 30 s budget each
turns it red.
Suite: 707/707 green, 5 m 36 s → 2 m 49 s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er, no SDK, no NuGet Maintainer directive, 2026-08-30: "the platform builds dll completely without any external dotnet kit or nuget." `memex build project <csproj|dir> --image <image>` compiles a .NET project with NO dotnet SDK and NO NuGet restore, against the assemblies of a MeshWeaver image. The CLI half is only the trip into the container — it shares ImageRunner (pull retry, digest pin, process launch) with `build plugin` rather than carrying a second copy. The work is `mw-plugin-test build-project`, three types in the tool already in the image: * ProjectFile — evaluates the .csproj WITHOUT MSBuild: properties, items, the default **/*.cs glob minus bin/obj, Compile Include/Remove, ProjectReference, PackageReference, implicit usings, the target-framework symbol ladder, the SDK's own default NoWarn (1701;1702), the nearest Directory.Build.props/.targets/Directory.Packages.props, and every <Import> whose condition holds. Anything it cannot reproduce FAILS the load by name; --accept acknowledges one. A silently dropped Nullable or NoWarn is a green build that is not the SDK's build. * ContainerReferenceSet — the C# port of MeshWeaver.Plugins/scripts/container-refs.py, read from /app instead of an extracted image: the image's own .deps.json for versions and the binding identity, /app plus the container's shared frameworks for the assemblies. Fails closed on an unreadable /app, a missing or ambiguous .deps.json, or MeshWeaver assemblies that disagree on their binding identity (MeshWeaver#143, caught in the image rather than at run time). A package is matched by the ASSEMBLY FILE on disk, never by its id alone; one the container does not supply is an ADDITIONAL library, reported by name and never skipped. * ProjectBuild — sequences the ProjectReference graph on the existing Cascade (a cycle is refused up front and named), compiles with Roslyn, and emits through the platform's own EmitPipeline. It builds its OWN CSharpCompilationOptions and never touches CreateCompilationOptions, which feeds GeneratedInputIdentity.OptionsFingerprint. Every progress line and diagnostic is streamed into an ActivityLog (ActivityCategory.Compilation) as it is produced; the console is a rendering of that stream. Measured against memex-portal-ai@sha256:15c49ee over MeshWeaver.Plugins/src: 12 of 54 non-test projects green, including MeshWeaver.Import (90 sources, 0 warnings under warnings-as-errors) with ClosedXML + CsvHelper as --extra-refs. The rest are named limitations: Razor/Blazor (15), SDK source generators (15), <Protobuf> (3), additional libraries (5), portal hosts (3). Also here: BuildPluginCommand's docker plumbing moves to ImageRunner (and gains `--init`, the convention every workflow already uses), and MeshWeaver.Compiler grants InternalsVisibleTo to mw-plugin-test so the emit is SHARED rather than re-implemented. 51 new tests, executed: csproj evaluation (globs, Remove, Directory.Build.props inheritance, the condition grammar, every loud refusal), reference resolution (assembly-on-disk matching, the metapackage case, the additional-library case, all five fail-closed paths), ProjectReference ordering and cycle detection, and the diagnostic standard — an unresolvable cref and an unused local are asserted to be REPORTED, and the emitted assembly is LOADED and invoked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/MeshWeaver.Cli/BuildPluginCommand.cs
…r kills the whole build The first real `mw-plugin-test build` run on a dev machine died with exit 70 and ZERO bytes on stdout and stderr. Two defects compounded. Every early fatal was mute: Print lives at the end of Finish, which only the successful pipeline reaches — LoadSync throwing, no packages, an unknown package id, LoadExternalModules refusing, and Run's outer Catch all returned their Report straight to Program, which turned it into an exit code and nothing else. Run now guarantees at its one seam that a FatalError report prints (full exception, stack included) and writes report.json when asked; Finish never sets FatalError, so nothing prints twice. With the silence fixed the real cause self-identified: LocalNodeRepo.Sweep eagerly reads EVERY file under the repo root and died on a dead Playwright symlink (e2e/.auth/profile/RunningChromeVersion) — gitignored, so invisible to every CI checkout, fatal for exactly the local no-dotnet loop the verb exists for. The sweep now skips an unreadable file loudly and continues; such a file crashed both bake paths before, so the shared swept-content contract cannot fork. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoG4USP5fd8wsxCD7e9q4x
…vity' into integration/core-batch
…' into integration/core-batch
…tegration/core-batch
…egration/core-batch
…into integration/core-batch
`<EmbeddedResource>` items are EMBEDDED now, each under the manifest name the SDK's own `CreateCSharpManifestResourceName` would have produced — the single biggest coverage gap in the SDK-free builder, blocking 19 of the 54 non-test projects in MeshWeaver.Plugins/src on nothing but `<EmbeddedResource Include="Data\**\*.md">`. #2850 refused them deliberately, and the reasoning was right: a wrong manifest-resource name is exactly the silent difference that design exists to prevent. The assembly compiles, ships, loads, and GetManifestResourceStream returns null at run time in another process — no error, no failing test, nothing a review can see. Core's own MeshWeaver.Messaging.Hub.csproj already carries a comment describing that outcome verbatim. So no naming rule here was recalled. Each was established by building a probe project with the real .NET SDK and reading the manifest-resource table back out of the emitted PE — including the asymmetry that makes the whole thing treacherous: the DIRECTORY is mangled into identifiers and the FILE NAME is not. Supported, measured: the default $(RootNamespace).<mangled dir>.<file name>; %(TargetPath) beating %(Link) beating the item spec; a file outside the project losing its directory entirely; LogicalName as attribute or child element; Include/Exclude/Remove/Update; the SDK's default **/*.resx glob; PUBLIC resources; CS1566 for a missing literal include; and $(AssemblyName) / $(RootNamespace) defaulting to the PROJECT name, which fixes an $(AssemblyName)-expands-to-empty bug this change exposed. Refused BY NAME, each with its own --accept token: .resx/.restext (the name is reproducible, the content needs resgen), a culture in the file name (the SDK routes it to a SATELLITE assembly and an explicit LogicalName does NOT rescue it), DependentUpon (the name becomes a class name extracted by MSBuild's own tokenizer), ManifestResourceName (the SDK's handling is a quirk), and embedding the build's own output. EmitPipeline gains an OVERLOAD rather than an optional parameter: adding a parameter replaces a method's signature, and the four-argument entry point stays byte-identical for MeshNodeCompilationService and NodeSetCompiler. CreateCompilationOptions and OptionsFingerprint are untouched. Verified by running: 226 tests green (32 new), including a PARITY test that builds one probe project with the real SDK and with build-project and asserts the manifest name sets are identical, plus three mutation proofs. Sweep over all 54 non-test projects in MeshWeaver.Plugins/src against the pinned portal image. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… obvious thing with All three found by continuing to MEASURE rather than by reading the code back, and each would have been silent. 1. An EXPLICIT %(Culture) beats %(WithCulture)='false' — the opposite of what the two names suggest. `Include="A.md" Culture="fr" WithCulture="false"` still emits a fr/ SATELLITE and leaves the main assembly without the resource, while `Include="C.de.md" WithCulture="false"` keeps it. The check order was the other way round, which would have embedded, under a name the SDK never produces, a resource the SDK does not put in this assembly at all. 2. A glob reaching OUTSIDE the project expanded to NOTHING — this evaluator enumerates from the project directory down — and an empty glob is legal, so the resources were simply absent with no line of output saying so. The real SDK expands `..\lb\**\*.md` and embeds what it finds. Now a named refusal. 3. %(LinkBase) synthesizes a %(Link) of `<LinkBase>\%(RecursiveDir)%(Filename) %(Extension)` for a file OUTSIDE the project cone and is IGNORED for one inside it — measured both ways. Only the outside case changes a name, so only the outside case is refused; refusing the inside one would be a false refusal on a no-op. Also: the measured sweep table, and the WhatsNew entry. The sweep number is 9 → 10 green of 54, not the 9 → ~24 the gap size suggested, and the reason is worth writing down: a refusal STOPS THE LOAD, so it masks every gap behind it. The 19 projects failing on the resource refusal were not 19 projects away from green — they were 19 projects about which nothing further was known. Ten of them want SDK source generators (the previous sweep counted 3), three want Razor, three want an additional library, one wants protoc. No project regressed, and none fails on an embedded resource any more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the SDK-free build-project pipeline so compiled projects can embed <EmbeddedResource> items under the same manifest-resource names the .NET SDK would generate, and tightens build/install observability (compile warnings surfaced in activities; install gating stall detection documented and tested). It also wires memex build project into the CLI and factors Docker image execution into a shared runner.
Changes:
- Add SDK-faithful manifest resource naming and container reference-set reading for
mw-plugin-test build-project, plus extensive new unit/integration tests. - Extend the compiler emit pipeline to carry compile warnings (even on successful emits) up to activities, and add tests pinning warning capture/formatting.
- Add docs + tests around install readability / cover-grant detection, and add a new CLI verb (
memex build project) with shared image-running plumbing.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/MeshWeaver.PluginTester/README.md | Documents build-project behavior and supported/refused constructs. |
| tools/MeshWeaver.PluginTester/Program.cs | Adds argv parsing + verb dispatch for build-project. |
| tools/MeshWeaver.PluginTester/ManifestResourceNames.cs | Implements SDK-equivalent manifest resource naming logic. |
| tools/MeshWeaver.PluginTester/LocalNodeRepo.cs | Skips unreadable files loudly during sweeps (avoid silent/fatal failures). |
| tools/MeshWeaver.PluginTester/ContainerReferenceSet.cs | Reads /app + deps.json to derive container reference set and package versions. |
| tools/MeshWeaver.PluginTester/CascadeBuild.cs | Ensures fatal errors are printed and report JSON is written for machine consumption. |
| test/MeshWeaver.PluginTester.Test/ProjectFileTest.cs | Pins evaluator contract, including EmbeddedResource behavior. |
| test/MeshWeaver.PluginTester.Test/ProjectBuildTest.cs | End-to-end tests for container-based compile + emit + load. |
| test/MeshWeaver.PluginTester.Test/ManifestResourceNamesTest.cs | Pins measured SDK manifest-resource naming edge cases. |
| test/MeshWeaver.PluginTester.Test/ContainerReferenceSetTest.cs | Pins fail-closed behavior and “match by assembly file on disk” semantics. |
| test/MeshWeaver.PluginCatalog.Test/InstallGatingHandshakeTest.cs | Updates contract commentary for cover-grant watch semantics. |
| test/MeshWeaver.PluginCatalog.Test/GatingDetectorTest.cs | Adds live-mesh tests pinning detector outcomes + budget + logging level. |
| test/MeshWeaver.Graph.Test/EmitPipelineAccess.cs | Thin internal access for warning tests to use canonical compiler options. |
| test/MeshWeaver.Graph.Test/CompileWarningsReachTheActivityTest.cs | Pins warning capture behavior (including doc/cref diagnostics) and capping. |
| src/MeshWeaver.Graph/Configuration/MeshNodeCompilationService.cs | Plumbs warnings from emit/diagnostics into compile activity logs. |
| src/MeshWeaver.Compiler/MeshWeaver.Compiler.csproj | Adds IVT for mw-plugin-test to reuse internal emit pipeline pieces. |
| src/MeshWeaver.Compiler/EmittedArtifact.cs | Extends emitted artifact model to carry warnings alongside digest/length. |
| src/MeshWeaver.Compiler/EmitPipeline.cs | Adds warning extraction/capping + emit overload supporting manifest resources. |
| src/MeshWeaver.Cli/README.md | Documents memex build project usage and option meanings. |
| src/MeshWeaver.Cli/Program.cs | Wires build project subcommand into CLI. |
| src/MeshWeaver.Cli/ImageRunner.cs | New shared Docker pull/pin/run plumbing used by CLI build verbs. |
| src/MeshWeaver.Cli/BuildProjectCommand.cs | New CLI command that runs mw-plugin-test build-project in an image. |
| src/MeshWeaver.Cli/BuildPluginCommand.cs | Refactors to reuse shared ImageRunner logic. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-31-the-sdk-free-builder-embeds-resources-under-the-sdks-own-names.md | What’s New entry for embedded-resource support in SDK-free builder. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-31-installs-no-longer-wait-30-seconds-for-a-node-that-is-optional.md | What’s New entry for install gating stall detector behavior change. |
| src/MeshWeaver.Documentation/Data/Architecture/Plugins.md | Adds cross-link/context for install readability and gating grant path contract. |
| src/MeshWeaver.Documentation/Data/Architecture/InstallReadability.md | New architecture page documenting the two “readability doors” + detector rationale. |
| src/MeshWeaver.Documentation/Data/Architecture/InMeshBuildAndTest.md | Adds sections documenting compiled-project build without SDK + embedded-resource semantics. |
| src/MeshWeaver.Documentation/Data/Architecture.md | Adds Install Readability to the Architecture index table. |
Suppressed comments (1)
src/MeshWeaver.Cli/BuildProjectCommand.cs:106
- When running in "already inside the image" mode (no
--image),ExtraReferenceDirectoriesare never forwarded to the in-image builder. That makesmemex build project ... --extra-refs ...behave differently depending on whether it is invoked outside vs. inside a MeshWeaver image.
var local = new List<string> { BuilderVerb, entry, "--output", outputDirectory };
local.AddRange(builderArgs.Skip(4));
return await _runner.Exec(InImageBuilder, local, ct);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (!doc.RootElement.TryGetProperty("targets", out var targets) | ||
| || targets.ValueKind != JsonValueKind.Object) | ||
| throw new UnreadableContainerException( | ||
| $"{Path.GetFileName(path)} has no targets section, so no assembly version can be read."); | ||
|
|
||
| JsonElement runtimeTarget = default; | ||
| foreach (var target in targets.EnumerateObject()) | ||
| runtimeTarget = target.Value; | ||
|
|
| public async Task<int> RunAsync(BuildProjectOptions options, CancellationToken ct) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(options); | ||
|
|
||
| var entry = Path.GetFullPath(options.ProjectPath); | ||
| if (!File.Exists(entry) && !Directory.Exists(entry)) | ||
| { | ||
| await error.WriteLineAsync($"error: '{entry}' is neither a project file nor a directory."); | ||
| return 2; |
| public async Task<string?> PullImage(string image, CancellationToken ct) | ||
| { | ||
| for (var attempt = 1; attempt <= PullAttempts; attempt++) | ||
| { | ||
| if (await Exec("docker", ["pull", image], ct) == 0) |
…d CLI conflict as #2850 This PR was DIRTY, so it was running no CI at all. Its branch is built on integration/core-batch (#2853), so it carries #2850's ImageRunner extraction and hit the identical conflict in src/MeshWeaver.Cli/BuildPluginCommand.cs — one 159-line block with an empty "ours" side, holding TWO unrelated things: * PullImage, which this history relocated to src/MeshWeaver.Cli/ImageRunner.cs; * main's ComposeSealedModules (#2851, #2858), which is genuinely new. "Take theirs" duplicates PullImage; "take ours" silently deletes main's method. Split at the seam instead: dropped 39 lines (relocated), kept 120 (main's new). Then the same two follow-ons the compiler names — ImageRunner.Capture is static so main's auto-merged call needed qualifying, and the split orphaned PullImage's `/// <summary>` opener (CS1570). 🚨 Verified neither side was lost, not just that it builds: ComposeSealedModules/StageModuleBundle present 5 references PullImage defined once — ImageRunner 1, BuildPluginCommand 0 MeshWeaver.Cli / PluginTester / PluginTester.Test -c Release -warnaserror, 0/0 MeshWeaver.PluginTester.Test 229/229 Note #2853 is now closed — three of its four members landed individually — so this branch's core-batch ancestry is history rather than a live dependency. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ompat red was a BASE artefact This PR was DIRTY (no CI at all) and additionally red on "Public surface (binary compatibility)". Both are addressed by merging main. ── the conflict (same as #2850/#2860) ── One 159-line block in src/MeshWeaver.Cli/BuildPluginCommand.cs holding TWO unrelated things: PullImage (relocated to ImageRunner.cs by this lane) and main's new ComposeSealedModules (#2851, #2858). Split at the seam — dropped 39 lines relocated, kept 120 of main's — then qualified ImageRunner.Capture (now static) and removed the `/// <summary>` opener the split orphaned. ── the binary-compat failure was measured against the WRONG BASE ── The gate said: Comparing against merge base ... branch integration/core-batch ✗ tools/MeshWeaver.PluginTester/ProjectFile.cs record Model — arity: primary constructor went from 21 to 22 parameter(s) 🚨 1 binary-breaking record change(s) That is a correct verdict about the wrong comparison. `ProjectFile.cs` DOES NOT EXIST ON MAIN — the Model record is introduced by #2850's lane, and the gate was comparing against integration/core-batch (#2853), where it existed at arity 21. Against main the record is NEW, so there is no prior signature to break. Not suppressed and NOT added to scripts/record-signatures.allow: an allow entry would assert a deliberate break where there is none, and would then go stale the moment #2850 lands (the shape that has blocked unrelated PRs before). Re-basing onto main is the fix; the gate re-runs against main and should now pass on its own. If it does not, the break is real and belongs in the allow file with a reason — but that is not what this data shows. MeshWeaver.PluginTester / .Test -c Release -warnaserror, 0 Warning(s) 0 Error(s) MeshWeaver.PluginTester.Test 198/198 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Test Results (shard 0)792 tests 788 ✅ 4m 23s ⏱️ Results for commit 38d2142. |
Test Results (shard 1)1 083 tests 1 083 ✅ 3m 43s ⏱️ Results for commit 38d2142. |
Test Results (shard 5) 8 files 8 suites 3m 59s ⏱️ Results for commit 38d2142. |
Test Results (shard 4)1 158 tests 1 158 ✅ 4m 28s ⏱️ Results for commit 38d2142. |
Test Results (shard 2)3 057 tests 2 865 ✅ 6m 36s ⏱️ Results for commit 38d2142. |
Test Results (shard 3)1 140 tests 1 140 ✅ 8m 11s ⏱️ Results for commit 38d2142. |
Test Results 44 files 44 suites 31m 22s ⏱️ Results for commit 38d2142. |
Removed from the merge queue —
|
|
Superseded by #2892, now merged as |
<EmbeddedResource>items are embedded now, each under the manifest name the SDK's ownCreateCSharpManifestResourceNamewould have produced.Measured over the 54 non-test projects in
MeshWeaver.Plugins/src, this was the single biggest coverage gap: 19 failed on nothing else, most on one line —<EmbeddedResource Include="Data\**\*.md">.Why #2850 refused it, and why that was right
Its PR body says a wrong manifest-resource name "is exactly the silent difference the design exists to prevent". That judgement was correct and this PR does not relax it. A wrong resource name fails nothing: the assembly compiles, the emit is verified, the DLL loads — and
Assembly.GetManifestResourceStream(name)returnsnullat run time, in another process, later. Core's ownMeshWeaver.Messaging.Hub.csprojalready carries a comment describing that exact outcome for itsstrings.*.json: "the build succeeds, the main assembly carries ZERO manifest resources, every lookup falls through to the key-fallback path, and the UI renders rawchat.newtokens."So every rule here was established by measurement, not recall — 15 probe projects built with the real .NET SDK, the names read back out of the emitted PE's
ManifestResourcetable. Where a rule could not be matched, the construct is refused by name.The rules, and how each was measured
The SDK's pipeline, reproduced step for step:
AssignTargetPathassigns%(TargetPath)(Microsoft.Common.CurrentVersion.targets says so out loud — "AssignTargetPath generates TargetPath metadata that is consumed by CreateManifestResourceNames target for manifest name generation"),AssignCulturesplits culture-carrying items off towards satellites,CreateCSharpManifestResourceNameturns the survivors into$(RootNamespace).<mangled dir>.<file name>.Data\with-dash\Three.md→…Data.with_dash.Three.md;Weird-File.Name.mdkeeps hyphen and dot9digits→_9digitsDot.9Dir→Dot._9Dir— each half mangled alone_DOUBLES--and_both →__; a project with both sibling dirs fails the real SDK withCS1508— that failure is the measurement$(RootNamespace)defaults to the PROJECT name$(AssemblyName)— proven with a project whose two differ; empty ⇒ no prefix at all%(TargetPath)>%(Link)> item specLinkrenames a file already inside the project..\shared\Shared.md→<ns>.Shared.md— no.., nosharedManifestResourceAttributes.Public, every one**/*.resx.resxis a refusal not a silent omissionCS1566; an empty glob is legal.resourcesneeds no special caseRefused by name, each with its own
--accepttoken.resx/.restext.resourcesname compiles green and throws at run time. Refusing loudly is the honest outcomeLogicalNamedoes not rescue it (measured).WithCulture="false"is the project-side fix and needs no acceptanceDependentUponstruct/interface/enum, takesrecord, drops generic arityManifestResourceName%(LogicalName), so csc falls back to the bare file name — the metadata does not do what it appears toLinkBaseoutside the project%(Link)this evaluator does not compute. Ignored inside the project, so only the outside case is refusedInclude="bin\…\$(AssemblyName).xml"builds green under the real SDK from clean (csc writes/doc:and reads/resource:in one invocation); named and skipped rather than failing the projectUnder invariant globalization the runtime reports no predefined culture even for
de, so the culture question cannot be answered — the capability is probed, and any dotted-basename resource is refused rather than guessed.Three findings worth calling out
%(Culture)beats%(WithCulture)='false'— the opposite of what the names suggest. My first implementation had the order backwards and would have embedded, under a name the SDK never produces, a resource the SDK puts in a satellite.$(AssemblyName)and$(RootNamespace)were never seeded, so$(AssemblyName)expanded to the empty string.MeshWeaver.Northwind.Domainreads it in an<EmbeddedResource Include="bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml">, which resolved to a file called.xml. Both now default to the project name, exactly whereMicrosoft.NET.Sdk.propssets them.Compileglobs today (pre-existing, not touched here) — worth a follow-up.Verified by RUNNING
MeshWeaver.Graph.Test1602 green — theEmitPipelineconsumers are unaffected.build-project, and the manifest name sets must be identical. It does not skip when the SDK is missing — a parity test that passes because it could not find its oracle is worse than none.MeshWeaver.Northwind.Model(11 glob-matched CSVs) built by the real SDK and bybuild-projectinside the combined tester+portal image — all 11 names AND their contents identical, byte for byte (SHA-256 per resource).The sweep: 9 → 10 of 54, and why that is the honest number
Both columns from the same image (
memex-portal-ai@sha256:6f38db08…, the pin inMeshWeaver.Plugins/.github/workflows/ci.yml),--accept targets, no--extra-refs. The "before" column was produced by building the pre-change builder fromorigin/integration/core-batchinto its own image and sweeping it — it reproduces the reported baseline of 9 exactly.<EmbeddedResource>refused<Protobuf><Import>above the mount<Sdk>element🚨 This is not the 9 → ~24 the gap size suggested, and the reason matters. A refusal stops the load, so it MASKS every gap behind it. Those 19 projects were never "19 projects away from green" — they were 19 projects about which nothing further was known. Closing the gap turned one green and revealed the real blocker for the other eighteen: ten want SDK source generators, three want Razor, three want an additional library, one wants protoc. That is why the old sweep counted 3 source-generator failures when the true figure is 14.
In a builder designed to refuse rather than guess, a failure ranking is a ranking of FIRST refusals. Removing the top entry redistributes its count; it does not add it to the green column. Razor (#2856) is now the largest remaining gap at 15, with source generators right behind at 14.
No project regressed, and none fails on an embedded resource any more.
Notes for review
EmitPipelinegains an OVERLOAD, not an optional parameter — adding a parameter replaces a method's signature, the same binary breakscripts/check-record-signatures.pyrefuses for records. The four-argument entry point is byte-identical forMeshNodeCompilationServiceandNodeSetCompiler, which pass no resources and never will.CreateCompilationOptionsandOptionsFingerprintare untouched.initproperties, not primary-constructor parameters — following feat(image): the container compiles Razor — the SDK-free build covers Blazor #2856's discipline.check-record-signatures.pypasses.AssemblyInfopositional parameter toProjectFile.Modeland feat(image): the container compiles Razor — the SDK-free build covers Blazor #2856 addsinitproperties to it; this PR only addsinitproperties, so the conflicts should be mechanical. All three also touchProjectFile.EvaluateItemandProjectBuild's compile path.ProjectFileTest.AnEmbeddedResourceFailsUntilAcknowledged_…asserted the old refusal and is replaced by one asserting the resolved SDK manifest name.🤖 Generated with Claude Code