@aglyn/tenant-data-admin 1.0.0-beta.213 → 1.0.0-beta.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/tenant-data-admin",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.214",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-util-fbserver": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-first-touch": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.214",
|
|
29
|
+
"@aglyn/shared-util-email": "1.0.0-beta.214",
|
|
30
|
+
"@aglyn/shared-util-fbserver": "1.0.0-beta.214",
|
|
31
|
+
"@aglyn/shared-util-first-touch": "1.0.0-beta.214",
|
|
32
|
+
"@aglyn/shared-util-http": "1.0.0-beta.214",
|
|
33
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.214",
|
|
34
34
|
"@msgpack/msgpack": "^3.1.3",
|
|
35
35
|
"@swc/helpers": "0.5.23",
|
|
36
36
|
"sharp": "^0.35.3",
|
|
@@ -59,6 +59,7 @@ export declare function resolveMarketplacePluginVersion(listingId: string, versi
|
|
|
59
59
|
trust?: string;
|
|
60
60
|
hostAbi?: number;
|
|
61
61
|
pluginId?: string;
|
|
62
|
+
identity?: string;
|
|
62
63
|
contributes?: PluginContributions;
|
|
63
64
|
} | null>;
|
|
64
65
|
export declare function getRealmPluginInstalls(options: {
|
|
@@ -137,6 +137,10 @@ import { tenantDataTag, withRenderCache } from "../render-cache.js";
|
|
|
137
137
|
// as absent, so the documented default applies rather than "nowhere".
|
|
138
138
|
const contributes = readPluginContributions((_data_manifest1 = data.manifest) == null ? void 0 : _data_manifest1.contributes);
|
|
139
139
|
const pluginId = typeof ((_data_manifest2 = data.manifest) == null ? void 0 : _data_manifest2.id) === 'string' && data.manifest.id ? String(data.manifest.id) : undefined;
|
|
140
|
+
// The namespace these bytes were published under (AGL-3390), which the
|
|
141
|
+
// loaders hold every registration to. Absent on a version published before
|
|
142
|
+
// identities existed, and such a version never server-renders.
|
|
143
|
+
const identity = typeof data.identity === 'string' && data.identity ? String(data.identity) : undefined;
|
|
140
144
|
return _extends({
|
|
141
145
|
sha256: String(data.sha256)
|
|
142
146
|
}, data.signature ? {
|
|
@@ -147,6 +151,8 @@ import { tenantDataTag, withRenderCache } from "../render-cache.js";
|
|
|
147
151
|
hostAbi
|
|
148
152
|
} : {}, pluginId ? {
|
|
149
153
|
pluginId
|
|
154
|
+
} : {}, identity ? {
|
|
155
|
+
identity
|
|
150
156
|
} : {}, contributes ? {
|
|
151
157
|
contributes
|
|
152
158
|
} : {});
|
|
@@ -240,6 +246,8 @@ async function resolveRealmPluginInstalls(options) {
|
|
|
240
246
|
hostAbi: pinned.hostAbi
|
|
241
247
|
} : {}, pinned.pluginId ? {
|
|
242
248
|
pluginId: pinned.pluginId
|
|
249
|
+
} : {}, pinned.identity ? {
|
|
250
|
+
identity: pinned.identity
|
|
243
251
|
} : {}, pinned.contributes ? {
|
|
244
252
|
contributes: pinned.contributes
|
|
245
253
|
} : {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/realm-plugins.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getPluginConfigSchema,\n isPluginRevoked,\n mergePluginConfig,\n readPluginContributions,\n resolvePluginConfig,\n type PluginContributions,\n type PluginRevocation,\n type RealmPluginInstall,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from './firebase-admin'\nimport { resolveOrgIdForHost } from './organizations'\nimport { tenantDataTag, withRenderCache } from '../render-cache'\n\n/**\n * One `pluginSettings` document, or `undefined` if it cannot be read.\n *\n * A failed read degrades to \"no answer at this level\" rather than throwing,\n * so a transient Firestore error costs a handler its overrides and not its\n * request. The caller layers what comes back, so a missing site document and\n * a missing site override are the same thing to it.\n */\nasync function readPluginSettings(\n scope: 'orgs' | 'hosts',\n scopeId: string,\n pluginId: string,\n): Promise<Record<string, unknown> | undefined> {\n try {\n return (\n await firebaseAdmin\n .app()\n .firestore()\n .collection(scope)\n .doc(scopeId)\n .collection('pluginSettings')\n .doc(pluginId)\n .get()\n ).data()\n } catch {\n return undefined\n }\n}\n\n/**\n * Server-side plugin config read (AGL-428): the stored overrides merged over\n * the plugin's declared defaults (type-coerced — the docs are\n * manager-writable). Without a registered schema the raw doc (or {}) comes\n * back, so handlers degrade to their own fallbacks.\n *\n * `hostId` resolves the SITE's answer on top of the workspace's (AGL-428,\n * AGL-1014), and a handler that has one must pass it. A per-site setting the console can\n * store and the server never reads is not a setting: the operator lowers the\n * flagship branch's booking horizon, the console shows the number they typed,\n * and `/api/bookings/slots` keeps offering the workspace's — a disagreement\n * with no error and no surface that shows both sides.\n *\n * The site read is CONDITIONAL on being handed a host, so a request with no\n * site in scope pays nothing for a document that could not apply to it. It\n * runs alongside the org read rather than after it: the two documents are\n * independent, and sequencing them would put a second serial round trip on\n * every request that resolves config.\n */\nexport async function getPluginConfig(\n orgId: string | null | undefined,\n pluginId: string,\n options: { hostId?: string | null } = {},\n): Promise<Record<string, unknown>> {\n const schema = getPluginConfigSchema(pluginId)\n const hostId = options.hostId\n if (!orgId && !hostId) return schema ? mergePluginConfig(schema, null) : {}\n const [org, host] = await Promise.all([\n orgId ? readPluginSettings('orgs', orgId, pluginId) : undefined,\n hostId ? readPluginSettings('hosts', hostId, pluginId) : undefined,\n ])\n // Coercion happens once, at the end, inside `resolvePluginConfig` — a\n // malformed site value falls back to the WORKSPACE value it was trying to\n // replace rather than skipping past it to the schema default.\n if (schema) return resolvePluginConfig(schema, { org, host })\n // No schema: there is no field list to layer by, so the site document\n // simply narrows the workspace's, key for key.\n return { ...(org ?? {}), ...(host ?? {}) }\n}\n\n/**\n * Server-side resolution of a workspace's TRUSTED-REALM plugin installs\n * (AGL-420). Install docs pin `{version, sha256}`, but the trust grant\n * (`trust: 'realm'` + the platform Ed25519 `signature`) lives on the\n * server-only version doc — staff sign AFTER review, possibly long after\n * the install — so this join is the single source the loaders consume:\n *\n * 1. Read the org's installs (and the host's, when a host is in scope).\n * 2. Join each pin with its `marketplaceListings/{id}/pluginVersions/{v}`\n * doc; only versions carrying `trust: 'realm'` survive.\n * 3. Drop revoked versions (`revocations/{listingId}` kill switch) — a\n * revocation beats a still-present trust grant.\n * 4. Drop hidden listings (AGL-948) — `resolveMarketplacePluginVersion`\n * returns null for a listing under staff takedown. `deletedAt` is not\n * a blocker; see that function for why the two differ.\n *\n * The returned sha256/signature come from the VERSION doc, not the install\n * copy, so a tampered install doc cannot smuggle different bytes past the\n * loader's content check.\n */\n\ninterface InstallPin {\n listingId: string\n version: string\n}\n\n/**\n * One version-doc read, shaped for the remote loaders' `resolveVersion`.\n *\n * Also the staff-takedown gate (AGL-948): a listing carrying `hiddenAt`\n * resolves to null, so an already-installed plugin stops loading the\n * moment staff hide it. Takedown used to only de-list — a plugin pulled\n * for abuse kept executing in every workspace that had installed it until\n * someone separately wrote a revocation. It lives HERE rather than in the\n * join because every remote path funnels through this function: the\n * console and site realm joins, and both apps' remote-server-bundle\n * loaders.\n *\n * `deletedAt` is deliberately NOT a blocker. A publisher retiring a\n * listing must not break the sites already paying for it; unpublish\n * blocks new installs (`install-plugin`) and that is all it means.\n */\nexport async function resolveMarketplacePluginVersion(\n listingId: string,\n version: string,\n): Promise<{\n sha256: string\n signature?: string\n trust?: string\n hostAbi?: number\n pluginId?: string\n contributes?: PluginContributions\n} | null> {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n // THE REVOCATION READ IS IN THIS `Promise.all`, NOT AFTER IT (2026-08-26).\n //\n // It is keyed on `listingId` alone, so it never depended on the two reads\n // above — awaiting it below them cost a SECOND sequential Firestore round\n // trip on every resolution, and `resolveRealmPluginInstalls` calls this\n // once per pinned install. That is the shape that grows with the number of\n // plugins an org has installed: `/api/orgs/realm-plugins` is deliberately\n // uncached (see the TTL note below), and at a P75 of 907ms it was already\n // the slowest org-scoped console route before anyone uploaded in volume.\n //\n // The trade is one extra read on the paths that exit early (a hidden\n // listing, a missing version doc) in exchange for halving the round trips\n // on the path that actually happens. The kill switch is UNCHANGED: the\n // check below still runs before any value is returned, and it still reads\n // live — this moves when the read is issued, never whether it is honored.\n const [listing, snapshot, revocationSnapshot] = await Promise.all([\n listingRef.get(),\n listingRef.collection('pluginVersions').doc(version).get(),\n firestore.collection('revocations').doc(listingId).get(),\n ])\n // A missing listing doc is NOT a blocker: Firestore does not cascade to\n // subcollections, so a hard-deleted listing leaves working installs\n // resolving off an orphaned version doc. Only an explicit takedown stops\n // them.\n if (listing.get('hiddenAt')) return null\n const data = snapshot.data()\n if (!data?.sha256) return null\n // THE KILL SWITCH BELONGS AT THE CHOKEPOINT (AGL-2307).\n //\n // It used to live one level up, in `resolveRealmPluginInstalls` — which is\n // the path the tenant render and the console gate take, and NOT the path\n // remote SERVER bundles take. `loadRemoteServerBundles` resolves through\n // this function and nothing else, so a per-version revocation left a realm\n // server handler running: `hiddenAt` caught a full takedown, but a targeted\n // revocation deliberately does not hide the listing, which is the whole\n // difference between the two controls.\n //\n // Placed here rather than added as a second copy at the server loader, for\n // the reason this file's own comment gives about `hiddenAt`: this is the\n // one function every realm consumer funnels through, and a control that has\n // to be remembered per consumer is one that will be missed by the next one.\n const revocation = revocationSnapshot.data() as PluginRevocation | undefined\n if (isPluginRevoked(revocation, version)) return null\n const hostAbi = Number(data.manifest?.hostAbi)\n // Where the loaders may run this version (AGL-3116), from the manifest it\n // was published and reviewed with. A block that does not read is treated\n // as absent, so the documented default applies rather than \"nowhere\".\n const contributes = readPluginContributions(data.manifest?.contributes)\n const pluginId =\n typeof data.manifest?.id === 'string' && data.manifest.id\n ? String(data.manifest.id)\n : undefined\n return {\n sha256: String(data.sha256),\n ...(data.signature ? { signature: String(data.signature) } : {}),\n ...(data.trust ? { trust: String(data.trust) } : {}),\n ...(Number.isInteger(hostAbi) && hostAbi > 0 ? { hostAbi } : {}),\n ...(pluginId ? { pluginId } : {}),\n ...(contributes ? { contributes } : {}),\n }\n}\n\n/**\n * SECURITY-RELEVANT TTL (AGL-1302): the join drops revoked and taken-down\n * versions, so this cache bounds how long a killed plugin keeps loading on\n * published sites — 60s, on par with the page ISR window that already\n * bounded it. Only the HOST-scoped call (the tenant render path) is cached;\n * the console's org-scoped call stays fresh so an install shows up in the\n * workspace the moment it lands.\n */\nconst REALM_PLUGIN_INSTALLS_TTL_SECONDS = 60\n\nexport async function getRealmPluginInstalls(options: {\n orgId?: string\n hostId?: string\n}): Promise<RealmPluginInstall[]> {\n const hostId = options.hostId\n if (!hostId || options.orgId) return resolveRealmPluginInstalls(options)\n try {\n return await withRenderCache({\n key: ['tenant-realm-plugins', hostId],\n revalidate: REALM_PLUGIN_INSTALLS_TTL_SECONDS,\n tags: [tenantDataTag(hostId)],\n read: () => resolveRealmPluginInstalls(options),\n })\n } catch (error) {\n console.error(error)\n return resolveRealmPluginInstalls(options)\n }\n}\n\nasync function resolveRealmPluginInstalls(options: {\n orgId?: string\n hostId?: string\n}): Promise<RealmPluginInstall[]> {\n const firestore = firebaseAdmin.app().firestore()\n const orgId =\n options.orgId ??\n (options.hostId ? await resolveOrgIdForHost(options.hostId) : null)\n\n // Switchboard gate (AGL-424): a workspace with an EXPLICIT\n // `enabledPlugins` list only realm-loads listings on it (install sync\n // keeps the list in step; toggling one off disables it without\n // uninstalling). Absent field = default-open, so pre-switchboard\n // installs keep loading.\n let enabledFilter: readonly string[] | null = null\n if (orgId) {\n const configured = (\n await firestore.collection('orgs').doc(orgId).get()\n ).get('enabledPlugins')\n if (Array.isArray(configured)) enabledFilter = configured.map(String)\n }\n\n const pins = new Map<string, InstallPin>()\n const collect = async (\n ref: FirebaseFirestore.CollectionReference,\n ): Promise<void> => {\n const snapshot = await ref.get()\n for (const doc of snapshot.docs) {\n const version = doc.get('version')\n if (typeof version === 'string' && version) {\n pins.set(doc.id, { listingId: doc.id, version })\n }\n }\n }\n if (orgId) {\n await collect(firestore.collection('orgs').doc(orgId).collection('installs'))\n }\n if (options.hostId) {\n // Host pins win over org pins for the same listing (more specific).\n await collect(\n firestore.collection('hosts').doc(options.hostId).collection('installs'),\n )\n }\n if (enabledFilter) {\n for (const listingId of [...pins.keys()]) {\n if (!enabledFilter.includes(listingId)) pins.delete(listingId)\n }\n }\n if (!pins.size) return []\n\n const installs = await Promise.all(\n [...pins.values()].map(async (pin): Promise<RealmPluginInstall | null> => {\n const pinned = await resolveMarketplacePluginVersion(\n pin.listingId,\n pin.version,\n )\n // The revocation check moved INTO `resolveMarketplacePluginVersion`\n // (AGL-2307) so the remote-server loader — which resolves through that\n // function and never reaches this join — gets it too. A `null` here\n // therefore already means \"taken down, unknown, or killed\".\n if (!pinned || pinned.trust !== 'realm' || !pinned.signature) return null\n return {\n listingId: pin.listingId,\n version: pin.version,\n sha256: pinned.sha256,\n trust: 'realm',\n signature: pinned.signature,\n ...(pinned.hostAbi !== undefined ? { hostAbi: pinned.hostAbi } : {}),\n ...(pinned.pluginId ? { pluginId: pinned.pluginId } : {}),\n ...(pinned.contributes ? { contributes: pinned.contributes } : {}),\n }\n }),\n )\n return installs.filter((install): install is RealmPluginInstall =>\n Boolean(install),\n )\n}\n"],"names":["getPluginConfigSchema","isPluginRevoked","mergePluginConfig","readPluginContributions","resolvePluginConfig","firebaseAdmin","resolveOrgIdForHost","tenantDataTag","withRenderCache","readPluginSettings","scope","scopeId","pluginId","app","firestore","collection","doc","get","data","undefined","getPluginConfig","orgId","options","schema","hostId","org","host","Promise","all","resolveMarketplacePluginVersion","listingId","version","listingRef","listing","snapshot","revocationSnapshot","sha256","revocation","hostAbi","Number","manifest","contributes","id","String","signature","trust","isInteger","REALM_PLUGIN_INSTALLS_TTL_SECONDS","getRealmPluginInstalls","resolveRealmPluginInstalls","key","revalidate","tags","read","error","console","enabledFilter","configured","Array","isArray","map","pins","Map","collect","ref","docs","set","keys","includes","delete","size","installs","values","pin","pinned","filter","install","Boolean"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/realm-plugins.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getPluginConfigSchema,\n isPluginRevoked,\n mergePluginConfig,\n readPluginContributions,\n resolvePluginConfig,\n type PluginContributions,\n type PluginRevocation,\n type RealmPluginInstall,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from './firebase-admin'\nimport { resolveOrgIdForHost } from './organizations'\nimport { tenantDataTag, withRenderCache } from '../render-cache'\n\n/**\n * One `pluginSettings` document, or `undefined` if it cannot be read.\n *\n * A failed read degrades to \"no answer at this level\" rather than throwing,\n * so a transient Firestore error costs a handler its overrides and not its\n * request. The caller layers what comes back, so a missing site document and\n * a missing site override are the same thing to it.\n */\nasync function readPluginSettings(\n scope: 'orgs' | 'hosts',\n scopeId: string,\n pluginId: string,\n): Promise<Record<string, unknown> | undefined> {\n try {\n return (\n await firebaseAdmin\n .app()\n .firestore()\n .collection(scope)\n .doc(scopeId)\n .collection('pluginSettings')\n .doc(pluginId)\n .get()\n ).data()\n } catch {\n return undefined\n }\n}\n\n/**\n * Server-side plugin config read (AGL-428): the stored overrides merged over\n * the plugin's declared defaults (type-coerced — the docs are\n * manager-writable). Without a registered schema the raw doc (or {}) comes\n * back, so handlers degrade to their own fallbacks.\n *\n * `hostId` resolves the SITE's answer on top of the workspace's (AGL-428,\n * AGL-1014), and a handler that has one must pass it. A per-site setting the console can\n * store and the server never reads is not a setting: the operator lowers the\n * flagship branch's booking horizon, the console shows the number they typed,\n * and `/api/bookings/slots` keeps offering the workspace's — a disagreement\n * with no error and no surface that shows both sides.\n *\n * The site read is CONDITIONAL on being handed a host, so a request with no\n * site in scope pays nothing for a document that could not apply to it. It\n * runs alongside the org read rather than after it: the two documents are\n * independent, and sequencing them would put a second serial round trip on\n * every request that resolves config.\n */\nexport async function getPluginConfig(\n orgId: string | null | undefined,\n pluginId: string,\n options: { hostId?: string | null } = {},\n): Promise<Record<string, unknown>> {\n const schema = getPluginConfigSchema(pluginId)\n const hostId = options.hostId\n if (!orgId && !hostId) return schema ? mergePluginConfig(schema, null) : {}\n const [org, host] = await Promise.all([\n orgId ? readPluginSettings('orgs', orgId, pluginId) : undefined,\n hostId ? readPluginSettings('hosts', hostId, pluginId) : undefined,\n ])\n // Coercion happens once, at the end, inside `resolvePluginConfig` — a\n // malformed site value falls back to the WORKSPACE value it was trying to\n // replace rather than skipping past it to the schema default.\n if (schema) return resolvePluginConfig(schema, { org, host })\n // No schema: there is no field list to layer by, so the site document\n // simply narrows the workspace's, key for key.\n return { ...(org ?? {}), ...(host ?? {}) }\n}\n\n/**\n * Server-side resolution of a workspace's TRUSTED-REALM plugin installs\n * (AGL-420). Install docs pin `{version, sha256}`, but the trust grant\n * (`trust: 'realm'` + the platform Ed25519 `signature`) lives on the\n * server-only version doc — staff sign AFTER review, possibly long after\n * the install — so this join is the single source the loaders consume:\n *\n * 1. Read the org's installs (and the host's, when a host is in scope).\n * 2. Join each pin with its `marketplaceListings/{id}/pluginVersions/{v}`\n * doc; only versions carrying `trust: 'realm'` survive.\n * 3. Drop revoked versions (`revocations/{listingId}` kill switch) — a\n * revocation beats a still-present trust grant.\n * 4. Drop hidden listings (AGL-948) — `resolveMarketplacePluginVersion`\n * returns null for a listing under staff takedown. `deletedAt` is not\n * a blocker; see that function for why the two differ.\n *\n * The returned sha256/signature come from the VERSION doc, not the install\n * copy, so a tampered install doc cannot smuggle different bytes past the\n * loader's content check.\n */\n\ninterface InstallPin {\n listingId: string\n version: string\n}\n\n/**\n * One version-doc read, shaped for the remote loaders' `resolveVersion`.\n *\n * Also the staff-takedown gate (AGL-948): a listing carrying `hiddenAt`\n * resolves to null, so an already-installed plugin stops loading the\n * moment staff hide it. Takedown used to only de-list — a plugin pulled\n * for abuse kept executing in every workspace that had installed it until\n * someone separately wrote a revocation. It lives HERE rather than in the\n * join because every remote path funnels through this function: the\n * console and site realm joins, and both apps' remote-server-bundle\n * loaders.\n *\n * `deletedAt` is deliberately NOT a blocker. A publisher retiring a\n * listing must not break the sites already paying for it; unpublish\n * blocks new installs (`install-plugin`) and that is all it means.\n */\nexport async function resolveMarketplacePluginVersion(\n listingId: string,\n version: string,\n): Promise<{\n sha256: string\n signature?: string\n trust?: string\n hostAbi?: number\n pluginId?: string\n identity?: string\n contributes?: PluginContributions\n} | null> {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n // THE REVOCATION READ IS IN THIS `Promise.all`, NOT AFTER IT (2026-08-26).\n //\n // It is keyed on `listingId` alone, so it never depended on the two reads\n // above — awaiting it below them cost a SECOND sequential Firestore round\n // trip on every resolution, and `resolveRealmPluginInstalls` calls this\n // once per pinned install. That is the shape that grows with the number of\n // plugins an org has installed: `/api/orgs/realm-plugins` is deliberately\n // uncached (see the TTL note below), and at a P75 of 907ms it was already\n // the slowest org-scoped console route before anyone uploaded in volume.\n //\n // The trade is one extra read on the paths that exit early (a hidden\n // listing, a missing version doc) in exchange for halving the round trips\n // on the path that actually happens. The kill switch is UNCHANGED: the\n // check below still runs before any value is returned, and it still reads\n // live — this moves when the read is issued, never whether it is honored.\n const [listing, snapshot, revocationSnapshot] = await Promise.all([\n listingRef.get(),\n listingRef.collection('pluginVersions').doc(version).get(),\n firestore.collection('revocations').doc(listingId).get(),\n ])\n // A missing listing doc is NOT a blocker: Firestore does not cascade to\n // subcollections, so a hard-deleted listing leaves working installs\n // resolving off an orphaned version doc. Only an explicit takedown stops\n // them.\n if (listing.get('hiddenAt')) return null\n const data = snapshot.data()\n if (!data?.sha256) return null\n // THE KILL SWITCH BELONGS AT THE CHOKEPOINT (AGL-2307).\n //\n // It used to live one level up, in `resolveRealmPluginInstalls` — which is\n // the path the tenant render and the console gate take, and NOT the path\n // remote SERVER bundles take. `loadRemoteServerBundles` resolves through\n // this function and nothing else, so a per-version revocation left a realm\n // server handler running: `hiddenAt` caught a full takedown, but a targeted\n // revocation deliberately does not hide the listing, which is the whole\n // difference between the two controls.\n //\n // Placed here rather than added as a second copy at the server loader, for\n // the reason this file's own comment gives about `hiddenAt`: this is the\n // one function every realm consumer funnels through, and a control that has\n // to be remembered per consumer is one that will be missed by the next one.\n const revocation = revocationSnapshot.data() as PluginRevocation | undefined\n if (isPluginRevoked(revocation, version)) return null\n const hostAbi = Number(data.manifest?.hostAbi)\n // Where the loaders may run this version (AGL-3116), from the manifest it\n // was published and reviewed with. A block that does not read is treated\n // as absent, so the documented default applies rather than \"nowhere\".\n const contributes = readPluginContributions(data.manifest?.contributes)\n const pluginId =\n typeof data.manifest?.id === 'string' && data.manifest.id\n ? String(data.manifest.id)\n : undefined\n // The namespace these bytes were published under (AGL-3390), which the\n // loaders hold every registration to. Absent on a version published before\n // identities existed, and such a version never server-renders.\n const identity =\n typeof data.identity === 'string' && data.identity\n ? String(data.identity)\n : undefined\n return {\n sha256: String(data.sha256),\n ...(data.signature ? { signature: String(data.signature) } : {}),\n ...(data.trust ? { trust: String(data.trust) } : {}),\n ...(Number.isInteger(hostAbi) && hostAbi > 0 ? { hostAbi } : {}),\n ...(pluginId ? { pluginId } : {}),\n ...(identity ? { identity } : {}),\n ...(contributes ? { contributes } : {}),\n }\n}\n\n/**\n * SECURITY-RELEVANT TTL (AGL-1302): the join drops revoked and taken-down\n * versions, so this cache bounds how long a killed plugin keeps loading on\n * published sites — 60s, on par with the page ISR window that already\n * bounded it. Only the HOST-scoped call (the tenant render path) is cached;\n * the console's org-scoped call stays fresh so an install shows up in the\n * workspace the moment it lands.\n */\nconst REALM_PLUGIN_INSTALLS_TTL_SECONDS = 60\n\nexport async function getRealmPluginInstalls(options: {\n orgId?: string\n hostId?: string\n}): Promise<RealmPluginInstall[]> {\n const hostId = options.hostId\n if (!hostId || options.orgId) return resolveRealmPluginInstalls(options)\n try {\n return await withRenderCache({\n key: ['tenant-realm-plugins', hostId],\n revalidate: REALM_PLUGIN_INSTALLS_TTL_SECONDS,\n tags: [tenantDataTag(hostId)],\n read: () => resolveRealmPluginInstalls(options),\n })\n } catch (error) {\n console.error(error)\n return resolveRealmPluginInstalls(options)\n }\n}\n\nasync function resolveRealmPluginInstalls(options: {\n orgId?: string\n hostId?: string\n}): Promise<RealmPluginInstall[]> {\n const firestore = firebaseAdmin.app().firestore()\n const orgId =\n options.orgId ??\n (options.hostId ? await resolveOrgIdForHost(options.hostId) : null)\n\n // Switchboard gate (AGL-424): a workspace with an EXPLICIT\n // `enabledPlugins` list only realm-loads listings on it (install sync\n // keeps the list in step; toggling one off disables it without\n // uninstalling). Absent field = default-open, so pre-switchboard\n // installs keep loading.\n let enabledFilter: readonly string[] | null = null\n if (orgId) {\n const configured = (\n await firestore.collection('orgs').doc(orgId).get()\n ).get('enabledPlugins')\n if (Array.isArray(configured)) enabledFilter = configured.map(String)\n }\n\n const pins = new Map<string, InstallPin>()\n const collect = async (\n ref: FirebaseFirestore.CollectionReference,\n ): Promise<void> => {\n const snapshot = await ref.get()\n for (const doc of snapshot.docs) {\n const version = doc.get('version')\n if (typeof version === 'string' && version) {\n pins.set(doc.id, { listingId: doc.id, version })\n }\n }\n }\n if (orgId) {\n await collect(firestore.collection('orgs').doc(orgId).collection('installs'))\n }\n if (options.hostId) {\n // Host pins win over org pins for the same listing (more specific).\n await collect(\n firestore.collection('hosts').doc(options.hostId).collection('installs'),\n )\n }\n if (enabledFilter) {\n for (const listingId of [...pins.keys()]) {\n if (!enabledFilter.includes(listingId)) pins.delete(listingId)\n }\n }\n if (!pins.size) return []\n\n const installs = await Promise.all(\n [...pins.values()].map(async (pin): Promise<RealmPluginInstall | null> => {\n const pinned = await resolveMarketplacePluginVersion(\n pin.listingId,\n pin.version,\n )\n // The revocation check moved INTO `resolveMarketplacePluginVersion`\n // (AGL-2307) so the remote-server loader — which resolves through that\n // function and never reaches this join — gets it too. A `null` here\n // therefore already means \"taken down, unknown, or killed\".\n if (!pinned || pinned.trust !== 'realm' || !pinned.signature) return null\n return {\n listingId: pin.listingId,\n version: pin.version,\n sha256: pinned.sha256,\n trust: 'realm',\n signature: pinned.signature,\n ...(pinned.hostAbi !== undefined ? { hostAbi: pinned.hostAbi } : {}),\n ...(pinned.pluginId ? { pluginId: pinned.pluginId } : {}),\n ...(pinned.identity ? { identity: pinned.identity } : {}),\n ...(pinned.contributes ? { contributes: pinned.contributes } : {}),\n }\n }),\n )\n return installs.filter((install): install is RealmPluginInstall =>\n Boolean(install),\n )\n}\n"],"names":["getPluginConfigSchema","isPluginRevoked","mergePluginConfig","readPluginContributions","resolvePluginConfig","firebaseAdmin","resolveOrgIdForHost","tenantDataTag","withRenderCache","readPluginSettings","scope","scopeId","pluginId","app","firestore","collection","doc","get","data","undefined","getPluginConfig","orgId","options","schema","hostId","org","host","Promise","all","resolveMarketplacePluginVersion","listingId","version","listingRef","listing","snapshot","revocationSnapshot","sha256","revocation","hostAbi","Number","manifest","contributes","id","String","identity","signature","trust","isInteger","REALM_PLUGIN_INSTALLS_TTL_SECONDS","getRealmPluginInstalls","resolveRealmPluginInstalls","key","revalidate","tags","read","error","console","enabledFilter","configured","Array","isArray","map","pins","Map","collect","ref","docs","set","keys","includes","delete","size","installs","values","pin","pinned","filter","install","Boolean"],"mappings":"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"}
|