@aglyn/plugins-marketplace 1.15.2 → 1.16.0
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 +15 -15
- package/src/lib/components/listing-content.component.js +14 -12
- package/src/lib/components/listing-content.component.js.map +1 -1
- package/src/lib/components/site-plugin-connections-card.component.js +4 -3
- package/src/lib/components/site-plugin-connections-card.component.js.map +1 -1
- package/src/lib/model/editor-connections.d.ts +1 -1
- package/src/lib/model/editor-connections.js +13 -2
- package/src/lib/model/editor-connections.js.map +1 -1
- package/src/lib/model/marketplace.d.ts +11 -0
- package/src/lib/model/marketplace.js +12 -2
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/model/plugin-connection-card.d.ts +6 -0
- package/src/lib/model/plugin-connection-card.js +8 -0
- package/src/lib/model/plugin-connection-card.js.map +1 -1
- package/src/lib/model/plugin-connections.d.ts +14 -0
- package/src/lib/model/plugin-connections.js +18 -1
- package/src/lib/model/plugin-connections.js.map +1 -1
- package/src/lib/server/admin-reviews.js +9 -0
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/admin-trust.js +11 -0
- package/src/lib/server/admin-trust.js.map +1 -1
- package/src/lib/server/connections/site-records-platform.js +8 -5
- package/src/lib/server/connections/site-records-platform.js.map +1 -1
- package/src/lib/server/install-plugin.js +34 -19
- package/src/lib/server/install-plugin.js.map +1 -1
- package/src/lib/server/listing-versions.js +7 -2
- package/src/lib/server/listing-versions.js.map +1 -1
- package/src/lib/server/revoke-cache-drop.d.ts +29 -34
- package/src/lib/server/revoke-cache-drop.js +43 -49
- package/src/lib/server/revoke-cache-drop.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/listing-versions.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 isPluginRevoked,\n newestInstallableVersion,\n type PluginApiHandler,\n type PluginRevocation,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n isVersionApproved,\n listingArtifactType,\n} from '../model/marketplace'\n\nimport {\n reconcileInstallTallies,\n versionCollectionFor,\n type LivePinTally,\n type ReconciledInstallTallies,\n} from './version-stats'\nimport { verifiedLivePins } from './install-pin-counts'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\nimport { publisherPermissionVerdict } from './publisher-permissions'\nimport { attestationsForBytes } from '../model/publisher-attestation'\nimport { compareArtifactVersions } from '../model/update-state'\n\n/**\n * One reconciled tally per version id, plus the totals (AGL-1418).\n *\n * Built from EVERY version document, never from a filtered subset: the buyer\n * branch shows approved versions only, and reconciling against that subset\n * would hand a pending version's installs to an approved one.\n */\nfunction reconcileFromSnapshot(\n docs: Array<{ id: string; get: (field: string) => unknown }>,\n listing: Record<string, unknown> | undefined,\n pins?: LivePinTally | null,\n): { byVersion: Map<string, { installCount: number; activeInstalls: number }> } & ReconciledInstallTallies {\n const reconciled = reconcileInstallTallies(\n docs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n installCount: Number(doc.get('installCount') ?? 0),\n activeInstalls: Number(doc.get('activeInstalls') ?? 0),\n })),\n {\n installCount: Number(listing?.['installCount'] ?? 0),\n activeInstalls: Number(listing?.['activeInstalls'] ?? 0),\n },\n pins,\n )\n const byVersion = new Map<string, { installCount: number; activeInstalls: number }>()\n reconciled.versions.forEach((entry) =>\n byVersion.set(entry.version, {\n installCount: entry.installCount,\n activeInstalls: entry.activeInstalls,\n }),\n )\n return { ...reconciled, byVersion }\n}\n\n/**\n * The publisher's own view of their versions (AGL-1079).\n *\n * AGL-966 made review per-version and got the mechanics right — a new\n * version enters `pending`, the previously approved one keeps installing,\n * nothing ships past a reviewer. The publisher was told none of it: an\n * email on the outcome, and in between a listing row identical to the one\n * they had before they published.\n *\n * Separate from the buyer projection above on purpose. `reviewState`, the\n * rejection reason, the sha and what was attested are the publisher's own\n * business and nobody else's — a buyer has no need to read why a version\n * they will never be offered was sent back. So this branch demands a token\n * and org membership rather than widening what the public route returns.\n */\nconst publisherVersions: PluginApiHandler = async (req, res) => {\n const listingId = String(req.query?.listingId ?? '')\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Listings are org-owned (AGL-652), so this is a question about the\n // publishing org, not a uid comparison. It asks \"Manage published\n // listings\" (`marketplace.manageListings`, AGL-4047), the key the edit\n // paths ask, whose default is the owner and admins this asked for by role\n // before. Staff read any listing's versions, as before.\n if (decoded['staff'] !== true) {\n const verdict = await publisherPermissionVerdict(\n decoded.uid,\n listing.profileId,\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )\n if (verdict === 'not-a-member') {\n // 404, not 403: whether a listing exists is not a stranger's business.\n return res.status(404).json({ error: 'Unknown listing' })\n }\n if (verdict === 'refused') {\n // A member sees the listing in their own workspace already, so the\n // refusal names the permission they are missing.\n return res.status(403).json({\n error: marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n ),\n reason: 'permission',\n permission: MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n })\n }\n }\n const artifactType = listingArtifactType(listing)\n const snapshot = await listingRef\n .collection(versionCollectionFor(artifactType))\n .orderBy('publishedAt', 'desc')\n .limit(20)\n .get()\n /**\n * The kill switch, on the publisher's own view of their versions\n * (AGL-2328).\n *\n * Staff are REQUIRED to type a reason to revoke a version. It was stored\n * and nothing read it: every reader in the repo consulted `versions` and\n * stopped, and this branch did not read the document at all — so a\n * publisher whose version had been pulled saw the row it had before,\n * still labelled by its REVIEW state. A version can be approved and\n * revoked at once, and \"Approved\" was the only word on it.\n *\n * ONE document read for the whole card, not one per version: the switch\n * is listing-scoped. The predicate is `isPluginRevoked`, shared with the\n * installer and the loaders (AGL-1085), so what a publisher is told is\n * disabled and what actually refuses to install cannot drift apart.\n */\n const revocation = (\n await firestore.collection('revocations').doc(listingId).get()\n ).data() as PluginRevocation | undefined\n // The counters the page prints, derived from the pins where there are pins\n // to derive from (AGL-1419) and reconciled against the listing totals\n // otherwise (AGL-1418), so this card cannot contradict the header above it.\n const pins = await verifiedLivePins({\n firestore: firestore as never,\n listingRef: listingRef as never,\n listingId,\n listing,\n artifactType,\n versionIds: snapshot.docs.map((doc) => String(doc.get('version') ?? doc.id)),\n updateTime: listingSnapshot.updateTime,\n })\n const tallies = reconcileFromSnapshot(snapshot.docs as never, listing, pins)\n const versions = snapshot.docs.map((doc) => {\n const sha256 = String(doc.get('sha256') ?? '')\n const versionId = String(doc.get('version') ?? doc.id)\n return {\n version: String(doc.get('version') ?? doc.id),\n publishedAtMs: doc.get('publishedAt')?.toMillis?.() ?? null,\n sha256,\n // Absent on every version published before AGL-966, which is what\n // `grandfathered` marks — those were never reviewed and saying\n // \"pending\" about them would be a lie in both directions.\n reviewState: String(doc.get('reviewState') ?? ''),\n grandfathered: Boolean(doc.get('grandfathered')),\n signed: Boolean(doc.get('signature')),\n changelog: String(doc.get('changelog') ?? ''),\n // Only reached an email before this (AGL-1079), where it got buried\n // under everything else in an inbox.\n rejectionReason: String(doc.get('reviewRejectionReason') ?? ''),\n // Per VERSION, because a listing-wide takedown and a single revoked\n // version are the same document and only the predicate tells them\n // apart.\n revoked: isPluginRevoked(revocation, versionId),\n activeInstalls: tallies.byVersion.get(versionId)?.activeInstalls ?? 0,\n installCount: tallies.byVersion.get(versionId)?.installCount ?? 0,\n // What they claimed about THESE bytes (AGL-969) — so they can see it\n // before they claim it again on the next version.\n attestation: attestationsForBytes(\n doc.get('publisherAttestation') as never,\n sha256,\n ),\n }\n })\n return res.status(200).json({\n versions,\n // The same reconciled totals the buyer branch returns (AGL-1418), so the\n // Review status card and the listing header cannot disagree about how\n // many installs there are.\n installCount: tallies.installCount,\n activeInstalls: tallies.activeInstalls,\n untrackedInstallCount: tallies.untrackedInstallCount,\n untrackedActiveInstalls: tallies.untrackedActiveInstalls,\n // The version installs actually resolve to (AGL-1016/966). Deliberately\n // NOT `latestVersion`, which names a version installs may refuse.\n latestApprovedVersion: String(listing.latestApprovedVersion ?? ''),\n latestVersion: String(listing.latestVersion ?? ''),\n reviewStatus: String(listing.reviewStatus ?? ''),\n /**\n * Listing-scoped, like the document it comes from (AGL-2328) — one\n * reason covers whatever `versions` names, so two revoked rows show the\n * same sentence. That is what was recorded; inventing a per-version\n * reason would be inventing a fact.\n *\n * `revokedBy` is deliberately NOT returned. It is a staff uid, and it\n * belongs in the audit log rather than on a publisher's screen — the\n * same line the consumer-facing card drew.\n */\n revocationReason: String(revocation?.reason ?? '').trim(),\n // The writers store `revokedAt` as a server timestamp; `atMs` is on the\n // TYPE but nothing writes it. Read both rather than trusting either,\n // and treat a revocation with no readable date as dateless rather than\n // printing an epoch.\n revokedAtMs: revocationTimeMs(revocation),\n })\n}\n\n/**\n * When the revocation was written, in millis, or null (AGL-2328).\n *\n * `PluginRevocation` declares `atMs?: number` and every writer in\n * `plugin-reviews/route.ts` writes `revokedAt: serverTimestamp()` instead.\n * A reader that trusted the type would print nothing for every revocation\n * that exists; one that trusted the writer would break if `atMs` is ever\n * the field that gets written. Both are read, admin `Timestamp` and the\n * seconds-shaped form a client SDK produces included.\n */\nfunction revocationTimeMs(\n revocation: (PluginRevocation & { revokedAt?: unknown }) | undefined,\n): number | null {\n const at = revocation?.revokedAt as\n | { toMillis?: () => number; seconds?: number }\n | undefined\n if (typeof at?.toMillis === 'function') return at.toMillis()\n if (typeof at?.seconds === 'number') return at.seconds * 1000\n return typeof revocation?.atMs === 'number' ? revocation.atMs : null\n}\n\n/**\n * Public version history for a plugin listing (AGL-431). The\n * `pluginVersions` docs are server-only (they carry publish internals),\n * but their changelog/trust/compat fields are exactly what a buyer needs\n * on the detail page — so this handler exposes THAT subset and nothing\n * else (no objectPath, no signature; sha stays out simply because the\n * client has no use for it).\n */\nexport const listingVersionsHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.query?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n try {\n // The publisher's own view (AGL-1079) — same collection, a different\n // question, and one that needs a token.\n if (String(req.query?.scope ?? '') === 'publisher') {\n return await publisherVersions(req, res)\n }\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n // Every artifact type has a version history now (AGL-1036), not just\n // plugins: the per-version install counts are worth the same to whoever\n // publishes a component as to whoever publishes a plugin. The collection\n // differs — plugins keep publish internals in `pluginVersions`, everything\n // else keeps content snapshots in `versions` — so the type decides which\n // to read, and the buyer-safe projection below stays the same either way.\n const listingSnapshot = await listingRef.get()\n const artifactType = listingArtifactType(listingSnapshot.data() ?? {})\n const isPlugin = artifactType === 'plugin'\n const snapshot = await listingRef\n .collection(versionCollectionFor(artifactType))\n .orderBy('publishedAt', 'desc')\n .limit(20)\n .get()\n // Buyers see APPROVED versions only (AGL-976).\n //\n // This handler returned every version, so the listing's Version history\n // advertised pending and rejected ones — with `Latest` on whichever was\n // newest. A version rejected for undeclared network access was shown to\n // buyers, by us, as the current release, changelog and all. Installing it\n // was already impossible (AGL-966 gates that), which made the card a\n // promise the product would not keep.\n //\n // Plugins only: `reviewState` is a plugin concept, and a copied artifact's\n // `versions` docs carry none — filtering those would empty every\n // component and template history.\n //\n // Through `isVersionApproved`, whose contract is the one that matters\n // here: ABSENT STATE IS NOT APPROVAL. That hides versions published\n // before review existed, which is the safe direction — the alternative is\n // showing unreviewed code as reviewed.\n const visibleDocs = isPlugin\n ? snapshot.docs.filter((doc) => isVersionApproved({\n reviewState: doc.get('reviewState'),\n }))\n : snapshot.docs\n // Derived from the live pins (AGL-1419), which are the only ground truth\n // and the only thing that can bring a count back DOWN — a tenant erase\n // sweeps `installs` without decrementing anything. Falls back to the\n // AGL-1418 reconciliation when there is nothing to derive from: a copied\n // artifact holds no pin, and a missing index must degrade to the previous\n // release rather than to zeros.\n //\n // Built from EVERY version doc rather than `visibleDocs`: the buyer filter\n // above drops unapproved versions, and reconciling against what survives\n // it would credit their installs to an approved version.\n const pins = await verifiedLivePins({\n firestore: firestore as never,\n listingRef: listingRef as never,\n listingId,\n listing: listingSnapshot.data(),\n artifactType,\n versionIds: snapshot.docs.map((doc) =>\n String(doc.get('version') ?? doc.id),\n ),\n updateTime: listingSnapshot.updateTime,\n })\n const tallies = reconcileFromSnapshot(\n snapshot.docs as never,\n listingSnapshot.data(),\n pins,\n )\n const versions = visibleDocs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n // Per-version install counts (AGL-1036). `installCount` is every install\n // that ever landed on this version; `activeInstalls` is how many are on\n // it now — the one that answers \"who is still on the old version\".\n installCount:\n tallies.byVersion.get(String(doc.get('version') ?? doc.id))\n ?.installCount ?? 0,\n activeInstalls:\n tallies.byVersion.get(String(doc.get('version') ?? doc.id))\n ?.activeInstalls ?? 0,\n ...(doc.get('changelog') ? { changelog: String(doc.get('changelog')) } : {}),\n ...(doc.get('trust') ? { trust: String(doc.get('trust')) } : {}),\n ...(Number.isInteger(doc.get('manifest')?.hostAbi)\n ? { hostAbi: Number(doc.get('manifest').hostAbi) }\n : {}),\n // Declared network allowlist (AGL-879 follow-up): the plugin origin\n // builds the /load CSP `connect-src` from this, so the sandbox only\n // reaches origins the manifest declared. Already buyer-visible in\n // review (\"Capabilities: network N\"), so exposing the origins here\n // leaks nothing new.\n ...(Array.isArray(doc.get('manifest')?.capabilities?.network)\n ? {\n network: (doc.get('manifest').capabilities.network as unknown[])\n .map((entry) => String(entry))\n .filter((entry) => /^https:\\/\\/[^\\s/]+$/.test(entry))\n .slice(0, 20),\n }\n : {}),\n publishedAtMs: doc.get('publishedAt')?.toMillis?.() ?? null,\n }))\n\n // Repair `latestApprovedVersion` when it disagrees with the versions\n // (AGL-1016, corrected by AGL-2368).\n //\n // The field is written by the approval route, so every plugin approved\n // before it existed carries none — and the console would report those as\n // \"no published version to compare against\" forever, which is a worse lie\n // than the one it replaced. The approved set is already in hand here and\n // `pluginVersions` is server-only, so this is the one place that can\n // repair it. Plugins only: `reviewState` and the approved-version\n // guarantee are a plugin concept, and a copied artifact's `versions` docs\n // carry neither.\n //\n // Two things were wrong with the original, and they compounded. It asked\n // `newestApprovedVersion` — approval only, blind to the kill switch — and\n // it \"only ever moved the value forward\". So it did not merely fail to\n // step the mirror back off a revoked version; it actively WROTE ONE BACK.\n // AGL-2306 taught the review route to repair the mirror on revoke, and the\n // very next hit on this handler undid it: stored v1.0.0 (correct), newest\n // approved v2.0.0 (revoked), v1.0.0 < v2.0.0, write. This endpoint is a\n // public unauthenticated GET, so \"the next hit\" is the next page view.\n //\n // Now derived from the whole set through the SHARED predicate the review\n // route repairs with, and applied whenever it differs — a derivation has\n // no direction to preserve. The monotonic rule belongs to the APPROVAL\n // writer, where it means \"a reviewer working through a backlog must not\n // walk the offer backwards\"; here it only ever preserved a wrong answer.\n const revocation = !isPlugin\n ? undefined\n : ((\n await firestore.collection('revocations').doc(listingId).get()\n ).data() as PluginRevocation | undefined)\n const newest = !isPlugin\n ? null\n : newestInstallableVersion(\n snapshot.docs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n reviewState: doc.get('reviewState'),\n })),\n revocation,\n compareArtifactVersions,\n )\n if (isPlugin) {\n const stored = listingSnapshot.get('latestApprovedVersion')\n const storedString = stored == null ? '' : String(stored)\n if (storedString !== (newest ?? '')) {\n await listingRef\n .set(\n {\n // Deleted rather than emptied when nothing qualifies, matching\n // `repairLatestApprovedVersion`: an ABSENT mirror reads as \"no\n // update to offer\", an empty string is a value readers have to\n // interpret.\n latestApprovedVersion:\n newest ?? firebaseAdmin.firestore.FieldValue.delete(),\n },\n { merge: true },\n )\n .catch(() => undefined)\n }\n }\n\n // The totals the header prints (AGL-1418). The listing document carries\n // its own copy of this pair and the client used to read it straight off\n // Firestore, which is how the header came to say `7 installs · 2 active`\n // over a version history that added up to `3 · 1`. Served from here so\n // one arithmetic answers the whole page.\n return res.status(200).json({\n versions,\n installCount: tallies.installCount,\n activeInstalls: tallies.activeInstalls,\n untrackedInstallCount: tallies.untrackedInstallCount,\n untrackedActiveInstalls: tallies.untrackedActiveInstalls,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Version lookup failed' })\n }\n}\n"],"names":["isPluginRevoked","newestInstallableVersion","firebaseAdmin","isVersionApproved","listingArtifactType","reconcileInstallTallies","versionCollectionFor","verifiedLivePins","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","publisherPermissionVerdict","attestationsForBytes","compareArtifactVersions","reconcileFromSnapshot","docs","listing","pins","reconciled","map","doc","version","String","get","id","installCount","Number","activeInstalls","byVersion","Map","versions","forEach","entry","set","publisherVersions","req","res","listingId","query","authorization","headers","idToken","startsWith","slice","length","undefined","status","json","error","decoded","app","auth","verifyIdToken","firestore","listingRef","collection","listingSnapshot","data","deletedAt","verdict","uid","profileId","reason","permission","artifactType","snapshot","orderBy","limit","revocation","versionIds","updateTime","tallies","sha256","versionId","publishedAtMs","toMillis","reviewState","grandfathered","Boolean","signed","changelog","rejectionReason","revoked","attestation","untrackedInstallCount","untrackedActiveInstalls","latestApprovedVersion","latestVersion","reviewStatus","revocationReason","trim","revokedAtMs","revocationTimeMs","at","revokedAt","seconds","atMs","listingVersionsHandler","method","scope","isPlugin","visibleDocs","filter","trust","isInteger","hostAbi","Array","isArray","capabilities","network","test","newest","stored","storedString","FieldValue","delete","merge","catch","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/listing-versions.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 isPluginRevoked,\n newestInstallableVersion,\n type PluginApiHandler,\n type PluginRevocation,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n isVersionApproved,\n listingArtifactType,\n} from '../model/marketplace'\n\nimport {\n reconcileInstallTallies,\n versionCollectionFor,\n type LivePinTally,\n type ReconciledInstallTallies,\n} from './version-stats'\nimport { verifiedLivePins } from './install-pin-counts'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\nimport { publisherPermissionVerdict } from './publisher-permissions'\nimport { attestationsForBytes } from '../model/publisher-attestation'\nimport { compareArtifactVersions } from '../model/update-state'\n\n/**\n * One reconciled tally per version id, plus the totals (AGL-1418).\n *\n * Built from EVERY version document, never from a filtered subset: the buyer\n * branch shows approved versions only, and reconciling against that subset\n * would hand a pending version's installs to an approved one.\n */\nfunction reconcileFromSnapshot(\n docs: Array<{ id: string; get: (field: string) => unknown }>,\n listing: Record<string, unknown> | undefined,\n pins?: LivePinTally | null,\n): { byVersion: Map<string, { installCount: number; activeInstalls: number }> } & ReconciledInstallTallies {\n const reconciled = reconcileInstallTallies(\n docs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n installCount: Number(doc.get('installCount') ?? 0),\n activeInstalls: Number(doc.get('activeInstalls') ?? 0),\n })),\n {\n installCount: Number(listing?.['installCount'] ?? 0),\n activeInstalls: Number(listing?.['activeInstalls'] ?? 0),\n },\n pins,\n )\n const byVersion = new Map<string, { installCount: number; activeInstalls: number }>()\n reconciled.versions.forEach((entry) =>\n byVersion.set(entry.version, {\n installCount: entry.installCount,\n activeInstalls: entry.activeInstalls,\n }),\n )\n return { ...reconciled, byVersion }\n}\n\n/**\n * The publisher's own view of their versions (AGL-1079).\n *\n * AGL-966 made review per-version and got the mechanics right — a new\n * version enters `pending`, the previously approved one keeps installing,\n * nothing ships past a reviewer. The publisher was told none of it: an\n * email on the outcome, and in between a listing row identical to the one\n * they had before they published.\n *\n * Separate from the buyer projection above on purpose. `reviewState`, the\n * rejection reason, the sha and what was attested are the publisher's own\n * business and nobody else's — a buyer has no need to read why a version\n * they will never be offered was sent back. So this branch demands a token\n * and org membership rather than widening what the public route returns.\n */\nconst publisherVersions: PluginApiHandler = async (req, res) => {\n const listingId = String(req.query?.listingId ?? '')\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n const listing = listingSnapshot.data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Listings are org-owned (AGL-652), so this is a question about the\n // publishing org, not a uid comparison. It asks \"Manage published\n // listings\" (`marketplace.manageListings`, AGL-4047), the key the edit\n // paths ask, whose default is the owner and admins this asked for by role\n // before. Staff read any listing's versions, as before.\n if (decoded['staff'] !== true) {\n const verdict = await publisherPermissionVerdict(\n decoded.uid,\n listing.profileId,\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )\n if (verdict === 'not-a-member') {\n // 404, not 403: whether a listing exists is not a stranger's business.\n return res.status(404).json({ error: 'Unknown listing' })\n }\n if (verdict === 'refused') {\n // A member sees the listing in their own workspace already, so the\n // refusal names the permission they are missing.\n return res.status(403).json({\n error: marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n ),\n reason: 'permission',\n permission: MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n })\n }\n }\n const artifactType = listingArtifactType(listing)\n const snapshot = await listingRef\n .collection(versionCollectionFor(artifactType))\n .orderBy('publishedAt', 'desc')\n .limit(20)\n .get()\n /**\n * The kill switch, on the publisher's own view of their versions\n * (AGL-2328).\n *\n * Staff are REQUIRED to type a reason to revoke a version. It was stored\n * and nothing read it: every reader in the repo consulted `versions` and\n * stopped, and this branch did not read the document at all — so a\n * publisher whose version had been pulled saw the row it had before,\n * still labelled by its REVIEW state. A version can be approved and\n * revoked at once, and \"Approved\" was the only word on it.\n *\n * ONE document read for the whole card, not one per version: the switch\n * is listing-scoped. The predicate is `isPluginRevoked`, shared with the\n * installer and the loaders (AGL-1085), so what a publisher is told is\n * disabled and what actually refuses to install cannot drift apart.\n */\n const revocation = (\n await firestore.collection('revocations').doc(listingId).get()\n ).data() as PluginRevocation | undefined\n // The counters the page prints, derived from the pins where there are pins\n // to derive from (AGL-1419) and reconciled against the listing totals\n // otherwise (AGL-1418), so this card cannot contradict the header above it.\n const pins = await verifiedLivePins({\n firestore: firestore as never,\n listingRef: listingRef as never,\n listingId,\n listing,\n artifactType,\n versionIds: snapshot.docs.map((doc) => String(doc.get('version') ?? doc.id)),\n updateTime: listingSnapshot.updateTime,\n })\n const tallies = reconcileFromSnapshot(snapshot.docs as never, listing, pins)\n const versions = snapshot.docs.map((doc) => {\n const sha256 = String(doc.get('sha256') ?? '')\n const versionId = String(doc.get('version') ?? doc.id)\n return {\n version: String(doc.get('version') ?? doc.id),\n publishedAtMs: doc.get('publishedAt')?.toMillis?.() ?? null,\n sha256,\n // Absent on every version published before AGL-966, which is what\n // `grandfathered` marks — those were never reviewed and saying\n // \"pending\" about them would be a lie in both directions.\n reviewState: String(doc.get('reviewState') ?? ''),\n grandfathered: Boolean(doc.get('grandfathered')),\n signed: Boolean(doc.get('signature')),\n changelog: String(doc.get('changelog') ?? ''),\n // Only reached an email before this (AGL-1079), where it got buried\n // under everything else in an inbox.\n rejectionReason: String(doc.get('reviewRejectionReason') ?? ''),\n // Per VERSION, because a listing-wide takedown and a single revoked\n // version are the same document and only the predicate tells them\n // apart.\n revoked: isPluginRevoked(revocation, versionId),\n activeInstalls: tallies.byVersion.get(versionId)?.activeInstalls ?? 0,\n installCount: tallies.byVersion.get(versionId)?.installCount ?? 0,\n // What they claimed about THESE bytes (AGL-969) — so they can see it\n // before they claim it again on the next version.\n attestation: attestationsForBytes(\n doc.get('publisherAttestation') as never,\n sha256,\n ),\n }\n })\n return res.status(200).json({\n versions,\n // The same reconciled totals the buyer branch returns (AGL-1418), so the\n // Review status card and the listing header cannot disagree about how\n // many installs there are.\n installCount: tallies.installCount,\n activeInstalls: tallies.activeInstalls,\n untrackedInstallCount: tallies.untrackedInstallCount,\n untrackedActiveInstalls: tallies.untrackedActiveInstalls,\n // The version installs actually resolve to (AGL-1016/966). Deliberately\n // NOT `latestVersion`, which names a version installs may refuse.\n latestApprovedVersion: String(listing.latestApprovedVersion ?? ''),\n latestVersion: String(listing.latestVersion ?? ''),\n reviewStatus: String(listing.reviewStatus ?? ''),\n /**\n * Listing-scoped, like the document it comes from (AGL-2328) — one\n * reason covers whatever `versions` names, so two revoked rows show the\n * same sentence. That is what was recorded; inventing a per-version\n * reason would be inventing a fact.\n *\n * `revokedBy` is deliberately NOT returned. It is a staff uid, and it\n * belongs in the audit log rather than on a publisher's screen — the\n * same line the consumer-facing card drew.\n */\n revocationReason: String(revocation?.reason ?? '').trim(),\n // The writers store `revokedAt` as a server timestamp; `atMs` is on the\n // TYPE but nothing writes it. Read both rather than trusting either,\n // and treat a revocation with no readable date as dateless rather than\n // printing an epoch.\n revokedAtMs: revocationTimeMs(revocation),\n })\n}\n\n/**\n * When the revocation was written, in millis, or null (AGL-2328).\n *\n * `PluginRevocation` declares `atMs?: number` and every writer in\n * `plugin-reviews/route.ts` writes `revokedAt: serverTimestamp()` instead.\n * A reader that trusted the type would print nothing for every revocation\n * that exists; one that trusted the writer would break if `atMs` is ever\n * the field that gets written. Both are read, admin `Timestamp` and the\n * seconds-shaped form a client SDK produces included.\n */\nfunction revocationTimeMs(\n revocation: (PluginRevocation & { revokedAt?: unknown }) | undefined,\n): number | null {\n const at = revocation?.revokedAt as\n | { toMillis?: () => number; seconds?: number }\n | undefined\n if (typeof at?.toMillis === 'function') return at.toMillis()\n if (typeof at?.seconds === 'number') return at.seconds * 1000\n return typeof revocation?.atMs === 'number' ? revocation.atMs : null\n}\n\n/**\n * Public version history for a plugin listing (AGL-431). The\n * `pluginVersions` docs are server-only (they carry publish internals),\n * but their changelog/trust/compat fields are exactly what a buyer needs\n * on the detail page — so this handler exposes THAT subset and nothing\n * else (no objectPath, no signature; sha stays out simply because the\n * client has no use for it).\n */\nexport const listingVersionsHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.query?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n try {\n // The publisher's own view (AGL-1079) — same collection, a different\n // question, and one that needs a token.\n if (String(req.query?.scope ?? '') === 'publisher') {\n return await publisherVersions(req, res)\n }\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n // Every artifact type has a version history now (AGL-1036), not just\n // plugins: the per-version install counts are worth the same to whoever\n // publishes a component as to whoever publishes a plugin. The collection\n // differs — plugins keep publish internals in `pluginVersions`, everything\n // else keeps content snapshots in `versions` — so the type decides which\n // to read, and the buyer-safe projection below stays the same either way.\n const listingSnapshot = await listingRef.get()\n const artifactType = listingArtifactType(listingSnapshot.data() ?? {})\n const isPlugin = artifactType === 'plugin'\n const snapshot = await listingRef\n .collection(versionCollectionFor(artifactType))\n .orderBy('publishedAt', 'desc')\n .limit(20)\n .get()\n // Buyers see APPROVED versions only (AGL-976).\n //\n // This handler returned every version, so the listing's Version history\n // advertised pending and rejected ones — with `Latest` on whichever was\n // newest. A version rejected for undeclared network access was shown to\n // buyers, by us, as the current release, changelog and all. Installing it\n // was already impossible (AGL-966 gates that), which made the card a\n // promise the product would not keep.\n //\n // Plugins only: `reviewState` is a plugin concept, and a copied artifact's\n // `versions` docs carry none — filtering those would empty every\n // component and template history.\n //\n // Through `isVersionApproved`, whose contract is the one that matters\n // here: ABSENT STATE IS NOT APPROVAL. That hides versions published\n // before review existed, which is the safe direction — the alternative is\n // showing unreviewed code as reviewed.\n const visibleDocs = isPlugin\n ? snapshot.docs.filter((doc) => isVersionApproved({\n reviewState: doc.get('reviewState'),\n }))\n : snapshot.docs\n // Derived from the live pins (AGL-1419), which are the only ground truth\n // and the only thing that can bring a count back DOWN — a tenant erase\n // sweeps `installs` without decrementing anything. Falls back to the\n // AGL-1418 reconciliation when there is nothing to derive from: a copied\n // artifact holds no pin, and a missing index must degrade to the previous\n // release rather than to zeros.\n //\n // Built from EVERY version doc rather than `visibleDocs`: the buyer filter\n // above drops unapproved versions, and reconciling against what survives\n // it would credit their installs to an approved version.\n const pins = await verifiedLivePins({\n firestore: firestore as never,\n listingRef: listingRef as never,\n listingId,\n listing: listingSnapshot.data(),\n artifactType,\n versionIds: snapshot.docs.map((doc) =>\n String(doc.get('version') ?? doc.id),\n ),\n updateTime: listingSnapshot.updateTime,\n })\n const tallies = reconcileFromSnapshot(\n snapshot.docs as never,\n listingSnapshot.data(),\n pins,\n )\n const versions = visibleDocs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n // Per-version install counts (AGL-1036). `installCount` is every install\n // that ever landed on this version; `activeInstalls` is how many are on\n // it now — the one that answers \"who is still on the old version\".\n installCount:\n tallies.byVersion.get(String(doc.get('version') ?? doc.id))\n ?.installCount ?? 0,\n activeInstalls:\n tallies.byVersion.get(String(doc.get('version') ?? doc.id))\n ?.activeInstalls ?? 0,\n ...(doc.get('changelog') ? { changelog: String(doc.get('changelog')) } : {}),\n ...(doc.get('trust') ? { trust: String(doc.get('trust')) } : {}),\n ...(Number.isInteger(doc.get('manifest')?.hostAbi)\n ? { hostAbi: Number(doc.get('manifest').hostAbi) }\n : {}),\n // Declared network allowlist (AGL-879 follow-up): the plugin origin\n // builds the /load CSP `connect-src` from this, so the sandbox only\n // reaches origins the manifest declared. Already buyer-visible in\n // review (\"Capabilities: network N\"), so exposing the origins here\n // leaks nothing new.\n ...(Array.isArray(doc.get('manifest')?.capabilities?.network)\n ? {\n network: (doc.get('manifest').capabilities.network as unknown[])\n .map((entry) => String(entry))\n .filter((entry) => /^https:\\/\\/[^\\s/]+$/.test(entry))\n .slice(0, 20),\n }\n : {}),\n // The services the version declares connections to (AGL-4197), by\n // label only: the listing says which plan connecting them needs. The\n // site's own connection card already shows these labels to installers.\n ...(Array.isArray(doc.get('manifest')?.connections)\n ? {\n connections: (doc.get('manifest').connections as unknown[])\n .map((entry) =>\n String((entry as { label?: unknown })?.label ?? '').slice(0, 80),\n )\n .filter(Boolean)\n .slice(0, 10),\n }\n : {}),\n publishedAtMs: doc.get('publishedAt')?.toMillis?.() ?? null,\n }))\n\n // Repair `latestApprovedVersion` when it disagrees with the versions\n // (AGL-1016, corrected by AGL-2368).\n //\n // The field is written by the approval route, so every plugin approved\n // before it existed carries none — and the console would report those as\n // \"no published version to compare against\" forever, which is a worse lie\n // than the one it replaced. The approved set is already in hand here and\n // `pluginVersions` is server-only, so this is the one place that can\n // repair it. Plugins only: `reviewState` and the approved-version\n // guarantee are a plugin concept, and a copied artifact's `versions` docs\n // carry neither.\n //\n // Two things were wrong with the original, and they compounded. It asked\n // `newestApprovedVersion` — approval only, blind to the kill switch — and\n // it \"only ever moved the value forward\". So it did not merely fail to\n // step the mirror back off a revoked version; it actively WROTE ONE BACK.\n // AGL-2306 taught the review route to repair the mirror on revoke, and the\n // very next hit on this handler undid it: stored v1.0.0 (correct), newest\n // approved v2.0.0 (revoked), v1.0.0 < v2.0.0, write. This endpoint is a\n // public unauthenticated GET, so \"the next hit\" is the next page view.\n //\n // Now derived from the whole set through the SHARED predicate the review\n // route repairs with, and applied whenever it differs — a derivation has\n // no direction to preserve. The monotonic rule belongs to the APPROVAL\n // writer, where it means \"a reviewer working through a backlog must not\n // walk the offer backwards\"; here it only ever preserved a wrong answer.\n const revocation = !isPlugin\n ? undefined\n : ((\n await firestore.collection('revocations').doc(listingId).get()\n ).data() as PluginRevocation | undefined)\n const newest = !isPlugin\n ? null\n : newestInstallableVersion(\n snapshot.docs.map((doc) => ({\n version: String(doc.get('version') ?? doc.id),\n reviewState: doc.get('reviewState'),\n })),\n revocation,\n compareArtifactVersions,\n )\n if (isPlugin) {\n const stored = listingSnapshot.get('latestApprovedVersion')\n const storedString = stored == null ? '' : String(stored)\n if (storedString !== (newest ?? '')) {\n await listingRef\n .set(\n {\n // Deleted rather than emptied when nothing qualifies, matching\n // `repairLatestApprovedVersion`: an ABSENT mirror reads as \"no\n // update to offer\", an empty string is a value readers have to\n // interpret.\n latestApprovedVersion:\n newest ?? firebaseAdmin.firestore.FieldValue.delete(),\n },\n { merge: true },\n )\n .catch(() => undefined)\n }\n }\n\n // The totals the header prints (AGL-1418). The listing document carries\n // its own copy of this pair and the client used to read it straight off\n // Firestore, which is how the header came to say `7 installs · 2 active`\n // over a version history that added up to `3 · 1`. Served from here so\n // one arithmetic answers the whole page.\n return res.status(200).json({\n versions,\n installCount: tallies.installCount,\n activeInstalls: tallies.activeInstalls,\n untrackedInstallCount: tallies.untrackedInstallCount,\n untrackedActiveInstalls: tallies.untrackedActiveInstalls,\n })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Version lookup failed' })\n }\n}\n"],"names":["isPluginRevoked","newestInstallableVersion","firebaseAdmin","isVersionApproved","listingArtifactType","reconcileInstallTallies","versionCollectionFor","verifiedLivePins","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","publisherPermissionVerdict","attestationsForBytes","compareArtifactVersions","reconcileFromSnapshot","docs","listing","pins","reconciled","map","doc","version","String","get","id","installCount","Number","activeInstalls","byVersion","Map","versions","forEach","entry","set","publisherVersions","req","res","listingId","query","authorization","headers","idToken","startsWith","slice","length","undefined","status","json","error","decoded","app","auth","verifyIdToken","firestore","listingRef","collection","listingSnapshot","data","deletedAt","verdict","uid","profileId","reason","permission","artifactType","snapshot","orderBy","limit","revocation","versionIds","updateTime","tallies","sha256","versionId","publishedAtMs","toMillis","reviewState","grandfathered","Boolean","signed","changelog","rejectionReason","revoked","attestation","untrackedInstallCount","untrackedActiveInstalls","latestApprovedVersion","latestVersion","reviewStatus","revocationReason","trim","revokedAtMs","revocationTimeMs","at","revokedAt","seconds","atMs","listingVersionsHandler","method","scope","isPlugin","visibleDocs","filter","trust","isInteger","hostAbi","Array","isArray","capabilities","network","test","connections","label","newest","stored","storedString","FieldValue","delete","merge","catch","console"],"mappings":"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"}
|
|
@@ -17,43 +17,38 @@
|
|
|
17
17
|
import { type PluginSiteCacheResult } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
|
|
18
18
|
import type { Firestore } from 'firebase-admin/firestore';
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* A PLATFORM EVENT CHANGED WHICH PLUGINS MAY LOAD (AGL-1152, AGL-4121).
|
|
21
21
|
*
|
|
22
22
|
* ## Why a revocation needs it
|
|
23
23
|
*
|
|
24
24
|
* The tenant stamps each `marketplacePlugin` node with its pinned install
|
|
25
|
-
* and its kill-switch state AT COMPOSE TIME,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* BEST EFFORT, and never throws: the tenant still refuses a revoked plugin
|
|
51
|
-
* at render time. This only shrinks the window in which cached HTML shows
|
|
52
|
-
* the old answer. Read `complete` before reading `dropped` — see
|
|
53
|
-
* {@link dropPluginSiteCache}.
|
|
25
|
+
* and its kill-switch state AT COMPOSE TIME, and resolves the signed realm
|
|
26
|
+
* installs once per site, so a page cached before a takedown, a revoked
|
|
27
|
+
* version or withdrawn realm trust goes on serving the old answer until it
|
|
28
|
+
* re-renders. Dropping the cache is what makes the stop take effect on
|
|
29
|
+
* already-cached HTML.
|
|
30
|
+
*
|
|
31
|
+
* ## One tag, not a host walk (AGL-4121)
|
|
32
|
+
*
|
|
33
|
+
* This used to walk the `installs` collection group, expand org-tier pins to
|
|
34
|
+
* their hosts and fan out a whole-site drop to each — capped at 200 sites, and
|
|
35
|
+
* only as good as the walk. Every read that decides which plugins load now
|
|
36
|
+
* carries the platform-wide plugin tag, and so does every page that made one,
|
|
37
|
+
* so a single `plugins: 'everywhere'` drop expires exactly those, on every
|
|
38
|
+
* site, under every cache key, with nothing to walk and nothing to cap. Sites
|
|
39
|
+
* that never ran the listing regenerate their plugin-reading pages once on
|
|
40
|
+
* their next visit; takedowns are rare enough for that to be the right trade.
|
|
41
|
+
*
|
|
42
|
+
* ## It must not be lost
|
|
43
|
+
*
|
|
44
|
+
* The app stages the drop in its durable publish outbox before it sends it,
|
|
45
|
+
* so a refused call is retried by the drain. When neither landed, this
|
|
46
|
+
* THROWS: the stop itself is already written, and the staff action that made
|
|
47
|
+
* it must come back as a failure to retry rather than a success that left the
|
|
48
|
+
* stopped bundle running on cached pages for the week-long backstop.
|
|
54
49
|
*/
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
export declare class PluginCacheDropLostError extends Error {
|
|
51
|
+
readonly listingId: string;
|
|
52
|
+
constructor(listingId: string);
|
|
58
53
|
}
|
|
59
|
-
export declare function dropCachesForListing(
|
|
54
|
+
export declare function dropCachesForListing(_firestore: Firestore, listingId: string): Promise<PluginSiteCacheResult>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
1
|
/**
|
|
3
2
|
* @license
|
|
4
3
|
* Copyright 2026 Aglyn LLC
|
|
@@ -15,60 +14,55 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
14
|
* See the License for the specific language governing permissions and
|
|
16
15
|
* limitations under the License.
|
|
17
16
|
*/ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
* A PLATFORM EVENT CHANGED WHICH PLUGINS MAY LOAD (AGL-1152, AGL-4121).
|
|
19
|
+
*
|
|
20
|
+
* ## Why a revocation needs it
|
|
21
|
+
*
|
|
22
|
+
* The tenant stamps each `marketplacePlugin` node with its pinned install
|
|
23
|
+
* and its kill-switch state AT COMPOSE TIME, and resolves the signed realm
|
|
24
|
+
* installs once per site, so a page cached before a takedown, a revoked
|
|
25
|
+
* version or withdrawn realm trust goes on serving the old answer until it
|
|
26
|
+
* re-renders. Dropping the cache is what makes the stop take effect on
|
|
27
|
+
* already-cached HTML.
|
|
28
|
+
*
|
|
29
|
+
* ## One tag, not a host walk (AGL-4121)
|
|
30
|
+
*
|
|
31
|
+
* This used to walk the `installs` collection group, expand org-tier pins to
|
|
32
|
+
* their hosts and fan out a whole-site drop to each — capped at 200 sites, and
|
|
33
|
+
* only as good as the walk. Every read that decides which plugins load now
|
|
34
|
+
* carries the platform-wide plugin tag, and so does every page that made one,
|
|
35
|
+
* so a single `plugins: 'everywhere'` drop expires exactly those, on every
|
|
36
|
+
* site, under every cache key, with nothing to walk and nothing to cap. Sites
|
|
37
|
+
* that never ran the listing regenerate their plugin-reading pages once on
|
|
38
|
+
* their next visit; takedowns are rare enough for that to be the right trade.
|
|
39
|
+
*
|
|
40
|
+
* ## It must not be lost
|
|
41
|
+
*
|
|
42
|
+
* The app stages the drop in its durable publish outbox before it sends it,
|
|
43
|
+
* so a refused call is retried by the drain. When neither landed, this
|
|
44
|
+
* THROWS: the stop itself is already written, and the staff action that made
|
|
45
|
+
* it must come back as a failure to retry rather than a success that left the
|
|
46
|
+
* stopped bundle running on cached pages for the week-long backstop.
|
|
47
|
+
*/ export class PluginCacheDropLostError extends Error {
|
|
48
|
+
constructor(listingId){
|
|
49
|
+
super(`The live sites could not be told that marketplace listing ${listingId} ` + 'was stopped, and the drop could not be queued for a retry. The stop ' + 'is saved; retry the action to drop the cached pages.'), this.listingId = listingId;
|
|
50
|
+
this.name = 'PluginCacheDropLostError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function dropCachesForListing(_firestore, listingId) {
|
|
54
|
+
if (!listingId) return {
|
|
21
55
|
dropped: 0,
|
|
22
56
|
skipped: 0,
|
|
23
57
|
complete: true
|
|
24
58
|
};
|
|
25
|
-
if (!listingId) return nothing;
|
|
26
|
-
let hostIds;
|
|
27
|
-
let installsFound;
|
|
28
|
-
try {
|
|
29
|
-
const installs = await firestore.collectionGroup('installs').where('listingId', '==', listingId).get();
|
|
30
|
-
if (installs.empty) return nothing;
|
|
31
|
-
installsFound = installs.size;
|
|
32
|
-
const hosts = new Set();
|
|
33
|
-
const orgIds = new Set();
|
|
34
|
-
for (const doc of installs.docs){
|
|
35
|
-
const owner = doc.ref.parent.parent;
|
|
36
|
-
if (!owner) continue;
|
|
37
|
-
if (owner.parent.id === 'hosts') hosts.add(owner.id);
|
|
38
|
-
else if (owner.parent.id === 'orgs') orgIds.add(owner.id);
|
|
39
|
-
}
|
|
40
|
-
// An org-tier pin applies to every host in the org, so it is the org's
|
|
41
|
-
// hosts that hold the cached HTML — the org itself renders nothing.
|
|
42
|
-
await Promise.all([
|
|
43
|
-
...orgIds
|
|
44
|
-
].map(async (orgId)=>{
|
|
45
|
-
const owned = await firestore.collection('hosts').where('orgId', '==', orgId).get();
|
|
46
|
-
for (const host of owned.docs)hosts.add(host.id);
|
|
47
|
-
}));
|
|
48
|
-
hostIds = [
|
|
49
|
-
...hosts
|
|
50
|
-
];
|
|
51
|
-
} catch (error) {
|
|
52
|
-
/*
|
|
53
|
-
* The READ failed, so nothing is known about which sites are affected —
|
|
54
|
-
* which is not the same as none being affected. `complete: false` for
|
|
55
|
-
* the same reason the shell uses it: a zero that reads as an answer is
|
|
56
|
-
* how a kill switch quietly does not take effect.
|
|
57
|
-
*/ console.error('[marketplace] could not find the sites running', listingId, error);
|
|
58
|
-
return {
|
|
59
|
-
installsFound: 0,
|
|
60
|
-
dropped: 0,
|
|
61
|
-
skipped: 0,
|
|
62
|
-
complete: false
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
59
|
const result = await dropPluginSiteCache({
|
|
66
|
-
hostIds,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return _extends({}, result, {
|
|
70
|
-
installsFound
|
|
60
|
+
hostIds: [],
|
|
61
|
+
plugins: 'everywhere',
|
|
62
|
+
reason: `marketplace listing ${listingId} stopped`
|
|
71
63
|
});
|
|
64
|
+
if (!result.complete) throw new PluginCacheDropLostError(listingId);
|
|
65
|
+
return result;
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
//# sourceMappingURL=revoke-cache-drop.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/revoke-cache-drop.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 dropPluginSiteCache,\n type PluginSiteCacheResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { Firestore } from 'firebase-admin/firestore'\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/revoke-cache-drop.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 dropPluginSiteCache,\n type PluginSiteCacheResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { Firestore } from 'firebase-admin/firestore'\n\n/**\n * A PLATFORM EVENT CHANGED WHICH PLUGINS MAY LOAD (AGL-1152, AGL-4121).\n *\n * ## Why a revocation needs it\n *\n * The tenant stamps each `marketplacePlugin` node with its pinned install\n * and its kill-switch state AT COMPOSE TIME, and resolves the signed realm\n * installs once per site, so a page cached before a takedown, a revoked\n * version or withdrawn realm trust goes on serving the old answer until it\n * re-renders. Dropping the cache is what makes the stop take effect on\n * already-cached HTML.\n *\n * ## One tag, not a host walk (AGL-4121)\n *\n * This used to walk the `installs` collection group, expand org-tier pins to\n * their hosts and fan out a whole-site drop to each — capped at 200 sites, and\n * only as good as the walk. Every read that decides which plugins load now\n * carries the platform-wide plugin tag, and so does every page that made one,\n * so a single `plugins: 'everywhere'` drop expires exactly those, on every\n * site, under every cache key, with nothing to walk and nothing to cap. Sites\n * that never ran the listing regenerate their plugin-reading pages once on\n * their next visit; takedowns are rare enough for that to be the right trade.\n *\n * ## It must not be lost\n *\n * The app stages the drop in its durable publish outbox before it sends it,\n * so a refused call is retried by the drain. When neither landed, this\n * THROWS: the stop itself is already written, and the staff action that made\n * it must come back as a failure to retry rather than a success that left the\n * stopped bundle running on cached pages for the week-long backstop.\n */\nexport class PluginCacheDropLostError extends Error {\n constructor(readonly listingId: string) {\n super(\n `The live sites could not be told that marketplace listing ${listingId} ` +\n 'was stopped, and the drop could not be queued for a retry. The stop ' +\n 'is saved; retry the action to drop the cached pages.',\n )\n this.name = 'PluginCacheDropLostError'\n }\n}\n\nexport async function dropCachesForListing(\n _firestore: Firestore,\n listingId: string,\n): Promise<PluginSiteCacheResult> {\n if (!listingId) return { dropped: 0, skipped: 0, complete: true }\n const result = await dropPluginSiteCache({\n hostIds: [],\n plugins: 'everywhere',\n reason: `marketplace listing ${listingId} stopped`,\n })\n if (!result.complete) throw new PluginCacheDropLostError(listingId)\n return result\n}\n"],"names":["dropPluginSiteCache","PluginCacheDropLostError","Error","listingId","name","dropCachesForListing","_firestore","dropped","skipped","complete","result","hostIds","plugins","reason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,mBAAmB,QAEd,gDAA+C;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,MAAMC,iCAAiCC;IAC5C,YAAY,AAASC,SAAiB,CAAE;QACtC,KAAK,CACH,CAAC,0DAA0D,EAAEA,UAAU,CAAC,CAAC,GACvE,yEACA,8DAJeA,YAAAA;QAMnB,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,eAAeC,qBACpBC,UAAqB,EACrBH,SAAiB;IAEjB,IAAI,CAACA,WAAW,OAAO;QAAEI,SAAS;QAAGC,SAAS;QAAGC,UAAU;IAAK;IAChE,MAAMC,SAAS,MAAMV,oBAAoB;QACvCW,SAAS,EAAE;QACXC,SAAS;QACTC,QAAQ,CAAC,oBAAoB,EAAEV,UAAU,QAAQ,CAAC;IACpD;IACA,IAAI,CAACO,OAAOD,QAAQ,EAAE,MAAM,IAAIR,yBAAyBE;IACzD,OAAOO;AACT"}
|