opencode-skills-collection 3.0.35 → 3.0.37

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.
Files changed (238) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +15 -1
  2. package/bundled-skills/accesslint-audit/SKILL.md +115 -0
  3. package/bundled-skills/accesslint-diff/SKILL.md +81 -0
  4. package/bundled-skills/accesslint-scan/SKILL.md +47 -0
  5. package/bundled-skills/composition-patterns/SKILL.md +87 -0
  6. package/bundled-skills/composition-patterns/rules/_sections.md +29 -0
  7. package/bundled-skills/composition-patterns/rules/_template.md +24 -0
  8. package/bundled-skills/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  9. package/bundled-skills/composition-patterns/rules/architecture-compound-components.md +112 -0
  10. package/bundled-skills/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  11. package/bundled-skills/composition-patterns/rules/patterns-explicit-variants.md +100 -0
  12. package/bundled-skills/composition-patterns/rules/react19-no-forwardref.md +42 -0
  13. package/bundled-skills/composition-patterns/rules/state-context-interface.md +191 -0
  14. package/bundled-skills/composition-patterns/rules/state-decouple-implementation.md +113 -0
  15. package/bundled-skills/composition-patterns/rules/state-lift-state.md +125 -0
  16. package/bundled-skills/debugging-toolkit/SKILL.md +35 -0
  17. package/bundled-skills/deploy-to-vercel/SKILL.md +304 -0
  18. package/bundled-skills/deploy-to-vercel/resources/deploy-codex.sh +301 -0
  19. package/bundled-skills/deploy-to-vercel/resources/deploy.sh +301 -0
  20. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  21. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  22. package/bundled-skills/docs/maintainers/backups/README-2026-06-02.md +687 -0
  23. package/bundled-skills/docs/maintainers/repo-growth-seo.md +4 -4
  24. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  25. package/bundled-skills/docs/users/bundles.md +245 -1
  26. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  27. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  28. package/bundled-skills/docs/users/getting-started.md +1 -1
  29. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  30. package/bundled-skills/docs/users/plugins.md +21 -13
  31. package/bundled-skills/docs/users/specialized-plugin-roadmap.md +95 -0
  32. package/bundled-skills/docs/users/usage.md +4 -4
  33. package/bundled-skills/docs/users/visual-guide.md +4 -4
  34. package/bundled-skills/polis-protocol/SKILL.md +93 -0
  35. package/bundled-skills/python-development/SKILL.md +35 -0
  36. package/bundled-skills/radix-ui-design-system/SKILL.md +2 -2
  37. package/bundled-skills/react-native-skills/SKILL.md +120 -0
  38. package/bundled-skills/react-native-skills/rules/_sections.md +86 -0
  39. package/bundled-skills/react-native-skills/rules/_template.md +28 -0
  40. package/bundled-skills/react-native-skills/rules/animation-derived-value.md +53 -0
  41. package/bundled-skills/react-native-skills/rules/animation-gesture-detector-press.md +95 -0
  42. package/bundled-skills/react-native-skills/rules/animation-gpu-properties.md +65 -0
  43. package/bundled-skills/react-native-skills/rules/design-system-compound-components.md +66 -0
  44. package/bundled-skills/react-native-skills/rules/fonts-config-plugin.md +71 -0
  45. package/bundled-skills/react-native-skills/rules/imports-design-system-folder.md +68 -0
  46. package/bundled-skills/react-native-skills/rules/js-hoist-intl.md +61 -0
  47. package/bundled-skills/react-native-skills/rules/list-performance-callbacks.md +44 -0
  48. package/bundled-skills/react-native-skills/rules/list-performance-function-references.md +132 -0
  49. package/bundled-skills/react-native-skills/rules/list-performance-images.md +53 -0
  50. package/bundled-skills/react-native-skills/rules/list-performance-inline-objects.md +97 -0
  51. package/bundled-skills/react-native-skills/rules/list-performance-item-expensive.md +94 -0
  52. package/bundled-skills/react-native-skills/rules/list-performance-item-memo.md +82 -0
  53. package/bundled-skills/react-native-skills/rules/list-performance-item-types.md +104 -0
  54. package/bundled-skills/react-native-skills/rules/list-performance-virtualize.md +67 -0
  55. package/bundled-skills/react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
  56. package/bundled-skills/react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
  57. package/bundled-skills/react-native-skills/rules/navigation-native-navigators.md +188 -0
  58. package/bundled-skills/react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
  59. package/bundled-skills/react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
  60. package/bundled-skills/react-native-skills/rules/react-state-dispatcher.md +91 -0
  61. package/bundled-skills/react-native-skills/rules/react-state-fallback.md +56 -0
  62. package/bundled-skills/react-native-skills/rules/react-state-minimize.md +65 -0
  63. package/bundled-skills/react-native-skills/rules/rendering-no-falsy-and.md +74 -0
  64. package/bundled-skills/react-native-skills/rules/rendering-text-in-text-component.md +36 -0
  65. package/bundled-skills/react-native-skills/rules/scroll-position-no-state.md +82 -0
  66. package/bundled-skills/react-native-skills/rules/state-ground-truth.md +80 -0
  67. package/bundled-skills/react-native-skills/rules/ui-expo-image.md +66 -0
  68. package/bundled-skills/react-native-skills/rules/ui-image-gallery.md +104 -0
  69. package/bundled-skills/react-native-skills/rules/ui-measure-views.md +78 -0
  70. package/bundled-skills/react-native-skills/rules/ui-menus.md +174 -0
  71. package/bundled-skills/react-native-skills/rules/ui-native-modals.md +77 -0
  72. package/bundled-skills/react-native-skills/rules/ui-pressable.md +61 -0
  73. package/bundled-skills/react-native-skills/rules/ui-safe-area-scroll.md +65 -0
  74. package/bundled-skills/react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
  75. package/bundled-skills/react-native-skills/rules/ui-styling.md +87 -0
  76. package/bundled-skills/skill-issue/SKILL.md +73 -0
  77. package/bundled-skills/tdd-workflows/SKILL.md +35 -0
  78. package/bundled-skills/vercel-cli-with-tokens/SKILL.md +361 -0
  79. package/bundled-skills/vercel-optimize/CONTRIBUTING.md +41 -0
  80. package/bundled-skills/vercel-optimize/SKILL.md +331 -0
  81. package/bundled-skills/vercel-optimize/lib/auth-route.mjs +23 -0
  82. package/bundled-skills/vercel-optimize/lib/budget-summary.mjs +182 -0
  83. package/bundled-skills/vercel-optimize/lib/citations.mjs +139 -0
  84. package/bundled-skills/vercel-optimize/lib/cost-coverage.mjs +143 -0
  85. package/bundled-skills/vercel-optimize/lib/dedup-recs.mjs +325 -0
  86. package/bundled-skills/vercel-optimize/lib/deep-dive.mjs +350 -0
  87. package/bundled-skills/vercel-optimize/lib/display-labels.mjs +185 -0
  88. package/bundled-skills/vercel-optimize/lib/extract-claims.mjs +550 -0
  89. package/bundled-skills/vercel-optimize/lib/framework-support.mjs +67 -0
  90. package/bundled-skills/vercel-optimize/lib/gates/build-minutes-fanout.mjs +69 -0
  91. package/bundled-skills/vercel-optimize/lib/gates/cold-start.mjs +66 -0
  92. package/bundled-skills/vercel-optimize/lib/gates/contract.mjs +79 -0
  93. package/bundled-skills/vercel-optimize/lib/gates/cwv-poor.mjs +87 -0
  94. package/bundled-skills/vercel-optimize/lib/gates/external-api-slow.mjs +55 -0
  95. package/bundled-skills/vercel-optimize/lib/gates/hard-gates.mjs +73 -0
  96. package/bundled-skills/vercel-optimize/lib/gates/index.mjs +45 -0
  97. package/bundled-skills/vercel-optimize/lib/gates/isr-overrevalidation.mjs +62 -0
  98. package/bundled-skills/vercel-optimize/lib/gates/middleware-heavy.mjs +51 -0
  99. package/bundled-skills/vercel-optimize/lib/gates/observability-events-attribution.mjs +56 -0
  100. package/bundled-skills/vercel-optimize/lib/gates/platform-bot-protection.mjs +115 -0
  101. package/bundled-skills/vercel-optimize/lib/gates/platform-fluid-compute.mjs +83 -0
  102. package/bundled-skills/vercel-optimize/lib/gates/region-misconfig.mjs +64 -0
  103. package/bundled-skills/vercel-optimize/lib/gates/route-errors.mjs +80 -0
  104. package/bundled-skills/vercel-optimize/lib/gates/scanner-driven.mjs +122 -0
  105. package/bundled-skills/vercel-optimize/lib/gates/select-candidates.mjs +134 -0
  106. package/bundled-skills/vercel-optimize/lib/gates/slow-route.mjs +88 -0
  107. package/bundled-skills/vercel-optimize/lib/gates/types.d.ts +38 -0
  108. package/bundled-skills/vercel-optimize/lib/gates/uncached-route.mjs +93 -0
  109. package/bundled-skills/vercel-optimize/lib/gates/usage-spike-triage.mjs +121 -0
  110. package/bundled-skills/vercel-optimize/lib/grade-recommendation.mjs +155 -0
  111. package/bundled-skills/vercel-optimize/lib/impact-label.mjs +126 -0
  112. package/bundled-skills/vercel-optimize/lib/impact-magnitude.mjs +60 -0
  113. package/bundled-skills/vercel-optimize/lib/investigation-brief.mjs +610 -0
  114. package/bundled-skills/vercel-optimize/lib/observation-safety.mjs +174 -0
  115. package/bundled-skills/vercel-optimize/lib/project-facts.mjs +99 -0
  116. package/bundled-skills/vercel-optimize/lib/queries.mjs +315 -0
  117. package/bundled-skills/vercel-optimize/lib/reconcile-candidates.mjs +372 -0
  118. package/bundled-skills/vercel-optimize/lib/render-report.mjs +955 -0
  119. package/bundled-skills/vercel-optimize/lib/repo-root.mjs +86 -0
  120. package/bundled-skills/vercel-optimize/lib/route-normalize.mjs +220 -0
  121. package/bundled-skills/vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs +38 -0
  122. package/bundled-skills/vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs +30 -0
  123. package/bundled-skills/vercel-optimize/lib/sanitizers/count-correct.mjs +52 -0
  124. package/bundled-skills/vercel-optimize/lib/sanitizers/function-duration-invocations.mjs +38 -0
  125. package/bundled-skills/vercel-optimize/lib/sanitizers/index.mjs +79 -0
  126. package/bundled-skills/vercel-optimize/lib/sanitizers/middleware-conflict.mjs +36 -0
  127. package/bundled-skills/vercel-optimize/lib/sanitizers/missing-citation.mjs +16 -0
  128. package/bundled-skills/vercel-optimize/lib/sanitizers/pre-release.mjs +74 -0
  129. package/bundled-skills/vercel-optimize/lib/sanitizers/rate-limit.mjs +67 -0
  130. package/bundled-skills/vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs +38 -0
  131. package/bundled-skills/vercel-optimize/lib/sanitizers/undeclared-dep.mjs +78 -0
  132. package/bundled-skills/vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs +37 -0
  133. package/bundled-skills/vercel-optimize/lib/sanitizers/window-units.mjs +32 -0
  134. package/bundled-skills/vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs +109 -0
  135. package/bundled-skills/vercel-optimize/lib/scanners/edge-heavy-import.mjs +94 -0
  136. package/bundled-skills/vercel-optimize/lib/scanners/force-dynamic.mjs +42 -0
  137. package/bundled-skills/vercel-optimize/lib/scanners/headers-in-page.mjs +44 -0
  138. package/bundled-skills/vercel-optimize/lib/scanners/index.mjs +35 -0
  139. package/bundled-skills/vercel-optimize/lib/scanners/large-static-asset.mjs +92 -0
  140. package/bundled-skills/vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs +42 -0
  141. package/bundled-skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs +55 -0
  142. package/bundled-skills/vercel-optimize/lib/scanners/missing-cache-headers.mjs +90 -0
  143. package/bundled-skills/vercel-optimize/lib/scanners/prisma-include-tree.mjs +42 -0
  144. package/bundled-skills/vercel-optimize/lib/scanners/region-pin-in-config.mjs +88 -0
  145. package/bundled-skills/vercel-optimize/lib/scanners/source-maps-production.mjs +36 -0
  146. package/bundled-skills/vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs +43 -0
  147. package/bundled-skills/vercel-optimize/lib/scanners/turbo-force-bypass.mjs +129 -0
  148. package/bundled-skills/vercel-optimize/lib/scanners/unoptimized-image.mjs +113 -0
  149. package/bundled-skills/vercel-optimize/lib/scanners/use-cache-date-stamp.mjs +106 -0
  150. package/bundled-skills/vercel-optimize/lib/support-topics.mjs +355 -0
  151. package/bundled-skills/vercel-optimize/lib/throttle.mjs +273 -0
  152. package/bundled-skills/vercel-optimize/lib/util.mjs +17 -0
  153. package/bundled-skills/vercel-optimize/lib/vercel.mjs +784 -0
  154. package/bundled-skills/vercel-optimize/lib/verify-claim.mjs +1296 -0
  155. package/bundled-skills/vercel-optimize/lib/workspace-resolver.mjs +521 -0
  156. package/bundled-skills/vercel-optimize/references/candidates.md +176 -0
  157. package/bundled-skills/vercel-optimize/references/data-collection.md +218 -0
  158. package/bundled-skills/vercel-optimize/references/docs-library.json +683 -0
  159. package/bundled-skills/vercel-optimize/references/doctrine.md +105 -0
  160. package/bundled-skills/vercel-optimize/references/observability-plus.md +108 -0
  161. package/bundled-skills/vercel-optimize/references/playbooks/README.md +53 -0
  162. package/bundled-skills/vercel-optimize/references/playbooks/ai-application.md +32 -0
  163. package/bundled-skills/vercel-optimize/references/playbooks/api-service.md +30 -0
  164. package/bundled-skills/vercel-optimize/references/playbooks/content-site.md +30 -0
  165. package/bundled-skills/vercel-optimize/references/playbooks/ecommerce.md +30 -0
  166. package/bundled-skills/vercel-optimize/references/playbooks/marketing.md +30 -0
  167. package/bundled-skills/vercel-optimize/references/playbooks/saas.md +31 -0
  168. package/bundled-skills/vercel-optimize/references/playbooks/sveltekit.md +75 -0
  169. package/bundled-skills/vercel-optimize/references/recommendations.md +203 -0
  170. package/bundled-skills/vercel-optimize/references/scanner-patterns.md +251 -0
  171. package/bundled-skills/vercel-optimize/references/scoring.md +205 -0
  172. package/bundled-skills/vercel-optimize/references/support-topics/README.md +46 -0
  173. package/bundled-skills/vercel-optimize/references/support-topics/astro-edge-middleware-scope.md +22 -0
  174. package/bundled-skills/vercel-optimize/references/support-topics/astro-output-mode-and-isr.md +22 -0
  175. package/bundled-skills/vercel-optimize/references/support-topics/auth-preserving-parallelization.md +22 -0
  176. package/bundled-skills/vercel-optimize/references/support-topics/bot-protection-product-guardrails.md +22 -0
  177. package/bundled-skills/vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md +23 -0
  178. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md +22 -0
  179. package/bundled-skills/vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md +23 -0
  180. package/bundled-skills/vercel-optimize/references/support-topics/cdn-cache-auth-safety.md +22 -0
  181. package/bundled-skills/vercel-optimize/references/support-topics/cold-start-initialization-bundle.md +22 -0
  182. package/bundled-skills/vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md +22 -0
  183. package/bundled-skills/vercel-optimize/references/support-topics/database-egress-pooling-region.md +22 -0
  184. package/bundled-skills/vercel-optimize/references/support-topics/dynamic-rendering-traps.md +22 -0
  185. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path-platform.md +22 -0
  186. package/bundled-skills/vercel-optimize/references/support-topics/external-api-critical-path.md +22 -0
  187. package/bundled-skills/vercel-optimize/references/support-topics/fast-data-transfer-payloads.md +22 -0
  188. package/bundled-skills/vercel-optimize/references/support-topics/fluid-compute-caveats.md +22 -0
  189. package/bundled-skills/vercel-optimize/references/support-topics/function-duration-io-and-after.md +22 -0
  190. package/bundled-skills/vercel-optimize/references/support-topics/function-invocation-reduction.md +22 -0
  191. package/bundled-skills/vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md +23 -0
  192. package/bundled-skills/vercel-optimize/references/support-topics/image-optimization-cost-control.md +22 -0
  193. package/bundled-skills/vercel-optimize/references/support-topics/isr-revalidation-static-generation.md +22 -0
  194. package/bundled-skills/vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md +22 -0
  195. package/bundled-skills/vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md +22 -0
  196. package/bundled-skills/vercel-optimize/references/support-topics/next-font-cls-self-hosting.md +23 -0
  197. package/bundled-skills/vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md +23 -0
  198. package/bundled-skills/vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md +23 -0
  199. package/bundled-skills/vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md +22 -0
  200. package/bundled-skills/vercel-optimize/references/support-topics/next-script-third-party-strategy.md +23 -0
  201. package/bundled-skills/vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md +22 -0
  202. package/bundled-skills/vercel-optimize/references/support-topics/not-found-catchall-request-waste.md +23 -0
  203. package/bundled-skills/vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md +22 -0
  204. package/bundled-skills/vercel-optimize/references/support-topics/observability-events-cost-attribution.md +22 -0
  205. package/bundled-skills/vercel-optimize/references/support-topics/post-response-work-waituntil.md +22 -0
  206. package/bundled-skills/vercel-optimize/references/support-topics/route-error-durable-offload.md +22 -0
  207. package/bundled-skills/vercel-optimize/references/support-topics/route-error-runtime-limits.md +22 -0
  208. package/bundled-skills/vercel-optimize/references/support-topics/runtime-cache-reusable-data.md +22 -0
  209. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md +22 -0
  210. package/bundled-skills/vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md +22 -0
  211. package/bundled-skills/vercel-optimize/references/support-topics/usage-spike-triage.md +22 -0
  212. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md +23 -0
  213. package/bundled-skills/vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md +22 -0
  214. package/bundled-skills/vercel-optimize/references/support-topics/workflow-resumable-stream-routes.md +23 -0
  215. package/bundled-skills/vercel-optimize/references/verification.md +102 -0
  216. package/bundled-skills/vercel-optimize/references/voice.md +76 -0
  217. package/bundled-skills/vercel-optimize/scripts/budget-summary.mjs +56 -0
  218. package/bundled-skills/vercel-optimize/scripts/build-docs.mjs +74 -0
  219. package/bundled-skills/vercel-optimize/scripts/check-citations.mjs +81 -0
  220. package/bundled-skills/vercel-optimize/scripts/check-docs-fresh.mjs +93 -0
  221. package/bundled-skills/vercel-optimize/scripts/collect-signals.mjs +576 -0
  222. package/bundled-skills/vercel-optimize/scripts/collect-sub-agent-outputs.mjs +296 -0
  223. package/bundled-skills/vercel-optimize/scripts/deep-dive.mjs +319 -0
  224. package/bundled-skills/vercel-optimize/scripts/gate-investigations.mjs +166 -0
  225. package/bundled-skills/vercel-optimize/scripts/merge-signals.mjs +192 -0
  226. package/bundled-skills/vercel-optimize/scripts/prepare-investigation-brief.mjs +231 -0
  227. package/bundled-skills/vercel-optimize/scripts/reconcile-candidates.mjs +62 -0
  228. package/bundled-skills/vercel-optimize/scripts/render-report.mjs +437 -0
  229. package/bundled-skills/vercel-optimize/scripts/scan-codebase.mjs +313 -0
  230. package/bundled-skills/vercel-optimize/scripts/verify-and-regen.mjs +346 -0
  231. package/bundled-skills/vercel-optimize/scripts/verify-finding.mjs +19 -0
  232. package/bundled-skills/vercel-react-view-transitions/SKILL.md +327 -0
  233. package/bundled-skills/vercel-react-view-transitions/references/css-recipes.md +242 -0
  234. package/bundled-skills/vercel-react-view-transitions/references/implementation.md +182 -0
  235. package/bundled-skills/vercel-react-view-transitions/references/nextjs.md +176 -0
  236. package/bundled-skills/vercel-react-view-transitions/references/patterns.md +262 -0
  237. package/package.json +2 -2
  238. package/skills_index.json +312 -0
@@ -0,0 +1,66 @@
1
+ // Signal: `function_start_type` dimension on `vercel.function_invocation.count` (cold|hot|prewarmed).
2
+ // Threshold WHY: 40%+ cold is fixable via Fluid keep-warm; 30% is the noise floor for serverless without keep-warm.
3
+ // total>=1000/14d (~3/hr) keeps Poisson CI on cold rate at ~±5% near the 40% threshold.
4
+ export const metadata = {
5
+ id: 'cold_start',
6
+ threshold: 'coldPct > 0.4 AND total >= 1000',
7
+ billingDimension: 'function-duration',
8
+ scope: 'route',
9
+ sourceCitation: 'vercel-optimize gate threshold',
10
+ description:
11
+ 'Routes where > 40% of invocations are cold-start, at meaningful traffic (>=1,000 total invocations in window). Cold starts add 200-800ms per request and break the perceived latency budget on cache-miss paths. The 40% threshold is where cold-rate becomes a real signal vs Poisson noise on serverless. Sourced from vercel.function_invocation.count grouped by function_start_type.',
12
+ };
13
+
14
+ export function gate(signals) {
15
+ const cs = extractColdStarts(signals);
16
+ return cs
17
+ .filter((r) => r.coldPct > 0.4 && r.total >= 1000)
18
+ .map((r) => ({
19
+ kind: metadata.id,
20
+ scope: 'route',
21
+ route: r.route,
22
+ files: [],
23
+ priority: Math.round(r.total * r.coldPct),
24
+ confidence: 0.92,
25
+ o11ySignal: `cold=${(r.coldPct * 100).toFixed(0)}%,inv=${r.total}`,
26
+ reason: 'high cold-start rate on hot route',
27
+ question: `What initialization or bundle overhead makes ${r.route} cold-start ${(r.coldPct * 100).toFixed(0)}% of ${r.total} invocations?`,
28
+ evidence: { metric: 'fnStartTypeByRoute', route: r.route, coldPct: r.coldPct, total: r.total, coldCount: r.coldCount ?? null },
29
+ }));
30
+ }
31
+
32
+ function extractColdStarts(signals) {
33
+ const live = signals.metrics?.fnStartTypeByRoute;
34
+ if (Array.isArray(live?.rows) && live.rows.some((r) => 'coldCount' in r || 'coldPct' in r)) {
35
+ return live.rows
36
+ .filter((r) => r.route)
37
+ .map((r) => ({
38
+ route: r.route,
39
+ total: r.total ?? 0,
40
+ coldCount: r.coldCount ?? 0,
41
+ coldPct: r.coldPct ?? 0,
42
+ }));
43
+ }
44
+
45
+ // Legacy fixture: pre-derived coldStartByRoute rows.
46
+ const direct = signals.metrics?.coldStartByRoute;
47
+ if (Array.isArray(direct?.rows)) {
48
+ return direct.rows
49
+ .filter((r) => r.route)
50
+ .map((r) => ({ route: r.route, coldPct: r.coldPct ?? 0, total: r.total ?? 0 }));
51
+ }
52
+
53
+ // Older legacy fixture: series + summary shape.
54
+ const legacy = signals.metrics?.coldStarts;
55
+ if (Array.isArray(legacy?.series)) {
56
+ return legacy.series
57
+ .map((s) => {
58
+ const total = s.summary?.count ?? 0;
59
+ const coldCount = s.summary?.coldCount ?? s.summary?.sum ?? 0;
60
+ return { route: s.groupValues?.route, total, coldPct: total > 0 ? coldCount / total : 0 };
61
+ })
62
+ .filter((r) => r.route);
63
+ }
64
+
65
+ return [];
66
+ }
@@ -0,0 +1,79 @@
1
+ const VALID_SCOPES = new Set(['route', 'file', 'account']);
2
+
3
+ export class CandidateContractError extends Error {
4
+ constructor(errors) {
5
+ super(`gate candidate contract failed:\n${errors.map((e) => `- ${e}`).join('\n')}`);
6
+ this.name = 'CandidateContractError';
7
+ this.errors = errors;
8
+ }
9
+ }
10
+
11
+ export function validateCandidates(candidates, ctx = {}) {
12
+ if (!Array.isArray(candidates)) {
13
+ throw new CandidateContractError([`${ctx.source ?? 'gate'}: expected candidate array`]);
14
+ }
15
+ const errors = [];
16
+ for (let i = 0; i < candidates.length; i++) {
17
+ errors.push(...validateCandidate(candidates[i], { ...ctx, index: i }).errors);
18
+ }
19
+ if (errors.length > 0) throw new CandidateContractError(errors);
20
+ return candidates;
21
+ }
22
+
23
+ export function validateCandidate(candidate, ctx = {}) {
24
+ const label = candidateLabel(candidate, ctx);
25
+ const errors = [];
26
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
27
+ return { ok: false, errors: [`${label}: candidate must be an object`] };
28
+ }
29
+
30
+ if (!nonEmptyString(candidate.kind)) errors.push(`${label}: kind must be a non-empty string`);
31
+ if (!VALID_SCOPES.has(candidate.scope)) {
32
+ errors.push(`${label}: scope must be one of route, file, account`);
33
+ }
34
+ if (!Number.isFinite(candidate.priority)) errors.push(`${label}: priority must be a finite number`);
35
+ if (!Number.isFinite(candidate.confidence)) errors.push(`${label}: confidence must be a finite number`);
36
+ if (Array.isArray(candidate.files)) {
37
+ if (!candidate.files.every((f) => typeof f === 'string' && f.length > 0)) {
38
+ errors.push(`${label}: files must contain only non-empty strings`);
39
+ }
40
+ } else {
41
+ errors.push(`${label}: files must be an array`);
42
+ }
43
+ if (!nonEmptyString(candidate.reason)) errors.push(`${label}: reason must be a non-empty string`);
44
+ if (!nonEmptyString(candidate.question)) errors.push(`${label}: question must be a non-empty string`);
45
+
46
+ if (candidate.scope === 'route') {
47
+ const hasRoute = nonEmptyString(candidate.route);
48
+ const hasHostname = nonEmptyString(candidate.hostname);
49
+ if (!hasRoute && !hasHostname) {
50
+ errors.push(`${label}: route-scoped candidates must set route or hostname`);
51
+ }
52
+ }
53
+ if (candidate.scope === 'file') {
54
+ if (candidate.route != null || candidate.hostname != null) {
55
+ errors.push(`${label}: file-scoped candidates must not set route or hostname`);
56
+ }
57
+ if (!Array.isArray(candidate.files) || candidate.files.length === 0) {
58
+ errors.push(`${label}: file-scoped candidates must include at least one file`);
59
+ }
60
+ }
61
+ if (candidate.scope === 'account') {
62
+ if (candidate.route != null || candidate.hostname != null) {
63
+ errors.push(`${label}: account-scoped candidates must not set route or hostname`);
64
+ }
65
+ }
66
+
67
+ return { ok: errors.length === 0, errors };
68
+ }
69
+
70
+ function candidateLabel(candidate, ctx) {
71
+ const source = ctx.source ?? 'gate';
72
+ const index = ctx.index == null ? '?' : ctx.index;
73
+ const kind = candidate?.kind ?? '?';
74
+ return `${source}[${index}] ${kind}`;
75
+ }
76
+
77
+ function nonEmptyString(value) {
78
+ return typeof value === 'string' && value.trim().length > 0;
79
+ }
@@ -0,0 +1,87 @@
1
+ // Thresholds are Google's "Poor" band (https://web.dev/articles/vitals): LCP p75 > 2500ms, INP > 200ms, CLS > 0.1.
2
+ // When Speed Insights isn't wired up the metrics come back empty and the gate is a no-op.
3
+ import { withRouteShapeWarnings } from '../route-normalize.mjs';
4
+
5
+ export const metadata = {
6
+ id: 'cwv_poor',
7
+ threshold: 'LCP p75>2500 OR INP p75>200 OR CLS p75>0.1, AND speed_insights count > 50',
8
+ billingDimension: 'speed-insights',
9
+ scope: 'route',
10
+ sourceCitation: 'https://web.dev/articles/vitals',
11
+ description:
12
+ 'Routes where Core Web Vitals fall into Google\'s "Poor" band on real-user traffic. LCP > 2500ms, INP > 200ms, or CLS > 0.1 each hurt SEO and conversion. Surfaces one candidate per (route, metric) pair to keep recommendations focused.',
13
+ };
14
+
15
+ // Below this floor p75 is too noisy to act on.
16
+ const MIN_PER_ROUTE_SAMPLES = 50;
17
+
18
+ export function gate(signals) {
19
+ const totalSamples = sumRows(signals.metrics?.cwvCount?.rows);
20
+ if (totalSamples === 0) return [];
21
+
22
+ const countByRoute = byRoute(signals.metrics?.cwvCountByRoute?.rows);
23
+ const lcpBy = byRoute(signals.metrics?.cwvLcpByRoute?.rows);
24
+ const inpBy = byRoute(signals.metrics?.cwvInpByRoute?.rows);
25
+ const clsBy = byRoute(signals.metrics?.cwvClsByRoute?.rows);
26
+
27
+ const routes = new Set([...lcpBy.keys(), ...inpBy.keys(), ...clsBy.keys()]);
28
+ const out = [];
29
+ for (const route of routes) {
30
+ const routeSamples = countByRoute.get(route) ?? 0;
31
+ if (routeSamples < MIN_PER_ROUTE_SAMPLES) continue;
32
+ const lcp = lcpBy.get(route);
33
+ const inp = inpBy.get(route);
34
+ const cls = clsBy.get(route);
35
+ const issues = [];
36
+ if (lcp != null && lcp > 2500) issues.push({ metric: 'LCP', value: Math.round(lcp), threshold: 2500, unit: 'ms' });
37
+ if (inp != null && inp > 200) issues.push({ metric: 'INP', value: Math.round(inp), threshold: 200, unit: 'ms' });
38
+ if (cls != null && cls > 0.1) issues.push({ metric: 'CLS', value: round2(cls), threshold: 0.1, unit: '' });
39
+ if (issues.length === 0) continue;
40
+
41
+ const summary = issues.map((i) => `${i.metric}=${i.value}${i.unit}`).join(',');
42
+ out.push(withRouteShapeWarnings({
43
+ kind: metadata.id,
44
+ scope: 'route',
45
+ route,
46
+ files: [],
47
+ priority: issues.reduce((s, i) => s + ratioOverThreshold(i), 0) * 10,
48
+ confidence: 0.82,
49
+ o11ySignal: summary,
50
+ reason: 'real-user Core Web Vitals in poor band',
51
+ question: `On ${route}, ${summary}. Which client-side work (bundle weight, blocking scripts, layout shifts, hydration) is responsible, and which change would land first?`,
52
+ evidence: {
53
+ metric: 'cwv',
54
+ route,
55
+ lcpMs: lcp != null ? Math.round(lcp) : null,
56
+ inpMs: inp != null ? Math.round(inp) : null,
57
+ cls: cls != null ? round2(cls) : null,
58
+ issues,
59
+ totalSpeedInsightsSamples: totalSamples,
60
+ routeSpeedInsightsSamples: routeSamples,
61
+ },
62
+ }, signals));
63
+ }
64
+ return out;
65
+ }
66
+
67
+ function byRoute(rows) {
68
+ const m = new Map();
69
+ for (const r of rows ?? []) {
70
+ if (!r.route || r.value == null) continue;
71
+ m.set(r.route, r.value);
72
+ }
73
+ return m;
74
+ }
75
+
76
+ function sumRows(rows) {
77
+ if (!Array.isArray(rows)) return 0;
78
+ return rows.reduce((s, r) => s + (r.value ?? 0), 0);
79
+ }
80
+
81
+ function round2(n) {
82
+ return Math.round(n * 100) / 100;
83
+ }
84
+
85
+ function ratioOverThreshold(i) {
86
+ return i.value / (i.threshold || 1);
87
+ }
@@ -0,0 +1,55 @@
1
+ // Volume floor pairs p75 with call_count so a single 5s cron/day doesn't fire the gate.
2
+ const MIN_CALL_COUNT = 500;
3
+
4
+ export const metadata = {
5
+ id: 'external_api_slow',
6
+ threshold: `p75Ms > 2000 AND callCount >= ${MIN_CALL_COUNT}`,
7
+ billingDimension: 'function-duration',
8
+ scope: 'route',
9
+ sourceCitation: 'vercel-optimize gate threshold',
10
+ description:
11
+ 'External API hostnames with p75 latency above 2 seconds AND at least 500 calls in the window. External API latency is a primary driver of function duration cost when the upstream is on a hot path; a single slow stale call isn\'t worth recommending against.',
12
+ };
13
+
14
+ export function gate(signals) {
15
+ const apis = extractExternalApis(signals);
16
+ const calls = extractCallCounts(signals);
17
+ return apis
18
+ .map((a) => ({ ...a, callCount: calls.get(a.hostname) ?? 0 }))
19
+ .filter((a) => a.p75Ms > 2000 && a.callCount >= MIN_CALL_COUNT)
20
+ .map((a) => ({
21
+ kind: metadata.id,
22
+ scope: 'route',
23
+ route: null,
24
+ files: [],
25
+ hostname: a.hostname,
26
+ // Weight by latency × call volume so 100k-call/2.1s outranks 1k-call/8s.
27
+ priority: Math.round((a.p75Ms * a.callCount) / 1000),
28
+ confidence: 0.88,
29
+ o11ySignal: `host=${a.hostname},p75=${a.p75Ms}ms,calls=${a.callCount}`,
30
+ reason: 'slow external dependency on hot path',
31
+ question: `Which routes call ${a.hostname} (p75=${a.p75Ms}ms across ${a.callCount} calls), and can the call be parallelized, cached, or moved off the critical path?`,
32
+ evidence: { metric: 'externalApiP75', hostname: a.hostname, p75Ms: a.p75Ms, callCount: a.callCount },
33
+ }));
34
+ }
35
+
36
+ function extractExternalApis(signals) {
37
+ const m = signals.metrics?.externalApiP75;
38
+ if (!m?.ok && !Array.isArray(m?.rows)) return [];
39
+ return (m?.rows ?? [])
40
+ .map((r) => ({
41
+ hostname: r.origin_hostname,
42
+ p75Ms: Math.round(r.value ?? 0),
43
+ }))
44
+ .filter((a) => a.hostname);
45
+ }
46
+
47
+ function extractCallCounts(signals) {
48
+ const m = signals.metrics?.externalApiCount;
49
+ const out = new Map();
50
+ if (!m) return out;
51
+ for (const r of m.rows ?? []) {
52
+ if (r?.origin_hostname) out.set(r.origin_hostname, r.value ?? 0);
53
+ }
54
+ return out;
55
+ }
@@ -0,0 +1,73 @@
1
+ import { canonicalizeRoute } from '../route-normalize.mjs';
2
+
3
+ export const FLAGS_ENDPOINT = '/.well-known/vercel/flags';
4
+ export const VERCEL_FLAGS_PACKAGES = [
5
+ '@vercel/flags',
6
+ '@vercel/flags/next',
7
+ '@vercel/flags/sveltekit',
8
+ '@vercel/flags/nuxt',
9
+ ];
10
+ export const WORKFLOW_ENDPOINT_PREFIXES = [
11
+ '/.well-known/workflow',
12
+ '/api/.well-known/workflow',
13
+ ];
14
+
15
+ export function applyHardGates(candidates, signals = {}) {
16
+ const allowed = [];
17
+ const gated = [];
18
+ for (const candidate of candidates) {
19
+ if (isFlagsEndpointCandidate(candidate)) {
20
+ gated.push({
21
+ ...candidate,
22
+ gatedReason: flagsEndpointReason(signals),
23
+ });
24
+ continue;
25
+ }
26
+ if (isWorkflowRuntimeEndpointCandidate(candidate)) {
27
+ gated.push({
28
+ ...candidate,
29
+ gatedReason: workflowEndpointReason(signals),
30
+ });
31
+ continue;
32
+ }
33
+ allowed.push(candidate);
34
+ }
35
+ return { allowed, gated };
36
+ }
37
+
38
+ export function isFlagsEndpointCandidate(candidate) {
39
+ if (!candidate || candidate.scope === 'account') return false;
40
+ const route = normalizeRoute(candidate.route);
41
+ return route === FLAGS_ENDPOINT;
42
+ }
43
+
44
+ export function isWorkflowRuntimeEndpointCandidate(candidate) {
45
+ if (!candidate || candidate.scope === 'account') return false;
46
+ const route = normalizeRoute(candidate.route);
47
+ if (!route) return false;
48
+ return WORKFLOW_ENDPOINT_PREFIXES.some((prefix) => (
49
+ route === prefix || route.startsWith(`${prefix}/`)
50
+ ));
51
+ }
52
+
53
+ function normalizeRoute(route) {
54
+ if (typeof route !== 'string') return null;
55
+ const normalized = canonicalizeRoute(route).replace(/\/+$/, '');
56
+ return normalized === '' ? '/' : normalized;
57
+ }
58
+
59
+ function flagsEndpointReason(signals) {
60
+ const packages = signals.stack?.vercelFlagsPackages;
61
+ if (Array.isArray(packages) && packages.length > 0) {
62
+ return `hardGated: ${FLAGS_ENDPOINT} is the Vercel Flags endpoint (${packages.join(', ')} detected), not an optimization target`;
63
+ }
64
+ return `hardGated: ${FLAGS_ENDPOINT} is the Vercel Flags endpoint, not an optimization target`;
65
+ }
66
+
67
+ function workflowEndpointReason(signals) {
68
+ const packages = signals.stack?.workflowPackages;
69
+ if (Array.isArray(packages) && packages.length > 0) {
70
+ return `hardGated: Vercel Workflow runtime endpoint (${packages.join(', ')} detected); long-running step/flow requests are expected orchestration, not an app-route optimization target`;
71
+ }
72
+ return 'hardGated: Vercel Workflow runtime endpoint; long-running step/flow requests are expected orchestration, not an app-route optimization target';
73
+ }
@@ -0,0 +1,45 @@
1
+ import * as uncachedRoute from './uncached-route.mjs';
2
+ import * as slowRoute from './slow-route.mjs';
3
+ import * as routeErrors from './route-errors.mjs';
4
+ import * as coldStart from './cold-start.mjs';
5
+ import * as isrOverrevalidation from './isr-overrevalidation.mjs';
6
+ import * as cwvPoor from './cwv-poor.mjs';
7
+ import * as platformFluidCompute from './platform-fluid-compute.mjs';
8
+ import * as platformBotProtection from './platform-bot-protection.mjs';
9
+ import * as middlewareHeavy from './middleware-heavy.mjs';
10
+ import * as externalApiSlow from './external-api-slow.mjs';
11
+ import * as scannerDriven from './scanner-driven.mjs';
12
+ import * as observabilityEventsAttribution from './observability-events-attribution.mjs';
13
+ import * as usageSpikeTriage from './usage-spike-triage.mjs';
14
+ import * as buildMinutesFanout from './build-minutes-fanout.mjs';
15
+ import * as regionMisconfig from './region-misconfig.mjs';
16
+
17
+ // Intentionally NOT registered:
18
+ // - `oversized_memory`: Fluid Compute floor is 2GB; per-route memory right-sizing isn't a customer lever.
19
+ // - `deploy_regression`: overlaps Vercel Agent Investigations; `vercel inspect` 404s across teams. slow_route deep-dive already carries per-deployment p95 trend.
20
+ export const gates = [
21
+ uncachedRoute,
22
+ slowRoute,
23
+ routeErrors,
24
+ coldStart,
25
+ isrOverrevalidation,
26
+ cwvPoor,
27
+ externalApiSlow,
28
+ scannerDriven,
29
+ // Account-scoped last so platform-scoped sort doesn't dilute code-scoped priority ordering during budget application.
30
+ platformFluidCompute,
31
+ platformBotProtection,
32
+ middlewareHeavy,
33
+ observabilityEventsAttribution,
34
+ usageSpikeTriage,
35
+ buildMinutesFanout,
36
+ regionMisconfig,
37
+ ];
38
+
39
+ // Overridable via `--max-candidates N` or `VERCEL_OPTIMIZE_MAX_CANDIDATES` (accepts `all`).
40
+ // `MAX_CODE_CANDIDATES` is a back-compat alias for tests importing the old name.
41
+ export const DEFAULT_MAX_CODE_CANDIDATES = 6;
42
+ export const MAX_CODE_CANDIDATES = DEFAULT_MAX_CODE_CANDIDATES;
43
+
44
+ // Bump on any threshold change so report + iteration baselines can detect gate-logic drift.
45
+ export const GATE_VERSION = '1.8.0';
@@ -0,0 +1,62 @@
1
+ // ISR writes re-execute the page render. A w/r ratio above 0.5 means writes are
2
+ // happening at least once for every two reads — high enough for the default
3
+ // audit to spend investigation budget. writes>100 avoids flapping on quiet routes.
4
+ export const metadata = {
5
+ id: 'isr_overrevalidation',
6
+ threshold: 'writes/reads > 0.5 AND writes > 100',
7
+ billingDimension: 'isr',
8
+ scope: 'route',
9
+ sourceCitation: 'https://vercel.com/docs/incremental-static-regeneration',
10
+ description:
11
+ 'ISR routes with > 1 write per 2 reads. The revalidate interval is too aggressive relative to read traffic — many reads pay to regenerate. Investigate whether the page can tolerate a longer revalidate window or on-demand revalidation via revalidateTag.',
12
+ };
13
+
14
+ export function gate(signals) {
15
+ const rows = extractRows(signals);
16
+ return rows
17
+ .filter((r) => r.writes > 100 && r.reads > 0 && r.writes / r.reads > 0.5)
18
+ .map((r) => {
19
+ const ratio = r.writes / r.reads;
20
+ return {
21
+ kind: metadata.id,
22
+ scope: 'route',
23
+ route: r.route,
24
+ files: [],
25
+ priority: Math.round(r.writes),
26
+ confidence: 0.88,
27
+ o11ySignal: `writes=${r.writes},reads=${r.reads},w/r=${ratio.toFixed(2)}`,
28
+ reason: 'ISR revalidating faster than read traffic justifies',
29
+ question: `On ${r.route}, ${r.writes} ISR writes against ${r.reads} reads (${(ratio * 100).toFixed(0)} writes per 100 reads) — what is the current revalidate interval and can it be lengthened or switched to on-demand?`,
30
+ evidence: {
31
+ metric: 'isrWritesByRoute',
32
+ route: r.route,
33
+ writes: r.writes,
34
+ reads: r.reads,
35
+ ratio,
36
+ },
37
+ };
38
+ });
39
+ }
40
+
41
+ function extractRows(signals) {
42
+ const writes = signals.metrics?.isrWritesByRoute?.rows ?? [];
43
+ const reads = signals.metrics?.isrReadsByRoute?.rows ?? [];
44
+
45
+ const writeByRoute = new Map();
46
+ for (const r of writes) {
47
+ if (!r.route) continue;
48
+ writeByRoute.set(r.route, (writeByRoute.get(r.route) ?? 0) + (r.value ?? 0));
49
+ }
50
+ const readByRoute = new Map();
51
+ for (const r of reads) {
52
+ if (!r.route) continue;
53
+ readByRoute.set(r.route, (readByRoute.get(r.route) ?? 0) + (r.value ?? 0));
54
+ }
55
+
56
+ const routes = new Set([...writeByRoute.keys(), ...readByRoute.keys()]);
57
+ return [...routes].map((route) => ({
58
+ route,
59
+ writes: writeByRoute.get(route) ?? 0,
60
+ reads: readByRoute.get(route) ?? 0,
61
+ }));
62
+ }
@@ -0,0 +1,51 @@
1
+ // Middleware runs in front of every matching request and is billed as edge invocations.
2
+ // If >50% of traffic hits middleware, the matcher is probably broader than necessary.
3
+ export const metadata = {
4
+ id: 'middleware_heavy',
5
+ threshold: 'middlewareInv/totalInv > 0.5 AND middlewareInv > 1000',
6
+ billingDimension: 'edge-requests',
7
+ scope: 'account',
8
+ sourceCitation: 'https://nextjs.org/docs/app/building-your-application/routing/middleware',
9
+ description:
10
+ 'Middleware invocations cover > 50% of total requests at non-trivial volume. The matcher is probably broader than necessary; narrow it to the paths that actually need auth/rewrites/headers.',
11
+ };
12
+
13
+ export function gate(signals) {
14
+ const middlewareInv = sumRows(signals.metrics?.middlewareCount?.rows);
15
+ if (middlewareInv < 1000) return [];
16
+
17
+ const totalInv = sumRows(signals.metrics?.requestsByRouteCache?.rows);
18
+ if (totalInv === 0) return [];
19
+
20
+ const ratio = middlewareInv / totalInv;
21
+ if (ratio <= 0.5) return [];
22
+
23
+ const top = [...(signals.metrics?.middlewareCount?.rows ?? [])]
24
+ .filter((r) => r.request_path)
25
+ .sort((a, b) => (b.value ?? 0) - (a.value ?? 0))
26
+ .slice(0, 5)
27
+ .map((r) => ({ request_path: r.request_path, count: r.value ?? 0 }));
28
+
29
+ return [{
30
+ kind: metadata.id,
31
+ scope: 'account',
32
+ files: [],
33
+ priority: Math.round(middlewareInv / 1000),
34
+ confidence: 0.84,
35
+ o11ySignal: `middleware_inv=${middlewareInv},total_req=${totalInv},ratio=${(ratio * 100).toFixed(0)}%`,
36
+ reason: 'middleware ran on more than half of all requests',
37
+ question: `Middleware invocations (${middlewareInv}) are ${(ratio * 100).toFixed(0)}% of all requests (${totalInv}). Which paths in middleware.ts require interception, and can the matcher be narrowed to exclude static assets, images, and routes that do not need rewriting?`,
38
+ evidence: {
39
+ metric: 'middlewareCount',
40
+ middlewareInv,
41
+ totalInv,
42
+ ratio,
43
+ topPaths: top,
44
+ },
45
+ }];
46
+ }
47
+
48
+ function sumRows(rows) {
49
+ if (!Array.isArray(rows)) return 0;
50
+ return rows.reduce((s, r) => s + (r.value ?? 0), 0);
51
+ }
@@ -0,0 +1,56 @@
1
+ // Observability Events is the metered SKU under Observability Plus.
2
+ // Threshold at >20% surfaces material spend; >30% is the critical band.
3
+ // Drivers correlate with low cache hit rate, high middleware invocation, and high custom-span cardinality.
4
+ export const metadata = {
5
+ id: 'observability_events_attribution',
6
+ threshold: 'observabilityEventsShare > 0.20 (critical at > 0.30)',
7
+ billingDimension: 'observability-events',
8
+ scope: 'account',
9
+ sourceCitation: 'vercel-optimize gate threshold',
10
+ description:
11
+ 'Observability Events line item exceeds 20% of total billed cost. High share usually traces to low cache hit rate, middleware-heavy traffic, or unconstrained custom-span cardinality. No sampling lever exists for Observability Plus; reduce upstream invocations instead.',
12
+ };
13
+
14
+ const EVENTS_RE = /^Observability Events$/i;
15
+
16
+ export function gate(signals) {
17
+ const services = signals?.usage?.services;
18
+ if (!Array.isArray(services) || services.length === 0) return [];
19
+
20
+ const total = sumBilled(services);
21
+ if (total <= 0) return [];
22
+
23
+ const eventsBilled = services
24
+ .filter((s) => EVENTS_RE.test(String(s?.name ?? '')))
25
+ .reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0);
26
+ if (eventsBilled <= 0) return [];
27
+
28
+ const share = eventsBilled / total;
29
+ if (share <= 0.20) return [];
30
+
31
+ const critical = share > 0.30;
32
+
33
+ return [{
34
+ kind: metadata.id,
35
+ scope: 'account',
36
+ files: [],
37
+ priority: critical ? 70 : 55,
38
+ confidence: 0.82,
39
+ o11ySignal: `observability_events_share=${(share * 100).toFixed(0)}%`,
40
+ reason: critical
41
+ ? 'observability events exceed 30% of total billed cost'
42
+ : 'observability events exceed 20% of total billed cost',
43
+ question: `Observability Events are ${(share * 100).toFixed(0)}% of the bill. Which routes drive event volume — low-cache-hit traffic, broad middleware invocation, or high custom-span cardinality — and can event volume be reduced upstream of the meter?`,
44
+ evidence: {
45
+ metric: 'usage.services',
46
+ eventsBilled,
47
+ totalBilled: total,
48
+ observabilityEventsShare: share,
49
+ critical,
50
+ },
51
+ }];
52
+ }
53
+
54
+ function sumBilled(services) {
55
+ return services.reduce((acc, s) => acc + Number(s.billedCost ?? s.cost ?? 0), 0);
56
+ }