Skip to content
cccskills
FreePublished on GitHub

plugins

Wire a MeshWeaver deployment to the plugin catalog — as a CONSUMER (installs plugins from a registry over HTTP, no git credential) or as the REGISTRY (holds the git credential, serves private plugin repos at /api/plugins). Use when a deployment's Plugin Catalog says "not configured", when adding a new plugin source repo, when issuing an install its registry token, when a plugin won't appear or won't install, or when auditing who can read the catalog. Covers the credential-encapsulation model, the Helm keys (and the trap that un-templated keys are silently dropped), token issuance, and the anonymous-registry exposure that a missing token list causes.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12 stars(GitHub 12 + cccskills 0)
Works withKubernetes

Run score Not tested

See how to install

Files included (1)

  • SKILL.md8.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

/plugins — the plugin catalog: registry and consumers

Plugins are folders of mesh nodes in private git repos. The point of the design is that only one install holds a git credential.

Systemorph/MeshWeaver.Plugins ─┐
                               ├─→ REGISTRY (public inst.) ── GET /api/plugins ──→ CONSUMER
Systemorph/education ──────────┘   holds the ONE credential   POST /api/plugins/files   no git access

npm/NuGet-style: the registry has source access, clients just speak HTTP. Model: PluginRegistry.md · Plugins.md.

Which am I configuring?

RegistryConsumer
Holds git credential✅❌ never
Helm valuespluginCatalog.sourcespluginCatalog.registryUrl (or .registries)
SecretPluginCatalog__RegistryTokens (the list it accepts)PluginCatalog__RegistryToken (the one it sends)
Todaythe public instance (the plugin registry instance)every other deployment

A registry is also a consumer of itself — the plugin registry instance sets both.

Consumer wiring

Zero-touch (preferred for new installs) — the install registers ITSELF on first boot with an admin-minted mwr_ registration key (Instance grants ▸ Registration keys on the registry; one key serves a whole scaffold, revocable), receives its mwi_ instance key and stores it (Admin/PluginRegistryCredential/…, master-key-encrypted). No token copying:

pluginCatalog:
  registryUrl: "https://registry.example.com"
  instanceId: "<this install's logical App ID>"   # REQUIRED with a bootstrap key; never derived
config:
  memex_portal:
    # Systemorph-operated deployments track their plugin repos continuously: a fresh install
    # record is seeded opted-in, so a plugin repo's green build lands with nobody clicking Update.
    # Install-time SEED only — flipping it later changes nothing for already-installed packages.
    PluginCatalog__AutoUpdateByDefault: "true"
secrets:
  memex_portal:
    PluginCatalog__BootstrapKey: "<an mwr_ registration key>"

Manual (unchanged, and always wins when both are set):

secrets:
  memex_portal:
    PluginCatalog__RegistryToken: "<the mwi_ key this install was issued in Settings ▸ Instances>"

Several registries instead of one:

pluginCatalog:
  registries:
    - {name: Plugins,   url: "https://registry.example.com"}
    - {name: Education, url: "https://<other-registry>"}

Registry wiring

pluginCatalog:
  sources:
    - {name: Plugins,   repoPath: "https://github.com/Systemorph/MeshWeaver.Plugins", ref: main}
    - {name: Education, repoPath: "https://github.com/Systemorph/education",          ref: main}
  # Sources every NEW instance registration is granted automatically (seeded into the instance's
  # Admin/_PluginGrant node — admins can still revoke per instance). Platform repo only; never
  # list private/paid sources here. Empty = registering grants nothing (the strict default).
  defaultGrants: ["Plugins/*"]
secrets:
  memex_portal:
    PluginCatalog__RegistryTokens: ["<token-for-install-a>", "<token-for-install-b>"]

format defaults to node-repo — a folder appears once it carries a <Folder>/index.json Space root with a PluginManifest. package-json is the alternative layout. Sources merge in configured order; on an id collision the first wins; a failing repo contributes nothing (logged) rather than breaking the catalog.

🚨 A registry with no tokens serves everyone

PluginCatalog:RegistryTokens empty ⇒ the registry answers anonymously. That is the local-dev / e2e stub mode. On a production registry it means the full catalog and every package's file content is readable by anyone who knows the URL.

Audit it — this is a one-line check, run it:

curl -sS https://<registry-host>/api/plugins -o /dev/null -w "%{http_code}\n"   # want 401
curl -sS -X POST https://<registry-host>/api/plugins/files \
  -H 'Content-Type: application/json' -d '{"id":"<some-id>"}' -o /dev/null -w "%{http_code}\n"  # want 401

200 unauthenticated = every private plugin repo behind that registry is public to anyone with the URL. Issue tokens and set RegistryTokens.

As of 2026-08-06 the plugin registry instance's /api/plugins returned 200 with 28 packages to an unauthenticated caller, including paid course content from Systemorph/education. Fixing it means issuing each install a token, setting RegistryTokens on the registry, and rolling — in that order, or consumers lose their catalog in the gap.

Issue a token:

openssl rand -base64 32

Store it in Key Vault (<env>-PluginCatalog-RegistryToken) and surface it through the SecretProviderClass — never in a values file.

Installing

Platform admin → Settings ▸ Administration ▸ Plugin Catalog → Install. It is an admin tab, not a browsable Space (a Space partition would correctly deny read to everyone else — that was the "Access denied on 'Plugins'" bug). The consumer pulls the package files over HTTP and imports them:

  • Content package → imports its folder into the target partition.
  • Code package → synthesizes its NodeType from the manifest, imports Source/*.cs as Code children, and requests a release so the mesh compiles the type live. No app rebuild, no NuGet.

Re-installing is an upsert; installing one module never disturbs another in a shared partition.

Troubleshooting

"Plugin catalog not configured" → EffectiveRegistries is empty: no registryUrl and no registries entry with a URL reached the pod. Check what actually landed, not what the values file says — first on the record: Deployments/<name> on the control instance carries the whole env precedence stack (inlineEnv, extraPortalConfig, the key-vault classes), and an Audit Hosting/InstanceAction compares it with the cluster. The direct read is break-glass (OperatingFromThePortal.md):

kubectl -n <env> get deploy memex-portal-deployment \
  -o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' | grep -i plugin
kubectl -n <env> get configmap memex-portal-config -o jsonpath='{.data}' | tr ',' '\n' | grep -i plugin

The #1 cause: the chart only emits keys it templates. Before pluginCatalog existed in values.yaml, every plugin key except AutoUpdateByDefault was silently dropped — which is why the plugin registry instance's registry config was hand-applied as raw Deployment env vars that no redeploy reproduces. If a key isn't in deploy/helm/templates/memex-portal/config.yaml, it does not reach the pod. Verify by rendering:

helm template t deploy/helm -f <values>.yaml | grep PluginCatalog

401 from the registry → the consumer's token isn't in the registry's RegistryTokens. Both sides validate through PluginRegistryTokens, one shared contract, so a mismatch is a real value mismatch, not a format one.

A folder in the repo doesn't appear in the catalog → it has no <Folder>/index.json Space root with a PluginManifest, or its source's format is wrong.

Package installs but the type doesn't work → it's a Code package; the compile happens live. Check the NodeType's compile status before suspecting the install.

Related

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

async

Free

Forwarding AccessContext through async/reactive boundaries, and the no-async rule on the actor-model mesh. Use when writing or reviewing any hub-reachable / Blazor-view / agent-round / IIoPool code that creates, reads, or updates a mesh node, OR when a write "silently does nothing" / "Access denied" appears after a .Subscribe / IIoPool / Observable.Create hop. The async/await/ToTask/.Wait/.Result/.GetAwaiter/FirstAsync family and lost AccessContext are the two ways async boundaries cause wedges and storms. Grounded in AsynchronousCalls.md, SyncedMeshNodeQueries.md, AccessContextPropagation.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

ci

Free

What CI proves, what it cannot prove, and how to write a gate that cannot pass on no evidence. Use when authoring or changing a GitHub Actions workflow, adding a required check, wiring a satellite repo CI, or reasoning about whether a green run actually covers your change. Covers the absolute no-skip-trapdoor rule (a gate must never carry continue-on-error on its input step nor an if that asks whether a secret is set — GitHub paints a skipped job the same colour as a passed one), the reusable workflow_call jobs satellite repos must call instead of hand-rolling, why runs on main are deliberately never cancelled, and the large body of in-mesh C# that no dotnet build or test ever type-checks.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

debug

Free

Debug a deadlock / hang / timeout in the mesh — a test that times out, a hub handler whose response never arrives, a GetRemoteStream / GetMeshNodeStream / GetQuery that never emits, an agent round or per-instance-hub activation that parks forever. Use whenever something times out and you need to find WHERE the message flow breaks down. The timeout is the symptom, never the bug — raising it just hangs longer. Grounded in DebuggingMessageFlow.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

Remove / reset a user (and their partition) on a running MeshWeaver portal (AKS). Use when you need to kick out a user — e.g. a throwaway test account — so they re-onboard from scratch. The normal delete API REFUSES a user's home ("user partition root … cannot be deleted"), so deletion is a Postgres-schema drop, not an MCP call. Covers where a user's data actually lives (per-user PG schemas named by BOTH id AND email, e.g. `rbuergi` + `rbuergi@systemorph.com`), how to reach PG (the connection string is inline in the portal's `ConnectionStrings__memex` env — NOT the `POSTGRES_PASSWORD` secret), the substring-match footgun (`buergi` also matches admin `rbuergi`), the in-memory resurrection gotcha (a recycle re-saves the node — a portal restart is what actually clears it), and the half-onboarded case where the user has NO schema at all (in-memory only → restart is the whole fix).

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

deploy

Free

Deploy MeshWeaver — the two routes (shared AKS cluster, and Azure Container Apps via the Aspire test/prod modes) and the traps that make a deploy look done when it is not. Use when rolling a code change onto a portal, standing up or patching an environment, or verifying that what is running is what you built. FIRST RULE (maintainer, 2026-09-08): on AKS an operation is a Hosting/InstanceAction on the control instance (Roll, Restart, Suspend, Audit, Reconcile, …) executed by the in-cluster operator — az aks command invoke / kubectl are break-glass, never the procedure. Also covers the bootstrap build-image / set-image / restart sequence the operator runs for you, why an env deploy.sh is first-time setup and not a code-update path, why the database migration is a run-once Job that a helm upgrade runs — never a Deployment to roll — and the DbVersionGate that makes a portal refuse to serve ahead of its schema.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

gui

Free

Build or review ANY MeshWeaver UI — layout areas, editable views, forms, tables, content editing, side-panel composers — by wiring the framework's EXISTING data-binding pieces, NEVER hand-rolling. The one rule is ALWAYS DATA-BIND, especially editable views: the backend layout area declares WHAT to render (a UiControl tree + node PATHS), and every value read / write-back happens on the GUI side through the per-node IMeshNodeStreamCache. Hand-woven binding — replicating a node into a /data/{id} copy + a save subscription, emitting HTML strings, hand-built selects/checkboxes, .Take(1) on a live stream, async/await/QueryAsync in a layout area — is the bug class behind "awaiting first data" / "renders empty" / a stuck editable view. Use when writing or reviewing a layout area, control, editor, form, table, or any data-bound view. Grounded in src/MeshWeaver.Layout/DataBinding/, Doc/GUI/DataBinding.md + Editor.md, the Edit extension (EditorExtensions.cs), and the node-bound editor controls.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 29, 2026

See all skills by Systemorph

Report a problem with this skill