* fix(plugins): resolve public artifacts from installed plugin roots
Externalized official plugins ship gateway-auth/doctor/secret/message-tool
public artifacts in their installed npm package, but the public-surface
loader only searched bundled locations. On packaged installs this emptied
the gateway auth bypass set for /api/channels/mattermost/command, so every
Mattermost slash callback was rejected 401 before the plugin ran.
Fall back to the plugin's installed package root (install-record index)
when bundled resolution misses; bundled/source locations keep precedence.
Fixes#98740
* fix(gateway): resolve channel auth-bypass artifacts via activation-gated facade
Route gateway-auth bypass artifact loading through the activation-gated
facade seam so externalized (installed) channel plugins keep their
unauthenticated callback paths, while denied/disabled plugins contribute
nothing and never execute artifact code. Reject hardlinked artifacts for
installed plugin roots in the facade loader per hardlink-policy.
Replaces the earlier install-record fallback in the shared bundled
artifact loader, which broadened every artifact surface past its trust
boundary (see PR review); other externalized-plugin surfaces are #98842.
Fixes#98740
* fix(gateway): load facade activation runtime via async lazy import
The sync createRequire/jiti candidate loader for
facade-activation-check.runtime.js only resolves from built dist; under
vitest source runs it cannot follow .js->.ts specifiers or workspace
package subpaths, so the gateway auth-bypass path threw 'Unable to load
facade activation check runtime' (CI shard failure). Add an async
activated-load API backed by dynamic import (vitest- and dist-native),
warm the shared runtime memo, and make the bypass consumer async - its
single caller already awaits behind the config-snapshot promise cache.
* revert(plugins): drop install-record fallback from public-surface loader
Superseded by the activation-gated facade seam in gateway-auth-bypass;
review found the loader-level fallback broadened every artifact surface
past its trust boundary (see PR #98819 discussion).
* fix(plugin-sdk): drop unused oxlint disable on async facade loader
* test(plugin-sdk): cover facade hardlink policy for installed vs core roots
* test(plugin-sdk): cover facade hardlink policy for installed vs core roots
---------
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
Reuses the current plugin metadata snapshot in facade activation checks when the resolved boundary config matches, avoiding repeated manifest registry loads on the facade path.
Falls back to manifest registry loading when the current snapshot is missing or belongs to a different config/environment. Adds regression coverage for snapshot mismatch, snapshot reuse, and Windows path normalization.
Co-authored-by: 郑苏波 (Super Zheng) <superzheng@tencent.com>
resolveTrackedFacadePluginId triggers config loading (plugin auto-enable,
channel discovery) which can re-enter loadBundledPluginPublicSurfaceModuleSync
for the same module. Because the sentinel was still empty at that point,
re-entrant callers saw undefined exports (e.g. shouldNormalizeGoogleProviderConfig).
Move Object.assign(sentinel, loaded) before the plugin ID resolution so any
re-entrant lookup through the cached sentinel finds the real exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Place a sentinel object in the loadedFacadeModules cache before the Jiti
sync load begins. Re-entrant calls (caused by circular facade references
from constant exports evaluated at module-evaluation time) now receive the
sentinel instead of recursing infinitely. Once the real module finishes
loading, Object.assign() back-fills the sentinel so any references
captured during the circular load phase see the final exports.
The Jiti load is wrapped in try/catch: on failure the sentinel is removed
from the cache so that subsequent retry attempts re-execute the load
instead of silently returning an empty object. The function returns the
sentinel (not the raw loaded module) to guarantee a single object identity
for all callers, including those that captured a reference during the
circular load phase.
Also tightens the generic constraint from <T> to <T extends object> so
Object.assign() is type-safe, and propagates the constraint to the
test-utils callers in bundled-plugin-public-surface.ts.
Fixes#57394