Skip to content

fix: a dependency record's module entry is a FLOOR, not an MVID pin (#3934) - #3946

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
worktree-agent-a9eb4ee0952a3e555
Sep 10, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
worktree-agent-a9eb4ee0952a3e555

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Implements #3934 — the platform owner's decision, not a proposal: a dependency record's module entry states a floor, not an MVID pin.

The chain this dissolves, measured on memex 2026-09-10

CompiledDependencies.CreateIdResolver resolved a module to mvid:<guid> — an exact build, checked first. An MVID moves on every compilation by construction, and Roslyn's deterministic MVID also hashes the absolute source paths, so two build-workspace lanes of one publication compiling identical source at $GITHUB_WORKSPACE and /repo fork it with byte-identical version attributes. No producer agreement can fix that.

  • PrebuiltAssemblySeeder declined 22 of 272 entries at one identity, across 3 module names, on one entry each: MeshWeaver.Markdown.Collaboration recorded mvid:798f92a0…, live mvid:825581836a…, with all eight ref: surfaces and !toolchain agreeing. socialmedia was 20/26, all six declines that one assembly.
  • A declined bundle compiles in-portal → latestAssemblyCollection: "local", which FileSystemAssemblyStore documents as "the bytes live in the local filesystem cache only; cross-silo readers must recompile". memex runs 2 replicas, so each declined the other's stamp and recompiled — two forced re-activations 2 seconds apart with identical inputs.
  • Net: https://memex.meshweaver.cloud/Posts/SavThankYou returned HTTP 200 rendering nothing for the whole day — Preview, Write, PostCard all absent — while the NodeType read compilationStatus: Ok. recycle was measured not to fix it.

What the floor is, and how a consumer compares it

A module resolves min:<version> over InstalledModuleAssembly.Version — its AssemblyInformationalVersion with +<sha> stripped. That is the one reader, and both the portal's resolver (NodeTypeCompilationHelpers.DependencyIdResolverOf) and the bake host's (mw-plugin-test's BakeHost.InProcess / ResolveDirectory) go through it, so a floor is never compared against a value nobody else computes.

CompiledDependencies.Satisfies is the comparison: ordinal equality, plus exactly one relaxation — two min: values order by NuGetVersionComparer, and live at-or-above stamped satisfies. MeshWeaver.Compiler now references MeshWeaver.Plugin.Packaging for that comparer rather than re-implementing the fold (check-module-platform-floor.py's own header: "two call sites computing the same fold differently either never converge or never fire, and both are silent"). A module stating no version keeps the exact mvid: pin — an environment that cannot state a version has no floor to offer, and inconclusive stays on the rebuild side.

Relation to what already exists: this is the same instinct as ModuleAdoptionPolicy R2 ("a different build commit or MVID is not evidence of incompatibility") one layer down, and unlike minMeshVersion it is measured, not authored — the version of the module the producer actually compiled against. ModuleVersionCompatibility's same-MAJOR stale-but-serving rule is a different question (may an already-adopted build keep serving over moved source) and is untouched.

Every reader changed

reader change
CompiledDependencies.Compare (the one comparison behind FindMismatch, FindMismatchAfterReevaluation, Validate, ValidateAfterReevaluation) floor semantics for min:; everything else ordinal
LiveContentKeyOf folds a satisfied entry at its RECORDED value, so the #1976 toolchain demotion survives a module rebuilt above its floor
CreateIdResolver new 4-arg overload taking moduleVersionOf; the 3-arg one delegates with "no version" (kept, so no public removal)
PrebuiltAssemblySeeder.SeedDetailed declines on Validate(...) and logs the status plus the sentence
NodeTypeCompilationHelpers — HasUsableBuild, HasStaleFrameworkBuild, the stale-build kickoff via the same comparison; new ModuleVersionsOf
NodeTypeBakeStatus.Classify / ClassifyDetailed (bake probe + sweep) via the same comparison; the mismatch string now names the floor
mw-plugin-test TreeBake / BakeHost (producer) ModuleVersionsOf / ModuleVersionResolverOf; ShippedByHostProblem's wording
ReleaseAvailability.SealedSetProblem (roll gate) enumerates the module lane by the new IsModuleLaneId so it keeps firing on floors
ServedModuleBytes.RecordedFor (registry #3244) a floor names no build, so it selects nothing and the shelf is served — stated in the doc comment rather than left as silence

What I deliberately did NOT relax

  • !toolchain — the PROXY still invalidates every record on a toolchain change. A module floor met with room to spare licenses nothing there.
  • !input — the direct observation is still exact.
  • ref: platform surfaces and absent — still ordinal. min: never compares to ref:, which keeps Release availability must confirm a CONSISTENT sealed set (dependency records match), not bundle presence — memex-cloud rolled to ci.7621 and then DECLINED SocialMedia at adoption #3175's one-producer rule intact: the bake reports NotChecked rather than quietly matching.
  • The sealed-set consistency gate — "the set carries one name at two builds" and "the set could not be read" still hold a roll for every module entry, floor or pin. Only the exact-build comparison retires, and only for a floor-shaped id, because an instance no longer declines that at adoption.
  • ModuleVersionCompatibility, the framework identity, PrebuiltAssemblySeeder.DeclineReason (framework MVID equality), and R2's link probe: untouched.

Clause 4 — an unmet floor is visible

Validate answers a DependencyRecordOutcome (Satisfied / Drifted / FloorNotMet / NotChecked) modelled on NodeDiagnosticsOutcome (#3912/#3916) — not a fourth way to say "I could not check". FindMismatch is a projection of it, so the string and status forms cannot disagree, and IsSatisfied is false for NotChecked.

And the silent-default path is gone: a recorded build whose bytes this process cannot resolve now overlays the existing AssemblyUnavailable diagnosis — the same one the pinned-release branch beside it already used, with the same version-gated self-heal — instead of binding the default configuration and rendering "Area not found". The overlay keeps the fail-fast contract (UnhandledMessageNack), so a typed request gets a terminal DeliveryFailure naming the NodeType rather than being ignored.

Controls, both directions

DependencyRecordFloorTest — a test that only proved acceptance would also pass for "adopt everything", so every relaxation arm is paired:

  • falsification arm (the old rule declined it, asserted verbatim):
    'MeshWeaver.Markdown.Collaboration' built against mvid:798f92a0, live is mvid:825581836a
  • positive control — a record BELOW its floor still declines, exact text:
    'MeshWeaver.Markdown.Collaboration' needs at least 1.6.0 — this environment has 1.5.0, below the recorded floor
  • min:3.0.0 vs a moved build at the same version answers DependencyRecordOutcome.Satisfied
  • separate arms proving !toolchain, !input, ref: and absent did not move
  • LiveContentKeyOf reproduces the stamped key when a module moved above its floor, and does not when below
  • every adopt-time reader exercised on both sides of one floor; ClassifyDetailed asserted to report the floor sentence
  • SealedSetConsistencyTest gains three arms: a floor-shaped record is still held by a set conflict, still Indeterminate on an unreadable set, and not held merely because the sealed build differs (the one retired check, pinned so "retired" is distinguishable from "quietly stopped firing")

FullMvidAssemblies gains MeshWeaver.Plugin.Packaging — its ratchet caught it, which is what it is for. Cost measured before taking it: a LEAF (zero ProjectReferences, so it pulls no subtree behind it) at 3 commits/60d, against a union already carrying Mesh.Contract (190/30d) and Messaging.Hub (135/30d); and the membership is correct, not merely cheap, since the comparer now decides which builds are adopted.

Would a portal now adopt the bundle that produced the SavThankYou outage?

Yes. All three builds of MeshWeaver.Markdown.Collaboration in that publication carry one InformationalVersion (core pins it to $(PlatformVersion) under CIRun; Plugins#1604 carries the same pin), so record and live both resolve min:3.0.0, Satisfies answers true, and the bundle is adopted instead of declined. Nothing then sets latestAssemblyCollection: "local", so neither replica compiles, neither invalidates the other, and the type's declared views register.

Verification

Release + -warnaserror, one project per invocation, all 0 Error(s): MeshWeaver.Compiler, Compiler.Pipeline, Graph, PluginCatalog, Hosting, Documentation, PluginTester, and the test projects Compiler.Pipeline.Test, Graph.Test, Memex.Portal.Shared.Test, PluginTester.Test, Hosting.Test, Hosting.Orleans.Test.

Tests: Compiler.Pipeline.Test 839 passed, Graph.Test 1516 passed, PluginTester.Test 361 passed, Memex.Portal.Shared.Test sealed-set/release filter 58 passed, Hosting.Test PrebuiltAdoptionPolicyTest 14 passed, DocumentationLinkIntegrityTest passed.

Doc: Doc/Architecture/DependencyRecordFloor, cross-linked from ModuleOwnedSiblingsRide (whose "agreement is cheaper than exclusivity" remedy this replaces) and ModuleAdoptionPolicy. What's New entry, Category: Fix.

Pairs-with: none — nothing public was removed. The diff adds public surface only (MinVersionScheme, Satisfies, IsModuleLaneId, Validate, ValidateAfterReevaluation, DependencyRecordOutcome, InstalledModuleAssembly.Version); the pre-existing CreateIdResolver, FindMismatch and FindMismatchAfterReevaluation signatures are all kept, and the only external caller found — MeshWeaver.Plugins' ShippedPrebuiltBundlesTest, which calls the 3-arg CreateIdResolver positionally with an empty module map — is unaffected, as is its mvid:some-other-build decline assertion.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7

…3934)

A compiled NodeType's dependency record compared each installed module by raw
MVID — an exact build. An MVID moves on every compilation BY CONSTRUCTION, and
Roslyn's deterministic MVID also hashes the ABSOLUTE SOURCE PATHS, so two lanes
of one publication compiling identical source at two paths fork it with
identical version attributes. Every NodeType binding such a module was declined
at adoption, compiled in-portal into `latestAssemblyCollection: "local"` on
whichever replica served, and each replica then invalidated the other's stamp.

Measured on memex 2026-09-10: 22 of 272 entries declined on ONE entry each
(`mvid:798f92a0…` recorded, `mvid:825581836a…` live, all eight `ref:` surfaces
and `!toolchain` agreeing); `/Posts/SavThankYou` returned HTTP 200 rendering
nothing for the whole day — every view `SocialMedia/Post` declares absent —
while the NodeType's own record read `compilationStatus: Ok`. A recycle was
measured not to fix it.

Platform owner's decision, in four clauses: there is no incompatibility (two
builds of one module name are ONE assembly identity, so `Assembly.LoadFrom`
returns the already-loaded copy); a record states a floor; a moved MVID never
triggers a runtime recompile; and an unmet floor must be VISIBLE.

What changes
- A module resolves `min:<version>` over `InstalledModuleAssembly.Version` (its
  `AssemblyInformationalVersion`, build metadata stripped) — the ONE reader both
  the portal's resolver and the bake host go through. A module stating no
  version keeps the exact `mvid:` pin: inconclusive stays on the rebuild side.
- `CompiledDependencies.Satisfies` is the one comparison, ordering with
  `NuGetVersionComparer` (MeshWeaver.Compiler now references
  MeshWeaver.Plugin.Packaging rather than re-implementing the fold). It REFUSES
  to compare across schemes, which is the safety property.
- `Validate`/`ValidateAfterReevaluation` answer a `DependencyRecordOutcome` —
  Satisfied / Drifted / FloorNotMet / NotChecked — modelled on
  `NodeDiagnosticsOutcome` (#3912/#3916), never a fourth way to say "I could not
  check". `FindMismatch` is a projection of it, so the two cannot disagree.
- `LiveContentKeyOf` folds a SATISFIED entry at its recorded value, so the
  toolchain demotion survives a module rebuilt above its floor.
- Clause 4: a recorded build whose bytes this process cannot resolve now
  overlays the existing `AssemblyUnavailable` diagnosis instead of silently
  binding the default configuration — the path that turned this into a blank
  page rather than a message. The overlay keeps the fail-fast NACK.

Deliberately NOT relaxed: `!toolchain`, `!input`, `ref:` platform surfaces and
`absent` are all still ordinal. `ReleaseAvailability`'s sealed-set gate now
enumerates the module lane by `IsModuleLaneId` so it keeps firing on floors —
only its exact-build comparison retires, and only for a floor-shaped id.

`FullMvidAssemblies` gains MeshWeaver.Plugin.Packaging: a LEAF (no project
references of its own) at 3 commits/60d, against a union already carrying
Mesh.Contract (190/30d) and Messaging.Hub (135/30d) — and the membership is
correct, since the comparer now decides which builds are adopted.

Controls run both directions: the old pin's decline asserted verbatim as the
falsification arm, a record below its floor still declining with the exact
sentence, and separate arms for each of the four things that did not move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7
Copilot AI lite review requested due to automatic review settings September 10, 2026 17:47

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.

🟢 Approval recommended

Changes appear coherent and well-tested for the new floor contract; the only remaining feedback is a minor performance/consistency nit in DependencyIdResolverOf.

Pull request overview

This PR implements issue #3934 by changing NodeType dependency records so a module dependency expresses a version floor (min:<version>) instead of an exact build pin (mvid:<guid>), preventing benign module rebuilds from causing prebuilt-bundle declines and runtime recompilation loops.

Changes:

  • Introduces min: module-floor IDs and SemVer ordering via NuGetVersionComparer, with a new DependencyRecordOutcome/Validate API to make “satisfied vs drifted vs floor-not-met vs not-checked” explicit.
  • Updates all major readers (adoption, bake probing, seeding, availability gating) to use the unified comparison semantics and to keep gates firing for floor-shaped module entries.
  • Replaces a “silent default config” fallback on missing compiled bytes with an explicit overlay diagnosis + self-heal, and adds focused tests + documentation for the new contract.
File summaries
File Description
tools/MeshWeaver.PluginTester/TreeBake.cs Adds module-version projection/resolver so the bake host can stamp min: floors consistently with the portal.
tools/MeshWeaver.PluginTester/BakeHost.cs Wires the new 4-arg CreateIdResolver and updates messaging to describe min: floors vs mvid: pins.
test/MeshWeaver.Compiler.Pipeline.Test/FrameworkBuildIdentityTest.cs Updates the full-MVID closure expectation to include MeshWeaver.Plugin.Packaging due to SemVer ordering dependency.
test/MeshWeaver.Compiler.Pipeline.Test/DependencyRecordFloorTest.cs Adds comprehensive tests pinning the new floor semantics, non-relaxations, and reader behavior.
test/Memex.Portal.Shared.Test/SealedSetConsistencyTest.cs Ensures the sealed-set gate still enumerates module-lane entries under min: and that only the intended check is retired.
src/MeshWeaver.PluginCatalog/ServedModuleBytes.cs Documents that min: floors intentionally don’t select an exact sealed module build for serving bytes.
src/MeshWeaver.PluginCatalog/ReleaseAvailability.cs Updates module-lane detection to include min: and retires the exact-build mismatch hold only for floor-shaped IDs.
src/MeshWeaver.Mesh.Contract/InstalledModuleAssembly.cs Adds the single canonical reader for a module’s ordered version (stripped informational version).
src/MeshWeaver.Graph/Configuration/NodeTypeEnrichmentHelpers.cs Replaces default-config fallback on missing bytes with an AssemblyUnavailable overlay + gated self-heal.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-10-a-rebuilt-module-no-longer-makes-a-pages-views-disappear.md Adds a user-facing Fix entry explaining the blank-page symptom and the resolution.
src/MeshWeaver.Documentation/Data/Architecture/ModuleOwnedSiblingsRide.md Updates architecture guidance to reference the new floor semantics as the consumer-side remedy.
src/MeshWeaver.Documentation/Data/Architecture/ModuleAdoptionPolicy.md Notes the dependency-record floor change without altering the higher-level adoption policy.
src/MeshWeaver.Documentation/Data/Architecture/DependencyRecordFloor.md New architecture page defining the floor contract, rationale, non-relaxations, and controls.
src/MeshWeaver.Compiler/NodeSetCompiler.cs Minor doc comment adjustment for CreateIdResolver cref formatting.
src/MeshWeaver.Compiler/MeshWeaver.Compiler.csproj Adds a reference to MeshWeaver.Plugin.Packaging to reuse the repo’s canonical SemVer ordering.
src/MeshWeaver.Compiler/DependencyRecordOutcome.cs Introduces DependencyRecordOutcome and DependencyRecordStatus to make “what was established” explicit.
src/MeshWeaver.Compiler/ContentKeyReevaluation.cs Doc comment adjustment for CreateIdResolver cref formatting.
src/MeshWeaver.Compiler/CompiledDependencies.cs Implements min: scheme, Satisfies, outcome-based validation, and module-lane predicate helper.
src/MeshWeaver.Compiler.Pipeline/PrebuiltAssemblySeeder.cs Switches decline logic to outcome-based Validate and logs the status + reason explicitly.
src/MeshWeaver.Compiler.Pipeline/NodeTypeCompilationHelpers.cs Uses the new floor-aware resolver and adds module-version mapping for the live environment.
Review details
  • Files reviewed: 20/20 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Comment on lines +2741 to 2745
internal static Func<string, string?> DependencyIdResolverOf(IMessageHub hub)
{
var versions = ModuleVersionsOf(hub);
return Compiler.CompiledDependencies.CreateIdResolver(
FrameworkBuildIdentity.ProcessSurfacePairs,
@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

254 tests   254 ✅  2m 37s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

496 tests   496 ✅  45s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

389 tests   389 ✅  59s ⏱️
  2 suites    0 💤
  2 files      0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 908 tests   1 908 ✅  3m 24s ⏱️
    4 suites      0 💤
    4 files        0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    6 files      6 suites   4m 59s ⏱️
2 626 tests 2 434 ✅ 192 💤 0 ❌
2 630 runs  2 438 ✅ 192 💤 0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 267 tests   1 265 ✅  3m 39s ⏱️
    3 suites      2 💤
    3 files        0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   17 files     17 suites   16m 26s ⏱️
6 940 tests 6 746 ✅ 194 💤 0 ❌
6 944 runs  6 750 ✅ 194 💤 0 ❌

Results for commit e8ba5e9.

♻️ This comment has been updated with latest results.

… map

`ArchitectureTopicMapTest.EveryArchitecturePage_IsListedInTheTopicMap` caught it —
a working URL alone does not make a page discoverable. Filed under "Node types",
beside the Toolchain Re-evaluation Lane it interacts with.

Local: MeshWeaver.Documentation.Test 373 passed (the whole project this time, not
just DocumentationLinkIntegrityTest — which is what let this reach CI).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7
@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 03f4d7b Sep 10, 2026
34 checks passed
rbuergi added a commit that referenced this pull request Sep 11, 2026
…line that names how it was answered

#3221 asks whether a sibling that is ITSELF another package's declared module
should ride into a second bundle. It is a design question, and everything that
made it hard has since moved: #3732/#3933 gave a publication ONE compilation,
#3934/#3946 made a record's module entry a FLOOR rather than an MVID pin, and
#3962/#3973 corrected the guard's stated reason. This settles it, measured.

THE ANSWER — it rides, IFF the platform host does not already ship it.
Being another package's declared module is not a reason to exclude it:
exclusivity was never available (AI `requires [Store]` while Essentials, which
DECLARES the assembly, `requires [AI, …]` — a package cycle) and would break a
supported install. What makes the ride safe is not exclusivity but ONE BUILD per
name per publication, asserted by the producer at compose time and again by the
consumer over the whole sealed set.

MEASURED 2026-09-11, with denominators:

  * Reference graph, MeshWeaver.Plugins main @ d6fa0338 — 79 index.json
    scanned, 37 declare a content.module, 148 csproj under src/. 18 of 37
    declared modules transitively reference another package's declared module;
    4 of 37 are RIDDEN (Markdown.Collaboration ×14, AI ×13, Maps ×4, Import ×1).
    That is the upper bound, not the answer.

  * What is actually PACKED — run 34563887604, success:
      job 103155487545 (MeshWeaver.AI):
        "closure: MeshWeaver.Markdown.Collaboration.dll does NOT ride —
         the platform ships it (measured off …/platform-refs)"
      => the exact pair this issue names no longer rides at all.
      job 103155487593 (MeshWeaver.Northwind.Application):
        "closure: MeshWeaver.Maps.dll RIDES — module-owned"
        "closure: MeshWeaver.Import.dll RIDES — module-owned"
      => the shape IS live today, on two names, in one bundle of 37.

  * The verdict on the composed sets — Plugins publish-bake 103160836446 and
    gate shard 103159059393; MeshWeaver.Crm publish-bake 103166073109, which
    composes ai.module.nupkg and essentials.module.nupkg side by side (the pair
    the 2026-09-08 hold named):
      "module set: 5 … 0 carried by more than one bundle, 0 carried at more than
       one BUILD" / "module set: 4 … 0 … 0"

CORRECTION — the page said the refusal was INERT until each satellite moves its
pin. Measured over every `uses: Systemorph/MeshWeaver/.github/workflows/…` line
in all six node repos: FIVE consume node-repo-gate.yml and
node-repo-publish-bake.yml at @main (Plugins, Reinsurance, SocialMedia,
Manufacturing, Crm), so they took the refusal the hour it merged; only
MeshWeaver.Education pins a sha (67cbbe0…) and the verdict is absent from that
file at that sha. Both directions off the LOGS, not inferred from the ref:
Plugins run 34563887604 and Crm run 34567369849 print the verdict line;
Education's publish-bake for run 34487980275 composes the same four bundles and
prints none.

THE ONE CODE CHANGE — node-repo-module-pack.yml's witness line printed its path
TWICE ("measured off /tmp/app/tmp/app"), because
`${PLATFORM_APP:+measured off $PLATFORM_APP}${PLATFORM_APP:-declared by …}`
expands `:-` to the VALUE when the variable is set. That is the branch that
matters — it is the one saying the answer came from the image rather than from a
hand-maintained list — and it is quoted verbatim in the page above. Stated once
in a `witness` variable instead.

CONTROL, both directions, executed against the REAL workflow file (the two
assignment lines and the echo extracted by content, never copied):

  before:
    MEASURED  … the platform ships it (measured off /tmp/app/tmp/app); …   ← defect
    DECLARED  … the platform ships it (declared by src/platform-shipped.txt); …
  after:
    PASS MEASURED  … (measured off /tmp/app)            [asserts "/tmp/app/tmp/app" ABSENT]
    PASS DECLARED  … (declared by src/platform-shipped.txt)  [asserts "measured off" ABSENT]

VERIFIED
  YAML parses — node-repo-module-pack 6 jobs
  check-workflow-timeouts.py          84 jobs, 2 exempt, 0 violations
  check-workflow-shell.py             0 live findings
  check-workflow-yaml-keys.py         34 files, 0 violations
  check-abbreviated-shas.py           34 files, 0 abbreviations
  check-workflow-permission-pairing.py 2 pairs, 0 violations
  MeshWeaver.Documentation      -c Release -warnaserror  0 Error(s), 0 Warning(s)
  MeshWeaver.Documentation.Test -c Release -warnaserror  0 Error(s), 0 Warning(s)
  DocumentationLinkIntegrityTest + ArchitectureTopicMapTest  5/5 passed (doclinks.trx)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Sep 11, 2026
…line that names how it was answered

#3221 asks whether a sibling that is ITSELF another package's declared module
should ride into a second bundle. It is a design question, and everything that
made it hard has since moved: #3732/#3933 gave a publication ONE compilation,
#3934/#3946 made a record's module entry a FLOOR rather than an MVID pin, and
#3962/#3973 corrected the guard's stated reason. This settles it, measured.

THE ANSWER — it rides, IFF the platform host does not already ship it.
Being another package's declared module is not a reason to exclude it:
exclusivity was never available (AI `requires [Store]` while Essentials, which
DECLARES the assembly, `requires [AI, …]` — a package cycle) and would break a
supported install. What makes the ride safe is not exclusivity but ONE BUILD per
name per publication, asserted by the producer at compose time and again by the
consumer over the whole sealed set.

MEASURED 2026-09-11, with denominators:

  * Reference graph, MeshWeaver.Plugins main @ d6fa0338 — 79 index.json
    scanned, 37 declare a content.module, 148 csproj under src/. 18 of 37
    declared modules transitively reference another package's declared module;
    4 of 37 are RIDDEN (Markdown.Collaboration ×14, AI ×13, Maps ×4, Import ×1).
    That is the upper bound, not the answer.

  * What is actually PACKED — run 34563887604, success:
      job 103155487545 (MeshWeaver.AI):
        "closure: MeshWeaver.Markdown.Collaboration.dll does NOT ride —
         the platform ships it (measured off …/platform-refs)"
      => the exact pair this issue names no longer rides at all.
      job 103155487593 (MeshWeaver.Northwind.Application):
        "closure: MeshWeaver.Maps.dll RIDES — module-owned"
        "closure: MeshWeaver.Import.dll RIDES — module-owned"
      => the shape IS live today, on two names, in one bundle of 37.

  * The verdict on the composed sets — Plugins publish-bake 103160836446 and
    gate shard 103159059393; MeshWeaver.Crm publish-bake 103166073109, which
    composes ai.module.nupkg and essentials.module.nupkg side by side (the pair
    the 2026-09-08 hold named):
      "module set: 5 … 0 carried by more than one bundle, 0 carried at more than
       one BUILD" / "module set: 4 … 0 … 0"

CORRECTION — the page said the refusal was INERT until each satellite moves its
pin. Measured over every `uses: Systemorph/MeshWeaver/.github/workflows/…` line
in all six node repos: FIVE consume node-repo-gate.yml and
node-repo-publish-bake.yml at @main (Plugins, Reinsurance, SocialMedia,
Manufacturing, Crm), so they took the refusal the hour it merged; only
MeshWeaver.Education pins a sha (67cbbe0…) and the verdict is absent from that
file at that sha. Both directions off the LOGS, not inferred from the ref:
Plugins run 34563887604 and Crm run 34567369849 print the verdict line;
Education's publish-bake for run 34487980275 composes the same four bundles and
prints none.

THE ONE CODE CHANGE — node-repo-module-pack.yml's witness line printed its path
TWICE ("measured off /tmp/app/tmp/app"), because
`${PLATFORM_APP:+measured off $PLATFORM_APP}${PLATFORM_APP:-declared by …}`
expands `:-` to the VALUE when the variable is set. That is the branch that
matters — it is the one saying the answer came from the image rather than from a
hand-maintained list — and it is quoted verbatim in the page above. Stated once
in a `witness` variable instead.

CONTROL, both directions, executed against the REAL workflow file (the two
assignment lines and the echo extracted by content, never copied):

  before:
    MEASURED  … the platform ships it (measured off /tmp/app/tmp/app); …   ← defect
    DECLARED  … the platform ships it (declared by src/platform-shipped.txt); …
  after:
    PASS MEASURED  … (measured off /tmp/app)            [asserts "/tmp/app/tmp/app" ABSENT]
    PASS DECLARED  … (declared by src/platform-shipped.txt)  [asserts "measured off" ABSENT]

VERIFIED
  YAML parses — node-repo-module-pack 6 jobs
  check-workflow-timeouts.py          84 jobs, 2 exempt, 0 violations
  check-workflow-shell.py             0 live findings
  check-workflow-yaml-keys.py         34 files, 0 violations
  check-abbreviated-shas.py           34 files, 0 abbreviations
  check-workflow-permission-pairing.py 2 pairs, 0 violations
  MeshWeaver.Documentation      -c Release -warnaserror  0 Error(s), 0 Warning(s)
  MeshWeaver.Documentation.Test -c Release -warnaserror  0 Error(s), 0 Warning(s)
  DocumentationLinkIntegrityTest + ArchitectureTopicMapTest  5/5 passed (doclinks.trx)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi deleted the worktree-agent-a9eb4ee0952a3e555 branch September 13, 2026 10:22
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