Skip to content

feat(build-project): embedded resources + assembly identity — the batch that unblocks memex-as-compiler - #2892

Merged
rbuergi merged 4 commits into
mainfrom
integration/build-project-batch
Aug 31, 2026
Merged

rbuergi merged 4 commits into
mainfrom
integration/build-project-batch

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Replaces #2855 and #2860, which cannot pass the merge queue as they stand: their branches descend from integration/core-batch and carry the originals of commits main has as squashes, so they read CLEAN on their pages and come back UNMERGEABLE from the queue, every time. This branch is current main + exactly their three unique commits, cherry-picked and union-resolved:

a3a07ac54  feat(build-project): embedded resources, with the SDK's manifest names
df443bcf7  fix(build-project): three more resource rules the SDK does not do the obvious thing with
bd5a1622b  fix(build-project): the emitted assembly carries the platform's binding identity

The resolution is a UNION, and the naive alternatives both lose a feature

Main gained Razor support (#2856, via #2850) in the same files these commits extend. Taking either side wholesale is destructive — checking the branch's files onto main would have deleted RazorGenerators.cs (−494 lines) and RazorBuildTest (−387), measured before resolving, which is why every conflicted region was merged by hand instead:

  • ProjectFile.cs — Razor's record/items/globs and the EmbeddedResource ordered log and the AssemblyInfo records now coexist; main's stricter exact-suffix glob helper kept; main's case "None" grouping (a .razor can arrive as a None item) kept over the branch's.
  • ProjectBuild.cs — RazorCount and ResourceCount on ProjectResult; the OK-log line reports sources + Razor files + resources.
  • Program.cs / READMEs — usage and help are the union of --razor-generators and -p:Name=Value.

Proof neither feature was lost: MeshWeaver.PluginTester.Test runs 265/265 — more than either source branch alone (#2860: 229, #2855: 198), because it is both suites together.

Why this is priority: it un-blocks the memex-as-compiler lane, measured

Swept all 49 non-test MeshWeaver.Plugins/src projects through build-project locally (native, fresh portal reference set built from current main):

builder green
main today (Razor only) 9
this batch (+ resources, + identity) AI-family refusal gone — MeshWeaver.AI itself: 169 sources, 0 errors, GREEN
this batch + the SDK's regex-generator DLL via --generators AI satellites flipping green in the running sweep (Anthropic ✅, AppleIntelligence ✅ …)

Three separate blockers fell in one afternoon: the <EmbeddedResource> refusal (this batch), the stale-reference CS0436s (fresh app), and CS8795 ([GeneratedRegex]) — closed by handing the builder the SDK's own System.Text.RegularExpressions.Generator.dll, which answers #2854's "source generators are the honest unknown" with a measured yes, they run.

Full final numbers land on #2854 when the sweep completes; the per-module opt-in list there grows from measurement, exactly as that draft prescribes.

MeshWeaver.PluginTester / .Test / MeshWeaver.Cli / MeshWeaver.Compiler: -c Release -warnaserror, 0 Warning(s) 0 Error(s).

(#2855 and #2860 will be closed in favour of this once it lands — their content is byte-identical here, their history is what could not merge.)

🤖 Generated with Claude Code

rbuergi and others added 3 commits August 31, 2026 09:27
`<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">`.

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>
(cherry picked from commit 62910be)
… 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>
(cherry picked from commit a8f38af)
…ng identity

`GenerateAssemblyInfo` is a TARGET, and `build-project` runs no targets — so every
assembly it emitted carried Roslyn's default identity, `AssemblyVersion=0.0.0.0`,
while the whole fleet binds `3.0.0.0`.

Nothing went red. Measured first-hand: building
`MeshWeaver.Plugins/src/MeshWeaver.Speech.Contract` through a tester image from
`src/Directory.Build.props` pins `3.0.0.0` precisely because #143 shipped 1.0.0.0
assemblies into a 3.0.0.0 process and CrashLoopBackOff'd a migration. The failure
would have surfaced in another repo, at runtime, as
`FileNotFoundException: … Version=3.0.0.0`.

`ProjectFile` now evaluates the identity and `ProjectBuild` synthesizes it into the
compilation as one more source document, exactly as the SDK's generated
`<Project>.AssemblyInfo.cs` is one more Compile item. Every rule was MEASURED against
SDK 10.0.400 rather than recalled — `Version=1.2.3-beta.4` → `1.2.3.0`, `1` →
`1.0.0.0`, `01.2.3` → `1.2.3.0`, `FileVersion` follows `AssemblyVersion` and never
`$(Version)`.

Also promoted out of this evaluator's "metadata that changes nothing" list, which was
true of the COMPILE and false of the ASSEMBLY: `InternalsVisibleTo` (sixty-odd
projects across these repos), `AssemblyMetadata` and `AssemblyAttribute` items.

`GenerateAssemblyInfo=false` synthesizes nothing (a second set is CS0579); each
`Generate…Attribute` switch is honoured. A property that cannot be derived is a NAMED
failure, never a plausible substitute.

Two divergences, both said out loud rather than hidden: `$(SourceRevisionId)` (the
SDK reads the commit from git; every build here reports the absent `+<sha>` suffix by
name, and the new `-p:Name=Value` gives exact parity) and `TargetFrameworkAttribute`
(a different target, with platform companions this evaluator cannot compute).

`-p:` also fixes a global property that was documented to win and did not: a
`<PropertyGroup>` could overwrite one, so the flag would have meant nothing for
exactly the properties a caller passes it for.

🚨 `CreateCompilationOptions` and `OptionsFingerprint` are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2e353f2)
Copilot AI lite review requested due to automatic review settings August 31, 2026 07:42
… a 22nd parameter

The binary-compat gate refused this batch — correctly, this time:

    record Model — arity: primary constructor went from 21 to 22 parameter(s)

#2855's original commit passed AssemblyInfo positionally. Its green attested to
a stale merge base (integration/core-batch) where the arity comparison ran
against a tree that already carried the change — the wrong-base shape its own
PR thread documented for a DIFFERENT record the same evening. Against main the
break is real, and the record's own doc block states the house rule the commit
violated:

    "These four are init PROPERTIES, not primary-constructor parameters. Adding
     a parameter — even with a default — REPLACES the record's constructor
     signature, and scripts/check-record-signatures.py refuses that for exactly
     the reason it should."

So AssemblyInfo now follows RazorItems/EmbeddedResources onto the initializer:

  * Model stays at arity 21;
  * `public AssemblyInfo AssemblyInfo { get; init; } = AssemblyInfo.None;`
  * AssemblyInfo.None — the "nothing was stamped" identity — keeps the property
    non-nullable with a valid default;
  * ToModel assigns it in the object initializer beside the other features.

No allow-file entry: the gate found a real break and the fix is to not break,
not to document breaking.

MeshWeaver.PluginTester / .Test  -c Release -warnaserror  0/0
MeshWeaver.PluginTester.Test     265/265
scripts/check-record-signatures.py  ✓ no binary-breaking changes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends the SDK-free build-project pipeline to more faithfully reproduce dotnet build output by embedding managed resources under SDK-equivalent manifest names and synthesizing SDK-style assembly identity (AssemblyVersion/FileVersion/etc.) without running MSBuild targets—supporting the “memex-as-compiler” lane.

Changes:

  • Add <EmbeddedResource> evaluation + manifest-name computation (SDK parity) and pass resources into the verified emit pipeline.
  • Synthesize SDK GenerateAssemblyInfo outputs into the compilation (including additional item-derived attributes) and add MSBuild-style global properties (-p:Name=Value / --property).
  • Add extensive tests and documentation/What’s New updates covering resource naming + assembly identity parity.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/MeshWeaver.PluginTester/README.md Updates build-project documentation (usage, capabilities, limitations).
tools/MeshWeaver.PluginTester/ProjectFile.cs Adds embedded-resource + assembly-identity evaluation, global property immutability, and acceptance/refusal rules.
tools/MeshWeaver.PluginTester/ProjectBuild.cs Logs resource names, synthesizes assembly-info source, and emits managed resources via EmitPipeline.
tools/MeshWeaver.PluginTester/Program.cs Adds -p:/--property parsing for MSBuild-style global properties.
tools/MeshWeaver.PluginTester/ManifestResourceNames.cs Implements SDK-measured manifest-resource naming rules (TargetPath/Link, Everett mangling, culture probing).
test/MeshWeaver.PluginTester.Test/ProjectFileTest.cs Updates/extends evaluator tests for embedded-resource behavior and blanket accept.
test/MeshWeaver.PluginTester.Test/ProjectAssemblyIdentityTest.cs New test suite validating emitted assembly identity via PE metadata (SDK parity + overrides).
test/MeshWeaver.PluginTester.Test/ManifestResourceNamesTest.cs New unit tests pinning measured naming rules for manifest resource names.
test/MeshWeaver.PluginTester.Test/EmbeddedResourceTest.cs New end-to-end + parity tests comparing emitted manifest resources against real dotnet build.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-31-the-sdk-free-builder-embeds-resources-under-the-sdks-own-names.md Adds a user-facing What’s New entry for embedded resource support.
src/MeshWeaver.Documentation/Data/Architecture/InMeshBuildAndTest.md Updates architectural docs with embedded resource support + updated sweep reporting.
src/MeshWeaver.Compiler/EmitPipeline.cs Adds an overload to embed managed resources during verified emit (preserving existing entry point).
src/MeshWeaver.Cli/README.md Updates CLI docs around evaluator refusals and accepted constructs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 141 to +153
**Still not supported, and each says so:** SDK source generators (they live in the SDK, not the
runtime; `--generators` supplies them), embedded resources, `<Protobuf>`, MSBuild `<Target>`s and
`<Sdk>` elements. Full measurements:
**Not supported, and each says so:** Razor/Blazor compilation (needs
`Microsoft.CodeAnalysis.Razor.Compiler`, `Microsoft.AspNetCore.Razor.Utilities.Shared` and
`Microsoft.Extensions.ObjectPool` in the image — it ships none), SDK source generators (they live in
the SDK, not the runtime; `--generators` supplies them), `<Protobuf>`, MSBuild `<Target>`s and
`<Sdk>` elements. **Embedded resources ARE supported** — under the SDK's own manifest names, every
rule established by building probe projects with the real SDK and reading the names back out of the
emitted PE; `.resx`, a culture in a file name (which the SDK routes to a SATELLITE assembly),
`DependentUpon` and `ManifestResourceName` are each refused BY NAME rather than guessed, because a
plausible-looking wrong resource name is the one defect nothing downstream can see. Full
measurements:
Comment on lines 78 to +79
| `--accept <construct>` | acknowledge one construct the evaluator cannot reproduce (`target:<Name>`, `embedded-resource`, `conditions`, `razor-css-scope`, `razor-not-compiled`). Repeatable |
| `--accept <construct>` | acknowledge one construct the evaluator cannot reproduce (`target:<Name>`, `conditions`, `embedded-resource`, `embedded-resource:{resx,culture,dependent-upon,manifest-resource-name,build-output}`). Repeatable |
Comment on lines +435 to 439
Over every non-test project in `MeshWeaver.Plugins/src` (54 of them), against the portal image
`memex-portal-ai@sha256:6f38db08…` (the pin `MeshWeaver.Plugins/.github/workflows/ci.yml` carries),
with `--accept targets` and no `--extra-refs`. Both columns come from the SAME image, so the delta is
the change and nothing else.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

792 tests   788 ✅  5m 2s ⏱️
  7 suites    4 💤
  7 files      0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 119 tests   1 119 ✅  3m 56s ⏱️
    7 suites      0 💤
    7 files        0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    8 files      8 suites   4m 18s ⏱️
1 490 tests 1 489 ✅ 1 💤 0 ❌
2 190 runs  2 189 ✅ 1 💤 0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 165 tests   1 165 ✅  4m 20s ⏱️
    7 suites      0 💤
    7 files        0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

3 057 tests   2 865 ✅  6m 31s ⏱️
    8 suites    192 💤
    8 files        0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 145 tests   1 145 ✅  7m 46s ⏱️
    7 suites      0 💤
    7 files        0 ❌

Results for commit bf7c7ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   44 files     44 suites   31m 56s ⏱️
8 768 tests 8 571 ✅ 197 💤 0 ❌
9 468 runs  9 271 ✅ 197 💤 0 ❌

Results for commit bf7c7ad.

@rbuergi
rbuergi added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit 9fb74fd Aug 31, 2026
25 checks passed
@rbuergi
rbuergi deleted the integration/build-project-batch branch October 24, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants