Skip to content

The registry SERVES sealed publications — GET /api/plugins/bundles/prebuilt/<identity>/<source> (#2483 part 1) - #2487

Merged
rbuergi merged 2 commits into
mainfrom
feat/2483-build-principal
Aug 27, 2026
Merged

rbuergi merged 2 commits into
mainfrom
feat/2483-build-principal

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2478 (base branch = its branch; retarget to main once it lands). Closes the Azure half of #2483.

What

GET /api/plugins/bundles/prebuilt/{identity}/{source} → the sealed publication's bundle names.
GET /api/plugins/bundles/prebuilt/{identity}/{source}/{bundle} → the bundle.

Behind the same authenticator and grant model as every other bundle route: the caller needs a whole-source grant (<source>/*) — a PluginGrantEntry on a mesh node, auditable and revocable — and the decision lands on the entitlement ledger like every other bundle decision.

Why

The only reader of prebuilt-bundles/<identity>/<source>/ was an Azure OIDC identity whose federated credentials live in Entra, none for pull_request — so a gate cannot fetch on the one event it exists for (AADSTS700213, measured this morning on SocialMedia#84 / Reinsurance#100). The portal already mounts the share; serving it moves the rule into the mesh.

Serves only what is sealed

PublishedBundleCatalogue.SealedBundlesOf — the boot seeder's own torn-publication rule, now exposed. Unsealed directory → 404. File on disk but not in the seal → 404. Segment that is not a bare name → 400 before any disk read.

Verified

PrebuiltPublicationTest pins all of it: sealed set to a whole-source grant, 403 without it, 404 on a torn directory, 404 on an unsealed file, path walk refused. The 25 existing bundle-endpoint tests are unchanged and green (MapPrebuilt sits inside the shared 401 filter).

Next

#2481 rewires upstream-seed from az storage to this route with an instance key — Azure leaves the gate entirely. Part 2 of #2483 (the mesh verifies GitHub's OIDC token directly) then removes the key too.

🤖 Generated with Claude Code

rbuergi and others added 2 commits August 27, 2026 10:21
…ebuilt/<identity>/<source>

Step 2 of the plugin build contract: a downstream repo INSTALLS its upstream from
the upstream's sealed publication. Until now the only reader of
prebuilt-bundles/<identity>/<source>/ was an Azure OIDC identity whose federated
credentials live in the Entra tenant — none for `pull_request`, so a gate could
not fetch on the one event it exists for (AADSTS700213, measured today).

The portal already mounts the share (PreWarm:PrebuiltBundleRoot). Serving it here
puts the read behind the SAME authenticator and grant model as every other bundle
route: who may fetch which source is a whole-source PluginGrantEntry
(`<source>/*`) on a mesh node — auditable, listable, revocable — never a rule in
a cloud tenant nobody can query. The decision is recorded on the entitlement
ledger like every other bundle decision.

Serves ONLY what the `_complete` seal lists (PublishedBundleCatalogue.SealedBundlesOf
— the boot seeder's own rule, now exposed): an unsealed directory is 404, a file
present on disk but not listed is 404, a segment that is not a bare name is 400
before any disk read. A consumer can never be handed a set the portal itself
would decline.

Pinned by PrebuiltPublicationTest: the sealed set to a whole-source grant; 403
without it; 404 on a torn directory; 404 on an unsealed file; path walk refused.

This is #2483 part 1. Part 2 (the mesh verifies GitHub's OIDC token as a build
principal) removes the instance key too; part 1 already removes Azure from the
gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 001 tests   997 ✅  17m 8s ⏱️
    9 suites    4 💤
    9 files      0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

4 483 tests   4 377 ✅  10m 11s ⏱️
    9 suites    106 💤
    9 files        0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 483 tests   1 483 ✅  6m 19s ⏱️
    8 suites      0 💤
    8 files        0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

962 tests   962 ✅  6m 33s ⏱️
  8 suites    0 💤
  8 files      0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

   10 files     10 suites   8m 37s ⏱️
2 399 tests 2 397 ✅ 2 💤 0 ❌
2 400 runs  2 398 ✅ 2 💤 0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

    8 files      8 suites   7m 39s ⏱️
1 093 tests   901 ✅ 192 💤 0 ❌
1 686 runs  1 494 ✅ 192 💤 0 ❌

Results for commit 1856a30.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

    52 files      52 suites   56m 28s ⏱️
11 419 tests 11 115 ✅ 304 💤 0 ❌
12 015 runs  11 711 ✅ 304 💤 0 ❌

Results for commit 1856a30.

@rbuergi
rbuergi changed the base branch from fix/install-seed-cap-outruns-its-fallback to main August 27, 2026 08:45
@rbuergi
rbuergi marked this pull request as ready for review August 27, 2026 08:45
Copilot AI lite review requested due to automatic review settings August 27, 2026 08:45
@rbuergi
rbuergi merged commit ef1d9da into main Aug 27, 2026
23 checks passed

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

Adds registry endpoints to serve sealed prebuilt plugin publications from the portal (list bundles + download a bundle) under the existing instance-key authentication and whole-source grant model, so downstream gates can fetch publications without Azure Files credentials.

Changes:

  • Adds GET /api/plugins/bundles/prebuilt/{identity}/{source} and .../{bundle} endpoints and wires them into MapPluginBundles.
  • Exposes PublishedBundleCatalogue.PublishedRootConfigKey and SealedBundlesOf(...) as the shared “only serve what is sealed” rule.
  • Introduces PrebuiltPublicationTest to pin authorization + sealed/torn behavior.

Reviewed changes

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

File Description
test/Memex.Portal.Shared.Test/PrebuiltPublicationTest.cs Adds an integration test covering sealed-set listing/download, grant enforcement, and torn/unsealed refusal.
src/MeshWeaver.PluginCatalog/PublishedBundleCatalogue.cs Adds a config-key alias and exposes SealedBundlesOf(...) to reuse the boot seeder’s “sealed publication” rule.
memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs Adds and maps the new /prebuilt/... endpoints enforcing whole-source grants and seal-based serving.
Suppressed comments (1)

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

  • IsBareName currently only rejects '/', '\', ':' and '.'/'..'. That still allows segments containing percent-escapes (e.g. ..%2Fplugins) and other invalid filename characters, which can weaken the "refuse before any disk read" guarantee and can cause unexpected path/IO exceptions. Consider rejecting % and any Path.GetInvalidFileNameChars().
    private static bool IsBareName(string segment) =>
        segment.Length > 0
        && segment.IndexOfAny(['/', '\\', ':']) < 0
        && segment != "." && segment != "..";

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

Comment on lines +167 to +171
/// <summary>
/// The bundle names one source's SEALED publication lists, or null when the publication is
/// torn (no sentinel, or a listed bundle absent). This is THE rule for "what may be served
/// from this directory" — the registry's prebuilt route reads through it so a consumer can
/// never be handed a torn set the boot seeder itself would refuse.
Comment on lines +210 to +217
if (PrebuiltDecision(http, identity, source, caller) is { } refused)
return refused;
var directory = PrebuiltDirectory(http, identity, source);
var sealed_ = directory is null ? null : PublishedBundleCatalogue.SealedBundlesOf(directory, Log(http));
if (sealed_ is null || !sealed_.Contains(bundle, StringComparer.OrdinalIgnoreCase))
return NoSuchBundle();
var path = Path.Combine(directory!, bundle);
return Results.File(path, "application/zip", fileDownloadName: bundle);
Comment on lines +88 to +90
// and a path segment that is not a bare name is refused before any disk read
var walk = await Get(app, $"/api/plugins/bundles/prebuilt/{Identity}/..%2F{Source}", grantedKey);
Assert.NotEqual(HttpStatusCode.OK, walk.StatusCode);
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