Skip to content

feat(#1660 WS3): API-surface framework identity — rebuild only when we need to - #1696

Merged
rbuergi merged 2 commits into
mainfrom
feat/1660-ws3-surface-identity
Aug 16, 2026
Merged

rbuergi merged 2 commits into
mainfrom
feat/1660-ws3-surface-identity

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

#1660 WS3 refinement — rebuild only when we need to: the API-surface identity

Rides on #1687 (commit-deterministic identity). The commit key made the CI bake adoptable but
OVER-invalidated: every platform merge — internal-only included — minted a new identity and
rebaked all content (measured: pending=82 on the routine ci.3979 roll). Content must rebake only
when the API SURFACE it compiles against changed — a breaking change.

The identity (one property, new resolution — FrameworkBuildIdentity)

  1. API-surface identity s<hash> — hosts that ship a meshweaver-surface.manifest
    (portals + the CI bake host, opt-in <MeshWeaverSurfaceManifest>true</>): the manifest records,
    per MeshWeaver compile reference, the SHA-256 of its reference assembly — the compiler's own
    surface oracle (byte-stable under body-only/private edits; flips on any public/protected — and,
    for IVT'd assemblies, internal — surface change; a hand-rolled API walk would miss the IVT
    subtlety, which is why the ref-asm is the oracle). The identity hashes the sorted
    (name, surface-id) pairs of a canonical content-surface set — the bake gate's own
    framework closure (36 assemblies; content referencing anything outside it cannot pass the gate),
    which is what makes the hash EQUAL across the bake host and the portals despite their different
    TPAs (the portal carries 26 more MeshWeaver assemblies — Blazor/Orleans/hosting — deliberately
    outside the content-staleness key). 🚨 Generator exception: MeshWeaver.Graph contributes
    its FULL implementation MVID — its code (DynamicMeshNodeAttributeGenerator, the skeleton/
    aggregation/@@-include/NuGet-directive pipeline in MeshNodeCompilationService +
    CodeQueryResolver) shapes the GENERATED INPUT of every NodeType compile, so a body-only change
    there must rebake without any API change. (Swept: the only other generator, SkeletonGenerator,
    lives in MeshWeaver.Plugin.Build — plugin build tooling, not in the portal compile path.)
  2. Commit identity g<sha> — fallback for manifest-less CI processes (test hosts), and
    retained everywhere as DIAGNOSTIC PROVENANCE (logged at warm-up start:
    framework identity {id}, build {g<sha>}).
  3. Graph MVID — local builds, unchanged.

All consumers follow the ONE property exactly as wired in #1687 (store tag, HasUsableBuild /
CompiledFrameworkVersion, bake report, build-claim fingerprint, seeder gate, artifact key);
AssemblyCacheGenerations.TagOf learns the s+7-hex tag shape.

Build plumbing

  • Directory.Build.props: _MeshWeaverWriteSurfaceManifest (built-in GetFileHash over
    @(ReferencePathWithRefAssemblies) filtered to MeshWeaver.*) + publish hookup. 🚨 The
    multi-arch container publish's ProduceReferenceAssembly=false optimization is REMOVED — ref
    assemblies are load-bearing now (no ref-asms ⇒ impl hashes ⇒ forked identity ⇒ every bake
    declines); the PR fix(ci): kill the multi-arch publish CopyRefAssembly race (MSB3883) #552 race stays killed by the per-leg bin/obj isolation.
  • mw-plugin-test --print-framework-identity — one-line diagnostic (identity + provenance); the
    proof scripts drive the REAL resolution path through it.
  • Publish skip: publish-bake-bundles.sh skips-with-notice when
    prebuilt-bundles/<identity>/<source>/ already holds files — an internal-only merge is a no-op
    publish ("rebuild only when we need to" applies to the publish too).
  • notify-dependents payload drops the (now wrong) identity field — receivers resolve their
    own.

Proof (measured through the real binary, mw-plugin-test --print-framework-identity)

  • Ref-asm oracle: byte-identical under method-body-only edits, changed by a public const add
    (sha256 of obj/ref/MeshWeaver.Data.dll across from-clean rebuilds); internal-member adds flip
    it for IVT'd assemblies — correct conservatism, documented.
  • Live identity (all via the real binary): baseline s9f38b6456177317dd2c6389469811f6b;
  • Unit pins: surface computation (stability, listed-assembly change, host-extra invisibility,
    emitter rule, absence marker), manifest parsing, canonical-list invariants, a closure pin test
    that recomputes the tester's csproj closure and fails naming the drift, the process resolution
    chain, tag attributability.
  • Full solution -c Release -warnaserror --no-incremental green; affected test projects green.

Ops notes

🤖 Generated with Claude Code

…e need to

The commit identity (#1687) made the CI bake adoptable but OVER-invalidated:
every merge — internal-only included — minted a new identity and rebaked all
content (pending=82 on the routine ci.3979 roll). The staleness key is now the
API SURFACE content compiles against, so a bake goes stale exactly on a
breaking change.

* FrameworkBuildIdentity: hosts shipping a meshweaver-surface.manifest (the
  portals + mw-plugin-test, opt-in MeshWeaverSurfaceManifest=true) resolve
  s<sha256/2> over the sorted (name, surface-id) pairs of the CANONICAL
  content-surface set — the bake gate's own 36-assembly framework closure,
  which is what keeps the hash equal across the bake host and the portals
  despite different TPAs. The surface-id per assembly is the SHA-256 of its
  REFERENCE assembly (the compiler's own surface oracle: byte-stable under
  body-only/private edits, moved by any surface change, internals included
  for IVT'd assemblies). 🚨 MeshWeaver.Graph contributes its FULL impl MVID —
  its code (DynamicMeshNodeAttributeGenerator, MeshNodeCompilationService's
  aggregation/@@-include/NuGet-directive pipeline, CodeQueryResolver) shapes
  the GENERATED input of every NodeType compile, so a body-only change there
  rebakes without an API change. Fallbacks: the g<sha> commit stamp
  (manifest-less CI processes; logged everywhere as provenance), then the
  Graph MVID (local). All consumers ride the one property; the retention
  sweep attributes the s-tag; the prewarmer logs identity + provenance.
* Directory.Build.props: _MeshWeaverWriteSurfaceManifest (GetFileHash over
  @(ReferencePathWithRefAssemblies), MeshWeaver.* only) + publish hookup; the
  multi-arch ProduceReferenceAssembly=false optimization is REMOVED — ref
  assemblies are load-bearing for the identity now (per-leg bin/obj isolation
  still kills the PR #552 race).
* publish-bake-bundles.sh skips-with-notice when the identity's bundle
  directory already exists — an internal-only merge is a no-op publish.
  notify-dependents drops the now-wrong identity payload field.
* mw-plugin-test --print-framework-identity: one-line diagnostic driving the
  REAL resolution path; the proofs use it.

Proven via that flag: baseline s9f38b645…; internal body-only edit in
MeshWeaver.Data keeps the identity; a public member on MeshWeaver.Layout
changes it; a body-only edit in MeshWeaver.Graph (ref-asm byte-stable)
changes it via the emitter rule; reverting restores the baseline. Ref-asm
oracle proven byte-identical across from-clean rebuilds and under body edits,
changed by a public const add. Unit pins for the computation, the canonical
list (incl. a csproj-closure drift test), the resolution chain, and the tag
grammar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 16, 2026 22:29

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

This PR refines the framework build identity used to key dynamic NodeType compilation/bake artifacts so that content is rebuilt only when the content-facing API surface changes (rather than on every internal-only framework merge), by introducing a surface-manifest-based s<hash> identity with commit g<sha> retained as provenance.

Changes:

  • Adds surface-manifest-driven framework identity resolution (s<hash>) with fallback to commit stamp (g<sha>) and Graph MVID, and updates consumers/tests accordingly.
  • Enables surface manifest emission/publish for the bake host and portals, and updates logging/diagnostics to include both identity and provenance.
  • Updates CD publish behavior and documentation to reflect surface identity, including skipping bundle upload when the target already has the bundles.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/MeshWeaver.PluginTester/Program.cs Adds --print-framework-identity diagnostic output.
tools/MeshWeaver.PluginTester/MeshWeaver.PluginTester.csproj Enables surface manifest generation for the bake host.
test/MeshWeaver.Graph.Test/FrameworkBuildIdentityTest.cs Adds unit tests for surface identity computation and process resolution.
src/MeshWeaver.Hosting/DynamicTypePreWarmerHostedService.cs Logs framework identity plus commit provenance at warm-up start.
src/MeshWeaver.Graph/Configuration/NodeTypeCompilationHelpers.cs Switches FrameworkVersion to process identity resolution (manifest → stamp → MVID).
src/MeshWeaver.Graph/Configuration/FrameworkBuildIdentity.cs Implements surface identity (s<hash>) computation, manifest parsing, and process-level resolution.
src/MeshWeaver.Graph/Configuration/AssemblyCacheRetention.cs Extends generation tag recognition to include s-prefixed tags.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-rebuild-only-on-breaking-change.md Adds What’s New entry describing the new behavior.
src/MeshWeaver.Documentation/Data/Architecture/ReleaseProcess.md Updates release/process docs for the new identity resolution chain.
src/MeshWeaver.Documentation/Data/Architecture/PluginPackaging.md Documents s<hash> / g<sha> / MVID identity shapes and rationale.
src/MeshWeaver.Documentation/Data/Architecture/NodeTypeCompilation.md Updates compilation docs to reflect surface-manifest identity.
src/MeshWeaver.Documentation/Data/Architecture/ContinuousDeliveryContract.md Updates CD contract and dependent notification payload to drop identity.
src/MeshWeaver.Documentation/Data/Architecture/CiContentBake.md Updates CI bake docs to explain adoptability via surface identity.
memex/Memex.Portal.Monolith/Memex.Portal.Monolith.csproj Enables surface manifest generation for the monolith portal.
memex/aspire/Memex.Portal.Distributed/Memex.Portal.Distributed.csproj Enables surface manifest generation for the distributed portal.
Directory.Build.props Adds targets to write/publish the surface manifest; documents ref-assembly requirement.
deploy/CHANGE-PROPAGATION.md Updates change propagation guidance for surface identity behavior.
deploy/aks/values.aks.yaml Updates AKS config comments to reflect API-surface identity semantics.
deploy/aks/DEPLOY-RUNBOOK.md Updates runbook explanation of identity and bake adoption.
.github/workflows/main-cd.yml Updates publish-bake comments and dependent dispatch payload (drops identity).
.github/scripts/publish-bake-bundles.sh Adds “skip publish if already present” behavior for surface-identity directories.

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

Comment thread .github/scripts/publish-bake-bundles.sh Outdated
Comment thread src/MeshWeaver.Graph/Configuration/FrameworkBuildIdentity.cs
@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

920 tests  ±0   919 ✅ ±0   11m 30s ⏱️ +25s
 10 suites ±0     1 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 371 tests  ±0   1 370 ✅ ±0   6m 5s ⏱️ +14s
   11 suites ±0       1 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 607 tests  +1   1 601 ✅ +1   7m 17s ⏱️ -15s
   11 suites ±0       6 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   11 files  ±0     11 suites  ±0   6m 11s ⏱️ +11s
2 197 tests ±0  2 006 ✅ ±0  191 💤 ±0  0 ❌ ±0 
2 568 runs  +1  2 377 ✅ +1  191 💤 ±0  0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

2 164 tests  ±0   2 061 ✅ ±0   7m 23s ⏱️ +25s
   11 suites ±0     103 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

2 455 tests  +8   2 451 ✅ +8   7m 50s ⏱️ -3s
   11 suites ±0       4 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

This pull request removes 6 and adds 14 tests. Note that renamed tests count towards both.
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ FrameworkVersion_IsTheResolvedIdentityOfTheGraphAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ PeRead_AgreesWithTheLoadedAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: \"   \")
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: \"\")
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: null)
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_PrefersTheStampedIdentity
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ CanonicalList_IsSortedAndDistinct_AndContainsTheExceptions
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ CanonicalList_MatchesTheTesterClosure
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ FrameworkVersion_IsTheProcessIdentityOfTheGraphAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ManifestParsing_RoundTrips
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ PeRead_AgreesWithTheLoadedAssembly_OnTheFallbackLayer
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_DegradesToTheFallback_WhenTheManifestHoldsNothingUsable
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_DegradesToTheFallback_WhenTheManifestIsUnreadable
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_FallsBackToStampThenMvid_WithoutAManifest
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_UsesTheManifestWhenPresent
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ SurfaceIdentity_ChangesWhenAListedAssemblysSurfaceChanges
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    65 files  ± 0      65 suites  ±0   46m 18s ⏱️ +56s
10 714 tests + 9  10 408 ✅ + 9  306 💤 ±0  0 ❌ ±0 
11 085 runs  +10  10 779 ✅ +10  306 💤 ±0  0 ❌ ±0 

Results for commit 3fd2b65. ± Comparison against base commit b969a4d.

This pull request removes 6 and adds 15 tests. Note that renamed tests count towards both.
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ FrameworkVersion_IsTheResolvedIdentityOfTheGraphAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ PeRead_AgreesWithTheLoadedAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: \"   \")
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: \"\")
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_FallsBackToTheContentIdentity(stamped: null)
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ Resolve_PrefersTheStampedIdentity
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ CanonicalList_IsSortedAndDistinct_AndContainsTheExceptions
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ CanonicalList_MatchesTheTesterClosure
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ FrameworkVersion_IsTheProcessIdentityOfTheGraphAssembly
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ManifestParsing_RoundTrips
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ PeRead_AgreesWithTheLoadedAssembly_OnTheFallbackLayer
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_DegradesToTheFallback_WhenTheManifestHoldsNothingUsable
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_DegradesToTheFallback_WhenTheManifestIsUnreadable
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_FallsBackToStampThenMvid_WithoutAManifest
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ ProcessIdentity_UsesTheManifestWhenPresent
MeshWeaver.Graph.Test.FrameworkBuildIdentityTest ‑ SurfaceIdentity_ChangesWhenAListedAssemblysSurfaceChanges
…

♻️ This comment has been updated with latest results.

…dentity resolution never faults boot

Copilot findings on #1696, both real:

1. The publish skip keyed on "any file exists" — a publish that died after 1 of
   N bundles froze the identity incomplete forever. The publisher now writes a
   _complete sentinel (listing the bundle set) strictly LAST; the skip keys on
   the sentinel alone, and the SAME contract holds on the read side:
   SeedPublishedRoot seeds only sealed source directories, and only the bundles
   the sentinel lists — an unsealed or torn publication is refused loudly and
   the sweep compiles instead (test: PublishedRoot_RefusesUnsealedAndTorn…).

2. ResolveProcessIdentity read the surface manifest unguarded — an unreadable/
   torn manifest would fault the boot-path identity Lazy. The chain is now the
   pure ResolveProcessIdentityWithDiagnostics: any manifest failure degrades to
   the stamp/MVID fallback with a warning CACHED BESIDE the identity (no
   mutable static), which the pre-warmer logs where it announces the identity
   (tests: ProcessIdentity_DegradesToTheFallback_*).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rbuergi

rbuergi commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Both review findings were real and are fixed in 3fd2b65:

  1. Partial-publish freeze — the publisher now writes a _complete sentinel (listing the bundle set) strictly LAST; the skip keys on the sentinel only, and the read side honours the same contract: SeedPublishedRoot seeds only sealed source directories and only the bundles the sentinel lists — unsealed/torn publications are refused loudly (pinned by PublishedRoot_RefusesUnsealedAndTornPublications).
  2. Unguarded manifest read — identity resolution is now the pure ResolveProcessIdentityWithDiagnostics: any manifest failure degrades to the stamp/MVID fallback with the warning cached beside the identity and logged by the pre-warmer where it announces the identity (pinned by ProcessIdentity_DegradesToTheFallback_*).

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