Repository navigation
Installed plugins stayed invisible on any instance set up before #902 - #1074
Merged
Merged
Conversation
A portal provisioned before the declared-access step came up with its whole
plugin baseline installed and unreadable: no spaces, no Store, and a Blazor
ErrorBoundary storm behind `[SYNC_STREAM] OnError … Owner=Store`. Found on atioz,
whose database held every package's content (store 237 nodes, edu 37, essentials
35, skill 34, agent 16, …) while 8 partitions carried 136 Public/Anonymous deny
assignments. memex and systemorph, installed after the change, were correct.
Two independent causes, both only reachable on a pre-existing instance:
1. The legacy gate could never heal. Those partitions carry
`{redirectOnDenied}` with no `publicRead` plus per-child Public/Anonymous
Viewer denies — the SCOPED shape applied with an empty declaration, which
current code cannot produce. EnsureDeclaredAccess is create-only, so the boot
repair pass read the policy, skipped it, and re-skipped it every boot since.
The heal cannot key on the policy alone: PublicRead is a plain bool, so
"absent" and "deliberately false" are indistinguishable after deserialization,
and a package is entitled to ship a gated policy
(PackageShippedPolicy_Survives_CreateOnly pins exactly that). It keys on the
PAIRING instead — a withholding policy AND the denies only the old installer
ever wrote. No catalog package ships a _Policy, and a shipped gated policy has
no denies beside it, so it stays untouched. Denies are swept BEFORE the policy
is written: the policy is the "already correct" marker, so writing it first
would strand a half-swept partition permanently.
2. Store was required by everything and selected by nothing. The boot pass
selected the pre-installed baseline and then only ORDERED it, which assumes
every requirement is itself selected. All 8 baseline packages declare
`requires: ["Store@^1.0.0"]` while Store is `preInstalled: false` — so there
was no install record, therefore no declared-access pass, therefore a
partition only system-security could read, therefore no catalog page left to
install it from by hand. DependencyClosure now expands the selection over the
full catalog before the sort, tolerantly: an unresolvable requirement is named
and stepped over, and a cycle terminates.
Also: AiSourcesInstallHook queried `nodeType:User` with no namespace filter,
which matches the self-typed User NodeType declaration node — so every install
tried to write AI settings into a partition named "User" and logged
`42P01: relation "user.mesh_nodes" does not exist` once per package. Caught, so
only noise, but it is how a stray `vuser` schema appeared.
Note a global admin cannot repair this by hand — that is by design, not a gap:
platform admin is not a data superuser, so the boot repair pass is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaZscDr1r9Rtte49qn2kgk
…l-and-store-dependency
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a production-facing regression where plugins installed on instances provisioned before the declared-access model could remain installed-but-invisible, and ensures unattended default installs include transitive dependencies (notably Store) so required infrastructure is actually installed and becomes readable.
Changes:
- Repairs legacy “scoped gate applied with empty declaration” by retiring legacy Public/Anonymous deny assignments and healing the partition policy to
PublicRead=truewhen the legacy fingerprint is present. - Expands unattended default install selection by closing over transitive
requiresdependencies before dependency ordering (tolerant of missing deps and cycles). - Avoids treating the
UserNodeType declaration as an actual user during AI source install hooks.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.PluginCatalog.Test/PluginDependencyOrderTest.cs | Adds tests validating dependency-closure behavior (pulls required deps, tolerates missing deps, terminates cycles). |
| test/MeshWeaver.PluginCatalog.Test/DeclaredAccessInstallTest.cs | Adds regression tests proving the legacy gate is actually unreadable pre-heal, and that shipped gated policies remain untouched. |
| src/MeshWeaver.PluginCatalog/PackageInstaller.cs | Implements legacy-gate fingerprint detection + deny retirement + policy healing for fully-public packages. |
| src/MeshWeaver.PluginCatalog/InstanceAutoRegistrationService.cs | Changes unattended boot selection to compute a full catalog, select baseline/pattern matches, then close over dependencies before ordering. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-10-your-plugins-are-visible-again.md | Adds a “What’s New” entry describing the user-visible fix and its automatic repair behavior. |
| src/MeshWeaver.AI/AiSourcesInstallHook.cs | Filters out the self-typed User NodeType declaration node from “users” queries to prevent bogus per-user writes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+431
to
+435
| /// <summary> | ||
| /// Whether an existing policy node already expresses the fully-public declaration. A node whose | ||
| /// content will not deserialize is treated as NOT declaring it — the heal then rewrites it into | ||
| /// the known-good shape, which is the safe direction for an unreadable policy. | ||
| /// </summary> |
Comment on lines
663
to
+670
| if (wanted.Count > 0 | ||
| && !deduped.Any(c => wanted.Any(w => w.Matches(c.Package.Source ?? "", c.Package.Id)))) | ||
| && !selected.Any(c => wanted.Any(w => w.Matches(c.Package.Source ?? "", c.Package.Id)))) | ||
| logger.LogWarning( | ||
| "The default install matched no packages for the operator's patterns " | ||
| + "(wanted [{Wanted}]; {Baseline} pre-installed package(s) were still " | ||
| + "selected). If the registry predates source-stamped catalog entries, a " | ||
| + "Source/* pattern cannot match — it fails closed rather than guessing.", | ||
| string.Join(", ", wanted), deduped.Count); | ||
| string.Join(", ", wanted), selected.Count); |
…l-and-store-dependency
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
The dependency closure added in this branch expands the boot selection over the FULL catalog — which would let a free package pull in a PAID one just by declaring it. That defeats the reason selection is source-scoped in the first place: an instance is routinely granted commercial content (course catalogues, customer modules) that it may buy and must never receive automatically. The closure now refuses a commercial requirement — positive price (purchasable) and negative (coupon-only) alike — and names it at Warning instead of acquiring it. The boot has no authorizing principal anyway, so PackageEntitlement.Authorize would refuse the install; pulling it in would buy nothing but a failed install on every boot. Found while scoping atioz and systemorph to Plugins-only: the registry carries Education and Reinsurance sources beside Plugins, so the edge is reachable in production, not hypothetical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EaZscDr1r9Rtte49qn2kgk
…l-and-store-dependency
…l-and-store-dependency
…-dependency' into fix/legacy-policy-heal-and-store-dependency
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The symptom
A portal provisioned before the declared-access step comes up with its whole plugin baseline installed and unreadable — no spaces, no Store, and a Blazor
ErrorBoundarystorm behind[SYNC_STREAM] OnError … Owner=Store/ "Error in Mesh|Node|AI|GitHub menu stream".Found live on client-i. Nothing had failed to download — the database held every package's content (
store237 nodes,edu37,essentials35,skill34,agent16,datamodelling15,publish13,collaboration9,feedback3) while 8 partitions carried 136 Public/Anonymous deny assignments.memexandsystemorph, installed after the change, were correct and showed all 15Agent/*nodes.Two independent causes
1. The legacy gate could never heal. Pre-#902 partitions carry
{redirectOnDenied: "X/Subscribe"}with nopublicRead, plus per-child Public/Anonymous Viewer denies — the SCOPED shape (EnsureScopedPublicRead) applied with an empty declaration, which current code cannot produce (declared.Count > 0guards it).EnsureDeclaredAccessis create-only, soInstalledPackageRepairServiceread the policy, skipped it, and re-skipped it on every boot since.The heal cannot key on the policy alone:
PartitionAccessPolicy.PublicReadis a plainbool, so "absent" and "deliberately false" are indistinguishable after deserialization — and a package is entitled to ship a gated policy, whichPackageShippedPolicy_Survives_CreateOnlypins. It keys on the pairing instead: a withholding policy AND the denies only the old installer ever wrote. Verified across all 21 catalog packages that none ships a_Policy/_Accessfile, and a shipped gated policy has no denies beside it — so it stays untouched.Denies are swept before the policy is written: the policy is the "already correct" marker, so writing it first would strand a half-swept partition permanently.
2. Store was required by everything and selected by nothing. The boot pass selected the pre-installed baseline and then only ordered it, which assumes every requirement is itself selected. All 8 baseline packages declare
requires: ["Store@^1.0.0"]while Store ispreInstalled: false— so no install record, therefore no declared-access pass, therefore a partition onlysystem-securitycould read, therefore no catalog page left to install it from by hand.DependencyClosurenow expands the selection over the full catalog before the sort, tolerantly: an unresolvable requirement is named and stepped over, and a cycle terminates.Also
AiSourcesInstallHook.Users()queriednodeType:Userwith no namespace filter, which matches the self-typedUserNodeType declaration node → every install tried to write AI settings into a partition namedUser→42P01: relation "user.mesh_nodes" does not exist, once per package. Caught, so only noise — but it is how a strayvuserschema appeared.Why this had to be a code fix
A global admin cannot repair the affected instances by hand:
patch @Agent/_Policy→Access denied: user 'rbuergi' lacks Update permission. Platform admin is not a data superuser and there is no break-glass surface — by design. The boot repair pass is the repair channel, so it had to learn to do it.Verification
MeshWeaver.PluginCatalog.Test— 214/214 green, includingPackageShippedPolicy_Survives_CreateOnly(the contract this deliberately does not break).LegacyScopedGate_OnAFullyPublicPackage_IsHealedrewinds a real install to the legacy shape and asserts the content is genuinely unreadable before healing, so it cannot pass vacuously;ShippedGatedPolicy_SurvivesTheRepairPassholds the create-only contract across the repair pass; three closure tests cover the pull-in, an unresolvable requirement, and a cycle.-c Release -warnaserrorclean onMeshWeaver.PluginCatalog,MeshWeaver.AI,MeshWeaver.PluginCatalog.TestandMeshWeaver.Hosting.Monolith.Test, re-verified after merging current main.🤖 Generated with Claude Code
https://claude.ai/code/session_01EaZscDr1r9Rtte49qn2kgk