You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[browser][coreCLR] Multiple flavors of dotnet.native.wasm #132772
At the moment we ship only one large dotnet.native.wasm that includes all CoreCLR VM modules, including event-pipe. We instead should ship multiple flavors
production version - as small as possible
dev loop version - with debugger support and event pipe
Separate dependency. Define its component boundary now; validate real debugger behavior when bring-up lands. Do not absorb browser debugger implementation into this project.
Production/development describe native capabilities, not the runtime repository's Debug/Checked/Release build configuration. A development-capable runtime can be optimized. The initial milestone does not make DWARF/unstripped binaries another prelinked dimension.
Initial research: Mono's existing model
Component construction and selection
Mono component CMake definitions enumerate debugger, hot_reload, diagnostics_tracing, and marshal-ilgen. Each has real and stub source lists and object targets. Static builds produce pairs named libmono-component-<name>-static.a and libmono-component-<name>-stub-static.a.
The common Mono component interface has an interface version and an availability callback. Component-specific interfaces supply the actual operations. The EventPipe stub is a real implementation of that contract, not an empty archive.
RuntimeComponentManifest.targets reads the runtime-pack manifest, computes component filenames, and selects the real implementation for requested components and the stub for unselected components. It exposes explicit link and exclusion item lists.
WasmApp.Common.targets requests debugger/hot_reload based on the effective Wasm debug level and requests diagnostics_tracing through EnableDiagnostics.
BrowserWasmApp.targets consumes those exclusions while constructing the customer link command. It also selects timezone, exception-handling, and SIMD alternatives.
Mono also demonstrates a second useful pattern: compiling the same small source set differently, rather than defining a full component interface. Mono mini CMake builds JS-EH/Wasm-EH alternatives from llvm-runtime.cpp and SIMD/non-SIMD interpreter alternatives from separate sources.
Reuse the build-time selection model, not necessarily Mono's runtime indirection. CoreCLR can retain direct calls and expose the same link-facing symbols from real and stub archives.
Infers native linking from native inputs and selected property changes.
Sets up Emscripten, generates interop helpers, compiles native inputs, and links the runtime archives.
Selects full/invariant timezone archives.
Omits globalization/ICU archives for invariant globalization in its link-input list.
Publishes relinked Wasm, matching Emscripten JavaScript, and optional symbol-map assets through WasmNativeAsset.
Handles build and nested-publish asset ordering.
These paths should be extended, not replaced with a parallel relinker.
The browser host CMake link currently produces one prelinked native asset set. It always links the full timezone/globalization inputs in the inspected source. The customer link already has more configurability than this in-tree prelink.
EventPipe has useful boundaries, but is not yet swappable
CoreCLR definitions couple FEATURE_EVENT_TRACE to the native FEATURE_PERFTRACING definition. Disabling just one CMake variable is not sufficient to establish a consistent feature-off build.
EventPipe CMake already separates generated event writers from runtime integration objects. The latter pull in shared EventPipe and diagnostic-server sources; the eventpipe interface target contributes their objects to its consumers.
CoreCLR static-library construction includes that EventPipe target, the interpreter, VM, and other dependencies in the static runtime construction.
VM integration is wider than the EventPipe implementation: initialization/shutdown, QCall registration, adapters, generated event calls, interpreter sampling, and shared native types all matter.
A concrete layout hazard exists in threads.h: FEATURE_PERFTRACING controls the m_activityId member. Building interchangeable archives with inconsistent feature defines can silently disagree about Thread layout.
Conclusion: introduce implementation-level real/stub guards while preserving a compatible native contract. Do not independently toggle global feature/layout macros on arbitrary object targets and assume symbol-name compatibility proves ABI compatibility.
Browser debugger is a dependency, not a finished component
On the inspected commit:
CoreCLR definitions define DEBUGGING_SUPPORTED globally, but exclude Wasm from FEATURE_METADATA_UPDATER.
ceemain.cpp skips InitializeDebugger() under TARGET_WASM.
Wasm helpers contain an unimplemented CorDBGetInterface with a portability assertion.
Therefore, current feature defines do not establish working browser debugging or Hot Reload. The planned debugger-on contract must be coordinated with bring-up; an existing placeholder must never be advertised as the real implementation.
No-workload selection crosses packaging and SDK boundaries
native.wasm.targets has a CoreCLR relink-property registry containing single-prebuilt defaults. Its EnableDiagnostics=false default is not evidence that EventPipe implementation code is absent from the current binary.
The current workload manifest targets infer workload use from diagnostics, invariant timezone, native symbol options, and other properties before importing native build targets. Simply adding flavor selection inside the workload relinker would be too late and would not solve no-workload builds.
WasmFeatures.props currently derives DebuggerSupport=false for non-Debug configurations. The agreed build-versus-publish defaults require a CoreCLR-specific reconciliation without changing Mono defaults.
WebAssembly Browser targets select build/publish assets, fingerprints, boot configuration, and the diagnostics JavaScript module.
The CoreCLR WebAssembly Browser targets already house non-Mono R2R behavior. This is a candidate home for non-workload native-flavor resolution, subject to verifying import/evaluation timing in packaged consumers.
ReadyToRunTests.cs includes a test marked no-workload that deliberately relinks using in-tree targets and EMSDK_PATH. Consequently, passing a no-workload test category alone does not prove that an app can build without a native toolchain. The new acceptance test must remove that escape hatch and inspect executed tasks and selected assets.
Proposed architecture
1. Common native base plus independent real/stub archives
Conceptual link:
browser host
+ common CoreCLR/VM/interpreter/platform archives
+ exactly one debugger implementation: real OR stub
+ exactly one EventPipe implementation: real OR stub
+ framework-generated or app-generated interop/thunk tables
-> matching dotnet.native.wasm + dotnet.native.js + symbol assets
Archive names and precise source ownership are provisional. Prefer existing target structure and small changes over a new generic component framework.
Contract requirements:
Real/stub archives provide the same externally required functions, globals, and any vtable/type-related linkage at the component boundary.
Keep signatures, calling conventions, native layouts, declarations, and generated metadata compatible with the common base.
New #ifdef guards select real bodies versus neutral/unavailable implementations. Do not use them to create conflicting shared type layouts.
Real-only internal helpers need not be artificially exported by the stub; the required link-facing symbol set must have parity.
Feature-off event emission and lifecycle hooks may be no-ops. Availability/session/debugger operations must accurately report unsupported/unavailable behavior rather than fabricate success.
Preserve managed behavior not equivalent to native diagnostics: EventPipe-off must not automatically disable in-process EventListener, all EventSource functionality, metrics, or managed exception stack traces.
Do not duplicate selected implementation objects inside the base archive and the component archive.
Avoid shipping both real and stub on the same link line and relying on archive order to pick the winner.
Scope new componentization to browser CoreCLR initially. Preserve desktop, Mono, and WASI construction paths.
Start with EventPipe: generated writers, runtime adapter, diagnostic-server integration, lifecycle/QCall boundary, and interpreter sampling form the initial ownership inventory. Keep shared GC, stack walking, threading, and metadata support in the base unless evidence proves that it is exclusively owned by the optional component.
For the debugger, inventory existing VM-facing declarations and the intended bring-up boundary now. The actual real archive and functional capability tests depend on that separate implementation becoming available.
2. Small authoritative description of components and prelinked flavors
Use one build-owned description to drive component selection, prelink construction, runtime-pack metadata, and app-side matching. Reuse current properties/manifests where practical; avoid independent CMake/MSBuild hardcoded flavor matrices.
The packaged description needs:
A schema/version identifier and runtime-pack identity.
Each component's real/stub archive paths and dependency constraints.
The effective feature values represented by each prelinked flavor.
Paths to the complete, matching native asset set.
Relevant baked-in native settings needed to reject incompatible reuse.
Enough information to explain why a prelinked match was selected or a relink was required.
Do not introduce a new public flavor switch by default. Resolve from existing feature properties, retaining an internal selected-flavor identity for logging and incremental build inputs. If existing property semantics cannot express the agreed contract cleanly, return to Pavel with that specific issue before adding a new public property.
Potential pack organization is a flavor subdirectory per native asset set plus shared archives. Final paths must be validated against runtime-pack manifests, NuGet harvesting, SDK conflict resolution, and asset fingerprinting; this is not a committed layout.
3. Two prelinked combinations, four customer-link combinations
Debugger
EventPipe
Initial prelinked asset set
Customer relink
Off
Off
Production
Supported
On
On
Development, dependent on real debugger bring-up
Supported, same dependency
Off
On
None
Supported
On
Off
None
Supported, dependent on real debugger bring-up
Initial prelinks share baseline globalization, timezone, ISA, and native-symbol settings. Additional variations are not silently folded into these two names.
Treat the Emscripten-generated JavaScript and Wasm as an atomic pair. Include matching symbol maps/debug companions when present. Share loader/runtime JavaScript only after proving its imports, exports, feature assumptions, and boot configuration are compatible with both native flavors.
4. App-side resolution algorithm
Determine the effective build operation. A build invoked as part of publish uses publish intent; do not accidentally stage development assets before the nested production publish.
Resolve component settings: build/run defaults both on, publish defaults both off, explicit settings win.
Reconcile DebuggerSupport, WasmDebugLevel, diagnostics settings, and SDK-injected defaults at the correct import/target stages. Detect contradictory explicit requests. Do not mistake a default injected by an earlier props file for an intentional user override.
Identify genuine relink requirements: custom native sources/archives, custom link inputs/options, app-specific generated callbacks/thunks, unsupported prelinked settings, or explicit WasmBuildNative=true.
Preserve the current implicit relink policy for trimmed Release publish when wasm-tools is installed, including existing opt-outs. A matching prelink does not automatically bypass that policy.
Otherwise select an exact prelinked match when available. This is the default route without wasm-tools.
If no exact match exists, relink when available and permitted. If linking is unavailable or WasmBuildNative=false, fail with the requested combination and the precise requirement. Never silently use a larger flavor or discard native references.
Feed one selected native asset set into both build and publish asset pipelines before conflict resolution/fingerprinting. Exclude every unselected flavor from deployed output.
Include effective flavor and link inputs in incremental dependency/stamp handling. Recompute correctly across build -> publish -> build and property-only changes.
No automatic workload installation is part of this design. Existing R2R crossgen2 requirements remain distinct from Emscripten/native relinking.
The exact-match rule initially governs the new component capabilities. Deferred axes must retain their existing supported semantics until explicitly extended; do not accidentally turn today's valid no-workload application into a workload-required application just by generalizing the matcher.
Implementation stages and boundaries
Stage 0: Establish evidence and final component boundaries
Create a convention-named worktree after approval of this plan.
Load the build/test guidance and applicable native/CoreCLR conventions before code changes.
Build a baseline in that worktree; retain the original checkout and its artifacts unchanged.
Inventory actual archive members, defined/undefined symbols, generated writers, imports/exports, and link maps.
Identify precisely which object sets are currently incorporated into the static runtime and which remain separate archives.
Enumerate layout-affecting macros and direct/inline VM dependencies crossing each proposed component boundary.
Establish native raw/Brotli size, full deployed asset size, startup, and representative execution baselines in an optimized configuration.
Verify package/SDK import paths using real packaged consumers, not only local imports.
Coordinate the debugger contract with its separate bring-up work.
Exit: a concrete source/symbol ownership table and no unresolved ABI assumption at the EventPipe boundary. Numerical size/performance budgets remain to be agreed after this baseline; no size win has been measured during this research.
Stage 1: EventPipe real/stub component
Extract native object ownership into real/stub alternatives without changing the default existing link.
Add implementation guards and compatible disabled behavior at the identified boundaries.
Make code generation produce real/stub event entry points from the same authoritative event definitions where needed.
Wire both in-tree browserhost and customer relinking to explicit component choices.
Exercise EventPipe on/off with interpreter and supported R2R.
Keep the debugger contract independent; do not claim its real implementation is complete.
Exit: both EventPipe variants link and run, feature-on behavior is preserved, and feature-off no longer includes the real EventPipe implementation.
Stage 2: Debugger boundary and dependency integration
Isolate the intended debugger implementation from VM support shared with ordinary execution and diagnostics.
Define and test the stub's native contract.
Integrate the real implementation once the independent browser-debugger work is available.
Test debugger on/off separately from EventPipe; ensure disabling one does not disable the other.
Do not expand this phase into Hot Reload bring-up or complete desktop-debugger removal.
Exit: symbol/ABI contract is established; functional debugger-on acceptance is explicitly blocked until the external bring-up dependency is satisfied. Do not mark this exit complete from link success alone.
Stage 3: Prelink production/development and package metadata
Build selected combinations from the same component choices used by customer relinking.
Stage matching native Wasm/JS/symbol assets and the authoritative selection metadata.
Update local pack copies, live-build harvesting, shipping packages, and platform-manifest bookkeeping.
Keep ordinary pack consumption from copying every flavor into the app.
Complete the development capability claim only after Stage 2's real-debugger dependency is satisfied.
Stage 4: No-workload resolver and defaults
Put resolution on a path available to both workload and no-workload consumers.
Adjust CoreCLR workload inference so shipped configurations do not require loading the native workload merely to choose an asset.
Implement the operation defaults and exact-match/error policy above.
Preserve workload-installed Release-publish relinking and explicit WasmBuildNative behavior.
Wire diagnostics JavaScript and boot configuration to the effective selection.
Document the intentional default/error changes as breaking changes before shipping.
Keep mechanical extraction, stub logic, and SDK/default policy in separately reviewable concerns where feasible; do not enable a default that depends on an unshipped resolver or missing real component.
Compare real/stub required symbol sets, including data symbols, and check for duplicate ownership.
Verify common layouts and relevant generated offsets; matching symbol names alone are insufficient.
Link all four component combinations once real debugger support is available.
Production link map/archive provenance contains stub implementations, not the real debugger/EventPipe implementation.
EventPipe-on produces usable events/traces; off does not initialize a diagnostic server or claim a successful session.
Managed EventListener/stack-trace behavior unrelated to native diagnostics remains intact.
Debugger-on eventually passes real breakpoint/step/inspection scenarios against the brought-up debugger; off reports unavailability correctly.
Exercise GC, exceptions, JS interop, framework P/Invoke, and interpreter/R2R transitions.
Packaged application matrix
Scenario
Required evidence
No-workload build/run
Development asset set selected; actual browser startup; no native toolchain invocation.
No-workload publish
Production asset set selected in both Debug and Release app configurations; actual browser startup.
Workload-installed trimmed Release publish
Existing implicit native relink preserved; effective production components selected.
Explicit shipped combination
Correct matching prelink when linking is not otherwise required.
Explicit mixed combination
Customer relink with workload; clear error without workload or with linking disabled.
Explicit WasmBuildNative=true
Real relink, not prelink reuse.
Native references and app-generated glue
Native inputs actually present and callable, or a clear unsupported/missing-workload error.
R2R build
Supported prebuilt framework R2R and native thunk tables remain compatible.
R2R publish
Supported trimmed/untrimmed closures run; crossgen2 activity is distinguished from native linking.
Property-only and operation switches
Correct assets after build -> publish -> build and feature toggles, without cleaning.
Boot/static-web-assets/fingerprints
Exactly one coherent native set is deployed; hashes refer to the selected content.
For the no-workload proof, use the SDK without installed wasm-tools and without EMSDK_PATH or in-tree toolchain fallback. Inspect binlogs/task execution to show no emcc/wasm-ld/wasm-opt work. Verify served asset content against the selected packaged flavor, accounting for fingerprinted names.
A test category, a generated response file, or a successful runtime-pack build is not sufficient evidence. Run a real browser WebAssembly/Blazor app from the produced output. Include no-relink ordinary apps and negative tests for unsupported app-specific native glue.
Use Debug for correctness iteration and an optimized native build for size/performance comparisons. Keep component capabilities distinct from compilation configuration. Report raw/compressed native sizes, total deployed asset sizes, and runtime-pack growth separately. Production must be demonstrably smaller than development in the comparison used to justify the change; set numerical budgets from the baseline rather than inventing them now.
No validation commands were run during planning. At implementation time, obtain current commands from the build-and-test skill, use binary logs, rebuild host/native staging after VM/native changes, regenerate shipping packages before workload tests, and verify test execution counts.
Deferred dimensions
Invariant globalization/timezone: preserve current relink support; add prelinked combinations only after measuring demand and pack-size cost. Timezone already has real/invariant archive prior art.
Native symbols and stripping: WasmNativeDebugSymbols, WasmNativeStrip, DWARF, and symbol maps are not equivalent to managed debugger capability. Define their matrix separately.
Profilers: do not map Mono profiler names directly to CoreCLR without defining supported equivalents. CoreCLR's normal ICorProfiler feature is disabled on Wasm in the inspected source.
Relaxed SIMD interpreter: a future code-generation/ISA dimension, not a debugger/EventPipe component. Investigate separately, including matching feature detection and fallback behavior.
Hot Reload: depends on separate bring-up; not implied by debugger-on.
WASI single-file: stretch goal with different host/linking constraints; no initial WASI flavor implementation.
General SDK cleanup after Mono removal: dotnet/runtime#133411 is adjacent work, not a prerequisite to replace the SDK wholesale here.
Remaining technical investigations
These are implementation investigations, not unanswered product-scope decisions:
Exact EventPipe and debugger symbol/source ownership, including generated entry points and shared VM helpers.
The debugger bring-up revision/PR providing the real browser implementation and stable integration boundary.
Final manifest format and pack directory layout that survive both local and shipping package paths.
The import point where CoreCLR operation defaults and user overrides can be distinguished without depending on an installed workload.
Safe stub behavior for every externally callable operation and consistency with managed feature switches.
Which supported R2R/native-callback cases reuse the common prelinked thunk inventory and which require app-specific linking.
Numerical size/startup/throughput budgets after a reproducible optimized baseline.
Activity
dotnet-policy-service commentedon Aug 26, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
[wasm] Fix CoreCLR WasmBuildNative defaults test and disable MEF test…
[wasm] Fix CoreCLR WasmBuildNative defaults test and disable MEF test…
pavelsavara commentedon Oct 1, 2026
Implementation plan
Decisions confirmed with Pavel
Production/development describe native capabilities, not the runtime repository's Debug/Checked/Release build configuration. A development-capable runtime can be optimized. The initial milestone does not make DWARF/unstripped binaries another prelinked dimension.
Initial research: Mono's existing model
Component construction and selection
libmono-component-<name>-static.aandlibmono-component-<name>-stub-static.a.EnableDiagnostics.Mono also demonstrates a second useful pattern: compiling the same small source set differently, rather than defining a full component interface. Mono mini CMake builds JS-EH/Wasm-EH alternatives from
llvm-runtime.cppand SIMD/non-SIMD interpreter alternatives from separate sources.Reuse the build-time selection model, not necessarily Mono's runtime indirection. CoreCLR can retain direct calls and expose the same link-facing symbols from real and stub archives.
Initial research: CoreCLR today
Existing relinking is a foundation, not new work
BrowserWasmApp.CoreCLR.targets already:
WasmNativeAsset.These paths should be extended, not replaced with a parallel relinker.
The browser host CMake link currently produces one prelinked native asset set. It always links the full timezone/globalization inputs in the inspected source. The customer link already has more configurability than this in-tree prelink.
EventPipe has useful boundaries, but is not yet swappable
FEATURE_EVENT_TRACEto the nativeFEATURE_PERFTRACINGdefinition. Disabling just one CMake variable is not sufficient to establish a consistent feature-off build.eventpipeinterface target contributes their objects to its consumers.FEATURE_PERFTRACINGcontrols them_activityIdmember. Building interchangeable archives with inconsistent feature defines can silently disagree aboutThreadlayout.Conclusion: introduce implementation-level real/stub guards while preserving a compatible native contract. Do not independently toggle global feature/layout macros on arbitrary object targets and assume symbol-name compatibility proves ABI compatibility.
Browser debugger is a dependency, not a finished component
On the inspected commit:
DEBUGGING_SUPPORTEDglobally, but exclude Wasm fromFEATURE_METADATA_UPDATER.InitializeDebugger()underTARGET_WASM.CorDBGetInterfacewith a portability assertion.cordbee_wkslibrary in the Wasm branch.Therefore, current feature defines do not establish working browser debugging or Hot Reload. The planned debugger-on contract must be coordinated with bring-up; an existing placeholder must never be advertised as the real implementation.
The broader browser CoreCLR roadmap, dotnet/runtime#134337, tracks debugger and Hot Reload bring-up. dotnet/runtime#124467 is specifically WebCIL/DAC compatibility, not the entire debugger implementation.
No-workload selection crosses packaging and SDK boundaries
EnableDiagnostics=falsedefault is not evidence that EventPipe implementation code is absent from the current binary.DebuggerSupport=falsefor non-Debug configurations. The agreed build-versus-publish defaults require a CoreCLR-specific reconciliation without changing Mono defaults.Important test-harness caveat
ReadyToRunTests.cs includes a test marked
no-workloadthat deliberately relinks using in-tree targets andEMSDK_PATH. Consequently, passing a no-workload test category alone does not prove that an app can build without a native toolchain. The new acceptance test must remove that escape hatch and inspect executed tasks and selected assets.Proposed architecture
1. Common native base plus independent real/stub archives
Conceptual link:
Archive names and precise source ownership are provisional. Prefer existing target structure and small changes over a new generic component framework.
Contract requirements:
#ifdefguards select real bodies versus neutral/unavailable implementations. Do not use them to create conflicting shared type layouts.EventListener, all EventSource functionality, metrics, or managed exception stack traces.Start with EventPipe: generated writers, runtime adapter, diagnostic-server integration, lifecycle/QCall boundary, and interpreter sampling form the initial ownership inventory. Keep shared GC, stack walking, threading, and metadata support in the base unless evidence proves that it is exclusively owned by the optional component.
For the debugger, inventory existing VM-facing declarations and the intended bring-up boundary now. The actual real archive and functional capability tests depend on that separate implementation becoming available.
2. Small authoritative description of components and prelinked flavors
Use one build-owned description to drive component selection, prelink construction, runtime-pack metadata, and app-side matching. Reuse current properties/manifests where practical; avoid independent CMake/MSBuild hardcoded flavor matrices.
The packaged description needs:
Do not introduce a new public flavor switch by default. Resolve from existing feature properties, retaining an internal selected-flavor identity for logging and incremental build inputs. If existing property semantics cannot express the agreed contract cleanly, return to Pavel with that specific issue before adding a new public property.
Potential pack organization is a flavor subdirectory per native asset set plus shared archives. Final paths must be validated against runtime-pack manifests, NuGet harvesting, SDK conflict resolution, and asset fingerprinting; this is not a committed layout.
3. Two prelinked combinations, four customer-link combinations
Initial prelinks share baseline globalization, timezone, ISA, and native-symbol settings. Additional variations are not silently folded into these two names.
Treat the Emscripten-generated JavaScript and Wasm as an atomic pair. Include matching symbol maps/debug companions when present. Share loader/runtime JavaScript only after proving its imports, exports, feature assumptions, and boot configuration are compatible with both native flavors.
4. App-side resolution algorithm
DebuggerSupport,WasmDebugLevel, diagnostics settings, and SDK-injected defaults at the correct import/target stages. Detect contradictory explicit requests. Do not mistake a default injected by an earlier props file for an intentional user override.WasmBuildNative=true.WasmBuildNative=false, fail with the requested combination and the precise requirement. Never silently use a larger flavor or discard native references.No automatic workload installation is part of this design. Existing R2R crossgen2 requirements remain distinct from Emscripten/native relinking.
The exact-match rule initially governs the new component capabilities. Deferred axes must retain their existing supported semantics until explicitly extended; do not accidentally turn today's valid no-workload application into a workload-required application just by generalizing the matcher.
Implementation stages and boundaries
Stage 0: Establish evidence and final component boundaries
Exit: a concrete source/symbol ownership table and no unresolved ABI assumption at the EventPipe boundary. Numerical size/performance budgets remain to be agreed after this baseline; no size win has been measured during this research.
Stage 1: EventPipe real/stub component
Exit: both EventPipe variants link and run, feature-on behavior is preserved, and feature-off no longer includes the real EventPipe implementation.
Stage 2: Debugger boundary and dependency integration
Exit: symbol/ABI contract is established; functional debugger-on acceptance is explicitly blocked until the external bring-up dependency is satisfied. Do not mark this exit complete from link success alone.
Stage 3: Prelink production/development and package metadata
Stage 4: No-workload resolver and defaults
WasmBuildNativebehavior.Stage 5: Acceptance, documentation, and shipping
Validation and acceptance
Use existing Wasm.Build.Tests guidance, native-default tests, workload-required tests, and R2R tests. Prefer extending their data-driven coverage over inventing another harness.
Native contract and feature behavior
Packaged application matrix
WasmBuildNative=trueFor the no-workload proof, use the SDK without installed wasm-tools and without
EMSDK_PATHor in-tree toolchain fallback. Inspect binlogs/task execution to show no emcc/wasm-ld/wasm-opt work. Verify served asset content against the selected packaged flavor, accounting for fingerprinted names.A test category, a generated response file, or a successful runtime-pack build is not sufficient evidence. Run a real browser WebAssembly/Blazor app from the produced output. Include no-relink ordinary apps and negative tests for unsupported app-specific native glue.
Use Debug for correctness iteration and an optimized native build for size/performance comparisons. Keep component capabilities distinct from compilation configuration. Report raw/compressed native sizes, total deployed asset sizes, and runtime-pack growth separately. Production must be demonstrably smaller than development in the comparison used to justify the change; set numerical budgets from the baseline rather than inventing them now.
No validation commands were run during planning. At implementation time, obtain current commands from the build-and-test skill, use binary logs, rebuild host/native staging after VM/native changes, regenerate shipping packages before workload tests, and verify test execution counts.
Deferred dimensions
WasmNativeDebugSymbols,WasmNativeStrip, DWARF, and symbol maps are not equivalent to managed debugger capability. Define their matrix separately.Remaining technical investigations
These are implementation investigations, not unanswered product-scope decisions: