Repository navigation
Plugin build contract: the bake ships the package, the install seed is bounded by its own work - #2478
Conversation
…ld them Four steps — take the image, install dependencies, build your plugin, test it — and one rule inside them: step 2 is an INSTALL, never a build. A repo that compiles its dependencies has rebuilt somebody else's product, once per mesh, on every run, and is testing bytes nobody will ship. The tooling for this already exists and is unused. `mw-plugin-test` is the producer AND the consumer (#1763): `--bake-output` emits assemblies with no mesh, `--seed` stands a mesh up on those bytes before it boots. Counted today: MeshWeaver (core) --bake-output=21 --seed=9 every plugin repo --bake-output=1-2 --seed=0 So core uses the split and no plugin repo does. All of them produce a bake and throw it away, staging dependencies as SOURCE instead — `stage-repo` in SocialMedia and Reinsurance, "Stage the required Plugins modules" in Education, whose build-package-repo.mjs fuses both checkouts into one tree the mesh then compiles end to end. Five meshes per Education run, each recompiling Store's 17 NodeType assemblies. Also records the two mechanisms that make "the bundles are mounted" insufficient, both measured on shard 1 this morning: * the BOOT lane filters bundles to types the mesh already holds — a handful on a fresh mesh — so a not-yet-installed package's bundles are skipped, and zero adopted is the CORRECT reading there; * the INSTALL lane, the only one that can serve a package as it arrives, caps the seed at 60s and on expiry compiles instead. That cap abandons the RESULT but not the WORK: the seed finished and reported 27 assemblies backed thirteen minutes after Store had already started Roslyn. A timeout on an operation that is by construction cheaper than its own fallback can only ever make things worse — when it fires, the system does both. Same inversion #1317 removed one layer down. And the half that is genuinely missing: compiled modules already have a serving lane to the registry, NodeType assemblies are baked to portal storage, and no plugin repo fetches either — core #805 removed the only HTTP registry consumer, leaving a local git tree as the package source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Architecture documentation page that defines a clear “plugin build contract” for MeshWeaver plugin repositories: dependencies must be installed as prebuilt artifacts (not rebuilt), and the gate must consume the baked bytes it tests. This fits into the broader CI/content-bake direction by codifying the intended bake/seed split and explaining why “green” is not evidence that seeding occurred.
Changes:
- Introduces
/Doc/Architecture/PluginBuildContractdocumenting the four-step plugin build flow (take image → install deps → build plugin → test). - Documents the
mw-plugin-testproducer/consumer split (--bake-outputvs--seed) and why “install must never be a build”. - Records measured current-state across repos and explains the observed failure mode where install-time seeding times out and falls back to compilation.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…n work Two changes, each of which the plugin build contract (Doc/Architecture/ PluginBuildContract, this branch) needs before step 2 — "install my dependencies" — can mean install rather than rebuild. 1. THE BAKE EMITS NODE DEFINITIONS. `BundleReader.Manifest.Content` has said all along that a consumer using a package as an upstream needs them — "the bytes only stamp nodes that already exist, so without the definitions there is nothing to stamp". The writer accepted them. Both bake call sites passed nothing. So every published bundle was assemblies-only, which is exactly why node-repo-publish-bake's `stage-repo` input carries "staging cannot be dropped until a published artifact carries the upstream's NODE DEFINITIONS", and why every plugin repo stages its upstreams as SOURCE and recompiles them. Measured 2026-08-27: core passes --seed in 9 places, every plugin repo in 0. BakeOutput.NodeDefinitionsOf collects the package's whole tree — nodes, source, assets — relative to its source folder; both BakeOutput and TreeBake pass it, with SourceIncluded declared true (a declaration, per its own remarks; this producer withholds nothing). Pinned by BundleCarriesTheUpstreamTest in the Education shape: repo 1 bakes a base package, and repo 2 reconstructs and rebuilds it from repo 1's BUNDLE with no checkout of repo 1 at all. 2. THE INSTALL-TIME SEED IS BOUNDED BY ITS OWN WORK, not by a wish. It was a flat 60 s over a seed that is a sequential Concat with a 30 s budget PER assembly — so a 17-assembly package (Store) was legitimately allowed several minutes while the outer cap fired at one. And Timeout abandons the RESULT, not the WORK: measured on Education e2e shard 1, Store was in Roslyn at 06:38, the "adoption attempt failed" line landed at 06:51, and the tally right behind it reported 27 assemblies backed. When the cap fired the system did BOTH — the seed and the compile it exists to avoid. Same inversion #1317 removed one layer down: a join must not out-run the answers it is joining. The bound is now floor + per-type × types, where per-type IS the inner budget, so the outer bound cannot expire while the inner work is inside its own. Pure (SeedBound), pinned by InstallSeedBoundTest with no hub. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…po wires it; and the gate cannot fetch on a PR without a pull_request federated credential (AADSTS700213, measured)
…ne (mwb_), issued by a Store/BuildPrincipal control node, tied to the same token that publishes; the registry serves publications so the gate never touches Azure
…esh — a trust rule on Store/BuildPrincipal, no secret; one more branch of the fork that already verifies mwa_ tokens
…outruns-its-fallback
Test Results (shard 3)1 001 tests 997 ✅ 17m 52s ⏱️ Results for commit 47331ad. |
Test Results (shard 5)4 482 tests 4 376 ✅ 11m 57s ⏱️ Results for commit 47331ad. |
Test Results (shard 0)962 tests 962 ✅ 6m 19s ⏱️ Results for commit 47331ad. |
Test Results (shard 4) 10 files 10 suites 9m 12s ⏱️ Results for commit 47331ad. |
Test Results (shard 1) 8 files 8 suites 7m 15s ⏱️ Results for commit 47331ad. |
Test Results (shard 2)1 483 tests 1 483 ✅ 6m 48s ⏱️ Results for commit 47331ad. ♻️ This comment has been updated with latest results. |
Test Results 52 files 52 suites 59m 25s ⏱️ Results for commit 47331ad. ♻️ This comment has been updated with latest results. |
|
Shard 2 went red with ten Monolith compile tests failing. Diagnosed before re-running, not after:
Environmental — Roslyn's metadata writer under runner pressure on the shard that carries the Monolith floor. Re-ran the failed jobs; nothing pushed. |
Three pieces that together make step 2 of the plugin build contract — install my dependencies — mean install rather than rebuild. The doc lands first so the code has something to be measured against.
1.
Doc/Architecture/PluginBuildContractFour steps: take the image, install dependencies as built artifacts, build your plugin, test it. One rule inside them: step 2 is an install, never a build. Plus the measured state of every repo against it, and why mounting bundles alone is not enough.
2. The bake emits node definitions
BundleReader.Manifest.Contenthas said all along that a consumer using a package as an upstream needs them — "the bytes only stamp nodes that already exist, so without the definitions there is nothing to stamp." The writer accepted them. Both bake call sites passed nothing. So every published bundle was assemblies-only, which is exactly whynode-repo-publish-bake'sstage-repocarries "staging cannot be dropped until a published artifact carries the upstream's NODE DEFINITIONS" — and why every plugin repo stages upstreams as source and recompiles them.--bake-output--seedBakeOutput.NodeDefinitionsOfcollects the package's whole tree relative to its source folder; bothBakeOutputandTreeBakepass it,SourceIncludeddeclaredtrue.Pinned in the Education shape (
BundleCarriesTheUpstreamTest): repo 1 bakes a base package; repo 2 reconstructs and rebuilds it from repo 1's bundle with no checkout of repo 1 at all. That is what "install my dependencies" means, and it was impossible before this.3. The install-time seed is bounded by its own work
It was a flat 60 s over a seed that is a sequential
Concatwith a 30 s budget per assembly — a 17-assembly package (Store) was legitimately allowed minutes while the cap fired at one. AndTimeoutabandons the result, not the work. Measured on Education e2e shard 1 this morning:When the cap fired the system did both — the seed and the compile it exists to avoid. Same inversion #1317 removed one layer down. Bound is now
floor + perType × typeswhereperTypeis the inner budget, so the outer bound cannot expire while the inner work is inside its own. Pure function, pinned with no hub (InstallSeedBoundTest).Not in this PR
Wiring
--seedinto each plugin repo's gate and droppingstage-repo— that is one PR per satellite once this is on main, and each is now a mechanical change rather than a design one.🤖 Generated with Claude Code