Repository navigation
feat(#1660 WS3): commit-deterministic framework identity — the CI bake seeds at boot - #1687
Conversation
…e seeds at boot Every pod boot re-baked all dynamic NodeTypes (baked=0 pending=282) because the framework identity differed per CI build: InformationalVersion carried +build.<UtcNow.Ticks> under CIRun (PR #145), so the Build-and-Test run that bakes and the main-cd run that builds the image could never agree — the defect that also killed the memex-bake Job (#1347, ded3275). The pairing the old props comment demanded, in one change: * Directory.Build.props: nothing per-build reaches a compiled attribute under CIRun — InformationalVersion is $(PlatformVersion) (SDK appends the checked-out sha), FileVersion is pinned; $(Version) keeps the monotonic .ci.<run#> for package versions and image tags ONLY. On GitHub Actions CIRun builds every assembly is stamped AssemblyMetadata("MeshWeaverFrameworkIdentity","g<sha>"). * FrameworkBuildIdentity (Graph): ONE identity resolution — the stamped commit identity when present (covers the WHOLE TPA reference set: a commit names the entire tree, the widening the ticks comment required), Graph MVID locally (content-exact for dirty trees). NodeTypeCompilationHelpers.FrameworkVersion resolves through it, so every consumer (HasUsableBuild, CompiledFrameworkVersion stamps, store FrameworkTag, bake report, build-claim fingerprint, seeder gate, bake artifact key) moves at once. Plugin.Build reads the same stamp from PE metadata (ReadIdentity); the retention sweep attributes the g-tag shape. * Delivery: main-cd publish-bake copies the BT run's baked-assemblies-* artifact to the portals' data shares (prebuilt-bundles/<identity>/<source>/, script .github/scripts/publish-bake-bundles.sh; config preflighted RED, artifact absence warns); boot seeds its own identity's bundles before the sweep (ShippedPrebuiltBundles.SeedPublishedRoot, PreWarm:PrebuiltBundleRoot, /data/prebuilt-bundles on AKS). notify-dependents dispatches meshweaver-framework-released to subscriber repos so dependents rebake on every framework release (in-repo dependency bumps re-key by commit already). * Runtime display: $(Version) rides the image config as MESHWEAVER_PLATFORM_VERSION; InstalledPlatformVersion/AppVersionService read it first, so the self-updater's current-version comparison keeps the run number. Proven: two CIRun builds of MeshWeaver.Graph with GITHUB_RUN_NUMBER 1111 vs 2222, 70s apart, are BIT-IDENTICAL (mvid ec8624fd…); a one-line source change flips the MVID (e33b7842…); generated AssemblyInfo carries no run number and no ticks. Graph.Test 59/59, PluginCatalog.Test 21/21 (incl. the CI-writes→portal-reads round trip), DocumentationLinkIntegrityTest green, full solution Release -warnaserror --no-incremental green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR makes the framework identity deterministic per commit (CI) so CI-baked NodeType assemblies can be reliably adopted at portal boot, and wires CD to publish the bake bundles to shared portal storage for consumption during warmup.
Changes:
- Introduces a unified “framework build identity” resolution (CI:
g<sha>stamp; local: Graph MVID) and updates all consumers to use it. - Adds a CD
publish-bakejob plus a publishing script to copy CI bake artifacts onto portal Azure Files in an identity-keyed layout, and seeds that layout during portal warmup. - Updates runtime version display to use an injected env var for the run-numbered version while keeping compiled assemblies commit-deterministic.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.PluginCatalog.Test/ShippedPrebuiltBundlesTest.cs | Adds tests for seeding from the CI-published bundle root and inert behavior when unconfigured. |
| test/MeshWeaver.PluginCatalog.Test/ModuleLandingServiceTest.cs | Switches “live framework identity” source to the centralized identity reader. |
| test/MeshWeaver.PluginCatalog.Test/ModuleFunnelTest.cs | Switches “live framework identity” source to the centralized identity reader. |
| test/MeshWeaver.Graph.Test/FrameworkBuildIdentityTest.cs | Pins identity resolution rules and asserts loaded-assembly vs PE-read agreement. |
| test/MeshWeaver.Graph.Test/AssemblyCacheRetentionTest.cs | Extends retention tag attribution tests to support g-prefixed identity tags. |
| src/MeshWeaver.Plugin.Build/ModulePackCommand.cs | Records framework identity in module pack metadata using PE-read identity. |
| src/MeshWeaver.Plugin.Build/FrameworkIdentity.cs | Adds ReadIdentity (stamp-or-MVID) and PE-level stamp decoding for producers. |
| src/MeshWeaver.Mesh.Contract/PlatformBuildInfo.cs | Introduces env-var-based runtime platform version discovery. |
| src/MeshWeaver.Hosting/ShippedPrebuiltBundles.cs | Adds seeding from CI-published root keyed by the running identity. |
| src/MeshWeaver.Hosting/DynamicTypePreWarmerHostedService.cs | Seeds both in-image bundles and published-root bundles before the sweep. |
| src/MeshWeaver.Graph/Configuration/NodeTypeCompilationHelpers.cs | Updates FrameworkVersion to resolve via the new identity scheme. |
| src/MeshWeaver.Graph/Configuration/FrameworkBuildIdentity.cs | Defines the single identity resolution rule and stamp reader for loaded assemblies. |
| src/MeshWeaver.Graph/Configuration/AssemblyCacheRetention.cs | Extends tag parsing to attribute CI commit-identity generations. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-ci-bake-seeds-at-boot.md | Adds a “What’s New” entry describing faster startup via CI bake adoption. |
| src/MeshWeaver.Documentation/Data/Architecture/ReleaseToProductionPipeline.md | Updates pipeline documentation to reflect CI bake publication/adoption. |
| src/MeshWeaver.Documentation/Data/Architecture/ReleaseProcess.md | Updates versioning docs to keep compiled attributes commit-deterministic and move run number to env var. |
| src/MeshWeaver.Documentation/Data/Architecture/PluginPackaging.md | Updates plugin packaging docs to reflect identity (commit stamp / MVID) semantics. |
| src/MeshWeaver.Documentation/Data/Architecture/NodeTypeCompilation.md | Updates compilation docs to describe the new identity scheme. |
| src/MeshWeaver.Documentation/Data/Architecture/ContinuousDeliveryContract.md | Documents post-promote bake publication and dependent-repo dispatch. |
| src/MeshWeaver.Blazor.Portal/Infrastructure/AppVersionService.cs | Reads injected runtime platform version first, falls back to assembly informational version. |
| memex/Memex.Portal.Shared/ShippedReleaseSeed.cs | Uses injected runtime platform version first for the Admin/PlatformVersion node. |
| memex/Memex.Client/Services/UpdateNotificationService.cs | Adjusts “is newer” logic so clients without .ci.N aren’t nagged on every connect. |
| memex/aspire/Memex.Portal.Distributed/Program.cs | Updates commentary about generation cadence under commit-scoped identity. |
| memex/aspire/Memex.Portal.Distributed/Memex.Portal.Distributed.csproj | Injects MESHWEAVER_PLATFORM_VERSION into container image config under CIRun. |
| Directory.Build.props | Makes CI compiled attributes commit-deterministic and stamps MeshWeaverFrameworkIdentity only on GitHub Actions CI builds. |
| deploy/CHANGE-PROPAGATION.md | Updates change propagation docs to reflect CI bake publication replacing the old bake image. |
| deploy/aks/values.aks.yaml | Adds PreWarm__PrebuiltBundleRoot configuration for CI-published bundle consumption. |
| deploy/aks/DEPLOY-RUNBOOK.md | Updates runbook to explain CI bake publication/adoption instead of a bake Job. |
| .github/workflows/main-cd.yml | Adds publish-bake and notify-dependents jobs; requests actions: read. |
| .github/workflows/dotnet-test.yml | Updates job comments to reflect identity-keyed artifacts and cross-workflow adoption. |
| .github/scripts/publish-bake-bundles.sh | Adds a script to publish baked bundles to Azure Files in the identity-keyed layout. |
Suppressed comments (1)
src/MeshWeaver.Plugin.Build/ModulePackCommand.cs:194
- This warning still says "framework MVID", but the value recorded is now the framework identity (CI commit stamp or local MVID). The message should match the new semantics to avoid misleading operators.
Console.Error.WriteLine(
$"warning: {FrameworkIdentity.IdentityAssembly}.dll not found at {graphDll} — the "
+ "bundle records no built-against framework MVID (diagnostic metadata only; the "
+ "landing gate is --min-mesh-version).");
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Test Results (shard 0)919 tests 918 ✅ 10m 59s ⏱️ Results for commit 1ac75bb. |
Test Results (shard 4)1 606 tests 1 600 ✅ 6m 32s ⏱️ Results for commit 1ac75bb. |
Test Results (shard 3) 11 files 11 suites 6m 15s ⏱️ Results for commit 1ac75bb. |
Test Results (shard 5)1 371 tests 1 370 ✅ 6m 32s ⏱️ Results for commit 1ac75bb. |
Test Results (shard 1)2 164 tests 2 061 ✅ 7m 18s ⏱️ Results for commit 1ac75bb. |
Test Results (shard 2)2 447 tests 2 443 ✅ 8m 7s ⏱️ Results for commit 1ac75bb. |
Test Results 65 files 65 suites 45m 46s ⏱️ Results for commit 1ac75bb. |
#1660 WS3 — commit-deterministic framework identity: the CI bake actually seeds at boot
Fixes the measured regression: every pod boot logged
DynamicTypePreWarmer: 282 of 282 dynamic NodeType(s) need building … baked=0 pending=282 frameworkstale=269— the CI bake existed butseeded ZERO, because the framework identity differed between the CI run that baked and the CD run
that built the image.
The history, precisely ("we had this")
a6a9e4cdf, 2026-07-02) introducedInformationalVersion = $(Version)+build.<UtcNow.Ticks>underCIRun— a per-build stamp compiled into every assembly,minting a fresh
MeshWeaver.GraphMVID per build.ded327556(2026-07-28) built the storage-publishing bake (memex-bake, a run-once Job likedb-migration, writing the shared assembly cache before the portal rolled). Born broken by the
ticks stamp: two publishes of the same commit could never share an identity.
64a9ff04f, 2026-08-12) retired it after it ran in zero namespaces, naming this exactwork as the fix: "Making it work needs FrameworkVersion widened … see Directory.Build.props".
mw-plugin-test --bake-output→baked-assemblies-<identity>), which could not be adopted cross-workflow for the same reason.The fix — one identity, deterministic per commit
Compile inputs (Directory.Build.props): under
CIRunnothing per-build reaches any compiledattribute:
InformationalVersion = $(PlatformVersion)(the SDK appends+<checked-out sha>),FileVersionpinned to$(_VersionNumeric).0.$(Version)keeps the monotonic.ci.<run#>forNuGet package versions and image tags only — verified it reaches no compiled attribute.
The identity (
FrameworkBuildIdentity, one resolution):CIRunandGITHUB_ACTIONS— a CI checkout is clean at its commit; a local-p:CIRun=truerepro can be dirty and stays on the MVID) are stampedAssemblyMetadata("MeshWeaverFrameworkIdentity", "g<sha>"); the identity is the commit. Acommit names the whole tree — every source file and every package pin — so any
reference-assembly change flips it: the widening over the whole TPA reference set that the old
🚨 comment demanded (Graph's bare MVID only covers Graph's own inputs), delivered as a strict
superset rather than a per-host TPA hash (which could never match between the bake host and the
portal — different processes have different TPAs).
Every consumer moved by construction — all flowed through
NodeTypeCompilationHelpers.FrameworkVersionalready:HasUsableBuild/HasStaleFrameworkBuild(staleness gate),ApplyCompileSuccess(CompiledFrameworkVersionstamp),
FileSystemAssemblyStore.FrameworkTag(store filename tag),NodeTypeBakeStatus(probe/report),
BuildProtocolDriver(build-claim fingerprint),PrebuiltAssemblySeeder(adoption gate +
LiveFrameworkMvid),BakeOutput(bake artifact identity → thebaked-assemblies-<identity>artifact key). The one file-side reader,Plugin.Build/FrameworkIdentity, gainedReadIdentity(metadata-only PE read of the stamp,MVID fallback) so producers record what the runtime actually compares;
ModulePackCommandandResolveFrameworkMviduse it.AssemblyCacheGenerations.TagOfaccepts theg+7-hex tag shape soCI generations stay reclaimable.
Delivery to storage (the restored memex-bake outcome, without the extra image):
main-cdgainspublish-bake(needs promote): downloads the Build-and-Test run'sbaked-assemblies-*artifact for the same commit and publishes it via.github/scripts/publish-bake-bundles.shto the portals' Azure Files data shares:prebuilt-bundles/<identity>/<source>/<bundle>.zip. Config is preflighted red (noskip-trapdoor): missing
BAKE_PUBLISH_TARGETSfails naming exactly what to provision. A missingartifact only warns (a reuse-green run legitimately baked nothing).
ShippedPrebuiltBundles.SeedPublishedRoot(new configPreWarm:PrebuiltBundleRoot, set to/data/prebuilt-bundlesinvalues.aks.yaml) seeds ONLYthe pod's own identity's subtree before the NodeType sweep — same seeder, same MVID gate
underneath. Unconfigured → inert.
prebuilt/lane is untouched and now redundant for this purpose (it wasnever wired into main-cd); it remains the same-build lane for the tester.
"Subscribe to new releases of any dependency and rebuild": in-repo dependency bumps need no
event — a Dependabot merge is a commit, a commit IS a new identity, the next CI run re-bakes. The
cross-repo edge is closed by
notify-dependents: onerepository_dispatch(
meshweaver-framework-released, payload commit/identity/version) per repo inBAKE_SUBSCRIBER_REPOS; node repos subscribe withon: repository_dispatch: { types: [meshweaver-framework-released] }and re-run their gate+bake.Reporter-class (like the platform-update webhook): unconfigured is a loud notice, never red.
Runtime version display: the run number stays visible without being compiled in — the
container publish injects
$(Version)asMESHWEAVER_PLATFORM_VERSION(
ContainerEnvironmentVariable, image config);ShippedReleaseSeed.InstalledPlatformVersion(self-updater current-version + Admin/PlatformVersion node) and
AppVersionServiceread it first.The self-updater's target selection keys on ACR tags (unchanged, still
.ci.<run#>monotonic);its
IsNewer(target, current)gate keeps its exact semantics because current keeps the runnumber via the env var.
Memex.Client(not in the solution/CI — MAUI, built separately) comparesat release granularity when its own build carries no run number, so a current client is not
nagged every connect.
Proof (measured, not claimed)
MeshWeaver.Graph,-p:CIRun=true,GITHUB_RUN_NUMBER=1111vs2222,70 s apart: output DLLs bit-identical (
cmpclean), MVIDec8624fdd279496f87e01c70c7a7d051both, identity
g5be81c02e…both,InformationalVersion = 3.0.0-rc3+5be81c02e….e33b7842b5244735a69d70b9d1739c80(identity/commitunchanged — which is exactly why the stamp is CI-only; see the props comment).
AssemblyInfo.cscarries no run number and no ticks (grepped both builds).MeshWeaver.Graph.Test(FrameworkBuildIdentity/AssemblyCacheRetention/PrebuiltAssemblySeeder/NodeTypeCompilationHelpers filters): 59/59 green, including new pins: stamped-wins/MVID-
fallback resolution, PE-read == loaded-assembly-read, store tag attributable by the sweep.
MeshWeaver.PluginCatalog.Test(ShippedPrebuiltBundles/ModuleLandingService/ModuleFunnel):21/21 green, including the CI-writes → portal-reads round trip
(
PublishedRoot_SeedsOwnIdentitysDirectory_AndIgnoresOtherIdentities) asserting the publishedlayout is found by the sweep's store probe (
pending=0shape) and another identity's bundlesare invisible.
DocumentationLinkIntegrityTestgreen after the doc updates.-c Release -warnaserror --no-incrementalgreen.Provisioned as part of this change (already live)
BAKE_PUBLISH_TARGETS= the three portals' data shares on storage accountf45d8e671caa74b96a6c37f(memexpvc-f3e4220c…, memex-cloudpvc-d9f747ad…, client-ipvc-9e341c82…— resolved from the PVs behind eachmemex-dataclaim).Storage File Data Privileged Contributorfor the CD OIDC principal(
github-actions-deploy) on that storage account — verified assigned.Ops follow-ups (not in this PR)
PreWarm__PrebuiltBundleRooton the next chart-managed apply (or akubectl set env) — the values.aks.yaml change is the persistent home; prod rollout is its owndecision.
BAKE_SUBSCRIBER_REPOS+DEPENDENT_DISPATCH_TOKEN(cross-repo dispatch) are deliberatelyunprovisioned — needs an org token decision; the notify job says so loudly per run. Receiver
wiring for the node repos is documented in ContinuousDeliveryContract.md (follow-up: plugins /
education / reinsurance / social-media workflows).
prebuilt-bundles/retention on the shares: bundles accumulate per commit; small, but worth asweep entry later (the assembly-cache retention pattern applies).
Closes the WS3 item of #1660.
🤖 Generated with Claude Code