Skip to content

Plugin build contract: the bake ships the package, the install seed is bounded by its own work - #2478

Merged
rbuergi merged 6 commits into
mainfrom
fix/install-seed-cap-outruns-its-fallback
Aug 27, 2026
Merged

rbuergi merged 6 commits into
mainfrom
fix/install-seed-cap-outruns-its-fallback

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

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/PluginBuildContract

Four 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.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 carries "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 --seed
core 21 9
every plugin repo 1–2 0

BakeOutput.NodeDefinitionsOf collects the package's whole tree relative to its source folder; both BakeOutput and TreeBake pass it, SourceIncluded declared true.

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 Concat with a 30 s budget per assembly — a 17-assembly package (Store) was legitimately allowed minutes while the cap fired at one. And Timeout abandons the result, not the work. Measured on Education e2e shard 1 this morning:

06:38  Store: "⏳ Compiling… Running Roslyn against 9 source binaries"
06:51  Install: prebuilt adoption attempt failed — the installed types compile instead
06:51  27 prebuilt assembly(ies) … 27 already current

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 × types where perType is 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 --seed into each plugin repo's gate and dropping stage-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

…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>
Copilot AI lite review requested due to automatic review settings August 27, 2026 07:26

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

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/PluginBuildContract documenting the four-step plugin build flow (take image → install deps → build plugin → test).
  • Documents the mw-plugin-test producer/consumer split (--bake-output vs --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>
@rbuergi rbuergi changed the title Document the plugin build contract — install dependencies, never rebuild them Plugin build contract: the bake ships the package, the install seed is bounded by its own work Aug 27, 2026
@rbuergi
rbuergi enabled auto-merge August 27, 2026 07:41
…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
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 001 tests   997 ✅  17m 52s ⏱️
    9 suites    4 💤
    9 files      0 ❌

Results for commit 47331ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

4 482 tests   4 376 ✅  11m 57s ⏱️
    9 suites    106 💤
    9 files        0 ❌

Results for commit 47331ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

962 tests   962 ✅  6m 19s ⏱️
  8 suites    0 💤
  8 files      0 ❌

Results for commit 47331ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

   10 files     10 suites   9m 12s ⏱️
2 399 tests 2 397 ✅ 2 💤 0 ❌
2 400 runs  2 398 ✅ 2 💤 0 ❌

Results for commit 47331ad.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

    8 files      8 suites   7m 15s ⏱️
1 093 tests   901 ✅ 192 💤 0 ❌
1 686 runs  1 494 ✅ 192 💤 0 ❌

Results for commit 47331ad.

@github-actions

github-actions Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 483 tests   1 483 ✅  6m 48s ⏱️
    8 suites      0 💤
    8 files        0 ❌

Results for commit 47331ad.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    52 files      52 suites   59m 25s ⏱️
11 418 tests 11 114 ✅ 304 💤 0 ❌
12 014 runs  11 710 ✅ 304 💤 0 ❌

Results for commit 47331ad.

♻️ This comment has been updated with latest results.

@rbuergi

rbuergi commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Shard 2 went red with ten Monolith compile tests failing. Diagnosed before re-running, not after:

  • Every failure is the same shape — NullReferenceException inside Roslyn's emitter: Microsoft.Cci.MetadataWriter.GetConsolidatedTypeParameters → NamedTypeSymbol.ContainingTypeDefinition. Not a MeshWeaver frame.
  • Control: the four failing classes are 7/7 green on unmodified origin/main locally.
  • Repro: the same four are 7/7 green on this branch locally.
  • The runtime surface of this PR is three files; Hosting.Monolith.Test references none of PluginTester, and SeedPrebuiltAssemblies returns 0 immediately when no IPrebuiltAssemblyConsumer is registered — which the Monolith host does not register. There is no path from this diff into Roslyn emit in that suite.
  • Same Microsoft.CodeAnalysis.CSharp 5.6.0 on both. Main's own run on 397db998 (post-Memex.Portal.Shared stops composing Postgres, gRPC, SignalR and InstanceSync; mesh.proto moves to Mesh.Contract #2479, which this branch merged) was green on shard 2.

Environmental — Roslyn's metadata writer under runner pressure on the shard that carries the Monolith floor. Re-ran the failed jobs; nothing pushed.

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