Skip to content

Bundle index serves published modules — GitSync-native registries have no install records - #1948

Merged
rbuergi merged 1 commit into
mainfrom
fix/bundle-index-from-sidecar
Aug 20, 2026
Merged

rbuergi merged 1 commit into
mainfrom
fix/bundle-index-from-sidecar

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

The bundle index/download keyed only on the registry's install records; memex-cloud (GitSync-provisioned) has none, so its bundle feed was permanently empty — every consumer read SkipNoBundle (found live: systemorph, the first real remote consumer, authenticated fine and adopted nothing).

  • ServableEntries = install records ∪ sidecar entries whose PackagePath stamps a source (records win on collision); IsGranted gates both identically, so licensing semantics are unchanged.
  • Publish accepts ?packagePath=<source>/<plugin> (validated; traversal refused) → LandModule stamps the entry.
  • node-repo-module-pack gains registry-source (default Plugins) and POSTs it. A re-publish stamps existing entries via fresh generations.

26/26 publish tests green (5 new for the path validation).

🤖 Generated with Claude Code

…stry has no install records

The bundle index and download route keyed exclusively on the registry's
own install records. memex-cloud provisions its packages as Spaces and
never runs the catalog install, so it has NO records — the bundle feed
was permanently empty and every consumer's module reconcile read
SkipNoBundle (found live 2026-08-20, the first real remote consumer:
systemorph authenticated fine and adopted nothing).

- ServableEntries = install records UNION activation-sidecar entries
  whose PackagePath stamps a source; records win on collision. IsGranted
  gates both identically, so licensing is unchanged.
- The publish route accepts ?packagePath=<source>/<plugin> (validated:
  two segments, plugin half must match the URL, traversal refused) and
  LandModule stamps it on the entry.
- node-repo-module-pack gains registry-source (default Plugins) and
  POSTs the packagePath. Re-publishing stamps existing entries via fresh
  generations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi enabled auto-merge August 20, 2026 20:54
Copilot AI lite review requested due to automatic review settings August 20, 2026 20:54

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

This pull request enables GitSync-native registries without install records to serve published module bundles.

Changes:

  • Validates and stamps package source paths during publication.
  • Combines installed packages with sidecar entries for bundle serving.
  • Propagates registry source metadata through the module-pack workflow.
  • Adds package-path validation tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Summary Final review comments
test/MeshWeaver.PluginCatalog.Test/ModulePublishTest.cs Tests package-path validation and stamping. No final comments.
src/MeshWeaver.PluginCatalog/ModulePublish.cs Validates and carries package source metadata. Critical (1 vote): Publisher-controlled sources are not bound to authorized credentials or configuration. Moderate (3 votes): Case-only package ID mismatches can prevent granted bundles from being served.
memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs Serves sidecar-published modules with grant checks. Nit (4 votes): Add endpoint coverage for sidecar-only entries and record-collision behavior.
.github/workflows/node-repo-module-pack.yml Sends registry source metadata during publishing. No final comments.
Suppressed comments (3)

.github/workflows/node-repo-module-pack.yml:198

  • These query values are interpolated without URI encoding. The packer explicitly permits SemVer build metadata such as 1.2.3+build, and a valid operator-supplied source can contain reserved query characters; +, &, #, or ? will be decoded as a different value or terminate the query, causing publishes to be refused or stamped incorrectly. Encode both values before constructing the URL.
            -X POST "$REGISTRY/api/plugins/bundles/$PACKAGE?version=$VERSION&packagePath=$REGISTRY_SOURCE/$PACKAGE" \

memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs:399

  • For install-record entries, InstalledPackages sets PackageId to PackagingManifest.IdPrefix + m.Id, but this sidecar-only branch uses the short plugin name. BuildResult uses PackageId for both the generated nuspec and Content-Disposition, so a published SocialMedia bundle is emitted as SocialMedia instead of the canonical MeshWeaver.Plugin.SocialMedia package. Keep the same package-id projection for both sources.
                            return new BundleEntry(
                                segments[1], e.Version!, segments[1],

memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs:394

  • The advertised “records win on collision” rule does not hold because deduplication keys the installed records by PluginId, while the sidecar branch compares the bare package ID (segments[1]). BundleEntry.PackageId is the shared raw package-ID field on both entries, so an installed SocialMedia record is not recognized and a second sidecar entry is appended, producing duplicate index items. Key recorded by PackageId before applying the grant filter.
                    var recorded = records.Select(r => r.PluginId)
                        .ToHashSet(StringComparer.OrdinalIgnoreCase);
                    var published = activation.Entries
                        .Where(e => e.Enabled
                            && !string.IsNullOrWhiteSpace(e.Version)
                            && e.PackagePath?.Split('/') is { Length: 2 } segments
                            && !recorded.Contains(segments[1]))

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +390 to +394
var published = activation.Entries
.Where(e => e.Enabled
&& !string.IsNullOrWhiteSpace(e.Version)
&& e.PackagePath?.Split('/') is { Length: 2 } segments
&& !recorded.Contains(segments[1]))
|| segments.Any(seg => string.IsNullOrWhiteSpace(seg)
|| seg is "." or ".."
|| seg.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|| !string.Equals(segments[1], plugin, StringComparison.OrdinalIgnoreCase))
Comment on lines +96 to +100
if (!string.IsNullOrWhiteSpace(packagePath))
{
var segments = packagePath.Split('/');
if (segments.Length != 2
|| segments.Any(seg => string.IsNullOrWhiteSpace(seg)
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   10 files     10 suites   4m 59s ⏱️
2 194 tests 2 193 ✅ 1 💤 0 ❌
2 649 runs  2 648 ✅ 1 💤 0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

633 tests   530 ✅  8m 51s ⏱️
 10 suites  103 💤
 10 files      0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 687 tests   1 685 ✅  5m 57s ⏱️
   11 suites      2 💤
   11 files        0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 563 tests   1 560 ✅  8m 50s ⏱️
   10 suites      3 💤
   10 files        0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

3 234 tests   3 042 ✅  7m 22s ⏱️
   10 suites    192 💤
   10 files        0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 585 tests   1 581 ✅  9m 0s ⏱️
   11 suites      4 💤
   11 files        0 ❌

Results for commit aebb0ac.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

    62 files      62 suites   45m 1s ⏱️
10 896 tests 10 591 ✅ 305 💤 0 ❌
11 351 runs  11 046 ✅ 305 💤 0 ❌

Results for commit aebb0ac.

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