<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: puppet</title>
    <description>The latest articles tagged 'puppet' on DEV Community.</description>
    <link>https://dev.to/t/puppet</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/puppet"/>
    <language>en</language>
    <item>
      <title>New Benchmarks now Available for Puppet SCE for Linux</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Thu, 30 Jul 2026 11:49:16 +0000</pubDate>
      <link>https://dev.to/puppet/new-benchmarks-now-available-for-puppet-sce-for-linux-34jf</link>
      <guid>https://dev.to/puppet/new-benchmarks-now-available-for-puppet-sce-for-linux-34jf</guid>
      <description>&lt;p&gt;Security Compliance Enforcement (SCE) for Linux 2.8.0 is now available, delivering updated CIS Benchmark coverage along with improvements to policy enforcement, auditing, and logging reliability. These updates help organizations strengthen compliance programs, reduce operational risk, and maintain confidence that Linux systems remain aligned with security and governance requirements.&amp;nbsp;SCE for Linux is available as part of Puppet Core and Puppet Enterprise Advanced.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Upgrade?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Stay aligned with current CIS guidance&amp;nbsp;through updated benchmark coverage for major enterprise Linux distributions.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Improve audit readiness&amp;nbsp;with expanded benchmark coverage and updated compliance checks.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Reduce operational risk&amp;nbsp;through fixes that improve firewall, integrity monitoring, and logging behavior.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Increase confidence in enforcement&amp;nbsp;by addressing issues that could prevent configurations from being applied as intended.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Updated CIS Benchmark support for RHEL 8, AlmaLinux 8, Oracle Linux 8, and Rocky Linux 8.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Enhancements that improve alignment with current benchmark recommendations across system hardening, auditing, networking, and account management.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Reliability improvements for firewall zone enforcement, AIDE-based integrity monitoring, and log forwarding.&lt;/li&gt;
&lt;li&gt;  Updated platform dependencies to support ongoing compatibility and maintainability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security and Maintenance Updates
&lt;/h2&gt;

&lt;p&gt;This release includes maintenance updates and issue resolutions that help improve platform stability, reduce security risk, and support more reliable compliance operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Review the release notes for complete details and upgrade guidance, then plan your upgrade to take advantage of the latest benchmark coverage and reliability improvements.&lt;/p&gt;

&lt;p&gt;If you're new to SCE, visit the documentation page to learn how it helps automate compliance assessments, policy enforcement, and ongoing audit readiness across enterprise Linux environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.puppet.com/sce/current/linux/scel_relnotes_280.htm" rel="noopener noreferrer"&gt;SCE for Linux Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.puppet.com/sce/current/intro/sce_intro.htm" rel="noopener noreferrer"&gt;Learn more about SCE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>puppet</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Building with AI: Our Approach to Responsible Agentic Development in Open Source</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:36:14 +0000</pubDate>
      <link>https://dev.to/puppet/building-with-ai-our-approach-to-responsible-agentic-development-in-open-source-5f2b</link>
      <guid>https://dev.to/puppet/building-with-ai-our-approach-to-responsible-agentic-development-in-open-source-5f2b</guid>
      <description>&lt;p&gt;The tech world has been building up towards the shift to a&amp;nbsp;&lt;strong&gt;fully agentic development life cycle&lt;/strong&gt;&amp;nbsp;for a few years now. AI is changing how software gets built.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Across the Puppet ecosystem, we're seeing a shift toward more agentic engineering workflows. AI helps generate code, shape documentation, and accelerate how Puppet modules evolve. This brings real benefits in speed and consistency, but it also raises important questions from the community:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  How are AI-generated changes validated?&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  How do you ensure consistency across modules?&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  What does this mean for contributors and maintainers?&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are exactly the kinds of questions we should be asking! This article will outline how Perforce and the Puppet team are approaching the use of AI in our open source modules and repositories.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Build Trust in AI-Assisted Contributions&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;At Perforce, AI is a core part of our process and our teams operate within a&amp;nbsp;&lt;strong&gt;defined, governed framework&lt;/strong&gt;&amp;nbsp;for development. We don’t rely on trust in the tool itself. We rely on the processes around it.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Whether a change is written by a person, generated with AI, or some mix of both, they are held to the same standards before it’s accepted and released.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In practice, that means:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Human review is always the gate:&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
Every change is reviewed by maintainers. AI can assist, but it doesn’t replace accountability.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI works within established patterns:&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
AI-generated code isn’t created in isolation. It’s guided by the same module structures, conventions, and expectations that already exist across the ecosystem.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Validation is continuous and enforced:&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
AI doesn’t change our standards. It reinforces them. AI-generated changes go through the same checks as any other contribution:&amp;nbsp;

&lt;ul&gt;
&lt;li&gt;  Test suites&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Integration validation&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Functional verification&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI output is a starting point, not a final artifact:&lt;/strong&gt;&amp;nbsp;
Generated code is iterated on, refined, and aligned before acceptance. We treat AI as an accelerator, not an authority.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The community plays an important role&lt;/strong&gt;&amp;nbsp;
Open source means visibility. The community can review changes, raise issues, and contribute ideas. That feedback loop adds another layer of resilience and helps shape how these workflows evolve.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At a high level, our approach is simple:&amp;nbsp;&lt;strong&gt;the outcome matters more than how the code was created.&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Community Contributions and AI&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;Community members are not required to use code assist tools when providing contributions to Puppet open source projects, even when those projects contain frameworks or files designed to support coding agents.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Contributions can be made using any tools or workflows that work best for you and there is no expectation to match the Perforce technology stack. While the Puppet team will be adding instructions, skills, and other artifacts to support agentic workflows, including tools like Claude Code, these are provided as optional enhancements, not requirements.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When contributions are reviewed, the same standards apply regardless of how they were created. This includes contributions that contain AI-generated code, are submitted by AI agents, or have been tested or reviewed using AI tools. We welcome contributions in any form, but all submissions go through the same review and approval process before they are accepted and released.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintaining Consistency Across the Ecosystem&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;Another important question we hear is how we avoid duplication or inconsistency as AI becomes part of the workflow. This is where&amp;nbsp;&lt;strong&gt;ecosystem-level thinking and governance&lt;/strong&gt;&amp;nbsp;matter most and where we will be incrementally improving our engineering patterns.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Here’s how we approach that:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Changes are evaluated in context&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
We don’t review contributions in isolation. We look at how they relate to existing modules and whether similar functionality already exists.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Reuse is prioritized over duplication&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
When reviewing changes, we actively ask whether something can be reused instead of reimplemented. This helps maintain DRY principles across the ecosystem—not just within a single module.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Shared patterns guide development&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
AI-assisted changes are aligned to standard module structures, established design patterns, and proven implementation approaches. This helps ensure consistency even as contribution velocity increases.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Documentation is treated as part of the system&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
Generated documentation and supporting artifacts are treated as first-class components. This improves clarity, supports reuse, and helps maintain long-term consistency across modules.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Governance Is Built In, Not Bolted On&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;Building with AI tools is not left to individual discretion. AI usage at Perforce follows&amp;nbsp;&lt;a href="https://trust.perforce.com/resources?s=h4ucgvqdld3suqiwml8o4d&amp;amp;name=ai-governance-at-perforce" rel="noopener noreferrer"&gt;structured processes&lt;/a&gt;&amp;nbsp;designed to ensure quality, security, and accountability across the lifecycle.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Here’s what that looks like for Puppet open source teams:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;AI tools and workflows are intentionally reviewed&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
We evaluate how AI tools are used within our development process, including how they interact with existing systems and patterns. Only tools approved for use by the Perforce AI governance team can be used for agentic development by Perforce employees.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Risks and dependencies are actively managed&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
Using agentic development flows introduces new considerations from code duplication to unintended behaviors. These are identified, reviewed, and addressed as part of normal development workflows.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Security and compliance are built into the process&lt;/strong&gt;&amp;nbsp;&amp;nbsp;
AI-assisted development operates within the same security, permission, and compliance boundaries as any other contribution.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI usage is part of a broader&amp;nbsp;&lt;a href="https://www.perforce.com/press-releases/%20ISO-42001-Certification" rel="noopener noreferrer"&gt;&lt;strong&gt;AI management system aligned with ISO 42001&lt;/strong&gt;,&lt;/a&gt;&amp;nbsp;ensuring responsible use across the lifecycle.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;We also recognize the broader concerns around AI. We're deliberate about how and where AI is used. At its core, governance is about maintaining control while enabling progress. AI should operate within the systems teams already trust, not bypass them.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Commitment to the Open Source Community &amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;This evolution isn’t happening in isolation. The Puppet modules team works within an open source ecosystem and it’s important that the community is informed about AI usage and included so that all members of the community are able to provide feedback as these processes evolve.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our goal is to provide:&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Clear communication about AI-assisted changes&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Transparency in how contributions are reviewed&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  Ongoing dialogue as workflows evolve&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a new world of software delivery that we all maintainers and contributors are collaborating in, and we'll need to work together to find the right process for everyone.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means Going Forward&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;The agentic software development lifecycle is going to continue to shift and grow. AI will continue to play a growing role in how modules are developed and maintained.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In your favorite Puppetlabs modules, you are going to see:&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Structural documentation, skills, instructions, and other frameworks to support agentic development&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  More AI-assisted contributions to open source repos&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  More structured documentation that aligns across the ecosystem&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  An increase in iteration cycles as agentic workflows grow and stabilize&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one thing that won't change:&amp;nbsp;&lt;strong&gt;Every contribution will be held to the same standards of quality, consistency, and trust.&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In the end, trust in AI doesn’t come from the model. It comes from the systems, processes, and people behind it.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;We want to hear from the community as we continue to evolve these practices and develop a solid agentic-supported workflow for our open source community. We’d love your feedback, so please share your thoughts here or in the Puppet Community Slack.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;These additional resources could be helpful if you are interested in this topic and want to learn more!&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://trust.perforce.com/resources?s=h4ucgvqdld3suqiwml8o4d&amp;amp;name=ai-governance-at-perforce" rel="noopener noreferrer"&gt;AI Governance at Perforce&lt;/a&gt;&amp;nbsp;(trust.perforce.com)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.perforce.com/press-releases/%20ISO-42001-Certification" rel="noopener noreferrer"&gt;Perforce AI Products and Features Achive ISO 42001 Certification&lt;/a&gt;&amp;nbsp;(perforce.com)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤖 AI Disclosure
&lt;/h2&gt;

&lt;p&gt;You'll probably notice a few tell-tale structural suggestions from AI on this one. I experimented with drafting with AI on this one, using our AI Governance documentation and some context I provided about how we work on the Puppet team. From that draft, it has gone through multiple human reviews and a LOT of editing and rework, with a final review by an AI agent for brand voice alignment. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>puppet</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Integrar los datos de fichaje con el software de nóminas: el flujo real (export-validación-gestoría)</title>
      <dc:creator>Nucleo360</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:17:52 +0000</pubDate>
      <link>https://dev.to/nucleo360/integrar-los-datos-de-fichaje-con-el-software-de-nominas-el-flujo-real-export-validacion-gestoria-524b</link>
      <guid>https://dev.to/nucleo360/integrar-los-datos-de-fichaje-con-el-software-de-nominas-el-flujo-real-export-validacion-gestoria-524b</guid>
      <description>&lt;p&gt;Desde el 12 de mayo de 2019, el Real Decreto-ley 8/2019 (BOE-A-2019-3481) obliga a todas las empresas en España a llevar un registro horario diario de su plantilla. Ese registro no vive aislado: cada mes, alguien tiene que convertir horas trabajadas, horas extra y ausencias en una nómina. Y ahí es donde muchas pymes pierden tiempo (y cometen errores).&lt;/p&gt;

&lt;p&gt;En este artículo documentamos el flujo real para llevar los datos de fichaje de &lt;a href="https://nucleo360.com/registro-horario/" rel="noopener noreferrer"&gt;Nucleo360&lt;/a&gt; hasta el software de nóminas o el gestor laboral, con un paso de validación intermedio que te puede ahorrar más de un disgusto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Empecemos por la honestidad técnica
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nucleo360 no calcula nóminas.&lt;/strong&gt; Es una decisión de producto deliberada: la plataforma cubre el registro horario, las vacaciones y ausencias, la gestión documental, el canal de denuncias y el resto de la gestión de RRHH de una pyme, y &lt;strong&gt;exporta la documentación y los datos al gestor laboral o al software de nóminas&lt;/strong&gt; que ya usa la empresa. El cálculo de la nómina (convenios, cotizaciones, IRPF) lo hace quien mejor sabe hacerlo: tu asesoría o tu programa de nóminas.&lt;/p&gt;

&lt;p&gt;Eso significa que la integración de la que hablamos aquí no es una sincronización mágica en tiempo real, sino un &lt;strong&gt;pipeline de datos clásico y robusto&lt;/strong&gt;: exportar → validar → entregar. Es aburrido, y por eso funciona.&lt;/p&gt;

&lt;h2&gt;
  
  
  El flujo en tres pasos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Paso 1 · Exportar el periodo desde Nucleo360
&lt;/h3&gt;

&lt;p&gt;Al cierre de cada mes (o del periodo que pacte tu gestoría), se exportan desde Nucleo360 los datos del registro horario del periodo: fichajes por empleado y día, con sus tramos de entrada y salida y los totales. El registro es &lt;strong&gt;inmutable y con trazabilidad&lt;/strong&gt; (quién fichó, cuándo y desde dónde), así que lo que exportas refleja lo que realmente se registró, no una versión editada a posteriori.&lt;/p&gt;

&lt;p&gt;Para nóminas, lo habitual es trabajar con el export en formato tabular (CSV) o con los informes del periodo, según lo que acepte tu gestor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8isktf0vh5n6u6w2l19e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8isktf0vh5n6u6w2l19e.png" alt=" " width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 2 · Validar antes de enviar
&lt;/h3&gt;

&lt;p&gt;Este es el paso que casi nadie hace y el que más errores evita. Antes de entregar los datos a quien hace la nómina, conviene comprobar tres cosas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Totales por empleado&lt;/strong&gt;: que las horas del mes cuadran con lo esperado (jornada pactada ± extras).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Huecos&lt;/strong&gt;: días laborables sin ningún fichaje. Pueden ser vacaciones o ausencias legítimas (cotéjalo con el módulo de vacaciones), o pueden ser olvidos que hay que resolver &lt;em&gt;antes&lt;/em&gt; de que lleguen a la nómina.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solapes&lt;/strong&gt;: tramos que se pisan entre sí (entrada a las 9:00 y a las 9:30 sin salida intermedia). Suelen indicar un fichaje duplicado o mal cerrado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Aquí tienes un &lt;strong&gt;ejemplo genérico&lt;/strong&gt; en Python (solo librería estándar) que hace exactamente eso sobre un CSV de fichajes. No depende de ningún proveedor: ajusta los nombres de columna a los de tu export real.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
EJEMPLO GENÉRICO de validación de un CSV de fichajes antes de
entregarlo al gestor laboral. Ajusta columnas y delimitador a tu export.

Formato esperado (una fila por tramo de fichaje):
    empleado_id;nombre;fecha;hora_entrada;hora_salida
    E001;Ana;2026-07-01;08:00;15:00
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="n"&gt;FORMATO_FECHA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FORMATO_HORA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%H:%M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cargar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tramos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# (empleado_id, fecha) -&amp;gt; [(inicio, fin), ...]
&lt;/span&gt;    &lt;span class="n"&gt;nombres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n_linea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fila&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fecha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;FORMATO_FECHA&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;entrada&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hora_entrada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;FORMATO_HORA&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="n"&gt;salida&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hora_salida&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;FORMATO_HORA&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[ERROR] Línea &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n_linea&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: fila ilegible (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;salida&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;entrada&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Turno nocturno: la salida cae en el día siguiente
&lt;/span&gt;                &lt;span class="n"&gt;salida&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empleado_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;entrada&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salida&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;nombres&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empleado_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fila&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nombre&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombres&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombres&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;incidencias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lista&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Solapes dentro del mismo día
&lt;/span&gt;        &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ini_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fin_a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ini_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fin_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ini_b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;fin_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[SOLAPE] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ini_a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fin_a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; se pisa con &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ini_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fin_b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;incidencias&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ini&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fin&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;fin&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ini&lt;/span&gt;
        &lt;span class="n"&gt;dias&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dia&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Huecos: días laborables (L-V) sin fichajes dentro del rango del archivo.
&lt;/span&gt;    &lt;span class="c1"&gt;# OJO: pueden ser vacaciones o ausencias legítimas; esto solo los señala.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fechas&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fechas&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fechas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;ultimo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fechas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[HUECO] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nombres&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;): sin fichajes el &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(¿vacaciones/ausencia sin cotejar?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;incidencias&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Totales por empleado ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="n"&gt;horas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nombres&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;horas&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; h en &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dias&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;emp&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; días&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;incidencias&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Uso: python valida_fichajes.py fichajes.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cargar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tramos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombres&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; incidencias detectadas.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El script devuelve código de salida distinto de cero si hay incidencias, así que puedes encadenarlo en cualquier automatización: si algo no cuadra, el archivo no sigue viaje hacia la nómina.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 3 · Entregar al gestor (bien empaquetado)
&lt;/h3&gt;

&lt;p&gt;La entrega falla casi siempre por detalles tontos de formato, no por los datos. Pacta con tu gestoría o con quien opere el software de nóminas, una sola vez y por escrito:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Columnas y su orden&lt;/strong&gt; (una tabla de mapeo de dos columnas es suficiente).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codificación&lt;/strong&gt;: UTF-8. Los nombres con tildes y eñes lo agradecerán.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delimitador&lt;/strong&gt; (&lt;code&gt;;&lt;/code&gt; es lo habitual en España, porque la coma decimal rompe los CSV con &lt;code&gt;,&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formato de fecha&lt;/strong&gt;: ISO 8601 (&lt;code&gt;2026-07-01&lt;/code&gt;) evita el clásico baile día/mes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fecha de corte&lt;/strong&gt;: qué día del mes se exporta, para que la nómina no espere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y conserva una copia de lo entregado: el registro horario debe conservarse &lt;strong&gt;4 años&lt;/strong&gt; (art. 34.9 del Estatuto de los Trabajadores).&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Y la integración directa por API?
&lt;/h2&gt;

&lt;p&gt;Pregunta legítima de cualquier perfil técnico: «¿puedo saltarme el CSV y tirar de API?».&lt;/p&gt;

&lt;p&gt;Respuesta honesta: a fecha de este artículo, la integración directa vía API con sistemas de nóminas está &lt;strong&gt;[PENDIENTE DE CONFIRMAR CON CLIENTE]&lt;/strong&gt; — no hay una API pública documentada que podamos enseñarte aquí, y preferimos no inventárnosla. El flujo soportado y probado es el que acabas de leer: export, validación y entrega.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Se integra directamente con algún programa de nóminas concreto?
&lt;/h2&gt;

&lt;p&gt;Misma honestidad: &lt;strong&gt;no hay integraciones directas confirmadas&lt;/strong&gt; con programas concretos de nóminas ni con gestorías [PENDIENTE DE CONFIRMAR CON CLIENTE]. Lo que sí funciona hoy es el puente universal: la mayoría de software de nóminas y gestorías aceptan importación de datos tabulares (CSV) o trabajan directamente con los informes del periodo. Pregunta a tu gestor qué formato de importación acepta su herramienta y pacta el mapeo de columnas del Paso 3.&lt;/p&gt;

&lt;p&gt;¿Menos glamuroso que un webhook? Sí. ¿Funciona con cualquier gestoría de España sin depender de que dos proveedores se pongan de acuerdo? También.&lt;/p&gt;

&lt;h2&gt;
  
  
  En resumen
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nucleo360 registra la jornada (registro inmutable, con trazabilidad) y &lt;strong&gt;exporta&lt;/strong&gt;; la nómina la calcula tu gestor o tu software de nóminas.&lt;/li&gt;
&lt;li&gt;Valida siempre antes de entregar: totales, huecos y solapes. El script de arriba es un punto de partida.&lt;/li&gt;
&lt;li&gt;Pacta el formato con tu gestoría una vez y por escrito; UTF-8, &lt;code&gt;;&lt;/code&gt; e ISO 8601 te evitan el 90 % de los problemas.&lt;/li&gt;
&lt;li&gt;API pública e integraciones directas con programas concretos: pendientes de confirmar; hoy el camino es el export.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si quieres ver el módulo de registro horario en detalle (el plan Basic es gratuito y sin permanencia), tienes toda la información en &lt;a href="https://nucleo360.com" rel="noopener noreferrer"&gt;nucleo360.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Última actualización: julio de 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nucleo360.com" rel="noopener noreferrer"&gt;Nucleo360.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rrhh</category>
      <category>puppet</category>
      <category>database</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Puppetlabs Modules Now Have Tiered Review Cycles</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:16:39 +0000</pubDate>
      <link>https://dev.to/puppet/puppetlabs-modules-now-have-tiered-review-cycles-42ob</link>
      <guid>https://dev.to/puppet/puppetlabs-modules-now-have-tiered-review-cycles-42ob</guid>
      <description>&lt;p&gt;If you've been watching the&amp;nbsp;&lt;a href="https://forge.puppet.com/modules/puppetlabs?utm_medium=social&amp;amp;utm_source=linkedin&amp;amp;utm_campaign=2026-puppet-cloud-repatriation-trends-2026&amp;amp;utm_content=blog&amp;amp;limit=25&amp;amp;sort_by=latest_release&amp;amp;module_groups=base%20pe_only" rel="noopener noreferrer"&gt;Puppetlabs namespace on the Forge&lt;/a&gt;&amp;nbsp;(or waiting on a pull request you opened) you've likely noticed that the pace of inclusion of community pull requests and fixes into the module releases has slowed over the last several cycles. That isn't how we want to serve the community that built these modules with us. The team has been reviewing how we can improve and this post is our commitment to do better. Here are the steps we're taking now:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Clear tiers with review cadences.&lt;/strong&gt;&amp;nbsp;Modules in the puppetlabs namespace will be grouped into three tiers, each with a defined review schedule (weekly, monthly, or quarterly).&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Defining what “review cadence” means in practice.&lt;/strong&gt;&amp;nbsp;Every community PR gets engineering review in the next scheduled cycle for that module’s tier. &amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Showing progress monthly.&lt;/strong&gt;&amp;nbsp;We’ll continue to publish the monthly Modules Roundup covering what was released.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Tiered Approach to Priorities&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;We've categorized supported modules in the puppetlabs namespace into three tiers, each with a defined community review cadence:&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Tier&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Count&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Community review cadence&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Tier 1:&lt;/strong&gt; core, business-critical modules&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Weekly&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Tier 2:&lt;/strong&gt; important, meaningfully used modules&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Monthly&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Tier 3:&lt;/strong&gt; niche or low-usage modules&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Quarterly&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What a Review Cadence Means
&lt;/h3&gt;

&lt;p&gt;Every community pull request (PR) on a module in the puppetlabs namespace gets engineering review during the next scheduled cycle for that module's tier. This doesn’t mean that every PR will be accepted. Sometimes a pull request needs feedback or changes, some pull requests won't fit a module's direction, but the cadence is intended to make sure that PRs will no longer sit quietly in a queue. That is the baseline we're holding ourselves to with these tiers and review cadences.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;We do have a backlog of requests over the years and we will be working through that backlog as well, but priority will be given to active contributions for the purposes of our review cadence.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tier 1: Our highest-priority modules&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;The following modules are our committed Tier 1 set. These modules will get a weekly review, with nightly CI coverage, and get priority on maintenance capacity:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  puppetlabs-apache&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-apt&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-augeas_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-concat&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-cron_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-docker&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-firewall&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-haproxy&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-inifile&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-java&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-java_ks&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-lvm&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-mount_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-mysql&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-ntp&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-package&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-postgresql&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-powershell&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-reboot&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-registry&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-ruby_task_helper&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-service&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-sshkeys_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-stdlib&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-vcsrepo&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tier 2: Monthly reviews&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;The following modules have currently been assigned to our Tier 2 for prioritization. These modules will get a monthly review.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &amp;nbsp;puppetlabs-accounts&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-acl&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-bolt_shim&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-chocolatey&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-facter_task&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-iis&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-kubernetes&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-motd&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-mount_iso&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-node_encrypt&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-puppet_conf&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-satellite_pe_tools&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-scheduled_task&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-sqlserver&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-sslcertificate&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-tomcat&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-windows_env&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-windows_eventlog&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-wsus_client&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tier 3: Quarterly reviews&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;The modules assigned to Tier 3 are still supported but will not generally be getting a high degree of priority. These modules will be reviewed quarterly and released as needed.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  ca_extend&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  influxdb&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppet_operational_dashboards&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-bash_task_helper&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-dropsonde&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-dsc_lite&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-facts&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-host_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-node_manager&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-pe_databases&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-pe_event_forwarding&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-pe_status_check&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-puppet_bolt_server&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-puppet_metrics_collector&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-puppet_status_check&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-selinux_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-splunk_hec&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-yumrepo_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-zfs_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  puppetlabs-zone_core&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  ruby-pwsh&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  support-tasks&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Product and Engineering teams are still reviewing the puppetlabs modules for final placement in Tier 2 or Tier 3 and will follow the cadence associated with their tier once that placement is confirmed. Ultimately, the lists above might shift slightly as the team finds that some modules might need more attention than others.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  What has already shipped?&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;It is one thing to say you will do better and quite another to follow through. We’re sharing this now because we have tangible changes already underway. In a focused sprint on Tier 1 modules, the team &lt;strong&gt;triaged and actioned several open community pull requests,&lt;/strong&gt;&amp;nbsp;some merged after review, some closed after discussion, others consolidated with newer work. You’ll see several modules released this month and next with these merged changes on the Forge. Keep an eye on the&amp;nbsp;&lt;a href="https://forge.puppet.com/modules/puppetlabs?utm_medium=social&amp;amp;utm_source=linkedin&amp;amp;utm_campaign=2026-puppet-cloud-repatriation-trends-2026&amp;amp;utm_content=blog&amp;amp;limit=25&amp;amp;sort_by=latest_release&amp;amp;module_groups=base%20pe_only" rel="noopener noreferrer"&gt;Forge modules list&lt;/a&gt;&amp;nbsp;or check out the next&amp;nbsp;&lt;a href="https://dev.to/jasonstcyr/series/34389"&gt;Puppet Modules Roundup&lt;/a&gt;&amp;nbsp;post for a summary!&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Issues With CI for community PRs&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;We're aware that testing community contributions has been harder than it should be. The current pull request model makes it difficult to run the full acceptance test suite against a contributor's PR without manual effort by Perforce employees, which slows reviews and discourages contributions. Solving this is a priority for the team and we have been prototyping an improved approach that will meet both user expectations and maintaining a secure infrastructure. We’ll share specifics once it's working end-to-end. This is a known gap we're committed to closing.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
  
  
  How We'll Show Our Work&amp;nbsp;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Monthly Modules Roundup.&lt;/strong&gt; We publish a roundup each month covering what &amp;nbsp;modules have been released, who contributed, and what’s next. You can follow the series here: Puppetlabs Modules Roundup on dev.to.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Contributor recognition.&lt;/strong&gt; The release notes on the Forge and in the monthly roundup will call out merged community PRs by name.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Public GitHub transparency.&lt;/strong&gt;&amp;nbsp;We will be following up with contributors directly in pull requests and issues in our open source modules. In advance of a release, we will continue to merge and close PRs so that you can see what will be included in an upcoming release.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can you help?&amp;nbsp;
&lt;/h2&gt;

&lt;p&gt;Keep contributing! Keep filing issues. Keep telling us when a module is in your way. If a PR of yours has stalled out, please rebase it or comment on the PR. And if the tiering above looks wrong for how &lt;em&gt;you&lt;/em&gt; use Puppet day-to-day, let us know! The whole point of publishing this article is so that the community can understand what is changing and also to invite you to shape where the priority goes.&amp;nbsp;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tiering is not a one-way decision.&lt;/strong&gt; &amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tiers outlined here have been based on the activity we have observed in our analytics and the importance identified by customers. If a module you depend on isn't in the tier you think it should be, or perhaps a long-waiting PR or issue is blocking you, reach out via the Puppet Community Slack or leave a comment on the monthly Puppet Modules Roundup posts. We want your input to make sure the tiering meet the needs of all our users.&lt;/p&gt;

</description>
      <category>puppet</category>
    </item>
    <item>
      <title>Driving Value from Puppet Metrics: Puppet Observability Data Connector</title>
      <dc:creator>klab-systems</dc:creator>
      <pubDate>Mon, 13 Jul 2026 16:28:45 +0000</pubDate>
      <link>https://dev.to/puppet/driving-value-from-puppet-metrics-puppet-observability-data-connector-391f</link>
      <guid>https://dev.to/puppet/driving-value-from-puppet-metrics-puppet-observability-data-connector-391f</guid>
      <description>&lt;h2&gt;
  
  
  What is the Puppet Observability Data Connector?
&lt;/h2&gt;

&lt;p&gt;The Puppet Observability Data Connector is a &lt;a href="https://forge.puppet.com/modules/puppetlabs/puppet_data_connector/readme?utm_medium=social&amp;amp;utm_source=linkedin&amp;amp;utm_campaign=2026-puppet-cloud-repatriation-trends-2026&amp;amp;utm_content=blog" rel="noopener noreferrer"&gt;premium Forge module&lt;/a&gt; included with Puppet Enterprise Advanced (PEA). This module provides a deeper dive into your Puppet agent reports. Visualizing these metrics gives you a great way to identify what is healthy and unhealthy in your environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Behind the scenes, this module provides 2 core functionalities.
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;It provides a report processor to convert Puppet reports into usable metrics.&lt;/li&gt;
&lt;li&gt;It manages an exporter to serve these metrics for your time series database to scrape.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example Metrics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Failed nodes&lt;/li&gt;
&lt;li&gt;Changed nodes&lt;/li&gt;
&lt;li&gt;Catalog cache failures&lt;/li&gt;
&lt;li&gt;Stale nodes&lt;/li&gt;
&lt;li&gt;Corrective changes&lt;/li&gt;
&lt;li&gt;Out of sync resources&lt;/li&gt;
&lt;li&gt;Average total run time&lt;/li&gt;
&lt;li&gt;Max run time&lt;/li&gt;
&lt;li&gt;Run time breakdowns

&lt;ul&gt;
&lt;li&gt;Total run time&lt;/li&gt;
&lt;li&gt;Fact generation time&lt;/li&gt;
&lt;li&gt;Catalog application time&lt;/li&gt;
&lt;li&gt;Config retrieval time&lt;/li&gt;
&lt;li&gt;Plugin sync time&lt;/li&gt;
&lt;li&gt;Convert catalog time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Managed resource count by host&lt;/li&gt;
&lt;li&gt;Managed resource count over time&lt;/li&gt;
&lt;li&gt;Catalog application time trends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the metrics provided by Puppet Observability Data Connector, you can make impactful dashboards. Below are some examples that I have created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All dashboards can be filtered by the environment and/or host. All thresholds for color coding can be modified to fit your environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fleet Status Dashboard Example
&lt;/h2&gt;

&lt;p&gt;This example 'Fleet Status' dashboard provides an always-open first-look insight into whether anything in your fleet requires immediate attention. I will break this down by section.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use a Fleet Status Dashboard?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;After deploying Puppet code to confirm no regressions&lt;/li&gt;
&lt;li&gt;During incidents to identify affected nodes&lt;/li&gt;
&lt;li&gt;As part of a daily ops review to verify fleet-wide compliance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fleet Health Summary (stat panels)
&lt;/h3&gt;

&lt;p&gt;A top-level count of nodes in each state from the most recent run. Six stats that give you an instant fleet pulse without scrolling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dz43nzvwnzt0vq1awgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dz43nzvwnzt0vq1awgo.png" alt="Fleet Health Summary" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Nodes Reporting&lt;/td&gt;
&lt;td&gt;Quantity of nodes submitting reports; this number should be 1:1 with the amount of Puppet agent managed nodes in your environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed Nodes&lt;/td&gt;
&lt;td&gt;Nodes where &lt;code&gt;puppet_status{state="failed"} == 1;&lt;/code&gt; any non-zero value requires immediate investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed Nodes&lt;/td&gt;
&lt;td&gt;Nodes where Puppet applied changes in the last run; expected after a code push, unexpected otherwise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unchanged Nodes&lt;/td&gt;
&lt;td&gt;Nodes fully converged — your desired steady state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog Cache Failures&lt;/td&gt;
&lt;td&gt;Nodes that could not obtain a fresh catalog and fell back to a cached one. Triggers on server/network unreachable &lt;strong&gt;or&lt;/strong&gt; any catalog compilation error (duplicate resource declaration, undefined variable, syntax error, missing module)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale Nodes (&amp;gt;2h)&lt;/td&gt;
&lt;td&gt;Nodes that haven't reported in over two run intervals; the agent may be down, hung, or disconnected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Node Run Status (bar gauges)
&lt;/h3&gt;

&lt;p&gt;Per-node breakdowns of the two most critical run-level signals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotuc0tywzr5cjfxk4v80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotuc0tywzr5cjfxk4v80.png" alt="Node Run Status" width="799" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time Since Last Puppet Run&lt;/td&gt;
&lt;td&gt;Minutes since each node last phoned home. Green = within one run interval (~30 min), yellow = approaching stale, red = definitely stale. Use to find silent failures where the agent has stopped running entirely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transaction Completed by Host&lt;/td&gt;
&lt;td&gt;Whether the last run completed (1) or was aborted mid-run (0). An aborted transaction means the node's actual state is unknown — treat it the same as a failure.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Compliance &amp;amp; Drift (stats + bar gauges + time series)
&lt;/h3&gt;

&lt;p&gt;This section separates &lt;em&gt;intentional&lt;/em&gt; changes (deployments) from &lt;em&gt;unintentional&lt;/em&gt; drift (external actors modifying managed state).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffitoz4ji3qmnkw7eg9ki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffitoz4ji3qmnkw7eg9ki.png" alt="Compliance and Drift" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Corrective Changes (Fleet)&lt;/td&gt;
&lt;td&gt;Aggregate count of corrective changes across all nodes. A corrective change means Puppet found a resource in the wrong state and fixed it — something external changed it. Persistent non-zero values are a compliance red flag.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Failed Resources (Fleet)&lt;/td&gt;
&lt;td&gt;Resources that Puppet attempted to enforce but could not. Non-zero always warrants investigation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Event Failures (Fleet)&lt;/td&gt;
&lt;td&gt;Resource-level event failures, which can appear even on nodes not marked failed. Catches partial failures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Out-of-Sync Resources (Fleet)&lt;/td&gt;
&lt;td&gt;Resources declared in the catalog but not yet in the desired state. Elevated values indicate pending convergence or a broken dependency chain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corrective Changes by Host&lt;/td&gt;
&lt;td&gt;Identifies which specific nodes are experiencing drift. Nodes with persistent corrective changes may have rogue processes or manual interventions overwriting Puppet-managed state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed Resources by Host&lt;/td&gt;
&lt;td&gt;Identifies which nodes have broken resources. Cross-reference with the node's Puppet report for the specific resource type and error message.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corrective Changes Over Time&lt;/td&gt;
&lt;td&gt;Trend view — spikes correlate with manual changes, automated tooling conflicts, or software self-repair mechanisms overwriting managed files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed Resources Over Time&lt;/td&gt;
&lt;td&gt;Trend view — a step-change increase after a code push indicates a bug in new Puppet code. A gradual increase may indicate environment drift.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Catalog Cache Status (stats + bar gauge)
&lt;/h3&gt;

&lt;p&gt;Catalog cache usage is a leading indicator of Puppet infrastructure health, often surfacing issues before nodes start failing outright.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqtaxc9avqjvhkm1c521.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqtaxc9avqjvhkm1c521.png" alt="Catalog Cache Status" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nodes: Cache Used on Failure&lt;/td&gt;
&lt;td&gt;A fresh catalog could not be obtained. Common causes: Puppet server unreachable, network failure, or a catalog &lt;strong&gt;compilation error&lt;/strong&gt; (duplicate resource declaration, undefined variable, syntax error, missing module). The node ran with a potentially stale catalog — investigate PE server logs and the agent's last run report.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes: Cache Explicitly Requested&lt;/td&gt;
&lt;td&gt;Nodes intentionally running offline (e.g., --use-cached-catalog). Expected during planned maintenance; unexpected otherwise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes: No Cache (Fresh Catalog)&lt;/td&gt;
&lt;td&gt;Healthy nodes compiling a fresh catalog every run — your desired steady state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog Cache on Failure – by Host&lt;/td&gt;
&lt;td&gt;Pinpoints which nodes are falling back to cache. Use to determine whether it's a widespread compiler/network issue or a node-specific catalog compilation error (check that node's agent log for the exact cause).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Full Dashboard Snapshot
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit40lpt8vtjwatndgt0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit40lpt8vtjwatndgt0k.png" alt="Full Fleet Status Dashboard" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Performance Example Dashboard
&lt;/h2&gt;

&lt;p&gt;This example 'Run Performance' dashboard acts as your performance and capacity planning dashboard. It answers whether Puppet is running efficiently and helps you locate bottlenecks before they become outages. I will break this down by section.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use a Run Performance dashboard?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run times are creeping up across the fleet&lt;/li&gt;
&lt;li&gt;After adding new modules or resource types to identify compile/apply costs&lt;/li&gt;
&lt;li&gt;To compare performance across environments (production vs. feature_demo)&lt;/li&gt;
&lt;li&gt;When planning infrastructure scaling (for example, adding more compilers or faster agents)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Summary (stat panels)
&lt;/h3&gt;

&lt;p&gt;Fleet-wide performance KPIs. These numbers represent your overall SLA for Puppet run completion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3qlt834r6v7gt37a35j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3qlt834r6v7gt37a35j9.png" alt="Performance Summary" width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg Total Run Time&lt;/td&gt;
&lt;td&gt;The mean end-to-end run duration across all selected nodes. Green &amp;lt; 120s, yellow 120–300s, red &amp;gt; 300s.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max Total Run Time&lt;/td&gt;
&lt;td&gt;The slowest node in the fleet — identifies outliers dragging down SLA.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg Fact Generation Time&lt;/td&gt;
&lt;td&gt;How long Facter is taking on average. This phase runs before catalog compilation so delays here block everything downstream.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg Catalog Application Time&lt;/td&gt;
&lt;td&gt;How long Puppet spends enforcing resources. A reliable proxy for catalog complexity and system responsiveness.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Run Time Trends (time series)
&lt;/h3&gt;

&lt;p&gt;Long-term trend views to distinguish persistent regressions from one-off anomalies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe16dr9tllbi3uq5zjgvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe16dr9tllbi3uq5zjgvt.png" alt="Runtime Trends" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Run Time Over Time&lt;/td&gt;
&lt;td&gt;The most important single trend. A sudden step-change after a deployment indicates new expensive resources. A gradual increase over weeks suggests catalog bloat or system degradation. A red threshold line at 300s provides a visual SLA boundary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fact Generation Time Over Time&lt;/td&gt;
&lt;td&gt;Isolates Facter performance from the rest of the run. Upward trends often indicate new custom facts, growing hardware inventories, or external fact sources becoming slow.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Run Time Breakdown – Latest Run (by Host) (bar gauges)
&lt;/h3&gt;

&lt;p&gt;Per-node, per-phase timing from the most recent run. Use this to pinpoint &lt;em&gt;which phase&lt;/em&gt; is slow on &lt;em&gt;which node&lt;/em&gt; rather than just knowing the total is high.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t7s9unvsjeo7pxkdaep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4t7s9unvsjeo7pxkdaep.png" alt="Runtime Breakdown" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Panel&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Phase&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Optimization Lever&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Run Time&lt;/td&gt;
&lt;td&gt;End-to-end wall clock&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fact Generation Time&lt;/td&gt;
&lt;td&gt;Running Facter&lt;/td&gt;
&lt;td&gt;Audit custom facts; disable unused external facts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalog Application Time&lt;/td&gt;
&lt;td&gt;Enforcing all resources&lt;/td&gt;
&lt;td&gt;Reduce catalog size; identify slow resource types (Package, Exec)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config Retrieval Time&lt;/td&gt;
&lt;td&gt;Fetching compiled catalog from PE server&lt;/td&gt;
&lt;td&gt;Scale compilers; check network latency between agent and server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin Sync Time&lt;/td&gt;
&lt;td&gt;Downloading Puppet functions/types/providers&lt;/td&gt;
&lt;td&gt;Audit and prune modules; check if all plugins are necessary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convert Catalog Time&lt;/td&gt;
&lt;td&gt;Transforming compiled catalog into apply-ready format&lt;/td&gt;
&lt;td&gt;Indicates catalog structural complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Resource Catalog (bar gauge + time series)
&lt;/h3&gt;

&lt;p&gt;Catalog size is the root cause of many performance problems. These panels correlate resource count with run time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0ceiac7t736d9jnc9ed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0ceiac7t736d9jnc9ed.png" alt="Resource Catalog" width="798" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Managed Resources by Host&lt;/td&gt;
&lt;td&gt;Nodes with significantly more resources than peers warrant investigation — are all those resources intentional? &lt;code&gt;pe.klab.internal&lt;/code&gt; managing 2,419 resources vs 61 for simpler nodes is expected for a PE server; unexpected divergence between similar nodes is not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Managed Resources Over Time&lt;/td&gt;
&lt;td&gt;A sudden jump indicates a code change added many new resources. Gradual growth over months indicates catalog bloat — exported resources collecting without cleanup, or defined resource types generating more instances than expected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Catalog Application Time Trends (time series)
&lt;/h3&gt;

&lt;p&gt;Deeper trend analysis for the two phases most sensitive to infrastructure and code changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhiz7jn017kcib7o8axo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhiz7jn017kcib7o8axo.png" alt="Catalog Application Time Trends" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;em&gt;What this tells you:&lt;/em&gt;
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Term&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catalog Application Time Over Time&lt;/td&gt;
&lt;td&gt;Correlate spikes with git commit timestamps. A spike immediately following a deployment confirms the new code introduced expensive resources. Persistent elevation after the spike stabilizes means the cost is structural, not transient.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config Retrieval Time Over Time&lt;/td&gt;
&lt;td&gt;Sustained increases across &lt;em&gt;all&lt;/em&gt; nodes simultaneously point to a PE server bottleneck (JVM heap, database pressure, or compiler overload). Increases on a single node suggest agent-side network issues.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Full Dashboard Snapshot
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzosx2n0dck5oj2sw3s15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzosx2n0dck5oj2sw3s15.png" alt="Full Run Performance Dashboard" width="799" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Download the Puppet Data Connector Forge Module
&lt;/h2&gt;

&lt;p&gt;Want to try it in your environment? Download the premium Forge module now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forge.puppet.com/modules/puppetlabs/puppet_data_connector/readme" rel="noopener noreferrer"&gt;Get the Module&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The Puppet Observability Data Connector provides valuable information about the Puppet agent nodes across your environment. Visualizing these metrics gives organizations key insights, allowing team members to identify problems, measure performance, and track changes. The dashboards shown here are just one of the many ways to drive value from your Puppet metrics.&lt;/p&gt;

&lt;p&gt;For assistance driving value from your Puppet metrics please ask your account team about how Professional Services could help you with dashboards that match your environment and operational priorities.&lt;/p&gt;

</description>
      <category>puppet</category>
    </item>
    <item>
      <title>Puppetlabs Modules Roundup – June 2026</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Thu, 09 Jul 2026 19:38:46 +0000</pubDate>
      <link>https://dev.to/puppet/puppetlabs-modules-roundup-june-2026-23da</link>
      <guid>https://dev.to/puppet/puppetlabs-modules-roundup-june-2026-23da</guid>
      <description>&lt;p&gt;In June 2026 we saw the release of a new &lt;code&gt;stdlib 10&lt;/code&gt; that was mentioned &lt;a href="https://dev.to/puppet/what-you-need-to-know-about-the-new-puppetlabs-stdlib-10-in-june-2026-1jah"&gt;in an earlier article&lt;/a&gt; along with numerous modules bumped to use the new stdlib 10. In total, 24 Puppetlabs modules were released to the Forge, ranging from a brand-new Windows security policy module to a large batch of third-party CVE fixes in Security Compliance Management. This roundup pulls the most important changes into one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Highlighted Updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New Module: Windows Local Security Policy Management
&lt;/h3&gt;

&lt;p&gt;The new &lt;a href="https://forge.puppet.com/modules/puppetlabs/security_policy/readme" rel="noopener noreferrer"&gt;security_policy module&lt;/a&gt; manages Windows local security policy using the Puppet Resource API, replacing manual &lt;code&gt;secedit&lt;/code&gt;/Local Security Policy editor work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides the &lt;code&gt;security_option&lt;/code&gt; and &lt;code&gt;user_right_assignment&lt;/code&gt; resource types, covering all 45 Windows Privilege Rights and the System Access settings.&lt;/li&gt;
&lt;li&gt;Ships a well-known SID map with a PowerShell fallback for domain and custom accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  stdlib 10.x Compatibility Pass
&lt;/h3&gt;

&lt;p&gt;A coordinated maintenance pass loosened the puppetlabs/stdlib dependency constraint across the module set to allow stdlib 10.x, clearing the way for downstream modules to pick up stdlib's latest release.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Affected modules: accounts, apache, apt, chocolatey, concat, docker, firewall, haproxy, inifile, lvm, motd, mysql, ntp, postgresql, wsus_client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More modules will be released in July as we roll out the support for stdlib 10.x.&lt;/p&gt;

&lt;h3&gt;
  
  
  Puppet Core Alignment / Puppet 7 Support Dropped
&lt;/h3&gt;

&lt;p&gt;Several modules completed their Puppet Core alignment pass this month, dropping Puppet 7 support in favor of Puppet 8 as Puppet 7 reaches end-of-life.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Affected modules: accounts, chocolatey, haproxy, java, motd, mysql, postgresql, stdlib.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note that the change to drop Puppet 7 in postgresql was done in a patch release, instead of a major release. This has been corrected in postgresql 10.6.3 and the change will be done again in a major release of that module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Compliance Management Patches ~40 CVEs
&lt;/h3&gt;

&lt;p&gt;Security Compliance Management 3.8.0, shipped as both &lt;code&gt;comply&lt;/code&gt; and &lt;code&gt;complyadm&lt;/code&gt;, updates a long list of bundled third-party components — Gorm.io, Protobuf, several Netty codec libraries, react-router, and KeyCloak — to close out roughly 40 CVEs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Also updates the bundled CIS-CAT Pro Assessor to v4.63.0, adding new STIG benchmarks for Amazon Linux 2023, Windows 11, Oracle Linux 9, and RHEL 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Updates Happened to Puppetlabs Modules in June 2026?
&lt;/h2&gt;

&lt;p&gt;The following is an alphabetical listing of modules which received updates in June 2026. If a module had multiple versions released, the updates are collected together, numbered with the "latest" version available.&lt;/p&gt;




&lt;h3&gt;
  
  
  accounts 9.0.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/accounts" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This release drops Puppet 7 support (&lt;strong&gt;BREAKING&lt;/strong&gt;) as part of the module's Puppet Core alignment work, and allows the stdlib dependency to move to 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2352) Drop Puppet 7 support (BREAKING) — Puppet Core alignment &lt;a href="https://github.com/puppetlabs/puppetlabs-accounts/pull/509" rel="noopener noreferrer"&gt;#509&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-accounts/pull/513" rel="noopener noreferrer"&gt;#513&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  apache 13.2.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-28 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/apache" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds RHEL 10 support, restores the ModSecurity engine on RHEL 10 via EPEL, and allows both stdlib and concat to move to their 10.x releases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add missing parameters to mod_md &lt;a href="https://github.com/puppetlabs/puppetlabs-apache/pull/2621" rel="noopener noreferrer"&gt;#2621&lt;/a&gt; (&lt;a href="https://github.com/smortex" rel="noopener noreferrer"&gt;smortex&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11851) Restore ModSecurity engine on RHEL 10 via EPEL &lt;a href="https://github.com/puppetlabs/puppetlabs-apache/pull/2635" rel="noopener noreferrer"&gt;#2635&lt;/a&gt; (&lt;a href="https://github.com/SugatD" rel="noopener noreferrer"&gt;SugatD&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11739) Add RHEL 10 support &lt;a href="https://github.com/puppetlabs/puppetlabs-apache/pull/2629" rel="noopener noreferrer"&gt;#2629&lt;/a&gt; (&lt;a href="https://github.com/SugatD" rel="noopener noreferrer"&gt;SugatD&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-apache/pull/2632" rel="noopener noreferrer"&gt;#2632&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;puppetlabs/concat: Allow 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-apache/pull/2630" rel="noopener noreferrer"&gt;#2630&lt;/a&gt; (&lt;a href="https://github.com/bastelfreak" rel="noopener noreferrer"&gt;bastelfreak&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  apt 11.3.2
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-26 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/apt" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A small release that only bumps the stdlib dependency to allow 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-apt/pull/1288" rel="noopener noreferrer"&gt;#1288&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  aws_inventory 0.8.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-17 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/aws_inventory" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This release allows the ruby_task_helper dependency to move to 1.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow ruby_task_helper 1.x &lt;a href="https://github.com/puppetlabs/puppetlabs-aws_inventory/pull/25" rel="noopener noreferrer"&gt;#25&lt;/a&gt; (&lt;a href="https://github.com/bastelfreak" rel="noopener noreferrer"&gt;bastelfreak&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  chocolatey 9.0.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/chocolatey" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Completes its Puppet Core alignment pass, improves package prefetch caching with case-insensitive matching, and allows stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2369) Puppet Core update &lt;a href="https://github.com/puppetlabs/puppetlabs-chocolatey/pull/378" rel="noopener noreferrer"&gt;#378&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11769) Cache prefetch results and match packages case-insensitively &lt;a href="https://github.com/puppetlabs/puppetlabs-chocolatey/pull/388" rel="noopener noreferrer"&gt;#388&lt;/a&gt; (&lt;a href="https://github.com/skyamgarp" rel="noopener noreferrer"&gt;skyamgarp&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-chocolatey/pull/387" rel="noopener noreferrer"&gt;#387&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  comply 3.8.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-12 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/comply" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Security Compliance Management 3.8.0 addresses roughly 40 CVEs across bundled third-party components, alongside operational fixes and licensing improvements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increased the CIS-CAT Pro Assessor license expiry time to a full year.&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;license_path&lt;/code&gt; parameter to update the CIS-CAT Pro Assessor license without upgrading SCM.&lt;/li&gt;
&lt;li&gt;Added an &lt;code&gt;assessor_scan_timeout&lt;/code&gt; option to control the task timeout for Windows 2022 domain controllers.&lt;/li&gt;
&lt;li&gt;Added a background scan sweeper to detect and cancel scans stuck in the "running" state.&lt;/li&gt;
&lt;li&gt;Fixed a race condition where timed-out PE job status polls could leave scans permanently stuck.&lt;/li&gt;
&lt;li&gt;Updated Gorm.io, Protobuf, multiple Netty codec libraries, react-router, and KeyCloak to address roughly 40 CVEs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the official &lt;a href="https://help.puppet.com/scm/current/Content/UserGuide/SCM/Release_notes/release_notes.htm#SecurityComplianceManagement380" rel="noopener noreferrer"&gt;release notes for comply 3.8.0&lt;/a&gt; for the full details.&lt;/p&gt;




&lt;h3&gt;
  
  
  complyadm 3.8.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-12 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/complyadm" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Ships the same Security Compliance Management 3.8.0 update as &lt;code&gt;comply&lt;/code&gt;, covering the same ~40 CVE remediations and operational fixes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increased the CIS-CAT Pro Assessor license expiry time to a full year.&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;license_path&lt;/code&gt; parameter to update the CIS-CAT Pro Assessor license without upgrading SCM.&lt;/li&gt;
&lt;li&gt;Added an &lt;code&gt;assessor_scan_timeout&lt;/code&gt; option to control the task timeout for Windows 2022 domain controllers.&lt;/li&gt;
&lt;li&gt;Added a background scan sweeper to detect and cancel scans stuck in the "running" state.&lt;/li&gt;
&lt;li&gt;Fixed a race condition where timed-out PE job status polls could leave scans permanently stuck.&lt;/li&gt;
&lt;li&gt;Updated Gorm.io, Protobuf, multiple Netty codec libraries, react-router, and KeyCloak to address roughly 40 CVEs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the official &lt;a href="https://help.puppet.com/scm/current/Content/UserGuide/SCM/Release_notes/release_notes.htm#SecurityComplianceManagement380" rel="noopener noreferrer"&gt;release notes for complyadm 3.8.0&lt;/a&gt; for the full details.&lt;/p&gt;




&lt;h3&gt;
  
  
  concat 10.0.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/concat" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This targeted release was part of the wave of bumps for the stdlib dependency to allow 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-concat/pull/837" rel="noopener noreferrer"&gt;#837&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  docker 10.4.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-28 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/docker" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Removes the upper version limit on the puppetlabs/apt dependency, fixes &lt;code&gt;compose up&lt;/code&gt; argument ordering, and allows stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not limit puppetlabs/apt requirement &amp;lt; v12 &lt;a href="https://github.com/puppetlabs/puppetlabs-docker/pull/1056" rel="noopener noreferrer"&gt;#1056&lt;/a&gt; (&lt;a href="https://github.com/mpdude" rel="noopener noreferrer"&gt;mpdude&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fix argument order on compose up &lt;a href="https://github.com/puppetlabs/puppetlabs-docker/pull/1037" rel="noopener noreferrer"&gt;#1037&lt;/a&gt; (&lt;a href="https://github.com/deligatedgeek" rel="noopener noreferrer"&gt;deligatedgeek&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-docker/pull/1057" rel="noopener noreferrer"&gt;#1057&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  edgeops 1.1.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/edgeops" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds host key fingerprint verification for Bolt 5.1.0+ targets, along with a batch of NETCONF/SSH hardening fixes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(PE-43703) Verify host key fingerprints supplied as &lt;code&gt;host-key-fingerprint&lt;/code&gt; in the target hash; takes precedence over &lt;code&gt;host-key-check&lt;/code&gt; and requires Bolt 5.1.0+. &lt;a href="https://github.com/puppetlabs/puppetlabs-edgeops/pull/38" rel="noopener noreferrer"&gt;#38&lt;/a&gt; (&lt;a href="https://github.com/owenbeckles" rel="noopener noreferrer"&gt;owenbeckles&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-42584) Correctly handle host key verification parameter &lt;a href="https://github.com/puppetlabs/puppetlabs-edgeops/pull/23" rel="noopener noreferrer"&gt;#23&lt;/a&gt; (&lt;a href="https://github.com/Ziaunys" rel="noopener noreferrer"&gt;Ziaunys&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-43619) Clean up SSH on timeout and always raise instead of returning partial data &lt;a href="https://github.com/puppetlabs/puppetlabs-edgeops/pull/33" rel="noopener noreferrer"&gt;#33&lt;/a&gt; (&lt;a href="https://github.com/Ziaunys" rel="noopener noreferrer"&gt;Ziaunys&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-43614) Add mutex synchronization for RPC message ID allocation &lt;a href="https://github.com/puppetlabs/puppetlabs-edgeops/pull/29" rel="noopener noreferrer"&gt;#29&lt;/a&gt; (&lt;a href="https://github.com/Ziaunys" rel="noopener noreferrer"&gt;Ziaunys&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-43652) Rename netconf_lock task to netconf_check_lock &lt;a href="https://github.com/puppetlabs/puppetlabs-edgeops/pull/35" rel="noopener noreferrer"&gt;#35&lt;/a&gt; (&lt;a href="https://github.com/Ziaunys" rel="noopener noreferrer"&gt;Ziaunys&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  firewall 8.5.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/firewall" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Two releases this month: 8.4.0 added CONNMARK-based policy routing support and several bugfixes, while 8.5.0 allows stdlib 10.x, drops &lt;code&gt;iptables-services&lt;/code&gt; from EL9+ package defaults, and fixes several ipset and chain-detection edge cases.&lt;/p&gt;

&lt;p&gt;Includes monthly releases: 8.5.0 (2026-06-25), 8.4.0 (2026-06-10).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-firewall/pull/1288" rel="noopener noreferrer"&gt;#1288&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(#1254) Remove iptables-services from EL9+ package defaults &lt;a href="https://github.com/puppetlabs/puppetlabs-firewall/pull/1296" rel="noopener noreferrer"&gt;#1296&lt;/a&gt; (&lt;a href="https://github.com/david22swan" rel="noopener noreferrer"&gt;david22swan&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(feat) Add restore_mark, nfmask, ctmask support for CONNMARK-based policy routing &lt;a href="https://github.com/puppetlabs/puppetlabs-firewall/pull/1291" rel="noopener noreferrer"&gt;#1291&lt;/a&gt; (&lt;a href="https://github.com/david22swan" rel="noopener noreferrer"&gt;david22swan&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(bugfix) Fix ipset idempotency: single-element array not in sync with String equivalent &lt;a href="https://github.com/puppetlabs/puppetlabs-firewall/pull/1286" rel="noopener noreferrer"&gt;#1286&lt;/a&gt; (&lt;a href="https://github.com/david22swan" rel="noopener noreferrer"&gt;david22swan&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(bugfix) Fix log_level idempotency when explicitly setting the iptables default value &lt;a href="https://github.com/puppetlabs/puppetlabs-firewall/pull/1284" rel="noopener noreferrer"&gt;#1284&lt;/a&gt; (&lt;a href="https://github.com/david22swan" rel="noopener noreferrer"&gt;david22swan&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  haproxy 8.2.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/haproxy" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Completes the module's Puppet 8 upgrade work, drops Puppet 7 support, and allows stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-haproxy/pull/642" rel="noopener noreferrer"&gt;#642&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(CAT-2373) Remove puppet 7 &lt;a href="https://github.com/puppetlabs/puppetlabs-haproxy/pull/631" rel="noopener noreferrer"&gt;#631&lt;/a&gt; (&lt;a href="https://github.com/gavindidrichsen" rel="noopener noreferrer"&gt;gavindidrichsen&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(CAT-2373)(02) Upgrade module to puppet 8 &lt;a href="https://github.com/puppetlabs/puppetlabs-haproxy/pull/629" rel="noopener noreferrer"&gt;#629&lt;/a&gt; (&lt;a href="https://github.com/gavindidrichsen" rel="noopener noreferrer"&gt;gavindidrichsen&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  inifile 6.4.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/inifile" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A small release to allow stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-inifile/pull/570" rel="noopener noreferrer"&gt;#570&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  java 12.0.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/java" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds CentOS 9 and Debian 13 support, allows stdlib 10.x, and adds support for downloading from a login/password-protected URL.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2376) Puppet Core update &lt;a href="https://github.com/puppetlabs/puppetlabs-java/pull/614" rel="noopener noreferrer"&gt;#614&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(CAT-2152) Add support for CentOS 9 &lt;a href="https://github.com/puppetlabs/puppetlabs-java/pull/606" rel="noopener noreferrer"&gt;#606&lt;/a&gt; (&lt;a href="https://github.com/skyamgarp" rel="noopener noreferrer"&gt;skyamgarp&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add support for Debian 13 (trixie) &lt;a href="https://github.com/puppetlabs/puppetlabs-java/pull/613" rel="noopener noreferrer"&gt;#613&lt;/a&gt; (&lt;a href="https://github.com/mika" rel="noopener noreferrer"&gt;mika&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Feat: Allow downloading from a login/password protected URL &lt;a href="https://github.com/puppetlabs/puppetlabs-java/pull/588" rel="noopener noreferrer"&gt;#588&lt;/a&gt; (&lt;a href="https://github.com/JGodin-C2C" rel="noopener noreferrer"&gt;JGodin-C2C&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-java/pull/626" rel="noopener noreferrer"&gt;#626&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  lvm 4.0.2
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-28 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/lvm" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This lvm release bumps the stdlib dependency to allow 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-lvm/pull/384" rel="noopener noreferrer"&gt;#384&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  motd 8.0.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/motd" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds Bolt 5.0 support, and allows stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2352) Puppet Core update &lt;a href="https://github.com/puppetlabs/puppetlabs-motd/pull/531" rel="noopener noreferrer"&gt;#531&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(CAT-2463) Add bolt 5.0 support &lt;a href="https://github.com/puppetlabs/puppetlabs-motd/pull/535" rel="noopener noreferrer"&gt;#535&lt;/a&gt; (&lt;a href="https://github.com/gavindidrichsen" rel="noopener noreferrer"&gt;gavindidrichsen&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-motd/pull/558" rel="noopener noreferrer"&gt;#558&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  mysql 17.0.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/mysql" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Fixes the RHEL/CentOS Stream 10 version check, and allows stdlib 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2381) Puppet Core update &lt;a href="https://github.com/puppetlabs/puppetlabs-mysql/pull/1688" rel="noopener noreferrer"&gt;#1688&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fix version check for RHEL/CentOS Stream 10 &lt;a href="https://github.com/puppetlabs/puppetlabs-mysql/pull/1686" rel="noopener noreferrer"&gt;#1686&lt;/a&gt; (&lt;a href="https://github.com/kajinamit" rel="noopener noreferrer"&gt;kajinamit&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-mysql/pull/1707" rel="noopener noreferrer"&gt;#1707&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ntp 11.1.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/ntp" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A small release that only bumps the stdlib dependency to allow 10.x.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-ntp/pull/742" rel="noopener noreferrer"&gt;#742&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  peadm 3.38.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-30 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/peadm" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds support for installing and upgrading to Puppet Enterprise 2023.8.10 and 2025.11.0, along with a cloud_database_host parameter for cloud-DB-backed installs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding support for PE 2023.8.10 and 2025.11.0 &lt;a href="https://github.com/puppetlabs/puppetlabs-peadm/pull/673" rel="noopener noreferrer"&gt;#673&lt;/a&gt; (&lt;a href="https://github.com/CharithaDunuwille" rel="noopener noreferrer"&gt;CharithaDunuwille&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-44022) Add cloud_database_host parameter for cloud-DB-backed installs &lt;a href="https://github.com/puppetlabs/puppetlabs-peadm/pull/665" rel="noopener noreferrer"&gt;#665&lt;/a&gt; (&lt;a href="https://github.com/mcdonaldseanp" rel="noopener noreferrer"&gt;mcdonaldseanp&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-44247) Add peadm-path PG-major HA upgrade coverage for replica pe-puppetdb &lt;a href="https://github.com/puppetlabs/puppetlabs-peadm/pull/666" rel="noopener noreferrer"&gt;#666&lt;/a&gt; (&lt;a href="https://github.com/steveax" rel="noopener noreferrer"&gt;steveax&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(PE-44595) Don't emit empty dns_alt_names flag in subplans::install &lt;a href="https://github.com/puppetlabs/puppetlabs-peadm/pull/672" rel="noopener noreferrer"&gt;#672&lt;/a&gt; (&lt;a href="https://github.com/steveax" rel="noopener noreferrer"&gt;steveax&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  postgresql 10.6.2
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-29 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/postgresql" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds basic EL10 support, allows both stdlib and concat to move to their 10.x releases, and fixes a bug where &lt;code&gt;postgresql_conf&lt;/code&gt; resources set to absent were handled incorrectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This release also incorrectly removed Puppet 7 support as a breaking change in a patch release. This has since been rolled back in 10.6.3. Removing Puppet 7 support will happen in a future major release.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add EL10 basic support - align EL10 PGSQL 16 default package version &lt;a href="https://github.com/puppetlabs/puppetlabs-postgresql/pull/1650" rel="noopener noreferrer"&gt;#1650&lt;/a&gt; (&lt;a href="https://github.com/ikonia" rel="noopener noreferrer"&gt;ikonia&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;fix: ignore postgresql_conf resource value when set to absent &lt;a href="https://github.com/puppetlabs/puppetlabs-postgresql/pull/1657" rel="noopener noreferrer"&gt;#1657&lt;/a&gt; (&lt;a href="https://github.com/davidassigbi" rel="noopener noreferrer"&gt;davidassigbi&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;grant creation via hiera through server::grant.pp &lt;a href="https://github.com/puppetlabs/puppetlabs-postgresql/pull/1668" rel="noopener noreferrer"&gt;#1668&lt;/a&gt; (&lt;a href="https://github.com/ikonia" rel="noopener noreferrer"&gt;ikonia&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-postgresql/pull/1681" rel="noopener noreferrer"&gt;#1681&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;puppetlabs/concat: Allow 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-postgresql/pull/1669" rel="noopener noreferrer"&gt;#1669&lt;/a&gt; (&lt;a href="https://github.com/bastelfreak" rel="noopener noreferrer"&gt;bastelfreak&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  sce_linux 2.7.0
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-16 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/sce_linux" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Adds CIS Benchmark support for the RHEL 10 family, fixes an rsyslog configuration file that was unconditionally overwritten, and drops RHEL 7 now that it's end of life.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support for RHEL 10, AlmaLinux 10, Oracle Linux 10, and Rocky Linux 10.&lt;/strong&gt; Enforces the CIS Benchmark for RHEL 10 (v1.0.1, Server Levels 1 and 2) and CIS Benchmark v1.0.0 for AlmaLinux 10, Oracle Linux 10, and Rocky Linux 10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rsyslog.conf file unconditionally overwritten.&lt;/strong&gt; Previously overwritten on every Puppet run even when logging configuration was set to ignore, affecting RHEL 7/8/9 and derivatives. No action required from users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Intrusion Detection Environment (AIDE) utility class.&lt;/strong&gt; Fixed incorrect configuration options generated for AIDE 0.19.x on RHEL 9 that caused &lt;code&gt;aide --init&lt;/code&gt; to fail.&lt;/li&gt;
&lt;li&gt;RHEL &lt;strong&gt;7.&lt;/strong&gt; RHEL 7 is end of life and no longer supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the official &lt;a href="https://help.puppet.com/sce/current/linux/scel_relnotes_270.htm" rel="noopener noreferrer"&gt;release notes for sce_linux 2.7.0&lt;/a&gt; for the full details.&lt;/p&gt;




&lt;h3&gt;
  
  
  security_policy 1.0.0
&lt;/h3&gt;

&lt;p&gt;🌟 &lt;strong&gt;&lt;em&gt;New Module:&lt;/em&gt;&lt;/strong&gt; 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/security_policy" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Brand-new module for managing Windows local security policy. Provides the &lt;code&gt;security_option&lt;/code&gt; and &lt;code&gt;user_right_assignment&lt;/code&gt; resource types (Puppet Resource API), covering all 45 Windows Privilege Rights and System Access settings. Initial release contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;security_option&lt;/code&gt; and &lt;code&gt;user_right_assignment&lt;/code&gt; resource types for managing Windows local security policy settings via &lt;code&gt;secedit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;security_policy&lt;/code&gt; class exposing 45 &lt;code&gt;Optional[Array[String]]&lt;/code&gt; parameters (one per privilege right), matching the layout of the legacy &lt;code&gt;dsc/securitypolicydsc&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PuppetX::Sid&lt;/code&gt; module: a static well-known SID map covering the 19 standard SIDs, with a PowerShell fallback via &lt;code&gt;Pwsh::Manager&lt;/code&gt; for domain and custom accounts.&lt;/li&gt;
&lt;li&gt;YAML-driven setting metadata loader (&lt;code&gt;PuppetX::SecurityPolicy.all_settings&lt;/code&gt;) instead of a hardcoded settings hash.&lt;/li&gt;
&lt;li&gt;Puppet requirement pinned to &amp;gt;= 8.0.0 &amp;lt; 9.0.0.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  stdlib 10.0.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-30 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/stdlib" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Drops Puppet 7 support, adds CentOS 9 support, extends Sensitive value support to more functions, and fixes &lt;code&gt;has_ip_address&lt;/code&gt;/&lt;code&gt;has_ip_network&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(CAT-2395) Puppet Core upgrade - drop support for Puppet 7 &lt;a href="https://github.com/puppetlabs/puppetlabs-stdlib/pull/1457" rel="noopener noreferrer"&gt;#1457&lt;/a&gt; (&lt;a href="https://github.com/LukasAud" rel="noopener noreferrer"&gt;LukasAud&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(CAT-2152) Add support for CentOS 9 &lt;a href="https://github.com/puppetlabs/puppetlabs-stdlib/pull/1442" rel="noopener noreferrer"&gt;#1442&lt;/a&gt; (&lt;a href="https://github.com/skyamgarp" rel="noopener noreferrer"&gt;skyamgarp&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Support &lt;code&gt;Sensitive&lt;/code&gt; values in more functions &lt;a href="https://github.com/puppetlabs/puppetlabs-stdlib/pull/1463" rel="noopener noreferrer"&gt;#1463&lt;/a&gt; (&lt;a href="https://github.com/alexjfisher" rel="noopener noreferrer"&gt;alexjfisher&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Support sensitive values in &lt;code&gt;to_json_pretty&lt;/code&gt; &lt;a href="https://github.com/puppetlabs/puppetlabs-stdlib/pull/1418" rel="noopener noreferrer"&gt;#1418&lt;/a&gt; (&lt;a href="https://github.com/alexjfisher" rel="noopener noreferrer"&gt;alexjfisher&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fix &lt;code&gt;has_ip_address&lt;/code&gt; and &lt;code&gt;has_ip_network&lt;/code&gt; functions &lt;a href="https://github.com/puppetlabs/puppetlabs-stdlib/pull/1448" rel="noopener noreferrer"&gt;#1448&lt;/a&gt; (&lt;a href="https://github.com/alexjfisher" rel="noopener noreferrer"&gt;alexjfisher&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  wsus_client 6.3.1
&lt;/h3&gt;

&lt;p&gt;📅 Latest release: 2026-06-25 (🌐 &lt;a href="https://forge.puppet.com/modules/puppetlabs/wsus_client" rel="noopener noreferrer"&gt;View on the Forge&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;stdlib 10.x now allowed as part of the dependency range bump.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(MODULES-11840) Allow puppetlabs/stdlib 10.x &lt;a href="https://github.com/puppetlabs/puppetlabs-wsus_client/pull/238" rel="noopener noreferrer"&gt;#238&lt;/a&gt; (&lt;a href="https://github.com/imaqsood" rel="noopener noreferrer"&gt;imaqsood&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Until Next Time!
&lt;/h2&gt;

&lt;p&gt;That wraps up the June 2026 roundup. If any of these modules intersect with your environment — especially the Security Compliance Management CVE fixes — the linked Forge pages and release notes are worth a closer look.&lt;/p&gt;

&lt;p&gt;Feedback on the series is always useful, especially if there are module families or release-note patterns that deserve more attention in future editions.&lt;/p&gt;

&lt;p&gt;More updates coming next month when the July 2026 releases land.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 AI Disclosure
&lt;/h2&gt;

&lt;p&gt;This roundup is produced by a mostly-automated pipeline, with some AI sprinkled in for orchestration and enrichment (or 'Combobulating' and 'Finagling'), followed by a human review (that would be me) before publishing.&lt;/p&gt;

&lt;p&gt;The automation is an &lt;a href="https://github.com/jst-cyr/puppetlabs-modules-roundup-writer" rel="noopener noreferrer"&gt;open-source project&lt;/a&gt; with deterministic python scripts to crawl the Forge and determine which &lt;code&gt;puppetlabs&lt;/code&gt; modules were released during a specific month (and catching when a module gets more than one release in a month). By combining a template, automation scripts, and some AI orchestration the content all gets pulled together for a structured markdown document. I then jump in to double-check the content and update any wording that seems repetitive or irrelevant (and sometimes I need to add some extra context that isn't in the changelog notes).&lt;/p&gt;

</description>
      <category>puppet</category>
    </item>
    <item>
      <title>Puppet Core 8.20 adds Ubuntu 26.04 and Security Fixes</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Mon, 06 Jul 2026 12:15:19 +0000</pubDate>
      <link>https://dev.to/puppet/puppet-core-820-adds-ubuntu-2604-and-security-fixes-1d84</link>
      <guid>https://dev.to/puppet/puppet-core-820-adds-ubuntu-2604-and-security-fixes-1d84</guid>
      <description>&lt;p&gt;The latest release of Puppet Core was targeted at addressing reported CVEs and adding agent support for Ubuntu 26.04, along with some other items that might be of interest to you! Here's a quick rundown on the highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu 26.04 (x86_64 and ARM) support added for Puppet agents&lt;/li&gt;
&lt;li&gt;Updated components for OpenSSL, net-imap, and concurrent-Ruby to address 13 CVEs&lt;/li&gt;
&lt;li&gt;Sensitive values are now handled correctly in &lt;code&gt;transactionstore.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On macOS, &lt;code&gt;plist&lt;/code&gt; files are now written atomically to prevent partial or zero-length reads.&lt;/li&gt;
&lt;li&gt;PXP agent metadata now uses uniquely named temporary files to resolve file-rename race conditions that were happening for Windows tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was just my quick summary of the notes, so make sure to read the &lt;a href="https://help.puppet.com/core/current/Content/PuppetCore/PuppetReleaseNotes/release_notes_puppet_x-8-20-0.htm" rel="noopener noreferrer"&gt;full release notes for Puppet Core 8.20&lt;/a&gt; to get all the details before you upgrade!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>puppet</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Puppet Enterprise Introduces Database-Backed CA Storage in 2025.11 release</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Thu, 02 Jul 2026 15:32:28 +0000</pubDate>
      <link>https://dev.to/puppet/puppet-enterprise-introduces-database-backed-ca-storage-in-202511-release-epl</link>
      <guid>https://dev.to/puppet/puppet-enterprise-introduces-database-backed-ca-storage-in-202511-release-epl</guid>
      <description>&lt;p&gt;The latest Puppet Enterprise releases are out and this one has a huge load of improvements, fixes, and security patches included!&lt;/p&gt;

&lt;h2&gt;
  
  
  Puppet Enterprise (PE) 2025.11 released!
&lt;/h2&gt;

&lt;p&gt;The full &lt;a href="https://help.puppet.com/pe/current/topics/release-notes-pe-x-11.htm" rel="noopener noreferrer"&gt;PE 2025.11 release notes&lt;/a&gt; are always the best way to get a full detail on what has changed, but here are some highlights of PE 2025.11!&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate Authority (CA): Database-backed Storage
&lt;/h3&gt;

&lt;p&gt;This new optional feature adds support for storing CA data in a PostgreSQL database instead of the file system. This improves performance and reliability and introduces API-driven capabilities and enhanced backup and recovery handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL 17 Supported
&lt;/h3&gt;

&lt;p&gt;PE-managed installations will automatically upgrade from verson 14 to 17 as part of the upgrade process, or you can update yourself before upgrading to PE 2025.11&lt;/p&gt;

&lt;h3&gt;
  
  
  Infra Assistant Goes GPT-5
&lt;/h3&gt;

&lt;p&gt;GPT-5 series models are now running under the hood of Infra Assistant, improving the quality of responses and the consistency for queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Patching Enhancements
&lt;/h3&gt;

&lt;p&gt;The advanced patching feature now has improvements across a variety of areas&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;puppet_run_concurrency&lt;/code&gt; setting allows you to get better performance out of patch group enrollment&lt;/li&gt;
&lt;li&gt;Improved validation of scheduled and immediate jobs to reduce risk of unintended or skipped executions.&lt;/li&gt;
&lt;li&gt;Cron scheduling has better user experience and improved validation across features.&lt;/li&gt;
&lt;li&gt;New configurable option to enable Puppet to run after patch jobs to refresh &lt;code&gt;pe_patch&lt;/code&gt; facts &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  New Endpoints for Classifier and Activity Service APIs
&lt;/h3&gt;

&lt;p&gt;The Classifier API introduced new &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;add-tags&lt;/code&gt; and &lt;code&gt;remove-tags&lt;/code&gt; endpoints to manage node group tags. The Activity service API now has &lt;code&gt;subscriptions&lt;/code&gt; endpoints to create subscriptions, list subscriptions, or fetch/delete a specific subscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Platform Updates, Resolved Issues, and Security Fixes
&lt;/h3&gt;

&lt;p&gt;The macOS 26 platform is now supported for both ARM and x86_64, while support has been removed for Ubuntu 18.04 and Ubuntu 20.04.&lt;/p&gt;

&lt;p&gt;Nearly 60 CVEs were addressed in this release, along with many resolved issues. You should definitely check out the &lt;a href="https://help.puppet.com/pe/current/topics/release-notes-pe-x-11.htm" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; for the full list of fixes! &lt;/p&gt;

&lt;h2&gt;
  
  
  PE 2023.8.10 Released
&lt;/h2&gt;

&lt;p&gt;Along with the new 2025.11 release, the latest patches for the LTS version PE 2023.8 has also been put out. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent platforms&lt;/strong&gt; were a big change, as macOS 26 was added, along with removing support for Ubuntu 18 and Ubuntu 20.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearly 60 CVEs resolved&lt;/strong&gt; in this release, many by component updates.&lt;/li&gt;
&lt;li&gt;Some additional fixes made in 2025.11 have also been inherited for the Activity service API and LDAP group attributes handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Official Release Notes
&lt;/h2&gt;

&lt;p&gt;Read more in the full release notes on help.puppet.com ⬇️&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.puppet.com/pe/current/topics/release-notes-pe-x-11.htm" rel="noopener noreferrer"&gt;PE 2025.11 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://help.puppet.com/pe/2023.8/topics/release-notes-pe-x-y-10.htm" rel="noopener noreferrer"&gt;PE 2023.8.10 Release Notes&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.puppet.com/downloads/puppet-enterprise" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Download Puppet Enterprise&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>devops</category>
    </item>
    <item>
      <title>SaaS Globalization Strategy: Technical Preparation for Chinese, European, and US Markets</title>
      <dc:creator>sweet</dc:creator>
      <pubDate>Wed, 24 Jun 2026 00:45:00 +0000</pubDate>
      <link>https://dev.to/feidou/saas-globalization-strategy-technical-preparation-for-chinese-european-and-us-markets-4g8k</link>
      <guid>https://dev.to/feidou/saas-globalization-strategy-technical-preparation-for-chinese-european-and-us-markets-4g8k</guid>
      <description>&lt;p&gt;Expanding a SaaS product into global markets requires more than translation — it demands infrastructure changes (edge deployment for low latency), compliance adaptation (GDPR, PIPL, CCPA), payment localization (Alipay, SEPA, Stripe), and cultural UX adjustments. This guide breaks down the technical and business preparation needed for the three largest SaaS markets: the US, Europe, and China. Each section includes actionable code examples, configuration snippets, and deployment patterns you can implement immediately. See the architecture in action at &lt;a href="https://tanstackship.com" rel="noopener noreferrer"&gt;tanstackship.com&lt;/a&gt;, a production SaaS shipping globally on Cloudflare Workers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Globalization Must Be Built, Not Bolted On
&lt;/h2&gt;

&lt;p&gt;The biggest mistake SaaS founders make with globalization is treating it as a phase-two feature. Retrofitting i18n, multi-currency pricing, and regional compliance onto a monolithic codebase typically costs &lt;strong&gt;3-5x more&lt;/strong&gt; than building it in from the start — and often requires a full rewrite of the database schema, routing layer, and deployment pipeline.&lt;/p&gt;

&lt;p&gt;The three largest SaaS markets have fundamentally different requirements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;US Market&lt;/th&gt;
&lt;th&gt;European Market&lt;/th&gt;
&lt;th&gt;Chinese Market&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;Multi (DE, FR, ES, etc.)&lt;/td&gt;
&lt;td&gt;Chinese (Simplified)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data regulation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CCPA/State laws&lt;/td&gt;
&lt;td&gt;GDPR (strictest)&lt;/td&gt;
&lt;td&gt;PIPL + CSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payment methods&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Credit cards, Stripe&lt;/td&gt;
&lt;td&gt;SEPA, PayPal, credit cards&lt;/td&gt;
&lt;td&gt;Alipay, WeChat Pay, UnionPay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS/US-East&lt;/td&gt;
&lt;td&gt;GDPR-compliant hosting&lt;/td&gt;
&lt;td&gt;ICP license, China-hosted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google, GitHub&lt;/td&gt;
&lt;td&gt;EU-specific (eIDAS)&lt;/td&gt;
&lt;td&gt;WeChat, Alipay, phone SMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing display&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$ (tax excl.)&lt;/td&gt;
&lt;td&gt;€ (VAT incl.)&lt;/td&gt;
&lt;td&gt;¥ (tax incl.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Content delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Global CDN&lt;/td&gt;
&lt;td&gt;Edge (300ms latency target)&lt;/td&gt;
&lt;td&gt;China CDN (ICP + DDoS shield)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Social login&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google, Apple, GitHub&lt;/td&gt;
&lt;td&gt;Google, Apple, LinkedIn&lt;/td&gt;
&lt;td&gt;WeChat, Alipay, phone OTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Email deliverability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SendGrid, AWS SES&lt;/td&gt;
&lt;td&gt;SendGrid (DPA signed)&lt;/td&gt;
&lt;td&gt;QQ Mail, Alibaba Direct Mail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Market 1: European Market Preparation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GDPR Compliance Checklist
&lt;/h3&gt;

&lt;p&gt;GDPR applies to any SaaS processing data of EU residents — regardless of where your company is incorporated. Here is the technical implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/gdpr.ts&lt;/span&gt;
&lt;span class="c1"&gt;// GDPR-compliant data handling utilities&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Explicit consent tracking&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ConsentRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;consentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marketing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;functional&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;third_party&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;granted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;ipAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="c1"&gt;// Anonymized&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recordConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;consentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConsentRecord&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;granted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`INSERT INTO consent_records (user_id, consent_type, granted, timestamp, ip_address)
     VALUES (?, ?, ?, unixepoch(), ?)`&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;consentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;granted&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;anonymizeIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getClientIp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Data portability export&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exportUserData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM subscriptions WHERE user_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM invoices WHERE user_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM analytics_events WHERE user_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;exportedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Right to erasure (GDPR Article 17)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteUserData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Anonymize personal data (keep transactional records for legal retention)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`UPDATE users SET
        email = 'deleted-' || id || '@redacted.com',
        name = 'Deleted User',
        avatar_url = NULL,
        deleted_at = unixepoch()
        WHERE id = ?`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE FROM analytics_events WHERE user_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE FROM session_tokens WHERE user_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EU Data Residency
&lt;/h3&gt;

&lt;p&gt;GDPR requires that EU user data stays within the EU or in countries with adequate protection. With Cloudflare Workers, use data localization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.jsonc&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;"name":&lt;/span&gt; &lt;span class="err"&gt;"tanstack-ship",&lt;/span&gt;
  &lt;span class="err"&gt;"compatibility_date":&lt;/span&gt; &lt;span class="err"&gt;"2026-06-01",&lt;/span&gt;
  &lt;span class="err"&gt;"workers_dev":&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;"routes":&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="err"&gt;"pattern":&lt;/span&gt; &lt;span class="err"&gt;"tanstackship.com",&lt;/span&gt; &lt;span class="err"&gt;"zone_id":&lt;/span&gt; &lt;span class="err"&gt;"YOUR_ZONE_ID"&lt;/span&gt; &lt;span class="err"&gt;},&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="err"&gt;"pattern":&lt;/span&gt; &lt;span class="err"&gt;"eu.tanstackship.com",&lt;/span&gt; &lt;span class="err"&gt;"zone_id":&lt;/span&gt; &lt;span class="err"&gt;"YOUR_ZONE_ID",&lt;/span&gt; &lt;span class="err"&gt;"custom_domain":&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;],&lt;/span&gt;
  &lt;span class="err"&gt;"d1_databases":&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="err"&gt;"binding":&lt;/span&gt; &lt;span class="err"&gt;"DB",&lt;/span&gt; &lt;span class="err"&gt;"database_id":&lt;/span&gt; &lt;span class="err"&gt;"GLOBAL_DB",&lt;/span&gt; &lt;span class="err"&gt;"database_name":&lt;/span&gt; &lt;span class="err"&gt;"tanstack-ship"&lt;/span&gt; &lt;span class="err"&gt;},&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="err"&gt;"binding":&lt;/span&gt; &lt;span class="err"&gt;"EU_DB",&lt;/span&gt; &lt;span class="err"&gt;"database_id":&lt;/span&gt; &lt;span class="err"&gt;"EU_DB",&lt;/span&gt; &lt;span class="err"&gt;"database_name":&lt;/span&gt; &lt;span class="err"&gt;"tanstack-ship-eu"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Route EU users to EU D1 instance&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;EU_COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EU_DB&lt;/span&gt; &lt;span class="c1"&gt;// GDPR-compliant D1 instance&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EU Payment: SEPA Direct Debit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// With Stripe, SEPA is handled via Payment Methods&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// €29.99&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eur&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payment_method_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sepa_debit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;card&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key EU-specific pricing considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display prices &lt;strong&gt;including VAT&lt;/strong&gt; (unlike the US)&lt;/li&gt;
&lt;li&gt;Register for VAT MOSS to handle cross-border EU sales&lt;/li&gt;
&lt;li&gt;Issue VAT-compliant invoices (required by law for B2B)&lt;/li&gt;
&lt;li&gt;SEPA transfers can take 1-3 business days — do not grant access until payment clears&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Market 2: Chinese Market Preparation
&lt;/h2&gt;

&lt;p&gt;Entering China is the most technically demanding market expansion. Here is what you need:&lt;/p&gt;

&lt;h3&gt;
  
  
  ICP License (Required for Any Hosted Service)
&lt;/h3&gt;

&lt;p&gt;Any website or app hosted on servers in mainland China or using a Chinese CDN must have an ICP (Internet Content Provider) license. The process takes &lt;strong&gt;4-8 weeks&lt;/strong&gt; and requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registered Chinese company (WFOE or joint venture) or partnership with a Chinese ICP-licensed entity&lt;/li&gt;
&lt;li&gt;ICP filing submission through your cloud provider (Alibaba Cloud, Tencent Cloud, AWS China)&lt;/li&gt;
&lt;li&gt;Domain name already ICP-filed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without an ICP license, your site will be blocked by the Great Firewall of China (GFW).&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure: Bypassing the Great Firewall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/geo-routing.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Route Chinese users to China-hosted instances&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CHINA_REGIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRegionalEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CHINA_REGIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Route to China-hosted instance via Cloudflare China Network&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://tanstack-ship.cn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Global instance&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://tanstackship.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cloudflare China Network&lt;/strong&gt; provides a GFW-compliant CDN layer without requiring your own Chinese servers. All major Chinese cloud providers also offer GPU/CPU instances:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alibaba Cloud (aliyun.com) — largest market share&lt;/li&gt;
&lt;li&gt;Tencent Cloud — second largest, WeChat integration&lt;/li&gt;
&lt;li&gt;AWS China (Beijing/Ningxia) — requires separate account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Chinese Payment Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/payment/china.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Alipay integration via Stripe or direct API&lt;/span&gt;

&lt;span class="c1"&gt;// Option 1: Stripe (simplest, higher fees)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ¥199.00&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payment_method_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alipay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;card&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;payment_method_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alipay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Option 2: Direct Alipay API (lower fees, more setup)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AlipaySdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alipay-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alipaySdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AlipaySdk&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALIPAY_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ALIPAY_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;alipaySdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alipay.trade.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bizContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;out_trade_no&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;199.00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TanStack Ship - Pro Plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;product_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAST_INSTANT_TRADE_PAY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chinese Social Login
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/auth/china.ts&lt;/span&gt;
&lt;span class="c1"&gt;// WeChat OAuth&lt;/span&gt;

&lt;span class="c1"&gt;// Step 1: Redirect user to WeChat authorization&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeChatAuthUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;appid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WECHAT_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;snsapi_login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`https://open.weixin.qq.com/connect/qrconnect?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Exchange code for access token&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeChatToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.weixin.qq.com/sns/oauth2/access_token?`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;appid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WECHAT_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WECHAT_APP_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;openid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;unionid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chinese Content Delivery Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Censored keywords&lt;/strong&gt;: Your content will be scanned. Common SaaS-blocked terms include sensitive political topics, VPN references, and cryptocurrency mentions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WeChat ecosystem&lt;/strong&gt;: WeChat Mini Programs, WeChat Pay, and WeChat Official Accounts are essential for B2C SaaS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email deliverability&lt;/strong&gt;: QQ Mail and 163.com are the dominant email providers — ensure your SPF, DKIM, and DMARC are configured for Chinese domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain blocking&lt;/strong&gt;: Some domains are blocked without clear reason. Register a &lt;code&gt;.cn&lt;/code&gt; domain as backup&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Market 3: US Market Preparation
&lt;/h2&gt;

&lt;p&gt;The US market is the most straightforward but still requires specific attention:&lt;/p&gt;

&lt;h3&gt;
  
  
  Tax Compliance: Sales Tax by State
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/tax/us-sales-tax.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Simplified sales tax calculation (use a service like TaxJar for production)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STATE_TAX_RATES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// California base rate&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// New York state (county/city adds more)&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Texas&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.065&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Washington&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 45+ more states&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateSalesTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;taxAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;STATE_TAX_RATES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;taxAmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Economic nexus: If you sell &amp;gt;$100K or 200+ transactions in a state, you must collect sales tax&lt;/li&gt;
&lt;li&gt;Use a service like TaxJar, Stripe Tax, or Anrok for automated calculation&lt;/li&gt;
&lt;li&gt;Display prices &lt;strong&gt;excluding tax&lt;/strong&gt; (unlike Europe)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  US Privacy Compliance (CCPA)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/ccpa.ts&lt;/span&gt;
&lt;span class="c1"&gt;// CCPA opt-out mechanism&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCcpOptOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`INSERT INTO privacy_opt_outs (user_id, regulation, opted_out_at)
     VALUES (?, 'CCPA', unixepoch())`&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Do not sell my personal information endpoint&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optOutOfSale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleCcpOptOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architectural Pattern: Regional Deployment with Edge Workers
&lt;/h2&gt;

&lt;p&gt;For SaaS products targeting all three markets simultaneously, use a regional deployment architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│                  Cloudflare                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────┐ │
│  │  US Worker   │  │  EU Worker   │  │ CN Edge  │ │
│  │  us-east     │  │  eu-west     │  │ (CF CN)  │ │
│  └──────┬───────┘  └──────┬───────┘  └────┬─────┘ │
│         │                 │               │        │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌────▼─────┐ │
│  │  US D1 DB    │  │  EU D1 DB    │  │ CN D1 DB │ │
│  │  (Virginia)  │  │ (Frankfurt)  │  │ (Beijing) │ │
│  └──────────────┘  └──────────────┘  └──────────┘ │
│         │                 │               │        │
│  ┌──────▼───────┐  ┌──────▼───────┐              │
│  │  US R2       │  │  EU R2       │              │
│  └──────────────┘  └──────────────┘              │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/middleware/geo-router.ts&lt;/span&gt;
&lt;span class="c1"&gt;// Edge middleware that routes to the correct regional deployment&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geoRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CN_DB&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EU_COUNTRIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EU_DB&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;US_DB&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Multi-Currency Pricing Database Schema
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;price_tiers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'US'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'EU'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ROW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'GLOBAL'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;unit_amount&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- In smallest currency unit (cents, fen, etc.)&lt;/span&gt;
  &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'year'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;tax_behavior&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'exclusive'&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tax_behavior&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'inclusive'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'exclusive'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unixepoch&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unixepoch&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_price_tiers_region&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;price_tiers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/pricing/regional.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRegionalPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ROW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT unit_amount, currency, tax_behavior
     FROM price_tiers
     WHERE product_id = ? AND region = ? AND interval = ? AND is_active = 1
     ORDER BY created_at DESC
     LIMIT 1`&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Globalization Readiness Checklist
&lt;/h2&gt;

&lt;p&gt;Before launching in a new market, verify each item:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Content is fully translated (not just UI, but transactional emails, docs, error pages)&lt;/li&gt;
&lt;li&gt;[ ] Date/time/number formatting uses &lt;code&gt;Intl.*&lt;/code&gt; APIs (never hardcoded)&lt;/li&gt;
&lt;li&gt;[ ] Currency symbols and formats match local expectations (€29,99 vs ¥199 vs $29.99)&lt;/li&gt;
&lt;li&gt;[ ] Regional payment methods are integrated and tested with sandbox credentials&lt;/li&gt;
&lt;li&gt;[ ] Tax calculation is automated per region (VAT, sales tax, consumption tax)&lt;/li&gt;
&lt;li&gt;[ ] Data residency requirements are met (GDPR EU, PIPL China, CCPA California)&lt;/li&gt;
&lt;li&gt;[ ] Privacy policy covers all applicable regulations (separate versions per region)&lt;/li&gt;
&lt;li&gt;[ ] CDN/edge routing directs users to the nearest regional deployment&lt;/li&gt;
&lt;li&gt;[ ] Email deliverability is configured for regional providers (QQ, 163, GMX, Gmail)&lt;/li&gt;
&lt;li&gt;[ ] Social login options include region-specific providers (WeChat, LINE, LinkedIn)&lt;/li&gt;
&lt;li&gt;[ ] Terms of Service are localized and legally reviewed per jurisdiction&lt;/li&gt;
&lt;li&gt;[ ] Customer support channels cover regional time zones (email, chat, phone)&lt;/li&gt;
&lt;li&gt;[ ] Pricing is localized to market expectations (not just currency conversion)&lt;/li&gt;
&lt;li&gt;[ ] ICP license is obtained for China (allow 4-8 weeks processing time)&lt;/li&gt;
&lt;li&gt;[ ] Cookie consent mechanism is GDPR-compliant (granular, documented)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Globalizing a SaaS product is a multi-dimensional challenge that touches every part of your stack — from database sharding and edge deployment to payment processing and legal compliance. The key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build globalization into your architecture from day one&lt;/strong&gt; — retrofitting regional routing, multi-currency pricing, and data residency onto a monolithic system is prohibitively expensive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each market has a dominant constraint&lt;/strong&gt;: GDPR (data privacy) in Europe, ICP/GFW (infrastructure) in China, and sales tax nexus (tax complexity) in the US&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments are the hardest part&lt;/strong&gt; — you need at least 3 payment providers (Stripe, Alipay/WeChat, SEPA) to cover all three markets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge deployment is your friend&lt;/strong&gt; — Cloudflare Workers' regional routing makes data residency compliance much simpler than traditional VPS-based architectures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with real users in each market&lt;/strong&gt; — localization bugs (wrong date formats, truncated text in Chinese characters, missing RTL support) only surface with native users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The companies that win at globalization are not the ones with the most features — they are the ones that make every user feel like the product was built for their market.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tanstackship.com/blog/react-internationalization-guide-2026" rel="noopener noreferrer"&gt;React Internationalization Complete Guide (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstackship.com/blog/hreflang-tags-ultimate-guide" rel="noopener noreferrer"&gt;hreflang Tags Ultimate Guide: Implementation for Multilingual SPAs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstackship.com/blog/cloudflare-workers-fullstack-ssr" rel="noopener noreferrer"&gt;Cloudflare Workers Full-Stack SSR: A Complete Guide for SaaS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>saas</category>
      <category>localization</category>
      <category>puppet</category>
      <category>globalization</category>
    </item>
    <item>
      <title>RHEL 10 support now available in Puppet SCE for Linux</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Thu, 18 Jun 2026 16:16:41 +0000</pubDate>
      <link>https://dev.to/puppet/rhel-10-support-now-available-in-puppet-sce-for-linux-4opi</link>
      <guid>https://dev.to/puppet/rhel-10-support-now-available-in-puppet-sce-for-linux-4opi</guid>
      <description>&lt;p&gt;Version 2.7.0 of Security Compliance Enforcement (SCE) for Linux is now &lt;a href="https://forge.puppet.com/modules/puppetlabs/sce_linux/readme" rel="noopener noreferrer"&gt;available for download from the Forge&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Support for the RHEL 10 family
&lt;/h2&gt;

&lt;p&gt;This release adds Red Hat Enterprise Linux (RHEL) 10 CIS benchmarks (v1.0.1, Server Levels 1 and 2). Teams adopting RHEL 10 or a compatible platform can bring those systems into compliance using the same trusted standards already in place across earlier RHEL versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other improvements
&lt;/h2&gt;

&lt;p&gt;Some other issues were also addressed, including logging issues with the rsyslog configuration file and intrusion detection on RHEL 9.&lt;/p&gt;

&lt;p&gt;For all the details, make sure to read the &lt;a href="https://help.puppet.com/sce/current/linux/scel_relnotes_270.htm" rel="noopener noreferrer"&gt;full release notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forge.puppet.com/modules/puppetlabs/sce_linux/readme" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;SCE Module on Puppet Forge&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Security Compliance Management 3.8.0 Is Now Available</title>
      <dc:creator>Jason St-Cyr</dc:creator>
      <pubDate>Tue, 16 Jun 2026 13:31:38 +0000</pubDate>
      <link>https://dev.to/puppet/security-compliance-management-380-is-now-available-1o26</link>
      <guid>https://dev.to/puppet/security-compliance-management-380-is-now-available-1o26</guid>
      <description>&lt;p&gt;Security Compliance Management (SCM) 3.8.0 is here, with updates focused on keeping compliance scans running reliably with less manual intervention and &lt;strong&gt;an important license update&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This release introduces automatic cleanup for stuck scans, improved control over scan behavior with configurable timeouts, extended CIS-CAT® Pro Assessor license support, and updated benchmark content. It also includes important security fixes across core components.&lt;/p&gt;

&lt;p&gt;⚠️ We recommend upgrading to SCM 3.8.0 before &lt;strong&gt;June 21, 2026&lt;/strong&gt; to avoid disruption, as the CIS-CAT Pro Assessor license included in SCM 3.7.1 expires on that date.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s changing in SCM 3.8.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CIS-CAT Pro Assessor licensing and version
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SCM 3.8.0 now contains CIS-CAT Pro Assessor v4.63.0&lt;/li&gt;
&lt;li&gt;The bundled &lt;strong&gt;CIS-CAT® Pro Assessor license&lt;/strong&gt; is now valid for &lt;strong&gt;one year&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;The license shipped with SCM 3.8.0 is valid until &lt;strong&gt;June 2027&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The license included in &lt;strong&gt;SCM 3.7.1 expires on June 21, 2026&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can now also update the license without upgrading SCM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;license_path&lt;/code&gt; parameter

&lt;ul&gt;
&lt;li&gt;Allows updating the CIS-CAT Pro Assessor license independently
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://help.puppet.com/scm/current/Content/UserGuide/SCM/update_assessor_license.htm" rel="noopener noreferrer"&gt;https://help.puppet.com/scm/current/Content/UserGuide/SCM/update_assessor_license.htm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Scan management, configuration, and reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Added a &lt;strong&gt;background scan sweeper&lt;/strong&gt; to detect and cancel scans stuck in a "running" state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed a race condition where timed-out Puppet Enterprise job status polls could leave scans permanently stuck&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New &lt;code&gt;assessor_scan_timeout&lt;/code&gt; option controls task timeout for &lt;strong&gt;Windows Server 2022 domain controllers&lt;/strong&gt; (Note: this isn't set by default)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increased default &lt;strong&gt;Max GraphQL requests limit&lt;/strong&gt; to &lt;strong&gt;300 requests&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Benchmark coverage updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;New benchmarks added for Amazon Linux 2023 STIG, Microsoft Windows 11 STIG, Oracle Linux 9 STIG, RHEL 10 STIG, and SUSE 16&lt;/li&gt;
&lt;li&gt;Updated benchmarks for: Amazon Linux 2, macOS, Debian, Windows, and Ubuntu (see the release notes for specific benchmark updates)&lt;/li&gt;
&lt;li&gt;Removed benchmarks for:

&lt;ul&gt;
&lt;li&gt;Azure Compute Windows Server 2019 v1.0.1
&lt;/li&gt;
&lt;li&gt;Azure Compute Windows Server 2022 v1.0.0
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Security fixes
&lt;/h2&gt;

&lt;p&gt;This release includes updates to address 40 vulnerabilities across several components. The following components were updated to address the vulnerabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gorm.io&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;netty-codec&lt;/li&gt;
&lt;li&gt;netty-codec-http&lt;/li&gt;
&lt;li&gt;netty-codec-http2&lt;/li&gt;
&lt;li&gt;netty-codec-haproxy&lt;/li&gt;
&lt;li&gt;netty-handler&lt;/li&gt;
&lt;li&gt;Protobuf&lt;/li&gt;
&lt;li&gt;react-router&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refer to the full release notes for the complete list of CVEs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Upgrade guidance
&lt;/h2&gt;

&lt;p&gt;To avoid scan interruptions, upgrade to &lt;strong&gt;SCM 3.8.0 before June 21, 2026&lt;/strong&gt;. This ensures continued use of the CIS-CAT Pro Assessor, access to updated benchmark content, improved security posture, and improvements in scan processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full release notes:
&lt;a href="https://help.puppet.com/scm/current/Content/UserGuide/SCM/Release_notes/release_notes.htm#SecurityComplianceManagement380" rel="noopener noreferrer"&gt;https://help.puppet.com/scm/current/Content/UserGuide/SCM/Release_notes/release_notes.htm#SecurityComplianceManagement380&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have questions or need assistance upgrading, reach out to Puppet Support.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 AI Disclosure
&lt;/h2&gt;

&lt;p&gt;This article was written and reviewed by the author, with the help of AI to assist in pulling together the details from multiple sources and general brand voice alignment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How did I do that?&lt;/strong&gt; For this particular article, I provided Microsoft 365 Copilot with the original release notes, my previous release announcement for 3.7.0, our company brand voice guidelines, and the official product release announcement that went out to customers. The LLM can then pull together the list of things that were updated and create a skeleton of an article. I then rewrite the content as needed to meet with my own tone of voice and get rid of the over-list-based approach that LLMs often take. It's also important to actually check back against the original release notes because sometimes the LLM will change certain words or remove words that change the meaning of what was in the release. I hope this helps if you are also writing with LLMs!&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>security</category>
      <category>devops</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Selenium vs Puppeteer vs Playwright: Complete Guide for Web Scraping, Testing &amp; AI Automation</title>
      <dc:creator>IPFoxy</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:23:25 +0000</pubDate>
      <link>https://dev.to/ipfoxy/selenium-vs-puppeteer-vs-playwright-complete-guide-for-web-scraping-testing-ai-automation-2619</link>
      <guid>https://dev.to/ipfoxy/selenium-vs-puppeteer-vs-playwright-complete-guide-for-web-scraping-testing-ai-automation-2619</guid>
      <description>&lt;p&gt;In 2026, browser automation has evolved far beyond traditional QA testing and web scraping. With the rise of LLM-powered AI Agents, automation frameworks have become the foundation for autonomous web browsing, task execution, and large-scale AI workflows.&lt;/p&gt;

&lt;p&gt;This guide compares Selenium, Puppeteer, and Playwright across architecture, performance, scalability, AI automation compatibility, and proxy integration.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  I. Introduction to the Three Automation Frameworks
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
&lt;strong&gt;1. Selenium: The Most Established Browser Automation Framework&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Selenium was introduced in 2004 and remains the industry standard for browser automation. With Selenium 5.0, the framework has undergone a major architectural upgrade.&lt;/p&gt;

&lt;p&gt;Key Features:&lt;br&gt;
• Supports Chrome, Firefox, Edge, Safari, and other major browsers.&lt;br&gt;
• Supports Java, Python, C#, JavaScript, and more.&lt;br&gt;
• Large ecosystem and strong enterprise adoption.&lt;/p&gt;

&lt;p&gt;Best For:&lt;br&gt;
Automated testing, enterprise workflows, and projects requiring broad browser compatibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Playwright&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Playwright was open-sourced by Microsoft in 2020 and has become the fastest-growing browser automation framework.&lt;/p&gt;

&lt;p&gt;Key Features:&lt;br&gt;
• Native support for Chromium, Firefox, and WebKit.&lt;br&gt;
• Built-in Auto-Wait mechanism.&lt;br&gt;
• Browser Context support for large-scale concurrency.&lt;/p&gt;

&lt;p&gt;Best For:&lt;br&gt;
Web scraping, AI Agent automation, eCommerce data collection, social media automation, and large-scale browser operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Puppeteer: Google's Native Chrome Automation Tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Released by the Chrome team in 2017, Puppeteer popularized the Chrome DevTools Protocol (CDP).&lt;/p&gt;

&lt;p&gt;Key Features:&lt;br&gt;
• Simple and intuitive API.&lt;br&gt;
• Fast execution speed.&lt;br&gt;
• Popular in web scraping and browser automation.&lt;/p&gt;

&lt;p&gt;Best For:&lt;br&gt;
Chrome/Chromium automation, web scraping, screenshots, and PDF generation.&lt;/p&gt;
&lt;h2&gt;
  
  
  II. Real-World Performance Comparison
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Startup Speed&lt;/strong&gt;&lt;br&gt;
In raw tests launching a headless browser, Puppeteer and Playwright achieve cold startup speeds of 100-300 milliseconds because they connect directly to the underlying binary protocols via WebSockets. Selenium, however, typically takes 1-2 seconds due to the need to initialize the corresponding Driver process and handle layered HTTP handshakes. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Concurrency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Selenium launches a new browser process for each isolated environment, resulting in significant memory usage.&lt;/p&gt;

&lt;p&gt;Playwright uses BrowserContext, allowing dozens of fully isolated environments to run within a single browser process while reducing resource consumption by over 70%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Stability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern React and Vue applications render content asynchronously. Selenium often requires manual waits, while Playwright automatically performs Actionability Checks before interacting with elements, improving reliability significantly.&lt;br&gt;
**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compatibility**
If your enterprise requires testing specific rendering issues on Safari within a physical Mac environment, or if your development team relies strictly on Java/C#, Selenium’s multi-language support and deep-rooted history make it irreplaceable. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;5. AI Agent Compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In 2026, AI Agent + Browser Automation has become one of the hottest directions. Projects like Claude Code, OpenAI Agent, and OpenManus all prioritize Playwright by default for three key reasons: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native MCP Protocol Support: Playwright can output the page’s Accessibility Tree directly to the LLM, eliminating the need to parse raw DOM text. &lt;/li&gt;
&lt;li&gt;Highly Efficient Token Usage: Compared to traditional DOM serialization, Playwright's optimized approach reduces token consumption for long sessions by 3 to 4 times. &lt;/li&gt;
&lt;li&gt;Execution Stability: Playwright’s auto-wait and auto-retry mechanisms drastically lower the failure rates of complex agent actions. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  III. Proxy Integration and Anti-Detection Capabilities
&lt;/h2&gt;

&lt;p&gt;For modern web scraping, accessing a page is only the beginning. Websites commonly evaluate:&lt;/p&gt;

&lt;p&gt;• IP Reputation&lt;br&gt;
• Request Frequency&lt;br&gt;
• Browser Fingerprints&lt;br&gt;
• Geographic Consistency&lt;/p&gt;

&lt;p&gt;Residential Proxies provide a more authentic browsing environment than datacenter IPs and help reduce blocking risks.&lt;br&gt;
IPFoxy offers residential proxies covering 200+ countries, static ISP proxies, mobile 4G/5G proxies, and supports both HTTP(S) and SOCKS5 protocols. It integrates seamlessly with Selenium, Puppeteer, and Playwright to significantly lower blocking risks for long-running scraper projects. &lt;/p&gt;

&lt;p&gt;Here is how to configure an IPFoxy proxy across the three frameworks:&lt;br&gt;
Selenium Proxy Configuration (Python)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.chrome.options&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;

&lt;span class="n"&gt;chrome_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Configure IPFoxy proxy address (with user/pass authentication)
# Static ISP or rotating residential proxies are recommended for the lowest fraud scores
&lt;/span&gt;&lt;span class="n"&gt;proxy_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://username:password@proxy.ipfoxy.com:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;chrome_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--proxy-server=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;proxy_server&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Launch driver
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chrome_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ipinfo.io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Verify the IP has switched to a clean residential IP from IPFoxy
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puppeteer Proxy Configuration (JavaScript)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://user:pass@ipfoxy-proxy:port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`--proxy-server=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright Proxy Configuration (Python)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://ipfoxy-proxy:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IV. Which Framework Should You Choose in 2026?
&lt;/h2&gt;

&lt;p&gt;Your technical choice should align directly with your business pain points and engineering stack: &lt;br&gt;
&lt;strong&gt;Choose Playwright if:&lt;/strong&gt;&lt;br&gt;
• You are building a new web scraping, data collection, or AI Agent project.&lt;br&gt;
• You need high concurrency and efficient resource usage.&lt;br&gt;
• You work primarily with Python or TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Puppeteer if:&lt;/strong&gt;&lt;br&gt;
• You are a Node.js-focused developer.&lt;br&gt;
• Your project only targets Chrome/Chromium.&lt;br&gt;
• You require direct access to advanced CDP capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Selenium if:&lt;/strong&gt;&lt;br&gt;
• Your organization relies heavily on Java or C#.&lt;br&gt;
• You already have large-scale Selenium Grid infrastructure.&lt;br&gt;
• Cross-browser testing is a top priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  V. Conclusion
&lt;/h2&gt;

&lt;p&gt;Selenium, Puppeteer, and Playwright are all excellent browser automation frameworks, but they serve different needs.&lt;/p&gt;

&lt;p&gt;For web scraping, AI Agents, multi-account automation, and large-scale data collection, Playwright is often the most future-proof choice. Combined with high-quality residential proxies, it can significantly improve scraping success rates and long-term stability.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>selenium</category>
      <category>puppet</category>
      <category>playwright</category>
    </item>
  </channel>
</rss>
