@aglyn/aglyn 1.0.0-beta.200 → 1.0.0-beta.201
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 +11 -11
- package/src/lib/app-utils/artifact-list-keys.js +10 -0
- package/src/lib/app-utils/artifact-list-keys.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +19 -0
- package/src/lib/app-utils/crm.js +27 -1
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +4 -4
- package/src/lib/app-utils/docs-help.generated.js +10 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +78 -64
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-state.d.ts +7 -3
- package/src/lib/app-utils/email-state.js +22 -6
- package/src/lib/app-utils/email-state.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +101 -9
- package/src/lib/app-utils/forms.js +137 -7
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +3 -3
- package/src/lib/app-utils/health-report.js +9 -9
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/host-naming.d.ts +12 -7
- package/src/lib/app-utils/host-naming.js +18 -3
- package/src/lib/app-utils/host-naming.js.map +1 -1
- package/src/lib/app-utils/lockdown.d.ts +19 -0
- package/src/lib/app-utils/lockdown.js +21 -0
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/media-embedded-fields.d.ts +40 -0
- package/src/lib/app-utils/media-embedded-fields.js +77 -0
- package/src/lib/app-utils/media-embedded-fields.js.map +1 -1
- package/src/lib/app-utils/media-metadata.d.ts +17 -1
- package/src/lib/app-utils/media-metadata.js +22 -2
- package/src/lib/app-utils/media-metadata.js.map +1 -1
- package/src/lib/app-utils/payment-risk.d.ts +75 -0
- package/src/lib/app-utils/payment-risk.js +139 -0
- package/src/lib/app-utils/payment-risk.js.map +1 -0
- package/src/lib/app-utils/plugin-api-request-host.d.ts +29 -0
- package/src/lib/app-utils/plugin-api-request-host.js +90 -0
- package/src/lib/app-utils/plugin-api-request-host.js.map +1 -0
- package/src/lib/app-utils/plugin-bundle-checks.d.ts +3 -2
- package/src/lib/app-utils/plugin-bundle-checks.js +76 -5
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +4 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/plugin-manager/billing-webhook-hooks.d.ts +9 -0
- package/src/lib/plugin-manager/billing-webhook-hooks.js +5 -1
- package/src/lib/plugin-manager/billing-webhook-hooks.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +9 -1
- package/src/lib/plugin-manager/plugin-activity-actions.js +11 -1
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-events.d.ts +22 -0
- package/src/lib/plugin-manager/plugin-events.js.map +1 -1
- package/src/lib/plugin-manager/plugin-jobs.d.ts +8 -4
- package/src/lib/plugin-manager/plugin-jobs.js +8 -4
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plugin-bundle-checks.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\n/**\n * Static plugin-bundle checks (AGL-426) — Strapi `strapi-plugin verify`\n * parity, but ALSO enforced server-side: the publish API runs the same\n * checks, so a bundle that fails locally fails there identically.\n *\n * The bundle is PARSED and the tree analysed (AGL-964). The checks used to\n * match source text with regexes, which meant they could not see computed\n * member access, could not follow a value into a call, and could not tell\n * code from a comment — `g['ev'+'al'](…)` passed while the words `eval(`\n * inside a comment failed. On a minified bundle, which every published\n * artifact is, that is close to no signal at all.\n *\n * These are still cheap heuristics, NOT a sandbox: they catch honest build\n * mistakes (leftover imports, missing entry exports, oversized bundles),\n * flag the APIs the platform never wants in marketplace code, and surface\n * the questions a reviewer should ask. A determined author can still hide\n * intent from any static pass. The real security boundary stays the trust\n * chain (sandbox iframe + per-manifest CSP / staff review + realm signing /\n * sha pinning / the kill switch).\n */\n\nimport { parse, type Node, type Options } from 'acorn'\nimport type { PluginContributions } from '../plugin-manager/plugin-contributions'\n\n/** The areas the verifier reports on, in the order a reviewer reads them. */\nexport type BundleCheckId =\n | 'parse'\n | 'entry'\n | 'self-contained'\n | 'size'\n | 'code-execution'\n | 'globals'\n | 'storage'\n | 'dynamic-import'\n | 'network'\n | 'obfuscation'\n | 'contributions'\n\nexport interface BundleCheckProblem {\n level: 'error' | 'warning'\n message: string\n /**\n * Which check produced this. Optional so a verdict stored by an older\n * checker still reads — those findings render on their own rather than\n * under a check row.\n */\n check?: BundleCheckId\n}\n\n/**\n * `unknown` is the one that earns its keep (AGL-1087). A check that could\n * not run — the network diff with no manifest supplied, everything at all\n * when the parse failed — must not render as green, or the page tells a\n * reviewer something was verified when nothing looked at it.\n */\nexport type BundleCheckStatus = 'pass' | 'fail' | 'question' | 'unknown'\n\nexport interface BundleCheckSummary {\n id: BundleCheckId\n /** Reviewer-facing name of the area. */\n label: string\n status: BundleCheckStatus\n /** What the check actually saw, when that is worth stating on its own. */\n detail?: string\n}\n\nexport interface BundleCheckResult {\n ok: boolean\n problems: BundleCheckProblem[]\n /** Every area, including the ones that found nothing (AGL-1087). */\n checks: BundleCheckSummary[]\n /** Which entry exports the source declares. */\n exports: { register: boolean; registerApi: boolean }\n /** What `register()` visibly registers (AGL-3116). */\n contributions: DetectedContributions\n}\n\n/**\n * What a bundle's `register()` visibly registers (AGL-3116): the declaration\n * its registration calls imply, read from the tree.\n */\nexport interface DetectedContributions {\n contributes: PluginContributions\n /**\n * Registrations the checker saw and could not read — an argument built at\n * runtime. What they contribute is unknown, so a declaration cannot be\n * checked against them and a reviewer has to read them.\n */\n unresolved: string[]\n}\n\nconst CHECK_LABELS: Record<BundleCheckId, string> = {\n parse: 'Parses as an ES module',\n entry: 'Exports an entry point',\n 'self-contained': 'Self-contained (no static imports)',\n size: 'Within the size limit',\n 'code-execution': 'No eval / Function constructor',\n globals: 'No computed access on a global',\n storage: 'No direct storage or cookie access',\n 'dynamic-import': 'No dynamic import of unknown code',\n network: 'Network calls match the manifest',\n obfuscation: 'No obfuscation shapes',\n contributions: 'Registrations match the declared contributions',\n}\n\n/** Check order as displayed — findings first would reorder on every bundle. */\nconst CHECK_ORDER: BundleCheckId[] = [\n 'parse',\n 'entry',\n 'self-contained',\n 'size',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n]\n\nexport const MAX_PLUGIN_BUNDLE_BYTES = 1_000_000\n\n/**\n * Stamped onto a cached verdict (AGL-962) so a stored result outlives only\n * the checker that produced it.\n *\n * `checkPluginBundle` is pure over immutable, content-addressed bytes, so a\n * verdict for a given sha256 never changes — but the CHECKS do. **Bump this\n * whenever a rule is added, removed or loosened**, and every stored verdict\n * from the old checker is ignored and recomputed on next read. Forgetting to\n * bump means a reviewer sees a verdict from a checker that no longer exists,\n * which is the failure mode re-running the verifier was meant to prevent.\n *\n * 2 — AST analysis replaces the regex scan (AGL-964).\n * 3 — per-check summary added to the verdict (AGL-1087).\n * 4 — calls through an alias are resolved (AGL-1090).\n * 5 — URLs held in a constant are resolved (AGL-1093).\n * 6 — registrations are compared with the declared contributions (AGL-3116).\n */\nexport const PLUGIN_VERIFIER_VERSION = 6\n\n/** A verdict as stored on a `pluginVersions` doc (AGL-962). */\nexport interface StoredBundleVerdict {\n ok?: boolean\n problems?: BundleCheckProblem[]\n /** Present from verifier 3 on (AGL-1087). */\n checks?: BundleCheckSummary[]\n sha256?: string\n verifierVersion?: number\n}\n\n/**\n * Whether a stored verdict may be served instead of re-running the checks.\n *\n * Fails closed on every mismatch: no verdict, a verdict for different bytes\n * (a republished version keeps its version string but changes sha), or a\n * verdict from a checker that has since changed. Any of those means\n * download-and-recompute, which costs one page view rather than showing a\n * reviewer a verdict that was never true of the bundle in front of them.\n */\nexport function isStoredVerdictCurrent(\n stored: StoredBundleVerdict | null | undefined,\n sha256: string,\n): boolean {\n if (!stored || !sha256) return false\n return (\n stored.sha256 === sha256 &&\n Number(stored.verifierVersion) === PLUGIN_VERIFIER_VERSION\n )\n}\n\n/**\n * Globals whose properties are the platform's business. Computed access on\n * one of these is flagged as computed access rather than by trying to\n * enumerate the property names an author might reach — `g[k]` hides its\n * property from any static pass, so the ACCESS is the finding.\n */\nconst GLOBAL_ROOTS = new Set([\n 'globalThis',\n 'window',\n 'self',\n 'top',\n 'parent',\n 'frames',\n 'document',\n])\n\n/** Storage the platform never wants reached directly (use host-mediated data). */\nconst STORAGE_NAMES = new Set(['localStorage', 'sessionStorage', 'indexedDB'])\n\n/** Network entry points, by the name they are called/constructed under. */\nconst NETWORK_CALLS = new Set(['fetch', 'sendBeacon'])\nconst NETWORK_CONSTRUCTORS = new Set([\n 'XMLHttpRequest',\n 'WebSocket',\n 'EventSource',\n])\n\n/**\n * Names worth following through an alias (AGL-1090).\n *\n * `const f = fetch; f(url)` used to pass every check, and the network row\n * rendered a green \"no network calls\" rather than going quiet — a two-line\n * indirection defeating the signal this whole verifier exists to produce.\n * Aliasing a VALUE was the exact gap the project was created to close, and\n * resolving global objects (`const g = globalThis`) never covered it.\n *\n * Reassignment, `arr[0]`, an object property and passing the function as an\n * argument all stay out of reach. The bar is that the cheap form is not free.\n */\nconst ALIASABLE_CALLABLES = new Set([\n 'eval',\n 'Function',\n 'fetch',\n 'sendBeacon',\n 'XMLHttpRequest',\n 'WebSocket',\n 'EventSource',\n])\n\n/**\n * Callees that legitimately take an absolute URL and reach nothing — so a URL\n * literal passed to them is not a call the network check failed to follow.\n * `createElementNS` matters most: an SVG namespace argument appears in a large\n * share of real bundles.\n */\nconst URL_TAKING_NON_CALLS = new Set([\n 'URL',\n 'URLSearchParams',\n 'createElementNS',\n 'createDocument',\n])\n\n/** Beyond this, a single line is not source a reviewer can read. */\nconst MAX_READABLE_LINE = 100_000\n/** A base64 blob at least this long is worth decoding before approving. */\nconst MIN_SUSPICIOUS_BASE64 = 1024\n/** How many `_0x…` names before the bundle counts as machine-obfuscated. */\nconst MIN_OBFUSCATED_NAMES = 5\n/** Verdicts are stored on a version doc; keep them bounded. */\nconst MAX_PROBLEMS = 40\n\nconst OBFUSCATED_NAME = /^_0x[0-9a-f]{4,}$/i\nconst BASE64_BLOB = /^[A-Za-z0-9+/\\r\\n]+={0,2}$/\n\ntype AnyNode = Node & Record<string, unknown>\n\n/**\n * Child keys that are NOT expressions in the surrounding scope — a\n * non-computed `.property`, an object literal key, a label. Walking into\n * them would read `x.localStorage` on an unrelated object as a global\n * storage access.\n */\nconst skippedChild = (node: AnyNode, key: string): boolean => {\n switch (node.type) {\n case 'MemberExpression':\n return key === 'property' && !node['computed']\n case 'Property':\n case 'PropertyDefinition':\n case 'MethodDefinition':\n return key === 'key' && !node['computed']\n case 'LabeledStatement':\n case 'BreakStatement':\n case 'ContinueStatement':\n return key === 'label'\n case 'ImportSpecifier':\n case 'ExportSpecifier':\n return true\n default:\n return false\n }\n}\n\n/** Depth-first walk over every expression-position node in the tree. */\nfunction walk(root: AnyNode, visit: (node: AnyNode) => void): void {\n const stack: AnyNode[] = [root]\n while (stack.length) {\n const node = stack.pop()\n if (!node) continue\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'type' || key === 'start' || key === 'end') continue\n if (skippedChild(node, key)) continue\n const value = node[key]\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && typeof item.type === 'string')\n stack.push(item as AnyNode)\n }\n } else if (\n value &&\n typeof value === 'object' &&\n typeof (value as AnyNode).type === 'string'\n ) {\n stack.push(value as AnyNode)\n }\n }\n }\n}\n\n/**\n * The string a node evaluates to, when that is knowable without running it.\n * Covers the shapes used to hide a property name from a text scan: a\n * string literal, a template with no interpolation, and literals added\n * together (`'ev' + 'al'`).\n */\nfunction staticString(\n node: AnyNode | null | undefined,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n if (!node) return null\n if (node.type === 'Literal')\n return typeof node['value'] === 'string' ? (node['value'] as string) : null\n // A name bound once to a constant string (AGL-1093). `const ENDPOINT =\n // 'https://…'; fetch(ENDPOINT)` is the idiomatic way to write a request,\n // and refusing to read it meant the network diff quietly did not run for\n // the bundles most likely to be honest.\n if (node.type === 'Identifier' && constants)\n return constants.get(node['name'] as string) ?? null\n if (node.type === 'TemplateLiteral') {\n const expressions = (node['expressions'] ?? []) as AnyNode[]\n const quasis = (node['quasis'] ?? []) as Array<{\n value: { cooked?: string }\n }>\n if (!expressions.length) {\n return quasis.length === 1 ? quasis[0]?.value?.cooked ?? null : null\n }\n // `${BASE}/zen` — resolvable exactly when every hole is.\n let out = ''\n for (let index = 0; index < quasis.length; index += 1) {\n out += quasis[index]?.value?.cooked ?? ''\n if (index < expressions.length) {\n const filled = staticString(expressions[index], constants)\n if (filled === null) return null\n out += filled\n }\n }\n return out\n }\n if (node.type === 'BinaryExpression' && node['operator'] === '+') {\n const left = staticString(node['left'] as AnyNode, constants)\n const right = staticString(node['right'] as AnyNode, constants)\n return left !== null && right !== null ? left + right : null\n }\n return null\n}\n\n/** The property name a member expression reads, computed or not. */\nfunction propertyName(\n node: AnyNode,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n const property = node['property'] as AnyNode\n if (!node['computed'])\n return property?.type === 'Identifier'\n ? (property['name'] as string)\n : null\n return staticString(property, constants)\n}\n\n/**\n * The origin of a URL argument, when it is a knowable http(s) URL.\n *\n * Scheme-checked HERE, once (AGL-1094). An opaque origin — every `data:`,\n * `blob:` and `javascript:` URL — makes `new URL(x).origin` the STRING\n * `\"null\"`, and a caller that then re-parsed that string threw `Invalid URL`\n * and took the whole checker down with it. Nothing downstream should have to\n * know that, so nothing downstream gets the chance.\n */\nfunction literalOrigin(\n node: AnyNode | undefined,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n const value = staticString(node, constants)\n if (!value) return null\n try {\n const url = new URL(value)\n return /^https?:$/.test(url.protocol) ? url.origin : null\n } catch {\n return null\n }\n}\n\ninterface CheckOptions {\n maxBytes?: number\n /**\n * The origins the manifest declares under `capabilities.network`. A\n * network call the manifest does not account for is the single most\n * useful signal the tree makes available — and the per-manifest CSP\n * would block it at runtime anyway, so the bundle and its manifest\n * disagreeing is a bug the publisher wants to hear about at publish\n * time, not a mystery in production. Undefined = \"not supplied\", which\n * downgrades those findings to warnings, because a checker that was\n * never told what was declared cannot claim anything was undeclared.\n */\n declaredNetwork?: string[]\n /**\n * The manifest's `contributes` block (AGL-3116): `null` when the manifest\n * declares none, `undefined` when no manifest was supplied — which leaves\n * the comparison `unknown`, as the network diff is without its manifest.\n */\n declaredContributions?: PluginContributions | null\n /**\n * Refuse a bundle that registers something while its manifest declares no\n * contributions at all. Set where a NEW version is judged — the publish\n * pipeline and the local verifier that mirrors it. The review page and the\n * re-verification sweep judge versions published before the contract,\n * which load under its default, and a rule they were never held to must\n * not turn them red after the fact.\n */\n requireContributions?: boolean\n}\n\n/** The detection result of a bundle no pass could read. */\nconst NOTHING_DETECTED: DetectedContributions = { contributes: {}, unresolved: [] }\n\n/**\n * Registration calls whose argument says what a bundle contributes, by the\n * name they are reached through: `h.aglyn.registerConsoleExtension(...)`,\n * `registerConsoleExtension(...)` after the realm template's host mapping,\n * `aglyn.components.registerComponent(...)`.\n */\nconst REGISTRATION_CALLS = new Set([\n 'registerConsoleExtension',\n 'registerSiteRuntime',\n 'registerComponent',\n 'addDependency',\n 'defineUiFeatureBundle',\n])\n\n/**\n * Console extension fields the shell draws on every screen of a workspace:\n * nav tabs, organization tabs, providers, staff pages and the dashboard and\n * settings entries. A plugin registering any of them loads with the shell.\n */\nconst SHELL_EXTENSION_FIELDS = [\n 'navItems',\n 'orgNavItems',\n 'providers',\n 'staffPages',\n 'dashboardCards',\n 'settingsSections',\n]\n\n/**\n * What `register()` visibly registers (AGL-3116), read from the tree.\n *\n * A heuristic like every check here: it reads registration calls whose\n * arguments are written out — an object literal, a string or a named slot —\n * and reports every call it cannot read as `unresolved` rather than guessing.\n * The declaration stays the contract; this is how a publisher learns theirs\n * is incomplete before a published page fails to load the plugin.\n */\nfunction detectRegistrations(\n program: AnyNode,\n constants: ReadonlyMap<string, string>,\n): DetectedContributions {\n const slots = new Set<string>()\n const routes = new Set<string>()\n const orgRoutes = new Set<string>()\n const components = new Set<string>()\n const features = new Set<string>()\n const unresolved = new Set<string>()\n let shell = false\n\n const objectProperty = (\n object: AnyNode | undefined,\n name: string,\n ): AnyNode | undefined => {\n if (object?.type !== 'ObjectExpression') return undefined\n for (const property of (object['properties'] ?? []) as AnyNode[]) {\n if (property.type !== 'Property') continue\n const key = property['key'] as AnyNode\n const keyName =\n !property['computed'] && key?.type === 'Identifier'\n ? (key['name'] as string)\n : staticString(key, constants)\n if (keyName === name) return property['value'] as AnyNode\n }\n return undefined\n }\n const listOf = (node: AnyNode | undefined): AnyNode[] | null =>\n node?.type === 'ArrayExpression' ? ((node['elements'] ?? []) as AnyNode[]) : null\n // A slot as a widget names it: a string, or a catalog entry\n // (`CONSOLE_WIDGET_SLOTS.hostActivity`), whose property IS the slot id.\n const slotName = (node: AnyNode | undefined): string | null => {\n const literal = staticString(node, constants)\n if (literal !== null) return literal\n return node?.type === 'MemberExpression' ? propertyName(node, constants) : null\n }\n\n walk(program, (node) => {\n if (node.type !== 'CallExpression') return\n const callee = node['callee'] as AnyNode\n const name =\n callee?.type === 'Identifier'\n ? (callee['name'] as string)\n : callee?.type === 'MemberExpression'\n ? propertyName(callee, constants)\n : null\n if (!name || !REGISTRATION_CALLS.has(name)) return\n const args = (node['arguments'] ?? []) as AnyNode[]\n if (name === 'registerConsoleExtension') {\n const extension = args[0]\n if (extension?.type !== 'ObjectExpression') {\n unresolved.add('a console extension built at runtime')\n return\n }\n const widgets = objectProperty(extension, 'widgets')\n if (widgets) {\n const list = listOf(widgets)\n if (!list) unresolved.add('a console widget list built at runtime')\n for (const widget of list ?? []) {\n const slot = slotName(objectProperty(widget, 'slot'))\n if (slot) slots.add(slot)\n else unresolved.add('a console widget whose slot is built at runtime')\n }\n }\n for (const field of SHELL_EXTENSION_FIELDS) {\n const value = objectProperty(extension, field)\n if (!value) continue\n shell = true\n if (field !== 'navItems' && field !== 'orgNavItems') continue\n const into = field === 'navItems' ? routes : orgRoutes\n const list = listOf(value)\n if (!list) unresolved.add(`console ${field} built at runtime`)\n for (const item of list ?? []) {\n const href = staticString(objectProperty(item, 'href'), constants)\n if (href) into.add(href)\n else unresolved.add(`a console ${field} entry whose href is built at runtime`)\n }\n }\n return\n }\n if (name === 'registerSiteRuntime') {\n const id = staticString(objectProperty(args[0], 'runtimeId'), constants)\n if (id) features.add(id)\n else unresolved.add('a site runtime whose runtimeId is built at runtime')\n return\n }\n if (name === 'registerComponent') {\n const id = staticString(objectProperty(args[1], '$id'), constants)\n if (id) components.add(id)\n else unresolved.add('a canvas component whose $id is built at runtime')\n return\n }\n // `addDependency` / `defineUiFeatureBundle`: a component bundle whose\n // entries register later, from a list this pass does not follow.\n unresolved.add('a canvas component bundle')\n })\n\n const contributes: PluginContributions = {}\n if (components.size || features.size) {\n contributes.site = {\n ...(components.size ? { components: [...components].sort() } : {}),\n ...(features.size ? { features: [...features].sort() } : {}),\n }\n }\n if (slots.size || routes.size || orgRoutes.size || shell) {\n contributes.console = {\n ...(slots.size ? { slots: [...slots].sort() } : {}),\n ...(routes.size ? { routes: [...routes].sort() } : {}),\n ...(orgRoutes.size ? { orgRoutes: [...orgRoutes].sort() } : {}),\n ...(shell ? { shell: true } : {}),\n }\n }\n return { contributes, unresolved: [...unresolved].sort() }\n}\n\n/** A declaration in the words a reviewer reads: `console slots hostActivity`. */\nfunction describeContributions(contributes: PluginContributions): string {\n const parts: string[] = []\n const site = contributes.site ?? {}\n const shell = contributes.console ?? {}\n if (site.components?.length) parts.push(`site components ${site.components.join(', ')}`)\n if (site.features?.length) parts.push(`site features ${site.features.join(', ')}`)\n if (shell.slots?.length) parts.push(`console slots ${shell.slots.join(', ')}`)\n if (shell.routes?.length) parts.push(`console routes ${shell.routes.join(', ')}`)\n if (shell.orgRoutes?.length) {\n parts.push(`console organization routes ${shell.orgRoutes.join(', ')}`)\n }\n if (shell.shell) parts.push('the console shell')\n return parts.join(' · ') || 'nothing'\n}\n\n/**\n * The `contributions` area (AGL-3116): what the bundle registers against what\n * its manifest declares.\n *\n * Every registration the tree shows must be declared, because the loaders\n * read ONLY the declaration: a slot the manifest omits is a slot whose\n * screens never load the plugin, and a component it omits is an element that\n * renders empty on every published page that places it. A declared entry the\n * bundle never registers is a warning — the plugin loads there for nothing.\n */\nfunction compareContributions(\n detected: DetectedContributions,\n options: CheckOptions | undefined,\n add: (\n level: BundleCheckProblem['level'],\n check: BundleCheckId,\n message: string,\n ) => void,\n details: Map<BundleCheckId, string>,\n markUnknown: (check: BundleCheckId) => void,\n): void {\n const found = detected.contributes\n const registers = describeContributions(found)\n const registersSomething =\n Boolean(found.site || found.console) || detected.unresolved.length > 0\n const unreadable = detected.unresolved.length\n ? ` It also makes registrations the checker could not read: ${detected.unresolved.join('; ')}.`\n : ''\n const declared = options?.declaredContributions\n if (declared === undefined) {\n // Nothing to compare against is `unknown`, never a pass — the same\n // reading the network row gives a bundle checked without its manifest.\n if (!registersSomething) {\n details.set('contributions', 'registers nothing')\n return\n }\n markUnknown('contributions')\n details.set(\n 'contributions',\n `registers ${registers} — no manifest was supplied, so nothing was compared`,\n )\n return\n }\n if (declared === null) {\n details.set(\n 'contributions',\n registersSomething ? `undeclared · registers ${registers}` : 'declares and registers nothing',\n )\n if (!registersSomething) return\n add(\n options?.requireContributions ? 'error' : 'warning',\n 'contributions',\n `the manifest declares no \"contributes\", and register() registers ` +\n `${registers}.${unreadable} Declare it — ` +\n `\"contributes\": ${JSON.stringify(found)} — because the loaders place ` +\n 'a plugin by its declaration alone. Undeclared, a published page ' +\n 'loads it only where its element is placed and the console loads it ' +\n 'with the shell.',\n )\n return\n }\n\n details.set('contributions', `declares ${describeContributions(declared)}`)\n const missing = (\n label: string,\n registered: readonly string[] | undefined,\n listed: readonly string[] | undefined,\n consequence: string,\n ) => {\n for (const id of registered ?? []) {\n if ((listed ?? []).includes(id)) continue\n add(\n 'error',\n 'contributions',\n `register() registers ${label} \"${id}\", which the manifest does not ` +\n `declare — ${consequence}`,\n )\n }\n }\n missing(\n 'the site component',\n found.site?.components,\n declared.site?.components,\n 'a published page that places it would never load the plugin',\n )\n missing(\n 'the site feature',\n found.site?.features,\n declared.site?.features,\n 'no published page would ever load the plugin to run it',\n )\n missing(\n 'a widget in the console slot',\n found.console?.slots,\n declared.console?.slots,\n 'a screen that renders the slot would never load the plugin',\n )\n missing(\n 'the console route',\n found.console?.routes,\n declared.console?.routes,\n 'the route would not find the plugin that serves it',\n )\n missing(\n 'the console organization route',\n found.console?.orgRoutes,\n declared.console?.orgRoutes,\n 'the route would not find the plugin that serves it',\n )\n if (found.console?.shell && !declared.console?.shell) {\n add(\n 'error',\n 'contributions',\n 'register() adds a nav tab, a provider or another entry the console ' +\n 'shell draws on every screen, and the manifest does not declare ' +\n '\"console\": { \"shell\": true } — the shell would draw it only on ' +\n 'screens that happened to load the plugin for something else',\n )\n }\n if (detected.unresolved.length) {\n add(\n 'warning',\n 'contributions',\n `the checker could not read ${detected.unresolved.join('; ')} — ` +\n 'confirm the declaration covers what it registers',\n )\n }\n const unused = (\n label: string,\n listed: readonly string[] | undefined,\n registered: readonly string[] | undefined,\n ) => {\n // Only when every registration of that kind was readable: an unreadable\n // one may be exactly the entry that is declared.\n if (detected.unresolved.length) return\n for (const id of listed ?? []) {\n if ((registered ?? []).includes(id)) continue\n add(\n 'warning',\n 'contributions',\n `the manifest declares ${label} \"${id}\", and register() never ` +\n 'registers it — the plugin would load there for nothing',\n )\n }\n }\n unused('the site component', declared.site?.components, found.site?.components)\n unused('the site feature', declared.site?.features, found.site?.features)\n unused('the console slot', declared.console?.slots, found.console?.slots)\n}\n\nfunction analyseBundle(\n source: string,\n options?: CheckOptions,\n): BundleCheckResult {\n const problems: BundleCheckProblem[] = []\n const seen = new Set<string>()\n // Every area starts as \"ran, found nothing\" and is downgraded by its own\n // findings, so the summary cannot drift from the problem list (AGL-1087).\n const status = new Map<BundleCheckId, BundleCheckStatus>(\n CHECK_ORDER.map((id) => [id, 'pass']),\n )\n const details = new Map<BundleCheckId, string>()\n const add = (\n level: BundleCheckProblem['level'],\n check: BundleCheckId,\n message: string,\n ) => {\n if (level === 'error') status.set(check, 'fail')\n else if (status.get(check) === 'pass') status.set(check, 'question')\n if (seen.has(message) || seen.size >= MAX_PROBLEMS) return\n seen.add(message)\n problems.push({ level, message, check })\n }\n /** Mark the areas that never ran, so they cannot read as clean. */\n const unknownFrom = (...ids: BundleCheckId[]) => {\n for (const id of ids) status.set(id, 'unknown')\n }\n const summarise = (): BundleCheckSummary[] =>\n CHECK_ORDER.map((id) => ({\n id,\n label: CHECK_LABELS[id],\n status: status.get(id) ?? 'unknown',\n ...(details.has(id) ? { detail: details.get(id) } : {}),\n }))\n\n const maxBytes = options?.maxBytes ?? MAX_PLUGIN_BUNDLE_BYTES\n const bytes = new TextEncoder().encode(source).byteLength\n details.set('size', `${bytes.toLocaleString('en-US')} bytes of ${maxBytes.toLocaleString('en-US')}`)\n if (bytes === 0) {\n add('error', 'size', 'bundle is empty')\n unknownFrom(\n 'parse',\n 'entry',\n 'self-contained',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n )\n return {\n ok: false,\n problems,\n checks: summarise(),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n if (bytes > maxBytes) {\n add('error', 'size', `bundle is ${bytes} bytes (limit ${maxBytes})`)\n }\n\n const parseOptions: Options = {\n ecmaVersion: 'latest',\n sourceType: 'module',\n allowHashBang: true,\n allowAwaitOutsideFunction: true,\n }\n let program: AnyNode\n try {\n program = parse(source, parseOptions) as unknown as AnyNode\n } catch (error) {\n // Unparseable means unanalysable, and the loader would fail on it too.\n // Nothing else ran, and saying so is the whole point of `unknown`: a\n // bundle nobody could read must not show nine green rows.\n add(\n 'error',\n 'parse',\n `bundle does not parse as an ES module: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n unknownFrom(\n 'entry',\n 'self-contained',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n )\n return {\n ok: false,\n problems,\n checks: summarise(),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n\n // Names bound to a global, so `const g = globalThis; g['ev'+'al']` reads\n // as access on a global rather than on some object we know nothing about.\n const aliases = new Set<string>()\n const noteAlias = (name: unknown, init: AnyNode | null | undefined) => {\n if (typeof name !== 'string' || !init) return\n const roots: AnyNode[] =\n init.type === 'LogicalExpression'\n ? [init['left'] as AnyNode, init['right'] as AnyNode]\n : [init]\n for (const root of roots) {\n if (\n root?.type === 'Identifier' &&\n GLOBAL_ROOTS.has(root['name'] as string)\n ) {\n aliases.add(name)\n }\n }\n }\n // Local name -> the constant string it holds (AGL-1093).\n //\n // A binding is only usable when the whole tree agrees on it: declared once,\n // never assigned again, and never a parameter or another binder anywhere.\n // Any of those means the value at a given call site is a GUESS, and a guess\n // is how a checker starts making claims about code it did not read.\n const constants = new Map<string, string>()\n {\n const bound = new Map<string, AnyNode>()\n const poisoned = new Set<string>()\n const declared = new Set<string>()\n const poison = (name: unknown) => {\n if (typeof name === 'string') poisoned.add(name)\n }\n /** Every name a pattern binds — a parameter shadowing a constant is a\n * different variable, and resolving it to the outer value would be a\n * fabrication. */\n const poisonPattern = (node: AnyNode | null | undefined) => {\n if (!node) return\n if (node.type === 'Identifier') return poison(node['name'])\n for (const key of [\n 'left',\n 'argument',\n 'properties',\n 'elements',\n 'value',\n ]) {\n const value = node[key]\n if (Array.isArray(value))\n value.forEach((item) => poisonPattern(item as AnyNode))\n else if (value) poisonPattern(value as AnyNode)\n }\n }\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n const id = node['id'] as AnyNode\n if (id?.type === 'Identifier') {\n const name = id['name'] as string\n if (declared.has(name)) poison(name)\n declared.add(name)\n bound.set(name, node['init'] as AnyNode)\n } else {\n poisonPattern(id)\n }\n } else if (node.type === 'AssignmentExpression') {\n poisonPattern(node['left'] as AnyNode)\n } else if (node.type === 'UpdateExpression') {\n poisonPattern(node['argument'] as AnyNode)\n } else if (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n ) {\n ;((node['params'] ?? []) as AnyNode[]).forEach(poisonPattern)\n if ((node['id'] as AnyNode)?.type === 'Identifier')\n poison((node['id'] as AnyNode)['name'])\n } else if (node.type === 'CatchClause') {\n poisonPattern(node['param'] as AnyNode)\n } else if (\n node.type === 'ClassDeclaration' ||\n node.type === 'ClassExpression'\n ) {\n if ((node['id'] as AnyNode)?.type === 'Identifier')\n poison((node['id'] as AnyNode)['name'])\n }\n })\n // Two passes, so `const BASE = 'https://x'` then `BASE + '/y'` resolves.\n // A third buys almost nothing and each is a full re-read of the bindings.\n for (let pass = 0; pass < 2; pass += 1) {\n for (const [name, init] of bound) {\n if (poisoned.has(name) || constants.has(name)) continue\n const value = staticString(init, constants)\n if (value !== null) constants.set(name, value)\n }\n }\n }\n\n // Local name -> the callable it stands for (AGL-1090). `const f = fetch`,\n // `const d = globalThis.fetch`, `const {fetch: h} = globalThis`.\n const callableAliases = new Map<string, string>()\n /** The aliasable callable an initializer denotes, if any. */\n const callableFrom = (init: AnyNode | null | undefined): string | null => {\n if (!init) return null\n if (\n init.type === 'Identifier' &&\n ALIASABLE_CALLABLES.has(init['name'] as string)\n ) {\n return init['name'] as string\n }\n // `globalThis.fetch` / `window['fe' + 'tch']` — the property is what the\n // name will stand for, and only when the object is a global (an unknown\n // object's `.fetch` is somebody else's method).\n if (init.type === 'MemberExpression') {\n const property = propertyName(init)\n const object = init['object'] as AnyNode\n if (\n property &&\n ALIASABLE_CALLABLES.has(property) &&\n object?.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(object['name'] as string) ||\n aliases.has(object['name'] as string))\n ) {\n return property\n }\n }\n return null\n }\n const noteCallableAlias = (\n id: AnyNode | null | undefined,\n init: AnyNode | null | undefined,\n ) => {\n if (!id) return\n if (id.type === 'Identifier') {\n const callable = callableFrom(init)\n if (callable) callableAliases.set(id['name'] as string, callable)\n return\n }\n // `const { fetch } = globalThis` and `const { fetch: h } = globalThis`.\n if (id.type === 'ObjectPattern' && init) {\n const fromGlobal =\n init.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(init['name'] as string) ||\n aliases.has(init['name'] as string))\n if (!fromGlobal) return\n for (const property of (id['properties'] ?? []) as AnyNode[]) {\n const key = property['key'] as AnyNode\n const value = property['value'] as AnyNode\n const name =\n key?.type === 'Identifier' ? (key['name'] as string) : staticString(key)\n if (\n name &&\n ALIASABLE_CALLABLES.has(name) &&\n value?.type === 'Identifier'\n ) {\n callableAliases.set(value['name'] as string, name)\n }\n }\n }\n }\n\n // Global-object aliases first, as a complete pass: the callable pass below\n // resolves `g.fetch` against them, and walk order is not source order.\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n const id = node['id'] as AnyNode\n if (id?.type === 'Identifier')\n noteAlias(id['name'], node['init'] as AnyNode)\n } else if (\n node.type === 'AssignmentExpression' &&\n node['operator'] === '='\n ) {\n const left = node['left'] as AnyNode\n if (left?.type === 'Identifier')\n noteAlias(left['name'], node['right'] as AnyNode)\n }\n })\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n noteCallableAlias(node['id'] as AnyNode, node['init'] as AnyNode)\n } else if (\n node.type === 'AssignmentExpression' &&\n node['operator'] === '='\n ) {\n noteCallableAlias(node['left'] as AnyNode, node['right'] as AnyNode)\n }\n })\n\n const isGlobalRef = (node: AnyNode | null | undefined): boolean =>\n !!node &&\n node.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(node['name'] as string) ||\n aliases.has(node['name'] as string))\n\n const declared = options?.declaredNetwork\n const allowlist = new Set(\n (declared ?? []).map((origin) => {\n try {\n return new URL(origin).origin\n } catch {\n return origin\n }\n }),\n )\n /** Level for a network finding: a claim only when we know what was declared. */\n const networkLevel: BundleCheckProblem['level'] = declared\n ? 'error'\n : 'warning'\n // What the bundle reaches for, so the summary row can SAY it (AGL-1087) —\n // \"calls https://api.stripe.com, declared\" is the line a reviewer would\n // otherwise open the bundle to get.\n const networkApis = new Set<string>()\n const networkOrigins = new Set<string>()\n let runtimeUrls = 0\n /** Origins handed to a callee the checker could not resolve (AGL-1090). */\n const unresolvedOrigins = new Set<string>()\n const noteNetwork = (api: string, urlArg: AnyNode | undefined) => {\n const origin = literalOrigin(urlArg, constants)\n networkApis.add(api)\n if (origin) networkOrigins.add(origin)\n else runtimeUrls += 1\n if (!declared) {\n add(\n 'warning',\n 'network',\n `bundle makes network calls (${api}) — check them against the ` +\n \"manifest's declared network capability\",\n )\n return\n }\n if (!allowlist.size) {\n add(\n networkLevel,\n 'network',\n `bundle calls ${api} but the manifest declares no network ` +\n 'capability — declare every origin under capabilities.network ' +\n '(the CSP blocks the rest at runtime)',\n )\n return\n }\n if (!origin) {\n add(\n 'warning',\n 'network',\n `${api} is called with a URL that is only known at runtime — it ` +\n 'cannot be checked against the declared origins',\n )\n return\n }\n if (!allowlist.has(origin)) {\n add(\n networkLevel,\n 'network',\n `${api} calls ${origin}, which the manifest does not declare ` +\n `(declared: ${[...allowlist].join(', ')})`,\n )\n }\n }\n\n /**\n * A call the checker could not resolve, given an absolute URL.\n *\n * Reported as a QUESTION, never a refusal: most of these are helpers, and\n * refusing every unrecognised call that mentions a URL would make the\n * verifier useless. But leaving them silent is what let the network row\n * claim \"no network calls\" over a bundle whose calls it simply could not\n * follow — and a URL to an origin the manifest never declared is exactly\n * what a reviewer should be pointed at.\n */\n const noteUnresolvedUrl = (\n name: string | null,\n urlArg: AnyNode | undefined,\n ) => {\n if (name && URL_TAKING_NON_CALLS.has(name)) return\n const origin = literalOrigin(urlArg, constants)\n if (!origin) return\n // A declared origin is consistent with the manifest and permitted by the\n // CSP, so there is no question to raise about it.\n if (allowlist.has(origin)) return\n unresolvedOrigins.add(origin)\n add(\n 'warning',\n 'network',\n `${name ? `${name}()` : 'a call'} is passed ${origin}, which the ` +\n 'manifest does not declare — the checker could not tell whether it ' +\n 'reaches the network, so read this call',\n )\n }\n\n let exportsRegister = false\n let exportsRegisterApi = false\n const noteExport = (name: unknown) => {\n if (name === 'register') exportsRegister = true\n else if (name === 'registerApi') exportsRegisterApi = true\n }\n\n let obfuscatedNames = 0\n\n walk(program, (node) => {\n switch (node.type) {\n // ---- entry exports + self-containment ----\n case 'ImportDeclaration':\n add(\n 'error',\n 'self-contained',\n 'bundle has static imports — realm bundles must be self-contained ' +\n '(react/@aglyn/aglyn come from the host ABI; see the realm rollup ' +\n 'template)',\n )\n break\n case 'ExportAllDeclaration':\n case 'ExportNamedDeclaration': {\n if (node['source']) {\n add(\n 'error',\n 'self-contained',\n 'bundle re-exports from another module — realm bundles must be ' +\n 'self-contained (see the realm rollup template)',\n )\n }\n const declaration = node['declaration'] as AnyNode | null\n if (declaration) {\n if (declaration['id'])\n noteExport((declaration['id'] as AnyNode)['name'])\n for (const declarator of (declaration['declarations'] ??\n []) as AnyNode[]) {\n const id = declarator['id'] as AnyNode\n if (id?.type === 'Identifier') noteExport(id['name'])\n }\n }\n for (const specifier of (node['specifiers'] ?? []) as AnyNode[]) {\n const exported = specifier['exported'] as AnyNode\n noteExport(\n exported?.type === 'Identifier'\n ? exported['name']\n : staticString(exported),\n )\n }\n break\n }\n\n // ---- dynamic import ----\n case 'ImportExpression': {\n const specifier = staticString(node['source'] as AnyNode, constants)\n if (specifier === null) {\n add(\n 'error',\n 'dynamic-import',\n 'dynamic import with a specifier computed at runtime is not ' +\n 'allowed — the loader cannot know what would be fetched',\n )\n } else if (/^https?:/i.test(specifier)) {\n add(\n 'error',\n 'dynamic-import',\n 'dynamic import of remote URLs is not allowed',\n )\n }\n break\n }\n\n // ---- calls ----\n case 'CallExpression':\n case 'NewExpression': {\n const callee = node['callee'] as AnyNode\n const args = (node['arguments'] ?? []) as AnyNode[]\n const written =\n callee?.type === 'Identifier'\n ? (callee['name'] as string)\n : callee?.type === 'MemberExpression'\n ? propertyName(callee, constants)\n : null\n // An alias resolves to what it stands for (AGL-1090), so `f(url)`\n // after `const f = fetch` is a fetch call.\n const name =\n (written && callableAliases.get(written)) ?? written ?? null\n\n if (name === 'eval')\n add('error', 'code-execution', 'eval() is not allowed')\n if (name === 'Function')\n add(\n 'error',\n 'code-execution',\n 'the Function constructor is not allowed',\n )\n // `(()=>{}).constructor('return 1')()` — the Function constructor\n // reached through any function value, which is why the CALL is the\n // finding rather than the name `Function`.\n if (name === 'constructor' && callee?.type === 'MemberExpression')\n add(\n 'error',\n 'code-execution',\n 'calling .constructor() is not allowed — it reaches the ' +\n 'Function constructor from any function value',\n )\n if (name && NETWORK_CALLS.has(name)) noteNetwork(name, args[0])\n else if (name && NETWORK_CONSTRUCTORS.has(name))\n noteNetwork(name, node.type === 'NewExpression' ? args[0] : args[1])\n // Something we could not follow, handed an absolute URL (AGL-1090).\n // Not a refusal — it may be a helper, a logger, or a link — but the\n // network row must stop rendering \"no network calls\" over it.\n else noteUnresolvedUrl(name, args[0])\n break\n }\n\n // ---- member access ----\n case 'MemberExpression': {\n const object = node['object'] as AnyNode\n const name = propertyName(node, constants)\n if (isGlobalRef(object) && name === null) {\n add(\n 'error',\n 'globals',\n `computed property access on ${\n (object['name'] as string) || 'a global'\n } is not allowed — a property chosen at runtime cannot be ` +\n 'reviewed',\n )\n break\n }\n // Property names only mean anything on a global: `response.cookie`\n // and `state.localStorage` are somebody else's object.\n if (name === 'cookie' && isGlobalRef(object))\n add('error', 'storage', 'document.cookie access is not allowed')\n if (name && STORAGE_NAMES.has(name) && isGlobalRef(object))\n add(\n 'error',\n 'storage',\n 'browser storage access is not allowed (use host-mediated data)',\n )\n if (name === 'eval' && isGlobalRef(object))\n add('error', 'code-execution', 'eval() is not allowed')\n break\n }\n\n // ---- bare references ----\n case 'Identifier': {\n const name = node['name'] as string\n if (STORAGE_NAMES.has(name))\n add(\n 'error',\n 'storage',\n 'browser storage access is not allowed (use host-mediated data)',\n )\n if (OBFUSCATED_NAME.test(name)) obfuscatedNames += 1\n break\n }\n\n // ---- obfuscation ----\n case 'Literal': {\n const value = node['value']\n if (\n typeof value === 'string' &&\n value.length >= MIN_SUSPICIOUS_BASE64 &&\n BASE64_BLOB.test(value)\n ) {\n add(\n 'warning',\n 'obfuscation',\n `bundle embeds a ${value.length}-character base64 literal — ` +\n 'decode it before approving',\n )\n }\n break\n }\n default:\n break\n }\n })\n\n if (!exportsRegister && !exportsRegisterApi) {\n add(\n 'error',\n 'entry',\n 'bundle exports neither register(host) nor registerApi() — ' +\n 'nothing for the loader to call',\n )\n }\n\n if (obfuscatedNames >= MIN_OBFUSCATED_NAMES) {\n add(\n 'warning',\n 'obfuscation',\n `bundle uses ${obfuscatedNames} machine-obfuscated identifiers ` +\n '(_0x… naming) — minifiers do not produce these',\n )\n }\n const longestLine = source\n .split('\\n')\n .reduce((longest, line) => Math.max(longest, line.length), 0)\n if (longestLine > MAX_READABLE_LINE) {\n add(\n 'warning',\n 'obfuscation',\n `bundle has a single ${longestLine}-character line — nothing on it ` +\n 'can be read in review; ask for a readable build or a source map',\n )\n }\n\n details.set(\n 'entry',\n [\n exportsRegister ? 'register(host)' : null,\n exportsRegisterApi ? 'registerApi()' : null,\n ]\n .filter(Boolean)\n .join(', ') || 'none',\n )\n\n // What the network check actually saw. Without the manifest it saw the\n // calls but could not judge them, and that is `unknown`, not a pass — the\n // row a reviewer must not read as \"checked and fine\".\n if (!declared) {\n if (networkApis.size) {\n status.set('network', 'unknown')\n details.set(\n 'network',\n `calls ${[...networkApis].join(', ')} — no declared origins were ` +\n 'supplied, so nothing was compared',\n )\n } else {\n details.set('network', 'no network calls')\n }\n } else if (!networkApis.size) {\n details.set(\n 'network',\n unresolvedOrigins.size\n ? `no resolved network calls, but ${[...unresolvedOrigins].join(', ')} ` +\n 'is passed to a call the checker could not follow'\n : 'no network calls',\n )\n } else {\n const origins = networkOrigins.size\n ? [...networkOrigins]\n .map((origin) =>\n allowlist.has(origin) ? `${origin} (declared)` : `${origin} (NOT declared)`,\n )\n .join(', ')\n : ''\n details.set(\n 'network',\n [\n `calls ${[...networkApis].join(', ')}`,\n origins,\n runtimeUrls\n ? `${runtimeUrls} call(s) with a URL only known at runtime`\n : '',\n ]\n .filter(Boolean)\n .join(' · '),\n )\n }\n\n const detected = detectRegistrations(program, constants)\n compareContributions(detected, options, add, details, (check) =>\n status.set(check, 'unknown'),\n )\n\n return {\n ok: !problems.some((problem) => problem.level === 'error'),\n problems,\n checks: summarise(),\n exports: { register: exportsRegister, registerApi: exportsRegisterApi },\n contributions: detected,\n }\n}\n\n/**\n * Run the checks over a bundle. NEVER throws (AGL-1094).\n *\n * The publish route calls this directly, so an unexpected failure in here\n * used to mean a 500 and no explanation for the publisher — and in the review\n * sweep it looked exactly like a missing artifact, which sent the\n * investigation to the storage bucket instead of to this file. A checker that\n * cannot analyse a bundle has an answer: say so, in the verdict, as an error.\n */\nexport function checkPluginBundle(\n source: string,\n options?: CheckOptions,\n): BundleCheckResult {\n try {\n return analyseBundle(source, options)\n } catch (error) {\n return {\n ok: false,\n problems: [\n {\n level: 'error',\n check: 'parse',\n message:\n 'the verifier failed on this bundle — treat it as unchecked and ' +\n `report it: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n checks: CHECK_ORDER.map((id) => ({\n id,\n label: CHECK_LABELS[id],\n status: id === 'parse' ? 'fail' : 'unknown',\n })),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n}\n"],"names":["parse","CHECK_LABELS","entry","size","globals","storage","network","obfuscation","contributions","CHECK_ORDER","MAX_PLUGIN_BUNDLE_BYTES","PLUGIN_VERIFIER_VERSION","isStoredVerdictCurrent","stored","sha256","Number","verifierVersion","GLOBAL_ROOTS","Set","STORAGE_NAMES","NETWORK_CALLS","NETWORK_CONSTRUCTORS","ALIASABLE_CALLABLES","URL_TAKING_NON_CALLS","MAX_READABLE_LINE","MIN_SUSPICIOUS_BASE64","MIN_OBFUSCATED_NAMES","MAX_PROBLEMS","OBFUSCATED_NAME","BASE64_BLOB","skippedChild","node","key","type","walk","root","visit","stack","length","pop","Object","keys","value","Array","isArray","item","push","staticString","constants","get","expressions","quasis","cooked","out","index","filled","left","right","propertyName","property","literalOrigin","url","URL","test","protocol","origin","NOTHING_DETECTED","contributes","unresolved","REGISTRATION_CALLS","SHELL_EXTENSION_FIELDS","detectRegistrations","program","slots","routes","orgRoutes","components","features","shell","objectProperty","object","name","undefined","keyName","listOf","slotName","literal","callee","has","args","extension","add","widgets","list","widget","slot","field","into","href","id","site","sort","console","describeContributions","parts","join","compareContributions","detected","options","details","markUnknown","found","declared","registers","registersSomething","Boolean","unreadable","declaredContributions","set","requireContributions","JSON","stringify","missing","label","registered","listed","consequence","includes","unused","analyseBundle","source","problems","seen","status","Map","map","level","check","message","unknownFrom","ids","summarise","detail","maxBytes","bytes","TextEncoder","encode","byteLength","toLocaleString","ok","checks","exports","register","registerApi","parseOptions","ecmaVersion","sourceType","allowHashBang","allowAwaitOutsideFunction","error","Error","String","aliases","noteAlias","init","roots","bound","poisoned","poison","poisonPattern","forEach","pass","callableAliases","callableFrom","noteCallableAlias","callable","fromGlobal","isGlobalRef","declaredNetwork","allowlist","networkLevel","networkApis","networkOrigins","runtimeUrls","unresolvedOrigins","noteNetwork","api","urlArg","noteUnresolvedUrl","exportsRegister","exportsRegisterApi","noteExport","obfuscatedNames","declaration","declarator","specifier","exported","written","longestLine","split","reduce","longest","line","Math","max","filter","origins","some","problem","checkPluginBundle"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plugin-bundle-checks.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\n/**\n * Static plugin-bundle checks (AGL-426) — Strapi `strapi-plugin verify`\n * parity, but ALSO enforced server-side: the publish API runs the same\n * checks, so a bundle that fails locally fails there identically.\n *\n * The bundle is PARSED and the tree analysed (AGL-964). The checks used to\n * match source text with regexes, which meant they could not see computed\n * member access, could not follow a value into a call, and could not tell\n * code from a comment — `g['ev'+'al'](…)` passed while the words `eval(`\n * inside a comment failed. On a minified bundle, which every published\n * artifact is, that is close to no signal at all.\n *\n * These are still cheap heuristics, NOT a sandbox: they catch honest build\n * mistakes (leftover imports, missing entry exports, oversized bundles),\n * flag the APIs the platform never wants in marketplace code, and surface\n * the questions a reviewer should ask. A determined author can still hide\n * intent from any static pass. The real security boundary stays the trust\n * chain (sandbox iframe + per-manifest CSP / staff review + realm signing /\n * sha pinning / the kill switch).\n */\n\nimport { parse, type Node, type Options } from 'acorn'\n// The leaf, not the barrel: the phishing screen's own reading of an input's\n// type, autocomplete token and raw HTML (AGL-3362).\nimport {\n credentialForInputAttribute,\n credentialInHtml,\n} from '@aglyn/shared-util-email/hosted-page-screen'\nimport type { PluginContributions } from '../plugin-manager/plugin-contributions'\n\n/** The areas the verifier reports on, in the order a reviewer reads them. */\nexport type BundleCheckId =\n | 'parse'\n | 'entry'\n | 'self-contained'\n | 'size'\n | 'code-execution'\n | 'globals'\n | 'storage'\n | 'dynamic-import'\n | 'network'\n | 'obfuscation'\n | 'contributions'\n | 'credentials'\n\nexport interface BundleCheckProblem {\n level: 'error' | 'warning'\n message: string\n /**\n * Which check produced this. Optional so a verdict stored by an older\n * checker still reads — those findings render on their own rather than\n * under a check row.\n */\n check?: BundleCheckId\n}\n\n/**\n * `unknown` is the one that earns its keep (AGL-1087). A check that could\n * not run — the network diff with no manifest supplied, everything at all\n * when the parse failed — must not render as green, or the page tells a\n * reviewer something was verified when nothing looked at it.\n */\nexport type BundleCheckStatus = 'pass' | 'fail' | 'question' | 'unknown'\n\nexport interface BundleCheckSummary {\n id: BundleCheckId\n /** Reviewer-facing name of the area. */\n label: string\n status: BundleCheckStatus\n /** What the check actually saw, when that is worth stating on its own. */\n detail?: string\n}\n\nexport interface BundleCheckResult {\n ok: boolean\n problems: BundleCheckProblem[]\n /** Every area, including the ones that found nothing (AGL-1087). */\n checks: BundleCheckSummary[]\n /** Which entry exports the source declares. */\n exports: { register: boolean; registerApi: boolean }\n /** What `register()` visibly registers (AGL-3116). */\n contributions: DetectedContributions\n}\n\n/**\n * What a bundle's `register()` visibly registers (AGL-3116): the declaration\n * its registration calls imply, read from the tree.\n */\nexport interface DetectedContributions {\n contributes: PluginContributions\n /**\n * Registrations the checker saw and could not read — an argument built at\n * runtime. What they contribute is unknown, so a declaration cannot be\n * checked against them and a reviewer has to read them.\n */\n unresolved: string[]\n}\n\nconst CHECK_LABELS: Record<BundleCheckId, string> = {\n parse: 'Parses as an ES module',\n entry: 'Exports an entry point',\n 'self-contained': 'Self-contained (no static imports)',\n size: 'Within the size limit',\n 'code-execution': 'No eval / Function constructor',\n globals: 'No computed access on a global',\n storage: 'No direct storage or cookie access',\n 'dynamic-import': 'No dynamic import of unknown code',\n network: 'Network calls match the manifest',\n obfuscation: 'No obfuscation shapes',\n contributions: 'Registrations match the declared contributions',\n credentials: 'No password, card or one-time-code inputs',\n}\n\n/** Check order as displayed — findings first would reorder on every bundle. */\nconst CHECK_ORDER: BundleCheckId[] = [\n 'parse',\n 'entry',\n 'self-contained',\n 'size',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n 'credentials',\n]\n\nexport const MAX_PLUGIN_BUNDLE_BYTES = 1_000_000\n\n/**\n * Stamped onto a cached verdict (AGL-962) so a stored result outlives only\n * the checker that produced it.\n *\n * `checkPluginBundle` is pure over immutable, content-addressed bytes, so a\n * verdict for a given sha256 never changes — but the CHECKS do. **Bump this\n * whenever a rule is added, removed or loosened**, and every stored verdict\n * from the old checker is ignored and recomputed on next read. Forgetting to\n * bump means a reviewer sees a verdict from a checker that no longer exists,\n * which is the failure mode re-running the verifier was meant to prevent.\n *\n * 2 — AST analysis replaces the regex scan (AGL-964).\n * 3 — per-check summary added to the verdict (AGL-1087).\n * 4 — calls through an alias are resolved (AGL-1090).\n * 5 — URLs held in a constant are resolved (AGL-1093).\n * 6 — registrations are compared with the declared contributions (AGL-3116).\n * 7 — inputs that collect a password, card or one-time code (AGL-3362).\n */\nexport const PLUGIN_VERIFIER_VERSION = 7\n\n/** A verdict as stored on a `pluginVersions` doc (AGL-962). */\nexport interface StoredBundleVerdict {\n ok?: boolean\n problems?: BundleCheckProblem[]\n /** Present from verifier 3 on (AGL-1087). */\n checks?: BundleCheckSummary[]\n sha256?: string\n verifierVersion?: number\n}\n\n/**\n * Whether a stored verdict may be served instead of re-running the checks.\n *\n * Fails closed on every mismatch: no verdict, a verdict for different bytes\n * (a republished version keeps its version string but changes sha), or a\n * verdict from a checker that has since changed. Any of those means\n * download-and-recompute, which costs one page view rather than showing a\n * reviewer a verdict that was never true of the bundle in front of them.\n */\nexport function isStoredVerdictCurrent(\n stored: StoredBundleVerdict | null | undefined,\n sha256: string,\n): boolean {\n if (!stored || !sha256) return false\n return (\n stored.sha256 === sha256 &&\n Number(stored.verifierVersion) === PLUGIN_VERIFIER_VERSION\n )\n}\n\n/**\n * Globals whose properties are the platform's business. Computed access on\n * one of these is flagged as computed access rather than by trying to\n * enumerate the property names an author might reach — `g[k]` hides its\n * property from any static pass, so the ACCESS is the finding.\n */\nconst GLOBAL_ROOTS = new Set([\n 'globalThis',\n 'window',\n 'self',\n 'top',\n 'parent',\n 'frames',\n 'document',\n])\n\n/** Storage the platform never wants reached directly (use host-mediated data). */\nconst STORAGE_NAMES = new Set(['localStorage', 'sessionStorage', 'indexedDB'])\n\n/** Network entry points, by the name they are called/constructed under. */\nconst NETWORK_CALLS = new Set(['fetch', 'sendBeacon'])\nconst NETWORK_CONSTRUCTORS = new Set([\n 'XMLHttpRequest',\n 'WebSocket',\n 'EventSource',\n])\n\n/**\n * Names worth following through an alias (AGL-1090).\n *\n * `const f = fetch; f(url)` used to pass every check, and the network row\n * rendered a green \"no network calls\" rather than going quiet — a two-line\n * indirection defeating the signal this whole verifier exists to produce.\n * Aliasing a VALUE was the exact gap the project was created to close, and\n * resolving global objects (`const g = globalThis`) never covered it.\n *\n * Reassignment, `arr[0]`, an object property and passing the function as an\n * argument all stay out of reach. The bar is that the cheap form is not free.\n */\nconst ALIASABLE_CALLABLES = new Set([\n 'eval',\n 'Function',\n 'fetch',\n 'sendBeacon',\n 'XMLHttpRequest',\n 'WebSocket',\n 'EventSource',\n])\n\n/**\n * Callees that legitimately take an absolute URL and reach nothing — so a URL\n * literal passed to them is not a call the network check failed to follow.\n * `createElementNS` matters most: an SVG namespace argument appears in a large\n * share of real bundles.\n */\nconst URL_TAKING_NON_CALLS = new Set([\n 'URL',\n 'URLSearchParams',\n 'createElementNS',\n 'createDocument',\n])\n\n/** Beyond this, a single line is not source a reviewer can read. */\nconst MAX_READABLE_LINE = 100_000\n/** A base64 blob at least this long is worth decoding before approving. */\nconst MIN_SUSPICIOUS_BASE64 = 1024\n/** How many `_0x…` names before the bundle counts as machine-obfuscated. */\nconst MIN_OBFUSCATED_NAMES = 5\n/** Verdicts are stored on a version doc; keep them bounded. */\nconst MAX_PROBLEMS = 40\n\nconst OBFUSCATED_NAME = /^_0x[0-9a-f]{4,}$/i\nconst BASE64_BLOB = /^[A-Za-z0-9+/\\r\\n]+={0,2}$/\n\ntype AnyNode = Node & Record<string, unknown>\n\n/**\n * Child keys that are NOT expressions in the surrounding scope — a\n * non-computed `.property`, an object literal key, a label. Walking into\n * them would read `x.localStorage` on an unrelated object as a global\n * storage access.\n */\nconst skippedChild = (node: AnyNode, key: string): boolean => {\n switch (node.type) {\n case 'MemberExpression':\n return key === 'property' && !node['computed']\n case 'Property':\n case 'PropertyDefinition':\n case 'MethodDefinition':\n return key === 'key' && !node['computed']\n case 'LabeledStatement':\n case 'BreakStatement':\n case 'ContinueStatement':\n return key === 'label'\n case 'ImportSpecifier':\n case 'ExportSpecifier':\n return true\n default:\n return false\n }\n}\n\n/** Depth-first walk over every expression-position node in the tree. */\nfunction walk(root: AnyNode, visit: (node: AnyNode) => void): void {\n const stack: AnyNode[] = [root]\n while (stack.length) {\n const node = stack.pop()\n if (!node) continue\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'type' || key === 'start' || key === 'end') continue\n if (skippedChild(node, key)) continue\n const value = node[key]\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && typeof item.type === 'string')\n stack.push(item as AnyNode)\n }\n } else if (\n value &&\n typeof value === 'object' &&\n typeof (value as AnyNode).type === 'string'\n ) {\n stack.push(value as AnyNode)\n }\n }\n }\n}\n\n/**\n * The string a node evaluates to, when that is knowable without running it.\n * Covers the shapes used to hide a property name from a text scan: a\n * string literal, a template with no interpolation, and literals added\n * together (`'ev' + 'al'`).\n */\nfunction staticString(\n node: AnyNode | null | undefined,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n if (!node) return null\n if (node.type === 'Literal')\n return typeof node['value'] === 'string' ? (node['value'] as string) : null\n // A name bound once to a constant string (AGL-1093). `const ENDPOINT =\n // 'https://…'; fetch(ENDPOINT)` is the idiomatic way to write a request,\n // and refusing to read it meant the network diff quietly did not run for\n // the bundles most likely to be honest.\n if (node.type === 'Identifier' && constants)\n return constants.get(node['name'] as string) ?? null\n if (node.type === 'TemplateLiteral') {\n const expressions = (node['expressions'] ?? []) as AnyNode[]\n const quasis = (node['quasis'] ?? []) as Array<{\n value: { cooked?: string }\n }>\n if (!expressions.length) {\n return quasis.length === 1 ? quasis[0]?.value?.cooked ?? null : null\n }\n // `${BASE}/zen` — resolvable exactly when every hole is.\n let out = ''\n for (let index = 0; index < quasis.length; index += 1) {\n out += quasis[index]?.value?.cooked ?? ''\n if (index < expressions.length) {\n const filled = staticString(expressions[index], constants)\n if (filled === null) return null\n out += filled\n }\n }\n return out\n }\n if (node.type === 'BinaryExpression' && node['operator'] === '+') {\n const left = staticString(node['left'] as AnyNode, constants)\n const right = staticString(node['right'] as AnyNode, constants)\n return left !== null && right !== null ? left + right : null\n }\n return null\n}\n\n/** The property name a member expression reads, computed or not. */\nfunction propertyName(\n node: AnyNode,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n const property = node['property'] as AnyNode\n if (!node['computed'])\n return property?.type === 'Identifier'\n ? (property['name'] as string)\n : null\n return staticString(property, constants)\n}\n\n/**\n * The origin of a URL argument, when it is a knowable http(s) URL.\n *\n * Scheme-checked HERE, once (AGL-1094). An opaque origin — every `data:`,\n * `blob:` and `javascript:` URL — makes `new URL(x).origin` the STRING\n * `\"null\"`, and a caller that then re-parsed that string threw `Invalid URL`\n * and took the whole checker down with it. Nothing downstream should have to\n * know that, so nothing downstream gets the chance.\n */\nfunction literalOrigin(\n node: AnyNode | undefined,\n constants?: ReadonlyMap<string, string>,\n): string | null {\n const value = staticString(node, constants)\n if (!value) return null\n try {\n const url = new URL(value)\n return /^https?:$/.test(url.protocol) ? url.origin : null\n } catch {\n return null\n }\n}\n\ninterface CheckOptions {\n maxBytes?: number\n /**\n * The origins the manifest declares under `capabilities.network`. A\n * network call the manifest does not account for is the single most\n * useful signal the tree makes available — and the per-manifest CSP\n * would block it at runtime anyway, so the bundle and its manifest\n * disagreeing is a bug the publisher wants to hear about at publish\n * time, not a mystery in production. Undefined = \"not supplied\", which\n * downgrades those findings to warnings, because a checker that was\n * never told what was declared cannot claim anything was undeclared.\n */\n declaredNetwork?: string[]\n /**\n * The manifest's `contributes` block (AGL-3116): `null` when the manifest\n * declares none, `undefined` when no manifest was supplied — which leaves\n * the comparison `unknown`, as the network diff is without its manifest.\n */\n declaredContributions?: PluginContributions | null\n /**\n * Refuse a bundle that registers something while its manifest declares no\n * contributions at all. Set where a NEW version is judged — the publish\n * pipeline and the local verifier that mirrors it. The review page and the\n * re-verification sweep judge versions published before the contract,\n * which load under its default, and a rule they were never held to must\n * not turn them red after the fact.\n */\n requireContributions?: boolean\n}\n\n/** The detection result of a bundle no pass could read. */\nconst NOTHING_DETECTED: DetectedContributions = { contributes: {}, unresolved: [] }\n\n/**\n * Registration calls whose argument says what a bundle contributes, by the\n * name they are reached through: `h.aglyn.registerConsoleExtension(...)`,\n * `registerConsoleExtension(...)` after the realm template's host mapping,\n * `aglyn.components.registerComponent(...)`.\n */\nconst REGISTRATION_CALLS = new Set([\n 'registerConsoleExtension',\n 'registerSiteRuntime',\n 'registerComponent',\n 'addDependency',\n 'defineUiFeatureBundle',\n])\n\n/**\n * Console extension fields the shell draws on every screen of a workspace:\n * nav tabs, organization tabs, providers, staff pages and the dashboard and\n * settings entries. A plugin registering any of them loads with the shell.\n */\nconst SHELL_EXTENSION_FIELDS = [\n 'navItems',\n 'orgNavItems',\n 'providers',\n 'staffPages',\n 'dashboardCards',\n 'settingsSections',\n]\n\n/**\n * What `register()` visibly registers (AGL-3116), read from the tree.\n *\n * A heuristic like every check here: it reads registration calls whose\n * arguments are written out — an object literal, a string or a named slot —\n * and reports every call it cannot read as `unresolved` rather than guessing.\n * The declaration stays the contract; this is how a publisher learns theirs\n * is incomplete before a published page fails to load the plugin.\n */\nfunction detectRegistrations(\n program: AnyNode,\n constants: ReadonlyMap<string, string>,\n): DetectedContributions {\n const slots = new Set<string>()\n const routes = new Set<string>()\n const orgRoutes = new Set<string>()\n const components = new Set<string>()\n const features = new Set<string>()\n const unresolved = new Set<string>()\n let shell = false\n\n const objectProperty = (\n object: AnyNode | undefined,\n name: string,\n ): AnyNode | undefined => {\n if (object?.type !== 'ObjectExpression') return undefined\n for (const property of (object['properties'] ?? []) as AnyNode[]) {\n if (property.type !== 'Property') continue\n const key = property['key'] as AnyNode\n const keyName =\n !property['computed'] && key?.type === 'Identifier'\n ? (key['name'] as string)\n : staticString(key, constants)\n if (keyName === name) return property['value'] as AnyNode\n }\n return undefined\n }\n const listOf = (node: AnyNode | undefined): AnyNode[] | null =>\n node?.type === 'ArrayExpression' ? ((node['elements'] ?? []) as AnyNode[]) : null\n // A slot as a widget names it: a string, or a catalog entry\n // (`CONSOLE_WIDGET_SLOTS.hostActivity`), whose property IS the slot id.\n const slotName = (node: AnyNode | undefined): string | null => {\n const literal = staticString(node, constants)\n if (literal !== null) return literal\n return node?.type === 'MemberExpression' ? propertyName(node, constants) : null\n }\n\n walk(program, (node) => {\n if (node.type !== 'CallExpression') return\n const callee = node['callee'] as AnyNode\n const name =\n callee?.type === 'Identifier'\n ? (callee['name'] as string)\n : callee?.type === 'MemberExpression'\n ? propertyName(callee, constants)\n : null\n if (!name || !REGISTRATION_CALLS.has(name)) return\n const args = (node['arguments'] ?? []) as AnyNode[]\n if (name === 'registerConsoleExtension') {\n const extension = args[0]\n if (extension?.type !== 'ObjectExpression') {\n unresolved.add('a console extension built at runtime')\n return\n }\n const widgets = objectProperty(extension, 'widgets')\n if (widgets) {\n const list = listOf(widgets)\n if (!list) unresolved.add('a console widget list built at runtime')\n for (const widget of list ?? []) {\n const slot = slotName(objectProperty(widget, 'slot'))\n if (slot) slots.add(slot)\n else unresolved.add('a console widget whose slot is built at runtime')\n }\n }\n for (const field of SHELL_EXTENSION_FIELDS) {\n const value = objectProperty(extension, field)\n if (!value) continue\n shell = true\n if (field !== 'navItems' && field !== 'orgNavItems') continue\n const into = field === 'navItems' ? routes : orgRoutes\n const list = listOf(value)\n if (!list) unresolved.add(`console ${field} built at runtime`)\n for (const item of list ?? []) {\n const href = staticString(objectProperty(item, 'href'), constants)\n if (href) into.add(href)\n else unresolved.add(`a console ${field} entry whose href is built at runtime`)\n }\n }\n return\n }\n if (name === 'registerSiteRuntime') {\n const id = staticString(objectProperty(args[0], 'runtimeId'), constants)\n if (id) features.add(id)\n else unresolved.add('a site runtime whose runtimeId is built at runtime')\n return\n }\n if (name === 'registerComponent') {\n const id = staticString(objectProperty(args[1], '$id'), constants)\n if (id) components.add(id)\n else unresolved.add('a canvas component whose $id is built at runtime')\n return\n }\n // `addDependency` / `defineUiFeatureBundle`: a component bundle whose\n // entries register later, from a list this pass does not follow.\n unresolved.add('a canvas component bundle')\n })\n\n const contributes: PluginContributions = {}\n if (components.size || features.size) {\n contributes.site = {\n ...(components.size ? { components: [...components].sort() } : {}),\n ...(features.size ? { features: [...features].sort() } : {}),\n }\n }\n if (slots.size || routes.size || orgRoutes.size || shell) {\n contributes.console = {\n ...(slots.size ? { slots: [...slots].sort() } : {}),\n ...(routes.size ? { routes: [...routes].sort() } : {}),\n ...(orgRoutes.size ? { orgRoutes: [...orgRoutes].sort() } : {}),\n ...(shell ? { shell: true } : {}),\n }\n }\n return { contributes, unresolved: [...unresolved].sort() }\n}\n\n/** A declaration in the words a reviewer reads: `console slots hostActivity`. */\nfunction describeContributions(contributes: PluginContributions): string {\n const parts: string[] = []\n const site = contributes.site ?? {}\n const shell = contributes.console ?? {}\n if (site.components?.length) parts.push(`site components ${site.components.join(', ')}`)\n if (site.features?.length) parts.push(`site features ${site.features.join(', ')}`)\n if (shell.slots?.length) parts.push(`console slots ${shell.slots.join(', ')}`)\n if (shell.routes?.length) parts.push(`console routes ${shell.routes.join(', ')}`)\n if (shell.orgRoutes?.length) {\n parts.push(`console organization routes ${shell.orgRoutes.join(', ')}`)\n }\n if (shell.shell) parts.push('the console shell')\n return parts.join(' · ') || 'nothing'\n}\n\n/**\n * The `contributions` area (AGL-3116): what the bundle registers against what\n * its manifest declares.\n *\n * Every registration the tree shows must be declared, because the loaders\n * read ONLY the declaration: a slot the manifest omits is a slot whose\n * screens never load the plugin, and a component it omits is an element that\n * renders empty on every published page that places it. A declared entry the\n * bundle never registers is a warning — the plugin loads there for nothing.\n */\nfunction compareContributions(\n detected: DetectedContributions,\n options: CheckOptions | undefined,\n add: (\n level: BundleCheckProblem['level'],\n check: BundleCheckId,\n message: string,\n ) => void,\n details: Map<BundleCheckId, string>,\n markUnknown: (check: BundleCheckId) => void,\n): void {\n const found = detected.contributes\n const registers = describeContributions(found)\n const registersSomething =\n Boolean(found.site || found.console) || detected.unresolved.length > 0\n const unreadable = detected.unresolved.length\n ? ` It also makes registrations the checker could not read: ${detected.unresolved.join('; ')}.`\n : ''\n const declared = options?.declaredContributions\n if (declared === undefined) {\n // Nothing to compare against is `unknown`, never a pass — the same\n // reading the network row gives a bundle checked without its manifest.\n if (!registersSomething) {\n details.set('contributions', 'registers nothing')\n return\n }\n markUnknown('contributions')\n details.set(\n 'contributions',\n `registers ${registers} — no manifest was supplied, so nothing was compared`,\n )\n return\n }\n if (declared === null) {\n details.set(\n 'contributions',\n registersSomething ? `undeclared · registers ${registers}` : 'declares and registers nothing',\n )\n if (!registersSomething) return\n add(\n options?.requireContributions ? 'error' : 'warning',\n 'contributions',\n `the manifest declares no \"contributes\", and register() registers ` +\n `${registers}.${unreadable} Declare it — ` +\n `\"contributes\": ${JSON.stringify(found)} — because the loaders place ` +\n 'a plugin by its declaration alone. Undeclared, a published page ' +\n 'loads it only where its element is placed and the console loads it ' +\n 'with the shell.',\n )\n return\n }\n\n details.set('contributions', `declares ${describeContributions(declared)}`)\n const missing = (\n label: string,\n registered: readonly string[] | undefined,\n listed: readonly string[] | undefined,\n consequence: string,\n ) => {\n for (const id of registered ?? []) {\n if ((listed ?? []).includes(id)) continue\n add(\n 'error',\n 'contributions',\n `register() registers ${label} \"${id}\", which the manifest does not ` +\n `declare — ${consequence}`,\n )\n }\n }\n missing(\n 'the site component',\n found.site?.components,\n declared.site?.components,\n 'a published page that places it would never load the plugin',\n )\n missing(\n 'the site feature',\n found.site?.features,\n declared.site?.features,\n 'no published page would ever load the plugin to run it',\n )\n missing(\n 'a widget in the console slot',\n found.console?.slots,\n declared.console?.slots,\n 'a screen that renders the slot would never load the plugin',\n )\n missing(\n 'the console route',\n found.console?.routes,\n declared.console?.routes,\n 'the route would not find the plugin that serves it',\n )\n missing(\n 'the console organization route',\n found.console?.orgRoutes,\n declared.console?.orgRoutes,\n 'the route would not find the plugin that serves it',\n )\n if (found.console?.shell && !declared.console?.shell) {\n add(\n 'error',\n 'contributions',\n 'register() adds a nav tab, a provider or another entry the console ' +\n 'shell draws on every screen, and the manifest does not declare ' +\n '\"console\": { \"shell\": true } — the shell would draw it only on ' +\n 'screens that happened to load the plugin for something else',\n )\n }\n if (detected.unresolved.length) {\n add(\n 'warning',\n 'contributions',\n `the checker could not read ${detected.unresolved.join('; ')} — ` +\n 'confirm the declaration covers what it registers',\n )\n }\n const unused = (\n label: string,\n listed: readonly string[] | undefined,\n registered: readonly string[] | undefined,\n ) => {\n // Only when every registration of that kind was readable: an unreadable\n // one may be exactly the entry that is declared.\n if (detected.unresolved.length) return\n for (const id of listed ?? []) {\n if ((registered ?? []).includes(id)) continue\n add(\n 'warning',\n 'contributions',\n `the manifest declares ${label} \"${id}\", and register() never ` +\n 'registers it — the plugin would load there for nothing',\n )\n }\n }\n unused('the site component', declared.site?.components, found.site?.components)\n unused('the site feature', declared.site?.features, found.site?.features)\n unused('the console slot', declared.console?.slots, found.console?.slots)\n}\n\n/**\n * Inputs a bundle builds that collect a password, a card number or a\n * one-time code (AGL-3362) — a reviewer's QUESTION, never a failure.\n *\n * The phishing screen holds a published page whose author defined such a\n * field, but a plugin renders its own inputs from code the page screen never\n * reads. So the same reading is applied here, to the bundle, at review: an\n * input's `type` or `autocomplete` in an object literal (`createElement`\n * props, a JSX runtime call, a DOM `Object.assign`), in `setAttribute`, and\n * in any HTML string. A warning rather than an error because a legitimate\n * plugin can own a sign-in (a members-only area, a booking account); what the\n * reviewer asks is whether its credentials go anywhere but its declared\n * origins. The sandbox tier cannot post a native form at all — its frame\n * has no `allow-forms` and its CSP says `form-action 'none'` — so the\n * question is about `connect-src`, which the network check already lists.\n */\nfunction credentialInputsIn(\n program: AnyNode,\n constants: ReadonlyMap<string, string>,\n): string[] {\n const found = new Set<'password' | 'card' | 'otp'>()\n const keyOf = (node: AnyNode): string | null => {\n const key = node['key'] as AnyNode\n if (!key) return null\n if (!node['computed'] && key.type === 'Identifier') return key['name'] as string\n return staticString(key, constants)\n }\n walk(program, (node) => {\n if (node.type === 'Property') {\n const key = keyOf(node)\n const value = staticString(node['value'] as AnyNode, constants)\n const field = key && value !== null ? credentialForInputAttribute(key, value) : null\n if (field) found.add(field)\n } else if (node.type === 'CallExpression') {\n const callee = node['callee'] as AnyNode\n const args = (node['arguments'] ?? []) as AnyNode[]\n if (callee?.type === 'MemberExpression' && propertyName(callee, constants) === 'setAttribute') {\n const name = staticString(args[0], constants)\n const value = staticString(args[1], constants)\n const field = name !== null && value !== null ? credentialForInputAttribute(name, value) : null\n if (field) found.add(field)\n }\n } else if (node.type === 'Literal' && typeof node['value'] === 'string') {\n const field = credentialInHtml(node['value'] as string)\n if (field) found.add(field)\n } else if (node.type === 'TemplateLiteral') {\n const text = ((node['quasis'] ?? []) as AnyNode[])\n .map((quasi) => String((quasi['value'] as { cooked?: string } | undefined)?.cooked ?? ''))\n .join('')\n const field = credentialInHtml(text)\n if (field) found.add(field)\n }\n })\n const words = { password: 'a password', card: 'a card number or security code', otp: 'a one-time code' }\n return [...found].map(\n (field) =>\n `builds an input that collects ${words[field]} — confirm what it is for, and that ` +\n 'it is sent only to the origins the manifest declares',\n )\n}\n\nfunction analyseBundle(\n source: string,\n options?: CheckOptions,\n): BundleCheckResult {\n const problems: BundleCheckProblem[] = []\n const seen = new Set<string>()\n // Every area starts as \"ran, found nothing\" and is downgraded by its own\n // findings, so the summary cannot drift from the problem list (AGL-1087).\n const status = new Map<BundleCheckId, BundleCheckStatus>(\n CHECK_ORDER.map((id) => [id, 'pass']),\n )\n const details = new Map<BundleCheckId, string>()\n const add = (\n level: BundleCheckProblem['level'],\n check: BundleCheckId,\n message: string,\n ) => {\n if (level === 'error') status.set(check, 'fail')\n else if (status.get(check) === 'pass') status.set(check, 'question')\n if (seen.has(message) || seen.size >= MAX_PROBLEMS) return\n seen.add(message)\n problems.push({ level, message, check })\n }\n /** Mark the areas that never ran, so they cannot read as clean. */\n const unknownFrom = (...ids: BundleCheckId[]) => {\n for (const id of ids) status.set(id, 'unknown')\n }\n const summarise = (): BundleCheckSummary[] =>\n CHECK_ORDER.map((id) => ({\n id,\n label: CHECK_LABELS[id],\n status: status.get(id) ?? 'unknown',\n ...(details.has(id) ? { detail: details.get(id) } : {}),\n }))\n\n const maxBytes = options?.maxBytes ?? MAX_PLUGIN_BUNDLE_BYTES\n const bytes = new TextEncoder().encode(source).byteLength\n details.set('size', `${bytes.toLocaleString('en-US')} bytes of ${maxBytes.toLocaleString('en-US')}`)\n if (bytes === 0) {\n add('error', 'size', 'bundle is empty')\n unknownFrom(\n 'parse',\n 'entry',\n 'self-contained',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n 'credentials',\n )\n return {\n ok: false,\n problems,\n checks: summarise(),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n if (bytes > maxBytes) {\n add('error', 'size', `bundle is ${bytes} bytes (limit ${maxBytes})`)\n }\n\n const parseOptions: Options = {\n ecmaVersion: 'latest',\n sourceType: 'module',\n allowHashBang: true,\n allowAwaitOutsideFunction: true,\n }\n let program: AnyNode\n try {\n program = parse(source, parseOptions) as unknown as AnyNode\n } catch (error) {\n // Unparseable means unanalysable, and the loader would fail on it too.\n // Nothing else ran, and saying so is the whole point of `unknown`: a\n // bundle nobody could read must not show nine green rows.\n add(\n 'error',\n 'parse',\n `bundle does not parse as an ES module: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n unknownFrom(\n 'entry',\n 'self-contained',\n 'code-execution',\n 'globals',\n 'storage',\n 'dynamic-import',\n 'network',\n 'obfuscation',\n 'contributions',\n 'credentials',\n )\n return {\n ok: false,\n problems,\n checks: summarise(),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n\n // Names bound to a global, so `const g = globalThis; g['ev'+'al']` reads\n // as access on a global rather than on some object we know nothing about.\n const aliases = new Set<string>()\n const noteAlias = (name: unknown, init: AnyNode | null | undefined) => {\n if (typeof name !== 'string' || !init) return\n const roots: AnyNode[] =\n init.type === 'LogicalExpression'\n ? [init['left'] as AnyNode, init['right'] as AnyNode]\n : [init]\n for (const root of roots) {\n if (\n root?.type === 'Identifier' &&\n GLOBAL_ROOTS.has(root['name'] as string)\n ) {\n aliases.add(name)\n }\n }\n }\n // Local name -> the constant string it holds (AGL-1093).\n //\n // A binding is only usable when the whole tree agrees on it: declared once,\n // never assigned again, and never a parameter or another binder anywhere.\n // Any of those means the value at a given call site is a GUESS, and a guess\n // is how a checker starts making claims about code it did not read.\n const constants = new Map<string, string>()\n {\n const bound = new Map<string, AnyNode>()\n const poisoned = new Set<string>()\n const declared = new Set<string>()\n const poison = (name: unknown) => {\n if (typeof name === 'string') poisoned.add(name)\n }\n /** Every name a pattern binds — a parameter shadowing a constant is a\n * different variable, and resolving it to the outer value would be a\n * fabrication. */\n const poisonPattern = (node: AnyNode | null | undefined) => {\n if (!node) return\n if (node.type === 'Identifier') return poison(node['name'])\n for (const key of [\n 'left',\n 'argument',\n 'properties',\n 'elements',\n 'value',\n ]) {\n const value = node[key]\n if (Array.isArray(value))\n value.forEach((item) => poisonPattern(item as AnyNode))\n else if (value) poisonPattern(value as AnyNode)\n }\n }\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n const id = node['id'] as AnyNode\n if (id?.type === 'Identifier') {\n const name = id['name'] as string\n if (declared.has(name)) poison(name)\n declared.add(name)\n bound.set(name, node['init'] as AnyNode)\n } else {\n poisonPattern(id)\n }\n } else if (node.type === 'AssignmentExpression') {\n poisonPattern(node['left'] as AnyNode)\n } else if (node.type === 'UpdateExpression') {\n poisonPattern(node['argument'] as AnyNode)\n } else if (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n ) {\n ;((node['params'] ?? []) as AnyNode[]).forEach(poisonPattern)\n if ((node['id'] as AnyNode)?.type === 'Identifier')\n poison((node['id'] as AnyNode)['name'])\n } else if (node.type === 'CatchClause') {\n poisonPattern(node['param'] as AnyNode)\n } else if (\n node.type === 'ClassDeclaration' ||\n node.type === 'ClassExpression'\n ) {\n if ((node['id'] as AnyNode)?.type === 'Identifier')\n poison((node['id'] as AnyNode)['name'])\n }\n })\n // Two passes, so `const BASE = 'https://x'` then `BASE + '/y'` resolves.\n // A third buys almost nothing and each is a full re-read of the bindings.\n for (let pass = 0; pass < 2; pass += 1) {\n for (const [name, init] of bound) {\n if (poisoned.has(name) || constants.has(name)) continue\n const value = staticString(init, constants)\n if (value !== null) constants.set(name, value)\n }\n }\n }\n\n // Local name -> the callable it stands for (AGL-1090). `const f = fetch`,\n // `const d = globalThis.fetch`, `const {fetch: h} = globalThis`.\n const callableAliases = new Map<string, string>()\n /** The aliasable callable an initializer denotes, if any. */\n const callableFrom = (init: AnyNode | null | undefined): string | null => {\n if (!init) return null\n if (\n init.type === 'Identifier' &&\n ALIASABLE_CALLABLES.has(init['name'] as string)\n ) {\n return init['name'] as string\n }\n // `globalThis.fetch` / `window['fe' + 'tch']` — the property is what the\n // name will stand for, and only when the object is a global (an unknown\n // object's `.fetch` is somebody else's method).\n if (init.type === 'MemberExpression') {\n const property = propertyName(init)\n const object = init['object'] as AnyNode\n if (\n property &&\n ALIASABLE_CALLABLES.has(property) &&\n object?.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(object['name'] as string) ||\n aliases.has(object['name'] as string))\n ) {\n return property\n }\n }\n return null\n }\n const noteCallableAlias = (\n id: AnyNode | null | undefined,\n init: AnyNode | null | undefined,\n ) => {\n if (!id) return\n if (id.type === 'Identifier') {\n const callable = callableFrom(init)\n if (callable) callableAliases.set(id['name'] as string, callable)\n return\n }\n // `const { fetch } = globalThis` and `const { fetch: h } = globalThis`.\n if (id.type === 'ObjectPattern' && init) {\n const fromGlobal =\n init.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(init['name'] as string) ||\n aliases.has(init['name'] as string))\n if (!fromGlobal) return\n for (const property of (id['properties'] ?? []) as AnyNode[]) {\n const key = property['key'] as AnyNode\n const value = property['value'] as AnyNode\n const name =\n key?.type === 'Identifier' ? (key['name'] as string) : staticString(key)\n if (\n name &&\n ALIASABLE_CALLABLES.has(name) &&\n value?.type === 'Identifier'\n ) {\n callableAliases.set(value['name'] as string, name)\n }\n }\n }\n }\n\n // Global-object aliases first, as a complete pass: the callable pass below\n // resolves `g.fetch` against them, and walk order is not source order.\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n const id = node['id'] as AnyNode\n if (id?.type === 'Identifier')\n noteAlias(id['name'], node['init'] as AnyNode)\n } else if (\n node.type === 'AssignmentExpression' &&\n node['operator'] === '='\n ) {\n const left = node['left'] as AnyNode\n if (left?.type === 'Identifier')\n noteAlias(left['name'], node['right'] as AnyNode)\n }\n })\n walk(program, (node) => {\n if (node.type === 'VariableDeclarator') {\n noteCallableAlias(node['id'] as AnyNode, node['init'] as AnyNode)\n } else if (\n node.type === 'AssignmentExpression' &&\n node['operator'] === '='\n ) {\n noteCallableAlias(node['left'] as AnyNode, node['right'] as AnyNode)\n }\n })\n\n const isGlobalRef = (node: AnyNode | null | undefined): boolean =>\n !!node &&\n node.type === 'Identifier' &&\n (GLOBAL_ROOTS.has(node['name'] as string) ||\n aliases.has(node['name'] as string))\n\n const declared = options?.declaredNetwork\n const allowlist = new Set(\n (declared ?? []).map((origin) => {\n try {\n return new URL(origin).origin\n } catch {\n return origin\n }\n }),\n )\n /** Level for a network finding: a claim only when we know what was declared. */\n const networkLevel: BundleCheckProblem['level'] = declared\n ? 'error'\n : 'warning'\n // What the bundle reaches for, so the summary row can SAY it (AGL-1087) —\n // \"calls https://api.stripe.com, declared\" is the line a reviewer would\n // otherwise open the bundle to get.\n const networkApis = new Set<string>()\n const networkOrigins = new Set<string>()\n let runtimeUrls = 0\n /** Origins handed to a callee the checker could not resolve (AGL-1090). */\n const unresolvedOrigins = new Set<string>()\n const noteNetwork = (api: string, urlArg: AnyNode | undefined) => {\n const origin = literalOrigin(urlArg, constants)\n networkApis.add(api)\n if (origin) networkOrigins.add(origin)\n else runtimeUrls += 1\n if (!declared) {\n add(\n 'warning',\n 'network',\n `bundle makes network calls (${api}) — check them against the ` +\n \"manifest's declared network capability\",\n )\n return\n }\n if (!allowlist.size) {\n add(\n networkLevel,\n 'network',\n `bundle calls ${api} but the manifest declares no network ` +\n 'capability — declare every origin under capabilities.network ' +\n '(the CSP blocks the rest at runtime)',\n )\n return\n }\n if (!origin) {\n add(\n 'warning',\n 'network',\n `${api} is called with a URL that is only known at runtime — it ` +\n 'cannot be checked against the declared origins',\n )\n return\n }\n if (!allowlist.has(origin)) {\n add(\n networkLevel,\n 'network',\n `${api} calls ${origin}, which the manifest does not declare ` +\n `(declared: ${[...allowlist].join(', ')})`,\n )\n }\n }\n\n /**\n * A call the checker could not resolve, given an absolute URL.\n *\n * Reported as a QUESTION, never a refusal: most of these are helpers, and\n * refusing every unrecognised call that mentions a URL would make the\n * verifier useless. But leaving them silent is what let the network row\n * claim \"no network calls\" over a bundle whose calls it simply could not\n * follow — and a URL to an origin the manifest never declared is exactly\n * what a reviewer should be pointed at.\n */\n const noteUnresolvedUrl = (\n name: string | null,\n urlArg: AnyNode | undefined,\n ) => {\n if (name && URL_TAKING_NON_CALLS.has(name)) return\n const origin = literalOrigin(urlArg, constants)\n if (!origin) return\n // A declared origin is consistent with the manifest and permitted by the\n // CSP, so there is no question to raise about it.\n if (allowlist.has(origin)) return\n unresolvedOrigins.add(origin)\n add(\n 'warning',\n 'network',\n `${name ? `${name}()` : 'a call'} is passed ${origin}, which the ` +\n 'manifest does not declare — the checker could not tell whether it ' +\n 'reaches the network, so read this call',\n )\n }\n\n let exportsRegister = false\n let exportsRegisterApi = false\n const noteExport = (name: unknown) => {\n if (name === 'register') exportsRegister = true\n else if (name === 'registerApi') exportsRegisterApi = true\n }\n\n let obfuscatedNames = 0\n\n walk(program, (node) => {\n switch (node.type) {\n // ---- entry exports + self-containment ----\n case 'ImportDeclaration':\n add(\n 'error',\n 'self-contained',\n 'bundle has static imports — realm bundles must be self-contained ' +\n '(react/@aglyn/aglyn come from the host ABI; see the realm rollup ' +\n 'template)',\n )\n break\n case 'ExportAllDeclaration':\n case 'ExportNamedDeclaration': {\n if (node['source']) {\n add(\n 'error',\n 'self-contained',\n 'bundle re-exports from another module — realm bundles must be ' +\n 'self-contained (see the realm rollup template)',\n )\n }\n const declaration = node['declaration'] as AnyNode | null\n if (declaration) {\n if (declaration['id'])\n noteExport((declaration['id'] as AnyNode)['name'])\n for (const declarator of (declaration['declarations'] ??\n []) as AnyNode[]) {\n const id = declarator['id'] as AnyNode\n if (id?.type === 'Identifier') noteExport(id['name'])\n }\n }\n for (const specifier of (node['specifiers'] ?? []) as AnyNode[]) {\n const exported = specifier['exported'] as AnyNode\n noteExport(\n exported?.type === 'Identifier'\n ? exported['name']\n : staticString(exported),\n )\n }\n break\n }\n\n // ---- dynamic import ----\n case 'ImportExpression': {\n const specifier = staticString(node['source'] as AnyNode, constants)\n if (specifier === null) {\n add(\n 'error',\n 'dynamic-import',\n 'dynamic import with a specifier computed at runtime is not ' +\n 'allowed — the loader cannot know what would be fetched',\n )\n } else if (/^https?:/i.test(specifier)) {\n add(\n 'error',\n 'dynamic-import',\n 'dynamic import of remote URLs is not allowed',\n )\n }\n break\n }\n\n // ---- calls ----\n case 'CallExpression':\n case 'NewExpression': {\n const callee = node['callee'] as AnyNode\n const args = (node['arguments'] ?? []) as AnyNode[]\n const written =\n callee?.type === 'Identifier'\n ? (callee['name'] as string)\n : callee?.type === 'MemberExpression'\n ? propertyName(callee, constants)\n : null\n // An alias resolves to what it stands for (AGL-1090), so `f(url)`\n // after `const f = fetch` is a fetch call.\n const name =\n (written && callableAliases.get(written)) ?? written ?? null\n\n if (name === 'eval')\n add('error', 'code-execution', 'eval() is not allowed')\n if (name === 'Function')\n add(\n 'error',\n 'code-execution',\n 'the Function constructor is not allowed',\n )\n // `(()=>{}).constructor('return 1')()` — the Function constructor\n // reached through any function value, which is why the CALL is the\n // finding rather than the name `Function`.\n if (name === 'constructor' && callee?.type === 'MemberExpression')\n add(\n 'error',\n 'code-execution',\n 'calling .constructor() is not allowed — it reaches the ' +\n 'Function constructor from any function value',\n )\n if (name && NETWORK_CALLS.has(name)) noteNetwork(name, args[0])\n else if (name && NETWORK_CONSTRUCTORS.has(name))\n noteNetwork(name, node.type === 'NewExpression' ? args[0] : args[1])\n // Something we could not follow, handed an absolute URL (AGL-1090).\n // Not a refusal — it may be a helper, a logger, or a link — but the\n // network row must stop rendering \"no network calls\" over it.\n else noteUnresolvedUrl(name, args[0])\n break\n }\n\n // ---- member access ----\n case 'MemberExpression': {\n const object = node['object'] as AnyNode\n const name = propertyName(node, constants)\n if (isGlobalRef(object) && name === null) {\n add(\n 'error',\n 'globals',\n `computed property access on ${\n (object['name'] as string) || 'a global'\n } is not allowed — a property chosen at runtime cannot be ` +\n 'reviewed',\n )\n break\n }\n // Property names only mean anything on a global: `response.cookie`\n // and `state.localStorage` are somebody else's object.\n if (name === 'cookie' && isGlobalRef(object))\n add('error', 'storage', 'document.cookie access is not allowed')\n if (name && STORAGE_NAMES.has(name) && isGlobalRef(object))\n add(\n 'error',\n 'storage',\n 'browser storage access is not allowed (use host-mediated data)',\n )\n if (name === 'eval' && isGlobalRef(object))\n add('error', 'code-execution', 'eval() is not allowed')\n break\n }\n\n // ---- bare references ----\n case 'Identifier': {\n const name = node['name'] as string\n if (STORAGE_NAMES.has(name))\n add(\n 'error',\n 'storage',\n 'browser storage access is not allowed (use host-mediated data)',\n )\n if (OBFUSCATED_NAME.test(name)) obfuscatedNames += 1\n break\n }\n\n // ---- obfuscation ----\n case 'Literal': {\n const value = node['value']\n if (\n typeof value === 'string' &&\n value.length >= MIN_SUSPICIOUS_BASE64 &&\n BASE64_BLOB.test(value)\n ) {\n add(\n 'warning',\n 'obfuscation',\n `bundle embeds a ${value.length}-character base64 literal — ` +\n 'decode it before approving',\n )\n }\n break\n }\n default:\n break\n }\n })\n\n if (!exportsRegister && !exportsRegisterApi) {\n add(\n 'error',\n 'entry',\n 'bundle exports neither register(host) nor registerApi() — ' +\n 'nothing for the loader to call',\n )\n }\n\n if (obfuscatedNames >= MIN_OBFUSCATED_NAMES) {\n add(\n 'warning',\n 'obfuscation',\n `bundle uses ${obfuscatedNames} machine-obfuscated identifiers ` +\n '(_0x… naming) — minifiers do not produce these',\n )\n }\n const longestLine = source\n .split('\\n')\n .reduce((longest, line) => Math.max(longest, line.length), 0)\n if (longestLine > MAX_READABLE_LINE) {\n add(\n 'warning',\n 'obfuscation',\n `bundle has a single ${longestLine}-character line — nothing on it ` +\n 'can be read in review; ask for a readable build or a source map',\n )\n }\n\n details.set(\n 'entry',\n [\n exportsRegister ? 'register(host)' : null,\n exportsRegisterApi ? 'registerApi()' : null,\n ]\n .filter(Boolean)\n .join(', ') || 'none',\n )\n\n // What the network check actually saw. Without the manifest it saw the\n // calls but could not judge them, and that is `unknown`, not a pass — the\n // row a reviewer must not read as \"checked and fine\".\n if (!declared) {\n if (networkApis.size) {\n status.set('network', 'unknown')\n details.set(\n 'network',\n `calls ${[...networkApis].join(', ')} — no declared origins were ` +\n 'supplied, so nothing was compared',\n )\n } else {\n details.set('network', 'no network calls')\n }\n } else if (!networkApis.size) {\n details.set(\n 'network',\n unresolvedOrigins.size\n ? `no resolved network calls, but ${[...unresolvedOrigins].join(', ')} ` +\n 'is passed to a call the checker could not follow'\n : 'no network calls',\n )\n } else {\n const origins = networkOrigins.size\n ? [...networkOrigins]\n .map((origin) =>\n allowlist.has(origin) ? `${origin} (declared)` : `${origin} (NOT declared)`,\n )\n .join(', ')\n : ''\n details.set(\n 'network',\n [\n `calls ${[...networkApis].join(', ')}`,\n origins,\n runtimeUrls\n ? `${runtimeUrls} call(s) with a URL only known at runtime`\n : '',\n ]\n .filter(Boolean)\n .join(' · '),\n )\n }\n\n const detected = detectRegistrations(program, constants)\n for (const message of credentialInputsIn(program, constants)) {\n add('warning', 'credentials', message)\n }\n compareContributions(detected, options, add, details, (check) =>\n status.set(check, 'unknown'),\n )\n\n return {\n ok: !problems.some((problem) => problem.level === 'error'),\n problems,\n checks: summarise(),\n exports: { register: exportsRegister, registerApi: exportsRegisterApi },\n contributions: detected,\n }\n}\n\n/**\n * Run the checks over a bundle. NEVER throws (AGL-1094).\n *\n * The publish route calls this directly, so an unexpected failure in here\n * used to mean a 500 and no explanation for the publisher — and in the review\n * sweep it looked exactly like a missing artifact, which sent the\n * investigation to the storage bucket instead of to this file. A checker that\n * cannot analyse a bundle has an answer: say so, in the verdict, as an error.\n */\nexport function checkPluginBundle(\n source: string,\n options?: CheckOptions,\n): BundleCheckResult {\n try {\n return analyseBundle(source, options)\n } catch (error) {\n return {\n ok: false,\n problems: [\n {\n level: 'error',\n check: 'parse',\n message:\n 'the verifier failed on this bundle — treat it as unchecked and ' +\n `report it: ${\n error instanceof Error ? error.message : String(error)\n }`,\n },\n ],\n checks: CHECK_ORDER.map((id) => ({\n id,\n label: CHECK_LABELS[id],\n status: id === 'parse' ? 'fail' : 'unknown',\n })),\n exports: { register: false, registerApi: false },\n contributions: NOTHING_DETECTED,\n }\n }\n}\n"],"names":["parse","credentialForInputAttribute","credentialInHtml","CHECK_LABELS","entry","size","globals","storage","network","obfuscation","contributions","credentials","CHECK_ORDER","MAX_PLUGIN_BUNDLE_BYTES","PLUGIN_VERIFIER_VERSION","isStoredVerdictCurrent","stored","sha256","Number","verifierVersion","GLOBAL_ROOTS","Set","STORAGE_NAMES","NETWORK_CALLS","NETWORK_CONSTRUCTORS","ALIASABLE_CALLABLES","URL_TAKING_NON_CALLS","MAX_READABLE_LINE","MIN_SUSPICIOUS_BASE64","MIN_OBFUSCATED_NAMES","MAX_PROBLEMS","OBFUSCATED_NAME","BASE64_BLOB","skippedChild","node","key","type","walk","root","visit","stack","length","pop","Object","keys","value","Array","isArray","item","push","staticString","constants","get","expressions","quasis","cooked","out","index","filled","left","right","propertyName","property","literalOrigin","url","URL","test","protocol","origin","NOTHING_DETECTED","contributes","unresolved","REGISTRATION_CALLS","SHELL_EXTENSION_FIELDS","detectRegistrations","program","slots","routes","orgRoutes","components","features","shell","objectProperty","object","name","undefined","keyName","listOf","slotName","literal","callee","has","args","extension","add","widgets","list","widget","slot","field","into","href","id","site","sort","console","describeContributions","parts","join","compareContributions","detected","options","details","markUnknown","found","declared","registers","registersSomething","Boolean","unreadable","declaredContributions","set","requireContributions","JSON","stringify","missing","label","registered","listed","consequence","includes","unused","credentialInputsIn","keyOf","text","map","quasi","String","words","password","card","otp","analyseBundle","source","problems","seen","status","Map","level","check","message","unknownFrom","ids","summarise","detail","maxBytes","bytes","TextEncoder","encode","byteLength","toLocaleString","ok","checks","exports","register","registerApi","parseOptions","ecmaVersion","sourceType","allowHashBang","allowAwaitOutsideFunction","error","Error","aliases","noteAlias","init","roots","bound","poisoned","poison","poisonPattern","forEach","pass","callableAliases","callableFrom","noteCallableAlias","callable","fromGlobal","isGlobalRef","declaredNetwork","allowlist","networkLevel","networkApis","networkOrigins","runtimeUrls","unresolvedOrigins","noteNetwork","api","urlArg","noteUnresolvedUrl","exportsRegister","exportsRegisterApi","noteExport","obfuscatedNames","declaration","declarator","specifier","exported","written","longestLine","split","reduce","longest","line","Math","max","filter","origins","some","problem","checkPluginBundle"],"mappings":"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"}
|
|
@@ -106,7 +106,10 @@
|
|
|
106
106
|
'invoice.marked_uncollectible',
|
|
107
107
|
'customer.updated',
|
|
108
108
|
'payment_method.attached',
|
|
109
|
-
'payment_method.detached'
|
|
109
|
+
'payment_method.detached',
|
|
110
|
+
// Fraud signals to staff (AGL-3356).
|
|
111
|
+
'radar.early_fraud_warning.created',
|
|
112
|
+
'review.opened'
|
|
110
113
|
];
|
|
111
114
|
/**
|
|
112
115
|
* How the CONNECT destination identifies itself (AGL-1948), mirroring
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/webhook-delivery.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\n/**\n * DID THE WEBHOOK ACTUALLY DO ANYTHING? (AGL-1954)\n *\n * Every signal we have about the billing webhook is a signal about the\n * REQUEST. Stripe records `delivery_success` off the status code. The\n * idempotency claim in `stripeEvents` records that the signature verified and\n * the handler was entered. `/api/health/billing` (AGL-1924) counts what\n * Stripe attempted and failed to deliver. All three read green for the one\n * failure this module exists to catch: **a handler that answers 200 and moves\n * nothing.**\n *\n * That is not hypothetical here. AGL-1798 is exactly it — `charge.refunded`\n * was never subscribed on the live destination, so AGL-1546's entitlement\n * revocation had no trigger, and every other indicator looked fine for as\n * long as it lasted. AGL-1551 is the same class from the other side: a week\n * of 100% rejected deliveries behind a green \"Active\" badge.\n *\n * ## The honest signal is an EFFECT, not a return code\n *\n * A check that asserts \"the handler ran\" is nearly as blind as the 200 —\n * the handler DID run in the AGL-1798 shape, it just had no work registered\n * for the event. So the route reports what it COMMITTED: an org's plan\n * mirrored, a `platformRevenue` row written, a refund stamped, an orphan\n * recorded, an audit row appended, a plugin claiming the event. Those are the\n * things whose absence is the bug.\n *\n * ## Three outcomes, and conflating the middle two is its own failure\n *\n * A binary \"did it write something\" would fire on every legitimately\n * irrelevant delivery — a tenant shopper's subscription carries no\n * `metadata.orgId` and correctly moves nothing on OUR side; a marketplace\n * refund resolves to no workspace customer and is correctly left to the\n * plugins; a `won` dispute nobody claimed reversed no money and correctly\n * wakes nobody. Alerting on those is alert fatigue, which ends with the alarm\n * muted and the real one lost inside it.\n *\n * So a branch that decides an event is not its business must SAY SO, by\n * name, through {@link WebhookEffectLedger.skip}. That is what separates:\n *\n * | outcome | meaning | alarm |\n * |-----------|----------------------------------------------------|-------|\n * | `acted` | something durable committed, or a plugin claimed it | no |\n * | `ignored` | a branch named a reason, or we never asked for this event type | no |\n * | `inert` | an event we deliberately subscribe to produced NEITHER | YES |\n *\n * `inert` is the whole point. Falling off the end of the dispatch with no\n * effect and no stated reason is precisely what a deleted write, an\n * unregistered plugin handler, or a renamed event type looks like from\n * inside the process — and it is the only one of the three that nothing else\n * in this system can see.\n *\n * ## Why the \"did we ask for it\" test is the required-events list\n *\n * Stripe delivers what the destination is subscribed to, and the destination\n * is created by `tools/scripts/setup-stripe.mjs` from `WEBHOOK_EVENTS`. An\n * event type outside that list reaching us is either a hand-added\n * subscription or a Connect delivery arriving at the platform destination —\n * unremarkable, and never our silent-drop bug, because we never claimed to\n * handle it. An event type INSIDE it is one we went out of our way to ask\n * Stripe for, so producing nothing from it is a contradiction by\n * construction.\n *\n * `REQUIRED_WEBHOOK_EVENTS` below is a copy of that list, and a copy is a\n * drift hazard — so `webhook-delivery.spec.ts` reads the `.mjs` and fails on\n * any divergence, in either direction. The alternative (moving the list\n * across the nx boundary out of `tools/`) is a wider change than this\n * warrants and is tracked on AGL-1948.\n *\n * Pure: no clock, no I/O, no Firestore. The route records, this decides, the\n * spec exercises every branch without a network.\n */\n\n/** What a single delivery turned out to be. */\nexport type WebhookDeliveryOutcome = 'acted' | 'ignored' | 'inert'\n\n/**\n * The event types the platform destination is subscribed to, mirroring\n * `WEBHOOK_EVENTS` in `tools/scripts/lib/stripe-webhook-health.mjs`.\n *\n * The list is the definition of \"we asked for this\", which is what makes a\n * no-op delivery of one of them a defect rather than a non-event. Kept in\n * the same order as the source so a diff between the two reads cleanly.\n */\nexport const REQUIRED_WEBHOOK_EVENTS: readonly string[] = [\n 'customer.subscription.created',\n 'customer.subscription.updated',\n 'customer.subscription.deleted',\n 'checkout.session.completed',\n 'invoice.finalized',\n 'invoice.paid',\n 'invoice.payment_failed',\n 'charge.refunded',\n 'charge.dispute.created',\n 'charge.dispute.closed',\n // AI overage charged as it accrues (AGL-3011), in the source list's order.\n 'invoice.voided',\n 'invoice.marked_uncollectible',\n 'customer.updated',\n 'payment_method.attached',\n 'payment_method.detached',\n]\n\n/**\n * How the CONNECT destination identifies itself (AGL-1948), mirroring\n * `CONNECT_SCOPE_METADATA_KEY` / `CONNECT_SCOPE_METADATA_VALUE` in\n * `tools/scripts/lib/stripe-webhook-health.mjs`.\n *\n * Connected-account events are delivered only to a destination created with\n * `connect: true`, and Stripe's API does not report that flag back on the\n * endpoint object — so the destination is identified by metadata we set at\n * creation instead. Same copy-and-guard arrangement as the list above.\n */\nexport const CONNECT_SCOPE_METADATA_KEY = 'aglyn_scope'\nexport const CONNECT_SCOPE_METADATA_VALUE = 'connect'\n\n/**\n * What the Connect destination must carry, mirroring\n * `CONNECT_WEBHOOK_EVENTS` in the same script lib.\n *\n * `account.updated` is the whole point of it: AGL-1997's `syncConnectAccountStatus`\n * is what stops a merchant whose Stripe account was later restricted from\n * going on selling against a stale `stripeChargesEnabled`, with the SHOPPER\n * meeting the failure at payment time. Without this event the handler cannot\n * run — which is exactly the state AGL-2122 found and fixed.\n */\nexport const REQUIRED_CONNECT_WEBHOOK_EVENTS: readonly string[] = ['account.updated']\n\n/**\n * Does this Stripe endpoint object carry our Connect scope marker?\n *\n * Coverage for it reuses `unsubscribedRequiredEvents` with\n * `REQUIRED_CONNECT_WEBHOOK_EVENTS` as the `required` argument — the wildcard\n * and null handling are identical questions, and a second copy of that logic\n * is a second place for it to drift.\n */\nexport function isConnectWebhookEndpoint(endpoint: unknown): boolean {\n const metadata = (endpoint as { metadata?: Record<string, unknown> })?.metadata\n return metadata?.[CONNECT_SCOPE_METADATA_KEY] === CONNECT_SCOPE_METADATA_VALUE\n}\n\n/**\n * A per-delivery record of what actually committed.\n *\n * Deliberately append-only and stringly-typed: the reasons are read by a\n * human in a log line and by a spec, never switched on. Keeping it dumb is\n * what stops it growing into a second dispatch table that can disagree with\n * the real one.\n */\nexport interface WebhookEffectLedger {\n /** Durable things this delivery committed, in the order they landed. */\n readonly effects: readonly string[]\n /** Reasons a branch decided the event was none of its business. */\n readonly skips: readonly string[]\n /**\n * Record a committed effect. Call this AFTER the write it names, so a\n * throw between the two cannot claim an effect that never landed.\n */\n effect(name: string): void\n /**\n * Record a deliberate no-op and why. This is the difference between \"not\n * ours\" and \"broken\", and it is the half that prevents alert fatigue.\n */\n skip(reason: string): void\n}\n\nexport function createWebhookEffectLedger(): WebhookEffectLedger {\n const effects: string[] = []\n const skips: string[] = []\n return {\n effects,\n skips,\n effect(name: string) {\n effects.push(name)\n },\n skip(reason: string) {\n skips.push(reason)\n },\n }\n}\n\n/** Firestore methods that MUTATE. Everything else is a read or a builder. */\nconst WRITE_METHODS = new Set(['set', 'update', 'create', 'delete', 'add'])\n\n/**\n * Wrap a Firestore handle so every write it commits lands in the ledger.\n *\n * ## Why this exists rather than a `ledger.effect()` beside each write\n *\n * A hand-placed note next to a write is a check that CANNOT FAIL in the one\n * way that matters. Delete the write and leave the note, and the ledger\n * cheerfully reports an effect that never happened — which is the same\n * \"asserts its own literals\" defect the health probes are written to avoid.\n * The note has to be caused by the write, not merely adjacent to it.\n *\n * So the effect is recorded from INSIDE the call, after it resolves. A write\n * that throws records nothing (it did not commit); a write that is deleted\n * from the source records nothing (it is not called). There is no edit to\n * the handler that removes the work and keeps the signal.\n *\n * ## What it deliberately does not see\n *\n * Writes issued through a DIFFERENT Firestore handle — the shared\n * `tenant-data-admin` writers (`writeOrgBilling`, `notifyOrgAdmins`,\n * `notifyStaff`), which each open their own — and anything a plugin does\n * inside its own handler. Those are noted explicitly by the caller where\n * they are the only consequence of a branch, and the comments at those call\n * sites say so. The boundary is stated rather than hidden: this covers the\n * route's own writes, which is where the route's own bugs live.\n *\n * A Proxy rather than a hand-written facade because the wrapped object is\n * passed on to `updateExisting` and friends, which call methods this module\n * has never heard of. A facade would have to enumerate them and would\n * silently drop the ones it forgot.\n */\nexport function observeWrites<T extends object>(\n handle: T,\n ledger: WebhookEffectLedger,\n label = 'firestore',\n): T {\n return new Proxy(handle, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver)\n if (typeof value !== 'function' || typeof property !== 'string') {\n return value\n }\n const method = property\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n )\n // `collection('orgs')` names the thing being written, so the ledger\n // reads `orgs.update` rather than `firestore.update`. Everything\n // else inherits the label it was reached through.\n const nextLabel =\n method === 'collection' && typeof args[0] === 'string'\n ? (args[0] as string)\n : label\n if (WRITE_METHODS.has(method)) {\n // Recorded on RESOLUTION. A rejected write committed nothing and\n // must not read as an effect.\n return Promise.resolve(result).then((settled) => {\n ledger.effect(`${nextLabel}.${method}`)\n return settled\n })\n }\n // Builders (`collection`, `doc`, `where`, `limit`) return objects\n // that can themselves be written through, so the wrapper follows\n // them. Thenables are returned bare: wrapping a promise would make\n // `then` look like a builder and recurse forever.\n if (\n result &&\n typeof result === 'object' &&\n typeof (result as { then?: unknown }).then !== 'function'\n ) {\n return observeWrites(result as object, ledger, nextLabel)\n }\n return result\n }\n },\n })\n}\n\nexport interface WebhookDeliveryVerdict {\n outcome: WebhookDeliveryOutcome\n /** One short phrase naming WHY, for the log line and the spec. */\n reason: string\n}\n\n/**\n * Classify one delivery.\n *\n * Order is the argument:\n *\n * 1. **Any effect, or any plugin claim, wins.** A delivery that both wrote\n * something and skipped something else did work; a partially-skipped\n * handler is not an idle one.\n * 2. **A named skip is an answer.** Reaching a branch that consciously\n * decided \"not ours\" is a handled event, not a dropped one.\n * 3. **An event type we never subscribed to is not our problem.** Nothing\n * promised to handle it.\n * 4. **Everything else is inert** — and by elimination that means: we asked\n * Stripe for this event, we received it, no handler claimed it, no branch\n * wrote anything, and no branch could say why.\n */\nexport function classifyWebhookDelivery(input: {\n type: string\n effects: readonly string[]\n skips: readonly string[]\n claimed: boolean\n /** Overridable so the spec can prove the rule rather than the list. */\n required?: readonly string[]\n}): WebhookDeliveryVerdict {\n const { type, effects, skips, claimed } = input\n const required = input.required ?? REQUIRED_WEBHOOK_EVENTS\n if (effects.length > 0) return { outcome: 'acted', reason: effects[0] }\n if (claimed) return { outcome: 'acted', reason: 'plugin-claimed' }\n if (skips.length > 0) return { outcome: 'ignored', reason: skips[0] }\n if (!required.includes(type)) {\n return { outcome: 'ignored', reason: 'not-subscribed' }\n }\n return { outcome: 'inert', reason: 'no-effect' }\n}\n\n/**\n * How long after Stripe CREATES an event its first delivery attempt may\n * plausibly take to reach us, in seconds. Mirrors `RETRY_LAG_SECONDS` in\n * `tools/scripts/lib/stripe-webhook-health.mjs`, under the same\n * read-the-`.mjs`-as-text drift guard as the lists above.\n *\n * Calibrated against the live account on 2026-08-18: the five deliveries that\n * succeeded first time landed in 1.0–3.7s; the one that did not —\n * `evt_1U49XtDYHP4psn7hA9VHPnZz`, whose three 400s ARE the three failures the\n * Stripe Dashboard was showing — landed 16,665s (4h 37m) late. Two minutes\n * sits three orders of magnitude clear of the healthy band and still inside\n * Stripe's first automatic retry interval, so it separates the two without\n * straddling either.\n */\nexport const RETRY_LAG_SECONDS = 120\n\n/**\n * What a delivery's lag says about the attempts BEFORE it.\n *\n * `unknown` is a first-class outcome, not a synonym for either answer. See\n * {@link classifyDeliveryLag} for why that matters more here than usual.\n */\nexport type WebhookDeliveryAttempt = 'first-attempt' | 'retried' | 'unknown'\n\n/** Why {@link classifyDeliveryLag} answered as it did. */\nexport type WebhookDeliveryLagReason =\n | 'measured'\n | 'created-absent'\n | 'created-not-a-number'\n | 'created-non-positive'\n | 'received-unusable'\n | 'clock-skew'\n\nexport interface WebhookDeliveryLagVerdict {\n attempt: WebhookDeliveryAttempt\n /** Seconds between `event.created` and our claim. Null unless `measured`. */\n lagSeconds: number | null\n reason: WebhookDeliveryLagReason\n}\n\n/**\n * DID THIS DELIVERY ONLY LAND ON A RETRY? (AGL-2039, the last arm of AGL-1948)\n *\n * ## The reconciliation this exists to close\n *\n * `/api/health/billing` scores EVENTS and `GET /v1/events?delivery_success=false`\n * is a TERMINAL-state filter: it selects events that are still pending or\n * failed EVERY attempt. An event that 400s three times and then succeeds on\n * the fourth reads back `delivery_success: true` and is, to that filter,\n * indistinguishable from one that succeeded immediately. The Stripe Dashboard\n * scores delivery ATTEMPTS, so the same hour reads 0% here and 30% there —\n * and both numbers are right. The three real failures were visible only in\n * the second reading, and each was a mirror that did not run when it should\n * have.\n *\n * The recoverable half of the attempt history is in our own data. The route\n * claims `stripeEvents/{id}` AFTER signature verification and DELETES the\n * claim when a handler throws, so the stamp always records the attempt that\n * actually got through. The distance from `event.created` to that stamp is\n * the delivery lag, and a lag of minutes means an earlier attempt failed.\n *\n * ## Why this is decided at WRITE time, not at read time\n *\n * The obvious implementation reads `receivedAt` off every claim document in\n * the window and subtracts. That turns a `.count()` aggregation — one\n * integer, zero documents read — into a document scan on a PUBLIC,\n * unauthenticated health endpoint polled every five minutes. Deciding here,\n * once, at the moment of the claim, lets the probe stay an aggregation over a\n * marker field, exactly like AGL-1954's `inertAtMs`.\n *\n * ## Absent, zero and unknown are THREE different things\n *\n * `strictNullChecks` is off repo-wide, so an absent `created` folds to falsy\n * and a naive `Number(event?.created ?? 0)` yields 0 — which subtracts to a\n * lag of ~1.7 BILLION seconds and reports every such delivery as a retry,\n * forever, on a perfectly healthy webhook. A monitoring signal that reds on\n * its own missing input is worse than no signal, because it is the thing that\n * gets muted. So each of those inputs is rejected by name:\n *\n * | input | attempt | reason |\n * |-----------------------------|------------|-------------------------|\n * | field absent / null | `unknown` | `created-absent` |\n * | a string, NaN, Infinity | `unknown` | `created-not-a-number` |\n * | `0` or negative | `unknown` | `created-non-positive` |\n * | our own stamp unusable | `unknown` | `received-unusable` |\n * | created in the FUTURE by more than the bar | `unknown` | `clock-skew` |\n *\n * A lag between `-threshold` and `+threshold` is `first-attempt`: sub-second\n * clock skew between Stripe's clock and ours is ordinary and is not evidence\n * of anything.\n *\n * Pure: no clock, no I/O. The caller supplies both timestamps.\n */\nexport function classifyDeliveryLag(input: {\n /** `event.created`, in unix SECONDS, exactly as Stripe sent it. */\n eventCreatedSeconds: unknown\n /** When we claimed the event, in unix MILLISECONDS. */\n receivedAtMs: unknown\n /** Overridable so the spec can prove the rule rather than the constant. */\n thresholdSeconds?: number\n}): WebhookDeliveryLagVerdict {\n const threshold = input.thresholdSeconds ?? RETRY_LAG_SECONDS\n const created = input.eventCreatedSeconds\n if (created === undefined || created === null) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'created-absent' }\n }\n if (typeof created !== 'number' || !Number.isFinite(created)) {\n return {\n attempt: 'unknown',\n lagSeconds: null,\n reason: 'created-not-a-number',\n }\n }\n // Zero is the value an absent field folds to under the repo's null rules,\n // and no Stripe event was created at the epoch. Rejected by name so it can\n // never be mistaken for a measurement.\n if (created <= 0) {\n return {\n attempt: 'unknown',\n lagSeconds: null,\n reason: 'created-non-positive',\n }\n }\n const received = input.receivedAtMs\n if (typeof received !== 'number' || !Number.isFinite(received) || received <= 0) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'received-unusable' }\n }\n const lagSeconds = received / 1000 - created\n // An event stamped in OUR future by more than the threshold is a clock\n // disagreement, not a fast delivery, and reporting it as a healthy first\n // attempt would be an answer we cannot support.\n if (lagSeconds < -threshold) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'clock-skew' }\n }\n return {\n attempt: lagSeconds > threshold ? 'retried' : 'first-attempt',\n lagSeconds,\n reason: 'measured',\n }\n}\n\n/**\n * Which required events is a destination NOT subscribed to? (AGL-1948)\n *\n * The same blind spot from the configuration side, and the cheaper half of\n * it: a subscription removed by hand in the Stripe dashboard produces no\n * failed delivery, no rejected request and no inert one either — Stripe\n * simply stops sending, and every count on the health probe reads a\n * perfectly healthy zero. `charge.refunded` was missing from the live\n * endpoint for exactly this reason (AGL-1798) and the only thing that ever\n * noticed was a script nobody ran.\n *\n * `['*']` is Stripe's wildcard subscription and covers everything.\n *\n * Returns the names, sorted, so the probe body is stable and a diff between\n * two readings is meaningful.\n */\nexport function unsubscribedRequiredEvents(\n enabledEvents: readonly string[] | null | undefined,\n required: readonly string[] = REQUIRED_WEBHOOK_EVENTS,\n): string[] {\n if (!enabledEvents) return []\n if (enabledEvents.includes('*')) return []\n const enabled = new Set(enabledEvents)\n return required.filter((event) => !enabled.has(event)).sort()\n}\n"],"names":["REQUIRED_WEBHOOK_EVENTS","CONNECT_SCOPE_METADATA_KEY","CONNECT_SCOPE_METADATA_VALUE","REQUIRED_CONNECT_WEBHOOK_EVENTS","isConnectWebhookEndpoint","endpoint","metadata","createWebhookEffectLedger","effects","skips","effect","name","push","skip","reason","WRITE_METHODS","Set","observeWrites","handle","ledger","label","Proxy","get","target","property","receiver","value","Reflect","method","args","result","apply","nextLabel","has","Promise","resolve","then","settled","classifyWebhookDelivery","input","type","claimed","required","length","outcome","includes","RETRY_LAG_SECONDS","classifyDeliveryLag","threshold","thresholdSeconds","created","eventCreatedSeconds","undefined","attempt","lagSeconds","Number","isFinite","received","receivedAtMs","unsubscribedRequiredEvents","enabledEvents","enabled","filter","event","sort"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/webhook-delivery.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\n/**\n * DID THE WEBHOOK ACTUALLY DO ANYTHING? (AGL-1954)\n *\n * Every signal we have about the billing webhook is a signal about the\n * REQUEST. Stripe records `delivery_success` off the status code. The\n * idempotency claim in `stripeEvents` records that the signature verified and\n * the handler was entered. `/api/health/billing` (AGL-1924) counts what\n * Stripe attempted and failed to deliver. All three read green for the one\n * failure this module exists to catch: **a handler that answers 200 and moves\n * nothing.**\n *\n * That is not hypothetical here. AGL-1798 is exactly it — `charge.refunded`\n * was never subscribed on the live destination, so AGL-1546's entitlement\n * revocation had no trigger, and every other indicator looked fine for as\n * long as it lasted. AGL-1551 is the same class from the other side: a week\n * of 100% rejected deliveries behind a green \"Active\" badge.\n *\n * ## The honest signal is an EFFECT, not a return code\n *\n * A check that asserts \"the handler ran\" is nearly as blind as the 200 —\n * the handler DID run in the AGL-1798 shape, it just had no work registered\n * for the event. So the route reports what it COMMITTED: an org's plan\n * mirrored, a `platformRevenue` row written, a refund stamped, an orphan\n * recorded, an audit row appended, a plugin claiming the event. Those are the\n * things whose absence is the bug.\n *\n * ## Three outcomes, and conflating the middle two is its own failure\n *\n * A binary \"did it write something\" would fire on every legitimately\n * irrelevant delivery — a tenant shopper's subscription carries no\n * `metadata.orgId` and correctly moves nothing on OUR side; a marketplace\n * refund resolves to no workspace customer and is correctly left to the\n * plugins; a `won` dispute nobody claimed reversed no money and correctly\n * wakes nobody. Alerting on those is alert fatigue, which ends with the alarm\n * muted and the real one lost inside it.\n *\n * So a branch that decides an event is not its business must SAY SO, by\n * name, through {@link WebhookEffectLedger.skip}. That is what separates:\n *\n * | outcome | meaning | alarm |\n * |-----------|----------------------------------------------------|-------|\n * | `acted` | something durable committed, or a plugin claimed it | no |\n * | `ignored` | a branch named a reason, or we never asked for this event type | no |\n * | `inert` | an event we deliberately subscribe to produced NEITHER | YES |\n *\n * `inert` is the whole point. Falling off the end of the dispatch with no\n * effect and no stated reason is precisely what a deleted write, an\n * unregistered plugin handler, or a renamed event type looks like from\n * inside the process — and it is the only one of the three that nothing else\n * in this system can see.\n *\n * ## Why the \"did we ask for it\" test is the required-events list\n *\n * Stripe delivers what the destination is subscribed to, and the destination\n * is created by `tools/scripts/setup-stripe.mjs` from `WEBHOOK_EVENTS`. An\n * event type outside that list reaching us is either a hand-added\n * subscription or a Connect delivery arriving at the platform destination —\n * unremarkable, and never our silent-drop bug, because we never claimed to\n * handle it. An event type INSIDE it is one we went out of our way to ask\n * Stripe for, so producing nothing from it is a contradiction by\n * construction.\n *\n * `REQUIRED_WEBHOOK_EVENTS` below is a copy of that list, and a copy is a\n * drift hazard — so `webhook-delivery.spec.ts` reads the `.mjs` and fails on\n * any divergence, in either direction. The alternative (moving the list\n * across the nx boundary out of `tools/`) is a wider change than this\n * warrants and is tracked on AGL-1948.\n *\n * Pure: no clock, no I/O, no Firestore. The route records, this decides, the\n * spec exercises every branch without a network.\n */\n\n/** What a single delivery turned out to be. */\nexport type WebhookDeliveryOutcome = 'acted' | 'ignored' | 'inert'\n\n/**\n * The event types the platform destination is subscribed to, mirroring\n * `WEBHOOK_EVENTS` in `tools/scripts/lib/stripe-webhook-health.mjs`.\n *\n * The list is the definition of \"we asked for this\", which is what makes a\n * no-op delivery of one of them a defect rather than a non-event. Kept in\n * the same order as the source so a diff between the two reads cleanly.\n */\nexport const REQUIRED_WEBHOOK_EVENTS: readonly string[] = [\n 'customer.subscription.created',\n 'customer.subscription.updated',\n 'customer.subscription.deleted',\n 'checkout.session.completed',\n 'invoice.finalized',\n 'invoice.paid',\n 'invoice.payment_failed',\n 'charge.refunded',\n 'charge.dispute.created',\n 'charge.dispute.closed',\n // AI overage charged as it accrues (AGL-3011), in the source list's order.\n 'invoice.voided',\n 'invoice.marked_uncollectible',\n 'customer.updated',\n 'payment_method.attached',\n 'payment_method.detached',\n // Fraud signals to staff (AGL-3356).\n 'radar.early_fraud_warning.created',\n 'review.opened',\n]\n\n/**\n * How the CONNECT destination identifies itself (AGL-1948), mirroring\n * `CONNECT_SCOPE_METADATA_KEY` / `CONNECT_SCOPE_METADATA_VALUE` in\n * `tools/scripts/lib/stripe-webhook-health.mjs`.\n *\n * Connected-account events are delivered only to a destination created with\n * `connect: true`, and Stripe's API does not report that flag back on the\n * endpoint object — so the destination is identified by metadata we set at\n * creation instead. Same copy-and-guard arrangement as the list above.\n */\nexport const CONNECT_SCOPE_METADATA_KEY = 'aglyn_scope'\nexport const CONNECT_SCOPE_METADATA_VALUE = 'connect'\n\n/**\n * What the Connect destination must carry, mirroring\n * `CONNECT_WEBHOOK_EVENTS` in the same script lib.\n *\n * `account.updated` is the whole point of it: AGL-1997's `syncConnectAccountStatus`\n * is what stops a merchant whose Stripe account was later restricted from\n * going on selling against a stale `stripeChargesEnabled`, with the SHOPPER\n * meeting the failure at payment time. Without this event the handler cannot\n * run — which is exactly the state AGL-2122 found and fixed.\n */\nexport const REQUIRED_CONNECT_WEBHOOK_EVENTS: readonly string[] = ['account.updated']\n\n/**\n * Does this Stripe endpoint object carry our Connect scope marker?\n *\n * Coverage for it reuses `unsubscribedRequiredEvents` with\n * `REQUIRED_CONNECT_WEBHOOK_EVENTS` as the `required` argument — the wildcard\n * and null handling are identical questions, and a second copy of that logic\n * is a second place for it to drift.\n */\nexport function isConnectWebhookEndpoint(endpoint: unknown): boolean {\n const metadata = (endpoint as { metadata?: Record<string, unknown> })?.metadata\n return metadata?.[CONNECT_SCOPE_METADATA_KEY] === CONNECT_SCOPE_METADATA_VALUE\n}\n\n/**\n * A per-delivery record of what actually committed.\n *\n * Deliberately append-only and stringly-typed: the reasons are read by a\n * human in a log line and by a spec, never switched on. Keeping it dumb is\n * what stops it growing into a second dispatch table that can disagree with\n * the real one.\n */\nexport interface WebhookEffectLedger {\n /** Durable things this delivery committed, in the order they landed. */\n readonly effects: readonly string[]\n /** Reasons a branch decided the event was none of its business. */\n readonly skips: readonly string[]\n /**\n * Record a committed effect. Call this AFTER the write it names, so a\n * throw between the two cannot claim an effect that never landed.\n */\n effect(name: string): void\n /**\n * Record a deliberate no-op and why. This is the difference between \"not\n * ours\" and \"broken\", and it is the half that prevents alert fatigue.\n */\n skip(reason: string): void\n}\n\nexport function createWebhookEffectLedger(): WebhookEffectLedger {\n const effects: string[] = []\n const skips: string[] = []\n return {\n effects,\n skips,\n effect(name: string) {\n effects.push(name)\n },\n skip(reason: string) {\n skips.push(reason)\n },\n }\n}\n\n/** Firestore methods that MUTATE. Everything else is a read or a builder. */\nconst WRITE_METHODS = new Set(['set', 'update', 'create', 'delete', 'add'])\n\n/**\n * Wrap a Firestore handle so every write it commits lands in the ledger.\n *\n * ## Why this exists rather than a `ledger.effect()` beside each write\n *\n * A hand-placed note next to a write is a check that CANNOT FAIL in the one\n * way that matters. Delete the write and leave the note, and the ledger\n * cheerfully reports an effect that never happened — which is the same\n * \"asserts its own literals\" defect the health probes are written to avoid.\n * The note has to be caused by the write, not merely adjacent to it.\n *\n * So the effect is recorded from INSIDE the call, after it resolves. A write\n * that throws records nothing (it did not commit); a write that is deleted\n * from the source records nothing (it is not called). There is no edit to\n * the handler that removes the work and keeps the signal.\n *\n * ## What it deliberately does not see\n *\n * Writes issued through a DIFFERENT Firestore handle — the shared\n * `tenant-data-admin` writers (`writeOrgBilling`, `notifyOrgAdmins`,\n * `notifyStaff`), which each open their own — and anything a plugin does\n * inside its own handler. Those are noted explicitly by the caller where\n * they are the only consequence of a branch, and the comments at those call\n * sites say so. The boundary is stated rather than hidden: this covers the\n * route's own writes, which is where the route's own bugs live.\n *\n * A Proxy rather than a hand-written facade because the wrapped object is\n * passed on to `updateExisting` and friends, which call methods this module\n * has never heard of. A facade would have to enumerate them and would\n * silently drop the ones it forgot.\n */\nexport function observeWrites<T extends object>(\n handle: T,\n ledger: WebhookEffectLedger,\n label = 'firestore',\n): T {\n return new Proxy(handle, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver)\n if (typeof value !== 'function' || typeof property !== 'string') {\n return value\n }\n const method = property\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n )\n // `collection('orgs')` names the thing being written, so the ledger\n // reads `orgs.update` rather than `firestore.update`. Everything\n // else inherits the label it was reached through.\n const nextLabel =\n method === 'collection' && typeof args[0] === 'string'\n ? (args[0] as string)\n : label\n if (WRITE_METHODS.has(method)) {\n // Recorded on RESOLUTION. A rejected write committed nothing and\n // must not read as an effect.\n return Promise.resolve(result).then((settled) => {\n ledger.effect(`${nextLabel}.${method}`)\n return settled\n })\n }\n // Builders (`collection`, `doc`, `where`, `limit`) return objects\n // that can themselves be written through, so the wrapper follows\n // them. Thenables are returned bare: wrapping a promise would make\n // `then` look like a builder and recurse forever.\n if (\n result &&\n typeof result === 'object' &&\n typeof (result as { then?: unknown }).then !== 'function'\n ) {\n return observeWrites(result as object, ledger, nextLabel)\n }\n return result\n }\n },\n })\n}\n\nexport interface WebhookDeliveryVerdict {\n outcome: WebhookDeliveryOutcome\n /** One short phrase naming WHY, for the log line and the spec. */\n reason: string\n}\n\n/**\n * Classify one delivery.\n *\n * Order is the argument:\n *\n * 1. **Any effect, or any plugin claim, wins.** A delivery that both wrote\n * something and skipped something else did work; a partially-skipped\n * handler is not an idle one.\n * 2. **A named skip is an answer.** Reaching a branch that consciously\n * decided \"not ours\" is a handled event, not a dropped one.\n * 3. **An event type we never subscribed to is not our problem.** Nothing\n * promised to handle it.\n * 4. **Everything else is inert** — and by elimination that means: we asked\n * Stripe for this event, we received it, no handler claimed it, no branch\n * wrote anything, and no branch could say why.\n */\nexport function classifyWebhookDelivery(input: {\n type: string\n effects: readonly string[]\n skips: readonly string[]\n claimed: boolean\n /** Overridable so the spec can prove the rule rather than the list. */\n required?: readonly string[]\n}): WebhookDeliveryVerdict {\n const { type, effects, skips, claimed } = input\n const required = input.required ?? REQUIRED_WEBHOOK_EVENTS\n if (effects.length > 0) return { outcome: 'acted', reason: effects[0] }\n if (claimed) return { outcome: 'acted', reason: 'plugin-claimed' }\n if (skips.length > 0) return { outcome: 'ignored', reason: skips[0] }\n if (!required.includes(type)) {\n return { outcome: 'ignored', reason: 'not-subscribed' }\n }\n return { outcome: 'inert', reason: 'no-effect' }\n}\n\n/**\n * How long after Stripe CREATES an event its first delivery attempt may\n * plausibly take to reach us, in seconds. Mirrors `RETRY_LAG_SECONDS` in\n * `tools/scripts/lib/stripe-webhook-health.mjs`, under the same\n * read-the-`.mjs`-as-text drift guard as the lists above.\n *\n * Calibrated against the live account on 2026-08-18: the five deliveries that\n * succeeded first time landed in 1.0–3.7s; the one that did not —\n * `evt_1U49XtDYHP4psn7hA9VHPnZz`, whose three 400s ARE the three failures the\n * Stripe Dashboard was showing — landed 16,665s (4h 37m) late. Two minutes\n * sits three orders of magnitude clear of the healthy band and still inside\n * Stripe's first automatic retry interval, so it separates the two without\n * straddling either.\n */\nexport const RETRY_LAG_SECONDS = 120\n\n/**\n * What a delivery's lag says about the attempts BEFORE it.\n *\n * `unknown` is a first-class outcome, not a synonym for either answer. See\n * {@link classifyDeliveryLag} for why that matters more here than usual.\n */\nexport type WebhookDeliveryAttempt = 'first-attempt' | 'retried' | 'unknown'\n\n/** Why {@link classifyDeliveryLag} answered as it did. */\nexport type WebhookDeliveryLagReason =\n | 'measured'\n | 'created-absent'\n | 'created-not-a-number'\n | 'created-non-positive'\n | 'received-unusable'\n | 'clock-skew'\n\nexport interface WebhookDeliveryLagVerdict {\n attempt: WebhookDeliveryAttempt\n /** Seconds between `event.created` and our claim. Null unless `measured`. */\n lagSeconds: number | null\n reason: WebhookDeliveryLagReason\n}\n\n/**\n * DID THIS DELIVERY ONLY LAND ON A RETRY? (AGL-2039, the last arm of AGL-1948)\n *\n * ## The reconciliation this exists to close\n *\n * `/api/health/billing` scores EVENTS and `GET /v1/events?delivery_success=false`\n * is a TERMINAL-state filter: it selects events that are still pending or\n * failed EVERY attempt. An event that 400s three times and then succeeds on\n * the fourth reads back `delivery_success: true` and is, to that filter,\n * indistinguishable from one that succeeded immediately. The Stripe Dashboard\n * scores delivery ATTEMPTS, so the same hour reads 0% here and 30% there —\n * and both numbers are right. The three real failures were visible only in\n * the second reading, and each was a mirror that did not run when it should\n * have.\n *\n * The recoverable half of the attempt history is in our own data. The route\n * claims `stripeEvents/{id}` AFTER signature verification and DELETES the\n * claim when a handler throws, so the stamp always records the attempt that\n * actually got through. The distance from `event.created` to that stamp is\n * the delivery lag, and a lag of minutes means an earlier attempt failed.\n *\n * ## Why this is decided at WRITE time, not at read time\n *\n * The obvious implementation reads `receivedAt` off every claim document in\n * the window and subtracts. That turns a `.count()` aggregation — one\n * integer, zero documents read — into a document scan on a PUBLIC,\n * unauthenticated health endpoint polled every five minutes. Deciding here,\n * once, at the moment of the claim, lets the probe stay an aggregation over a\n * marker field, exactly like AGL-1954's `inertAtMs`.\n *\n * ## Absent, zero and unknown are THREE different things\n *\n * `strictNullChecks` is off repo-wide, so an absent `created` folds to falsy\n * and a naive `Number(event?.created ?? 0)` yields 0 — which subtracts to a\n * lag of ~1.7 BILLION seconds and reports every such delivery as a retry,\n * forever, on a perfectly healthy webhook. A monitoring signal that reds on\n * its own missing input is worse than no signal, because it is the thing that\n * gets muted. So each of those inputs is rejected by name:\n *\n * | input | attempt | reason |\n * |-----------------------------|------------|-------------------------|\n * | field absent / null | `unknown` | `created-absent` |\n * | a string, NaN, Infinity | `unknown` | `created-not-a-number` |\n * | `0` or negative | `unknown` | `created-non-positive` |\n * | our own stamp unusable | `unknown` | `received-unusable` |\n * | created in the FUTURE by more than the bar | `unknown` | `clock-skew` |\n *\n * A lag between `-threshold` and `+threshold` is `first-attempt`: sub-second\n * clock skew between Stripe's clock and ours is ordinary and is not evidence\n * of anything.\n *\n * Pure: no clock, no I/O. The caller supplies both timestamps.\n */\nexport function classifyDeliveryLag(input: {\n /** `event.created`, in unix SECONDS, exactly as Stripe sent it. */\n eventCreatedSeconds: unknown\n /** When we claimed the event, in unix MILLISECONDS. */\n receivedAtMs: unknown\n /** Overridable so the spec can prove the rule rather than the constant. */\n thresholdSeconds?: number\n}): WebhookDeliveryLagVerdict {\n const threshold = input.thresholdSeconds ?? RETRY_LAG_SECONDS\n const created = input.eventCreatedSeconds\n if (created === undefined || created === null) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'created-absent' }\n }\n if (typeof created !== 'number' || !Number.isFinite(created)) {\n return {\n attempt: 'unknown',\n lagSeconds: null,\n reason: 'created-not-a-number',\n }\n }\n // Zero is the value an absent field folds to under the repo's null rules,\n // and no Stripe event was created at the epoch. Rejected by name so it can\n // never be mistaken for a measurement.\n if (created <= 0) {\n return {\n attempt: 'unknown',\n lagSeconds: null,\n reason: 'created-non-positive',\n }\n }\n const received = input.receivedAtMs\n if (typeof received !== 'number' || !Number.isFinite(received) || received <= 0) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'received-unusable' }\n }\n const lagSeconds = received / 1000 - created\n // An event stamped in OUR future by more than the threshold is a clock\n // disagreement, not a fast delivery, and reporting it as a healthy first\n // attempt would be an answer we cannot support.\n if (lagSeconds < -threshold) {\n return { attempt: 'unknown', lagSeconds: null, reason: 'clock-skew' }\n }\n return {\n attempt: lagSeconds > threshold ? 'retried' : 'first-attempt',\n lagSeconds,\n reason: 'measured',\n }\n}\n\n/**\n * Which required events is a destination NOT subscribed to? (AGL-1948)\n *\n * The same blind spot from the configuration side, and the cheaper half of\n * it: a subscription removed by hand in the Stripe dashboard produces no\n * failed delivery, no rejected request and no inert one either — Stripe\n * simply stops sending, and every count on the health probe reads a\n * perfectly healthy zero. `charge.refunded` was missing from the live\n * endpoint for exactly this reason (AGL-1798) and the only thing that ever\n * noticed was a script nobody ran.\n *\n * `['*']` is Stripe's wildcard subscription and covers everything.\n *\n * Returns the names, sorted, so the probe body is stable and a diff between\n * two readings is meaningful.\n */\nexport function unsubscribedRequiredEvents(\n enabledEvents: readonly string[] | null | undefined,\n required: readonly string[] = REQUIRED_WEBHOOK_EVENTS,\n): string[] {\n if (!enabledEvents) return []\n if (enabledEvents.includes('*')) return []\n const enabled = new Set(enabledEvents)\n return required.filter((event) => !enabled.has(event)).sort()\n}\n"],"names":["REQUIRED_WEBHOOK_EVENTS","CONNECT_SCOPE_METADATA_KEY","CONNECT_SCOPE_METADATA_VALUE","REQUIRED_CONNECT_WEBHOOK_EVENTS","isConnectWebhookEndpoint","endpoint","metadata","createWebhookEffectLedger","effects","skips","effect","name","push","skip","reason","WRITE_METHODS","Set","observeWrites","handle","ledger","label","Proxy","get","target","property","receiver","value","Reflect","method","args","result","apply","nextLabel","has","Promise","resolve","then","settled","classifyWebhookDelivery","input","type","claimed","required","length","outcome","includes","RETRY_LAG_SECONDS","classifyDeliveryLag","threshold","thresholdSeconds","created","eventCreatedSeconds","undefined","attempt","lagSeconds","Number","isFinite","received","receivedAtMs","unsubscribedRequiredEvents","enabledEvents","enabled","filter","event","sort"],"mappings":"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"}
|
|
@@ -54,12 +54,21 @@ export interface BillingWebhookEvent {
|
|
|
54
54
|
export interface BillingWebhookHandlerResult {
|
|
55
55
|
/** True when this handler recognised the event as belonging to it. */
|
|
56
56
|
claimed?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The site the claimed record belongs to (AGL-3360), when the handler
|
|
59
|
+
* knows it. The route reads it for a charge's fraud signal: the seller's
|
|
60
|
+
* pattern row names the sites the charges were for, whichever plugin sold
|
|
61
|
+
* them, without the route knowing what an order or a booking is.
|
|
62
|
+
*/
|
|
63
|
+
hostId?: string;
|
|
57
64
|
}
|
|
58
65
|
export type BillingWebhookHandler = (event: BillingWebhookEvent) => Promise<void | BillingWebhookHandlerResult> | void | BillingWebhookHandlerResult;
|
|
59
66
|
/** What `runBillingWebhookHandlers` reports back about a whole dispatch. */
|
|
60
67
|
export interface BillingWebhookDispatchResult {
|
|
61
68
|
/** True when ANY handler claimed the event. */
|
|
62
69
|
claimed: boolean;
|
|
70
|
+
/** Every distinct `hostId` a handler reported, in handler order. */
|
|
71
|
+
hostIds: string[];
|
|
63
72
|
}
|
|
64
73
|
/**
|
|
65
74
|
* Registers a webhook handler. The plugin loader guarantees each plugin's
|