Plugins

A plugin is not a package — it is a repo of ordinary mesh nodes. MeshWeaver ships dynamic content (node types, whole modules, docs, sample data, AI content) from a git repository of node repos, installed with the framework's existing GitSync and compiled live on the mesh. There is no manifest format and no NuGet — the node is the manifest, and the mesh versions it.

This is deliberately the framework replicated by nothing: it reuses Static Repo Import, Node Type Compilation, and the mesh's node/version machinery.

A plugin is mesh nodes

Compatibility is API-based. A plugin should continue working across platform and dependency versions while the APIs it uses remain compatible. Version/build-identity differences alone are not runtime refusals; missing or incompatible referenced APIs are. Follow the Module Adoption Policy for measured loadability, continuity and eager adoption. Preserve a working generation when a newer one cannot load, and report the actual missing contract. Dependency declarations describe intent; they do not replace measurement. For source NodeTypes, an unusable prebuilt cache entry normally means compilation against the current platform, rather than holding the platform to that entry's build.

A node repo is exactly the on-disk shape the sample partitions use — a *.json per node plus its Source/ (and Test/) C# — e.g. the Publish plugin and its Slide type:

Publish/index.json                the plugin ROOT — nodeType "Store/Plugin", content PluginContent (INSIDE the folder)
Publish/Slide/index.json          a NodeType node (Content = NodeTypeDefinition{ configuration })
Publish/Slide/Source/*.cs         the content type + layout areas — compiled live
Publish/Slide/Test/*.cs           the type's own tests — compiled together with Source
Publish/manifest.lock             CI-maintained content fingerprint (per-file sha256 + moduleVersion)

A child NodeType may equally be a sibling Widget.json next to a Widget/Source/ folder (the shape Edu/ and LinkedIn/ use) — index.json inside the folder and a sibling <Type>.json are both valid. Only the plugin root must be index.json inside the folder (below).

The plugin folder is the unit of import, so its partition root lives inside it as index.json (the NodeFileMapper mapping GitSync uses) — a sibling <Plugin>.json outside the folder would not be part of the partition.

Concept Where it lives (node-native)
The "manifest" the plugin root (<Plugin>/index.json, nodeType: "Store/Plugin") — its PluginContent carries the cover, price, entry point and install paths
The "kind" (content vs code) the child's NodeType — a NodeType-with-Source/ vs plain content
The "version" the node's version, mesh-tracked, bumped on every change — nobody hand-bumps a field
The "installer" GitSyncGitHubSyncService.ImportFromGitHub / StaticRepoImporter
"what to install" the registry's per-instance PluginGrant (entitlement) plus the installation's PluginCatalog:InstallByDefault / InstallPreInstalledPackages (what it actually installs)

The plugin root is a Store/Plugin node

The root <Plugin>/index.json is nodeType: "Store/Plugin" — the Store's generic root type — carrying a PluginContent (the cover Body, Description, Video/Poster, the OgImage social preview, EntryPoint, MarketingPath, SetupPath, InstallPaths, price). Card-level identity (Name, the one-line Description tagline, Icon, Category) stays on the node itself. The Store/Plugin type owns the install funnel: the anonymous cover, the auto-gated children (AddPluginGating) and the store card.

🚨 Store/Plugin, PluginContent and AddPluginGating are not framework code in this repo — they are themselves a dynamic node type, shipped by the Store plugin in Systemorph/MeshWeaver.Plugins (Store/Plugin/Source/*.cs) and compiled live on the mesh. Grepping this repository for them finds nothing by design. The canonical reference for the repo shape is MeshWeaver.Plugins/AGENTS.md.

That boundary is also why the installer addresses the gating pass's cover grant as a well-known PATH rather than asking the type — see Install Readability for the two doors an install can open, and why waiting on the optional one cost every install 30 seconds.

Install = GitSync

Installing a plugin is importing its node repo into the mesh. ImportFromGitHub(repo, ref, space, …) fetches the git folder and parses each file through FileFormatParserRegistry — which handles the .json node files and the .cs Source, keying each node's type off the parsed nodeType. A NodeType node + its Source/*.cs land as a NodeType with Code children, and the mesh's first-build compile makes the type live. No app rebuild, no NuGet.

The version is the node's version, so "update available" is a git diff, not a hand-edited number.

The registry — one credentialed hub, many consumers

GitSync needs credentials for a private plugins repo — and you don't want every installation to hold them. So one MeshWeaver instance (memex) is the registry: it alone holds the source credential, syncs the plugins repo into its mesh, and re-serves plugins over HTTP. Every other installation pulls from the registry, never from git — the credential is encapsulated in the registry, exactly like npm / NuGet (the registry has source access; clients just speak HTTP).

The surface is two endpoints on the registry (PluginRegistryEndpoints), backed by its configured git sources (the plugins repo). They are not public: a caller presents its registered instance key (Authorization: Bearer mwi_…), and both the listing and the file fetch are scoped to that instance's admin-owned PluginGrant. Without a valid key the request is 401PluginCatalog:RequireInstanceKey defaults to true, so a registry that configures nothing refuses anonymous callers.

Verb Returns
GET /api/plugins { packages:[PackageManifest…] } — the calling instance's granted modules from the configured sources (node-native <Plugin>/index.json Store/Plugin roots by default)
POST /api/plugins/files {id} { files:[{relativePath, content}…] } — the files that plugin (by id) ships

A consuming instance reads this through RegistryPackageSource and installs — from the Store (the PluginCatalog node's Catalog area) on a click, or unattended on first boot via PluginCatalog:InstallByDefault / InstallPreInstalledPackages. Either way the package's files are parsed into nodes and upserted locally — a Code package synthesizes its NodeType + Source Code and compiles live; a Content package imports its folder. The registry ships the capability, never data instances — and no GitHub credential lives on the consumer at all. Full reference: Plugin Registry.

Dynamic node types — a module that compiles itself

A NodeType node whose Source/*.cs defines its content type + layout areas is a dynamic node type: the mesh compiles it with Roslyn on install and serves instances immediately. Proven examples shipped this way: Publish/Slide, the whole Store (including Store/Plugin itself), and the education types Edu/Lesson, Edu/Module, Edu/Exercise, Edu/Quiz. Each is gated in the plugins repo's own CI (Plugin Catalog CICompile every NodeType via scripts/compile-check.py, then Compile + render node repos which imports each repo into a real portal image and runs its in-node tests) — that compile is never covered by this repository's dotnet build or test suite.

Rules the runtime compile enforces (each load-bearing when migrating a compiled module):

What is (and isn't) a dynamic node type

A dynamic node type is compiled content + layout areas, per node. That covers a large class — anything self-contained (Slide, the Edu course pages). It cannot, on its own, host:

Modules that need those (the exercise fork/validate control plane, AI agent execution) keep their service layer compiled while their content + self-contained types ship as node repos. Turning the service layer itself into git-delivered plugins is a separate capability (a boot-time module loader).

The migration this enables

The same primitive — a node repo in a git source, imported into a running mesh — is how the static node repos (Doc, Agent, Skill, samples) and module content move off the image and onto git delivery: their content exports to the plugins repo (GitSync), and their self-contained types become dynamic node types. Everything from git, versioned by the node, no NuGet.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.